spiegel-keyman/windows/src/engine/keymanx64/keymanx64.cpp
2021-05-07 17:17:48 +10:00

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;
}