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

525 lines
19 KiB
C++

/*
Name: kmhook_getmessage
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 - Visual keyboard updating
14 Sep 2006 - mcdurdin - Add wxm_keymanchar message for character map and other tools
04 Dec 2006 - mcdurdin - Add support for mosewheel on character map even when not focused
22 Jan 2007 - mcdurdin - Fix for K_NPENTER
16 May 2007 - mcdurdin - I819 - Test for Keyman functioning
30 May 2007 - mcdurdin - I864 - Log exceptions in hook procedures
13 Jul 2007 - mcdurdin - I934 - Prep for x64
27 Mar 2008 - mcdurdin - I1337 - move mousewheel handling before PM_REMOVE to avoid mousing both osk and active app
16 Jan 2009 - mcdurdin - Minor performance improvements around debugging
27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration
30 Jan 2009 - mcdurdin - I1835 - Improve refresh performance
11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888
11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
11 Dec 2009 - mcdurdin - I1455 - keyboard activation per 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 apps
06 Apr 2010 - mcdurdin - I2271 - Select Keyboard tidy up
04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value
04 May 2010 - mcdurdin - I2297 - Keyboard and focus switching reliability
10 May 2010 - mcdurdin - Enable exception logging
25 May 2010 - mcdurdin - I1632 - Keyboard Options
24 Jun 2010 - mcdurdin - I2435 - Keyman Engine stays attached to a number of apps when Keyman shuts down - due to local keyboardhook attachment
29 Jun 2010 - mcdurdin - I2435 - Make Keyman exit more reliably by sending rather than posting KM_EXIT
29 Jun 2010 - mcdurdin - I2435 - Prevent Keyman from re-initialising when it is shutting down
18 Feb 2011 - mcdurdin - I2712 - Font helper not supporting SMP
12 May 2011 - mcdurdin - I2908 - Fix double strike issue
19 Jul 2011 - mcdurdin - I2999 - If switch for all apps is off, Keyman icon does not reflect active keyboard when application switched
17 Aug 2012 - mcdurdin - I3432 - V9.0 - Add support for &platform and &baselayout to Keyman Engine
21 Feb 2012 - mcdurdin - I3253 - Wrong hook handle passed to CallNextHookEx
03 Jul 2012 - mcdurdin - I3385 - Keyman Engine thinks it is in Keyman D_esktop Light mode in parent process when starting
04 Nov 2012 - mcdurdin - I3532 - V9.0 - Merge of I3253 - Wrong hook handle passed to CallNextHookEx
04 Nov 2012 - mcdurdin - I3533 - V9.0 - Merge of I3385 - Keyman Engine thinks it is in Keyman D_esktop Light mode in parent process when starting
20 Nov 2012 - mcdurdin - I3583 - V9.0 - Remove AITIP::NotifyKeyboardChange as obsolete
28 Nov 2012 - mcdurdin - I3594 - V9.0 - Remove old SelectKeyboard code and related messages
01 Dec 2012 - mcdurdin - I3617 - V9.0 - Keyboard hook obsolete, strip out code
24 Oct 2013 - mcdurdin - I3933 - V9.0 - Keyman tray icon menu is not showing installed keyboards
07 Nov 2013 - mcdurdin - I3949 - V9.0 - Keyboard selection and notification needs consolidation
24 Apr 2014 - mcdurdin - I4196 - V9.0 - wm_kmmoreposting must be refactored for TIP work as it is not sequential
16 Jun 2014 - mcdurdin - I4271 - V9.0 - Switch language for all applications is not working
23 Jun 2014 - mcdurdin - I4287 - V9.0 - Remove extraneous AppIntegration class type tests
25 Sep 2014 - mcdurdin - I4412 - V9.0 - Character Map needs to insert characters using SendInput
29 Mar 2015 - mcdurdin - I4642 - V9.0 - In Logos, generated backspace receives a scan of 0x00 instead of 0xFF
22 Apr 2015 - mcdurdin - I4674 - V9.0 - Hotkeys do not always work consistently
09 Aug 2015 - mcdurdin - I4793 - Race condition with preserved keys and modifiers
28 Mar 2016 - mcdurdin - I4933 - Compat issue with Firefox 42 and IE and Keyman 9 TSF
25 Oct 2016 - mcdurdin - I5136 - Remove additional product references from Keyman Engine
*/
// I3583 // I4287
#include "pch.h"
void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam);
void ProcessWMKeymanControl(HWND hwnd, WPARAM wParam, LPARAM lParam);
void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam);
void GetCapsAndNumlockState();
/*
BOOL SysTabCtrl(HWND hwnd)
{
char buf[64];
GetClassName(hwnd, buf, 64);
return !strcmpi(buf, "SysTabControl32");
}
*/
BOOL IsMessageEquivalent(LPMSG m1, LPMSG m2)
{
return m1->hwnd == m2->hwnd &&
m1->lParam == m2->lParam &&
m1->message == m2->message &&
m1->time == m2->time &&
m1->pt.x == m2->pt.x &&
m1->pt.y == m2->pt.y &&
m1->wParam == m2->wParam;
}
LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LRESULT res = 0;
#ifdef _DEBUG_EXCEPTION
res = _kmnGetMessageProc(nCode,wParam,lParam);
#else
__try
{
res = _kmnGetMessageProc(nCode,wParam,lParam);
}
__except(ExceptionMessage("kmnGetMessageProc", GetExceptionInformation()))
{
}
#endif
return res;
}
LRESULT _kmnGetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
LPMSG mp;
mp = (LPMSG)lParam;
// If the message sent should be ignored, just return
if(nCode < 0)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td)
{
if(!Globals::CheckControllers())
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
if(!Globals_InitThread())
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
_td = ThreadGlobals();
if(!_td)
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(!_td->FInitialised) // Keyman keyboard is being selected for first time in active process
{
if(!Globals::CheckControllers()) return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam); // I2435 - Prevent Keyman from re-initialising when it is shutting down // I3253 // I3532
if(!InitialiseProcess(mp->hwnd)) return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if (((mp->message >= WM_KEYFIRST && mp->message <= WM_KEYLAST) || mp->message == wm_keymankeydown || mp->message == wm_keymankeyup ||
mp->message == wm_keyman_keyevent)
&& ShouldDebug(sdmMessage)) {
DebugMessage(mp, wParam);
}
if ((mp->message == WM_KEYDOWN || mp->message == WM_SYSKEYDOWN || mp->message == WM_KEYUP || mp->message == WM_SYSKEYUP)) { // I4642
BYTE scan = KEYMSG_LPARAM_SCAN(mp->lParam);
CheckScheduledRefresh();
_td->LastScanCode = scan;
_td->LastKey = mp->wParam;
switch (mp->wParam) {
case VK_MENU:
case VK_CONTROL:
case VK_SHIFT:
if (scan != SCAN_FLAG_KEYMAN_KEY_EVENT) { // I4793
ProcessModifierChange((UINT)mp->wParam, mp->message == WM_KEYUP || mp->message == WM_SYSKEYUP, KEYMSG_FLAG_EXTENDED(mp->lParam));
}
break;
}
// 4 Aug 2003 - mcdurdin - Stuff any Keyman wm_key* message with the correct scancode
if (mp->wParam != VK_BACK) {
if(scan == SCAN_FLAG_KEYMAN_KEY_EVENT) {
mp->lParam = (mp->lParam & 0xFF00FFFFL) | (MapVirtualKey((UINT)mp->wParam, 0) << 16);
}
}
}
// I1337 - move mousewheel message checking before the PM_REMOVE handling - otherwise we can't pre-modify the message
// TODO: Move to WH_MOUSE_LL proc in main process
if(mp->message == WM_MOUSEWHEEL)
{
HWND hwndMouseWheel = FindWindow("TfrmVisualKeyboard", NULL);
if(hwndMouseWheel && GetWindowThreadProcessId(mp->hwnd, NULL) != GetWindowThreadProcessId(hwndMouseWheel, NULL))
{
RECT rcMouseWheel;
POINT pt;
GetWindowRect(hwndMouseWheel, &rcMouseWheel);
pt.x = LOWORD(mp->lParam);
pt.y = HIWORD(mp->lParam);
if(PtInRect(&rcMouseWheel, pt))
{
// if(!_td->state.PreviousNoRemove) // I2908
PostMessage(WindowFromPoint(pt), WM_MOUSEWHEEL, mp->wParam, mp->lParam);
mp->message = WM_NULL;
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
}
}
if((wParam & PM_REMOVE) == 0) // I2908
{
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
#if 0 // I2908
if(_td->state.PreviousNoRemove && IsMessageEquivalent(&_td->state.msg, mp))
{
/*
Was the previous message the same as this one. If so, don't process it again.
*/
_td->state.PreviousNoRemove = (wParam & PM_REMOVE) == 0;
_td->state.msg = *mp;
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
#endif
_td->state.msg = *mp;
/*_td->state.PreviousNoRemove = (wParam & PM_REMOVE) == 0; I2908 */
// 16 April 2005, 7 May 2007 - mcdurdin - Check if Keyman is receiving messages (reimplement from v6.2) */
if(mp->message == wm_test_keyman_functioning && mp->wParam == TKF_PING)
{
PostMessage(mp->hwnd, wm_test_keyman_functioning, TKF_RESPONSE, mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
// I3594
if(mp->message == wm_keyman_refresh)
{
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_refresh %d tag=%d", mp->wParam, mp->lParam);
HandleRefresh((int) mp->wParam, (LONG) mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(mp->message == wm_keyman_control_internal) // I3933
{
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_control_internal hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam); // I4674
ProcessWMKeymanControlInternal(mp->hwnd, mp->wParam, mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if (mp->message == wm_keyman_control)
{
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman_control hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam);
ProcessWMKeymanControl(mp->hwnd, mp->wParam, mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(mp->message == wm_keyman) // I3933
{
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keyman hwnd=%x %x %x", mp->hwnd, mp->wParam, mp->lParam); // I4674
ProcessWMKeyman(mp->hwnd, mp->wParam, mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
/*
Handle WM_UNICHAR messages for RichEdit control -- should we test RichEdit version?
*/
if(mp->message == WM_UNICHAR && Addin_ShouldProcessUnichar(mp->hwnd))
{
if(Addin_ProcessUnichar(mp->hwnd, (DWORD) mp->wParam))
{
mp->message = 0;
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
}
/*
Handle wm_keyman_control_internal messages
*/
/*
If the keyboard needs to be activated, select it: messaging from the main application
*/
if(_td->lpActiveKeyboard)
{
_td->state.lpkb = _td->lpActiveKeyboard->Keyboard;
_td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard;
}
// I4412
if(mp->message == wm_keymanshift)
{
SendDebugMessageFormat(0, sdmInternat, 0, "GetMessage: wm_keymanshift %x %x", mp->wParam, mp->lParam);
SelectApplicationIntegration();
if(!_td->app->IsWindowHandled(mp->hwnd)) _td->app->HandleWindow(mp->hwnd);
if(_td->app->DebugControlled())
{
if(mp->wParam == 1) *Globals::ShiftState() = (DWORD) mp->lParam;
else *Globals::ShiftState() = 0;
}
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(*Globals::hwndIM() != 0)
{
if(mp->message == wm_keymanim_close)
{
if(mp->lParam) KMHideIM();
if(mp->wParam)
{
if(_td->app)
{
BOOL isUsingCoreProcessor = Globals::get_CoreIntegration();
if (isUsingCoreProcessor) {
// Call the core keyboard processor to process the queued actions
if (!_td->lpActiveKeyboard) {
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if (KM_KBP_STATUS_OK != km_kbp_process_queued_actions(_td->lpActiveKeyboard->lpCoreKeyboardState)) {
SendDebugMessageFormat(0, sdmGlobal, 0, "_kmnGetMessageProc wm_keymanim_close process event fail");
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
BOOL emitKeyStroke;
ProcessActions(&emitKeyStroke);
}
_td->app->SetCurrentShiftState(Globals::get_ShiftState());
_td->app->SendActions();
}
}
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(!*Globals::hwndIMAlways())
{
if(mp->message == wm_keymankeydown)
PostMessage(*Globals::hwndIM(), WM_KEYDOWN, mp->wParam, mp->lParam);
else if(mp->message == wm_keymankeyup)
PostMessage(*Globals::hwndIM(), WM_KEYUP, mp->wParam, mp->lParam);
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
}
/*
If no keyboard is active, return
*/
if(!_td->lpActiveKeyboard || _td->FInRefreshKeyboards)
{
if(_td->app) _td->app->ResetContext();
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
if(mp->message > WM_MOUSEFIRST && mp->message <= WM_MOUSELAST && !IsIMWindow(mp->hwnd))
{
// > MOUSEFIRST ignores mouse movement
if(_td->app) _td->app->ResetContext();
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
/* // I3617
Text Services Framework tells us we don't need to continue here
*/
return CallNextHookEx(Globals::get_hhookGetMessage(), nCode, wParam, lParam);
}
void ProcessWMKeyman(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if(!_td) return;
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ProcessWMKeyman wm_keyman: %d %d", wParam, lParam);
switch(wParam)
{
case KM_DISABLEUI:
_td->KeymanUIDisabled = TRUE;
break;
case KM_ENABLEUI:
_td->KeymanUIDisabled = FALSE;
break;
case KM_FOCUSCHANGED:
if(lParam & KMF_WINDOWCHANGED)
{
hwnd = GetFocus();
if(_td->lpActiveKeyboard) {
_td->state.lpkb = _td->lpActiveKeyboard->Keyboard;
_td->state.lpCoreKb = _td->lpActiveKeyboard->lpCoreKeyboard;
}
SelectApplicationIntegration();
if(_td->app && !_td->app->IsWindowHandled(hwnd)) _td->app->HandleWindow(hwnd);
_td->state.windowunicode = !_td->app || _td->app->IsUnicode();
if(IsFocusedThread())
{
if(_td->app) _td->app->ResetQueue();
GetCapsAndNumlockState();
Addin_FocusChanged(hwnd);
UpdateActiveWindows();
}
}
break;
}
}
void ProcessWMKeymanControlInternal(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(hwnd);
switch(wParam)
{
case KMCI_GETACTIVEKEYBOARD: // I3933
ReportActiveKeyboard(LOWORD(lParam)); // I3949
break;
case KMCI_SELECTKEYBOARD_BACKGROUND: // I4271
case KMCI_SELECTKEYBOARD: // I3933
SelectKeyboardHKL((DWORD) lParam, wParam == KMCI_SELECTKEYBOARD);
break;
case KMCI_SELECTKEYBOARD_BACKGROUND_TSF: // I4271
case KMCI_SELECTKEYBOARD_TSF: // I3933
SelectKeyboardTSF((DWORD) lParam, wParam == KMCI_SELECTKEYBOARD_TSF);
break;
case KMCI_SETFOREGROUND: // I3933
SetForegroundWindow(hwnd);
break;
}
}
void
ProcessWMKeymanControl(HWND hwnd, WPARAM wParam, LPARAM lParam) {
UNREFERENCED_PARAMETER(hwnd);
switch (wParam) {
case KMC_PROFILECHANGED:
if (!Globals::IsControllerThread(GetCurrentThreadId())) {
break;
}
WORD wAtom = HIWORD(lParam);
char atomStr[128];
if (GetAtomName(wAtom, atomStr, 128)) {
// Is this a keyman keyboard check c_clsidKMTipTextService
WCHAR clsidstr[40];
StringFromGUID2(c_clsidKMTipTextService, clsidstr, _countof(clsidstr));
PWSTR atomWstr = strtowstr(atomStr);
if (wcsstr(atomWstr, clsidstr)) {
isKeymanKeyboardActive = TRUE;
}
else {
isKeymanKeyboardActive = FALSE;
}
}
break;
}
}
/*
GetCapsAndNumlockState:
Updates the global caps and numlock state when a window is focused, because it
may have been reset while Keyman was not aware of it
*/
void GetCapsAndNumlockState() { // I4793
DWORD n = Globals::get_ShiftState();
if(GetKeyState(VK_NUMLOCK) & 1) *Globals::ShiftState() |= NUMLOCKFLAG;
else *Globals::ShiftState() &= ~NUMLOCKFLAG;
if(GetKeyState(VK_CAPITAL) & 1) *Globals::ShiftState() |= CAPITALFLAG;
else *Globals::ShiftState() &= ~CAPITALFLAG;
if(GetKeyState(VK_SHIFT) < 0) *Globals::ShiftState() |= K_SHIFTFLAG;
else *Globals::ShiftState() &= ~K_SHIFTFLAG;
if(GetKeyState(VK_RCONTROL) < 0) *Globals::ShiftState() |= RCTRLFLAG;
else *Globals::ShiftState() &= ~RCTRLFLAG;
if(GetKeyState(VK_LCONTROL) < 0) *Globals::ShiftState() |= LCTRLFLAG;
else *Globals::ShiftState() &= ~LCTRLFLAG;
if(GetKeyState(VK_LMENU) < 0) *Globals::ShiftState() |= LALTFLAG;
else *Globals::ShiftState() &= ~LALTFLAG;
if(GetKeyState(VK_RMENU) < 0) *Globals::ShiftState() |= RALTFLAG;
else *Globals::ShiftState() &= ~RALTFLAG;
SendDebugMessageFormat(0, sdmInternat, 0, "GetCapsAndNumlockState (Enter:%x Exit:%x)", n, Globals::get_ShiftState());
}
/*
Update the Keyman shift state based on the key event. This has to be
done from both the GetMessage hook and the TSF methods, because we don't
consistently receive modifier events in some applications (e.g. ALT in Firefox)
via TSF, and we don't receive the notifications via the GetMessage hook when
in UWP apps (#4369).
TODO: test whether we still need the GetMessage hook for reading modifier state.
*/
void ProcessModifierChange(UINT key, BOOL isUp, BOOL isExtended) { // I4793
UINT flag = 0;
switch(key) {
case VK_SHIFT: flag = K_SHIFTFLAG; break;
case VK_MENU: flag = isExtended ? RALTFLAG : LALTFLAG; break;
case VK_CONTROL: flag = isExtended ? RCTRLFLAG : LCTRLFLAG; break;
}
UINT oldShiftState = Globals::get_ShiftState();
if(isUp) {
*Globals::ShiftState() &= ~flag;
}
else {
*Globals::ShiftState() |= flag;
}
SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessModifierChange(%x, %d, %d) -> shift state from %x to %x",
key, isUp, isExtended, oldShiftState, Globals::get_ShiftState());
}
BOOL IsFocusedThread()
{
GUITHREADINFO gti;
gti.cbSize = sizeof(GUITHREADINFO);
if(!GetGUIThreadInfo(0, &gti))
{
DebugLastError("GetGUIThreadInfo");
return FALSE;
}
return gti.hwndFocus == GetFocus();
//return GetCurrentThreadId() == GetWindowThreadProcessId(GetForegroundWindow(), NULL);
}