fix: support keyboard shortcuts with CJK input sources (Korean, Chinese, Japanese) (#1649)

When a non-Latin input source is active, event.charactersIgnoringModifiers
returns CJK characters that cannot match Latin shortcut keys. This adds
ASCII-capable input source fallback in KeyboardLayout and updates the
matchShortcut guard to skip early-return when event chars are non-ASCII.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
이수민 2026-03-20 18:47:19 +09:00 committed by GitHub
parent 22689d5e0d
commit 8b49d66763
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 3 deletions

View file

@ -10472,8 +10472,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
// For command-based shortcuts, trust AppKit's layout-aware characters when present.
// Keep this strict for letter shortcuts to avoid physical-key collisions across layouts,
// while still allowing keyCode fallback for digit/punctuation shortcuts on non-US layouts.
// When a non-Latin input source is active (Korean, Chinese, Japanese, etc.),
// charactersIgnoringModifiers returns non-ASCII characters that can never match
// a Latin shortcut key skip this guard and fall through to layout-based matching.
let hasEventChars = !(eventCharsIgnoringModifiers?.isEmpty ?? true)
let eventCharsAreASCII = eventCharsIgnoringModifiers?.allSatisfy(\.isASCII) ?? true
if hasEventChars,
eventCharsAreASCII,
flags.contains(.command),
!flags.contains(.control),
shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey) {
@ -10496,12 +10501,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
// so keep ANSI keyCode fallback for control-modified shortcuts. Also allow fallback for
// command punctuation shortcuts, since some non-US layouts report different characters
// for the same physical key even when menu-equivalent semantics should still apply.
// When a non-Latin input source is active, treat non-ASCII event chars the same as
// absent chars they carry no usable Latin key identity.
let hasUsableEventChars = hasEventChars && eventCharsAreASCII
let allowANSIKeyCodeFallback = flags.contains(.control)
|| (flags.contains(.command)
&& !flags.contains(.control)
&& (
!shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey)
|| (!hasEventChars && (layoutCharacter?.isEmpty ?? true))
|| (!hasUsableEventChars && (layoutCharacter?.isEmpty ?? true))
))
if allowANSIKeyCodeFallback, let expectedKeyCode = keyCodeForShortcutKey(shortcutKey) {
return event.keyCode == expectedKeyCode

View file

@ -15,12 +15,31 @@ class KeyboardLayout {
/// Translate a physical keyCode to the character AppKit would use for shortcut matching,
/// preserving command-aware layouts such as "Dvorak - QWERTY Command".
/// CJK input sources (Korean, Chinese, Japanese) lack kTISPropertyUnicodeKeyLayoutData,
/// so we fall back to TISCopyCurrentASCIICapableKeyboardInputSource() in that case.
static func character(
forKeyCode keyCode: UInt16,
modifierFlags: NSEvent.ModifierFlags = []
) -> String? {
guard let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
let layoutDataPointer = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
if let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
let result = characterFromInputSource(source, forKeyCode: keyCode, modifierFlags: modifierFlags) {
return result
}
// Current input source has no Unicode layout data (e.g. Korean, Chinese, Japanese IME).
// Fall back to the ASCII-capable source so shortcut matching still works.
if let asciiSource = TISCopyCurrentASCIICapableKeyboardInputSource()?.takeRetainedValue(),
let result = characterFromInputSource(asciiSource, forKeyCode: keyCode, modifierFlags: modifierFlags) {
return result
}
return nil
}
private static func characterFromInputSource(
_ source: TISInputSource,
forKeyCode keyCode: UInt16,
modifierFlags: NSEvent.ModifierFlags
) -> String? {
guard let layoutDataPointer = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
return nil
}