From 5d073fa44f4905eac9f6b9d4d84d7134f12af0b8 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Fri, 21 Aug 2026 23:21:34 +0300 Subject: [PATCH 1/6] fix(windows): resync caps lock state when Keyman keyboard is activated The Caps Lock and Num Lock flags in Globals::ShiftState() are updated only as key events pass through the engine, so they go stale when the toggle is changed while a non-Keyman layout is active. The existing resync in GetCapsAndNumlockState() runs on window focus change, which a keyboard switch does not trigger, so the core processor was told Caps Lock was off until the user toggled it twice. Extract the toggle resync as RefreshToggleState() and call it from TIPActivateKeyboard(), which is the profile activation path for both Win+Space and the Windows language selector. Fixes #16422 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011GdFpFi5eqJP6C1YjuQytY --- windows/src/engine/keyman32/appint/aiTIP.cpp | 3 +++ windows/src/engine/keyman32/capsstate.cpp | 18 ++++++++++++++++++ windows/src/engine/keyman32/capsstate.h | 1 + .../src/engine/keyman32/kmhook_getmessage.cpp | 6 +----- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index a2baada55b9..8401ccb8eed 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.cpp +++ b/windows/src/engine/keyman32/appint/aiTIP.cpp @@ -67,6 +67,9 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPIsKeymanRunning() { extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateKeyboard(GUID *profile) { // I3581 PKEYMAN64THREADDATA _td = ThreadGlobals(); if(!_td) return FALSE; + + RefreshToggleState(); // #16422 - a keyboard switch does not change window focus + if(profile != NULL) { for(int i = 0; i < _td->nKeyboards; i++) { for(int j = 0; j < _td->lpKeyboards[i].nProfiles; j++) { diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index 4f9b466e9ee..4a6631f5de5 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -29,3 +29,21 @@ BOOL IsCapsLockOn(void) { return GetKeyState(VK_CAPITAL) & 1; } + +/* + RefreshToggleState: + + Resyncs the caps and numlock state, because it may have been changed while + Keyman was not aware of it +*/ +void RefreshToggleState(void) { + DWORD n = Globals::get_ShiftState(); + + if (GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG; + else *Globals::ShiftState() &= ~CAPITALFLAG; + + if (GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG; + else *Globals::ShiftState() &= ~NUMLOCKFLAG; + + SendDebugMessageFormat("Enter: %x Exit: %x", n, Globals::get_ShiftState()); +} diff --git a/windows/src/engine/keyman32/capsstate.h b/windows/src/engine/keyman32/capsstate.h index d28ba9c0db9..0be449db302 100644 --- a/windows/src/engine/keyman32/capsstate.h +++ b/windows/src/engine/keyman32/capsstate.h @@ -20,5 +20,6 @@ #define __CAPSSTATE_H BOOL IsCapsLockOn(void); +void RefreshToggleState(void); #endif diff --git a/windows/src/engine/keyman32/kmhook_getmessage.cpp b/windows/src/engine/keyman32/kmhook_getmessage.cpp index 4c11dc31b50..4efd4879463 100644 --- a/windows/src/engine/keyman32/kmhook_getmessage.cpp +++ b/windows/src/engine/keyman32/kmhook_getmessage.cpp @@ -418,11 +418,7 @@ ProcessWMKeymanControl(WPARAM wParam, LPARAM lParam) { void GetCapsAndNumlockState() { // I4793 DWORD n = Globals::get_ShiftState(); - if(GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG; - else *Globals::ShiftState() &= ~NUMLOCKFLAG; - - if(GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG; - else *Globals::ShiftState() &= ~CAPITALFLAG; + RefreshToggleState(); if(GetKeyState(VK_SHIFT) < 0) *Globals::ShiftState() |= K_SHIFTFLAG; else *Globals::ShiftState() &= ~K_SHIFTFLAG; From f586e94cf5ae4052ce97d3c3ebe20bb7d95b4f88 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Sat, 22 Aug 2026 10:25:22 +0300 Subject: [PATCH 2/6] Update windows/src/engine/keyman32/appint/aiTIP.cpp Co-authored-by: Marc Durdin --- windows/src/engine/keyman32/appint/aiTIP.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index 8401ccb8eed..be657e4a124 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.cpp +++ b/windows/src/engine/keyman32/appint/aiTIP.cpp @@ -68,7 +68,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateKeyboard(GUID *profile) PKEYMAN64THREADDATA _td = ThreadGlobals(); if(!_td) return FALSE; - RefreshToggleState(); // #16422 - a keyboard switch does not change window focus + RefreshToggleState(); if(profile != NULL) { for(int i = 0; i < _td->nKeyboards; i++) { From 13326b409132e4c226a927244920d40a305e221c Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Sat, 22 Aug 2026 10:25:46 +0300 Subject: [PATCH 3/6] Update windows/src/engine/keyman32/capsstate.cpp Co-authored-by: Marc Durdin --- windows/src/engine/keyman32/capsstate.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index 4a6631f5de5..7d1e1eb096a 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -30,12 +30,10 @@ BOOL IsCapsLockOn(void) { return GetKeyState(VK_CAPITAL) & 1; } -/* - RefreshToggleState: - - Resyncs the caps and numlock state, because it may have been changed while - Keyman was not aware of it -*/ +/** + * Resync the Caps Lock and Num Lock state, because it may have been + * changed while Keyman was not aware of it + */ void RefreshToggleState(void) { DWORD n = Globals::get_ShiftState(); From dcbfcf810f6253f3986af913610116c77ac4638e Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Sat, 22 Aug 2026 10:26:23 +0300 Subject: [PATCH 4/6] Update windows/src/engine/keyman32/capsstate.cpp Co-authored-by: Marc Durdin --- windows/src/engine/keyman32/capsstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index 7d1e1eb096a..b87ad92ff5c 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -35,7 +35,7 @@ BOOL IsCapsLockOn(void) { * changed while Keyman was not aware of it */ void RefreshToggleState(void) { - DWORD n = Globals::get_ShiftState(); + DWORD previousShiftState = Globals::get_ShiftState(); if (GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG; else *Globals::ShiftState() &= ~CAPITALFLAG; From fb91d2fc7526cd9eaab1675155863e77e1255616 Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Sat, 22 Aug 2026 10:26:53 +0300 Subject: [PATCH 5/6] Update windows/src/engine/keyman32/capsstate.cpp Co-authored-by: Marc Durdin --- windows/src/engine/keyman32/capsstate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index b87ad92ff5c..85d58900b72 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -43,5 +43,5 @@ void RefreshToggleState(void) { if (GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG; else *Globals::ShiftState() &= ~NUMLOCKFLAG; - SendDebugMessageFormat("Enter: %x Exit: %x", n, Globals::get_ShiftState()); + SendDebugMessageFormat("Enter: %x Exit: %x", previousShiftState, Globals::get_ShiftState()); } From 446559c2210ffa9df5431285f09f87ed60ef795a Mon Sep 17 00:00:00 2001 From: Matthew Lee Date: Mon, 24 Aug 2026 09:19:47 -0400 Subject: [PATCH 6/6] Update windows/src/engine/keyman32/capsstate.cpp Co-authored-by: rc-swag <58423624+rc-swag@users.noreply.github.com> --- windows/src/engine/keyman32/capsstate.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/windows/src/engine/keyman32/capsstate.cpp b/windows/src/engine/keyman32/capsstate.cpp index 85d58900b72..bc3fc36e5b8 100644 --- a/windows/src/engine/keyman32/capsstate.cpp +++ b/windows/src/engine/keyman32/capsstate.cpp @@ -31,8 +31,10 @@ BOOL IsCapsLockOn(void) { } /** - * Resync the Caps Lock and Num Lock state, because it may have been - * changed while Keyman was not aware of it + * Resync the Caps Lock and Num Lock state cache. + * + * Use when state may be stale after focus changes or when the toggle keys + * changed while this Keyman engine instance was not processing key events. */ void RefreshToggleState(void) { DWORD previousShiftState = Globals::get_ShiftState();