mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-09 18:35:32 +00:00
fix(windows): reinstall low level keyboard hook if it gets removed
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
This commit is contained in:
parent
9e991a14e0
commit
711541be60
18 changed files with 484 additions and 16 deletions
|
|
@ -292,6 +292,8 @@ type
|
|||
procedure UnregisterControllerWindows; // I4731
|
||||
function IsSysTrayWindow(AHandle: THandle): Boolean;
|
||||
procedure GetTrayIconHandle; // I4731
|
||||
procedure WatchDogKeyEvent;
|
||||
|
||||
protected
|
||||
procedure DoInterfaceHotkey(Target: Integer);
|
||||
|
||||
|
|
@ -849,6 +851,14 @@ begin
|
|||
if (TKeymanHint(lParam) = KH_EXITPRODUCT) and (wParam = mrOk) then
|
||||
UnloadProduct;
|
||||
end;
|
||||
KMC_WATCHDOG_KEYEVENT:
|
||||
WatchDogKeyEvent;
|
||||
KMC_WATCHDOG_FAKEFREEZE:
|
||||
begin
|
||||
TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze begin', []);
|
||||
Sleep(5000);
|
||||
TDebugLogClient.Instance.WriteMessage('kmc_fakefreeze end', []);
|
||||
end;
|
||||
//TOUCH KMC_CONTEXT:
|
||||
//TOUCH begin
|
||||
//TOUCH if LParam <> 0 then
|
||||
|
|
@ -857,6 +867,13 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.WatchDogKeyEvent;
|
||||
begin
|
||||
TDebugLogClient.Instance.WriteMessage('Attempting to send WatchDogKeyEvent', []);
|
||||
if kmint.KeymanEngineControl <> nil then
|
||||
kmint.KeymanEngineControl.WatchDogKeyEvent;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer);
|
||||
begin
|
||||
if not Assigned(FRunningProduct) then
|
||||
|
|
|
|||
105
windows/src/engine/keyman32/LowLevelHookWatchDog.cpp
Normal file
105
windows/src/engine/keyman32/LowLevelHookWatchDog.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-11-17
|
||||
*
|
||||
* Handle situations where Windows silently uninstalls our low level keyboard
|
||||
* hook, and dynamically reinstall it. The hook can be uninstalled when
|
||||
* keyman.exe becomes unresponsive for more than 200msec (default timeout) --
|
||||
* this can be due to something Keyman is doing, but it could also happen during
|
||||
* high system load. The hook will only be uninstalled if a key is pressed while
|
||||
* Keyman is unresponsive.
|
||||
*
|
||||
* Per Windows documentation:
|
||||
* https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks
|
||||
*
|
||||
* This works by tracking last event times for both the WH_KEYBOARD_LL hook and
|
||||
* the WH_GETMESSAGE hook. These two hooks both receive key events, but the
|
||||
* WH_GETMESSAGE hook runs in the focused thread context, whereas WH_KEYBOARD_LL
|
||||
* runs in keyman.exe main thread context. The WH_KEYBOARD_LL hook receives the
|
||||
* key event first.
|
||||
*
|
||||
* This (static) class is only used in the keyman.exe main thread context.
|
||||
*
|
||||
* Notification of HookIsAlive is a straightforward call from the low level
|
||||
* keyboard hook procedure, but the KeyEventReceivedInGetMessageProc function
|
||||
* must be signalled across processes. We have chosen to do this with a posted
|
||||
* message to the master controller, which is then handled by
|
||||
* UfrmKeyman7Main.pas, and passed through Keyman_WatchDogKeyEvent.
|
||||
*
|
||||
* The message handler was implemented in UfrmKeyman7Main.pas rather than in
|
||||
* kmhook_getmessage.cpp, because it appears that kmnGetMessageProc does not see
|
||||
* messages that were posted by it or child functions (this is by observation, not
|
||||
* documentation -- I was not able to find documentation on this, but surmise it
|
||||
* is to prevent deadlock/infinite loop scenarios, which could easily lockup
|
||||
* Windows entirely).
|
||||
*
|
||||
* No key data is passed in the event, only the information that a key was
|
||||
* pressed.
|
||||
*/
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
/**
|
||||
* minimum number of milliseconds between the last LowLevel and GetMessage
|
||||
* events before we assume the low level hook has been uninstalled, and we need
|
||||
* to reinstall it.
|
||||
*/
|
||||
#define WATCHDOG_THRESHOLD 1000
|
||||
|
||||
static ULONGLONG LastLowLevelEventTick = 0;
|
||||
static ULONGLONG LastGetMessageEventTick = 0;
|
||||
|
||||
void LowLevelHookWatchDog::HookIsAlive() {
|
||||
// ULONGLONG Previous = LastLowLevelEventTick;
|
||||
LastLowLevelEventTick = GetTickCount64();
|
||||
// SendDebugMessageFormat("LowLevelHookWatchDog::HookIsAlive currentLL=%llu currentGM=%llu (lastLL=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous);
|
||||
}
|
||||
|
||||
void LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc() {
|
||||
// ULONGLONG Previous = LastGetMessageEventTick;
|
||||
LastGetMessageEventTick = GetTickCount64();
|
||||
// SendDebugMessageFormat("LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc currentLL=%llu currentGM=%llu (lastGM=%llu)", LastLowLevelEventTick, LastGetMessageEventTick, Previous);
|
||||
|
||||
// This is a good place to check if we are still alive -- shortly after each
|
||||
// keystroke event in the GetMessage hook, as this means at worst we'll have
|
||||
// one or two keystrokes where Keyman must recover
|
||||
if(!CheckIfHookIsAlive()) {
|
||||
ReinstallHook();
|
||||
}
|
||||
}
|
||||
|
||||
bool LowLevelHookWatchDog::CheckIfHookIsAlive() {
|
||||
if(LastGetMessageEventTick < LastLowLevelEventTick) {
|
||||
// this shouldn't be possible but rather safe than sorry
|
||||
return true;
|
||||
}
|
||||
|
||||
return LastGetMessageEventTick - LastLowLevelEventTick < WATCHDOG_THRESHOLD;
|
||||
}
|
||||
|
||||
void LowLevelHookWatchDog::ReinstallHook() {
|
||||
//keyman32.cpp:
|
||||
SendDebugMessageFormat(
|
||||
"Attempting to reinstall hook because watchdog threshold exceeded by %llu msec (last LL=%llu last GM=%llu)",
|
||||
LastGetMessageEventTick - LastLowLevelEventTick,
|
||||
LastLowLevelEventTick,
|
||||
LastGetMessageEventTick
|
||||
);
|
||||
|
||||
if(!RestartLowLevelHook()) {
|
||||
SendDebugMessage("Attempt to reinstall low level hook may have failed, see previous log messages");
|
||||
} else {
|
||||
SendDebugMessage("Attempt to reinstall low level hook succeeded");
|
||||
}
|
||||
|
||||
// We should assume the hook is alive at this point to avoid repeated resets
|
||||
HookIsAlive();
|
||||
}
|
||||
|
||||
#ifndef _WIN64
|
||||
extern "C" void __declspec(dllexport) WINAPI Keyman_WatchDogKeyEvent() {
|
||||
SendDebugMessageFormat("Keyman_WatchDogKeyEvent");
|
||||
LowLevelHookWatchDog::KeyEventReceivedInGetMessageProc();
|
||||
}
|
||||
#endif
|
||||
32
windows/src/engine/keyman32/LowLevelHookWatchDog.h
Normal file
32
windows/src/engine/keyman32/LowLevelHookWatchDog.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-11-17
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Watch for scenarios where the low level keyboard hook may be
|
||||
* uninstalled by Windows, e.g. when there is heavy system load, and
|
||||
* reinstall it.
|
||||
*/
|
||||
class LowLevelHookWatchDog {
|
||||
public:
|
||||
/**
|
||||
* @brief Update the watchdog timestamp to current time, because the low
|
||||
* level hook is receiving messages successfully
|
||||
*/
|
||||
static void HookIsAlive();
|
||||
|
||||
/**
|
||||
* @brief Update the watchdog GetMessageProc timestamp to current time;
|
||||
* this is called by the Keyman_WatchDogKeyEvent function, when
|
||||
* a message posted from the GetMessageProc hook is received by
|
||||
* keyman.exe.
|
||||
*/
|
||||
static void KeyEventReceivedInGetMessageProc();
|
||||
|
||||
private:
|
||||
static bool CheckIfHookIsAlive();
|
||||
static void ReinstallHook();
|
||||
};
|
||||
|
||||
|
|
@ -141,6 +141,8 @@ LRESULT _kmnLowLevelKeyboardProc(
|
|||
|
||||
SendDebugEntry();
|
||||
|
||||
LowLevelHookWatchDog::HookIsAlive();
|
||||
|
||||
PKBDLLHOOKSTRUCT hs = (PKBDLLHOOKSTRUCT) lParam;
|
||||
|
||||
BOOL extended = hs->flags & LLKHF_EXTENDED ? TRUE : FALSE;
|
||||
|
|
|
|||
|
|
@ -279,6 +279,24 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_GetInitialised(BOOL *FSingleA
|
|||
return Globals::get_Keyman_Initialised();
|
||||
}
|
||||
|
||||
#ifndef _WIN64
|
||||
BOOL InitLowLevelHook() {
|
||||
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
|
||||
|
||||
*Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124
|
||||
return Globals::get_hhookLowLevelKeyboardProc() != NULL;
|
||||
}
|
||||
|
||||
BOOL UninitLowLevelHook() {
|
||||
BOOL RetVal = TRUE;
|
||||
if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124
|
||||
RetVal = FALSE;
|
||||
|
||||
*Globals::hhookLowLevelKeyboardProc() = NULL;
|
||||
return RetVal;
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOL InitHooks()
|
||||
{
|
||||
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
|
||||
|
|
@ -286,7 +304,7 @@ BOOL InitHooks()
|
|||
*Globals::hhookGetMessage() = SetWindowsHookExW(WH_GETMESSAGE, (HOOKPROC) kmnGetMessageProc, hinst, Globals::get_FSingleThread());
|
||||
*Globals::hhookCallWndProc() = SetWindowsHookExW(WH_CALLWNDPROC, (HOOKPROC) kmnCallWndProc, hinst, Globals::get_FSingleThread());
|
||||
#ifndef _WIN64
|
||||
*Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124
|
||||
InitLowLevelHook();
|
||||
#endif;
|
||||
|
||||
return
|
||||
|
|
@ -304,19 +322,14 @@ BOOL UninitHooks()
|
|||
|
||||
if(Globals::get_hhookGetMessage() && !UnhookWindowsHookEx(Globals::get_hhookGetMessage()))
|
||||
RetVal = FALSE;
|
||||
else
|
||||
*Globals::hhookGetMessage() = NULL;
|
||||
*Globals::hhookGetMessage() = NULL;
|
||||
|
||||
if(Globals::get_hhookCallWndProc() && !UnhookWindowsHookEx(Globals::get_hhookCallWndProc()))
|
||||
RetVal = FALSE;
|
||||
else
|
||||
*Globals::hhookCallWndProc() = NULL;
|
||||
*Globals::hhookCallWndProc() = NULL;
|
||||
|
||||
#ifndef _WIN64
|
||||
if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124
|
||||
RetVal = FALSE;
|
||||
else
|
||||
*Globals::hhookLowLevelKeyboardProc() = NULL;
|
||||
RetVal = UninitLowLevelHook() && RetVal;
|
||||
#endif
|
||||
|
||||
return RetVal;
|
||||
|
|
@ -473,6 +486,27 @@ extern "C" BOOL _declspec(dllexport) WINAPI Keyman_RestartEngine()
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL RestartLowLevelHook() {
|
||||
BOOL result=true;
|
||||
|
||||
#ifndef _WIN64
|
||||
if(!Globals::get_Keyman_Initialised()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!UninitLowLevelHook()) {
|
||||
SendDebugMessageFormat("Failed to uninstall low level hook. GetLastError = %d", GetLastError());
|
||||
result = FALSE;
|
||||
}
|
||||
if(!InitLowLevelHook()) {
|
||||
SendDebugMessageFormat("Failed to install low level hook. GetLastError = %d", GetLastError());
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Utility guff functions
|
||||
|
|
|
|||
|
|
@ -49,3 +49,5 @@ EXPORTS
|
|||
Keyman_UnregisterControllerThread
|
||||
|
||||
SetCustomPostKeyCallback
|
||||
|
||||
Keyman_WatchDogKeyEvent
|
||||
|
|
|
|||
|
|
@ -361,6 +361,7 @@
|
|||
<ClCompile Include="kmprocess.cpp" />
|
||||
<ClCompile Include="$(KEYMAN_ROOT)\common\windows\cpp\src\registry.cpp" />
|
||||
<ClCompile Include="kmprocessactions.cpp" />
|
||||
<ClCompile Include="LowLevelHookWatchDog.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
|
|
@ -450,6 +451,7 @@
|
|||
<ClInclude Include="keyman-debug-etw.h" />
|
||||
<ClInclude Include="keyeventsenderthread.h" />
|
||||
<ClInclude Include="keystate.h" />
|
||||
<ClInclude Include="LowLevelHookWatchDog.h" />
|
||||
<ClInclude Include="$(KEYMAN_ROOT)\common\windows\cpp\include\registry.h" />
|
||||
<ClInclude Include="security.h" />
|
||||
<ClInclude Include="serialkeyeventclient.h" />
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@
|
|||
|
||||
#define KMC_PROFILECHANGED 18 // 9.0.426.0 // I3933
|
||||
|
||||
#define KMC_HINTRESPONSE 19 // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum
|
||||
|
||||
#define KMC_WATCHDOG_FAKEFREEZE 20 // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability
|
||||
#define KMC_WATCHDOG_KEYEVENT 21 // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread
|
||||
|
||||
|
||||
#define PC_UPDATE 0 // Tell Keyman to update its display of active keyboard
|
||||
#define PC_UPDATE_LANGUAGESWITCH 1 // Tell Keyman to update its display of active keyboard and then open language switch form
|
||||
#define PC_HOTKEYCHANGE 2 // Tell Keyman that a hotkey was pressed to switch keyboard
|
||||
|
|
|
|||
|
|
@ -263,6 +263,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd);
|
|||
|
||||
#include "k32_tsf.h"
|
||||
#include "k32_visualkeyboardinterface.h"
|
||||
#include "LowLevelHookWatchDog.h"
|
||||
|
||||
void ReportActiveKeyboard(WORD wCommand); // I3933 // I3949
|
||||
void SelectKeyboardHKL(DWORD hkl, BOOL foreground); // I3933 // I3949 // I4271
|
||||
|
|
@ -273,4 +274,6 @@ void ProcessModifierChange(UINT key, BOOL isUp, BOOL isExtended); // I4793
|
|||
BOOL SetupCoreEnvironment(km_core_option_item **test_env_opts);
|
||||
void DeleteCoreEnvironment(km_core_option_item *test_env_opts);
|
||||
|
||||
BOOL RestartLowLevelHook();
|
||||
|
||||
#endif // _KEYMANENGINE_H
|
||||
|
|
|
|||
|
|
@ -146,6 +146,15 @@ LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|||
}
|
||||
|
||||
if ((mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN || mp->message == WM_KEYUP || mp->message == WM_SYSKEYUP)) { // I4642
|
||||
|
||||
// To help our low level keyboard hook recover if system load causes it to
|
||||
// be uninstalled, we tell the controller process that we should have seen a
|
||||
// keystroke event in the low level keyboard hook; this gets passed into the
|
||||
// LowLevelHookWatchDog via keyman.exe
|
||||
if(mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN) {
|
||||
Globals::PostMasterController(wm_keyman_control, KMC_WATCHDOG_KEYEVENT, 0);
|
||||
}
|
||||
|
||||
BYTE scan = KEYMSG_LPARAM_SCAN(mp->lParam);
|
||||
CheckScheduledRefresh();
|
||||
_td->LastScanCode = scan;
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ type
|
|||
TKeyman32ControllerSendMessageFunction = function(msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
|
||||
TKeyman32ControllerPostMessageFunction = procedure(msg: UINT; wParam: WPARAM; lParam: LPARAM); stdcall;
|
||||
TKeyman32UpdateTouchPanelVisibilityFunction = procedure(Value: BOOL); stdcall;
|
||||
TKeyman32WatchDogKeyEvent = procedure; stdcall;
|
||||
|
||||
TKeymanControl = class(TKeymanAutoObject, IKeymanCustomisationAccess, IIntKeymanControl, IKeymanControl, IKeymanEngineControl)
|
||||
private
|
||||
|
|
@ -97,6 +98,7 @@ type
|
|||
FKeyman_PostMasterController: TKeyman32ControllerPostMessageFunction;
|
||||
FKeyman_StartExit: TKeyman32ExitFunction; // I3092
|
||||
FKeyman_UpdateTouchPanelVisibility: TKeyman32UpdateTouchPanelVisibilityFunction;
|
||||
FKeyman_WatchDogKeyEvent: TKeyman32WatchDogKeyEvent;
|
||||
procedure LoadKeyman32;
|
||||
procedure StartKeyman32;
|
||||
procedure Do_Keyman_Exit;
|
||||
|
|
@ -148,6 +150,7 @@ type
|
|||
procedure DisableUserInterface; safecall;
|
||||
procedure EnableUserInterface; safecall;
|
||||
procedure UpdateTouchPanelVisibility(Value: Boolean); safecall;
|
||||
procedure WatchDogKeyEvent; safecall; // 32 bit only
|
||||
|
||||
procedure DiagnosticTestException; safecall;
|
||||
|
||||
|
|
@ -524,6 +527,25 @@ begin
|
|||
{$ENDIF}
|
||||
end;
|
||||
|
||||
(**
|
||||
* Tell the LowLevelHookWatchDog that a key event has been received
|
||||
*)
|
||||
procedure TKeymanControl.WatchDogKeyEvent;
|
||||
begin
|
||||
{$IFDEF WIN64}
|
||||
Error(Cardinal(E_NOTIMPL));
|
||||
{$ELSE}
|
||||
KL.MethodEnter(Self, 'WatchDogKeyEvent', []);
|
||||
try
|
||||
LoadKeyman32;
|
||||
FKeyman_WatchDogKeyEvent;
|
||||
finally
|
||||
KL.MethodExit(Self, 'WatchDogKeyEvent');
|
||||
end;
|
||||
{$ENDIF}
|
||||
end;
|
||||
|
||||
|
||||
procedure TKeymanControl.RegisterControllerThread(Value: LongWord);
|
||||
begin
|
||||
{$IFDEF WIN64}
|
||||
|
|
@ -750,6 +772,9 @@ begin
|
|||
@FKeyman_SendMasterController := ProcAddr('Keyman_SendMasterController');
|
||||
@FKeyman_PostMasterController := ProcAddr('Keyman_PostMasterController');
|
||||
@FKeyman_UpdateTouchPanelVisibility := ProcAddr('Keyman_UpdateTouchPanelVisibility');
|
||||
{$IFNDEF WIN64}
|
||||
@FKeyman_WatchDogKeyEvent := ProcAddr('Keyman_WatchDogKeyEvent');
|
||||
{$ENDIF}
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ const
|
|||
|
||||
KMC_HINTRESPONSE = 19; // 14.0 HIWORD(wParam) = ModalResult, lParam = hint enum
|
||||
|
||||
KMC_WATCHDOG_FAKEFREEZE = 20; // 19.0 - pause Keyman for 5 seconds for debug purposes to test stability
|
||||
KMC_WATCHDOG_KEYEVENT = 21; // 19.0 - let the LowLevelHookWatchDog know that input has happened on another thread
|
||||
|
||||
PC_UPDATE = 0;
|
||||
PC_UPDATE_LANGUAGESWITCH = 1;
|
||||
PC_HOTKEYCHANGE = 2;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
(*
|
||||
Name: KeymanControlRestart
|
||||
Copyright: Copyright (C) SIL International.
|
||||
Documentation:
|
||||
Description:
|
||||
Documentation:
|
||||
Description:
|
||||
Create Date: 19 Jun 2007
|
||||
|
||||
Modified Date: 19 Jun 2007
|
||||
Authors: mcdurdin
|
||||
Related Files:
|
||||
Dependencies:
|
||||
Related Files:
|
||||
Dependencies:
|
||||
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
Bugs:
|
||||
Todo:
|
||||
Notes:
|
||||
History: 19 Jun 2007 - mcdurdin - Initial version
|
||||
*)
|
||||
unit KeymanEngineControl;
|
||||
|
|
@ -43,6 +43,10 @@ type
|
|||
procedure UnregisterMasterController(Value: LongWord); safecall;
|
||||
procedure RegisterControllerThread(Value: LongWord); safecall;
|
||||
procedure UnregisterControllerThread(Value: LongWord); safecall;
|
||||
|
||||
// New in 19.0
|
||||
|
||||
procedure WatchDogKeyEvent; safecall; // 32 bit only
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
|
|||
1
windows/src/support/fakefreeze/.gitignore
vendored
Normal file
1
windows/src/support/fakefreeze/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
x64/
|
||||
39
windows/src/support/fakefreeze/fakefreeze.cpp
Normal file
39
windows/src/support/fakefreeze/fakefreeze.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by mcdurdin on 2025-11-17
|
||||
*
|
||||
* Tell keyman.exe to pause for 5 seconds to force Windows to silently uninstall
|
||||
* the low level keyboard hook, per Windows documentation at
|
||||
* https://learn.microsoft.com/en-us/windows/win32/winmsg/lowlevelkeyboardproc#remarks
|
||||
*
|
||||
* See LowLevelHookWatchDog.cpp for more detail
|
||||
*/
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define KMC_WATCHDOG_FAKEFREEZE 20
|
||||
|
||||
int main() {
|
||||
std::cout << "Posting a freeze message to Keyman master controller\n";
|
||||
UINT wm_keyman_control = RegisterWindowMessage(L"WM_KEYMAN_CONTROL");
|
||||
HWND hwnd = FindWindow(L"TfrmKeyman7Main", NULL);
|
||||
if (hwnd == NULL) {
|
||||
std::cout << "Keyman master controller window not found\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!PostMessage(hwnd, wm_keyman_control, KMC_WATCHDOG_FAKEFREEZE, 0)) {
|
||||
std::cout << "Error calling Keyman KMC_WATCHDOG_FAKEFREEZE\n";
|
||||
}
|
||||
std::cout << "Sleeping 5 seconds...\n";
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
Sleep(1000);
|
||||
std::cout << "..." << i << "\n";
|
||||
}
|
||||
std::cout << "Keyman should be responsive again now\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
31
windows/src/support/fakefreeze/fakefreeze.sln
Normal file
31
windows/src/support/fakefreeze/fakefreeze.sln
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36616.10 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fakefreeze", "fakefreeze.vcxproj", "{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x64.Build.0 = Debug|x64
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Debug|x86.Build.0 = Debug|Win32
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.ActiveCfg = Release|x64
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x64.Build.0 = Release|x64
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.ActiveCfg = Release|Win32
|
||||
{BDDE094F-B409-4DF5-AA9E-6DEC09B362CE}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BA12682B-94DC-4E10-AE3F-743D64317F12}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
131
windows/src/support/fakefreeze/fakefreeze.vcxproj
Normal file
131
windows/src/support/fakefreeze/fakefreeze.vcxproj
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{bdde094f-b409-4df5-aa9e-6dec09b362ce}</ProjectGuid>
|
||||
<RootNamespace>fakefreeze</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="fakefreeze.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
22
windows/src/support/fakefreeze/fakefreeze.vcxproj.filters
Normal file
22
windows/src/support/fakefreeze/fakefreeze.vcxproj.filters
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="fakefreeze.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
Add table
Reference in a new issue