fix(router): pass numpad keys through in Hangul mode#1
Merged
Conversation
In 3-beolsik layouts digits 0-9 map to Hangul jamo, causing numpad input to be incorrectly composed as Hangul. Route all numpad keys (except Enter, which remains unified with main Enter) to flushAndPassToSystem by checking both the .numericPad flag and the kVK_ANSI_Keypad* keycode range.
hiking90
added a commit
that referenced
this pull request
May 21, 2026
…an mode) User report: in 영문→long→short→short sequence, the first short press correctly exits real Caps Lock (LED off), but the second short press — intended to toggle to Korean — instead turns the LED back on, looking to the user like Caps Lock re-activated. Root cause: design conflict between doc 30 SET semantics (LED ON = Korean) and doc 32 HID realLockOn (LED ON = real Caps Lock active). In HID mode, the LED was carrying two meanings: - LED on for Korean mode (doc 30 SET via setMode/toggleEngineMode) - LED on for realLockOn (doc 32 explicit setState in enterRealCapsLock) Visually identical, semantically distinct. After exiting realLockOn and then toggling to Korean, the LED turned on for the second reason and the user (correctly) interpreted it as Caps Lock — conflicting with the macOS convention "Caps Lock LED on = uppercase locked". Fix: in HID mode, LED only reflects realLockOn. Mode indicator moves to the menu bar icon entirely. - InputStateCoordinator.setMode and toggleEngineMode: LED sync gate changed from `mode != .hidRealLockOn` to `mode == .cgEventTapAuthority`. doc 30 SET semantics still apply when HID isn't active (fallback); in HID mode, mode changes don't touch LED. - OngeulInputController.performExitRealCapsLock: was `setState(korean)` (LED on if restored to Korean per doc 30 SET); now unconditionally `setState(false)` (LED off — restored mode shows via menu bar icon). - doc 32 updated: § Doc 30과의 관계 clarifies the LED scope split, § 단일 상태 enum module table updated, ASCII state diagram note changed, transitions table includes LED column showing OFF/ON explicitly, 동작 시나리오 #1 and #4 reworded. End-user behavior after this fix: - 영문 → long → short → short → Korean - LED: off → on(realLockOn) → off(exit) → off(toggle) - Mode: English → English+caps → English → Korean - LED never turns on for "just being in Korean mode" — only when real Caps Lock is active. Matches macOS convention; resolves user confusion. Phase 1 PR (#13) unaffected — doc 30 SET continues to apply in the CGEventTap-only baseline (no HID). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hiking90
added a commit
that referenced
this pull request
May 22, 2026
…ift)
Detailed review of the CapsLock on/off mechanism found one real design
gap: realLockOn was decided "global" (doc 32 §10) but our realLockOn is
coupled to forced-English mode, unlike true macOS Caps Lock which is
orthogonal to input source. Consequence:
1. App A English → long press → realLockOn (English + caps, LED on)
2. Cmd+Tab to App B (per-app mode = Korean)
3. App B activateServer → activateApp → setMode(.korean) → engine.setMode
runs ungated → engine becomes Korean WHILE realLockOn flag/LED stay on
4. User types in App B → Korean, not the expected English uppercase
5. (and a short-tap exit would restore App A's mode into App B)
Fix (option B — session-scoped realLockOn): on deactivateServer, if
realLockOn is active, force-exit it and restore the pre-caps mode before
coordinator.deactivate persists the per-app mode (so English isn't saved
over the user's real mode).
- CapsLockHIDMonitor.exitRealLockForSessionEnd(): resets monitor state
(mode → .hidToggleAuthority, clears modeBeforeRealLock / timer /
flags), sets LED off, returns the pre-caps mode. Runs on the IMK
lifecycle thread (main), so no controller dispatch needed.
- InputStateCoordinator.restoreModeAfterRealLock(_:): sets engine mode
back (LED untouched via HID gate). Called before deactivate's per-app
save so the restored mode is what gets persisted.
- OngeulInputController.deactivateServer: invokes the above at the top.
Trade-off: unlike true macOS Caps Lock, ours no longer persists across
app switches. Accepted because of the English-coupling; realLockOn is a
transient "type uppercase now" intent and reverting the source app to
its pre-caps state on session end is cleaner than drift.
doc 32 updated:
- § 동작 시나리오 #10 rewritten (session-scoped exit)
- § 미해결 #1 decision reversed (global → session-scoped, with rationale)
- § 미해결 #8 added (alpha-lock external desync — known limitation)
- § 미해결 #9 added (on/off asymmetry — intentional, documented)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
3-390,3-final) digits0-9are mapped to Hangul jamo, so numpad input was being composed as Hangul instead of inserting digits.flushAndPassToSystem.NSEvent.ModifierFlags.numericPadand thekVK_ANSI_Keypad*keycode range. Arrow keys also carry.numericPad, but they're routed earlier inrouteKeyDown, so this check is unambiguous by the time it runs.Test plan
xcodebuild test -scheme OngeulTests -only-testing:OngeulTests/HandleKeyDownRouterTests— 18 tests pass (4 new: numpad digits, numpad operators, numpad Enter regression, main-row digit regression).