mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-08 16:47:46 +00:00
Merge pull request #5002 from keymanapp/fix/windows/4976-redesign-keymanx64-lifecycle
fix(windows): make keymanx64 responsible for its own lifecycle
This commit is contained in:
commit
a6a658e8bc
3 changed files with 149 additions and 134 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,15 @@ 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;
|
||||
|
||||
BOOL KeymanStarted = FALSE;
|
||||
|
||||
// Global strings
|
||||
|
||||
|
|
@ -75,12 +75,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 +117,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 +155,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 +300,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);
|
||||
|
|
@ -314,28 +312,56 @@ BOOL StartKeyman(HWND hWnd)
|
|||
if(!Keyman_Initialise(hWnd, FALSE))
|
||||
return Fail(hWnd, szError_FailedToInitialise);
|
||||
|
||||
KeymanStarted = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
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) {
|
||||
CloseHandle(hWatcherThreadTerminateEvent);
|
||||
hWatcherThreadTerminateEvent = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
PostMessage(hwndLocalController, WM_USER_PlatformComm, wParam, lParam);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -347,24 +373,40 @@ BOOL SendPlatformComms32(WPARAM wParam, LPARAM lParam)
|
|||
//
|
||||
void Shutdown()
|
||||
{
|
||||
int wm_keyman = RegisterWindowMessage(L"wm_keyman");
|
||||
DWORD_PTR dwResult;
|
||||
if (KeymanStarted) {
|
||||
int wm_keyman = RegisterWindowMessage(L"wm_keyman");
|
||||
DWORD_PTR dwResult;
|
||||
|
||||
Keyman_StartExit(); // I3092
|
||||
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. */
|
||||
/* Tell all threads that it is time to exit. This is important to do before we shutdown
|
||||
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
|
||||
SendMessageTimeout(HWND_BROADCAST, wm_keyman, KM_EXIT, 0, SMTO_NORMAL, 1000, &dwResult); // I3092
|
||||
|
||||
Keyman_Exit();
|
||||
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 */
|
||||
|
||||
if (hWatcherThreadTerminateEvent != NULL) {
|
||||
// Both of these handles will be valid if one of them is, per CreateWatcherThread
|
||||
SetEvent(hWatcherThreadTerminateEvent);
|
||||
WaitForSingleObject(hWatcherThread, INFINITE);
|
||||
CloseHandle(hWatcherThread);
|
||||
CloseHandle(hWatcherThreadTerminateEvent);
|
||||
}
|
||||
if (hParentProcessHandle != NULL) {
|
||||
CloseHandle(hParentProcessHandle);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -374,7 +416,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 +430,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