diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index c72d2fe8577..6790d5b46df 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -6,7 +6,7 @@ using namespace km::core; using namespace kmx; -// TODO consolodate with appint.cpp and put in public library. + static KMX_BOOL ContextItemsFromAppContext(KMX_WCHAR *buf, km_core_context_item** outPtr) { assert(buf); diff --git a/windows/src/engine/keyman32/appcontext.cpp b/windows/src/engine/keyman32/appcontext.cpp new file mode 100644 index 00000000000..de68efe0ae7 --- /dev/null +++ b/windows/src/engine/keyman32/appcontext.cpp @@ -0,0 +1,166 @@ +#include "pch.h" +// AppContext Class Methods +AppContext::AppContext() { + Reset(); +} + +WCHAR * +AppContext::BufMax(int n) { + WCHAR *p = wcschr(CurContext, 0); // I3091 + + if (CurContext == p || n == 0) + return p; /* empty context or 0 characters requested, return pointer to end of context */ // I3091 + + WCHAR *q = p; // I3091 + for (; p != NULL && p > CurContext && (INT_PTR)(q - p) < n; p = decxstr(p, CurContext)) + ; // I3091 + + if ((INT_PTR)(q - p) > n) + p = incxstr(p); /* Copes with deadkey or supplementary pair at start of returned buffer making it too long */ // I3091 + + return p; // I3091 +} + +void +AppContext::Delete() { + if (CharIsDeadkey()) { + pos -= 2; + } else if (CharIsSurrogatePair()) { + pos--; + } + // SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Delete"); + + if (pos > 0) + pos--; + CurContext[pos] = 0; + // if(--pos < 0) pos = 0; + // SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Delete"); +} + +void +AppContext::Reset() { + pos = 0; + CurContext[0] = 0; + + // SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Reset"); +} + +void +AppContext::Get(WCHAR *buf, int bufsize) { + // surrogate pairs need to be treated as a single unit, therefore use + // BufMax to find a start index. + // BufMax handles the case where a surrogate pair at the + // start of the buffer is split by bufsize + for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) { + *buf = *p; + if (Uni_IsSurrogate1(*p) && bufsize - 2 > 0) { + buf++; + p++; + *buf = *p; + bufsize--; + } + buf++; + } + + *buf = 0; +} + +void +AppContext::Set(const WCHAR *buf) { + const WCHAR *p; + WCHAR *q; + + // We may be past a buffer longer than our internal + // buffer. So we shift to make sure we capture the end + // of the string, not the start + p = wcschr(buf, 0); + q = (WCHAR *)p; + while (p != NULL && p > buf && (intptr_t)(q - p) < MAXCONTEXT - 1) { + p = decxstr((WCHAR *)p, (WCHAR *)buf); + } + + // If the first character in the buffer is a surrogate pair, + // or a deadkey, our buffer may be too long, so move to the + // next character in the buffer + if ((intptr_t)(q - p) > MAXCONTEXT - 1) { + p = incxstr((WCHAR *)p); + } + + for (q = CurContext; *p; p++, q++) { + *q = *p; + } + + *q = 0; + pos = (int)(intptr_t)(q - CurContext); + CurContext[MAXCONTEXT - 1] = 0; +} + +BOOL +AppContext::CharIsDeadkey() { + if (pos < 3) // code_sentinel, deadkey, #, 0 + return FALSE; + return CurContext[pos - 3] == UC_SENTINEL && CurContext[pos - 2] == CODE_DEADKEY; +} + +BOOL +AppContext::CharIsSurrogatePair() { + if (pos < 2) // low_surrogate, high_surrogate + return FALSE; + + return Uni_IsSurrogate1(CurContext[pos - 2]) && Uni_IsSurrogate2(CurContext[pos - 1]); +} + +BOOL +AppContext::IsEmpty() { + return (BOOL)(pos == 0); +} + +BOOL +ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len) { + assert(contextItems); + assert(outBuf); + + km_core_context_item *km_core_context_it = contextItems; + uint8_t contextLen = 0; + for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) { + ++contextLen; + } + + WCHAR *buf = new WCHAR[(contextLen * 3) + 1]; // *3 if every context item was a deadkey + uint8_t idx = 0; + km_core_context_it = contextItems; + for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) { + switch (km_core_context_it->type) { + case KM_CORE_CT_CHAR: + if (Uni_IsSMP(km_core_context_it->character)) { + buf[idx++] = static_cast Uni_UTF32ToSurrogate1(km_core_context_it->character); + buf[idx++] = static_cast Uni_UTF32ToSurrogate2(km_core_context_it->character); + } else { + buf[idx++] = (km_core_cp)km_core_context_it->character; + } + break; + case KM_CORE_CT_MARKER: + assert(km_core_context_it->marker > 0); + buf[idx++] = UC_SENTINEL; + buf[idx++] = CODE_DEADKEY; + buf[idx++] = static_cast(km_core_context_it->marker); + break; + } + } + + buf[idx] = 0; // Null terminate character array + + if (wcslen(buf) > len) { + // Truncate to length 'len' using AppContext so that the context closest to the caret is preserved + // and the truncation will not split deadkeys or surrogate pairs + // Note by using the app context class we will truncate the context to the MAXCONTEXT length if 'len' + // is greater than MAXCONTEXT + AppContext context; + context.Set(buf); + context.Get(outBuf, len); + } else { + wcscpy_s(outBuf, wcslen(buf) + 1, buf); + } + delete[] buf; + return TRUE; +} diff --git a/windows/src/engine/keyman32/appcontext.h b/windows/src/engine/keyman32/appcontext.h new file mode 100644 index 00000000000..d0859262dfd --- /dev/null +++ b/windows/src/engine/keyman32/appcontext.h @@ -0,0 +1,107 @@ +#ifndef _APPCONTEXT_H +#define _APPCONTEXT_H +/* + Name: appcontext + Copyright: Copyright (C) SIL International. + Documentation: + Description: + Create Date: 23 Nov 2023 + + Modified Date: 23 Nov 2023 + Authors: rcruickshank + Related Files: + Dependencies: + + Bugs: + Todo: + Notes: AppContext is retained to support calldll with the external interface for the 3rd party IMX keyboards + that worked with KMX formatted Context Strings. It is also used once for debug logging the ProcessHook. + History: +*/ + +class AppContext { +private: + WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated. + int pos; + +public: + AppContext(); + + /** + * Removes a single code point from the end of the CurContext closest to the caret; + * i.e. it will be both code units if a surrogate pair. If it is a deadkey it will + * remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value. + */ + void Delete(); + + /** + * Clears the CurContext and resets the position - pos - index + */ + void Reset(); + + /** + * Copies the characters in CurContext to supplied buffer. + * If bufsize is reached before the entire context was copied, the buf + * will be truncated to number of valid characters possible with null character + * termination. e.g. it will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair + * @param buf The data buffer to copy current context + * @param bufsize The number of code units ie size of the WCHAR buffer - not the code points + */ + void Get(WCHAR *buf, int bufsize); + + /** + * Sets the CurContext to the supplied buf character array and updates the pos index. + * + * @param buf + */ + void Set(const WCHAR *buf); + + /** + * Returns a pointer to the character in the current context buffer which + * will have at most n valid xstring units remaining until the null terminating + * character. It will be one code unit less than bufsize if that would + * have meant splitting a surrogate pair or deadkey. + * + * @param n The maximum number of valid xstring units (not code points or code units) + * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units + */ + WCHAR *BufMax(int n); + + /** + * Returns TRUE if the last xstring unit in the context is a deadkey + * + * @return BOOL + */ + BOOL CharIsDeadkey(); + + /** + * Returns TRUE if the last xstring unit in the CurContext is a surrogate pair. + * @return BOOL + */ + BOOL CharIsSurrogatePair(); + + /** + * Returns TRUE if the context is empty + * @return BOOL + */ + BOOL AppContext::IsEmpty(); +}; + +/** + * Convert km_core_context_item array into an kmx char buffer. + * Caller is responsible for freeing the memory. + * The length is restricted to a maximum of MAXCONTEXT length. If the number + * of input km_core_context_items exceeds this length the characters furthest + * from the caret will be truncated. + * + * @param contextItems the input core context array. (km_core_context_item) + * @param [out] outBuf the kmx character array output. caller to free memory. + * + * @return BOOL True if array created successfully + */ +BOOL ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len); + + + +#endif diff --git a/windows/src/engine/keyman32/appint/aiTIP.cpp b/windows/src/engine/keyman32/appint/aiTIP.cpp index a7faa7170fe..93ffcb445d2 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.cpp +++ b/windows/src/engine/keyman32/appint/aiTIP.cpp @@ -269,89 +269,27 @@ char *debugstr(PWSTR buf) { /* 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 - 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); +BOOL AITIP::ReadContext(PWSTR buf) { + if (buf == nullptr) { + return FALSE; } -} -void AITIP::ReadContext() { - WCHAR buf[MAXCONTEXT]; PKEYMAN64THREADDATA _td = ThreadGlobals(); - if(!_td) return; + if(!_td) return FALSE; 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); + return TRUE; } else { SendDebugMessageFormat(0, sdmAIDefault, 0, "AITIP::ReadContext: transitory context, so use buffered context [Updateable=%d]", _td->TIPFUpdateable); useLegacy = TRUE; // I3575 + return FALSE; } } -void AITIP::CopyContext(AppContext *savedContext) { - savedContext->CopyFrom(context); -} - -void AITIP::RestoreContextOnly(AppContext *savedContext) { - context->CopyFrom(savedContext); -} /* Output actions */ diff --git a/windows/src/engine/keyman32/appint/aiTIP.h b/windows/src/engine/keyman32/appint/aiTIP.h index 93c9acd4ab5..bfbca87d89e 100644 --- a/windows/src/engine/keyman32/appint/aiTIP.h +++ b/windows/src/engine/keyman32/appint/aiTIP.h @@ -37,9 +37,6 @@ class AITIP : public AIWin2000Unicode { -private: - void MergeContextWithCache(PWSTR buf, AppContext *context); // I4262 - private: BOOL useLegacy; @@ -50,20 +47,6 @@ class AITIP : public AIWin2000Unicode AITIP(); ~AITIP(); - /** - * Copy the member context - * - * @param[out] savedContext the copied context - */ - void CopyContext(AppContext *savedContext); - - /** - * Restore the passed context to the member context - * - * @param savedContext the context to restore - */ - void RestoreContextOnly(AppContext *savedContext); - /* Information functions */ virtual BOOL CanHandleWindow(HWND ahwnd); @@ -71,7 +54,12 @@ class AITIP : public AIWin2000Unicode /* Context functions */ - virtual void ReadContext(); + /** + * Reads the current application context upto MAXCONTEXT length into the supplied buffer. + * @param buf The data buffer to copy current application context into, must + * be MAXCONTEXT WCHARs or larger. + */ + virtual BOOL ReadContext(PWSTR buf); /* Queue and sending functions */ diff --git a/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp b/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp index a6b540fd47a..e8562bea35d 100644 --- a/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp +++ b/windows/src/engine/keyman32/appint/aiWin2000Unicode.cpp @@ -43,16 +43,9 @@ #include "pch.h" // I4128 // I4287 #include "serialkeyeventclient.h" - -AIWin2000Unicode::AIWin2000Unicode() -{ - context = new AppContext; -} - -AIWin2000Unicode::~AIWin2000Unicode() -{ - delete context; +AIWin2000Unicode::AIWin2000Unicode() { } +AIWin2000Unicode::~AIWin2000Unicode(){} /* Information functions */ @@ -68,7 +61,7 @@ BOOL AIWin2000Unicode::HandleWindow(HWND ahwnd) if(hwnd != ahwnd) { hwnd = ahwnd; - context->Reset(); + ResetContext(); } return TRUE; } @@ -87,33 +80,20 @@ BOOL AIWin2000Unicode::IsUnicode() /* Context functions */ -void AIWin2000Unicode::ReadContext() -{ -} - -void AIWin2000Unicode::AddContext(WCHAR ch) //I2436 -{ - context->Add(ch); +BOOL AIWin2000Unicode::ReadContext(PWSTR buf) { + UNREFERENCED_PARAMETER(buf); + // We cannot read any context from legacy apps, so we return a + // failure here -- telling Core to maintain its own cached + // context. + return FALSE; } void AIWin2000Unicode::ResetContext() { - context->Reset(); -} - -WCHAR *AIWin2000Unicode::ContextBuf(int n) -{ - return context->Buf(n); -} - -WCHAR *AIWin2000Unicode::ContextBufMax(int n) -{ - return context->BufMax(n); -} - -void AIWin2000Unicode::SetContext(const WCHAR* buf) -{ - return context->Set(buf); + PKEYMAN64THREADDATA _td = ThreadGlobals(); + if (_td && _td->lpActiveKeyboard && _td->lpActiveKeyboard->lpCoreKeyboardState) { + km_core_state_context_clear(_td->lpActiveKeyboard->lpCoreKeyboardState); + } } BYTE SavedKbdState[256]; @@ -126,40 +106,6 @@ BOOL AIWin2000Unicode::SendActions() // I4196 return PostKeys(); } -BOOL AIWin2000Unicode::QueueAction(int ItemType, DWORD dwData) -{ - int result = AppIntegration::QueueAction(ItemType, dwData); - - //SendDebugMessageFormat(hwnd, sdmAIDefault, 0, "App::QueueAction ItemType=%d dwData=%x", ItemType, dwData); - - switch(ItemType) - { - case QIT_VKEYDOWN: - break; - - case QIT_DEADKEY: - context->Add(UC_SENTINEL); - context->Add(CODE_DEADKEY); - context->Add((WORD) dwData); - break; - - case QIT_CHAR: - context->Add((WORD) dwData); - break; - - case QIT_BACK: - if(dwData & BK_BACKSPACE) - while(context->CharIsDeadkey()) context->Delete(); - //if(dwData == CODE_DEADKEY) break; - context->Delete(); - if(dwData & BK_BACKSPACE) - while(context->CharIsDeadkey()) context->Delete(); - break; - } - - return result; -} - // I1512 - SendInput with VK_PACKET for greater robustness BOOL AIWin2000Unicode::PostKeys() diff --git a/windows/src/engine/keyman32/appint/aiWin2000Unicode.h b/windows/src/engine/keyman32/appint/aiWin2000Unicode.h index 1ee49ed001f..523832742f6 100644 --- a/windows/src/engine/keyman32/appint/aiWin2000Unicode.h +++ b/windows/src/engine/keyman32/appint/aiWin2000Unicode.h @@ -32,15 +32,9 @@ class AIWin2000Unicode:public AppIntegration BOOL PostKeys(); - -protected: - AppContext *context; - public: - AIWin2000Unicode(); - ~AIWin2000Unicode(); - - virtual BOOL QueueAction(int ItemType, DWORD dwData); + AIWin2000Unicode(); + ~AIWin2000Unicode(); /* Information functions */ @@ -51,13 +45,9 @@ class AIWin2000Unicode:public AppIntegration /* Context functions */ - virtual void ReadContext(); + virtual BOOL ReadContext(PWSTR buf); virtual void ResetContext(); - virtual void AddContext(WCHAR ch); //I2436 - virtual WCHAR *ContextBuf(int n); - virtual WCHAR *ContextBufMax(int n); - virtual void SetContext(const WCHAR* buf); - + /* Queue and sending functions */ virtual BOOL SendActions(); // I4196 diff --git a/windows/src/engine/keyman32/appint/appint.cpp b/windows/src/engine/keyman32/appint/appint.cpp index 8a2b92e5aa3..13be8efda4f 100644 --- a/windows/src/engine/keyman32/appint/appint.cpp +++ b/windows/src/engine/keyman32/appint/appint.cpp @@ -32,161 +32,6 @@ const LPSTR ItemTypes[8] = { "QIT_VKEYDOWN", "QIT_VKEYUP", "QIT_VSHIFTDOWN", "QIT_VSHIFTUP", "QIT_CHAR", "QIT_DEADKEY", "QIT_BELL", "QIT_BACK" }; -/* AppContext */ - -AppContext::AppContext() -{ - Reset(); -} - -void AppContext::Add(WCHAR ch) -{ - if(pos == MAXCONTEXT - 1) { -// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: MAXCONTEXT[%d]: %ws", pos, CurContext); - auto p = incxstr(CurContext); - auto n = p - CurContext; - memmove(CurContext, p, (MAXCONTEXT - n) * 2); - pos -= (int)n; - } - - CurContext[pos++] = ch; - CurContext[pos] = 0; - - SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Add(%x) [%d]: %s", ch, pos, Debug_UnicodeString(CurContext)); -} - -WCHAR *AppContext::Buf(int n) -{ - WCHAR *p; - - //SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Buf(%d)", n); - //if(n == 0) return wcschr(CurContext, 0); - //if(*CurContext == 0) return NULL; - - for(p = wcschr(CurContext, 0); p != NULL && n > 0 && p > CurContext; p = decxstr(p, CurContext), n--); - //for(p = wcschr(CurContext, 0); n > 0 && p > CurContext; p--, n--); - - if(n > 0) return NULL; - return p; -} - -WCHAR *AppContext::BufMax(int n) -{ - WCHAR *p = wcschr(CurContext, 0); // I3091 - - if(CurContext == p || n == 0) return p; /* empty context or 0 characters requested, return pointer to end of context */ // I3091 - - WCHAR *q = p; // I3091 - for(; p != NULL && p > CurContext && (INT_PTR)(q-p) < n; p = decxstr(p, CurContext)); // I3091 - - if((INT_PTR)(q-p) > n) p = incxstr(p); /* Copes with deadkey or supplementary pair at start of returned buffer making it too long */ // I3091 - - return p; // I3091 -} - -void AppContext::Delete() -{ - if (CharIsDeadkey()) { - pos -= 2; - } else if (CharIsSurrogatePair()) { - pos--; - } - //SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::Delete"); - - if(pos > 0) pos--; - CurContext[pos] = 0; - //if(--pos < 0) pos = 0; - //SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Delete"); -} - -void AppContext::Reset() -{ - pos = 0; - CurContext[0] = 0; - -// SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext: Reset"); -} - -void AppContext::Get(WCHAR *buf, int bufsize) -{ - // surrogate pairs need to be treated as a single unit, therefore use - // BufMax to find a start index. - // BufMax handles the case where a surrogate pair at the - // start of the buffer is split by bufsize - for (WCHAR *p = this->BufMax(bufsize); *p && bufsize > 0; p++, bufsize--) - { - *buf = *p; - if(Uni_IsSurrogate1(*p) && bufsize - 2 > 0) { - buf++; p++; - *buf = *p; - bufsize--; - } - buf++; - } - - *buf = 0; -} - -void AppContext::CopyFrom(AppContext *source) // I3575 -{ - SendDebugMessageFormat(0, sdmAIDefault, 0, "AppContext::CopyFrom source=%s; before copy, dest=%s", Debug_UnicodeString(source->CurContext, 0), Debug_UnicodeString(CurContext, 0)); - wcscpy_s(CurContext, _countof(CurContext), source->CurContext); - pos = source->pos; -} - - -void AppContext::Set(const WCHAR *buf) -{ - const WCHAR *p; - WCHAR *q; - - // We may be past a buffer longer than our internal - // buffer. So we shift to make sure we capture the end - // of the string, not the start - p = wcschr(buf, 0); - q = (WCHAR *)p; - while (p != NULL && p > buf && (intptr_t)(q - p) < MAXCONTEXT - 1) { - p = decxstr((WCHAR *)p, (WCHAR *)buf); - } - - // If the first character in the buffer is a surrogate pair, - // or a deadkey, our buffer may be too long, so move to the - // next character in the buffer - if ((intptr_t)(q - p) > MAXCONTEXT - 1) { - p = incxstr((WCHAR *)p); - } - - for (q = CurContext; *p; p++, q++) { - *q = *p; - } - - *q = 0; - pos = (int)(intptr_t)(q - CurContext); - CurContext[MAXCONTEXT - 1] = 0; - -} - -BOOL AppContext::CharIsDeadkey() -{ - if(pos < 3) // code_sentinel, deadkey, #, 0 - return FALSE; - return CurContext[pos-3] == UC_SENTINEL && - CurContext[pos-2] == CODE_DEADKEY; -} - -BOOL AppContext::CharIsSurrogatePair() -{ - if (pos < 2) // low_surrogate, high_surrogate - return FALSE; - - return Uni_IsSurrogate1(CurContext[pos - 2]) && - Uni_IsSurrogate2(CurContext[pos - 1]); -} - -BOOL AppContext::IsEmpty() { - return (BOOL)(pos == 0); -} - /* AppActionQueue */ AppActionQueue::AppActionQueue() @@ -226,84 +71,3 @@ AppIntegration::AppIntegration() hwnd = NULL; FShiftFlags = 0; } - -BOOL ContextItemsFromAppContext(WCHAR const* buf, km_core_context_item** outPtr) -{ - assert(buf); - assert(outPtr); - km_core_context_item* context_items = new km_core_context_item[wcslen(buf) + 1]; - WCHAR const *p = buf; - uint8_t contextIndex = 0; - while (*p) { - if (*p == UC_SENTINEL) { - assert(*(p + 1) == CODE_DEADKEY); - // we know the only uc_sentinel code in the context is code_deadkey, which has only 1 parameter: uc_sentinel code_deadkey - // setup dead key context item - p += 2; - context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_MARKER, {0,}, {*p} }; - } else if (Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p + 1))) { - // handle surrogate - context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_CHAR, {0,}, {(char32_t)Uni_SurrogateToUTF32(*p, *(p + 1))} }; - p++; - } else { - context_items[contextIndex++] = km_core_context_item{ KM_CORE_CT_CHAR, {0,}, {*p} }; - } - p++; - } - // terminate the context_items array. - context_items[contextIndex] = km_core_context_item KM_CORE_CONTEXT_ITEM_END; - - *outPtr = context_items; - return true; -} - - -BOOL -ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len) { - assert(contextItems); - assert(outBuf); - - km_core_context_item *km_core_context_it = contextItems; - uint8_t contextLen = 0; - for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) { - ++contextLen; - } - - WCHAR *buf = new WCHAR[(contextLen*3)+ 1 ]; // *3 if every context item was a deadkey - uint8_t idx = 0; - km_core_context_it = contextItems; - for (; km_core_context_it->type != KM_CORE_CT_END; ++km_core_context_it) { - switch (km_core_context_it->type) { - case KM_CORE_CT_CHAR: - if (Uni_IsSMP(km_core_context_it->character)) { - buf[idx++] = static_cast Uni_UTF32ToSurrogate1(km_core_context_it->character); - buf[idx++] = static_cast Uni_UTF32ToSurrogate2(km_core_context_it->character); - } else { - buf[idx++] = (km_core_cp)km_core_context_it->character; - } - break; - case KM_CORE_CT_MARKER: - assert(km_core_context_it->marker > 0); - buf[idx++] = UC_SENTINEL; - buf[idx++] = CODE_DEADKEY; - buf[idx++] = static_cast(km_core_context_it->marker); - break; - } - } - - buf[idx] = 0; // Null terminate character array - - if (wcslen(buf) > len) { - // Truncate to length 'len' using AppContext so that the context closest to the caret is preserved - // and the truncation will not split deadkeys or surrogate pairs - // Note by using the app context class we will truncate the context to the MAXCONTEXT length if 'len' - // is greater than MAXCONTEXT - AppContext context; - context.Set(buf); - context.Get(outBuf, len); - } else { - wcscpy_s(outBuf, wcslen(buf) + 1, buf); - } - delete[] buf; - return TRUE; -} diff --git a/windows/src/engine/keyman32/appint/appint.h b/windows/src/engine/keyman32/appint/appint.h index 8048c4a8a94..ae31a1b54f1 100644 --- a/windows/src/engine/keyman32/appint/appint.h +++ b/windows/src/engine/keyman32/appint/appint.h @@ -65,103 +65,6 @@ class AppActionQueue int GetQueueSize() { return QueueSize; } }; -class AppContext -{ -private: - WCHAR CurContext[MAXCONTEXT]; //!< CurContext[0] is furthest from the caret and buffer is null terminated. - int pos; - -public: - AppContext(); - /** - * Copy "source" AppContext to this AppContext - * - * @param source AppContext to copy - */ - void CopyFrom(AppContext *source); - - /** - * Add a single code unit to the Current Context. Not necessarily a complete code point - * - * @param Code unit to add - */ - void Add(WCHAR ch); - - /** - * Removes a single code point from the end of the CurContext closest to the caret; - * i.e. it will be both code units if a surrogate pair. If it is a deadkey it will - * remove three code points: UC_SENTINEL, CODE_DEADKEY and deadkey value. - */ - void Delete(); - - /** - * Clears the CurContext and resets the position - pos - index - */ - void Reset(); - - /** - * Copies the characters in CurContext to supplied buffer. - * If bufsize is reached before the entire context was copied, the buf - * will be truncated to number of valid characters possible with null character - * termination. e.g. it will be one code unit less than bufsize if that would - * have meant splitting a surrogate pair - * @param buf The data buffer to copy current context - * @param bufsize The number of code units ie size of the WCHAR buffer - not the code points - */ - void Get(WCHAR *buf, int bufsize); - - /** - * Sets the CurContext to the supplied buf character array and updates the pos index. - * - * @param buf - */ - void Set(const WCHAR *buf); - - /** - * Returns a pointer to the character in the current context buffer which - * will have at most n valid xstring units remaining until the null terminating - * character. It will be one code unit less than bufsize if that would - * have meant splitting a surrogate pair or deadkey. - * - * @param n The maximum number of valid xstring units (not code points or code units) - * @return WCHAR* Pointer to the start postion for a buffer of maximum n xstring units - */ - WCHAR *BufMax(int n); - - /** - * Returns a pointer to the character in the current context buffer which - * will have n valid xstring units remaining until the the null terminating character. - * OR - * Returns NULL if there are less than n valid xstring units in the current context. - * Background this was historically for performance during rule evaluation, if there - * are not enough characters to compare, don't event attempt the comparison. - * - * @param n The number of valid xstring units (not code points or code units) - * @return KMX_WCHAR* Pointer to the start postion for a buffer of maximum n characters - */ - WCHAR *Buf(int n); - - /** - * Returns TRUE if the last xstring unit in the context is a deadkey - * - * @return BOOL - */ - BOOL CharIsDeadkey(); - - /** - * Returns TRUE if the last xstring unit in the CurContext is a surrogate pair. - * @return BOOL - */ - BOOL CharIsSurrogatePair(); - - /** - * Returns TRUE if the context is empty - * @return BOOL - */ - BOOL AppContext::IsEmpty(); - -}; - class AppIntegration:public AppActionQueue { protected: @@ -180,12 +83,12 @@ class AppIntegration:public AppActionQueue virtual BOOL IsUnicode() = 0; /* Context functions */ - - virtual void ReadContext() = 0; + /** + * Reads the current application context upto MAXCONTEXT length into the supplied buffer. + * @param buf The data buffer to copy current application context + */ + virtual BOOL ReadContext(PWSTR buf) = 0; virtual void ResetContext() = 0; - virtual void AddContext(WCHAR ch) = 0; //I2436 - virtual WCHAR *ContextBuf(int n) = 0; - virtual WCHAR *ContextBufMax(int n) = 0; /* Queue and sending functions */ @@ -193,30 +96,6 @@ class AppIntegration:public AppActionQueue virtual BOOL SendActions() = 0; // I4196 }; -/** - * Convert AppContext array into an array of core context items. - * Caller is responsible for freeing the memory. - * - * @param buf appcontext character array - * @param outPtr The ouput array of context items. caller to free memory - * @return BOOL True if array created successfully - */ -BOOL ContextItemsFromAppContext(WCHAR const* buf, km_core_context_item** outPtr); - -/** - * Convert km_core_context_item array into an kmx char buffer. - * Caller is responsible for freeing the memory. - * The length is restricted to a maximum of MAXCONTEXT length. If the number - * of input km_core_context_items exceeds this length the characters furthest - * from the caret will be truncated. - * - * @param contextItems the input core context array. (km_core_context_item) - * @param [out] outBuf the kmx character array output. caller to free memory. - * - * @return BOOL True if array created successfully - */ -BOOL ContextItemToAppContext(km_core_context_item *contextItems, PWSTR outBuf, DWORD len); - extern const LPSTR ItemTypes[]; #endif diff --git a/windows/src/engine/keyman32/keyman32.vcxproj b/windows/src/engine/keyman32/keyman32.vcxproj index 70b8402ad79..021aa55d65d 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj +++ b/windows/src/engine/keyman32/keyman32.vcxproj @@ -176,6 +176,7 @@ + %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) @@ -354,6 +355,7 @@ + @@ -387,4 +389,4 @@ - + \ No newline at end of file diff --git a/windows/src/engine/keyman32/keyman32.vcxproj.filters b/windows/src/engine/keyman32/keyman32.vcxproj.filters index dcf5f5d4c51..0f16750b7c0 100644 --- a/windows/src/engine/keyman32/keyman32.vcxproj.filters +++ b/windows/src/engine/keyman32/keyman32.vcxproj.filters @@ -138,6 +138,9 @@ Source Files + + Source Files + @@ -246,6 +249,9 @@ Header Files + + Header Files + diff --git a/windows/src/engine/keyman32/keymanengine.h b/windows/src/engine/keyman32/keymanengine.h index 3c6e2d3affc..c0db193fdfb 100644 --- a/windows/src/engine/keyman32/keymanengine.h +++ b/windows/src/engine/keyman32/keymanengine.h @@ -126,7 +126,6 @@ BOOL IsSysTrayWindow(HWND hwnd); BOOL InitialiseProcess(HWND hwnd); BOOL UninitialiseProcess(BOOL Lock); -BOOL IsKeyboardUnicode(); BOOL IsFocusedThread(); @@ -231,6 +230,7 @@ void keybd_shift(LPINPUT pInputs, int* n, BOOL isReset, LPBYTE const kbd); #include "keymancontrol.h" #include "keyboardoptions.h" #include "kmprocessactions.h" +#include "appcontext.h" #include "syskbd.h" #include "vkscancodes.h" diff --git a/windows/src/engine/keyman32/kmprocess.cpp b/windows/src/engine/keyman32/kmprocess.cpp index 7e5e8070567..b83079b03d7 100644 --- a/windows/src/engine/keyman32/kmprocess.cpp +++ b/windows/src/engine/keyman32/kmprocess.cpp @@ -70,23 +70,29 @@ BOOL fOutputKeystroke; -/*char *getcontext() -{ - WCHAR buf[128]; - static char bufout[128]; +char *getcontext_debug() { + PKEYMAN64THREADDATA _td = ThreadGlobals(); - if(!_td) return ""; - _td->app->GetWindowContext(buf, 128); - WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL); - return bufout; -}*/ + if (!_td || !_td->lpActiveKeyboard || !_td->lpActiveKeyboard->lpCoreKeyboardState){ + return ""; + } + + WCHAR buf[(MAXCONTEXT * 3) + 1]; // *3 if every context item was a deadkey + km_core_context_item *citems = nullptr; + if (KM_CORE_STATUS_OK != km_core_context_get( + km_core_state_context(_td->lpActiveKeyboard->lpCoreKeyboardState), &citems)) { + return ""; + } + + DWORD context_length = (DWORD)km_core_context_item_list_size(citems); + if (!ContextItemToAppContext(citems, buf, context_length)) { + km_core_context_items_dispose(citems); + return ""; + } + km_core_context_items_dispose(citems); + return Debug_UnicodeString(buf); -char *getcontext_debug() { - //return ""; - PKEYMAN64THREADDATA _td = ThreadGlobals(); - if(!_td) return ""; - return Debug_UnicodeString(_td->app->ContextBufMax(128)); } /** @@ -98,14 +104,15 @@ char *getcontext_debug() { static BOOL Process_Event_Core(PKEYMAN64THREADDATA _td) { - PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT); - km_core_context_item *citems = nullptr; - ContextItemsFromAppContext(contextBuf, &citems); - if (KM_CORE_STATUS_OK != km_core_context_set(km_core_state_context(_td->lpActiveKeyboard->lpCoreKeyboardState), citems)) { - km_core_context_items_dispose(citems); - return FALSE; + WCHAR application_context[MAXCONTEXT]; + if (_td->app->ReadContext(application_context)) { + km_core_context_status result; + result = km_core_state_context_set_if_needed(_td->lpActiveKeyboard->lpCoreKeyboardState, reinterpret_cast(application_context)); + if (result == KM_CORE_CONTEXT_STATUS_ERROR || result == KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT) { + SendDebugMessageFormat(0, sdmGlobal, 0, "Process_Event_Core: km_core_state_context_set_if_needed returned [%d]", result); + } } - km_core_context_items_dispose(citems); + SendDebugMessageFormat( 0, sdmGlobal, 0, "ProcessEvent: vkey[%d] ShiftState[%d] isDown[%d]", _td->state.vkey, static_cast(Globals::get_ShiftState() & (KM_CORE_MODIFIER_MASK_ALL | KM_CORE_MODIFIER_MASK_CAPS)), (uint8_t)_td->state.isDown); @@ -139,8 +146,6 @@ BOOL ProcessHook() fOutputKeystroke = FALSE; // TODO: 5442 no longer needs to be global once we use core processor - _td->app->ReadContext(); - if(_td->state.msg.message == wm_keymankeydown) { // I4827 if (ShouldDebug(sdmKeyboard)) { SendDebugMessageFormat(_td->state.msg.hwnd, sdmKeyboard, 0, "Key pressed: %s Context '%s'", @@ -212,9 +217,6 @@ BOOL ProcessHook() _td->app->SetCurrentShiftState(Globals::get_ShiftState()); _td->app->SendActions(); // I4196 } - // output context for debugging - // PWSTR contextBuf = _td->app->ContextBufMax(MAXCONTEXT); - // SendDebugMessageFormat(0, sdmAIDefault, 0, "Kmprocess::ProcessHook After cxt=%s", Debug_UnicodeString(contextBuf, 1)); return !fOutputKeystroke; } diff --git a/windows/src/engine/keyman32/kmprocessactions.cpp b/windows/src/engine/keyman32/kmprocessactions.cpp index c9d607a596c..1eecfa6807e 100644 --- a/windows/src/engine/keyman32/kmprocessactions.cpp +++ b/windows/src/engine/keyman32/kmprocessactions.cpp @@ -78,10 +78,8 @@ static BOOL processPersistOpt( } static BOOL processInvalidateContext( - AITIP* app, - km_core_state* keyboardState + AITIP* app ) { - km_core_context_clear(km_core_state_context(keyboardState)); app->ResetContext(); return TRUE; } @@ -158,7 +156,7 @@ BOOL ProcessActions(BOOL* emitKeyStroke) continueProcessingActions = TRUE; break; case KM_CORE_IT_INVALIDATE_CONTEXT: - continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState); + continueProcessingActions = processInvalidateContext(_td->app); break; case KM_CORE_IT_CAPSLOCK: continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE); @@ -202,7 +200,7 @@ ProcessActionsNonUpdatableParse(BOOL* emitKeyStroke) { continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE); break; case KM_CORE_IT_INVALIDATE_CONTEXT: - continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState); + continueProcessingActions = processInvalidateContext(_td->app); break; } if (!continueProcessingActions) { @@ -228,7 +226,7 @@ ProcessActionsExternalEvent() { continueProcessingActions = processCapsLock(act, !_td->state.isDown, FALSE, TRUE); break; case KM_CORE_IT_INVALIDATE_CONTEXT: - continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState); + continueProcessingActions = processInvalidateContext(_td->app); break; } if (!continueProcessingActions) { diff --git a/windows/src/engine/keyman64/keyman64.vcxproj b/windows/src/engine/keyman64/keyman64.vcxproj index f80c93089d7..d75962c5963 100644 --- a/windows/src/engine/keyman64/keyman64.vcxproj +++ b/windows/src/engine/keyman64/keyman64.vcxproj @@ -177,6 +177,7 @@ + @@ -276,6 +277,7 @@ + @@ -327,4 +329,4 @@ - + \ No newline at end of file diff --git a/windows/src/engine/keyman64/keyman64.vcxproj.filters b/windows/src/engine/keyman64/keyman64.vcxproj.filters index cd8aeefb322..06ba0e951e1 100644 --- a/windows/src/engine/keyman64/keyman64.vcxproj.filters +++ b/windows/src/engine/keyman64/keyman64.vcxproj.filters @@ -38,6 +38,7 @@ + @@ -72,6 +73,7 @@ +