From 8c3775f8d699fa187e545b555bb3d32aa72ba223 Mon Sep 17 00:00:00 2001 From: Incursor Teknoloji <111435762+IncursorTech@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:13:55 +0300 Subject: [PATCH] fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts (#29587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts On macOS the main-process paste intercept matched `code === 'KeyV'` OR `key === 'v'`, so on Turkish F (where the C letter sits on the physical KeyV) Cmd+C was routed to ui:appMenuPaste. Trust the produced key when it is a Latin letter or digit and only fall back to the physical code for non-Latin layouts (Cyrillic etc.), matching the shared keybinding matcher. Fixes #29574 Claude-Session: https://claude.ai/code/session_01TYbS9P1pnW4WRN7bGpuNaW * test(shortcuts): cover Dvorak, Korean and IME composition for the macOS paste intercept Co-Authored-By: Claude Opus 5.5 * refactor(shortcuts): match the macOS paste intercept by produced key token, in its own module The produced key token wins on every Latin layout, punctuation included (BEPO puts . on KeyV); non-Latin and IME keys fall back to the physical key. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: İzzetEmre Co-authored-by: Jinwoo-H Co-authored-by: Claude Opus 5.5 --- src/main/window/mac-app-paste-input.test.ts | 50 +++++++++++++++++++ src/main/window/mac-app-paste-input.ts | 14 ++++++ .../window/main-window-shortcut-routing.ts | 2 +- .../window/main-window-visual-lifecycle.ts | 12 ----- 4 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 src/main/window/mac-app-paste-input.test.ts create mode 100644 src/main/window/mac-app-paste-input.ts diff --git a/src/main/window/mac-app-paste-input.test.ts b/src/main/window/mac-app-paste-input.test.ts new file mode 100644 index 00000000000..556bdee47be --- /dev/null +++ b/src/main/window/mac-app-paste-input.test.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { isMacAppPasteInput } from './mac-app-paste-input' + +function cmdKey(key: string, code: string, extra: Partial = {}): Electron.Input { + return { + type: 'keyDown', + key, + code, + isAutoRepeat: false, + isComposing: false, + meta: true, + control: false, + alt: false, + shift: false, + location: 0, + modifiers: ['meta'], + ...extra + } +} + +describe('isMacAppPasteInput', () => { + beforeEach(() => { + vi.spyOn(process, 'platform', 'get').mockReturnValue('darwin') + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it.each([ + ['US Cmd+V', cmdKey('v', 'KeyV'), true], + ['Caps Lock Cmd+V', cmdKey('V', 'KeyV'), true], + ['Turkish F Cmd+V (V lives on KeyC)', cmdKey('v', 'KeyC'), true], + ['Turkish F Cmd+C (C lives on KeyV)', cmdKey('c', 'KeyV'), false], + ['Dvorak Cmd+V (V lives on Period)', cmdKey('v', 'Period'), true], + ['Dvorak Cmd+K (K lives on KeyV)', cmdKey('k', 'KeyV'), false], + ['BÉPO Cmd+. (. lives on KeyV)', cmdKey('.', 'KeyV'), false], + ['Cyrillic Cmd+V (non-Latin key on KeyV)', cmdKey('м', 'KeyV'), true], + ['Korean Cmd+V (Hangul jamo on KeyV)', cmdKey('ㅍ', 'KeyV'), true], + ['Cmd+V during IME composition', cmdKey('Process', 'KeyV'), true], + ['Cmd+Shift+V', cmdKey('V', 'KeyV', { shift: true }), false] + ])('%s', (_name, input, expected) => { + expect(isMacAppPasteInput(input)).toBe(expected) + }) + + it('never claims paste off macOS', () => { + vi.spyOn(process, 'platform', 'get').mockReturnValue('linux') + expect(isMacAppPasteInput(cmdKey('v', 'KeyV'))).toBe(false) + }) +}) diff --git a/src/main/window/mac-app-paste-input.ts b/src/main/window/mac-app-paste-input.ts new file mode 100644 index 00000000000..ac4b314e498 --- /dev/null +++ b/src/main/window/mac-app-paste-input.ts @@ -0,0 +1,14 @@ +import { logicalKeyTokenFromInput, physicalCodeKeyTokenFromInput } from '../../shared/keybindings' + +export function isMacAppPasteInput(input: Electron.Input): boolean { + return ( + process.platform === 'darwin' && + input.type === 'keyDown' && + input.meta && + !input.control && + !input.alt && + !input.shift && + // Why: the produced key wins so remapped layouts (Turkish F puts C on KeyV) don't paste; non-Latin and IME keys fall back to the physical key. + (logicalKeyTokenFromInput(input) ?? physicalCodeKeyTokenFromInput(input)) === 'V' + ) +} diff --git a/src/main/window/main-window-shortcut-routing.ts b/src/main/window/main-window-shortcut-routing.ts index 71e1cb44451..ba2ecb35424 100644 --- a/src/main/window/main-window-shortcut-routing.ts +++ b/src/main/window/main-window-shortcut-routing.ts @@ -20,7 +20,7 @@ import type { Store } from '../persistence' import type { CreateMainWindowOptions } from './main-window-contracts' import type { MainWindowFocusLifecycle } from './main-window-focus-lifecycle' import { sendResolvedWindowShortcutAction } from './main-window-shortcut-actions' -import { isMacAppPasteInput } from './main-window-visual-lifecycle' +import { isMacAppPasteInput } from './mac-app-paste-input' export function installMainWindowShortcutRouting(args: { focus: MainWindowFocusLifecycle diff --git a/src/main/window/main-window-visual-lifecycle.ts b/src/main/window/main-window-visual-lifecycle.ts index f26b823d60d..200d08c77b1 100644 --- a/src/main/window/main-window-visual-lifecycle.ts +++ b/src/main/window/main-window-visual-lifecycle.ts @@ -95,18 +95,6 @@ export function installMacosVisibilityRepaint(window: BrowserWindow): void { }) } -export function isMacAppPasteInput(input: Electron.Input): boolean { - return ( - process.platform === 'darwin' && - input.type === 'keyDown' && - input.meta && - !input.control && - !input.alt && - !input.shift && - (input.code === 'KeyV' || input.key.toLowerCase() === 'v') - ) -} - // Why: titlebar content center sits ~18 CSS px from top (×zoom); traffic lights are ~12px tall, so top edge = center − 6. export const TITLEBAR_CSS_CENTER = 18 export const TRAFFIC_LIGHT_RADIUS = 6