mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-15 02:17:32 +00:00
fix(windows): make keymanx64 responsible for its own lifecycle
Fixes #4976. Fixes KEYMAN-WINDOWS-2K. Fixes KEYMAN-WINDOWS-3F. Fixes KEYMAN-WINDOWS-5A. Fixes KEYMAN-WINDOWS-3E. This fix reworks keymanx64's lifecycle, moving responsibility for process shutdown from keyman.exe to keymanx64.exe. This eliminates the need for interprocess communication, and simplifies the startup and shutdown of keymanx64.exe. Removing this means that we can more safely handle situations where two instances of keymanx64 may be started, as one of them will rapidly terminate when it discovers that its parent process has disappeared.
This commit is contained in:
parent
617330d8e1
commit
53b37d44f0
3 changed files with 130 additions and 129 deletions
|
|
@ -253,7 +253,6 @@ type
|
|||
procedure WMUserStart(var Message: TMessage); message WM_USER_Start;
|
||||
procedure WMUserParameterPass(var Message: TMessage); message WM_USER_ParameterPass;
|
||||
procedure WMUserSendFontChange(var Message: TMessage); message WM_USER_SendFontChange;
|
||||
procedure WMUserPlatformComms(var Message: TMessage); message WM_USER_PlatformComms;
|
||||
procedure WMUserVisualKeyboardClosed(var Message: TMessage); message WM_USER_VisualKeyboardClosed; // I4243
|
||||
procedure SetTrayIcon(rp: TRunningProduct; kbd: IKeymanKeyboardInstalled);
|
||||
procedure TrayIconMouseUp(Sender: TObject; Button: TMouseButton;
|
||||
|
|
@ -272,9 +271,7 @@ type
|
|||
//procedure SnapToolHelp;
|
||||
procedure RecreateTaskbarIcons;
|
||||
function StartKeymanEngine: Boolean;
|
||||
procedure SendPlatformComms64(msg, value: DWORD);
|
||||
procedure ClosePlatformComms64;
|
||||
procedure StartPlatformComms64;
|
||||
procedure StartKeymanX64;
|
||||
procedure OpenTextEditor;
|
||||
//function GetCachedKeymanID(hkl: DWORD): DWORD;
|
||||
procedure ShowLanguageSwitchForm;
|
||||
|
|
@ -379,10 +376,6 @@ const
|
|||
const
|
||||
MSGFLT_ADD = 1;
|
||||
|
||||
const
|
||||
PC_CLOSE = 2;
|
||||
PC_GETAPPLICATION = 4;
|
||||
|
||||
const
|
||||
KEYMANID_NONKEYMAN = -1;
|
||||
KEYMANID_INVALID: Cardinal = $FFFFFFFD;
|
||||
|
|
@ -555,8 +548,6 @@ begin
|
|||
|
||||
FreeAndNil(FInputPane);
|
||||
|
||||
ClosePlatformComms64;
|
||||
|
||||
UnregisterHotkeys;
|
||||
|
||||
with TRegistryErrorControlled.Create do // I2890
|
||||
|
|
@ -1289,14 +1280,6 @@ begin
|
|||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.WMUserPlatformComms(var Message: TMessage);
|
||||
begin
|
||||
case Message.wParam of
|
||||
PC_GETAPPLICATION:
|
||||
Message.Result := Application.Handle; // I3758
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.WMUserSendFontChange(var Message: TMessage);
|
||||
begin
|
||||
PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
|
||||
|
|
@ -1368,7 +1351,7 @@ begin
|
|||
|
||||
kmint.KeymanEngineControl.RestartEngine; // I1486
|
||||
|
||||
StartPlatformComms64;
|
||||
StartKeymanX64;
|
||||
|
||||
RegisterHotkeys;
|
||||
end;
|
||||
|
|
@ -1969,25 +1952,6 @@ begin
|
|||
FInUpdateOSKVisibility := False;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.SendPlatformComms64(msg, value: DWORD);
|
||||
var
|
||||
hwnd: THandle;
|
||||
begin
|
||||
// TODO: Test Wow64
|
||||
KL.Log('SendPlatformComms64 ENTER: %d %d', [msg, value]);
|
||||
|
||||
hwnd := FindWindow('Keymanx64', nil);
|
||||
if hwnd <> 0 then
|
||||
PostMessage(hwnd, WM_USER_PlatformComms, msg, value);
|
||||
|
||||
KL.Log('SendPlatformComms64 EXIT');
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.ClosePlatformComms64;
|
||||
begin
|
||||
SendPlatformComms64(PC_CLOSE, 0);
|
||||
end;
|
||||
|
||||
type
|
||||
TLangSwitchRefreshWatcher = class(TThread)
|
||||
private
|
||||
|
|
@ -2013,28 +1977,46 @@ begin
|
|||
FLangSwitchRefreshWatcher.Start;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.StartPlatformComms64;
|
||||
procedure TfrmKeyman7Main.StartKeymanX64;
|
||||
var
|
||||
dir, s: WideString;
|
||||
sei: TShellExecuteInfoW;
|
||||
cmd: string;
|
||||
si: TStartupInfoW;
|
||||
pi: TProcessInformation;
|
||||
v: PWideChar;
|
||||
processHandle: THandle;
|
||||
begin
|
||||
if not IsWow64 then Exit; // I4374
|
||||
|
||||
dir := ExtractFilePath(ParamStr(0));
|
||||
s := dir + 'keymanx64.exe';
|
||||
try
|
||||
FillChar(si, sizeof(TStartupInfoW), 0);
|
||||
FillChar(pi, sizeof(TProcessInformation), 0);
|
||||
si.cb := sizeof(TStartupInfo);
|
||||
si.dwFlags := STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow := SW_HIDE;
|
||||
|
||||
if not FileExists(s) then Exit; // Keyman x64 is not installed
|
||||
|
||||
FillChar(sei, SizeOf(sei), 0);
|
||||
sei.cbSize := SizeOf(sei);
|
||||
sei.Wnd := Handle;
|
||||
sei.lpVerb := 'open';
|
||||
sei.lpFile := PWideChar(s);
|
||||
sei.lpParameters := '';
|
||||
sei.lpDirectory := PWideChar(dir);
|
||||
sei.nShow := SW_SHOW;
|
||||
|
||||
if not ShellExecuteExW(@sei) then Exit; // log
|
||||
if not DuplicateHandle(GetCurrentProcess, GetCurrentProcess,
|
||||
GetCurrentProcess, @processHandle, 0, True, DUPLICATE_SAME_ACCESS) then
|
||||
RaiseLastOSError;
|
||||
cmd := Format('%s%s %d %d %d', [ExtractFilePath(ParamStr(0)), 'keymanx64.exe', processHandle, Handle, Application.Handle]);
|
||||
// Duplicate the string because CreateProcess requires a mutable buffer, so
|
||||
// this guarantees it
|
||||
v := StrNew(PWideChar(cmd));
|
||||
try
|
||||
if not CreateProcessW(nil, v, nil, nil, True, NORMAL_PRIORITY_CLASS, nil, nil, si, pi) then
|
||||
RaiseLastOSError;
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
finally
|
||||
StrDispose(v);
|
||||
end;
|
||||
except
|
||||
on E:Exception do
|
||||
begin
|
||||
// We're going to handle any exceptions here but we'd like to know that
|
||||
// they happened
|
||||
TKeymanSentryClient.ReportHandledException(E, 'Error starting keymanx64', True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TfrmKeyman7Main.HotkeyWndProc(var Message: TMessage);
|
||||
|
|
@ -2217,5 +2199,4 @@ initialization
|
|||
ChangeWindowMessageFilter(wm_keyman_globalswitch, MSGFLT_ADD);
|
||||
ChangeWindowMessageFilter(wm_keyman_globalswitch_process, MSGFLT_ADD);
|
||||
ChangeWindowMessageFilter(wm_keyman_control_internal, MSGFLT_ADD); // I3933
|
||||
ChangeWindowMessageFilter(WM_USER_PlatformComms, MSGFLT_ADD);
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -25,20 +25,13 @@
|
|||
#include "keymansentry.h"
|
||||
|
||||
// Forward declarations of functions included in this code module
|
||||
|
||||
ATOM MyRegisterClass(HINSTANCE hInstance);
|
||||
BOOL InitInstance(HINSTANCE, int);
|
||||
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
BOOL Fail(HWND, PWSTR);
|
||||
BOOL UniqueInstance();
|
||||
|
||||
// Global constants
|
||||
|
||||
//#define KMC_GETLOADED 3
|
||||
|
||||
#define WM_USER_PlatformComm (WM_USER+103)
|
||||
#define PC_CLOSE 2
|
||||
#define PC_GETLOADED 3
|
||||
#define PC_GETAPPLICATION 4 // I3758
|
||||
BOOL ParseCmdLine(LPTSTR lpCmdLine);
|
||||
BOOL CreateWatcherThread(HWND hWnd);
|
||||
|
||||
// External functions in Keyman64.dll
|
||||
|
||||
|
|
@ -54,8 +47,13 @@ extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_UnregisterControllerWindow
|
|||
|
||||
// Global variables
|
||||
|
||||
HINSTANCE hInst; // current instance
|
||||
HWND hwndController = NULL, hwndControllerOwner = NULL; // keyman x86 controller window handles // I3758
|
||||
HINSTANCE hInst; // Current instance
|
||||
HWND hwndController = NULL; // Keyman x86 Main Form window handle
|
||||
HWND hwndControllerOwner = NULL; // Keyman x86 Application window handles
|
||||
HANDLE hParentProcessHandle = NULL; // Keyman x86 process handle
|
||||
|
||||
HANDLE hWatcherThread = NULL;
|
||||
HANDLE hWatcherThreadTerminateEvent = NULL;
|
||||
|
||||
// Global strings
|
||||
|
||||
|
|
@ -75,12 +73,13 @@ const PWSTR
|
|||
szError_FailedToRegister = L"Failed to register window class",
|
||||
szError_FailedToInitInstance = L"Failed to initialise application",
|
||||
|
||||
szError_InvalidCommandline = L"Incorrect command line for Keyman Engine x64; should be passed handle of Keyman Engine x86",
|
||||
szError_WatcherThreadFailed = L"Failed to create watcher thread",
|
||||
|
||||
szFail_UnknownError = L"Unknown error %d",
|
||||
szFail_ErrorFormat = L"%s: %s (%d)",
|
||||
szFail_ErrorFormat_OtherUnknown = L"%s: An unknown error occurred",
|
||||
|
||||
szKeymanX64Mutex = L"KeymanEXEx6470",
|
||||
|
||||
szWindowClass_x86_Wnd = L"TfrmKeyman7Main"; // Do not localize
|
||||
|
||||
//const char *szGPA_ChangeWindowMessageFilter = "ChangeWindowMessageFilter"; // Do not localize
|
||||
|
|
@ -116,11 +115,8 @@ int APIENTRY _tWinMain(HINSTANCE hInstance,
|
|||
|
||||
MSG msg;
|
||||
|
||||
if (!UniqueInstance()) {
|
||||
// If keymanx64 is already running, let's not
|
||||
// report an error. This can happen if keyman.exe
|
||||
// fails unexpectedly, and is then restarted.
|
||||
//Fail(0, szError_CannotRunMultipleInstances);
|
||||
if (!ParseCmdLine(lpCmdLine)) {
|
||||
Fail(0, szError_InvalidCommandline);
|
||||
keyman_sentry_shutdown();
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -157,21 +153,34 @@ int APIENTRY _tWinMain(HINSTANCE hInstance,
|
|||
return (int) msg.wParam;
|
||||
}
|
||||
|
||||
BOOL UniqueInstance()
|
||||
{
|
||||
HANDLE hMutex = CreateMutex(NULL, FALSE, szKeymanX64Mutex);
|
||||
if(!hMutex) return FALSE;
|
||||
//
|
||||
// FUNCTION: ParseCmdLine(LPSTSTR)
|
||||
//
|
||||
// PURPOSE: Parses command line and finds input parameters
|
||||
//
|
||||
// COMMENTS:
|
||||
//
|
||||
// Reads parent process handle, parent process main window and
|
||||
// parent process application window handle from the command
|
||||
// line and validates them.
|
||||
//
|
||||
BOOL ParseCmdLine(LPTSTR lpCmdLine) {
|
||||
while (iswspace(*lpCmdLine)) lpCmdLine++;
|
||||
if (!*lpCmdLine) return FALSE;
|
||||
hParentProcessHandle = (HANDLE)wcstoull(lpCmdLine, &lpCmdLine, 10);
|
||||
if (hParentProcessHandle == 0 || hParentProcessHandle == (HANDLE) ULLONG_MAX) return FALSE;
|
||||
|
||||
switch(WaitForSingleObject(hMutex, 0))
|
||||
{
|
||||
case WAIT_ABANDONED:
|
||||
case WAIT_OBJECT_0:
|
||||
return TRUE;
|
||||
case WAIT_FAILED:
|
||||
case WAIT_TIMEOUT:
|
||||
return FALSE;
|
||||
}
|
||||
return FALSE;
|
||||
while (iswspace(*lpCmdLine)) lpCmdLine++;
|
||||
if (!*lpCmdLine) return FALSE;
|
||||
hwndController = (HWND) wcstoull(lpCmdLine, &lpCmdLine, 10);
|
||||
if (hwndController == 0 || hwndController == (HWND) ULLONG_MAX) return FALSE;
|
||||
|
||||
while (iswspace(*lpCmdLine)) lpCmdLine++;
|
||||
if (!*lpCmdLine) return FALSE;
|
||||
hwndControllerOwner = (HWND) wcstoull(lpCmdLine, &lpCmdLine, 10);
|
||||
if (hwndControllerOwner == 0 || hwndControllerOwner == (HWND) ULLONG_MAX) return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -289,24 +298,11 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
|
|||
//
|
||||
BOOL StartKeyman(HWND hWnd)
|
||||
{
|
||||
if(!ChangeWindowMessageFilter(WM_USER_PlatformComm, MSGFLT_ADD))
|
||||
return Fail(hWnd, szError_ChangeWindowMessageFilter);
|
||||
|
||||
if(!Keyman_ResetInitialisation()) // I3092
|
||||
return Fail(hWnd, szError_FailedToInitialise);
|
||||
|
||||
hwndController = FindWindow(szWindowClass_x86_Wnd, NULL); // I3758
|
||||
if(hwndController == NULL)
|
||||
return Fail(hWnd, szError_Keymanx86NotFound);
|
||||
|
||||
DWORD_PTR dwResult;
|
||||
|
||||
if(SendMessageTimeout(hwndController, WM_USER_PlatformComm, PC_GETAPPLICATION, 0, SMTO_BLOCK, 5000, &dwResult) == 0) // I3758
|
||||
return Fail(hWnd, szError_Keymanx86NotFound);
|
||||
|
||||
hwndControllerOwner = (HWND) dwResult; // I3758
|
||||
if(hwndControllerOwner == NULL)
|
||||
return Fail(hWnd, szError_Keymanx86NotFound);
|
||||
if (!CreateWatcherThread(hWnd))
|
||||
return Fail(hWnd, szError_WatcherThreadFailed);
|
||||
|
||||
if(!Keyman_RegisterControllerWindow(hwndControllerOwner) || !Keyman_RegisterControllerWindow(hwndController)) // I3758
|
||||
return Fail(hWnd, szError_FailedToRegisterController);
|
||||
|
|
@ -318,24 +314,47 @@ BOOL StartKeyman(HWND hWnd)
|
|||
}
|
||||
|
||||
//
|
||||
// FUNCTION: SendPlatformComms32(WPARAM, LPARAM)
|
||||
// FUNCTION: WatcherThreadProc(LPVOID)
|
||||
//
|
||||
// PURPOSE: Post a command to Keyman Engine x86
|
||||
// PURPOSE: Watches for parent process termination
|
||||
//
|
||||
BOOL SendPlatformComms32(WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
// Search again; lets us reconnect if
|
||||
// Keyman.exe crashes and is retarted
|
||||
HWND hwndLocalController = FindWindow(szWindowClass_x86_Wnd, NULL);
|
||||
if(hwndLocalController == NULL)
|
||||
{
|
||||
// TODO: Perhaps we should trigger a shutdown here, because
|
||||
// keyman.exe was not found. Right now, there is some confusion
|
||||
// in regards to responsibility. See #4976
|
||||
MessageBox(0, szError_Keymanx86NotFound_Comms, szTitle, MB_OK);
|
||||
return FALSE;
|
||||
// COMMENTS:
|
||||
//
|
||||
// The watcher thread triggers destruction of this process
|
||||
// as soon as the parent process is terminated.
|
||||
//
|
||||
DWORD WINAPI WatcherThreadProc(LPVOID lpParameter) {
|
||||
HWND hwnd = (HWND)lpParameter;
|
||||
HANDLE handles[2] = { hWatcherThreadTerminateEvent, hParentProcessHandle };
|
||||
|
||||
if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) == WAIT_OBJECT_0 + 1) {
|
||||
// We'll post a message to our window if the parent process terminated,
|
||||
// but not during a shutdown in other circumstances, because that would
|
||||
// have been triggered by a shutdown of the process anyway.
|
||||
PostMessage(hwnd, WM_CLOSE, 0, 0);
|
||||
}
|
||||
PostMessage(hwndLocalController, WM_USER_PlatformComm, wParam, lParam);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// FUNCTION: CreateWatcherThread(HWND)
|
||||
//
|
||||
// PURPOSE: Creates the Watcher Thread
|
||||
//
|
||||
// COMMENTS:
|
||||
//
|
||||
// Creates the watcher thread which watches for
|
||||
// termination of the parent process.
|
||||
//
|
||||
BOOL CreateWatcherThread(HWND hWnd) {
|
||||
hWatcherThreadTerminateEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (hWatcherThreadTerminateEvent == NULL)
|
||||
return FALSE;
|
||||
|
||||
hWatcherThread = CreateThread(NULL, 0, WatcherThreadProc, hWnd, 0, NULL);
|
||||
if (hWatcherThread == NULL)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -353,18 +372,27 @@ void Shutdown()
|
|||
Keyman_StartExit(); // I3092
|
||||
|
||||
/* Tell all threads that it is time to exit. This is important to do before we shutdown
|
||||
because we have got a per-thread keyboard hook that needs to be detached before we
|
||||
lose our message hooks. */
|
||||
because we want to try and detach from as many processes as possible so we don't
|
||||
remain locked in memory. */
|
||||
|
||||
SendMessageTimeout(HWND_BROADCAST, wm_keyman, KM_EXIT, 0, SMTO_NORMAL, 1000, &dwResult); // I3092
|
||||
|
||||
Keyman_Exit();
|
||||
|
||||
/* Unregister those windows that we registered earlier - don't fail on error though */
|
||||
|
||||
if(hwndController != NULL) Keyman_UnregisterControllerWindow(hwndController); // I3758
|
||||
if(hwndControllerOwner != NULL) Keyman_UnregisterControllerWindow(hwndControllerOwner); // I3758
|
||||
|
||||
PostQuitMessage(0);
|
||||
|
||||
/* Cleanup */
|
||||
|
||||
SetEvent(hWatcherThreadTerminateEvent);
|
||||
WaitForSingleObject(hWatcherThread, INFINITE);
|
||||
CloseHandle(hWatcherThread);
|
||||
CloseHandle(hWatcherThreadTerminateEvent);
|
||||
CloseHandle(hParentProcessHandle);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -374,7 +402,6 @@ void Shutdown()
|
|||
//
|
||||
// WM_CREATE - initialise
|
||||
// WM_DESTROY - post a quit message and return
|
||||
// WM_USER_PlatformComm - process commands from Keyman Engine x86
|
||||
//
|
||||
//
|
||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
|
|
@ -389,12 +416,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||
Shutdown();
|
||||
break;
|
||||
|
||||
case WM_USER_PlatformComm:
|
||||
switch(wParam)
|
||||
{
|
||||
case PC_CLOSE: PostMessage(hWnd, WM_CLOSE, 0, 0); break;
|
||||
}
|
||||
|
||||
default:
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const
|
|||
WM_USER_Start = WM_USER+101;
|
||||
WM_USER_ParameterPass = WM_USER+100;
|
||||
WM_USER_SendFontChange = WM_USER+102;
|
||||
WM_USER_PlatformComms = WM_USER+103;
|
||||
WM_USER_VisualKeyboardClosed = WM_USER+105; // I4242
|
||||
|
||||
const
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue