Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(windows): removed cached context windows engine #10065

Merged
merged 7 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/kmx/kmx_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
166 changes: 166 additions & 0 deletions windows/src/engine/keyman32/appcontext.cpp
Original file line number Diff line number Diff line change
@@ -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<WCHAR> Uni_UTF32ToSurrogate1(km_core_context_it->character);
buf[idx++] = static_cast<WCHAR> 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<WCHAR>(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;
}
107 changes: 107 additions & 0 deletions windows/src/engine/keyman32/appcontext.h
Original file line number Diff line number Diff line change
@@ -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
74 changes: 6 additions & 68 deletions windows/src/engine/keyman32/appint/aiTIP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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);
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 */

Expand Down
Loading