/*
  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
	/*_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);
  // TODO: 5442 - remove if/ else as there will no longer be the old LoadKeyboard option
  if (Globals::get_CoreIntegration()) {
    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 Core: km_kbp_keyboard_load failed for %ls with error status [%d]", keyboardPath, err_status); // TODO: 5442 - remove word Core
      delete keyboardPath;
      return FALSE;
    }
    delete keyboardPath;
    SendDebugMessageFormat(0, sdmGlobal, 0, "Keyman_ForceKeyboard Core: %s OK", FileName); // TODO: 5442 - remove word Core

    km_kbp_option_item *core_environment = nullptr;

    if(!SetupCoreEnvironment(&core_environment)) {
      SendDebugMessageFormat(0, sdmLoad, 0, "Keyman_ForceKeyboard Core: Unable to set environment options for keyboard %s", FileName); // TODO: 5442 - remove word Core
      return FALSE;
    }

    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);
      return FALSE;
    }

    ResetCapsLock();
    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
      ReleaseKeyboardMemoryCore(&_td->lpActiveKeyboard->lpCoreKeyboard);
      return FALSE;
    }

    LoadDLLsCore(_td->lpActiveKeyboard);
    ActivateDLLs(_td->lpActiveKeyboard);
    LoadKeyboardOptionsREGCore(_td->lpActiveKeyboard, _td->lpActiveKeyboard->lpCoreKeyboardState);
    RefreshPreservedKeys(TRUE);
    return TRUE;
  } else {
    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);
    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

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