diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index b8c14304e8..2c58f97b02 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -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. diff --git a/windows/src/engine/keymanx64/keymanx64.cpp b/windows/src/engine/keymanx64/keymanx64.cpp index fa31a585d9..9779768523 100644 --- a/windows/src/engine/keymanx64/keymanx64.cpp +++ b/windows/src/engine/keymanx64/keymanx64.cpp @@ -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); } diff --git a/windows/src/global/delphi/general/UserMessages.pas b/windows/src/global/delphi/general/UserMessages.pas index d9b40e65a5..9480714f0a 100644 --- a/windows/src/global/delphi/general/UserMessages.pas +++ b/windows/src/global/delphi/general/UserMessages.pas @@ -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