From ee6f8590a6aeac13a21b27e245e73b2018badd79 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 30 Apr 2021 15:02:18 +1000 Subject: [PATCH 1/4] 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. --- windows/src/engine/keyman/UfrmKeyman7Main.pas | 93 ++++------ windows/src/engine/keymanx64/keymanx64.cpp | 165 ++++++++++-------- .../global/delphi/general/UserMessages.pas | 1 - 3 files changed, 130 insertions(+), 129 deletions(-) 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..9f253ad26d 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,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); } 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 From 492394c4f6766b86dd7e59b28ccbbfbe8310d915 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 7 May 2021 17:08:11 +1000 Subject: [PATCH 2/4] chore: address review comment --- windows/src/engine/keyman32/k32_globals.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index dc4b68de36..ea5372c2ff 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -361,10 +361,11 @@ BOOL Globals::get_debug_ToConsole() { return f_debug_ToConsole; } void Globals::SetBaseKeyboardName(wchar_t *baseKeyboardName, wchar_t *baseKeyboardNameAlt) { // I4583 wcscpy_s(f_BaseKeyboardName, baseKeyboardName); wcscpy_s(f_BaseKeyboardNameAlt, baseKeyboardNameAlt); + SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardName(name='%ws', nameAlt='%ws')", baseKeyboardName, baseKeyboardNameAlt); } void Globals::SetBaseKeyboardFlags(char *baseKeyboard, BOOL simulateAltGr, BOOL mnemonicDeadkeyConversionMode) { // I4583 // I4552 - SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardFlags(baseKeyboard='%s', simulateAltGr=%d, mnemonicDeadkeyConversionMode=%d", + SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardFlags(baseKeyboard='%s', simulateAltGr=%d, mnemonicDeadkeyConversionMode=%d)", baseKeyboard, simulateAltGr, mnemonicDeadkeyConversionMode); strcpy_s(f_BaseKeyboard, baseKeyboard); f_SimulateAltGr = simulateAltGr; From a23266a6b989c0125f311368a03292c703670390 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 7 May 2021 17:17:48 +1000 Subject: [PATCH 3/4] chore: address review comments --- windows/src/engine/keymanx64/keymanx64.cpp | 42 ++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/windows/src/engine/keymanx64/keymanx64.cpp b/windows/src/engine/keymanx64/keymanx64.cpp index 9f253ad26d..9779768523 100644 --- a/windows/src/engine/keymanx64/keymanx64.cpp +++ b/windows/src/engine/keymanx64/keymanx64.cpp @@ -55,6 +55,8 @@ HANDLE hParentProcessHandle = NULL; // Keyman x86 process handle HANDLE hWatcherThread = NULL; HANDLE hWatcherThreadTerminateEvent = NULL; +BOOL KeymanStarted = FALSE; + // Global strings const PWSTR @@ -310,6 +312,8 @@ BOOL StartKeyman(HWND hWnd) if(!Keyman_Initialise(hWnd, FALSE)) return Fail(hWnd, szError_FailedToInitialise); + KeymanStarted = TRUE; + return TRUE; } @@ -352,8 +356,11 @@ BOOL CreateWatcherThread(HWND hWnd) { return FALSE; hWatcherThread = CreateThread(NULL, 0, WatcherThreadProc, hWnd, 0, NULL); - if (hWatcherThread == NULL) + if (hWatcherThread == NULL) { + CloseHandle(hWatcherThreadTerminateEvent); + hWatcherThreadTerminateEvent = NULL; return FALSE; + } return TRUE; } @@ -366,18 +373,20 @@ BOOL CreateWatcherThread(HWND hWnd) { // 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 want to try and detach from as many processes as possible so we don't - remain locked in memory. */ + /* 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 */ @@ -388,11 +397,16 @@ void Shutdown() /* Cleanup */ - SetEvent(hWatcherThreadTerminateEvent); - WaitForSingleObject(hWatcherThread, INFINITE); - CloseHandle(hWatcherThread); - CloseHandle(hWatcherThreadTerminateEvent); - CloseHandle(hParentProcessHandle); + 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); + } } // From 23505d8e7b0291180ca42afbeed3a0f42f68dc32 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 7 May 2021 17:19:43 +1000 Subject: [PATCH 4/4] chore: remove extra file --- windows/src/engine/keyman32/k32_globals.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/windows/src/engine/keyman32/k32_globals.cpp b/windows/src/engine/keyman32/k32_globals.cpp index ea5372c2ff..dc4b68de36 100644 --- a/windows/src/engine/keyman32/k32_globals.cpp +++ b/windows/src/engine/keyman32/k32_globals.cpp @@ -361,11 +361,10 @@ BOOL Globals::get_debug_ToConsole() { return f_debug_ToConsole; } void Globals::SetBaseKeyboardName(wchar_t *baseKeyboardName, wchar_t *baseKeyboardNameAlt) { // I4583 wcscpy_s(f_BaseKeyboardName, baseKeyboardName); wcscpy_s(f_BaseKeyboardNameAlt, baseKeyboardNameAlt); - SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardName(name='%ws', nameAlt='%ws')", baseKeyboardName, baseKeyboardNameAlt); } void Globals::SetBaseKeyboardFlags(char *baseKeyboard, BOOL simulateAltGr, BOOL mnemonicDeadkeyConversionMode) { // I4583 // I4552 - SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardFlags(baseKeyboard='%s', simulateAltGr=%d, mnemonicDeadkeyConversionMode=%d)", + SendDebugMessageFormat(0, sdmAIDefault, 0, "Globals::SetBaseKeyboardFlags(baseKeyboard='%s', simulateAltGr=%d, mnemonicDeadkeyConversionMode=%d", baseKeyboard, simulateAltGr, mnemonicDeadkeyConversionMode); strcpy_s(f_BaseKeyboard, baseKeyboard); f_SimulateAltGr = simulateAltGr;