Skip to content

fix(windows): reinstall low level keyboard hook if it gets removed#15179

Merged
mcdurdin merged 3 commits intomasterfrom
fix/windows/8064-low-level-hook-watch-dog
Nov 21, 2025
Merged

fix(windows): reinstall low level keyboard hook if it gets removed#15179
mcdurdin merged 3 commits intomasterfrom
fix/windows/8064-low-level-hook-watch-dog

Conversation

@mcdurdin
Copy link
Copy Markdown
Member

@mcdurdin mcdurdin commented Nov 17, 2025

This change improves the stability of Keyman for Windows by monitoring the health of the low level keyboard hook. If keyman.exe is unresponsive at any time, Windows can silently uninstall its low level keyboard hook, which results in (at least) two problems:

  • Keyman's hotkeys stop working
  • A modifier key can become stuck, if it was pressed around the time Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high system load, e.g. rendering graphics, videoconference calls, compiling software.

Restarting Keyman always resolved both of these two issues in the past, but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is included for simulating a keyman.exe hang by posting a wm_keyman_control:KMC_WATCHDOG_FAKEFREEZE message to it, which keyman.exe responds to by Sleep()ing for 5 seconds. While keyman.exe is freezing, any keystroke will cause the low level hook to be uninstalled by Windows.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for related events.

One final small change in keyman32.cpp, as I refactored the WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out hook variables when uninstalling a hook, because if the hook fails to uninstall, there's really nothing we can do about it anyway, and we probably shouldn't be trying again.

Related:

Fixes: #8064

User Testing

TEST_KEYMAN: Please run through a basic test of Keyman keyboard input. Swap keyboards, type text, swap applications, verify that Keyman continues to run robustly. Confirm that modifier keys do not become 'stuck' while you are running tests. Please run this test for at least 5 minutes of normal usage.

TEST_FREEZE: Download and unzip the attached fakefreeze.zip. Setup hotkeys in Keyman (e.g. to show/hide On Screen Keyboard). Run fakefreeze.exe, and while the 5 second timer it shows is running, press any of the Keyman hotkeys. Note that the hotkey has no effect. After the timer finishes, press the hotkey again and verify that it now works again. (Note that by design, you may need to press another key before pressing the hotkey, after a freeze, as the first time you press a key, Keyman will detect that it needs to heal itself.)

fakefreeze.zip

This change improves the stability of Keyman for Windows by monitoring
the health of the low level keyboard hook. If keyman.exe is unresponsive
at any time, Windows can silently uninstall its low level keyboard hook,
which results in (at least) two problems:

* Keyman's hotkeys stop working
* A modifier key can become stuck, if it was pressed around the time
  Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high
system load, e.g. rendering graphics, videoconference calls, compiling
software.

Restarting Keyman always resolved both of these two issues in the past,
but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is included for simulating a keyman.exe
hang by posting a `wm_keyman_control:KMC_WATCHDOG_FAKEFREEZE` message to
it, which keyman.exe responds to by `Sleep()`ing for 5 seconds. While
keyman.exe is freezing, any keystroke will cause the low level hook to
be uninstalled by Windows.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for
related events.

One final small change in keyman32.cpp, as I refactored the
WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out
hook variables when uninstalling a hook, because if the hook fails to
uninstall, there's really nothing we can do about it anyway, and we
probably shouldn't be trying again.

Fixes: #8064
@keymanapp-test-bot
Copy link
Copy Markdown

keymanapp-test-bot bot commented Nov 17, 2025

