fix(desktop): ignore IME composition keydowns in keybind combo resolution
Chinese/Japanese/Korean IMEs emit keydown events during composition that carry preedit keystrokes and the commit keypress (Enter/Space/Shift for candidate selection). Treating them as combos fires unrelated keybinds — e.g. typing 你 with a CJK IME could dispatch session.new and silently open a new session. Guard comboFromEvent(): - Bail out entirely while composing (event.isComposing or key === 'Process') - Ignore keydowns whose event.key is a bare modifier name but whose code is a regular key — legacy IMEs that synthesize keystrokes (Q9 2002 sends key="Control" with code="KeyW") would otherwise canonicalize into phantom combos like mod+w that close the active tab. Tested with Q9 (九方) legacy IME on Windows.
This commit is contained in:
@@ -50,6 +50,9 @@ const MODIFIER_CODES = new Set([
|
||||
'ShiftRight'
|
||||
])
|
||||
|
||||
// Modifier names as reported by `event.key` on a bare modifier keydown.
|
||||
const MODIFIER_KEYS = new Set(['Alt', 'Control', 'Meta', 'Shift'])
|
||||
|
||||
function baseKeyFromCode(code: string): string | null {
|
||||
if (code.startsWith('Key')) {
|
||||
return code.slice(3).toLowerCase()
|
||||
@@ -101,10 +104,28 @@ function baseKeyFromEventKey(key: string, shiftKey: boolean): string | null {
|
||||
// Returns the canonical combo for a keydown, or null while only modifiers are
|
||||
// held (so capture mode keeps waiting for a real key).
|
||||
export function comboFromEvent(event: KeyboardEvent): string | null {
|
||||
// IME composition (Chinese/Japanese/Korean input): the keydown events
|
||||
// during composition carry preedit keystrokes and the commit keypress
|
||||
// (Enter/Space/Shift for candidate selection). Treating them as combos
|
||||
// fires unrelated keybinds — e.g. typing 你 with a Chinese IME sent a
|
||||
// keydown that dispatched `session.new` and silently opened a new session.
|
||||
// Bail out entirely while composing.
|
||||
if (event.isComposing || event.key === 'Process') {
|
||||
return null
|
||||
}
|
||||
|
||||
if (MODIFIER_CODES.has(event.code)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// A keydown whose `key` is a modifier name but whose `code` is a regular
|
||||
// key is not a real modifier chord — legacy IMEs that synthesize keystrokes
|
||||
// (Q9 2002 sends key="Control" with code="KeyW") produce these, and they
|
||||
// would canonicalize to phantom combos (Ctrl+W → close active tab). Ignore.
|
||||
if (MODIFIER_KEYS.has(event.key)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const base = baseKeyFromEventKey(event.key, event.shiftKey) ?? baseKeyFromCode(event.code)
|
||||
|
||||
if (!base) {
|
||||
|
||||
Reference in New Issue
Block a user