mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-09 00:57:43 +00:00
437 lines
13 KiB
C++
437 lines
13 KiB
C++
/*
|
|
Name: keymanx64
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 11 Dec 2009
|
|
|
|
Modified Date: 25 Oct 2016
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
|
|
03 Oct 2011 - mcdurdin - I3093 - Keyman Engine x64 shows a garbled error message if it cannot start
|
|
03 Oct 2011 - mcdurdin - I3092 - Keyman Engine does not restart nicely if shutdown uncleanly
|
|
05 Nov 2012 - mcdurdin - I3547 - V9.0 Use _countof to tidyup code
|
|
01 Jan 2013 - mcdurdin - I3677 - V9.0 - OSK and Keyman menu show an entry in taskbar
|
|
17 Jan 2013 - mcdurdin - I3758 - V9.0 - keymanx64 can find incorrect window handle for keyman.exe
|
|
25 Oct 2016 - mcdurdin - I5136 - Remove additional product references from Keyman Engine
|
|
*/
|
|
#include "keymanx64.h" // I5136
|
|
#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 ParseCmdLine(LPTSTR lpCmdLine);
|
|
BOOL CreateWatcherThread(HWND hWnd);
|
|
|
|
// External functions in Keyman64.dll
|
|
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_Initialise(HWND Handle, BOOL FSingleApp);
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_ResetInitialisation(void); // I3092
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_StartExit(void); // I3092
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_Exit(void);
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_RegisterControllerWindow(HWND hwnd);
|
|
extern "C" BOOL __declspec( dllimport ) WINAPI Keyman_UnregisterControllerWindow(HWND hwnd);
|
|
|
|
#define KM_EXIT 4
|
|
#define KM_EXITFLUSH 5
|
|
|
|
// Global variables
|
|
|
|
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
|
|
|
|
const PWSTR
|
|
szWindowClass = L"Keymanx64", // Do not localize
|
|
szWindowTitle = L"Keymanx64", // Do not localize
|
|
|
|
szTitle = L"Keyman Engine x64",
|
|
szError_Keymanx86NotFound = L"Keyman Engine x86 is not running. Do not run keymanx64.exe -- it must be started by Keyman Engine x86",
|
|
szError_Keymanx86NotFound_Comms = L"Keyman Engine x86 has closed unexpectedly. Closing down Keyman Engine x64",
|
|
szError_FailedToRegisterController = L"Failed to register controller window",
|
|
szError_FailedToInitialise = L"Failed to initialise Keyman Engine x64",
|
|
|
|
szError_ChangeWindowMessageFilter = L"Failed to prepare message filters",
|
|
|
|
szError_CannotRunMultipleInstances = L"Only one instance of Keyman Engine x64 can run at a time",
|
|
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",
|
|
|
|
szWindowClass_x86_Wnd = L"TfrmKeyman7Main"; // Do not localize
|
|
|
|
//const char *szGPA_ChangeWindowMessageFilter = "ChangeWindowMessageFilter"; // Do not localize
|
|
|
|
#define KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE_KEYMANX64 KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE ".keymanx64"
|
|
|
|
//
|
|
// FUNCTION: _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int
|
|
//
|
|
// PURPOSE: Program entry point
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
// A mutex is used to ensure only one instance of Keyman Engine x64
|
|
//
|
|
int APIENTRY _tWinMain(HINSTANCE hInstance,
|
|
HINSTANCE hPrevInstance,
|
|
LPTSTR lpCmdLine,
|
|
int nCmdShow)
|
|
{
|
|
UNREFERENCED_PARAMETER(hPrevInstance);
|
|
|
|
keyman_sentry_init(false, KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE_KEYMANX64);
|
|
keyman_sentry_setexceptionfilter();
|
|
|
|
// We used the 'Started' event when testing Sentry integration in 14.0 alpha
|
|
// but we don't want or need it for stable.
|
|
// keyman_sentry_report_message(KEYMAN_SENTRY_LEVEL_INFO, "Started " KEYMAN_SENTRY_LOGGER_DESKTOP_ENGINE_KEYMANX64);
|
|
|
|
if (!wcscmp(lpCmdLine, L"-sentry-client-test-exception")) {
|
|
keyman_sentry_test_crash();
|
|
}
|
|
|
|
MSG msg;
|
|
|
|
if (!ParseCmdLine(lpCmdLine)) {
|
|
Fail(0, szError_InvalidCommandline);
|
|
keyman_sentry_shutdown();
|
|
return 1;
|
|
}
|
|
|
|
if (!MyRegisterClass(hInstance)) {
|
|
Fail(0, szError_FailedToRegister);
|
|
keyman_sentry_shutdown();
|
|
return 1;
|
|
}
|
|
|
|
if (!InitInstance(hInstance, nCmdShow)) {
|
|
if (GetLastError() != ERROR_SUCCESS) {
|
|
// If WM_CREATE returns -1, then CreateWindow fails but
|
|
// GetLastError returns ERROR_SUCCESS. In this situation,
|
|
// we know that the error has already been reported via
|
|
// StartKeyman() so we don't want to re-report it. If
|
|
// CreateWindow fails for another reason, then we should
|
|
// be reporting it.
|
|
Fail(0, szError_FailedToInitInstance);
|
|
}
|
|
keyman_sentry_shutdown();
|
|
return 1;
|
|
}
|
|
|
|
// Main message loop:
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
keyman_sentry_shutdown();
|
|
|
|
return (int) msg.wParam;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// FUNCTION: MyRegisterClass(HINSTANCE)
|
|
//
|
|
// PURPOSE: Register the window class for Keyman Engine x64
|
|
//
|
|
ATOM MyRegisterClass(HINSTANCE hInstance)
|
|
{
|
|
WNDCLASSEX wcex;
|
|
|
|
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcex.lpfnWndProc = WndProc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = hInstance;
|
|
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(1));
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
|
wcex.lpszMenuName = 0;
|
|
wcex.lpszClassName = szWindowClass;
|
|
wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(1));
|
|
|
|
return RegisterClassEx(&wcex);
|
|
}
|
|
|
|
//
|
|
// FUNCTION: Fail(HWND, PWSTR)
|
|
//
|
|
// PURPOSE: Displays an error message and always returns FALSE
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
// The last error code and message will be loaded and shown as well
|
|
// create and display the main program window.
|
|
//
|
|
BOOL Fail(HWND hwnd, PWSTR msg)
|
|
{
|
|
#ifndef _DEBUG
|
|
UNREFERENCED_PARAMETER(hwnd);
|
|
#endif
|
|
|
|
WCHAR buf[512], outbuf[1024];
|
|
DWORD err = GetLastError();
|
|
if(err != ERROR_SUCCESS)
|
|
{
|
|
if(!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, buf, _countof(buf), NULL)) // I3093 // I3547
|
|
{
|
|
wsprintfW(buf, szFail_UnknownError, err);
|
|
}
|
|
wsprintfW(outbuf, szFail_ErrorFormat, msg, buf, err);
|
|
}
|
|
else
|
|
wsprintfW(outbuf, szFail_ErrorFormat_OtherUnknown, msg);
|
|
|
|
size_t sz = WideCharToMultiByte(CP_UTF8, 0, outbuf, -1, NULL, 0, NULL, NULL);
|
|
if (sz == 0) return FALSE;
|
|
|
|
char *buffer = (char *) calloc(sz, sizeof(char));
|
|
if (buffer == NULL) return FALSE;
|
|
|
|
if (WideCharToMultiByte(CP_UTF8, 0, outbuf, -1, buffer, (int) sz, NULL, NULL) != 0) {
|
|
buffer[sz - 1] = 0;
|
|
keyman_sentry_report_message(KEYMAN_SENTRY_LEVEL_ERROR, buffer, true);
|
|
}
|
|
|
|
free(buffer);
|
|
|
|
#ifdef _DEBUG
|
|
MessageBox(hwnd, outbuf, szTitle, MB_OK | MB_ICONERROR);
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// FUNCTION: InitInstance(HINSTANCE, int)
|
|
//
|
|
// PURPOSE: Saves instance handle and creates main window
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
// In this function, we save the instance handle in a global variable and
|
|
// create the main program window.
|
|
//
|
|
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
|
|
{
|
|
UNREFERENCED_PARAMETER(nCmdShow);
|
|
|
|
HWND hWnd;
|
|
|
|
hInst = hInstance; // Store instance handle in our global variable
|
|
|
|
hWnd = CreateWindow(szWindowClass, szWindowTitle, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
|
|
|
|
if (!hWnd)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// FUNCTION: StartKeyman(HWND)
|
|
//
|
|
// PURPOSE: Initialise the Keyman Engine DLL
|
|
//
|
|
// COMMENTS:
|
|
//
|
|
// Shows an error message box if it fails, in debug mode.
|
|
// Sends a Sentry error report on failure. Tries to find
|
|
// the Keyman Engine x86 windows for communications.
|
|
//
|
|
BOOL StartKeyman(HWND hWnd)
|
|
{
|
|
if(!Keyman_ResetInitialisation()) // I3092
|
|
return Fail(hWnd, szError_FailedToInitialise);
|
|
|
|
if (!CreateWatcherThread(hWnd))
|
|
return Fail(hWnd, szError_WatcherThreadFailed);
|
|
|
|
if(!Keyman_RegisterControllerWindow(hwndControllerOwner) || !Keyman_RegisterControllerWindow(hwndController)) // I3758
|
|
return Fail(hWnd, szError_FailedToRegisterController);
|
|
|
|
if(!Keyman_Initialise(hWnd, FALSE))
|
|
return Fail(hWnd, szError_FailedToInitialise);
|
|
|
|
KeymanStarted = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// FUNCTION: WatcherThreadProc(LPVOID)
|
|
//
|
|
// PURPOSE: Watches for parent process termination
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// FUNCTION: Shutdown()
|
|
//
|
|
// PURPOSE: Shutdown tasks including finalising the Keyman Engine x64 DLL
|
|
//
|
|
//
|
|
void Shutdown()
|
|
{
|
|
if (KeymanStarted) {
|
|
int wm_keyman = RegisterWindowMessage(L"wm_keyman");
|
|
DWORD_PTR dwResult;
|
|
|
|
Keyman_StartExit(); // I3092
|
|
|
|
/* 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
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
//
|
|
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
|
|
//
|
|
// PURPOSE: Processes messages for the main window.
|
|
//
|
|
// WM_CREATE - initialise
|
|
// WM_DESTROY - post a quit message and return
|
|
//
|
|
//
|
|
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_CREATE:
|
|
if(!StartKeyman(hWnd)) return -1;
|
|
return 0;
|
|
|
|
case WM_DESTROY:
|
|
Shutdown();
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|