@keymanapp-test-bot keymanapp-test-bot bot added this to the A19S16 milestone Nov 17, 2025
void LowLevelHookWatchDog::HookIsAlive() {
// ULONGLONG Previous = LastLowLevelEventTick;
LastLowLevelEventTick = GetTickCount64();
// SendDebugMessageFormat("LowLevelHookWatchDog::HookIsAlive currentLL=%llu currentGM=%llu (lastLL=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving these debug messages in, but commented out, on purpose.


#define KMC_PROFILECHANGED 18 // 9.0.426.0 // I3933

#define KMC_HINTRESPONSE 19 // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line just to match KeymanControlMessages.pas

@mcdurdin mcdurdin requested a review from rc-swag November 17, 2025 15:49
Copy link
Copy Markdown
Contributor

@rc-swag rc-swag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

I was initally thinking a in memory total freeze count might be useful for anaylytics and we could post to sentry if we reached a threshold. It might help see a patern.
However, reading the stackoverflow and the frequency you see hotkeys stop working it is not really needed.

@mcdurdin
Copy link
Copy Markdown
Member Author

I was initally thinking a in memory total freeze count might be useful for anaylytics and we could post to sentry if we reached a threshold.

Yes, it would be a good idea to record an event on sentry in the event of a freeze. That would give us real world frequency. I will look at adding that as a follow-up to this PR.

@Meng-Heng Meng-Heng self-assigned this Nov 19, 2025
@Meng-Heng
Copy link
Copy Markdown
Contributor

Test Specifications

  1. MacBook Bootcamp Windows 10
  2. Applications: Notepad, Google Docs, and Chrome.

Prerequisites

  1. Uninstall Keyman & Restart machine
  2. Download & Install Keyman 19.0.163-alpha-test-15179

Test Results

  • TEST_KEYMAN (PASSED):
  1. Launch Keyman and install 6 keyboards
    • GFF Amharic
    • IPA (SIL)
    • US Basic
    • Khmer Basic
    • Ezidi
    • Korean Dubeolsik
  2. Open Keyman Configuration to set hotkeys for keyboard swap
  3. Open Google Docs & Notepad
  4. Start typing and switching to all of the keyboards while the OSK is open
  5. The US Basic Keyman keyboard is selected during windows switching
  6. Verified: Test type in Google Docs, Notepad, Chrome URL work as expected
  7. Verified: The modifier keys do not become 'stuck' after a press and switch back to the default layer
  8. Verified: Swap keyboards through hotkeys, shortcut key, and click work as expected
  9. Verified: OSK appears correctly on key presses, layers switches, and keyboard swaps
  10. Verified: IPA (SIL) keyboard's options for before and after work as expected.
  • TEST_FREEZE (PASSED):
  1. Install and Run fakefreeze.exe
  2. The timer runs 5s
  3. Verifiedː The following keys does not work
    • Shift + Alt + K (Switch ON/OFF OSK)
    • Shift + Ctrl + E (Switch to US Basic keyboard)
    • Shift + Ctrl + K (Switch to Khmer Basic keyboard)
  4. After the timer ends
  5. Verified: No additional key press is needed, the hotkeys & shortcut keys work immediately
    • Shift + Alt + K (Switch ON/OFF OSK)
    • Shift + Ctrl + E (Switch to US Basic keyboard)
    • Shift + Ctrl + K (Switch to Khmer Basic keyboard)
    • Win + Space
  6. Verified:
    • IPA Options setting remains the same
    • Type in Notepad and Google Docs work as expected.

@keymanapp-test-bot keymanapp-test-bot bot removed the user-test-required User tests have not been completed label Nov 19, 2025
@mcdurdin mcdurdin merged commit 32db754 into master Nov 21, 2025
8 checks passed
@mcdurdin mcdurdin deleted the fix/windows/8064-low-level-hook-watch-dog branch November 21, 2025 09:40
@github-project-automation github-project-automation bot moved this from Todo to Done in Keyman Nov 21, 2025
mcdurdin added a commit that referenced this pull request Nov 21, 2025
This change improves the stability of Keyman for Windows by monitoring
the health of the low level keyboard hook. If keyman.exe is unresponsive
at any time, Windows can silently uninstall its low level keyboard hook,
which results in (at least) two problems:

* Keyman's hotkeys stop working
* A modifier key can become stuck, if it was pressed around the time
  Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high
system load, e.g. rendering graphics, videoconference calls, compiling
software.

Restarting Keyman always resolved both of these two issues in the past,
but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is not included in this cherry-pick; see
PR #15179 for this.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for
related events.

One final small change in keyman32.cpp, as I refactored the
WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out
hook variables when uninstalling a hook, because if the hook fails to
uninstall, there's really nothing we can do about it anyway, and we
probably shouldn't be trying again.

Fixes: #8064
Cherry-pick-of: #15179
Test-bot: skip
Build-bot: skip
mcdurdin added a commit that referenced this pull request Nov 21, 2025
This change improves the stability of Keyman for Windows by monitoring
the health of the low level keyboard hook. If keyman.exe is unresponsive
at any time, Windows can silently uninstall its low level keyboard hook,
which results in (at least) two problems:

* Keyman's hotkeys stop working
* A modifier key can become stuck, if it was pressed around the time
  Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high
system load, e.g. rendering graphics, videoconference calls, compiling
software.

Restarting Keyman always resolved both of these two issues in the past,
but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is not included in this cherry-pick; see
PR #15179 for this.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for
related events.

One final small change in keyman32.cpp, as I refactored the
WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out
hook variables when uninstalling a hook, because if the hook fails to
uninstall, there's really nothing we can do about it anyway, and we
probably shouldn't be trying again.

Fixes: #8064
Cherry-pick-of: #15179
Test-bot: skip
Build-bot: skip
mcdurdin added a commit that referenced this pull request Nov 21, 2025
This change improves the stability of Keyman for Windows by monitoring
the health of the low level keyboard hook. If keyman.exe is unresponsive
at any time, Windows can silently uninstall its low level keyboard hook,
which results in (at least) two problems:

* Keyman's hotkeys stop working
* A modifier key can become stuck, if it was pressed around the time
  Keyman became unresponsive.

The most common scenario in which Keyman can become unresponsive is high
system load, e.g. rendering graphics, videoconference calls, compiling
software.

Restarting Keyman always resolved both of these two issues in the past,
but with this patch, I hope that this will no longer be necessary.

A related 'fakefreeze' project is not included in this cherry-pick; see
PR #15179 for this.

Logging has been updated; look for "LowLevelHookWatchDog" in the log for
related events.

One final small change in keyman32.cpp, as I refactored the
WH_KEYBOARD_LL hook installation/uninstallation, was to always clear out
hook variables when uninstalling a hook, because if the hook fails to
uninstall, there's really nothing we can do about it anyway, and we
probably shouldn't be trying again.

Fixes: #8064
Cherry-pick-of: #15179
Test-bot: skip
Build-bot: skip
@keyman-server
Copy link
Copy Markdown
Collaborator

Changes in this pull request will be available for download in Keyman version 19.0.165-alpha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

bug(windows): modifier key occasionally is 'stuck on'

4 participants