From 0e332b6f6ce0af5aaae57538b57ec794db1c7bce Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 26 Aug 2026 10:08:40 +0200 Subject: [PATCH 1/2] fix(windows): simplify keyboard refresh and make it more on-demand Clean up the keyboard refresh process. Keyman COM API will post a `wm_keyman_refresh`, `KR_REQUEST_REFRESH` message when changes have been made to the list of installed keyboards (or some other keyboard-related settings change). Keyman Engine (any arch) will handle this message, in any process, and post a message to the master controller (keyman.exe, TfrmKeyman7Main), `wm_keyman_refresh`, `KR_PRE_REFRESH`. Keyman Engine (keyman32) will handle _this_ message in the master controller thread context, and increment the global refresh tag. It also asks the other architecture host process(es) to update their global refresh counter to the same value (by posting `wm_keyman_refresh`, `KM_PRE_REFRESH` to those process(es).) This refresh tag is then compared on-demand in each thread to the thread's current refresh tag value, when a keyboard activation is received, on set focus, or on a keystroke, and the keyboard list is refreshed at that point. This means that keyboard installation no longer triggers a large amount of activity reloading all the keyboards on all windowed threads, but rather they will gradually reload as the user interacts with them. It also fixes #15333, which related to having threads with only child windows -- that is, that were parented by top-level windows from other threads -- which thus never received the global broadcast in the earlier design. For example, VSCode, Notepad in Win11, Chrome. Fixes: #15333 --- .../keyman32 => include}/keymancontrol.h | 5 + windows/src/desktop/kmshell/main/UfrmMain.pas | 25 +-- .../kmshell/startup/help/UfrmTextEditor.pas | 5 +- windows/src/desktop/kmshell/util/kmint.pas | 17 +- windows/src/engine/keyman/UfrmKeyman7Main.pas | 23 ++- windows/src/engine/keyman/kmint.pas | 3 +- windows/src/engine/keyman32/appint/aiTIP.cpp | 3 + windows/src/engine/keyman32/globals.h | 12 +- windows/src/engine/keyman32/k32_globals.cpp | 27 ++- windows/src/engine/keyman32/keyman32.cpp | 179 ++++++++---------- windows/src/engine/keyman32/keyman32.vcxproj | 2 +- .../engine/keyman32/keyman32.vcxproj.filters | 2 +- windows/src/engine/keyman32/keyman64.h | 1 - windows/src/engine/keyman32/keymanengine.h | 8 +- .../engine/keyman32/kmhook_callwndproc.cpp | 8 +- .../src/engine/keyman32/kmhook_getmessage.cpp | 30 +-- windows/src/engine/keymanhp/keymanhp.cpp | 13 +- windows/src/engine/keymanhp/keymanhp.h | 20 +- windows/src/engine/keymanhp/keymanhp.vcxproj | 1 + .../engine/keymanhp/keymanhp.vcxproj.filters | 3 + .../delphi/general/KeymanControlMessages.pas | 2 + 21 files changed, 220 insertions(+), 169 deletions(-) rename windows/{src/engine/keyman32 => include}/keymancontrol.h (94%) diff --git a/windows/src/engine/keyman32/keymancontrol.h b/windows/include/keymancontrol.h similarity index 94% rename from windows/src/engine/keyman32/keymancontrol.h rename to windows/include/keymancontrol.h index b25d0ceebf..a83749234a 100644 --- a/windows/src/engine/keyman32/keymancontrol.h +++ b/windows/include/keymancontrol.h @@ -67,6 +67,11 @@ #define KMC_LANGUAGEHOTKEY 20 //TOUCH #define KMC_CONTEXT 19 // 9.0.450.0 +// Cross-architecture comms +#define KMC_REGISTER_HOST_WINDOW_X64 23 +#define KMC_REGISTER_HOST_WINDOW_ARM64 24 + #define RWM_KEYMAN_CONTROL "WM_KEYMAN_CONTROL" +#define RWM_KEYMAN_CONTROL_W L"WM_KEYMAN_CONTROL" #define khLanguageSwitch 8 diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index e367a795ae..a8fdfe9845 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -623,20 +623,23 @@ procedure TfrmMain.AppEventsMessage(var Msg: tagMSG; var Handled: Boolean); begin inherited; - if (Msg.message = wm_keyman_refresh) and (Msg.WParam = KR_SETTINGS_CHANGED) then + if Msg.message = wm_keyman_refresh then begin - // If we generated this refresh ourselves, ignore it - if (kmcom <> nil) and (Msg.lParam <> KeymanEngineControl.LastRefreshToken) then + if Msg.WParam = KR_SETTINGS_CHANGED then begin - // The refresh event was generated by another process or thread - // - // We may receive the notification multiple times because it is broadcast - // to all top-level windows and we probably have a bunch of them. - // So, if we've already processed it, don't do so again. - if Msg.lParam <> LastRefreshToken then + // If we generated this refresh ourselves, ignore it + if (kmcom <> nil) and (Msg.lParam <> KeymanEngineControl.LastRefreshToken) then begin - LastRefreshToken := Msg.lParam; - DoRefresh; + // The refresh event was generated by another process or thread + // + // We may receive the notification multiple times because it is broadcast + // to all top-level windows and we probably have a bunch of them. + // So, if we've already processed it, don't do so again. + if Msg.lParam <> LastRefreshToken then + begin + LastRefreshToken := Msg.lParam; + DoRefresh; + end; end; end; end; diff --git a/windows/src/desktop/kmshell/startup/help/UfrmTextEditor.pas b/windows/src/desktop/kmshell/startup/help/UfrmTextEditor.pas index d67143e8ee..f0f46cfc6d 100644 --- a/windows/src/desktop/kmshell/startup/help/UfrmTextEditor.pas +++ b/windows/src/desktop/kmshell/startup/help/UfrmTextEditor.pas @@ -144,7 +144,7 @@ type procedure editorKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); private cefFonts: TframeCEFHost; - wm_keyman_control, wm_keyman_refresh: UINT; + wm_keyman_control: UINT; FUpdating: Boolean; FCheckFontsThread: TCheckFontsThread; FCheckFontKeyboards: TCheckFontKeyboards; @@ -248,7 +248,6 @@ procedure TfrmTextEditor.TntFormCreate(Sender: TObject); begin inherited; wm_keyman_control := RegisterWindowMessage('WM_KEYMAN_CONTROL'); - wm_keyman_refresh := RegisterWindowMessage('WM_KEYMANREFRESH'); HelpTopic := 'context/text-editor'; Caption := MsgFromId(SKTextEditorCaption); @@ -724,7 +723,7 @@ end; procedure TfrmTextEditor.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean); begin - if (Msg.message = wm_keyman_refresh) and (Msg.wParam = KR_REFRESH) then + if (Msg.message = wm_keyman_control) and (Msg.wParam = KMC_REFRESH) then begin StartCheckFontsThread; end; diff --git a/windows/src/desktop/kmshell/util/kmint.pas b/windows/src/desktop/kmshell/util/kmint.pas index 334e0b0409..c63abdbc6c 100644 --- a/windows/src/desktop/kmshell/util/kmint.pas +++ b/windows/src/desktop/kmshell/util/kmint.pas @@ -1,18 +1,18 @@ (* Name: kmint Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 1 Aug 2006 Modified Date: 26 Jun 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 01 Aug 2006 - mcdurdin - Remove old Keyman 6 integration 01 Aug 2006 - mcdurdin - Look for pxx file in startup folder to determine product to start 05 Nov 2007 - mcdurdin - I937, I1128 - Repair COM object if it fails at startup @@ -43,7 +43,6 @@ function LoadKMCOM: Boolean; const KEYMAN_LAYOUT_CUSTOM = $000005FE; - KR_REFRESH = 2; KR_SETTINGS_CHANGED = 3; const @@ -136,7 +135,7 @@ begin end; procedure CoFreeUnusedLibrariesEx(dwUnloadDelay, dwReserved: DWORD); stdcall; external 'ole32.dll'; - + function LoadKMCOM: Boolean; var msg: string; diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 4b51f337c1..fd5c24fcf2 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -351,6 +351,7 @@ var wm_keyman_globalswitch, wm_keyman_globalswitch_process, wm_keyman_control, wm_keyman_control_internal, wm_test_keyman_functioning: Cardinal; FEnableCrashTest: Boolean = False; + wm_keyman_refresh: Cardinal; const KMC_StartProduct = 0; @@ -697,6 +698,10 @@ begin end; function TfrmKeyman7Main.AppMessage(var Message: TMessage): Boolean; +var + hwndTextEditor: THandle; +const + KR_PRE_REFRESH = 1; begin if Message.Msg = WM_ACTIVATEAPP then begin @@ -728,6 +733,19 @@ begin end; Result := True; end + else if Message.Msg = wm_keyman_refresh then + begin + if Message.WParam = KR_PRE_REFRESH then + begin + // The text editor needs a special prod for keyboard changes + hwndTextEditor := FindWindow('TfrmTextEditor', nil); + if hwndTextEditor <> 0 then + begin + PostMessage(hwndTextEditor, wm_keyman_control, KMC_REFRESH, 0); + end; + end; + Result := False; + end else if Message.Msg = wm_keyman_control then begin Result := True; @@ -1241,13 +1259,13 @@ begin // #12905: icon is otherwise invalid TKeymanSentryClient.ReportHandledException(E, 'SetTrayIcon: Invalid icon for keyboard "'+kbd.ID+'"; icon filename "'+kbd.IconFilename+'"'); // Use the default app icon - rp.FTrayIcon.Icon.Assign(FTrayIcon); + rp.FTrayIcon.Icon.Assign(FTrayIcon); end; end else begin // Use the default app icon - rp.FTrayIcon.Icon.Assign(FTrayIcon); + rp.FTrayIcon.Icon.Assign(FTrayIcon); end; if VisualKeyboardVisible then @@ -2119,6 +2137,7 @@ initialization wm_keyman_globalswitch_process := RegisterWindowMessage('WM_KEYMAN_GLOBALSWITCH_PROCESS'); wm_keyman_control := RegisterWindowMessage('WM_KEYMAN_CONTROL'); wm_keyman_control_internal := RegisterWindowMessage('WM_KEYMAN_CONTROL_INTERNAL'); // I3933 + wm_keyman_refresh := RegisterWindowMessage('WM_KEYMANREFRESH'); ChangeWindowMessageFilter(wm_keyman_control, MSGFLT_ADD); ChangeWindowMessageFilter(wm_keyman_globalswitch, MSGFLT_ADD); diff --git a/windows/src/engine/keyman/kmint.pas b/windows/src/engine/keyman/kmint.pas index 76170998d0..6d1f937d18 100644 --- a/windows/src/engine/keyman/kmint.pas +++ b/windows/src/engine/keyman/kmint.pas @@ -48,8 +48,7 @@ uses MessageIdentifiers; const - KR_REQUEST_REFRESH = 0; // Send this to any window, which will make Keyman post a KR_REFRESH to all top-level windows - KR_REFRESH = 2; // Finally this message get sent to all the other top-level windows in the system. + KR_REQUEST_REFRESH = 0; // Send this to any window, which will make Keyman post a KR_PRE_REFRESH to controllers, which will cause all threads to eventually refresh keyboards list KR_SETTINGS_CHANGED = 3; function KeymanCustomisation: IKeymanCustomisation; diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index a2baada55b..6e67a9ac59 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; + + RefreshThreadKeyboardsIfRequired(); + 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/globals.h b/windows/src/engine/keyman32/globals.h index 9db8044394..02b799f1ed 100644 --- a/windows/src/engine/keyman32/globals.h +++ b/windows/src/engine/keyman32/globals.h @@ -88,8 +88,15 @@ public: static DWORD *ShiftState(); -#ifndef _WIN64 static LONG *RefreshTag(); + static LONG get_RefreshTag(); + +#ifndef _WIN64 + static HWND get_hwndHostX64(); + static HWND get_hwndHostARM64(); + + static HWND *hwndHostX64(); + static HWND *hwndHostARM64(); #endif static HHOOK get_hhookGetMessage(); @@ -216,8 +223,7 @@ typedef struct tagKEYMAN64THREADDATA BOOL CoreProcessEventRun; // True if core process event has been run BOOL FInRefreshKeyboards; - BOOL RefreshRequired; - LONG RefreshTag_Thread; // TODO: we may be able to eliminate this with our delayed refresh pattern? + LONG RefreshTag_Thread; /* Addin Globals */ diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index 807b246466..19d2b0a12e 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -95,6 +95,12 @@ BOOL isKeymanKeyboardActive = FALSE; static DWORD dwTlsIndex = TLS_OUT_OF_INDEXES; static CRITICAL_SECTION csGlobals; +// In order that every thread starts off needing a refresh, we set the starting +// refresh tag to 1, but the thread-level refresh tag will be 0 (initialized by +// `LocalAlloc(LPTR)`) +#define INITIAL_GLOBAL_REFRESH_TAG 1 +#define INITIAL_THREAD_REFRESH_TAG 0 + PKEYMAN64THREADDATA Globals_InitThread() { EnterCriticalSection(&csGlobals); @@ -269,9 +275,13 @@ static wchar_t __declspec(align(8)) static UINT f_vk_prefix = 0; -#ifndef _WIN64 __declspec(align(8)) static LONG - f_RefreshTag = 0; + f_RefreshTag = INITIAL_GLOBAL_REFRESH_TAG; + +#ifndef _WIN64 +__declspec(align(8)) static HWND + f_hwndHostX64 = 0, + f_hwndHostARM64 = 0; #endif static BOOL @@ -310,8 +320,15 @@ DWORD *Globals::InitialisingThread() { return &f_InitialisingThread; } // I43 DWORD *Globals::ShiftState() { return &f_ShiftState; } -#ifndef _WIN64 LONG *Globals::RefreshTag() { return &f_RefreshTag; } +LONG Globals::get_RefreshTag() { return f_RefreshTag; } + +#ifndef _WIN64 +HWND Globals::get_hwndHostX64() { return f_hwndHostX64; } +HWND *Globals::hwndHostX64() { return &f_hwndHostX64; } + +HWND Globals::get_hwndHostARM64() { return f_hwndHostARM64; } +HWND *Globals::hwndHostARM64() { return &f_hwndHostARM64; } #endif HHOOK Globals::get_hhookCallWndProc() { return f_hhookCallWndProc; } @@ -436,8 +453,10 @@ BOOL Globals::ResetControllers() // I3092 f_FSingleThread = FALSE; f_hwndIM = 0; f_hwndIMAlways = 0; + f_RefreshTag = INITIAL_GLOBAL_REFRESH_TAG; #ifndef _WIN64 - f_RefreshTag = 0; + f_hwndHostX64 = NULL; + f_hwndHostARM64 = NULL; #endif Globals::Unlock(); diff --git a/windows/src/engine/keyman32/keyman32.cpp b/windows/src/engine/keyman32/keyman32.cpp index 312865c61f..998fa1ebcf 100644 --- a/windows/src/engine/keyman32/keyman32.cpp +++ b/windows/src/engine/keyman32/keyman32.cpp @@ -106,6 +106,9 @@ HINSTANCE g_hInstance; +void RefreshKeyboards(); +void LoadKeyboardSettings(); + /*******************************************************************************************/ /* */ /* Keyman Initialisation */ @@ -216,7 +219,7 @@ BOOL InitThread() _td->FInitialising = TRUE; // Control re-entrancy, this is thread safe because the variable is per-thread - RefreshKeyboards(TRUE); + LoadKeyboardSettings(); _td->FInitialising = FALSE; // Control re-entrancy, this is thread safe because the variable is per-thread @@ -412,7 +415,9 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_Initialise(HWND Handle, BOOL UpdateActiveWindows(); *Globals::Keyman_Initialised() = TRUE; - RefreshKeyboards(TRUE); // I4786 + + // We initialize keyboards on the controller thread immediately + RefreshThreadKeyboardsIfRequired(); SendDebugMessageFormat("Keyman is now initialised"); @@ -576,27 +581,15 @@ extern "C" DWORD _declspec(dllexport) WINAPI GetActiveKeymanID() // //--------------------------------------------------------------------------------------------------------- -// This function prevents a refresh event from being processed more than once -// by a thread, for instance if a thread has multiple top-level windows -BOOL UpdateRefreshTag(LONG tag) // I1835 - Reduce chatter -{ +void HandleRefresh(int code, LONG tag) { PKEYMAN64THREADDATA _td = ThreadGlobals(); - if(!_td) return FALSE; - if(_td->RefreshTag_Thread != tag) - { - _td->RefreshTag_Thread = tag; - return TRUE; - } - return FALSE; -} + if(!_td) return; -void HandleRefresh(int code, LONG tag) -{ switch (code) { case KR_REQUEST_REFRESH: // This is sent by Keyman COM API, ApplyToRunningKeymanEngine - SendDebugMessageFormat("#### Refresh Requested ####"); + SendDebugMessageFormat("HandleRefresh(KR_REQUEST_REFRESH, %d); global refresh tag = %d; input thread refresh tag = %d", tag, Globals::get_RefreshTag(), _td->RefreshTag_Thread); // We ask the master controller window to tell all instances // of keyman32/keyman64 that a refresh is coming through @@ -608,41 +601,35 @@ void HandleRefresh(int code, LONG tag) break; case KR_PRE_REFRESH: -#ifndef _WIN64 - // We only need to broadcast the message from Win32; this avoids - // a double-broadcast which could happen if both keyman32 and keyman64 - // receive the message, as they have independently managed RefreshTags - // This RefreshTag is only ever incremented here by the master controller - // so this is a thread safe operation - tag = ++(*Globals::RefreshTag()); + SendDebugMessageFormat("HandleRefresh(KR_PRE_REFRESH, %d); global refresh tag = %d; input thread refresh tag = %d", tag, Globals::get_RefreshTag(), _td->RefreshTag_Thread); - if (UpdateRefreshTag(tag)) { - // The Keyman process gets the update first - RefreshKeyboards(FALSE); - PostMessage(HWND_BROADCAST, wm_keyman_refresh, KR_REFRESH, tag); + // The tag parameter will be zero for x86 controller, but will be the + // current tag for other architectures, coming from the x86 controller + + if(Globals::get_InitialisingThread() != GetCurrentThreadId()) { + SendDebugMessageFormat("HandleRefresh: unexpected KR_PRE_REFRESH on a non-controller thread"); + break; } -#endif - break; + #ifndef _WIN64 - case KR_REFRESH: - // All threads need to have their keyboard list - // refreshed after an update, but only once per - // refresh request - if (UpdateRefreshTag(tag)) { -#ifdef _WIN64 - if (Globals::get_InitialisingThread() == GetCurrentThreadId()) { - // If this is the keyman.x64 or keymanarm64 thread, then we should - // go ahead and process the refresh immediately so - // that global settings are updated - RefreshKeyboards(FALSE); - } - else -#endif - // We'll update when we are called into action - ScheduleRefresh(); + // This RefreshTag is only ever incremented here by the controller thread + // for x86 so this is a thread safe operation + *Globals::RefreshTag() = Globals::get_RefreshTag() + 1; + + if(Globals::get_hwndHostX64() != NULL) { + PostMessage(Globals::get_hwndHostX64(), wm_keyman_refresh, KR_PRE_REFRESH, Globals::get_RefreshTag()); } + if(Globals::get_hwndHostARM64() != NULL) { + PostMessage(Globals::get_hwndHostARM64(), wm_keyman_refresh, KR_PRE_REFRESH, Globals::get_RefreshTag()); + } +#else + *Globals::RefreshTag() = tag; + #endif + + SendDebugMessageFormat("HandleRefresh: new tag = %d", Globals::get_RefreshTag()); + RefreshThreadKeyboardsIfRequired(); break; } } @@ -695,6 +682,40 @@ void LoadBaseLayoutSettings() { // I4552 // I4583 delete reg; } +/** + * If (per-architecture) global refresh tag differs from the current thread + * refresh tag, that indicates that a change has happened to the list of loaded + * keyboards, and we need to refresh the list now. + */ +void RefreshThreadKeyboardsIfRequired() { + PKEYMAN64THREADDATA _td = ThreadGlobals(); + if (!_td) { + return; + } + + SendDebugMessageFormat("RefreshThreadKeyboardsIfRequired(): global refresh tag = %d; input thread refresh tag = %d", Globals::get_RefreshTag(), _td->RefreshTag_Thread); + + if (_td->RefreshTag_Thread != Globals::get_RefreshTag()) { + _td->RefreshTag_Thread = Globals::get_RefreshTag(); + RefreshKeyboards(); + } +} + +/** + * Read the keyboard switch hotkeys, and base layout settings + */ +void LoadKeyboardSettings() { +#ifndef _WIN64 + Hotkeys::Reload(); // I4326 // I4390 +#endif + + // Read global underlying keyboard flags + + if(Globals::get_InitialisingThread() == GetCurrentThreadId()) { + LoadBaseLayoutSettings(); // I4583 + } +} + // ----------------------------------------------------------------------------------------------- // - - // - RefreshKeyboards: .... - @@ -743,32 +764,8 @@ void RefreshKeyboardProfiles(INTKEYBOARDINFO *kp, BOOL isTransient) { } } -void ScheduleRefresh() { - if (GetCurrentThreadId() == GetWindowThreadProcessId(GetForegroundWindow(), NULL)) { - RefreshKeyboards(FALSE); - } - else { - PKEYMAN64THREADDATA _td = ThreadGlobals(); - if (!_td) { - return; - } - _td->RefreshRequired = TRUE; - } -} -void CheckScheduledRefresh() { - PKEYMAN64THREADDATA _td = ThreadGlobals(); - if (!_td) { - return; - } - if (_td->RefreshRequired) { - RefreshKeyboards(FALSE); - _td->RefreshRequired = FALSE; - } -} - -void RefreshKeyboards(BOOL Initialising) -{ +void RefreshKeyboards() { OutputThreadDebugString("RefreshKeyboards"); char sz[_MAX_FNAME]; char oldname[_MAX_FNAME]; @@ -784,29 +781,17 @@ void RefreshKeyboards(BOOL Initialising) oldname[0] = 0; - if(!Initialising) - { - if(_td->lpActiveKeyboard) - strcpy(oldname, _td->lpActiveKeyboard->Name); - - if(_td->lpActiveKeyboard) DeactivateDLLs(_td->lpActiveKeyboard); - _td->lpActiveKeyboard = NULL; - _td->ActiveKeymanID = KEYMANID_NONKEYMAN; + if(_td->lpActiveKeyboard) { + strcpy(oldname, _td->lpActiveKeyboard->Name); } + if(_td->lpActiveKeyboard) DeactivateDLLs(_td->lpActiveKeyboard); + _td->lpActiveKeyboard = NULL; + _td->ActiveKeymanID = KEYMANID_NONKEYMAN; + ReleaseKeyboards(TRUE); - /* Read the "keyboard off hotkey", simulate Alt+Gr, Hotkeys-Toggle flags */ - -#ifndef _WIN64 - Hotkeys::Reload(); // I4326 // I4390 -#endif - - // Read global underlying keyboard flags - - if(Globals::get_InitialisingThread() == GetCurrentThreadId()) { - LoadBaseLayoutSettings(); // I4583 - } + LoadKeyboardSettings(); /* Read the keyboards */ @@ -854,13 +839,13 @@ void RefreshKeyboards(BOOL Initialising) } delete reg; // I2714 - if(!Initialising) - for(int i = 0; i < _td->nKeyboards; i++) - if(!_strcmpi(_td->lpKeyboards[i].Name, oldname)) - { - SelectKeyboard(_td->lpKeyboards[i].KeymanID); // I3594 - break; - } + for(int i = 0; i < _td->nKeyboards; i++) { + if(!_strcmpi(_td->lpKeyboards[i].Name, oldname)) + { + SelectKeyboard(_td->lpKeyboards[i].KeymanID); // I3594 + break; + } + } _td->FInRefreshKeyboards = FALSE; diff --git a/windows/src/engine/keyman32/keyman32.vcxproj b/windows/src/engine/keyman32/keyman32.vcxproj index 5d246b7bbe..dd5643290e 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj +++ b/windows/src/engine/keyman32/keyman32.vcxproj @@ -1156,7 +1156,7 @@ - + diff --git a/windows/src/engine/keyman32/keyman32.vcxproj.filters b/windows/src/engine/keyman32/keyman32.vcxproj.filters index c5b38a60d1..a812f04249 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj.filters +++ b/windows/src/engine/keyman32/keyman32.vcxproj.filters @@ -210,7 +210,7 @@ Header Files - + Header Files diff --git a/windows/src/engine/keyman32/keyman64.h b/windows/src/engine/keyman32/keyman64.h index eb199722c7..c062c73cb6 100644 --- a/windows/src/engine/keyman32/keyman64.h +++ b/windows/src/engine/keyman32/keyman64.h @@ -108,7 +108,6 @@ #define KR_REQUEST_REFRESH 0 #define KR_PRE_REFRESH 1 -#define KR_REFRESH 2 #define KR_SETTINGS_CHANGED 3 // Broadcast when Keyman Configuration settings change /* WM_KEY* message analysis */ diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index 15bf2f1b59..6c769951e5 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -207,12 +207,8 @@ void _OutputThreadDebugString(char* s); /* Keyboard selection functions */ void HandleRefresh(int code, LONG tag); -void RefreshKeyboards(BOOL Initialising); -void CheckScheduledRefresh(); -void ScheduleRefresh(); +void RefreshThreadKeyboardsIfRequired(); void ReleaseKeyboards(BOOL Lock); -void CheckScheduledRefresh(); -void ScheduleRefresh(); /* Glossary conversion functions */ @@ -245,7 +241,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd); #include "keystate.h" #include "calldll.h" -#include "keymancontrol.h" +#include "..\..\..\include\keymancontrol.h" #include "keyboardoptions.h" #include "kmprocessactions.h" #include "appcontext.h" diff --git a/windows/src/engine/keyman32/kmhook_callwndproc.cpp b/windows/src/engine/keyman32/kmhook_callwndproc.cpp index eedb06bc0b..38d1fd3909 100644 --- a/windows/src/engine/keyman32/kmhook_callwndproc.cpp +++ b/windows/src/engine/keyman32/kmhook_callwndproc.cpp @@ -163,7 +163,7 @@ LRESULT _kmnCallWndProc(int nCode, WPARAM wParam, LPARAM lParam) { case WM_KILLFOCUS: { - CheckScheduledRefresh(); + RefreshThreadKeyboardsIfRequired(); HWND hwnd = IsLanguageSwitchWindowVisible(); // I4326 if(hwnd != NULL && !Globals::IsControllerThread(GetCurrentThreadId())) SendToLanguageSwitchWindow(hwnd, VK_ESCAPE, 0); // I3025 @@ -178,7 +178,7 @@ LRESULT _kmnCallWndProc(int nCode, WPARAM wParam, LPARAM lParam) } break; case WM_SETFOCUS: - CheckScheduledRefresh(); + RefreshThreadKeyboardsIfRequired(); if (IsSysTrayWindow(cp->hwnd)) // I2443 - always do the focus change now? really unsure about this one SendDebugMessageFormat("WM_SETFOCUS -- not hooking because IsSysTrayWindow"); else if (Globals::IsControllerThread(GetCurrentThreadId())) @@ -193,11 +193,11 @@ LRESULT _kmnCallWndProc(int nCode, WPARAM wParam, LPARAM lParam) case WM_ACTIVATE: if(cp->wParam == WA_ACTIVE || cp->wParam == WA_CLICKACTIVE) { - CheckScheduledRefresh(); + RefreshThreadKeyboardsIfRequired(); } break; case WM_INPUTLANGCHANGE: - CheckScheduledRefresh(); + RefreshThreadKeyboardsIfRequired(); SendDebugMessageFormat("WM_INPUTLANGCHANGE %x %x Hwnd=%x Parent=%x Focus=%x Active=%x", cp->wParam, cp->lParam, cp->hwnd, GetParent(cp->hwnd), GetFocus(), GetActiveWindow()); ReportActiveKeyboard(PC_UPDATE); // I4288 diff --git a/windows/src/engine/keyman32/kmhook_getmessage.cpp b/windows/src/engine/keyman32/kmhook_getmessage.cpp index 4c11dc31b5..7767bf394b 100644 --- a/windows/src/engine/keyman32/kmhook_getmessage.cpp +++ b/windows/src/engine/keyman32/kmhook_getmessage.cpp @@ -157,7 +157,7 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) BYTE scan = KEYMSG_LPARAM_SCAN(mp->lParam); BYTE keyTransitionEvent = KEYMSG_FLAG_TRANSITION(mp->lParam); - CheckScheduledRefresh(); + RefreshThreadKeyboardsIfRequired(); UpdateLastKeyCache(_td, mp->wParam, scan, keyTransitionEvent); switch (mp->wParam) { @@ -393,20 +393,24 @@ void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam) void ProcessWMKeymanControl(WPARAM wParam, LPARAM lParam) { - switch (wParam) { - case KMC_PROFILECHANGED: - { - if (!Globals::IsControllerThread(GetCurrentThreadId())) { - break; - } - WORD wAtom = HIWORD(lParam); - char atomStr[128]; - if (GlobalGetAtomName(wAtom, atomStr, 128)) { - isKeymanKeyboardActive = strstr(atomStr, cs_clsidKMTipTextService) != nullptr; - } - break; + if (!Globals::IsControllerThread(GetCurrentThreadId())) { + return; + } + + if (wParam == KMC_PROFILECHANGED) { + WORD wAtom = HIWORD(lParam); + char atomStr[128]; + if (GlobalGetAtomName(wAtom, atomStr, 128)) { + isKeymanKeyboardActive = strstr(atomStr, cs_clsidKMTipTextService) != nullptr; } } +#ifndef _WIN64 + else if(wParam == KMC_REGISTER_HOST_WINDOW_X64) { + *Globals::hwndHostX64() = (HWND) lParam; + } else if(wParam == KMC_REGISTER_HOST_WINDOW_ARM64) { + *Globals::hwndHostARM64() = (HWND) lParam; + } +#endif } /* diff --git a/windows/src/engine/keymanhp/keymanhp.cpp b/windows/src/engine/keymanhp/keymanhp.cpp index 81d386c000..483bbfa701 100644 --- a/windows/src/engine/keymanhp/keymanhp.cpp +++ b/windows/src/engine/keymanhp/keymanhp.cpp @@ -98,9 +98,7 @@ const PWSTR szFail_UnknownError = L"Unknown error %d", szFail_ErrorFormat = L"%s: %s (%d)", - szFail_ErrorFormat_OtherUnknown = L"%s: An unknown error occurred", - - szWindowClass_x86_Wnd = L"TfrmKeyman7Main"; // Do not localize + szFail_ErrorFormat_OtherUnknown = L"%s: An unknown error occurred"; //const char *szGPA_ChangeWindowMessageFilter = "ChangeWindowMessageFilter"; // Do not localize #define KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE_KEYMANHP KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE ARCH_LOG_FILE @@ -353,6 +351,15 @@ BOOL StartKeyman(HWND hWnd) if(!Keyman_Initialise(hWnd, FALSE)) return Fail(hWnd, szError_FailedToInitialise); + // Tell the x86 controller our architecture's controller window handle which + // will process wm_keyman_refresh messages (in keyman##.dll) + UINT wm_keyman_control = RegisterWindowMessage(RWM_KEYMAN_CONTROL_W); +#if defined(_M_X64) + PostMessage(hwndController, wm_keyman_control, KMC_REGISTER_HOST_WINDOW_X64, (LPARAM) hWnd); +#elif defined(_M_ARM64) + PostMessage(hwndController, wm_keyman_control, KMC_REGISTER_HOST_WINDOW_ARM64, (LPARAM) hWnd); +#endif + KeymanStarted = TRUE; return TRUE; diff --git a/windows/src/engine/keymanhp/keymanhp.h b/windows/src/engine/keymanhp/keymanhp.h index 28a1f391d5..823a16ad45 100644 --- a/windows/src/engine/keymanhp/keymanhp.h +++ b/windows/src/engine/keymanhp/keymanhp.h @@ -1,18 +1,18 @@ /* Name: keymanhp Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 11 Dec 2009 Modified Date: 11 Dec 2009 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version */ #pragma once @@ -23,9 +23,9 @@ #define WINVER 0x0600 // Change this to the appropriate value to target other versions of Windows. #endif -#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. #define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows. -#endif +#endif #ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later. #define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE. @@ -35,3 +35,5 @@ #include #include #include + +#include "..\..\..\include\keymancontrol.h" \ No newline at end of file diff --git a/windows/src/engine/keymanhp/keymanhp.vcxproj b/windows/src/engine/keymanhp/keymanhp.vcxproj index 3aedb14b70..37e63c2707 100644 --- a/windows/src/engine/keymanhp/keymanhp.vcxproj +++ b/windows/src/engine/keymanhp/keymanhp.vcxproj @@ -280,6 +280,7 @@ true + diff --git a/windows/src/engine/keymanhp/keymanhp.vcxproj.filters b/windows/src/engine/keymanhp/keymanhp.vcxproj.filters index 7c736dcc17..65eddfbff0 100644 --- a/windows/src/engine/keymanhp/keymanhp.vcxproj.filters +++ b/windows/src/engine/keymanhp/keymanhp.vcxproj.filters @@ -32,6 +32,9 @@ Header Files + + Header Files + diff --git a/windows/src/global/delphi/general/KeymanControlMessages.pas b/windows/src/global/delphi/general/KeymanControlMessages.pas index 601abee40a..ac143e2825 100644 --- a/windows/src/global/delphi/general/KeymanControlMessages.pas +++ b/windows/src/global/delphi/general/KeymanControlMessages.pas @@ -63,6 +63,8 @@ const WHR_INIT_FAILURE = 1; // 19.0 - hook failed to (re)install WHR_UNINIT_FAILURE = 2; // 19.0 - hook failed to uninstall + KMC_REGISTER_HOST_WINDOW_X64 = 23; + KMC_REGISTER_HOST_WINDOW_ARM64 = 24; PC_UPDATE = 0; PC_UPDATE_LANGUAGESWITCH = 1; From e7bc51fcda0a8e05cf8a1ff15d5a57a7658b85cb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 27 Aug 2026 07:53:40 +0200 Subject: [PATCH 2/2] chore(windows): formatting Co-authored-by: Ross Cruickshank --- windows/src/engine/keyman32/keyman32.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/windows/src/engine/keyman32/keyman32.cpp b/windows/src/engine/keyman32/keyman32.cpp index 998fa1ebcf..c60f674bf5 100644 --- a/windows/src/engine/keyman32/keyman32.cpp +++ b/windows/src/engine/keyman32/keyman32.cpp @@ -612,8 +612,7 @@ void HandleRefresh(int code, LONG tag) { break; } - #ifndef _WIN64 - +#ifndef _WIN64 // This RefreshTag is only ever incremented here by the controller thread // for x86 so this is a thread safe operation *Globals::RefreshTag() = Globals::get_RefreshTag() + 1; @@ -626,7 +625,7 @@ void HandleRefresh(int code, LONG tag) { } #else *Globals::RefreshTag() = tag; - #endif +#endif SendDebugMessageFormat("HandleRefresh: new tag = %d", Globals::get_RefreshTag()); RefreshThreadKeyboardsIfRequired();