spiegel-keyman/windows/src/engine/keyman32/keyman32.cpp

1071 lines
40 KiB
C++

/*
Name: Keyman32
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 1 Aug 2006
Modified Date: 25 Oct 2016
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 01 Aug 2006 - mcdurdin - Clean up SelectKeyboard and Visual Keyboard updating
14 Sep 2006 - mcdurdin - Fix bugs with multiple InitialiseProcess calls causing Keyman to fail to start up correctly
14 Sep 2006 - mcdurdin - Fix buffer overflow with Keyman_ForceKeyboard causing shift states to be flushed in Keyman Developer debugger
04 Dec 2006 - mcdurdin - Add Vista support
04 Dec 2006 - mcdurdin - Add Keyman_IsProductLoaded function
04 Jan 2007 - mcdurdin - Fix shutdown order for Keyman Engine
16 May 2007 - mcdurdin - I819 - Test if Keyman is functioning
16 May 2007 - mcdurdin - I820 - Activate keyboard for first active application after start
30 May 2007 - mcdurdin - I863 - Fixed some systems unable to restart Keyman while system is starting up (userinit)
04 Jun 2007 - mcdurdin - I819 - Use a timeout to ensure Keyman can shutdown in a timely fashion if another application is not responding
19 Jun 2007 - mcdurdin - I819 - Improve Keyman stability
13 Jul 2007 - mcdurdin - I910 - Log errors starting Keyman
05 Nov 2007 - mcdurdin - I1087 - Add language switching hotkeys for Desktop Pro
27 Mar 2008 - mcdurdin - I1287 - Switch keyboard and language togher
20 Jul 2008 - mcdurdin - I1546 - Language switch not working with some keyboard IDs
10 Sep 2008 - mcdurdin - I1627 - Switching languages with keyboard should be in LIBRARY_NAME
19 Sep 2008 - mcdurdin - I1635 - Fix x64 crash due to memory corruption
29 Sep 2008 - mcdurdin - I1657 - Fix switching off keyboard bug introduced in build 252
16 Jan 2009 - mcdurdin - I1798 - Fix keyboards not always switching correctly first time after startup
27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration
30 Jan 2009 - mcdurdin - I1835 - Improve refresh performance
09 Mar 2009 - mcdurdin - I1888 - Keyman Engine can crash with some multithreaded applications
11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888
30 Nov 2009 - mcdurdin - I934 - x64 groundwork - registry keys
11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
11 Dec 2009 - mcdurdin - I1455 - Remove unneeded hooks
11 Dec 2009 - mcdurdin - I934 - x64 - keyboard hook per thread
11 Dec 2009 - mcdurdin - I1455 - Update keyboard activation per focused thread
12 Mar 2010 - mcdurdin - I934 - x64 - Complete
12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
29 Mar 2010 - mcdurdin - I1089 - One keyboard - all applications
29 Mar 2010 - mcdurdin - I2264 - Keyman Engine should not attach to various processes
29 Mar 2010 - mcdurdin - I2265 - Keyman fails to switch keyboard correctly when not associated with a language
29 Mar 2010 - mcdurdin - I2255 - Keyman fails to work correctly on restart due to keyman32 or keyman64 being locked by another process
29 Mar 2010 - mcdurdin - I2244 - Keyman ceases to accept input with after some time
06 Apr 2010 - mcdurdin - I2271 - Select Keyboard tidy up
06 Apr 2010 - mcdurdin - I2266 - Reduce chatter when switching keyboards
06 Apr 2010 - mcdurdin - I2285 - Stop initial keyboard load from caching and reloading Keyman keyboard
06 Apr 2010 - mcdurdin - I2264 - FAIL: Keyman Engine must be allowed to attach to all processes for performance reasons
09 Apr 2010 - mcdurdin - I2293 - Keyman thread initialisation was re-entrant
04 May 2010 - mcdurdin - I2355 - Resolve deadlocks loading keyman32.dll
04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value
14 May 2010 - mcdurdin - I2375 - During keyboard refresh, don't call SelectKeyboard because we just need to clear state
25 May 2010 - mcdurdin - I1632 - Keyboard Options
29 Jun 2010 - mcdurdin - I2435 - Shutdown of Keyman Engine can re-initialise and hang
29 Jun 2010 - mcdurdin - I2441 - SelectKeyboard cache needs to be initialised when Keyman starts to prevent wrong keyboard language links
29 Jun 2010 - mcdurdin - I2437 - Crash unloading keyboard because keyboard options not initialised when using forced keyboards
29 Jun 2010 - mcdurdin - I2448 - Regression - Keyman Engine fails to restart in .285
18 Feb 2011 - mcdurdin - I2714 - Leak of registry handle when refreshing keyboards
19 Aug 2011 - mcdurdin - I3040 - keyman32.dll can crash if advapi32.dll is not loaded when first called from a process
03 Oct 2011 - mcdurdin - I3092 - Keyman Engine does not restart nicely when shutdown uncleanly
04 Nov 2011 - mcdurdin - I3108 - Keyman Engine could crash if it shutdown and restarted with keyman32.dll still in memory
07 Nov 2011 - mcdurdin - I3127 - If ThreadGlobals is null but Keyman_Shutdown is false, then Keyman can crash on startup
02 Dec 2011 - mcdurdin - I3158 - Keyman Engine can crash other applications when multiple OEM products are started, with a _tcscpy assertion
23 Dec 2011 - mcdurdin - I3191 - Corruption of keyboard selection cache between x86 and x64
24 Jan 2012 - mcdurdin - I3143 - Report more useful information when Keyman fails to start
24 Jan 2012 - mcdurdin - I3173 - Keyman can crash when Keyman_StopForcingKeyboard called and keyboard is not already forced
04 Nov 2012 - mcdurdin - I3523 - V9.0 - Merge of I3143 - Report more useful information when Keyman fails to start
04 Nov 2012 - mcdurdin - I3524 - V9.0 - Merge of I3158 - Keyman Engine can crash other applications when multiple OEM products are started, with a _tcscpy assertion
04 Nov 2012 - mcdurdin - I3525 - V9.0 - Merge of I3173 - Keyman can crash when Keyman_StopForcingKeyboard called and keyboard is not already forced
04 Nov 2012 - mcdurdin - I3526 - V9.0 - Merge of I3191 - Corruption of keyboard selection cache between x86 and x64
05 Nov 2012 - mcdurdin - I3547 - V9.0 Use _countof to tidyup code
20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
28 Nov 2012 - mcdurdin - I3594 - V9.0 - Remove old SelectKeyboard code and related messages
01 Dec 2012 - mcdurdin - I3613 - V9.0 - System shadow keyboards obsolete, strip out remaining code
01 Dec 2012 - mcdurdin - I3616 - V9.0 - Language association obsolete, strip out code
17 Jan 2013 - mcdurdin - I3759 - V9.0 - UnderlyingLayout stores a HKL in the registry but expects a KLID in the code
17 Jan 2013 - mcdurdin - I3760 - V9.0 - Clean up refresh code to avoid repeated dereferences and indexing
17 Jan 2013 - mcdurdin - I3761 - V9.0 - If multiple profiles are enabled for a given keyboard, Keyman32 can crash with memory errors
06 Mar 2014 - mcdurdin - I4124 - V9.0 - Language switch dialog is not working in v9
16 Apr 2014 - mcdurdin - I4169 - V9.0 - Mnemonic layouts should be recompiled to positional based on user-selected base keyboard
03 Aug 2014 - mcdurdin - I4326 - V9.0 - Switch-off hotkey not working, then keyboard hotkey stopped working (win 8.1 jeremy) [High]
28 Aug 2014 - mcdurdin - I4390 - V9.0 - Free vs Pro
25 Sep 2014 - mcdurdin - I4412 - V9.0 - Character Map needs to insert characters using SendInput
16 Oct 2014 - mcdurdin - I4461 - V9.0 - Keyman_BuildKeyboardList was including keyboards installed but with no profiles
23 Oct 2014 - mcdurdin - I4461 - V9.0 - Keyman_BuildKeyboardList was including keyboards installed but with no profiles
14 Nov 2014 - mcdurdin - I4516 - V9.0 - Language hotkeys associated with non-primary keyboards do not trigger language change
03 Feb 2015 - mcdurdin - I4581 - V9.0 - Underlying layout fails to load and we never know what the AltGr flag is now
06 Feb 2015 - mcdurdin - I4552 - V9.0 - Add mnemonic recompile option to ignore deadkeys
06 Feb 2015 - mcdurdin - I4583 - V9.0 - Remove altgr lookup test from keyman32 and put it into the registry
06 Feb 2015 - mcdurdin - I4588 - V9.0 - Support if(&baselayout) with all of the ISO names
22 Apr 2015 - mcdurdin - I4660 - V9.0 - underlyinglayout, if not specified, tries to read invalid location in HKLM\System\CurrentControlSet\Keyboard Layouts\\layout file
14 May 2015 - mcdurdin - I4714 - V9.0 - Keyboard and language hotkeys don't always work
02 Jul 2015 - mcdurdin - I4786 - If no baselayout is specified by the user, default to en-US (kbdus.dll)
09 Aug 2015 - mcdurdin - I4844 - Tidy up PostDummyKeyEvent calls
25 Oct 2016 - mcdurdin - I5136 - Remove additional product references from Keyman Engine
*/
#include "pch.h"
#include "serialkeyeventserver.h"
HINSTANCE g_hInstance;
/*******************************************************************************************/
/* */
/* Keyman Initialisation */
/* */
/*******************************************************************************************/
BOOL __stdcall DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID reserved)
{
UNREFERENCED_PARAMETER(reserved);
g_hInstance = hinstDll;
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
//OutputThreadDebugString("DLL_PROCESS_ATTACH");
if(!Globals_InitProcess()) return FALSE;
break;
case DLL_PROCESS_DETACH:
if (reserved == NULL) {
// If reserved == NULL, that means the library is being unloaded, but
// the process is not terminating.
//OutputThreadDebugString("DLL_PROCESS_DETACH not terminating");
UninitialiseProcess(FALSE);
Globals_UninitProcess();
}
else {
//OutputThreadDebugString("DLL_PROCESS_DETACH terminating");
}
break;
case DLL_THREAD_ATTACH:
//OutputThreadDebugString("DLL_THREAD_ATTACH");
Globals_InitThread();
break;
case DLL_THREAD_DETACH:
//OutputThreadDebugString("DLL_THREAD_DETACH");
UninitialiseProcess(FALSE);
Globals_UninitThread();
break;
}
return TRUE;
}
void UninitDebuggingEx();
/**
* Frees memory and cleans up
*
* @param Lock If FALSE, does not attempt to release other DLLs (e.g. call()ed DLLs)
*/
BOOL UninitialiseProcess(BOOL Lock)
{
if(!Globals_ProcessInitialised()) return TRUE;
ReleaseKeyboards(Lock);
UninitDebuggingEx();
PKEYMAN64THREADDATA _td = ThreadGlobals(); // This is safe because of Globals_ProcessInitialised call above
if(_td)
{
if(_td->msgbuf) delete _td->msgbuf;
_td->msgbuf = NULL;
if(_td->IndexStack) delete _td->IndexStack;
_td->IndexStack = NULL;
if (_td->miniContext) delete _td->miniContext;
_td->miniContext = NULL;
if (_td->msgbuf) delete _td->msgbuf;
_td->msgbuf = NULL;
}
return TRUE;
}
LONG FStartedInitialise = FALSE;
#define MSGFLT_ADD 1
BOOL (WINAPI * PChangeWindowMessageFilter)(UINT, DWORD) = NULL;
void DoCWMF(UINT msg)
{
if (!(*PChangeWindowMessageFilter)(msg, MSGFLT_ADD)) {
DebugLastError("ChangeWindowMessageFilter");
}
}
void DoChangeWindowMessageFilter()
{
HMODULE hUser32 = LoadLibrary("user32");
if (!hUser32)
return;
PChangeWindowMessageFilter = (BOOL (WINAPI *)(UINT,DWORD))GetProcAddress(hUser32, "ChangeWindowMessageFilter");
if (!PChangeWindowMessageFilter)
{
FreeLibrary(hUser32);
return;
}
DoCWMF(wm_keyman); // I3594
DoCWMF(wm_keymankeydown);
DoCWMF(wm_keymankeyup);
DoCWMF(wm_keyman_grabwindowproc);
DoCWMF(wm_keyman_refresh);
DoCWMF(wm_kmgetactivekeymanid);
DoCWMF(wm_keymanim_close);
DoCWMF(wm_keymanim_contextchanged);
DoCWMF(wm_keymanshift);
DoCWMF(wm_keyman_control); // I4714
DoCWMF(wm_keyman_control_internal); // I4714
FreeLibrary(hUser32);
}
BOOL InitThread(HWND hwnd)
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td)
{
SetLastError(ERROR_KEYMAN_THREAD_DATA_NOT_READY); // I3143 // I3523
return FALSE;
}
if(_td->FInitialising)
{
SetLastError(ERROR_KEYMAN_CANNOT_REINITIALISE_THREAD); // I3143 // I3523
return FALSE;
// I2435 - Shutdown of Keyman Engine re-initialising and causing a deadlock; moved up above LoadProducts
// I2448 - Removed "|| _td->FInitialised" -- this should not be included because sometimes it can be re-initialised.
}
// TODO: Check if we are initialising again and don't reallocate memory. This can happen if Keyman is restarted
_td->FInitialising = TRUE; // Control re-entrancy, this is thread safe because the variable is per-thread
_td->IndexStack = new WORD[GLOBAL_ContextStackSize]; //Globals::Ini()->ContextStackSize]; // I3158 // I3524
if(!_td->IndexStack)
{
SendDebugMessage(hwnd, sdmGlobal, 0, "InitialiseProcess: Failed to allocate memory for IndexStack");
SetLastError(ERROR_KEYMAN_MEMORY_ALLOCATION_FAILED); // I3143 // I3523
return FALSE;
}
_td->miniContext = new WCHAR[GLOBAL_ContextStackSize]; //Globals::Ini()->ContextStackSize]; // I3158 // I3524
if(!_td->miniContext)
{
SendDebugMessage(hwnd, sdmGlobal, 0, "InitialiseProcess: Failed to allocate memory for miniContext");
SetLastError(ERROR_KEYMAN_MEMORY_ALLOCATION_FAILED); // I3143 // I3523
return FALSE;
}
_td->msgbuf = new MSG[GLOBAL_MsgStackSize]; //Globals::Ini()->MsgStackSize]; // I3158 // I3524
if(!_td->msgbuf)
{
SendDebugMessage(hwnd, sdmGlobal, 0, "InitialiseProcess: Failed to allocate memory for msgbuf");
SetLastError(ERROR_KEYMAN_MEMORY_ALLOCATION_FAILED); // I3143 // I3523
return FALSE;
}
RefreshKeyboards(TRUE);
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "--InitialiseProcess LEAVE--");
_td->FInitialising = FALSE; // Control re-entrancy, this is thread safe because the variable is per-thread
return _td->FInitialised = TRUE;
}
/*
Compatibility flags. Search for the use of these for
more documentation on why they exist.
*/
void Initialise_Flag_ShouldSerializeInput() {
flag_ShouldSerializeInput = Reg_GetDebugFlag(REGSZ_Flag_ShouldSerializeInput, TRUE);
}
BOOL InitialiseProcess(HWND hwnd)
{
if(InterlockedExchange(&FStartedInitialise, TRUE))
{
//SendDebugMessage(hwnd, sdmGlobal, 0, "InitialiseProcess: Failed because process is already initialising");
return InitThread(hwnd);
}
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "--InitialiseProcess ENTER--");
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ProcessID=%d ThreadID=%d CmdLine=%s", GetCurrentProcessId(), GetCurrentThreadId(),
GetCommandLine());
Initialise_Flag_ShouldSerializeInput();
wm_keyman = RegisterWindowMessage(RWM_KEYMAN);
wm_keymankeydown = RegisterWindowMessage("WM_KEYMANKEYDOWN");
wm_keymankeyup = RegisterWindowMessage("WM_KEYMANKEYUP");
wm_keyman_grabwindowproc = RegisterWindowMessage("WM_KEYMAN_GRABWINDOWPROC");
wm_keyman_refresh = RegisterWindowMessage("WM_KEYMANREFRESH");
wm_kmgetactivekeymanid = RegisterWindowMessage("WM_KEYMAN_GETACTIVEKEYMANID");
wm_keymanim_close = RegisterWindowMessage("WM_KEYMANIM_CLOSE");
wm_keymanim_contextchanged = RegisterWindowMessage("WM_KEYMANIM_CONTEXTCHANGED");
wm_keymanshift = RegisterWindowMessage("WM_KEYMANSHIFT");
wm_keyman_control = RegisterWindowMessage(RWM_KEYMAN_CONTROL);
wm_keyman_control_internal = RegisterWindowMessage("WM_KEYMAN_CONTROL_INTERNAL");
wm_test_keyman_functioning = RegisterWindowMessage("wm_test_keyman_functioning");
DoChangeWindowMessageFilter();
//GetINIAdvanced(); /* I163x - Fix crash due to memory corruption - Globals::Ini not initialized before use - only appeared to happen on x64? */ // I3158 // I3524
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ContextStackSize: %d", GLOBAL_ContextStackSize); // Globals::Ini()->ContextStackSize); // I3158 // I3524
return InitThread(hwnd);
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_GetInitialised(BOOL *FSingleApp)
{
*FSingleApp = Globals::get_FSingleThread();
return Globals::get_Keyman_Initialised();
}
BOOL InitHooks()
{
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
*Globals::hhookGetMessage() = SetWindowsHookExW(WH_GETMESSAGE, (HOOKPROC) kmnGetMessageProc, hinst, Globals::get_FSingleThread());
*Globals::hhookCallWndProc() = SetWindowsHookExW(WH_CALLWNDPROC, (HOOKPROC) kmnCallWndProc, hinst, Globals::get_FSingleThread());
#ifndef _WIN64
*Globals::hhookLowLevelKeyboardProc() = SetWindowsHookExW(WH_KEYBOARD_LL, (HOOKPROC) kmnLowLevelKeyboardProc, hinst, Globals::get_FSingleThread()); // I4124
#endif;
return
Globals::get_hhookGetMessage() != NULL
&& Globals::get_hhookCallWndProc() != NULL
#ifndef _WIN64
&& Globals::get_hhookLowLevelKeyboardProc() != NULL // I4124
#endif
;
}
BOOL UninitHooks()
{
BOOL RetVal = TRUE;
if(Globals::get_hhookGetMessage() && !UnhookWindowsHookEx(Globals::get_hhookGetMessage()))
RetVal = FALSE;
else
*Globals::hhookGetMessage() = NULL;
if(Globals::get_hhookCallWndProc() && !UnhookWindowsHookEx(Globals::get_hhookCallWndProc()))
RetVal = FALSE;
else
*Globals::hhookCallWndProc() = NULL;
#ifndef _WIN64
if(Globals::get_hhookLowLevelKeyboardProc() && !UnhookWindowsHookEx(Globals::get_hhookLowLevelKeyboardProc())) // I4124
RetVal = FALSE;
else
*Globals::hhookLowLevelKeyboardProc() = NULL;
#endif
return RetVal;
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_ResetInitialisation() // I3092
{
if(!Globals::ResetControllers())
{
SendDebugMessage(0, sdmGlobal, 0, "Keyman_ResetInitialisation: Failed because controllers and global data could not be reset.");
return FALSE;
}
return TRUE;
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_Initialise(HWND Handle, BOOL FSingleApp)
{
HINSTANCE hinst;
Globals::LoadDebugSettings();
if(Globals::get_Keyman_Initialised())
{
SendDebugMessage(Handle, sdmGlobal, 0, "Keyman_Initialise: Failed because " LIBRARY_NAME " is already initialised");
SetLastError(ERROR_ALREADY_INITIALIZED); // I3143 // I3523
return FALSE;
}
if(Globals::get_Keyman_Shutdown() || !ThreadGlobals()) // I3108 // I3127
{
*Globals::Keyman_Shutdown() = FALSE; // I2255, I2244
if(!Globals_InitThread()) // I3108 - must re-init thread because it would have failed during the initial startup of the process due to thinking we were shutting down
{
SendDebugMessage(Handle, sdmGlobal, 0, "Keyman_Initialise: Failed for " LIBRARY_NAME " because Keyman_Shutdown was set and could not be reset.");
// SetLastError already set by Globals_InitThread // I3143 // I3523
return FALSE;
}
}
if(FSingleApp)
*Globals::FSingleThread() = GetWindowThreadProcessId(Handle, NULL);
else
*Globals::FSingleThread() = 0;
*Globals::InitialisingThread() = GetCurrentThreadId(); // I4326
hinst = GetModuleHandle(LIBRARY_NAME);
InitDebugging();
if (!Globals::InitSettings()) {
SendDebugMessageFormat(Handle, sdmGlobal, 0, "Keyman_Initialise: Failed to initialise global settings. GetLastError = %d", GetLastError());
return FALSE;
}
if(!Globals::InitHandles()) // I3040
{
DebugLastError("Globals::InitHandles");
// SetLastError: Globals::InitHandles will return a windows error code // I3143 // I3523
return FALSE;
}
if(!InitialiseProcess(Handle))
{
SendDebugMessage(Handle, sdmGlobal, 0, "Keyman_Initialise: Failed to initialise for current process");
// SetLastError: InitialiseProcess will return an error code // I3143 // I3523
return FALSE; /* Failed to verify certificate */
}
#ifndef _WIN64
ISerialKeyEventServer::Startup();
#endif
InitHooks();
DWORD dwLastError = GetLastError();
SendDebugMessageFormat(Handle, sdmGlobal, 0, "GetMessage=%x CallWndProc=%x GetLastError=%d",
Globals::get_hhookGetMessage(), Globals::get_hhookCallWndProc(), dwLastError);
#ifndef _WIN64
SendDebugMessageFormat(Handle, sdmGlobal, 0, "Keyboard_LL=%x", // I4124
Globals::get_hhookLowLevelKeyboardProc());
#endif
UpdateActiveWindows();
*Globals::Keyman_Initialised() = TRUE;
RefreshKeyboards(TRUE); // I4786
SendDebugMessageFormat(Handle, sdmGlobal, 0, "Keyman is now initialised");
return TRUE;
}
/*void GetINIAdvanced(void) // I3158 // I3524
{
Globals::Ini()->ContextStackSize = 80;
Globals::Ini()->MsgStackSize = 80;
Globals::Ini()->MaxKeyboards = 32;
}*/
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_StartExit(void) // I3092
{
*Globals::Keyman_Shutdown() = TRUE;
return TRUE;
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_Exit(void)
{
if(Globals::get_InitialisingThread() != GetCurrentThreadId()) { // I4326
SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_Exit called from thread %d that did not initialise (which was %d)", GetCurrentThreadId(), Globals::get_InitialisingThread());
return FALSE;
}
#ifndef _WIN64
Hotkeys::Unload();
#endif
*Globals::InitialisingThread() = 0;
BOOL RetVal = TRUE;
*Globals::Keyman_Shutdown() = TRUE;
ReleaseKeyboards(TRUE);
Addin_Release();
if(!Globals::get_Keyman_Initialised())
{
return TRUE;
}
*Globals::Keyman_Initialised() = FALSE;
DWORD_PTR ret;
SendMessageTimeout(HWND_BROADCAST, wm_keyman_grabwindowproc, 0, 0, SMTO_ABORTIFHUNG | SMTO_NORMAL, 1000, &ret);
RetVal = RetVal && UninitHooks();
UninitDebugging();
#ifndef _WIN64
ISerialKeyEventServer::Shutdown();
#endif
//if(RetVal) ;
return RetVal;
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_RestartEngine()
{
if(Globals::get_Keyman_Initialised())
{
UninitHooks();
InitHooks();
UpdateActiveWindows(); // I1798
}
return TRUE;
}
/*******************************************************************************************/
/* */
/* Keyman Keyboard Override Functions */
/* */
/*******************************************************************************************/
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_StopForcingKeyboard();
void RefreshPreservedKeys(BOOL Activating);
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_ForceKeyboard(PCSTR FileName)
{
SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_ForceKeyboard: ENTER %s", FileName);
Keyman_StopForcingKeyboard(); // 7.0.219.0
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return FALSE;
strncpy(_td->ForceFileName, FileName, MAX_PATH - 1);
_td->ForceFileName[MAX_PATH-1] = 0;
if(_td->lpActiveKeyboard)
{
DeactivateDLLs(_td->lpActiveKeyboard);
}
_td->lpActiveKeyboard = new INTKEYBOARDINFO;
memset(_td->lpActiveKeyboard, 0, sizeof(INTKEYBOARDINFO)); // I2437 - Crash unloading keyboard due to keyboard options not init
_splitpath_s(FileName, NULL, 0, NULL, 0, _td->lpActiveKeyboard->Name, sizeof(_td->lpActiveKeyboard->Name), NULL, 0);
PWCHAR keyboardPath = strtowstr(_td->ForceFileName);
km_kbp_status err_status = km_kbp_keyboard_load(keyboardPath, &_td->lpActiveKeyboard->lpCoreKeyboard);
if (err_status != KM_KBP_STATUS_OK) {
SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard: km_kbp_keyboard_load failed for %ls with error status [%d]", keyboardPath, err_status);
delete keyboardPath;
goto fail;
}
delete keyboardPath;
SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard: %s OK", FileName);
km_kbp_option_item *core_environment = nullptr;
if(!SetupCoreEnvironment(&core_environment)) {
SendDebugMessageFormat(0, sdmLoad, 0, "Keyman_ForceKeyboard: Unable to set environment options for keyboard %s", FileName);
goto fail;
}
err_status =
km_kbp_state_create(_td->lpActiveKeyboard->lpCoreKeyboard, core_environment, &_td->lpActiveKeyboard->lpCoreKeyboardState);
DeleteCoreEnvironment(core_environment);
if (err_status != KM_KBP_STATUS_OK) {
SendDebugMessageFormat(
0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: km_kbp_state_create failed with error status [%d]", err_status);
// Dispose of the keyboard to leave us in a consitent state
ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard);
goto fail;
}
// TODO: #5882 Add km_kbp_event to reset keyboard action sent to keyman core
// (only the core knows the caps rules such CAPS_ALWAYS_OFF)
// so it can then respond with a possible reset
err_status = km_kbp_keyboard_get_imx_list(_td->lpActiveKeyboard->lpCoreKeyboard, &_td->lpActiveKeyboard->lpIMXList);
if (err_status != KM_KBP_STATUS_OK) {
SendDebugMessageFormat(0, sdmLoad, 0, "Keyman_ForceKeyboard Core: km_kbp_keyboard_get_imx_list failed with error status [%d]", err_status);
// Dispose of the keyboard to leave us in a consistent state
ReleaseStateMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboardState);
ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard);
goto fail;
}
LoadDLLs(_td->lpActiveKeyboard);
ActivateDLLs(_td->lpActiveKeyboard);
LoadKeyboardOptionsREGCore(_td->lpActiveKeyboard, _td->lpActiveKeyboard->lpCoreKeyboardState);
RefreshPreservedKeys(TRUE);
return TRUE;
// happy to use while(!done) pattern
fail:
delete _td->lpActiveKeyboard;
_td->lpActiveKeyboard = NULL;
_td->ForceFileName[0] = 0;
return FALSE;
}
extern "C" BOOL _declspec(dllexport) WINAPI Keyman_StopForcingKeyboard()
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td)
{
SetLastError(ERROR_KEYMAN_THREAD_DATA_NOT_READY); // I3173 // I3525
return FALSE;
}
if(!_td->lpActiveKeyboard)
{
SetLastError(ERROR_KEYMAN_KEYBOARD_NOT_ACTIVE); // I3173 // I3525
return FALSE;
}
SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_StopForcingKeyboard");
if(_td->ForceFileName[0])
{
SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_StopForcingKeyboard: Stopping forcing");
if(!DeactivateDLLs(_td->lpActiveKeyboard)) return FALSE; // I3173 // I3525
if(!UnloadDLLs(_td->lpActiveKeyboard)) return FALSE; // I3173 // I3525
_td->ForceFileName[0] = 0;
ReleaseStateMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboardState);
ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard);
RefreshPreservedKeys(FALSE);
delete _td->lpActiveKeyboard;
_td->lpActiveKeyboard = NULL;
}
return TRUE;
}
//---------------------------------------------------------------------------------------------------------
//
// Utility guff functions
//
//---------------------------------------------------------------------------------------------------------
/*
#define MENU_CLASS "#32768" // Windows Desktop Menu Class
BOOL IsDesktopMenuVisible()
{
static char str[128];
static HWND hWndDesktopMenu = NULL; // Windows Desktop Menu Window
if(!IsWindow(hWndDesktopMenu))
{
hWndDesktopMenu = GetWindow(GetDesktopWindow(), GW_CHILD);
while(hWndDesktopMenu)
{
GetClassName(hWndDesktopMenu, (LPSTR) str, sizeof(str));
if(strcmp((LPSTR) str, MENU_CLASS) == 0)
break;
hWndDesktopMenu = GetWindow(hWndDesktopMenu, GW_HWNDNEXT);
}
}
return(IsWindowVisible(hWndDesktopMenu));
}
*/
BOOL ReleaseKeyboardMemory(LPKEYBOARD kbd)
{
if(!kbd) return TRUE;
delete kbd;
return TRUE;
}
BOOL ReleaseStateMemoryCore(km_kbp_state **state) {
if (!*state) return TRUE;
km_kbp_state_dispose(*state);
*state = NULL;
return TRUE;
}
BOOL ReleaseKeyboardMemoryCore(km_kbp_keyboard **kbd) {
if (!*kbd)
return TRUE;
km_kbp_keyboard_dispose(*kbd);
*kbd = NULL;
return TRUE;
}
//---------------------------------------------------------------------------------------------------------
//
// Selecting Keyman Keyboards
//
//---------------------------------------------------------------------------------------------------------
extern "C" DWORD _declspec(dllexport) WINAPI GetActiveKeymanID()
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return FALSE;
return _td->ActiveKeymanID;
}
//---------------------------------------------------------------------------------------------------------
//
// Refreshing Keyman's keyboard list
//
//---------------------------------------------------------------------------------------------------------
// This function prevents a refresh event from being processed more than once
// by a thread, for instance if a thread has multiple top-level windows
BOOL UpdateRefreshTag(LONG tag) // I1835 - Reduce chatter
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return FALSE;
if(_td->RefreshTag_Thread != tag)
{
_td->RefreshTag_Thread = tag;
return TRUE;
}
return FALSE;
}
void HandleRefresh(int code, LONG tag)
{
switch (code)
{
case KR_REQUEST_REFRESH:
// This is sent by Keyman COM API, ApplyToRunningKeymanEngine
SendDebugMessageFormat(0, sdmGlobal, 0, "#### Refresh Requested ####");
// We ask the master controller window to tell all instances
// of keyman32/keyman64 that a refresh is coming through
Globals::PostMasterController(wm_keyman_refresh, KR_PRE_REFRESH, 0);
// We need to tell the controller window to refresh itself also
// TODO: eliminate this message (we can hop onto the above message)
Globals::PostMasterController(wm_keyman_control, KMC_REFRESH, 0);
break;
case KR_PRE_REFRESH:
#ifndef _WIN64
// We only need to broadcast the message from Win32; this avoids
// a double-broadcast which could happen if both keyman32 and keyman64
// receive the message, as they have independently managed RefreshTags
// This RefreshTag is only ever incremented here by the master controller
// so this is a thread safe operation
tag = ++(*Globals::RefreshTag());
if (UpdateRefreshTag(tag)) {
// The Keyman process gets the update first
RefreshKeyboards(FALSE);
PostMessage(HWND_BROADCAST, wm_keyman_refresh, KR_REFRESH, tag);
}
#endif
break;
case KR_REFRESH:
// All threads need to have their keyboard list
// refreshed after an update, but only once per
// refresh request
if (UpdateRefreshTag(tag)) {
#ifdef _WIN64
if (Globals::get_InitialisingThread() == GetCurrentThreadId()) {
// If this is the keymanx64 thread, then we should
// go ahead and process the refresh immediately so
// that global settings are updated
RefreshKeyboards(FALSE);
}
else
#endif
// We'll update when we are called into action
ScheduleRefresh();
}
break;
}
}
BOOL ConvertStringToGuid(WCHAR *buf, GUID *guid) // I3581
{
return IIDFromString(buf, guid) == S_OK;
}
void LoadBaseLayoutSettings() { // I4552 // I4583
char underlyingLayout[16] = "";
wchar_t baseLayout[MAX_PATH];
DWORD dwUnderlyingLayout = 0;
RegistryReadOnly *reg = new RegistryReadOnly(HKEY_CURRENT_USER);
if(reg->OpenKeyReadOnly(REGSZ_KeymanCU)) {
if(reg->ReadString(REGSZ_UnderlyingLayout, underlyingLayout, 15)) {
dwUnderlyingLayout = strtoul(underlyingLayout, NULL, 16); // I4516 // I4581
wsprintf(underlyingLayout, "%08x", (unsigned int) dwUnderlyingLayout); // I3759 // I4581
} else {
underlyingLayout[0] = 0;
}
Globals::SetBaseKeyboardFlags(underlyingLayout, reg->ReadInteger(REGSZ_SimulateAltGr), !reg->ValueExists(REGSZ_DeadkeyConversionMode) || reg->ReadInteger(REGSZ_DeadkeyConversionMode));
} else {
Globals::SetBaseKeyboardFlags("", 0, 1);
}
delete reg;
reg = new RegistryReadOnly(HKEY_LOCAL_MACHINE);
if(underlyingLayout[0] && // I4660
reg->OpenKeyReadOnly(REGSZ_SystemKeyboardLayouts) &&
reg->OpenKeyReadOnly(underlyingLayout) &&
reg->ReadString(L"layout file", baseLayout, MAX_PATH)) {
wchar_t langName[16], countryName[16], baseLayoutAlt[34];
if(GetLocaleInfoW(LOWORD(dwUnderlyingLayout), LOCALE_SISO639LANGNAME, langName, _countof(langName)) > 0 &&
GetLocaleInfoW(LOWORD(dwUnderlyingLayout), LOCALE_SISO3166CTRYNAME, countryName, _countof(countryName)) > 0) { // I4588 // I4786
wsprintfW(baseLayoutAlt, L"%ls-%ls", langName, countryName);
Globals::SetBaseKeyboardName(baseLayout, baseLayoutAlt);
} else {
Globals::SetBaseKeyboardName(baseLayout, L"en-US"); // I4786
}
} else {
Globals::SetBaseKeyboardName(L"kbdus.dll", L"en-US"); // I4786
}
delete reg;
}
// -----------------------------------------------------------------------------------------------
// - -
// - RefreshKeyboards: .... -
// - -
// -----------------------------------------------------------------------------------------------
void RefreshKeyboardProfiles(INTKEYBOARDINFO *kp, BOOL isTransient) {
RegistryReadOnly *reg2 = Reg_GetKeymanInstalledKeyboard(kp->Name); // I3581
if (reg2)
{
PCSTR RootKey = isTransient ? REGSZ_TransientLanguageProfiles : REGSZ_LanguageProfiles;
if (reg2->OpenKeyReadOnly(RootKey)) // I3581
{
char bufProfile[LOCALE_NAME_MAX_LENGTH];
int nProfile = 0;
while (reg2->GetKeyNames(bufProfile, LOCALE_NAME_MAX_LENGTH, nProfile))
{
RegistryReadOnly *reg3 = Reg_GetKeymanInstalledKeyboard(kp->Name);
if (reg3->OpenKeyReadOnly(RootKey) &&
reg3->OpenKeyReadOnly(bufProfile) &&
reg3->ValueExists(REGSZ_LanguageProfiles_LangID) &&
reg3->ValueExists(REGSZ_ProfileGUID))
{
kp->nProfiles++;
if (kp->nProfiles == 1) kp->Profiles = new INTKEYBOARDPROFILE[1];
else
{
INTKEYBOARDPROFILE *pProfiles = new INTKEYBOARDPROFILE[kp->nProfiles]; // I3761
memcpy(pProfiles, kp->Profiles, sizeof(INTKEYBOARDPROFILE) * (kp->nProfiles - 1)); // I3761
delete kp->Profiles;
kp->Profiles = pProfiles;
}
LPINTKEYBOARDPROFILE ikp = &kp->Profiles[kp->nProfiles - 1]; // I3761
WCHAR bufW[40];
reg3->ReadString(REGWSZ_ProfileGUID, bufW, _countof(bufW));
ConvertStringToGuid(bufW, &ikp->Guid);
ikp->LangID = (LANGID)reg3->ReadInteger(REGSZ_LanguageProfiles_LangID);
SendDebugMessageFormat(0, sdmGlobal, 0, "Found profile %ls, language id %x for keyboard %s", bufW, ikp->LangID, kp->Name);
}
delete reg3;
nProfile++;
}
}
delete reg2;
}
}
void ScheduleRefresh() {
if (GetCurrentThreadId() == GetWindowThreadProcessId(GetForegroundWindow(), NULL)) {
RefreshKeyboards(FALSE);
}
else {
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) {
return;
}
_td->RefreshRequired = TRUE;
}
}
void CheckScheduledRefresh() {
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) {
return;
}
if (_td->RefreshRequired) {
RefreshKeyboards(FALSE);
_td->RefreshRequired = FALSE;
}
}
void RefreshKeyboards(BOOL Initialising)
{
OutputThreadDebugString("RefreshKeyboards");
char sz[_MAX_FNAME];
char oldname[_MAX_FNAME];
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td || _td->FInRefreshKeyboards) return;
_td->FInRefreshKeyboards = TRUE;
// Can happen when multiple top-level windows for one process
Addin_Refresh();
SendDebugMessageFormat(0,sdmGlobal,0,"---ENTER RefreshKeyboards---");
//FInRefreshKeyboards = TRUE;
oldname[0] = 0;
if(!Initialising)
{
if(_td->lpActiveKeyboard)
strcpy(oldname, _td->lpActiveKeyboard->Name);
if(_td->lpActiveKeyboard) DeactivateDLLs(_td->lpActiveKeyboard);
_td->lpActiveKeyboard = NULL;
_td->ActiveKeymanID = KEYMANID_NONKEYMAN;
}
ReleaseKeyboards(TRUE);
/* Read the "keyboard off hotkey", simulate Alt+Gr, Hotkeys-Toggle flags */
#ifndef _WIN64
Hotkeys::Reload(); // I4326 // I4390
#endif
// Read global underlying keyboard flags
if(Globals::get_InitialisingThread() == GetCurrentThreadId()) {
LoadBaseLayoutSettings(); // I4583
}
/* Read the keyboards */
RegistryReadOnly *reg = new RegistryReadOnly(HKEY_CURRENT_USER);
if(reg->OpenKeyReadOnly(REGSZ_KeymanActiveKeyboards))
{
int i = 0, nk;
INTKEYBOARDINFO *kp;
_td->nKeyboards = 0;
while(reg->GetValueNames(sz, _MAX_FNAME, i))
{
/* Read the active keyboards */
nk = atoi(sz);
if(nk >= _td->nKeyboards)
{
kp = new INTKEYBOARDINFO[nk+1];
memset(kp, 0, sizeof(INTKEYBOARDINFO)*(nk+1));
if(_td->lpKeyboards)
{
memcpy(kp, _td->lpKeyboards, sizeof(INTKEYBOARDINFO)*_td->nKeyboards);
delete[] _td->lpKeyboards;
}
_td->nKeyboards = nk + 1;
_td->lpKeyboards = kp;
}
kp = &_td->lpKeyboards[nk]; // I3760
kp->KeymanID = atoi(sz);
reg->ReadString(sz, kp->Name, 64);
/* Read the shadow keyboard id */ // I3613
kp->nProfiles = 0;
kp->Profiles = NULL;
RefreshKeyboardProfiles(kp, FALSE); // Read standard profiles
RefreshKeyboardProfiles(kp, TRUE); // Read transient profiles
SendDebugMessageFormat(0,sdmGlobal,0,"RefreshKeyboards: Added keyboard %s, %d",
kp->Name, kp->KeymanID);
i++;
}
}
delete reg; // I2714
if(!Initialising)
for(int i = 0; i < _td->nKeyboards; i++)
if(!_strcmpi(_td->lpKeyboards[i].Name, oldname))
{
SelectKeyboard(_td->lpKeyboards[i].KeymanID); // I3594
break;
}
SendDebugMessageFormat(0,sdmGlobal,0,"---LEAVE RefreshKeyboards---");
_td->FInRefreshKeyboards = FALSE;
}
void ReleaseKeyboards(BOOL Lock)
{
OutputThreadDebugString("ReleaseKeyboards");
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td || !_td->lpKeyboards) return;
if(Lock) if(_td->lpActiveKeyboard && !_td->ForceFileName[0]) DeactivateDLLs(_td->lpActiveKeyboard);
for(int i = 0; i < _td->nKeyboards; i++)
{
if(Lock) UnloadDLLs(&_td->lpKeyboards[i]);
ReleaseStateMemoryCore(&_td->lpKeyboards[i].lpCoreKeyboardState);
ReleaseKeyboardMemoryCore(&_td->lpKeyboards[i].lpCoreKeyboard);
if(_td->lpKeyboards[i].Profiles) delete _td->lpKeyboards[i].Profiles; // I3581
}
_td->nKeyboards = 0;
delete _td->lpKeyboards;
_td->lpKeyboards = NULL;
if(!_td->ForceFileName[0]) _td->lpActiveKeyboard = NULL;
}
/**
Keyman_GetLastActiveWindow and Keyman_GetLastFocusWindow were previously published as part of keymanapi.h.
They are now used only by the COM API internally.
*/
extern "C" HWND _declspec(dllexport) WINAPI Keyman_GetLastActiveWindow()
{
return (HWND) Globals::SendMasterController(wm_keyman_control, KMC_GETLASTACTIVE, 0);
}
extern "C" HWND _declspec(dllexport) WINAPI Keyman_GetLastFocusWindow()
{
return (HWND) Globals::SendMasterController(wm_keyman_control, KMC_GETLASTFOCUS, 0);
}
/**
GetSystemStore was previously published as an internal API. It is now available
only within keyman32/keyman64.
*/
PWSTR GetSystemStore(LPKEYBOARD kb, DWORD SystemID)
{
for (DWORD i = 0; i < kb->cxStoreArray; i++)
if (kb->dpStoreArray[i].dwSystemID == SystemID) return kb->dpStoreArray[i].dpString;
return NULL;
}
void PostDummyKeyEvent() { // I3301 - Handle I3250 regression with inadvertent menu activation with Alt keys // I3534 // I4844
keybd_event((BYTE) Globals::get_vk_prefix(), SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0); // I3250 - is this unnecessary?
keybd_event((BYTE) Globals::get_vk_prefix(), SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0); // I3250 - is this unnecessary?
}