Skip to content

Commit 4c92271

Browse files
Merge pull request #1913 from centraldogma99/fix/korean-input-keyboard-shortcuts
Fix keyboard shortcuts not working with Korean input mode
2 parents c5b3066 + 8cd9cd9 commit 4c92271

4 files changed

Lines changed: 31 additions & 8 deletions

File tree

Sources/AppDelegate.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8889,7 +8889,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
88898889
private func handleCustomShortcut(event: NSEvent) -> Bool {
88908890
// `charactersIgnoringModifiers` can be nil for some synthetic NSEvents and certain special keys.
88918891
// Treat nil as "" and rely on keyCode/layout-aware fallback logic where needed.
8892-
let chars = (event.charactersIgnoringModifiers ?? "").lowercased()
8892+
// When a non-Latin input source is active (Korean, Chinese, Japanese, etc.),
8893+
// charactersIgnoringModifiers returns non-ASCII characters that never match
8894+
// Latin shortcut keys. Normalize via KeyboardLayout so downstream comparisons
8895+
// (Cmd+1-9, Ctrl+1-9, omnibar N/P, command palette, etc.) work correctly.
8896+
let chars = KeyboardLayout.normalizedCharacters(for: event)
88938897
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
88948898
let hasControl = flags.contains(.control)
88958899
let hasCommand = flags.contains(.command)

Sources/KeyboardLayout.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,41 @@ class KeyboardLayout {
1515

1616
/// Translate a physical keyCode to the character AppKit would use for shortcut matching,
1717
/// preserving command-aware layouts such as "Dvorak - QWERTY Command".
18-
/// CJK input sources (Korean, Chinese, Japanese) lack kTISPropertyUnicodeKeyLayoutData,
19-
/// so we fall back to TISCopyCurrentASCIICapableKeyboardInputSource() in that case.
18+
/// Some CJK input sources lack kTISPropertyUnicodeKeyLayoutData, and others (Korean
19+
/// 두벌식) have it but UCKeyTranslate still returns non-ASCII characters. In either
20+
/// case we fall back to TISCopyCurrentASCIICapableKeyboardInputSource().
2021
static func character(
2122
forKeyCode keyCode: UInt16,
2223
modifierFlags: NSEvent.ModifierFlags = []
2324
) -> String? {
2425
if let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
25-
let result = characterFromInputSource(source, forKeyCode: keyCode, modifierFlags: modifierFlags) {
26+
let result = characterFromInputSource(source, forKeyCode: keyCode, modifierFlags: modifierFlags),
27+
result.allSatisfy(\.isASCII) {
2628
return result
2729
}
28-
// Current input source has no Unicode layout data (e.g. Korean, Chinese, Japanese IME).
29-
// Fall back to the ASCII-capable source so shortcut matching still works.
30+
// Current input source has no Unicode layout data or returned a non-ASCII
31+
// character (e.g. Korean 두벌식 has layout data but UCKeyTranslate still
32+
// produces Hangul). Fall back to the ASCII-capable source so shortcut
33+
// matching still works.
3034
if let asciiSource = TISCopyCurrentASCIICapableKeyboardInputSource()?.takeRetainedValue(),
3135
let result = characterFromInputSource(asciiSource, forKeyCode: keyCode, modifierFlags: modifierFlags) {
3236
return result
3337
}
3438
return nil
3539
}
3640

41+
/// Return the ASCII-normalized equivalent of `event.charactersIgnoringModifiers`,
42+
/// falling back through the ASCII-capable input source for non-Latin input methods.
43+
/// Use this wherever code compares raw event characters against Latin shortcut keys.
44+
static func normalizedCharacters(for event: NSEvent) -> String {
45+
let raw = (event.charactersIgnoringModifiers ?? "").lowercased()
46+
if raw.allSatisfy(\.isASCII) { return raw }
47+
if let layoutChar = character(forKeyCode: event.keyCode, modifierFlags: []) {
48+
return layoutChar
49+
}
50+
return raw
51+
}
52+
3753
private static func characterFromInputSource(
3854
_ source: TISInputSource,
3955
forKeyCode keyCode: UInt16,

Sources/Panels/BrowserPanelView.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3735,7 +3735,10 @@ private struct OmnibarTextFieldRepresentable: NSViewRepresentable {
37353735
#endif
37363736
let keyCode = event.keyCode
37373737
let modifiers = event.modifierFlags.intersection([.command, .control, .shift, .option, .function])
3738-
let lowered = event.charactersIgnoringModifiers?.lowercased() ?? ""
3738+
// When a non-Latin input source is active (Korean, Chinese, Japanese),
3739+
// charactersIgnoringModifiers returns non-ASCII characters. Normalize
3740+
// via KeyboardLayout so Cmd/Ctrl+N/P navigation works across input sources.
3741+
let lowered = KeyboardLayout.normalizedCharacters(for: event)
37393742
let hasCommandOrControl = modifiers.contains(.command) || modifiers.contains(.control)
37403743

37413744
// Cmd/Ctrl+N and Cmd/Ctrl+P should repeat while held.

Sources/Panels/BrowserPopupWindowController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private class BrowserPopupPanel: NSPanel {
5050
// Cmd+W: close this popup panel only
5151
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
5252
if flags == .command,
53-
event.charactersIgnoringModifiers == "w" {
53+
KeyboardLayout.normalizedCharacters(for: event) == "w" {
5454
#if DEBUG
5555
dlog("popup.panel.cmdW close")
5656
#endif

0 commit comments

Comments
 (0)