mirror of
https://github.com/keymanapp/keyman.git
synced 2026-11-03 17:47:50 +00:00
Fixes #3084. This does two things: 1. Cleans up a bunch of places where we used to use `(int)` typecasts for pointer math, which was problematic. We now use `(INT_PTR)` per MSDN https://docs.microsoft.com/en-us/windows/win32/winprog64/rules-for-using-pointers and then cast that down to `(int)` where necessary, e.g. when storing string lengths which are never going to be more than a few hundred characters! Doing this explicitly helps to clarify that we are aware of the typecast and believe it to be safe. 2. Adds in some build infrastructure for future use of Coverity Scan https://scan.coverity.com/ which we plan to use for further code quality updates. I have submitted the project to Coverity and are now waiting for approval so we can check results. Once we have approval, I do plan to add this to the nightly build (we need to keep submissions under 3 builds/day). Note: I have not yet added Keyman Core (Windows) to this project, nor are we currently building Keyman Core (macOS) or Keyman for Linux, but we should consider adding those in future.
703 lines
24 KiB
C++
703 lines
24 KiB
C++
/*
|
|
Name: aiTIP
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 19 Jun 2007
|
|
|
|
Modified Date: 23 Feb 2016
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 19 Jun 2007 - mcdurdin - I890 - Fix deadkeys in TSF
|
|
13 Jul 2007 - mcdurdin - I934 - Prep for x64
|
|
27 Jan 2009 - mcdurdin - I1797 - Add fallback for AIWin2000 app integration
|
|
11 Mar 2009 - mcdurdin - I1894 - Fix threading bugs introduced in I1888
|
|
07 Sep 2009 - mcdurdin - I2096 - TSF addin should not use fake 0x88 message for langbar icon updates
|
|
11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
|
|
12 Mar 2010 - mcdurdin - I934 - x64 - Complete
|
|
12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
|
|
04 May 2010 - mcdurdin - I2351 - Robustness - verify _td return value
|
|
24 Jun 2010 - mcdurdin - I2436 - Add space to context for AIWin2000Unicode when not matched
|
|
07 Nov 2012 - mcdurdin - I3549 - V9.0 - x64 version of kmtip addin
|
|
17 Nov 2012 - mcdurdin - I3572 - V9.0 - Keyman Engine should avoid processing VK_PACKET
|
|
17 Nov 2012 - mcdurdin - I3573 - V9.0 - Don't recreate the TSF AppIntegration with each keystroke
|
|
17 Nov 2012 - mcdurdin - I3574 - V9.0 - TIP AppIntegration should inherit from AIWin2000Unicode AppInt in order to support legacy fallback for transitory TSF contexts
|
|
17 Nov 2012 - mcdurdin - I3575 - V9.0 - context must be cached for legacy mode in TSF as rules are processed twice
|
|
20 Nov 2012 - mcdurdin - I3581 - V9.0 - KMTip needs to pass activated profile guid through to Keyman32 to switch keyboards
|
|
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
|
|
28 Nov 2012 - mcdurdin - I3588 - V9.0 - Use preserved keys in TSF to handle key combinations in Keyman
|
|
28 Nov 2012 - mcdurdin - I3589 - V9.0 - Handle key state for KeyUp events in TSF
|
|
11 Aug 2013 - mcdurdin - I3775 - V9.0 - Fail I3762 - AltGr combinations are not matched on French base layout
|
|
11 Nov 2013 - mcdurdin - I3961 - V9.0 - Clean up communication between keyman32 and keyman
|
|
24 Apr 2014 - mcdurdin - I4196 - V9.0 - wm_kmmoreposting must be refactored for TIP work as it is not sequential
|
|
02 May 2014 - mcdurdin - I4223 - V9.0 - Moving with cursor keys does not reset context
|
|
10 Jun 2014 - mcdurdin - I4262 - V9.0 - TSF deadkeys do not function correctly
|
|
16 Jun 2014 - mcdurdin - I4272 - V9.0 - TIP only outputs first 127 characters of a rule result
|
|
16 Jun 2014 - mcdurdin - I4266 - V9.0 - Deadkeys only work in first 61 characters of document with TIP
|
|
23 Jun 2014 - mcdurdin - I4287 - V9.0 - Remove extraneous AppIntegration class type tests
|
|
26 Jun 2014 - mcdurdin - I4290 - Keys that have rules but are not matched due to context do not generate output
|
|
03 Aug 2014 - mcdurdin - I4278 - V9.0 - PQR test in dead key test fails in TIP
|
|
13 Aug 2014 - mcdurdin - I4370 - Deadkeys are still not working in Winword TIP mode
|
|
13 Aug 2014 - mcdurdin - I4370 - Deadkeys are still not working in Winword TIP mode
|
|
31 Dec 2014 - mcdurdin - I4548 - V9.0 - When Alt is down, release of Ctrl, Shift is not detectable within TIP in some languages
|
|
12 Jan 2015 - mcdurdin - I4562 - V9.0 - Forced output of virtual keys no longer works
|
|
03 Feb 2015 - mcdurdin - I4582 - V9.0 - Most underlying layout code in Keyman32 is now obsolete and needs to be removed
|
|
04 Feb 2015 - mcdurdin - I4585 - V9.0 - Keyman Engine appears to ignore space in context when no rule for it
|
|
29 Mar 2015 - mcdurdin - I4642 - V9.0 - In Logos, generated backspace receives a scan of 0x00 instead of 0xFF
|
|
09 Aug 2015 - mcdurdin - I4793 - Race condition with preserved keys and modifiers
|
|
09 Aug 2015 - mcdurdin - I4844 - Tidy up PostDummyKeyEvent calls
|
|
23 Feb 2016 - mcdurdin - I4978 - Keyboard options do not apply correctly because they are set twice accidentally
|
|
*/
|
|
#include "pch.h" // I3583 // I4287
|
|
|
|
extern const int VKContextReset[256];
|
|
|
|
DWORD TSFShiftToShift(LPARAM shift); // I3588
|
|
|
|
extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateKeyboard(GUID *profile) { // I3581
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return FALSE;
|
|
if(profile != NULL) {
|
|
for(int i = 0; i < _td->nKeyboards; i++) {
|
|
for(int j = 0; j < _td->lpKeyboards[i].nProfiles; j++) {
|
|
if(IsEqualGUID(_td->lpKeyboards[i].Profiles[j].Guid, *profile)) {
|
|
SelectKeyboard(_td->lpKeyboards[i].KeymanID); // I3594
|
|
ReportActiveKeyboard(_td, PC_UPDATE); // I3961
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SendDebugMessage(0, sdmAIDefault, 0, "TIPActivateKeyboard: Could not find profile");
|
|
SelectKeyboard(KEYMANID_NONKEYMAN); // I3594
|
|
ReportActiveKeyboard(_td, PC_UPDATE); // I3961
|
|
return FALSE;
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) BOOL WINAPI TIPActivateEx(BOOL FActivate) { // I3581
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return FALSE;
|
|
if(!FActivate)
|
|
SelectKeyboard(KEYMANID_NONKEYMAN); // I3594
|
|
Globals::PostMasterController(wm_keyman_control, KMC_SETFOCUSINFO, 0); // I3961
|
|
UpdateKeymanUI();
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
Update the Keyman toggle state based on the key event
|
|
*/
|
|
void ProcessToggleChange(UINT key) { // I4793
|
|
UINT flag = 0;
|
|
|
|
switch(key) {
|
|
case VK_CAPITAL: flag = CAPITALFLAG; break;
|
|
case VK_NUMLOCK: flag = NUMLOCKFLAG; break;
|
|
default: return;
|
|
}
|
|
|
|
if (GetKeyState(key) & 1) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "ProcessToggleChange: Setting %s", flag == CAPITALFLAG ? "CAPITALFLAG" : "NUMLOCKFLAG");
|
|
*Globals::ShiftState() |= flag;
|
|
}
|
|
else {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "ProcessToggleChange: Clearing %s", flag == CAPITALFLAG ? "CAPITALFLAG" : "NUMLOCKFLAG");
|
|
*Globals::ShiftState() &= ~flag;
|
|
}
|
|
}
|
|
|
|
extern "C" __declspec(dllexport) BOOL WINAPI TIPProcessKey(WPARAM wParam, LPARAM lParam, // I3589 // I3588
|
|
PKEYMANPROCESSOUTPUTFUNC outfunc, PKEYMANGETCONTEXTFUNC ctfunc, BOOL Updateable, BOOL Preserved) {
|
|
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) {
|
|
return FALSE;
|
|
}
|
|
|
|
WORD keyFlags = HIWORD(lParam);
|
|
BOOL isUp = keyFlags & KF_UP ? TRUE : FALSE;
|
|
BOOL extended = keyFlags & KF_EXTENDED ? TRUE : FALSE;
|
|
BYTE scan = keyFlags & 0xFF;
|
|
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Enter VirtualKey=%s lParam=%x IsUp=%d Extended=%d Updateable=%d Preserved=%d", Debug_VirtualKey((WORD) wParam), lParam, isUp, extended, Updateable, Preserved);
|
|
|
|
if(_td->LastKey == wParam && scan == 0) { // I4642
|
|
scan = _td->LastScanCode;
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Scan code was zero so using cached scan code %x", scan);
|
|
}
|
|
|
|
if(scan == SCAN_FLAG_KEYMAN_KEY_EVENT) { // I4370
|
|
if (wParam == VK_CAPITAL && !isUp) {
|
|
// Must also record toggle state change when Keyman has generated
|
|
// a Caps Lock event
|
|
ProcessToggleChange((UINT)wParam); // I4793
|
|
}
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Virtual Key was generated by Keyman [Scan=0xFF]");
|
|
return FALSE;
|
|
}
|
|
|
|
if(!_td->lpActiveKeyboard) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Fail -- lpActiveKeyboard = NULL");
|
|
return FALSE;
|
|
}
|
|
|
|
if(!_td->app) {
|
|
_td->app = new AITIP;
|
|
if(!_td->app) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Fail -- could not allocate app");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
DWORD LocalShiftState = Globals::get_ShiftState();
|
|
|
|
if(!Preserved) {
|
|
switch(wParam) {
|
|
case VK_CAPITAL:
|
|
if(!isUp) ProcessToggleChange((UINT) wParam); // I4793
|
|
if (!Updateable) {
|
|
// We only want to process the Caps Lock key event once --
|
|
// in the first pass (!Updateable).
|
|
KeyCapsLockPress(isUp); // I4548
|
|
}
|
|
return FALSE;
|
|
case VK_SHIFT:
|
|
if (!Updateable) {
|
|
// We only want to process the Shift key event once --
|
|
// in the first pass (!Updateable).
|
|
KeyShiftPress(isUp); // I4548
|
|
}
|
|
// Fall through
|
|
case VK_MENU:
|
|
case VK_CONTROL:
|
|
return FALSE;
|
|
|
|
case VK_NUMLOCK:
|
|
if(!isUp) ProcessToggleChange((UINT) wParam); // I4793
|
|
return FALSE;
|
|
}
|
|
if(isUp) {
|
|
return FALSE; // return value ignored in this case; we only needed it for testing anyway
|
|
}
|
|
} else {
|
|
// Mask out Ctrl, Shift and Alt and include new modifiers // I4548
|
|
DWORD NewShiftState = TSFShiftToShift(lParam); // I3588
|
|
SendDebugMessageFormat(0, sdmGlobal, 0, "TIPProcessKey: TSFShiftToShift start with %x, include %x", LocalShiftState, NewShiftState);
|
|
*Globals::ShiftState() = (LocalShiftState & K_NOTMODIFIERFLAG) | NewShiftState; // I3588
|
|
}
|
|
|
|
_td->TIPFUpdateable = Updateable;
|
|
_td->TIPFPreserved = Preserved; // I4290
|
|
|
|
_td->state.msg.hwnd = GetFocus();
|
|
_td->state.msg.lParam = 0;
|
|
_td->state.msg.message = wm_keymankeydown;
|
|
_td->state.vkey = (WORD) wParam;
|
|
|
|
_td->state.lpkb = _td->lpActiveKeyboard->Keyboard;
|
|
|
|
_td->state.windowunicode = TRUE;
|
|
_td->state.startgroup = &_td->state.lpkb->dpGroupArray[_td->state.lpkb->StartGroup[BEGIN_UNICODE]];
|
|
|
|
_td->state.NoMatches = TRUE;
|
|
_td->state.LoopTimes = 0;
|
|
_td->state.StopOutput = FALSE;
|
|
|
|
_td->state.charCode = CharFromVK(&_td->state.vkey, Globals::get_ShiftState()); // I4582
|
|
|
|
_td->TIPProcessOutput = outfunc;
|
|
_td->TIPGetContext = ctfunc;
|
|
|
|
AppContextWithStores *savedContext = NULL; // I4370 // I4978
|
|
if(!Updateable) { // I4370
|
|
savedContext = new AppContextWithStores(_td->lpActiveKeyboard->Keyboard->cxStoreArray); // I4978
|
|
_td->app->SaveContext(savedContext);
|
|
}
|
|
|
|
BOOL res = ProcessHook();
|
|
|
|
if(!Updateable) { // I4370
|
|
if(res) { // I4585
|
|
// Reset the context if match found
|
|
_td->app->RestoreContext(savedContext);
|
|
}
|
|
delete savedContext;
|
|
savedContext = NULL;
|
|
}
|
|
|
|
_td->TIPProcessOutput = NULL;
|
|
_td->TIPGetContext = NULL;
|
|
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: Success, res=%d", res);
|
|
|
|
if(!res && Updateable) { // I4562
|
|
//if(wParam <= 0xFF && VKContextReset[wParam]) _td->app->ResetContext(); // I3438 // I4223
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: key not matched: %s", Debug_VirtualKey((WORD) wParam));
|
|
}
|
|
|
|
if(Preserved) {
|
|
// Restore shift state
|
|
// TODO: Is this necessary? See keybd_shift_reset() instead and I5394. Some more
|
|
// formal mapping of the modifier state machine would be helpful
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "TIPProcessKey: restoring shift state from %x to %x", Globals::get_ShiftState(), LocalShiftState);
|
|
*Globals::ShiftState() = LocalShiftState;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* AI constructor and destructor */
|
|
|
|
AITIP::AITIP() {
|
|
::AIWin2000Unicode(); // I3574
|
|
|
|
WM_KEYMANDEBUG_CANDEBUG = RegisterWindowMessage("WM_KEYMANDEBUG_CANDEBUG");
|
|
WM_KEYMANDEBUG_GETUNICODESTATUS = RegisterWindowMessage("WM_KEYMANDEBUG_GETUNICODESTATUS");
|
|
WM_KEYMANDEBUG_GETCONTEXT = RegisterWindowMessage("WM_KEYMANDEBUG_GETCONTEXT");
|
|
WM_KEYMANDEBUG_ACTION = RegisterWindowMessage("WM_KEYMANDEBUG_ACTION");
|
|
WM_KEYMANDEBUG_RULEMATCH = RegisterWindowMessage("WM_KEYMANDEBUG_RULEMATCH");
|
|
}
|
|
|
|
AITIP::~AITIP() {
|
|
}
|
|
|
|
/* Information functions */
|
|
|
|
BOOL AITIP::CanHandleWindow(HWND ahwnd) {
|
|
UNREFERENCED_PARAMETER(ahwnd);
|
|
return TRUE; // I3574
|
|
}
|
|
|
|
BOOL AITIP::HandleWindow(HWND ahwnd) {
|
|
FIsDebugControlWindow = IsDebugControlWindow(ahwnd);
|
|
return AIWin2000Unicode::HandleWindow(ahwnd); // I3574
|
|
}
|
|
|
|
BOOL AITIP::IsWindowHandled(HWND ahwnd) {
|
|
return AIWin2000Unicode::IsWindowHandled(ahwnd); // I3574
|
|
}
|
|
|
|
BOOL AITIP::IsUnicode() {
|
|
return TRUE;
|
|
}
|
|
|
|
//#define DEBUG_MERGECONTEXT
|
|
|
|
#ifdef DEBUG_MERGECONTEXT
|
|
char *debugstr(PWSTR buf) {
|
|
WCHAR *p;
|
|
char *bufout = new char[30*7];
|
|
char *q;
|
|
for(p = buf, q = bufout; *p && (p-buf < 30); p++)
|
|
{
|
|
wsprintf(q, "U+%4.4X ", *p); q = strchr(q, 0);
|
|
}
|
|
//WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL);
|
|
return bufout;
|
|
}
|
|
#endif
|
|
|
|
/* Context functions */
|
|
|
|
void AITIP::MergeContextWithCache(PWSTR buf, AppContext *local_context) { // I4262
|
|
WCHAR tmpbuf[MAXCONTEXT], contextExDeadkeys[MAXCONTEXT];
|
|
local_context->Get(tmpbuf, MAXCONTEXT-1);
|
|
|
|
int n = 0;
|
|
PWSTR p = tmpbuf, q = contextExDeadkeys, r = buf; // I4266
|
|
while(*p) {
|
|
if(*p == UC_SENTINEL) {
|
|
p += 2; // We know the only UC_SENTINEL CODE in the context is CODE_DEADKEY, which has only 1 parameter: UC_SENTINEL CODE_DEADKEY <deadkey_id>
|
|
n++;
|
|
} else {
|
|
*q++ = *p;
|
|
}
|
|
p++;
|
|
}
|
|
*q = 0;
|
|
|
|
if(n > 0 && wcslen(buf) > wcslen(contextExDeadkeys)) { // I4266
|
|
r += wcslen(buf) - wcslen(contextExDeadkeys);
|
|
}
|
|
|
|
// We have to cut off the context comparison from the left by #deadkeys matched to ensure we are comparing like with like,
|
|
// at least when tmpbuf len=MAXCONTEXT-1 at entry.
|
|
|
|
#ifdef DEBUG_MERGECONTEXT
|
|
char *mc1 = debugstr(buf), *mc2 = debugstr(contextExDeadkeys), *mc3 = debugstr(tmpbuf);
|
|
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache TIP:'%s' Context:'%s' DKContext:'%s'",
|
|
mc1, mc2, mc3);
|
|
|
|
delete mc1;
|
|
delete mc2;
|
|
delete mc3;
|
|
#endif
|
|
|
|
if(wcscmp(r, contextExDeadkeys) != 0) {
|
|
// context has changed, reset context
|
|
#ifdef DEBUG_MERGECONTEXT
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache --> load context from app (losing deadkeys)");
|
|
#endif
|
|
local_context->Set(buf);
|
|
} else {
|
|
#ifdef DEBUG_MERGECONTEXT
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::MergeContextWithCache --> loading cached context");
|
|
#endif
|
|
wcscpy_s(buf, MAXCONTEXT, tmpbuf);
|
|
}
|
|
}
|
|
|
|
void AITIP::ReadContext() {
|
|
if(DebugControlled()) {
|
|
Debug_FillContextBuffer();
|
|
return;
|
|
}
|
|
|
|
WCHAR buf[MAXCONTEXT];
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return;
|
|
|
|
if(_td->TIPGetContext && (*_td->TIPGetContext)(MAXCONTEXT-1, buf) == S_OK) { // I3575 // I4262
|
|
if(ShouldDebug(sdmKeyboard)) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: full context [Updateable=%d] %s", _td->TIPFUpdateable, Debug_UnicodeString(buf));
|
|
}
|
|
useLegacy = FALSE; // I3575
|
|
|
|
// If the text content of the context is identical, inject the deadkeys
|
|
// Otherwise, reset the cachedContext to match buf, no deadkeys
|
|
|
|
MergeContextWithCache(buf, context);
|
|
|
|
if(ShouldDebug(sdmKeyboard)) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: after merge [Updateable=%d] %s", _td->TIPFUpdateable, Debug_UnicodeString(buf));
|
|
}
|
|
|
|
context->Set(buf);
|
|
} else {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: transitory context, so use buffered context [Updateable=%d]", _td->TIPFUpdateable);
|
|
useLegacy = TRUE; // I3575
|
|
}
|
|
}
|
|
|
|
AppContextWithStores::AppContextWithStores(int nKeyboardOptions) : AppContext() { // I4978
|
|
this->nKeyboardOptions = nKeyboardOptions;
|
|
KeyboardOptions = new INTKEYBOARDOPTIONS[nKeyboardOptions];
|
|
memset(KeyboardOptions, 0, sizeof(INTKEYBOARDOPTIONS) * nKeyboardOptions);
|
|
}
|
|
|
|
AppContextWithStores::~AppContextWithStores() { // I4978
|
|
for(DWORD i = 0; i < nKeyboardOptions; i++) {
|
|
if(KeyboardOptions[i].Value) delete KeyboardOptions[i].Value;
|
|
}
|
|
delete KeyboardOptions;
|
|
}
|
|
|
|
void AITIP::SaveContext(AppContextWithStores *savedContext) { // I4370 // I4978
|
|
savedContext->CopyFrom(context);
|
|
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td || !_td->lpActiveKeyboard || !_td->lpActiveKeyboard->Keyboard) return;
|
|
|
|
assert(savedContext->nKeyboardOptions == _td->lpActiveKeyboard->Keyboard->cxStoreArray);
|
|
|
|
for(DWORD i = 0; i < savedContext->nKeyboardOptions; i++) { // I4978
|
|
if(_td->lpActiveKeyboard->KeyboardOptions[i].Value != NULL) {
|
|
savedContext->KeyboardOptions[i].Value = new WCHAR[wcslen(_td->lpActiveKeyboard->KeyboardOptions[i].Value)+1];
|
|
wcscpy_s(savedContext->KeyboardOptions[i].Value, wcslen(_td->lpActiveKeyboard->KeyboardOptions[i].Value)+1, _td->lpActiveKeyboard->KeyboardOptions[i].Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AITIP::RestoreContext(AppContextWithStores *savedContext) { // I4370 // I4978
|
|
context->CopyFrom(savedContext);
|
|
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td || !_td->lpActiveKeyboard || !_td->lpActiveKeyboard->Keyboard) return;
|
|
LPINTKEYBOARDINFO kp = _td->lpActiveKeyboard;
|
|
|
|
assert(savedContext->nKeyboardOptions == kp->Keyboard->cxStoreArray);
|
|
|
|
for(DWORD i = 0; i < savedContext->nKeyboardOptions; i++) { // I4978
|
|
if(kp->KeyboardOptions[i].Value == NULL && savedContext->KeyboardOptions[i].Value != NULL) {
|
|
// Restore the previously saved value as it was reset
|
|
kp->KeyboardOptions[i].OriginalStore = kp->Keyboard->dpStoreArray[i].dpString;
|
|
kp->Keyboard->dpStoreArray[i].dpString = kp->KeyboardOptions[i].Value = savedContext->KeyboardOptions[i].Value;
|
|
savedContext->KeyboardOptions[i].Value = NULL;
|
|
} else if(kp->KeyboardOptions[i].Value != NULL && savedContext->KeyboardOptions[i].Value == NULL) {
|
|
// Clear the newly saved value back to the default
|
|
delete kp->KeyboardOptions[i].Value;
|
|
kp->KeyboardOptions[i].Value = NULL;
|
|
kp->Keyboard->dpStoreArray[i].dpString = kp->KeyboardOptions[i].OriginalStore;
|
|
} else if(kp->KeyboardOptions[i].Value != NULL && savedContext->KeyboardOptions[i].Value != NULL &&
|
|
wcscmp(kp->KeyboardOptions[i].Value, savedContext->KeyboardOptions[i].Value) != 0) {
|
|
// Restore the previously saved value as it was changed
|
|
delete kp->KeyboardOptions[i].Value;
|
|
kp->Keyboard->dpStoreArray[i].dpString = kp->KeyboardOptions[i].Value = savedContext->KeyboardOptions[i].Value;
|
|
savedContext->KeyboardOptions[i].Value = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Output actions */
|
|
|
|
BOOL AITIP::QueueAction(int ItemType, DWORD dwData) {
|
|
if(DebugControlled()) {
|
|
switch(ItemType) {
|
|
case QIT_VKEYDOWN:
|
|
if((dwData & QVK_KEYMASK) <= VK__MAX && VKContextReset[(BYTE) dwData]) context->Reset(); // I3438 // I4370
|
|
break;
|
|
|
|
case QIT_DEADKEY:
|
|
context->Add(UC_SENTINEL); // I4370
|
|
context->Add(CODE_DEADKEY); // I4370
|
|
context->Add((WORD) dwData); // I4370
|
|
break;
|
|
|
|
case QIT_CHAR:
|
|
context->Add((WORD) dwData); // I4370
|
|
break;
|
|
|
|
case QIT_BACK:
|
|
if(dwData == BK_BACKSPACE)
|
|
while(context->CharIsDeadkey()) context->Delete(); // I4370
|
|
context->Delete(); // I4370
|
|
if(dwData == BK_BACKSPACE)
|
|
while(context->CharIsDeadkey()) context->Delete(); // I4370
|
|
break;
|
|
}
|
|
|
|
SendMessage(GetDebugControlWindow(), WM_KEYMANDEBUG_ACTION, ItemType, dwData);
|
|
return TRUE;
|
|
}
|
|
|
|
return AIWin2000Unicode::QueueAction(ItemType, dwData); // I3575
|
|
}
|
|
|
|
BOOL AITIP::SendActions() { // I4196
|
|
if(DebugControlled()) {
|
|
return TRUE;
|
|
}
|
|
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return FALSE;
|
|
|
|
if(!_td->TIPFUpdateable) { // I3575
|
|
QueueSize = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
if(useLegacy) { // I3575
|
|
return AIWin2000Unicode::SendActions(); // I3575 // I4196
|
|
}
|
|
|
|
if(*Globals::hwndIM()) {
|
|
PostMessage(*Globals::hwndIM(), wm_keymanim_contextchanged, 0, 0);
|
|
}
|
|
|
|
return PostKeys();
|
|
}
|
|
|
|
BOOL AITIP::PostKeys() {
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) {
|
|
return FALSE;
|
|
}
|
|
|
|
if(QueueSize == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
int bk=0;
|
|
WCHAR *OutBuf = new WCHAR[QueueSize+1], *p = OutBuf; // I4272
|
|
|
|
*OutBuf = 0;
|
|
|
|
for(int n = 0; n < QueueSize; n++) {
|
|
switch(Queue[n].ItemType) {
|
|
case QIT_CHAR:
|
|
*p++ = (WCHAR) Queue[n].dwData;
|
|
*p = 0;
|
|
break;
|
|
case QIT_BELL:
|
|
MessageBeep(MB_ICONASTERISK);
|
|
break;
|
|
case QIT_BACK:
|
|
if(Queue[n].dwData == BK_DEADKEY) break;
|
|
if(p > OutBuf) {
|
|
*(--p) = 0;
|
|
} else {
|
|
bk++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(_td->TIPProcessOutput && (bk > 0 || OutBuf[0])) {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::PostKeys: output bk=%d outcount=%d", bk, (INT_PTR)(p-OutBuf));
|
|
(*_td->TIPProcessOutput)(bk, OutBuf, (int)(INT_PTR)(p - OutBuf));
|
|
} else {
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::PostKeys: no output");
|
|
}
|
|
|
|
delete OutBuf; // I4272
|
|
|
|
QueueSize = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/* Utility functions */
|
|
|
|
DWORD TSFShiftToShift(LPARAM shift) // I3588
|
|
{
|
|
DWORD res = 0;
|
|
if(shift & TF_MOD_ALT) res |= LALTFLAG;
|
|
if(shift & TF_MOD_CONTROL) res |= LCTRLFLAG;
|
|
if(shift & TF_MOD_SHIFT) res |= K_SHIFTFLAG;
|
|
if(shift & TF_MOD_LALT) res |= LALTFLAG;
|
|
if(shift & TF_MOD_RALT) res |= RALTFLAG;
|
|
if((shift & (TF_MOD_RALT|TF_MOD_LCONTROL)) == (TF_MOD_RALT|TF_MOD_LCONTROL)) res |= RALTFLAG; // I3775
|
|
else if(shift & TF_MOD_LCONTROL) res |= LCTRLFLAG;
|
|
if(shift & TF_MOD_RCONTROL) res |= RCTRLFLAG;
|
|
return res;
|
|
}
|
|
|
|
/* Debug Integration */
|
|
|
|
BOOL AITIP::IsDebugControlWindow(HWND hwnd)
|
|
{
|
|
static int WM_KEYMANDEBUG_CANDEBUG = RegisterWindowMessage("WM_KEYMANDEBUG_CANDEBUG");
|
|
DWORD_PTR dwResult;
|
|
SendMessageTimeout(hwnd, WM_KEYMANDEBUG_CANDEBUG, 0, 0, SMTO_BLOCK, 50, &dwResult);
|
|
return dwResult != 0;
|
|
}
|
|
|
|
HWND AITIP::GetDebugControlWindow()
|
|
{
|
|
if(!FIsDebugControlWindow) return NULL;
|
|
return hwnd;
|
|
}
|
|
|
|
BOOL AITIP::DebugControlled()
|
|
{
|
|
return FIsDebugControlWindow;
|
|
}
|
|
|
|
void AITIP::Debug_FillContextBuffer()
|
|
{
|
|
WCHAR buf[MAXCONTEXT];
|
|
if(DebugControlled() &&
|
|
SendMessage(GetDebugControlWindow(), WM_KEYMANDEBUG_GETCONTEXT, MAXCONTEXT, (LPARAM) buf))
|
|
{
|
|
context->Set(buf); // I4370
|
|
SendDebugMessageFormat(0, sdmKeyboard, 0, "AIDebugger::FillContextBuffer(%ls)", buf);
|
|
}
|
|
else
|
|
{
|
|
context->Reset(); // I4370
|
|
SendDebugMessageFormat(0, sdmKeyboard, 0, "AIDebugger::FillContextBuffer()-Reset");
|
|
}
|
|
}
|
|
|
|
#define MAXSTOREOFFSETS 20
|
|
|
|
struct AIDEBUGINFO
|
|
{
|
|
int cbSize;
|
|
int ItemType;
|
|
PWSTR Context, Output;
|
|
LPKEY Rule;
|
|
LPGROUP Group;
|
|
DWORD_PTR Flags;
|
|
WORD StoreOffsets[MAXSTOREOFFSETS*2+1]; // pairs--store, char position, terminated by 0xFFFF
|
|
};
|
|
|
|
void FillStoreOffsets(AIDEBUGINFO *di)
|
|
{
|
|
int i, n;
|
|
PWSTR p;
|
|
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return;
|
|
|
|
for(i = n = 0, p = di->Rule->dpContext; *p; p = incxstr(p), i++)
|
|
{
|
|
if(*p == UC_SENTINEL && (*(p+1) == CODE_ANY || *(p+1) == CODE_NOTANY))
|
|
{
|
|
di->StoreOffsets[n++] = *(p+2) - 1;
|
|
di->StoreOffsets[n++] = _td->IndexStack[i];
|
|
}
|
|
if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX)
|
|
{
|
|
di->StoreOffsets[n++] = *(p+2) - 1;
|
|
di->StoreOffsets[n++] = _td->IndexStack[*(p+3) - 1];
|
|
}
|
|
if(n == MAXSTOREOFFSETS*2) break;
|
|
}
|
|
|
|
if(n < MAXSTOREOFFSETS*2)
|
|
for(p = di->Rule->dpOutput; *p; p = incxstr(p))
|
|
{
|
|
if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX)
|
|
{
|
|
di->StoreOffsets[n++] = *(p+2) - 1;
|
|
di->StoreOffsets[n++] = _td->IndexStack[*(p+3) - 1];
|
|
}
|
|
if(n == MAXSTOREOFFSETS*2) break;
|
|
}
|
|
di->StoreOffsets[n] = 0xFFFF;
|
|
}
|
|
|
|
BOOL AITIP::QueueDebugInformation(int ItemType, LPGROUP Group, LPKEY Rule, PWSTR fcontext, PWSTR foutput, DWORD_PTR dwExtraFlags)
|
|
{
|
|
PKEYMAN64THREADDATA _td = ThreadGlobals();
|
|
if(!_td) return TRUE;
|
|
if(!_td->ForceFileName[0]) return TRUE;
|
|
|
|
SendDebugMessageFormat(0, sdmAIDefault, 0, "AIDebugger::QueueDebugInformation ItemType=%d", ItemType);
|
|
AIDEBUGINFO di;
|
|
di.cbSize = sizeof(AIDEBUGINFO);
|
|
di.ItemType = ItemType; // int
|
|
di.Context = fcontext; // PWSTR
|
|
di.Rule = Rule; // LPKEY
|
|
di.Group = Group; // LPGROUP
|
|
di.Output = foutput; // PWSTR
|
|
di.Flags = dwExtraFlags; // DWORD
|
|
|
|
if(di.Rule) FillStoreOffsets(&di);
|
|
|
|
// data required
|
|
// keystroke
|
|
// context for rule
|
|
// if rule, then output of rule
|
|
// match positions for all stores in rule
|
|
|
|
if(DebugControlled())
|
|
SendMessage(GetDebugControlWindow(), WM_KEYMANDEBUG_RULEMATCH, ItemType, (LPARAM) &di);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
typedef BOOL(WINAPI *PREFRESHPRESERVEDKEYSFUNC)(BOOL Activating);
|
|
|
|
void RefreshPreservedKeys(BOOL Activating) {
|
|
#ifdef _WIN64
|
|
HMODULE hModule = GetModuleHandle("kmtip64");
|
|
#else
|
|
HMODULE hModule = GetModuleHandle("kmtip");
|
|
#endif
|
|
if (hModule != NULL) {
|
|
PREFRESHPRESERVEDKEYSFUNC pRefreshPreservedKeys = (PREFRESHPRESERVEDKEYSFUNC)GetProcAddress(hModule, "RefreshPreservedKeys");
|
|
if (pRefreshPreservedKeys) {
|
|
pRefreshPreservedKeys(Activating);
|
|
}
|
|
}
|
|
}
|