Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 86 additions & 2 deletions scripts/patch-linux-window-ui.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const {
applyLinuxI18nGatePatch,
applyLinuxProfileSettingsMenuPatch,
applyLinuxSafeMonospaceFontStackPatch,
applyLinuxThreadSidePanelNativeTooltipPatch,
applyLinuxTooltipWindowControlsCollisionPatch,
applyLinuxWindowControlsSafeAreaPatch,
} = require("./patches/webview-assets.js");
const { patchAssetFiles } = require("./patches/shared.js");

Expand Down Expand Up @@ -587,6 +590,9 @@ test("default core patch descriptors are grouped and unique", () => {
"opaque-window-default-general-settings",
"opaque-window-default-webview-index",
"opaque-window-default-resolved-theme",
"linux-window-controls-safe-area",
"linux-tooltip-window-controls-collision",
"linux-thread-side-panel-native-tooltip",
"linux-fast-mode-model-guard",
"linux-safe-monospace-font-stack",
"subagent-nickname-metadata-shape",
Expand Down Expand Up @@ -1201,7 +1207,7 @@ test("uses the frameless native Codex titlebar for primary Linux windows", () =>
].join("");
const patched = applyPatchTwice(applyLinuxNativeTitlebarPatch, source);

assert.match(patched, /n===`linux`\?\{titleBarStyle:`hidden`,titleBarOverlay:\{color:a\.nativeTheme\.shouldUseDarkColors\?`#111111`:o2,symbolColor:a\.nativeTheme\.shouldUseDarkColors\?v2:_2,height:Math\.round\(g2\*r\)\}\}/);
assert.match(patched, /n===`linux`\?\{titleBarStyle:`hidden`,titleBarOverlay:\{color:a\.nativeTheme\.shouldUseDarkColors\?`#111111`:o2,symbolColor:a\.nativeTheme\.shouldUseDarkColors\?v2:_2,height:Math\.round\(30\*r\)\}\}/);
assert.doesNotMatch(patched, /n===`win32`\?\{titleBarStyle:`hidden`,titleBarOverlay:b2\(r\)\}:\{titleBarStyle:`default`\}/);
assert.doesNotMatch(patched, /n===`win32`\|\|n===`linux`\?\{titleBarStyle:`hidden`,titleBarOverlay:b2\(r\)\}/);
});
Expand All @@ -1222,12 +1228,90 @@ test("updates the Linux native titlebar overlay when nativeTheme changes", () =>
);
assert.match(
patched,
/e\.setTitleBarOverlay\(process\.platform===`linux`\?\{color:a\.nativeTheme\.shouldUseDarkColors\?`#111111`:o2,symbolColor:a\.nativeTheme\.shouldUseDarkColors\?v2:_2,height:Math\.round\(g2\*this\.windowZooms\.get\(e\.id\)\)\}:b2\(this\.windowZooms\.get\(e\.id\)\)\)/,
/e\.setTitleBarOverlay\(process\.platform===`linux`\?\{color:a\.nativeTheme\.shouldUseDarkColors\?`#111111`:o2,symbolColor:a\.nativeTheme\.shouldUseDarkColors\?v2:_2,height:Math\.round\(30\*this\.windowZooms\.get\(e\.id\)\)\}:b2\(this\.windowZooms\.get\(e\.id\)\)\)/,
);
assert.doesNotMatch(patched, /webContents\.executeJavaScript\(/);
assert.doesNotMatch(patched, /data-codex-window-type/);
});

test("adds a right-side safe area for Linux window controls in application menu chrome", () => {
const source = [
"var l=Object.freeze({default:Object.freeze({left:0,right:0}),mac:Object.freeze({legacy:Object.freeze({left:66+c,right:0}),modern:Object.freeze({left:76+c,right:0})}),applicationMenu:Object.freeze({left:0,right:0})});",
"var m=Object.freeze({applicationMenu:Object.freeze({left:0,right:0})});",
].join("");

const patched = applyPatchTwice(applyLinuxWindowControlsSafeAreaPatch, source);

assert.equal(
(patched.match(/applicationMenu:Object\.freeze\(\{left:0,right:138\}\)/g) ?? []).length,
2,
);
assert.doesNotMatch(
patched,
/applicationMenu:Object\.freeze\(\{left:0,right:0\}\)/,
);
});

