Fix Windows keyboard issues with non-Latin layouts: hotkey detection, text insertion, phantom characters#134
Open
topmonroe9 wants to merge 3 commits intosavbell:mainfrom
Open
Conversation
…lipboard paste, event suppression - Fix PynputBackend defaulting unknown keys to SPACE, causing false activations - Add Windows VK code fallback map for layout-independent hotkey detection (e.g., Alt+F works correctly with Russian keyboard layout) - Add win32_event_filter with SuppressException to prevent activation combo keys from producing characters in the active application - Add clipboard-based text insertion method using Windows API (ctypes), which is instant, Unicode-aware, and layout-independent - Keep pynput as default input method for cross-platform compatibility; clipboard is available as an option (Windows only, falls back to pynput) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused PynputKey import from input_simulation.py - Don't create PynputController when using clipboard method - Configure Win32 API ctypes signatures once (_ensure_win32_api) - Add null checks for GlobalAlloc/GlobalLock return values - Extract magic sleep delay into _PASTE_SETTLE_DELAY constant - Clear _pressed_vks on listener restart to avoid stale state - Extract chord-matching logic into KeyChord.would_activate() to eliminate duplication between KeyChord.is_active() and the win32_event_filter suppression check - Rewrite _win32_event_filter docstring to accurately describe the SuppressException mechanism and thread-safety assumptions - Move MODIFIER_VKS to a class constant _MODIFIER_VKS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move WM_ message constants to class-level (_WM_KEYDOWN etc.) - Replace walrus operator with standard loop for broader compatibility - Add KeyChord.contains_key() and use it for key-up forwarding instead of checking mutable pressed_keys state - Wrap SuppressException import in try/except ImportError for pynput version resilience - Read fallback interval from config instead of hardcoded 0.005 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
|
Hi @savbell! 👋 This PR is ready for review. It fixes three issues that make WhisperWriter unusable on Windows with non-Latin keyboard layouts (Russian in my case, but the fix applies to any layout — Greek, Arabic, etc.). Happy to address any feedback or questions. Thanks for the great project! |
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.
This PR fixes three related issues that make WhisperWriter unusable on Windows when a non-Latin keyboard layout (e.g., Russian, Greek, Arabic) is active. All changes are Windows-specific and guarded by
sys.platform == 'win32', so Linux and macOS behavior is completely unaffected.Problems
1. Hotkeys don't work with non-Latin layouts
When the user has a Russian layout active and presses
Alt+F, pynput's internalToUnicode()call translates the physical F key to the Russian characterаand createsKeyCode(char='а', vk=None). Sincekey_maponly has entries for Latin characters, the lookup fails and_translate_key_eventwas falling back toKeyCode.SPACE— causing both missed activations and false triggers.2. Phantom characters appear when pressing hotkeys
Even after fixing detection, pressing
Alt+Fon a Russian layout would typeаinto the focused application, because pynput's default behavior forwards the key event to the OS after processing it.3. Transcribed text is garbled with non-Latin layouts
When using pynput's character-by-character typing (
keyboard.press('ж')), pynput internally callsVkKeyScan()which may fail or produce wrong results for non-Latin characters depending on the active layout. The result is garbled output (e.g., comma becomesb).Solutions
Fix 1: Virtual Key code fallback map (
_build_vk_map)Added an explicit mapping from Windows Virtual Key codes (which are layout-independent —
VK_F = 0x46regardless of layout) to our internalKeyCodeenum. When pynput's standardkey_maplookup fails, we fall back to VK codes captured from the raw Win32 event.Fix 2:
win32_event_filterwithSuppressExceptionAdded a low-level event filter via pynput's
win32_event_filterlistener option. This filter:ToUnicode()discards iton_input_eventso the chord is properly trackedpynput._util.win32.SystemHook.SuppressExceptionto prevent the key from reaching other applications (the Win32 hook returns 1, telling Windows to drop the event)Only non-modifier keys are suppressed — modifier keys (Alt, Ctrl, Shift, Win) pass through normally so they don't break other shortcuts.
Fix 3: Clipboard paste input method
Added a new
clipboardoption for theinput_methodsetting that:GlobalAlloc/SetClipboardDatawithCF_UNICODETEXT)Ctrl+Vviakeybd_eventusing virtual key codes (layout-independent)The implementation uses
ctypesdirectly — no external dependencies. On non-Windows platforms it falls back to pynput. The defaultinput_methodremainspynputfor cross-platform compatibility.Bonus fix:
KeyCode.SPACEdefault removed_translate_key_eventwas usingself.key_map.get(pynput_key, KeyCode.SPACE)— any unrecognized key was silently mapped to SPACE, causing false chord activations. Now unrecognized keys returnNoneand are ignored.Files changed
src/key_listener.py— VK map, event filter,would_activate(),contains_key(), None handlingsrc/input_simulation.py— clipboard paste method via Win32 APIsrc/config_schema.yaml— addedclipboardtoinput_methodoptions with descriptionTesting
Tested manually on Windows 11 with Russian and English layouts:
Notes
SuppressExceptionimport uses pynput's internal API (pynput._util.win32.SystemHook), wrapped intry/except ImportErrorfor resilience against future pynput versions. Tested with pynput 1.7.6.PynputBackendtranslating non-space keys to space #75 which also fixes theKeyCode.SPACEdefault, but this PR goes further by solving the root cause (layout-independent key detection) and adding the clipboard paste method.