diff --git a/packages/app/src/keyboard/browser-shortcuts.test.ts b/packages/app/src/keyboard/browser-shortcuts.test.ts index e6d1911cc..be0aa026c 100644 --- a/packages/app/src/keyboard/browser-shortcuts.test.ts +++ b/packages/app/src/keyboard/browser-shortcuts.test.ts @@ -136,6 +136,48 @@ describe("buildBrowserKeyboardPolicy", () => { }); }); + it("keeps Ctrl+W out of the window menu after the tab-close shortcut is remapped", () => { + const bindings = buildEffectiveBindings({ + "workspace-tab-close-current-ctrl-w-non-mac": "Ctrl+Y", + }); + + const policy = buildBrowserKeyboardPolicy({ bindings, isMac: false, isDesktop: true }); + + expect(policy.prefixes).not.toContainEqual({ + alt: false, + code: "KeyW", + control: true, + key: "w", + meta: false, + shift: false, + }); + expect(policy.menuPrefixes).toContainEqual({ + alt: false, + code: "KeyW", + control: true, + key: "w", + meta: false, + shift: false, + }); + }); + + it("leaves macOS browser back and forward shortcuts in the guest", () => { + const bindings = buildEffectiveBindings({}); + + const policy = buildBrowserKeyboardPolicy({ bindings, isMac: true, isDesktop: true }); + + for (const code of ["BracketLeft", "BracketRight"]) { + expect(policy.prefixes).not.toContainEqual({ + alt: false, + code, + control: false, + key: code === "BracketLeft" ? "[" : "]", + meta: true, + shift: false, + }); + } + }); + it("does not publish plain browser keys", () => { const bindings = buildEffectiveBindings({}); const policy = buildBrowserKeyboardPolicy({ bindings, isMac: false, isDesktop: true }); diff --git a/packages/app/src/keyboard/browser-shortcuts.ts b/packages/app/src/keyboard/browser-shortcuts.ts index d03467eed..470005e71 100644 --- a/packages/app/src/keyboard/browser-shortcuts.ts +++ b/packages/app/src/keyboard/browser-shortcuts.ts @@ -104,8 +104,22 @@ function prefixFromCombo(combo: KeyCombo, isMac: boolean): BrowserShortcutPrefix return prefix.meta || prefix.control || prefix.alt ? prefix : null; } +function isBrowserNativeNavigationPrefix(prefix: BrowserShortcutPrefix, isMac: boolean): boolean { + return ( + isMac && + prefix.meta && + !prefix.control && + !prefix.alt && + !prefix.shift && + (prefix.code === "BracketLeft" || prefix.code === "BracketRight") + ); +} + function canCrossBrowserBoundary(binding: ParsedShortcutBinding, isMac: boolean): boolean { - return binding.parsedChord.every((combo) => prefixFromCombo(combo, isMac) !== null); + return binding.parsedChord.every((combo) => { + const prefix = prefixFromCombo(combo, isMac); + return prefix !== null && !isBrowserNativeNavigationPrefix(prefix, isMac); + }); } function prefixKey(prefix: BrowserShortcutPrefix): string { @@ -165,10 +179,24 @@ function buildBrowserShortcutPrefixes(input: BrowserShortcutPolicyInput): Browse export function buildBrowserKeyboardPolicy( input: BrowserShortcutPolicyInput, ): BrowserKeyboardPolicy { - const menuPrefixes = buildBrowserShortcutPrefixes({ ...input, chordState: undefined }); + const idlePrefixes = buildBrowserShortcutPrefixes({ ...input, chordState: undefined }); const prefixes = input.chordState && input.chordState.step > 0 ? buildBrowserShortcutPrefixes(input) - : menuPrefixes; + : idlePrefixes; + const menuPrefixes = [...idlePrefixes]; + if (!input.isMac) { + const closeWindowGuard: BrowserShortcutPrefix = { + alt: false, + code: "KeyW", + control: true, + key: "w", + meta: false, + shift: false, + }; + if (!menuPrefixes.some((prefix) => prefixKey(prefix) === prefixKey(closeWindowGuard))) { + menuPrefixes.push(closeWindowGuard); + } + } return { menuPrefixes, prefixes }; }