test("patches remaining Linux window controls safe areas when another copy is already patched", () => {
const source = [
"var l=Object.freeze({applicationMenu:Object.freeze({left:0,right:138})});",
"var m=Object.freeze({applicationMenu:Object.freeze({left:0,right:0})});",
].join("");

const patched = applyPatchTwice(applyLinuxWindowControlsSafeAreaPatch, source);

assert.equal(
(patched.match(/applicationMenu:Object\.freeze\(\{left:0,right:138\}\)/g) ?? []).length,
2,
);
assert.doesNotMatch(
patched,
/applicationMenu:Object\.freeze\(\{left:0,right:0\}\)/,
);
});

test("keeps tooltips out of the Linux window controls titlebar area", () => {
const middleware =
"middleware:[a({mainAxis:C,crossAxis:t}),c({padding:8}),l({padding:8}),u({padding:8,apply({availableWidth:e,availableHeight:t,elements:n,rects:r}){n.floating.style.setProperty(`--radix-tooltip-trigger-width`,`1px`)}})]";
const source = `${middleware};${middleware}`;

const patched = applyPatchTwice(applyLinuxTooltipWindowControlsCollisionPatch, source);

assert.equal(
(patched.match(/padding:\{top:44,right:8,bottom:8,left:8\}/g) ?? []).length,
6,
);
assert.doesNotMatch(patched, /[,(]\{padding:8\}/);
});

test("patches remaining tooltip collision middleware when another copy is already patched", () => {
const patchedMiddleware =
"middleware:[a({mainAxis:C,crossAxis:t}),c({padding:{top:44,right:8,bottom:8,left:8}}),l({padding:{top:44,right:8,bottom:8,left:8}}),u({padding:{top:44,right:8,bottom:8,left:8},apply({availableWidth:e,availableHeight:t,elements:n,rects:r}){n.floating.style.setProperty(`--radix-tooltip-trigger-width`,`1px`)}})]";
const defaultMiddleware =
"middleware:[a({mainAxis:C,crossAxis:t}),c({padding:8}),l({padding:8}),u({padding:8,apply({availableWidth:e,availableHeight:t,elements:n,rects:r}){n.floating.style.setProperty(`--radix-tooltip-trigger-width`,`1px`)}})]";
const source = `${patchedMiddleware};${defaultMiddleware}`;

const patched = applyPatchTwice(applyLinuxTooltipWindowControlsCollisionPatch, source);

assert.equal(
(patched.match(/padding:\{top:44,right:8,bottom:8,left:8\}/g) ?? []).length,
6,
);
assert.doesNotMatch(patched, /[,(]\{padding:8\}/);
});

test("removes native title tooltip from the thread side panel toolbar action", () => {
const toolbar =
"function dt(e){let t=(0,X.c)(11),{children:n,disabled:r,label:i,onClick:a,color:o,pressed:s,shortcut:c}=e,l=r===void 0?!1:r,u=o===`outline`?s?`outlineActive`:`outline`:s?`secondary`:`ghost`,d;t[0]!==n||t[1]!==l||t[2]!==i||t[3]!==a||t[4]!==s||t[5]!==u?(d=(0,q.jsx)(R,{size:`toolbar`,color:u,\"aria-label\":i,\"aria-pressed\":s,disabled:l,title:i,onClick:a,uniform:!0,children:n}),t[0]=n,t[1]=l,t[2]=i,t[3]=a,t[4]=s,t[5]=u,t[6]=d):d=t[6];let f;return t[7]!==i||t[8]!==c||t[9]!==d?(f=(0,q.jsx)(L,{tooltipContent:i,shortcut:c,delayOpen:!0,children:d}),t[7]=i,t[8]=c,t[9]=d,t[10]=f):f=t[10],f}var Rt=j({toggleSidePanel:{id:`thread.sidePanel.toggle`,defaultMessage:`Toggle side panel`,description:`Toggles the thread side panel in a local or new thread`}});";
const source = `${toolbar}${toolbar}`;

const patched = applyPatchTwice(applyLinuxThreadSidePanelNativeTooltipPatch, source);

assert.match(patched, /"aria-label":i/);
assert.match(patched, /tooltipContent:i/);
assert.doesNotMatch(patched, /title:i/);
});

test("adds Linux menu hiding next to Windows removeMenu calls", () => {
const source = "process.platform===`win32`&&k.removeMenu(),";
const patched = applyPatchTwice(applyLinuxMenuPatch, source);
Expand Down
33 changes: 33 additions & 0 deletions scripts/patches/core/all-linux/webview/theme-and-sunset/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
const {
applyLinuxAppSunsetPatch,
applyLinuxOpaqueWindowsDefaultPatch,
applyLinuxThreadSidePanelNativeTooltipPatch,
applyLinuxTooltipWindowControlsCollisionPatch,
applyLinuxWindowControlsSafeAreaPatch,
} = require("../../../../webview-assets.js");

module.exports = [
Expand Down Expand Up @@ -46,4 +49,34 @@ module.exports = [
skipDescription: "translucent sidebar default patch",
apply: applyLinuxOpaqueWindowsDefaultPatch,
},
{
id: "linux-window-controls-safe-area",
phase: "webview-asset",
order: 1040,
ciPolicy: "optional",
pattern: /^use-window-controls-safe-area-.*\.js$/,
missingDescription: "window controls safe-area bundle",
skipDescription: "Linux window controls safe-area patch",
apply: applyLinuxWindowControlsSafeAreaPatch,
},
{
id: "linux-tooltip-window-controls-collision",
phase: "webview-asset",
order: 1050,
ciPolicy: "optional",
pattern: /^tooltip-.*\.js$/,
missingDescription: "tooltip bundle",
skipDescription: "Linux tooltip titlebar collision patch",
apply: applyLinuxTooltipWindowControlsCollisionPatch,
},
{
id: "linux-thread-side-panel-native-tooltip",
phase: "webview-asset",
order: 1060,
ciPolicy: "optional",
pattern: /^thread-app-shell-chrome-.*\.js$/,
missingDescription: "thread app shell chrome bundle",
skipDescription: "Linux thread side panel native tooltip patch",
apply: applyLinuxThreadSidePanelNativeTooltipPatch,
},
];
8 changes: 5 additions & 3 deletions scripts/patches/main-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const {
requireName,
} = require("./shared.js");

const LINUX_TITLEBAR_OVERLAY_HEIGHT = 30;

// Main-process patches adapt Electron shell behavior: windows, tray, menu,
// single-instance handling, file manager integration, and packaged runtime glue.
function applyLinuxFileManagerPatch(currentSource) {
Expand Down Expand Up @@ -92,7 +94,7 @@ function applyLinuxWindowOptionsPatch(currentSource, iconAsset) {

function applyLinuxNativeTitlebarPatch(currentSource) {
const patchedPrimaryTitlebarRegex =
/===`linux`\?\{titleBarStyle:`hidden`,titleBarOverlay:\{color:([A-Za-z_$][\w$]*)\.nativeTheme\.shouldUseDarkColors\?([A-Za-z_$][\w$]*):([A-Za-z_$][\w$]*),symbolColor:\1\.nativeTheme\.shouldUseDarkColors\?([A-Za-z_$][\w$]*):([A-Za-z_$][\w$]*),height:Math\.round\(([A-Za-z_$][\w$]*)\*[A-Za-z_$][\w$]*\)\}\}/;
/===`linux`\?\{titleBarStyle:`hidden`,titleBarOverlay:\{color:([A-Za-z_$][\w$]*)\.nativeTheme\.shouldUseDarkColors\?([A-Za-z_$][\w$]*):([A-Za-z_$][\w$]*),symbolColor:\1\.nativeTheme\.shouldUseDarkColors\?([A-Za-z_$][\w$]*):([A-Za-z_$][\w$]*),height:Math\.round\(((?:[A-Za-z_$][\w$]*|\d+(?:\.\d+)?)?)\*[A-Za-z_$][\w$]*\)\}\}/;
const alreadyPatchedTitlebarMatch = currentSource.match(patchedPrimaryTitlebarRegex);

const primaryTitlebarRegex =
Expand Down Expand Up @@ -129,7 +131,7 @@ function applyLinuxNativeTitlebarPatch(currentSource) {
[, electronAlias, lightSymbolAlias, darkSymbolAlias, overlayHeightAlias] = overlayHelperMatch;
[, , , darkBackgroundAlias, lightBackgroundAlias] = linuxBackgroundMatch;
const replacement =
`case\`primary\`:return ${platformAlias}===\`darwin\`?${opaqueWindowsAlias}?{titleBarStyle:\`hiddenInset\`,trafficLightPosition:${trafficLightAlias}(${zoomAlias})}:{vibrancy:\`menu\`,titleBarStyle:\`hiddenInset\`,trafficLightPosition:${trafficLightAlias}(${zoomAlias})}:${platformAlias}===\`win32\`?{titleBarStyle:\`hidden\`,titleBarOverlay:${overlayHelperAlias}(${zoomAlias})}:${platformAlias}===\`linux\`?{titleBarStyle:\`hidden\`,titleBarOverlay:{color:${electronAlias}.nativeTheme.shouldUseDarkColors?\`#111111\`:${lightBackgroundAlias},symbolColor:${electronAlias}.nativeTheme.shouldUseDarkColors?${lightSymbolAlias}:${darkSymbolAlias},height:Math.round(${overlayHeightAlias}*${zoomAlias})}}:{titleBarStyle:\`default\`};`;
`case\`primary\`:return ${platformAlias}===\`darwin\`?${opaqueWindowsAlias}?{titleBarStyle:\`hiddenInset\`,trafficLightPosition:${trafficLightAlias}(${zoomAlias})}:{vibrancy:\`menu\`,titleBarStyle:\`hiddenInset\`,trafficLightPosition:${trafficLightAlias}(${zoomAlias})}:${platformAlias}===\`win32\`?{titleBarStyle:\`hidden\`,titleBarOverlay:${overlayHelperAlias}(${zoomAlias})}:${platformAlias}===\`linux\`?{titleBarStyle:\`hidden\`,titleBarOverlay:{color:${electronAlias}.nativeTheme.shouldUseDarkColors?\`#111111\`:${lightBackgroundAlias},symbolColor:${electronAlias}.nativeTheme.shouldUseDarkColors?${lightSymbolAlias}:${darkSymbolAlias},height:Math.round(${LINUX_TITLEBAR_OVERLAY_HEIGHT}*${zoomAlias})}}:{titleBarStyle:\`default\`};`;

primaryTitlebarRegex.lastIndex = 0;
patchedSource = patchedSource.replace(primaryTitlebarRegex, replacement);
Expand Down Expand Up @@ -181,7 +183,7 @@ function applyLinuxNativeTitlebarPatch(currentSource) {

const [, windowAlias, windowTypeAlias, updateAlias, windowsOverlayHelperAlias] = overlaySyncMatch;
const linuxOverlay =
`{color:${electronAlias}.nativeTheme.shouldUseDarkColors?\`#111111\`:${lightBackgroundAlias},symbolColor:${electronAlias}.nativeTheme.shouldUseDarkColors?${lightSymbolAlias}:${darkSymbolAlias},height:Math.round(${overlayHeightAlias}*this.windowZooms.get(${windowAlias}.id))}`;
`{color:${electronAlias}.nativeTheme.shouldUseDarkColors?\`#111111\`:${lightBackgroundAlias},symbolColor:${electronAlias}.nativeTheme.shouldUseDarkColors?${lightSymbolAlias}:${darkSymbolAlias},height:Math.round(${LINUX_TITLEBAR_OVERLAY_HEIGHT}*this.windowZooms.get(${windowAlias}.id))}`;
const overlaySyncReplacement =
`installWindowsTitleBarOverlaySync(${windowAlias},${windowTypeAlias}){if((process.platform!==\`win32\`&&process.platform!==\`linux\`)||${windowTypeAlias}!==\`primary\`)return;let ${updateAlias}=()=>{${windowAlias}.isDestroyed()||${windowAlias}.setTitleBarOverlay(process.platform===\`linux\`?${linuxOverlay}:${windowsOverlayHelperAlias}(this.windowZooms.get(${windowAlias}.id)))};return ${electronAlias}.nativeTheme.on(\`updated\`,${updateAlias}),${updateAlias}(),()=>{${electronAlias}.nativeTheme.off(\`updated\`,${updateAlias})}}`;
const replacedSource = patchedSource.replace(overlaySyncRegex, overlaySyncReplacement);
Expand Down
73 changes: 73 additions & 0 deletions scripts/patches/webview-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const path = require("node:path");
// They stay fail-soft because upstream chunk names and minified symbols drift.
const LINUX_SAFE_MONOSPACE_FONT_STACK =
"\"Noto Sans Mono\", \"DejaVu Sans Mono\", \"Liberation Mono\", \"Ubuntu Mono\", ui-monospace, \"SFMono-Regular\", \"SF Mono\", Menlo, Consolas, monospace";
const LINUX_TOOLTIP_COLLISION_PADDING_TOP = 44;
const LINUX_WINDOW_CONTROLS_SAFE_AREA_RIGHT = 138;

function applyLinuxSafeMonospaceFontStackPatch(currentSource) {
const safeLinuxMonoFontPattern =
Expand Down Expand Up @@ -192,6 +194,74 @@ function applyLinuxOpaqueWindowsDefaultPatch(currentSource) {
return patchedSource;
}

function applyLinuxWindowControlsSafeAreaPatch(currentSource) {
const currentInset = `applicationMenu:Object.freeze({left:0,right:${LINUX_WINDOW_CONTROLS_SAFE_AREA_RIGHT}})`;
const defaultInset = "applicationMenu:Object.freeze({left:0,right:0})";
if (currentSource.includes(defaultInset)) {
return currentSource.split(defaultInset).join(currentInset);
}

if (currentSource.includes(currentInset)) {
return currentSource;
}

if (currentSource.includes("applicationMenu:Object.freeze({left:0,right:")) {
console.warn(
"WARN: Could not find Linux window controls safe-area insertion point — skipping safe-area patch",
);
}

return currentSource;
}

function applyLinuxTooltipWindowControlsCollisionPatch(currentSource) {
const currentPadding = `padding:{top:${LINUX_TOOLTIP_COLLISION_PADDING_TOP},right:8,bottom:8,left:8}`;
const defaultMiddleware = "middleware:[a({mainAxis:C,crossAxis:t}),c({padding:8}),l({padding:8}),u({padding:8,apply({availableWidth:e,availableHeight:t,elements:n,rects:r})";
const patchedMiddleware =
`middleware:[a({mainAxis:C,crossAxis:t}),c({${currentPadding}}),l({${currentPadding}}),u({${currentPadding},apply({availableWidth:e,availableHeight:t,elements:n,rects:r})`;

if (currentSource.includes(defaultMiddleware)) {
return currentSource.split(defaultMiddleware).join(patchedMiddleware);
}

if (currentSource.includes(currentPadding)) {
return currentSource;
}

if (currentSource.includes("middleware:[") && currentSource.includes("availableWidth")) {
console.warn(
"WARN: Could not find tooltip collision padding insertion point — skipping Linux tooltip titlebar collision patch",
);
}

return currentSource;
}

function applyLinuxThreadSidePanelNativeTooltipPatch(currentSource) {
const nativeTitleNeedle = 'disabled:l,title:i,onClick:a,uniform:!0';
const nativeTitlePatch = 'disabled:l,onClick:a,uniform:!0';

if (!currentSource.includes("id:`thread.sidePanel.toggle`")) {
return currentSource;
}

if (currentSource.includes(nativeTitlePatch) && !currentSource.includes(nativeTitleNeedle)) {
return currentSource;
}

if (currentSource.includes(nativeTitleNeedle)) {
return currentSource.split(nativeTitleNeedle).join(nativeTitlePatch);
}

if (currentSource.includes("tooltipContent:i") && currentSource.includes("title:i")) {
console.warn(
"WARN: Could not find thread side panel native tooltip insertion point — skipping Linux duplicate side panel tooltip patch",
);
}

return currentSource;
}

function applyLinuxAppSunsetPatch(currentSource) {
const statsigKey = "2929582856";
const disabledGatePattern = /if\(!1&&([A-Za-z_$][\w$]*)\(`2929582856`\)\)\{/u;
Expand Down Expand Up @@ -1130,6 +1200,9 @@ module.exports = {
applyPersistentRateLimitFooterPatch,
applyLinuxAppSunsetPatch,
applyLinuxOpaqueWindowsDefaultPatch,
applyLinuxThreadSidePanelNativeTooltipPatch,
applyLinuxTooltipWindowControlsCollisionPatch,
applyLinuxWindowControlsSafeAreaPatch,
applyLinuxSafeMonospaceFontStackPatch,
applyLinuxFastModeModelGuardPatch,
applyLocalEnvironmentActionModalDraftPatch,
Expand Down
Loading