spiegel-keyman/windows/src/engine/keyman32/Keyman32.cpp
Marc Durdin ccc6498739 fix(windows): Keyman was crashing sporadically
Fixes #2147.

This potentially addresses the issues reported in #2147 where Keyman
would crash due to a race condition as a critical section was not
deleted before attempting to recreate it.

Some logging has been added for debugging these issues.

Also fixes memory leaks and another race on startup relating to loading
the taskbar icon.

There are still some known memory leaks because DLL_THREAD_DETACH is
not called for all threads before keyman32.dll is unloaded, so those
threads don't get the opportunity to clean themsselves up. However, the
key danger with the csGlobals critical section has been addressed.
2019-11-06 17:44:32 +11:00

1076 lines
41 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 Desktop
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
*/
// I3616 // I4169 // I5136
//
// WARNING: Virtual key output is partially supported in this file, however
// it has never been fully debugged; several known problems exist:
// 1) Interference with backspace
// 2) Performance hit
// some code has been commented out; you may wish to debug and
// uncomment this code if you find it necessary to support it.
// (We also ran out of time in testing and debugging it; it is
// very complex and totally illogical - just to encourage you to
// start with it!)
//
#include "pch.h"
#include "serialkeyeventserver.h"
/*
If DEBUG is defined, give information in the map file about PRIVATE (=static)
functions that don't normally appear in the map file.
*/
//#define CERTCHECK_DISABLE
#define KEYMAN_MSGFLAG 0x4000000L
#define DEBUGLOG
LRESULT CALLBACK kmnKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
int MapVirtualKeys(int keyCode, UINT shiftFlags);
int KPostMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
HINSTANCE g_hInstance;
/*******************************************************************************************/
/* */
/* Keyman Initialisation */
/* */
/*******************************************************************************************/
BOOL TestDebugProcess()
{
/*WCHAR buf[260];
GetModuleFileNameW(NULL, buf, 260);
if(wcsicmp(buf, L"c:\\temp\\keymanx64.exe") != 0) return FALSE;*/
return TRUE;
}
BOOL ShouldAttachToProcess();
BOOL __stdcall DllMain(HINSTANCE hinstDll, DWORD fdwReason, LPVOID reserved)
{
UNREFERENCED_PARAMETER(reserved);
g_hInstance = hinstDll;
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
//if(!TestDebugProcess()) return FALSE;
//if(!ShouldAttachToProcess()) return FALSE;
OutputThreadDebugString("DLL_PROCESS_ATTACH");
if(!Globals_InitProcess()) return FALSE;
break;
case DLL_PROCESS_DETACH:
//if(!TestDebugProcess()) return FALSE;
if (reserved == NULL) {
OutputThreadDebugString("DLL_PROCESS_DETACH not terminating");
// If reserved == NULL, that means the library is being unloaded, but
// the process is not terminating.
//
// We only cleanup after ourselves if the process is not terminating
// because of an issue with the order of DLL detach: if msctf.dll is
// detached first, then we end up causing an exception in msctf when
// we try to do our cleanup in CloseTSF.
//
// https://devblogs.microsoft.com/oldnewthing/20120105-00/?p=8683
//
// See https://github.com/keymanapp/keyman/issues/1723 for details
// relating to SumatraPDF. Note that this is hard to reproduce; I
// have been unable to reproduce the issue on my test machines.
//
// Note: Keyman may be violating a loader lock rule by calling
// CloseTSF from here. This needs further investigation...
UninitialiseProcess(FALSE);
Globals_UninitProcess();
}
else {
OutputThreadDebugString("DLL_PROCESS_DETACH terminating");
}
break;
case DLL_THREAD_ATTACH:
//if(!TestDebugProcess()) return FALSE;
OutputThreadDebugString("DLL_THREAD_ATTACH");
Globals_InitThread();
break;
case DLL_THREAD_DETACH:
//if(!TestDebugProcess()) return FALSE;
OutputThreadDebugString("DLL_THREAD_DETACH");
UninitialiseProcess(FALSE);
Globals_UninitThread();
break;
}
return TRUE;
}
void UninitDebuggingEx();
BOOL UninitialiseProcess(BOOL Lock)
{
//SendDebugMessageFormat(0, sdmGlobal, 0, "DLL_PROCESS_DETACH");
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()
{
PChangeWindowMessageFilter = (BOOL (WINAPI *)(UINT,DWORD))GetProcAddress(LoadLibrary("user32"), "ChangeWindowMessageFilter");
if(!PChangeWindowMessageFilter)
return;
DoCWMF(wm_keyman); // I3594
DoCWMF(wm_keyman_keyevent);
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
}
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_keyman_keyevent = RegisterWindowMessage("WM_KEYMAN_KEYEVENT");
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);
_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
/*_td->lpActiveKeyboard->KeymanID = 0;
_td->lpActiveKeyboard->nIMDLLs = 0;
_td->lpActiveKeyboard->IMDLLs = NULL;
_td->lpActiveKeyboard->KeyboardOptions = NULL;*/
_splitpath_s(FileName, NULL, 0, NULL, 0, _td->lpActiveKeyboard->Name, sizeof(_td->lpActiveKeyboard->Name), NULL, 0);
if(LoadKeyboard(_td->ForceFileName, &_td->lpActiveKeyboard->Keyboard))
{
SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_ForceKeyboard: %s OK", FileName);
ResetCapsLock();
LoadDLLs(_td->lpActiveKeyboard);
ActivateDLLs(_td->lpActiveKeyboard);
LoadKeyboardOptions(_td->lpActiveKeyboard); // I2437 - Crash unloading keyboard due to keyboard options not set
RefreshPreservedKeys(TRUE);
return TRUE;
}
SendDebugMessageFormat(0,sdmGlobal,0,"Keyman_ForceKeyboard: %s FAIL", FileName);
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;
FreeKeyboardOptions(_td->lpActiveKeyboard);
ReleaseKeyboardMemory(_td->lpActiveKeyboard->Keyboard);
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;
}
//---------------------------------------------------------------------------------------------------------
//
// 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
//
//---------------------------------------------------------------------------------------------------------
BOOL UpdateRefreshTag(LONG tag) // I1835 - Reduce chatter
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return FALSE;
if(_td->RefreshTag_Process < tag)
{
_td->RefreshTag_Process = tag;
return TRUE;
}
return FALSE;
/*LONG lOriginal, lResult;
do {
lOriginal = _td->RefreshTag_Process;
lResult = max(lOriginal, tag);
} while(InterlockedCompareExchange(&_td->RefreshTag_Process, lResult, lOriginal) != lOriginal);
return lResult > lOriginal;*/
}
void HandleRefresh(int code, LONG tag)
{
switch(code)
{
case KR_REQUEST_REFRESH:
SendDebugMessageFormat(0,sdmGlobal,0,"#### Refresh Requested ####");
//PostMessage(GetDesktopWindow(), wm_keyman_refresh, KR_PRE_REFRESH, 0);
Globals::PostControllers(wm_keyman_refresh, KR_PRE_REFRESH, 0);
Globals::PostControllers(wm_keyman_control, KMC_REFRESH, 0);
break;
case KR_PRE_REFRESH:
tag = InterlockedIncrement(Globals::RefreshTag());
if(UpdateRefreshTag(tag))
RefreshKeyboards(FALSE); // The Keyman window gets the update first
PostMessage(HWND_BROADCAST, wm_keyman_refresh, KR_REFRESH, tag);
break;
case KR_REFRESH:
if(UpdateRefreshTag(tag))
RefreshKeyboards(FALSE);
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", 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, 0);
}
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"%s-%s", 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 RefreshKeyboards(BOOL Initialising)
{
OutputThreadDebugString("RefreshKeyboards");
char sz[_MAX_FNAME];
char oldname[_MAX_FNAME];
RegistryReadOnly *reg2;
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(_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
reg2 = Reg_GetKeymanInstalledKeyboard(kp->Name); // I3581
if(reg2)
{
if(reg2->OpenKeyReadOnly(REGSZ_LanguageProfiles)) // I3581
{
char bufProfile[LOCALE_NAME_MAX_LENGTH];
int nProfile = 0;
kp->nProfiles = 0;
kp->Profiles = NULL;
while(reg2->GetKeyNames(bufProfile, LOCALE_NAME_MAX_LENGTH, nProfile))
{
RegistryReadOnly *reg3 = Reg_GetKeymanInstalledKeyboard(kp->Name);
if(reg3->OpenKeyReadOnly(REGSZ_LanguageProfiles) && reg3->OpenKeyReadOnly(bufProfile))
{
if(reg3->ValueExists(REGSZ_ProfileGUID) && reg3->ValueExists("LangID") && reg3->ValueExists("Locale")) //TODO: constnats
{
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(L"profile guid", bufW, _countof(bufW)); //TODO define string constant
ConvertStringToGuid(bufW, &ikp->Guid);
reg3->ReadString(L"Locale", ikp->Locale, _countof(ikp->Locale));
ikp->LangID = (LANGID) reg3->ReadInteger("LangID");
}
}
delete reg3;
nProfile++;
}
}
delete reg2;
}
kp->Keyboard = NULL;
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]);
FreeKeyboardOptions(&_td->lpKeyboards[i]);
ReleaseKeyboardMemory(_td->lpKeyboards[i].Keyboard);
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;
}
BOOL ShouldAttachToProcess()
{
return TRUE;
#if 0
char buf[MAX_PATH]; -- if you return false, the DLL repeatedly attempts to attach to the process for every message received by the process -- oops!
HINSTANCE hinst = GetModuleHandle(LIBRARY_NAME);
if(!GetModuleFileName(hinst, buf, MAX_PATH)) return 0;
char drive[_MAX_DRIVE], dir[_MAX_DIR];
_splitpath_s(buf, drive, _countof(drive), dir, _countof(dir), NULL, 0, NULL, 0); // I3547
_makepath_s(buf, _countof(buf), drive, dir, "process", ".cfg"); // I3547
HANDLE hConfig = CreateFile(buf, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(hConfig != INVALID_HANDLE_VALUE)
{
DWORD sz = 0;
if(ReadFile(hConfig, buf, MAX_PATH-1, &sz, NULL))
{
CloseHandle(hConfig);
assert(sz < MAX_PATH);
buf[sz] = 0;
_strlwr(buf);
if(strstr(buf, "attachtoallprocesses") != NULL) return TRUE;
}
else
CloseHandle(hConfig);
}
if(!GetModuleFileName(NULL, buf, MAX_PATH)) return 0;
char *p = buf + lstrlen(buf) - lstrlen("cmd.exe");
if(p >= buf && !lstrcmpi(p, "cmd.exe")) return 0; /* NEVER, EVER, ATTACH TO CMD */
if(p >= buf && !lstrcmpi(p, "dwm.exe")) return 0; /* NEVER, EVER, ATTACH TO DWM */
p = buf + lstrlen(buf) - lstrlen("csrss.exe");
if(p >= buf && !lstrcmpi(p, "csrss.exe")) return 0; /* NEVER, EVER, ATTACH TO CSRSS */
if(p >= buf && !lstrcmpi(p, "lsass.exe")) return 0; /* NEVER, EVER, ATTACH TO CSRSS */
if(p >= buf && !lstrcmpi(p, "mstsc.exe")) return 0; /* NEVER, EVER, ATTACH TO RDPCLIENT */
p = buf + lstrlen(buf) - lstrlen("ehmsas.exe");
if(p >= buf && !lstrcmpi(p, "ehmsas.exe")) return 0; /* NEVER, EVER, ATTACH TO ehmsas */
p = buf + lstrlen(buf) - lstrlen("regedit.exe");
if(p >= buf && !lstrcmpi(p, "svchost.exe")) return 0; /* NEVER, EVER, ATTACH TO SVCHOST */
if(p >= buf && !lstrcmpi(p, "wininit.exe")) return 0; /* NEVER, EVER, ATTACH TO WININIT */
if(p >= buf && !lstrcmpi(p, "rdpclip.exe")) return 0; /* NEVER, EVER, ATTACH TO WININIT */
if(p >= buf && !lstrcmpi(p, "wuauclt.exe")) return 0; /* NEVER, EVER, ATTACH TO WINDEBUG */
p = buf + lstrlen(buf) - lstrlen("winlogon.exe");
if(p >= buf && !lstrcmpi(p, "services.exe")) return 0; /* NEVER, EVER, ATTACH TO SERVICES */
if(p >= buf && !lstrcmpi(p, "winlogon.exe")) return 0; /* NEVER, EVER, ATTACH TO WINLOGON */
if(p >= buf && !lstrcmpi(p, "windebug.exe")) return 0; /* NEVER, EVER, ATTACH TO WINDEBUG */
p = buf + lstrlen(buf) - lstrlen("vmwareuser.exe");
if(p >= buf && !lstrcmpi(p, "vmwareuser.exe")) return 0; /* NEVER, EVER, ATTACH TO VMWAREUSER */
return TRUE;
#endif
}
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?
}