diff --git a/common/core/desktop/src/km_kbp_keyboard_api.cpp b/common/core/desktop/src/km_kbp_keyboard_api.cpp index 8a2ef7b3ad..04c8eab956 100644 --- a/common/core/desktop/src/km_kbp_keyboard_api.cpp +++ b/common/core/desktop/src/km_kbp_keyboard_api.cpp @@ -13,7 +13,7 @@ #include #include "keyboard.hpp" #include "processor.hpp" -#include "kmx/kmx_processevent.hpp" +#include "kmx/kmx_processor.hpp" #include "mock/mock_processor.hpp" #include "rust/rust_mock_processor.hpp" diff --git a/common/core/desktop/src/kmx/kmx_actions.cpp b/common/core/desktop/src/kmx/kmx_actions.cpp index 03c70eb134..7eea892b63 100644 --- a/common/core/desktop/src/kmx/kmx_actions.cpp +++ b/common/core/desktop/src/kmx/kmx_actions.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include using namespace km::kbp; using namespace kmx; diff --git a/common/core/desktop/src/kmx/kmx_capslock.cpp b/common/core/desktop/src/kmx/kmx_capslock.cpp index 999fa08f64..63856be18d 100644 --- a/common/core/desktop/src/kmx/kmx_capslock.cpp +++ b/common/core/desktop/src/kmx/kmx_capslock.cpp @@ -2,12 +2,12 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include using namespace km::kbp; using namespace kmx; -void KMX_Processor::ResetCapsLock(void) +void KMX_ProcessEvent::ResetCapsLock(void) { DebugLog("ResetCapsLock: enter"); @@ -24,7 +24,7 @@ void KMX_Processor::ResetCapsLock(void) } -void KMX_Processor::KeyCapsLockPress(KMX_BOOL FIsUp) +void KMX_ProcessEvent::KeyCapsLockPress(KMX_BOOL FIsUp) { if(m_keyboard.Keyboard->dwFlags & KF_CAPSONONLY) { @@ -43,7 +43,7 @@ void KMX_Processor::KeyCapsLockPress(KMX_BOOL FIsUp) } -void KMX_Processor::KeyShiftPress(KMX_BOOL FIsUp) +void KMX_ProcessEvent::KeyShiftPress(KMX_BOOL FIsUp) { if(!m_environment.capsLock()) return; diff --git a/common/core/desktop/src/kmx/kmx_consts.cpp b/common/core/desktop/src/kmx/kmx_consts.cpp index 4e549df995..199e3ff45a 100644 --- a/common/core/desktop/src/kmx/kmx_consts.cpp +++ b/common/core/desktop/src/kmx/kmx_consts.cpp @@ -1,5 +1,5 @@ -#include "kmx_processor.h" +#include "kmx_processevent.h" namespace km { namespace kbp { diff --git a/common/core/desktop/src/kmx/kmx_context.cpp b/common/core/desktop/src/kmx/kmx_context.cpp index 6efa11f94f..79db902d7d 100644 --- a/common/core/desktop/src/kmx/kmx_context.cpp +++ b/common/core/desktop/src/kmx/kmx_context.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include using namespace km::kbp; using namespace kmx; diff --git a/common/core/desktop/src/kmx/kmx_debug.cpp b/common/core/desktop/src/kmx/kmx_debug.cpp index a58fd8e17e..a820c6bc45 100644 --- a/common/core/desktop/src/kmx/kmx_debug.cpp +++ b/common/core/desktop/src/kmx/kmx_debug.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include "kmx_processor.h" +#include "kmx_processevent.h" #include #include diff --git a/common/core/desktop/src/kmx/kmx_environment.cpp b/common/core/desktop/src/kmx/kmx_environment.cpp index 4fac39182f..e2f7359469 100644 --- a/common/core/desktop/src/kmx/kmx_environment.cpp +++ b/common/core/desktop/src/kmx/kmx_environment.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include +#include #include #include #include diff --git a/common/core/desktop/src/kmx/kmx_file.cpp b/common/core/desktop/src/kmx/kmx_file.cpp index 2ce631a01c..1a6bdf64f3 100644 --- a/common/core/desktop/src/kmx/kmx_file.cpp +++ b/common/core/desktop/src/kmx/kmx_file.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include "kmx_processor.h" +#include "kmx_processevent.h" using namespace km::kbp; using namespace kmx; @@ -11,7 +11,7 @@ using namespace kmx; #include #endif -KMX_BOOL KMX_Processor::Load(km_kbp_path_name KeyboardName) +KMX_BOOL KMX_ProcessEvent::Load(km_kbp_path_name KeyboardName) { if(!LoadKeyboard(KeyboardName, &m_keyboard.Keyboard)) return FALSE; // I5136 @@ -75,7 +75,7 @@ unsigned long CalculateBufferCRC(unsigned long count, KMX_BYTE *p) return crc; } -KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard) +KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard) { PKMX_BYTE buf; FILE *fp; @@ -174,7 +174,7 @@ KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKe return TRUE; } -PKMX_WCHAR KMX_Processor::StringOffset(PKMX_BYTE base, KMX_DWORD offset) +PKMX_WCHAR KMX_ProcessEvent::StringOffset(PKMX_BYTE base, KMX_DWORD offset) { if(offset == 0) return NULL; return (PKMX_WCHAR)(base + offset); @@ -188,7 +188,7 @@ PKMX_WCHAR KMX_Processor::StringOffset(PKMX_BYTE base, KMX_DWORD offset) we don't copy the strings This method is used on 64-bit architectures. */ -LPKEYBOARD KMX_Processor::CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) +LPKEYBOARD KMX_ProcessEvent::CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) { PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) base; @@ -271,7 +271,7 @@ LPKEYBOARD KMX_Processor::CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) Fixup the keyboard by expanding pointers. On disk the pointers are stored relative to the beginning of the file, but we need real pointers. This method is used on 32-bit architectures. */ -LPKEYBOARD KMX_Processor::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) +LPKEYBOARD KMX_ProcessEvent::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) { KMX_DWORD i, j; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) base; @@ -311,7 +311,7 @@ LPKEYBOARD KMX_Processor::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) #endif -KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, size_t sz) +KMX_BOOL KMX_ProcessEvent::VerifyChecksum(PKMX_BYTE buf, size_t sz) { KMX_DWORD tempcs; PCOMP_KEYBOARD ckbp; @@ -324,7 +324,7 @@ KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, size_t sz) return tempcs == CalculateBufferCRC(sz, buf); } -KMX_BOOL KMX_Processor::VerifyKeyboard(PKMX_BYTE filebase, size_t sz) +KMX_BOOL KMX_ProcessEvent::VerifyKeyboard(PKMX_BYTE filebase, size_t sz) { KMX_DWORD i; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; diff --git a/common/core/desktop/src/kmx/kmx_modifiers.cpp b/common/core/desktop/src/kmx/kmx_modifiers.cpp index 1da7a8a3c9..0de3ce6ed8 100644 --- a/common/core/desktop/src/kmx/kmx_modifiers.cpp +++ b/common/core/desktop/src/kmx/kmx_modifiers.cpp @@ -2,7 +2,7 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ -#include "kmx_processor.h" +#include "kmx_processevent.h" using namespace km::kbp; using namespace kmx; @@ -78,7 +78,7 @@ static KMX_BYTE states[MAX_RSHIFT][MAX_KSHIFT] = { * K_CTRLFLAG and K_ALTFLAG as well as specific keys correctly. */ -KMX_BOOL KMX_Processor::IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift) { +KMX_BOOL KMX_ProcessEvent::IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift) { // // The rule shift must have ISVIRTUALKEY bit set for virt.keys // diff --git a/common/core/desktop/src/kmx/kmx_options.cpp b/common/core/desktop/src/kmx/kmx_options.cpp index 51538d2157..a4417cb755 100644 --- a/common/core/desktop/src/kmx/kmx_options.cpp +++ b/common/core/desktop/src/kmx/kmx_options.cpp @@ -3,7 +3,7 @@ Authors: mcdurdin */ #include "processor.hpp" -#include "kmx_processor.h" +#include "kmx_processevent.h" #include #include diff --git a/common/core/desktop/src/kmx/kmx_processevent.cpp b/common/core/desktop/src/kmx/kmx_processevent.cpp index 58bc29e33e..893ff1d9ba 100644 --- a/common/core/desktop/src/kmx/kmx_processevent.cpp +++ b/common/core/desktop/src/kmx/kmx_processevent.cpp @@ -1,189 +1,682 @@ -#include +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include "kmx_processevent.h" #include "state.hpp" -#include "kmx/kmx_processevent.hpp" -namespace km { - namespace kbp +using namespace km::kbp; +using namespace kmx; + +/* Globals */ + +KMX_BOOL km::kbp::kmx::g_debug_ToConsole = FALSE; +KMX_BOOL km::kbp::kmx::g_debug_KeymanLog = TRUE; +KMX_BOOL km::kbp::kmx::g_silent = FALSE; + +/* +* KMX_ProcessEvent +*/ + +KMX_ProcessEvent::KMX_ProcessEvent() : m_actions(&m_context), m_options(&m_keyboard) { + m_indexStack = new KMX_WORD[GLOBAL_ContextStackSize]; + m_miniContext = new KMX_WCHAR[GLOBAL_ContextStackSize]; + m_miniContextIfLen = 0; +} + +KMX_ProcessEvent::~KMX_ProcessEvent() { + delete[] m_indexStack; + delete[] m_miniContext; +} + +char VKeyToChar(KMX_UINT modifiers, KMX_UINT vk) { + // We only map SHIFT and UNSHIFTED, and CAPS LOCK + + if ((modifiers & ~(K_SHIFTFLAG | CAPITALFLAG)) != 0) { + return 0; + } + + bool + shifted = (modifiers & K_SHIFTFLAG) == K_SHIFTFLAG, + caps = (modifiers & CAPITALFLAG) == CAPITALFLAG; + + if (vk == KM_KBP_VKEY_SPACE) { + // Override for space because it is the same for + // shifted and unshifted. + return 32; + } + + for (int i = 0; s_char_to_vkey[i].vk; i++) { + if (s_char_to_vkey[i].caps && caps) { + if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == !shifted) return i + 32; + } + else { + if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == shifted) return i + 32; + } + } + return 0; +} + +/* +* KMX_BOOL ProcessEvent(); +* +* Parameters: none +* +* Returns: TRUE if keystroke should be eaten +* +* Called by: FilterFunc +* +* ProcessEvent organizes the messages and gives them to the appropriate routines to +* process, and checks the state of Windows for the keyboard handling. +*/ + +KMX_BOOL KMX_ProcessEvent::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWORD modifiers) +{ + LPKEYBOARD kbd = m_keyboard.Keyboard; + + m_kbp_state = state; + + if (m_environment.capsLock()) + modifiers |= CAPITALFLAG; + + m_state.vkey = vkey; + m_state.charCode = VKeyToChar(modifiers, vkey); + m_modifiers = modifiers; + m_state.LoopTimes = 0; + + if (kbd->StartGroup[BEGIN_UNICODE] == (KMX_DWORD) -1) { + DebugLog("Non-Unicode keyboards are not supported."); + return FALSE; + } + + LPGROUP gp = &kbd->dpGroupArray[kbd->StartGroup[BEGIN_UNICODE]]; + + KMX_BOOL fOutputKeystroke = FALSE; + + ProcessGroup(gp, &fOutputKeystroke); + + m_kbp_state = nullptr; + + return !fOutputKeystroke; +} + +/* +* PRIVATE KMX_BOOL ProcessGroup(LPGROUP gp); +* +* Parameters: gp Pointer to group to process inside +* +* Returns: TRUE if messages are to be sent, +* and FALSE if no messages are to be sent. +* +* Called by: ProcessEvent, recursive inside groups +* +* ProcessKey is where the keystroke conversion and output takes place. This routine +* has a lot of crucial code in it! +*/ + +KMX_BOOL KMX_ProcessEvent::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) +{ + KMX_DWORD i; + LPKEY kkp = NULL; + PKMX_WCHAR p; + int sdmfI; + + /* + If the number of nested groups goes higher than 50, then break out - this is + a limitation of stack size. This is basically a catch-all for freaky apps that + cause message loopbacks and nasty things like that. Okay, it's really a catch all + for bugs! This means the user's system shouldn't hang. + */ + + LPKEYBOARD kbd = m_keyboard.Keyboard; + + sdmfI = -1; + + for(i = 0; i < kbd->cxGroupArray; i++) + if(gp == &kbd->dpGroupArray[i]) + { + sdmfI = i; + break; + } + + if(++m_state.LoopTimes > 50) { + DebugLog("Aborting output: m_state.LoopTimes exceeded."); + m_state.StopOutput = TRUE; + return FALSE; + } - km_kbp_status kmx_processor::validate() const { - return _valid ? KM_KBP_STATUS_OK : KM_KBP_STATUS_INVALID_KEYBOARD; - } + /* + The rule matching loop. - kmx_processor::kmx_processor(kbp::path p) + This loop iterates through all the rules in the group that is currently being + processed. Each rule in a group can be of three different types: + 1. A virtual key rule, where the key to be matched is a virtual key + 2. A normal key rule (WM_CHAR), where the key to be matched is an Ascii char. + 3. A rule in a keyless group, where only the context is matched. + + The loop goes through and checks the rules like that. This loop could be optimized + with standard searching techniques - the ContextMatch may be difficult. + */ + + DebugLog("m_state.vkey: %s shiftFlags: %x; charCode: %X", Debug_VirtualKey(m_state.vkey), m_modifiers, m_state.charCode); // I4582 + DebugLog("m_context: %s", Debug_UnicodeString(m_context.GetFullContext())); + + if(gp) + { + for(kkp = gp->dpKeyArray, i=0; i < gp->cxKeyArray; i++, kkp++) { - p.replace_extension(".kmx"); - _valid = bool(_kmx.Load(p.c_str())); - - if (!_valid) - return; - - keyboard_attributes::options_store defaults; - _kmx.GetOptions()->Init(defaults); - - for (auto const & opt: defaults) + if(!ContextMatch(kkp)) continue; + if(!gp->fUsingKeys) { - if (!opt.empty() && opt.scope == KM_KBP_OPT_KEYBOARD ) - persisted_store()[opt.key] = opt.value; + if(kkp->dpContext[0] != 0) break; else continue; } - // Fill out attributes - auto v = _kmx.GetKeyboard()->Keyboard->version; - auto vs = std::to_string(v >> 16) + "." + std::to_string(v & 0xffff); - _attributes = keyboard_attributes(static_cast(p.stem()), - std::u16string(vs.begin(), vs.end()), p.parent(), defaults); - } + //if(kkp->Key == m_state.vkey) + //SendDebugMessageFormat(m_state.msg.hwnd, sdmKeyboard, 0, "kkp->Key: %d kkp->ShiftFlags: %x", + // kkp->Key, kkp->ShiftFlags); - - char16_t const * kmx_processor::lookup_option(km_kbp_option_scope scope, std::u16string const & key) const - { - char16_t const * pValue = nullptr; - switch(scope) + /* Keyman 6.0: support Virtual Characters */ + if(IsEquivalentShift(kkp->ShiftFlags, m_modifiers)) { - case KM_KBP_OPT_KEYBOARD: - pValue = _kmx.GetOptions()->LookUp(key); - break; - case KM_KBP_OPT_ENVIRONMENT: - pValue = _kmx.GetEnvironment()->LookUp(key); - break; - default: - break; + if(kkp->Key > VK__MAX && kkp->Key == m_state.vkey) break; // I3438 // I4582 + else if(kkp->Key == m_state.vkey) break; // I4169 } - - return pValue ? pValue : nullptr; + else if(kkp->ShiftFlags == 0 && kkp->Key == m_state.charCode && m_state.charCode != 0) break; } + } - option kmx_processor::update_option(km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) + if(!gp || i == gp->cxKeyArray) + { + /* + No rule was found that corresponded to the current state of the context and + keyboard. NoMatch should be checked for everything except virtual keys; and + context should also be kept. + + If the message was a virtual key, then just return without checking NoMatch. + NoMatch shouldn't be used for virtual keys because it will mean that no key + can ever get through that isn't matched - including arrows, func. keys, etc !! + Context is not kept for virtual keys being output. + */ + + DebugLog("No match was found in group %d of %d", sdmfI, m_keyboard.Keyboard->cxGroupArray); + + if(!gp || (m_state.charCode == 0 && gp->fUsingKeys)) // I4585 + // 7.0.241.0: I1133 - Fix mismatched parentheses on m_state.charCode - ie. we don't want to output this letter if !gp->fUsingKeys { - switch(scope) { - case KM_KBP_OPT_KEYBOARD: - _kmx.GetOptions()->Set(key, value); - persisted_store()[key] = value; - break; - case KM_KBP_OPT_ENVIRONMENT: - _kmx.GetEnvironment()->Load(key, value); - break; - default: - return option(); - break; - } + KMX_BOOL fIsBackspace = m_state.vkey == KM_KBP_VKEY_BKSP && (m_modifiers & (LCTRLFLAG|RCTRLFLAG|LALTFLAG|RALTFLAG)) == 0; // I4128 - return option(scope, key, value); + if(fIsBackspace) { // I4838 // I4933 + PKMX_WCHAR pdeletecontext = m_context.Buf(1); // I4933 + if(!pdeletecontext || *pdeletecontext == 0) { // I4933 + m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + *pOutputKeystroke = TRUE; // I4933 + return FALSE; // I4933 + } + m_actions.QueueAction(QIT_BACK, BK_BACKSPACE); // I4933 + } else { // I4024 // I4128 // I4287 // I4290 + DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128 + m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + *pOutputKeystroke = TRUE; + return FALSE; + } + } + else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0) + { + /* NoMatch rule found, and is a character key */ + PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + } + else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys) + { + /* No rule found, is a character key */ + m_actions.QueueAction(QIT_CHAR, m_state.charCode); } - km_kbp_status kmx_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { - // Construct a context buffer from the items + return TRUE; + } - std::u16string ctxt; - auto cp = state->context(); - for (auto c = cp.begin(); c != cp.end(); c++) { - switch (c->type) { - case KM_KBP_CT_CHAR: - if (Uni_IsSMP(c->character)) { - ctxt += Uni_UTF32ToSurrogate1(c->character); - ctxt += Uni_UTF32ToSurrogate2(c->character); - } - else { - ctxt += (km_kbp_cp) c->character; - } - break; - case KM_KBP_CT_MARKER: - assert(c->marker > 0); - ctxt += UC_SENTINEL; - ctxt += CODE_DEADKEY; - ctxt += c->marker; - break; + DebugLog("match found in rule %d", i); + + /* + Save the context that will be used for output when the 'context' keyword is used. + For each deadkey, we need to add 2 characters; look in related stores as well... + */ + + assert(kkp != NULL); + + m_miniContextIfLen = xstrlen(kkp->dpContext) - xstrlen_ignoreifopt(kkp->dpContext); + + // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support + if(*kkp->dpContext == UC_SENTINEL && *(kkp->dpContext+1) == CODE_NUL) + u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)-1) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 + else + u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 + + m_miniContext[GLOBAL_ContextStackSize-1] = 0; + + /* + The next section includes several optimizations that make the code a little harder + to read, but are probably worth it in the time that they save. + + If the output string doesn't have a "context" byte at the start, post backspaces + to erase the appropriate number of characters in the application. If it does have + a "context" byte at the start, then the string won't change, and no backspaces are + necessary. You could go one step further with this optimization, in PostAllKeys, + by comparing the starts of the strings to see what is same, and not backspacing + that, but it is probably not necessary. + */ + + p = kkp->dpOutput; + if(*p != UC_SENTINEL || *(p+1) != CODE_CONTEXT) + { + for(p = decxstr((KMX_WCHAR *) u16chr(m_miniContext, 0), m_miniContext); p != NULL; p = decxstr(p, m_miniContext)) + { + if(*p == UC_SENTINEL) + switch(*(p+1)) + { + case CODE_DEADKEY: m_actions.QueueAction(QIT_BACK, BK_DEADKEY); break; + case CODE_NUL: break; // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support + } + else + { + m_actions.QueueAction(QIT_BACK, 0); + } + } + p = kkp->dpOutput; + } + else + p = incxstr(p); // otherwise, the "context" entry has to be jumped over + + /* Use PostString to post the rest of the output string. */ + + if(PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches) + { + if(gp->dpMatch && *gp->dpMatch) + { + PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + } + } + + return TRUE; +} + +/* +* int PostString( PKMX_CHAR str, KMX_BOOL *useMode, LPMSG mp, +* LPKEYBOARD lpkb ); +* +* Parameters: str Pointer to string to send +* useMode Pointer to KMX_BOOL about whether a "use" command was found +* mp Pointer to MSG structure to copy in outputting messages +* lpkb Pointer to global keyboard structure +* +* Returns: 0 to continue, 1 and 2 to return. +* +* Called by: ProcessKey +* +* PostString posts a string of "context", "index", "beep", characters and virtual keys +* to the active application, via the Keyman PostKey buffer. +*/ + +int KMX_ProcessEvent::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr, KMX_BOOL *pOutputKeystroke) +{ + PKMX_WCHAR p, q, temp; + LPSTORE s; + int n1, n2; + int i, n, shift; + KMX_BOOL FoundUse = FALSE; + + // TODO: Refactor to use incxstr + for(p = str; *p && (p < endstr || !endstr); p++) + { + if(*p == UC_SENTINEL) + switch(*(++p)) + { + case CODE_EXTENDED: // Start of a virtual key section w/shift codes + p++; + + shift = *p; //(*p<<8) | *(p+1); + m_actions.QueueAction(QIT_VSHIFTDOWN, shift); + + p++; + + m_actions.QueueAction(QIT_VKEYDOWN, *p); + m_actions.QueueAction(QIT_VKEYUP, *p); + + m_actions.QueueAction(QIT_VSHIFTUP, shift); + + p++; // CODE_EXTENDEDEND + ////// CODE_EXTENDEDEND will be incremented by loop + + break; + + case CODE_DEADKEY: // A deadkey to be output + p++; + m_actions.QueueAction(QIT_DEADKEY, *p); + break; + case CODE_BEEP: // Sound an 'iconasterisk' beep + m_actions.QueueAction(QIT_BELL, 0); + break; + case CODE_CONTEXT: // copy the context to the output + { + KMX_BOOL ignoreOutputKeystroke; + PostString(m_miniContext, lpkb, (PKMX_WCHAR) u16chr(m_miniContext, 0), &ignoreOutputKeystroke); + break; + } + case CODE_CONTEXTEX: + p++; + for (q = m_miniContext, i = m_miniContextIfLen; *q && i < *p - 1; i++, q = incxstr(q)); + if (*q) { + KMX_BOOL ignoreOutputKeystroke; + temp = incxstr(q); + PostString(q, lpkb, temp, &ignoreOutputKeystroke); + } + break; + case CODE_RETURN: // stop processing and start PostAllKeys + m_state.StopOutput = TRUE; + return psrPostMessages; + + case CODE_CALL: + p++; + DebugLog("CallDLL not supported [store=%d].\n", *p-1); + FoundUse = TRUE; + break; + case CODE_USE: // use another group + p++; + ProcessGroup(&lpkb->dpGroupArray[*p-1], pOutputKeystroke); + if(m_state.StopOutput) return psrPostMessages; + FoundUse = TRUE; + break; + case CODE_CLEARCONTEXT: + // no longer supported, no-op + break; + case CODE_INDEX: + p++; + s = &m_keyboard.Keyboard->dpStoreArray[*p - 1]; + p++; + + n = m_indexStack[*p - 1]; + for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); + PostString(temp, lpkb, incxstr(temp), pOutputKeystroke); + break; + case CODE_SETOPT: + p++; + n1 = *p - 1; + p++; + n2 = *p - 1; + GetOptions()->Set(n1, n2); + break; + case CODE_RESETOPT: + p++; + n1 = *p - 1; + GetOptions()->Reset(m_kbp_state->processor(), n1); + break; + case CODE_SAVEOPT: + p++; + n1 = *p - 1; + GetOptions()->Save(*m_kbp_state, n1); + break; + case CODE_IFSYSTEMSTORE: + p+=3; + break; + case CODE_SETSYSTEMSTORE: + p+=2; + break; + } + else { + if(Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p+1))) { + m_actions.QueueAction(QIT_CHAR, Uni_SurrogateToUTF32(*p, *(p+1))); + p++; + } else { + m_actions.QueueAction(QIT_CHAR, *p); + } + } + } + return FoundUse ? psrPostMessages : psrCheckMatches; +} + + +KMX_BOOL KMX_ProcessEvent::IsMatchingBaseLayout(PKMX_WCHAR layoutName) // I3432 +{ + KMX_BOOL bEqual = u16icmp(layoutName, static_cast(m_environment.baseLayout().c_str())) == 0 || // I4583 + u16icmp(layoutName, static_cast(m_environment.baseLayoutAlt().c_str())) == 0; // I4583 + + return bEqual; +} + +KMX_BOOL KMX_ProcessEvent::IsMatchingPlatformString(PKMX_WCHAR platform) // I3432 +{ + // TODO retrieve platform string from client environment + // TODO cleanup to use a vector. + PKMX_WCHAR t = new KMX_WCHAR[u16len(m_environment.platform().c_str()) + 1], context = NULL; + u16cpy(t, /*wcslen(s->dpString)+1,*/ m_environment.platform().c_str()); + PKMX_WCHAR p = u16tok(t, u' ', &context); + while (p != NULL) { + if (u16icmp(platform, p) == 0) { + delete [] t; + return TRUE; + } + p = u16tok(NULL, u' ', &context); + } + + delete [] t; + return FALSE; +} + +KMX_BOOL KMX_ProcessEvent::IsMatchingPlatform(LPSTORE s) // I3432 +{ + PKMX_WCHAR t = new KMX_WCHAR[u16len(s->dpString)+1]; + u16cpy(t, /*wcslen(s->dpString)+1,*/ s->dpString); + PKMX_WCHAR context = NULL; + PKMX_WCHAR platform = u16tok(t, u' ', &context); + while(platform != NULL) + { + if(!IsMatchingPlatformString(platform)) + { + s->dwSystemID = TSS_PLATFORM_NOMATCH; + delete [] t; + return FALSE; + } + platform = u16tok(NULL, u' ', &context); + } + + s->dwSystemID = TSS_PLATFORM_MATCH; + delete [] t; + return TRUE; +} + +/* +* KMX_BOOL ContextMatch( LPKEY kkp ); +* +* Parameters: kkp Rule to compare +* +* Returns: 0 on OK, 1 on not equal +* +* Called by: ProcessKey +* +* ContextMatch compares the context of a rule with the current context. +*/ + +KMX_BOOL KMX_ProcessEvent::ContextMatch(LPKEY kkp) +{ + KMX_WORD /*i,*/ n; + PKMX_WCHAR p, q, qbuf, temp; + PKMX_WORD indexp; + LPSTORE s, t; + KMX_BOOL bEqual; + + memset(m_indexStack, 0, GLOBAL_ContextStackSize*sizeof(KMX_WORD)); + + p = kkp->dpContext; + + if(*p == 0) + { + return TRUE; + } + + if(*p == UC_SENTINEL && *(p+1) == CODE_NUL) + { + // If context buf is longer than the context, then obviously not start of doc. + if(m_context.Buf(xstrlen_ignoreifopt(p))) return FALSE; + p = incxstr(p); + if(*p == 0) return TRUE; + } + + for(PKMX_WCHAR pp = p; pp && *pp; pp = incxstr(pp)) + { + if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFOPT) + { + s = &m_keyboard.Keyboard->dpStoreArray[(*(pp+2))-1]; + t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; + + bEqual = u16cmp(s->dpString, t->dpString) == 0; + if(*(pp+3) == 1 && bEqual) return FALSE; + if(*(pp+3) == 2 && !bEqual) return FALSE; + } + else if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFSYSTEMSTORE) + { + KMX_DWORD dwSystemID = *(pp+2)-1; + t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; + switch(dwSystemID) + { + case TSS_PLATFORM_MATCH: // Cached platform result - a matching platform + bEqual = TRUE; + break; + case TSS_PLATFORM_NOMATCH: // Cached platform result - not a matching platform + bEqual = FALSE; + break; + case TSS_PLATFORM: + bEqual = IsMatchingPlatform(t); + break; + case TSS_BASELAYOUT: + bEqual = IsMatchingBaseLayout(t->dpString); + break; + default: + { + PKMX_WCHAR ss = GetSystemStore(m_keyboard.Keyboard, dwSystemID); + if(ss == NULL) return FALSE; + bEqual = u16cmp(ss, t->dpString) == 0; } } - _kmx.GetContext()->Set(ctxt.c_str()); - _kmx.GetActions()->ResetQueue(); - state->actions().clear(); + if(*(pp+3) == 1 && bEqual) return FALSE; + if(*(pp+3) == 2 && !bEqual) return FALSE; + } + } - if (!_kmx.ProcessEvent(state, vk, modifier_state)) { - // We need to output the default keystroke - state->actions().push_emit_keystroke(); - } + q = qbuf = m_context.Buf(xstrlen_ignoreifopt(p)); + if(!q) + { + return FALSE; // context buf is too short! + } + indexp = m_indexStack; - for (auto i = 0; i < _kmx.GetActions()->Length(); i++) { - auto a = _kmx.GetActions()->Get(i); - switch (a.ItemType) { - case QIT_CAPSLOCK: - //TODO: add Caps Event - //dwData = 0 == off; 1 == on - //state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CAPSLOCK, {0,}, {0} }); - break; - case QIT_VKEYDOWN: - case QIT_VKEYUP: - case QIT_VSHIFTDOWN: - case QIT_VSHIFTUP: - //TODO: eliminate?? - break; - case QIT_CHAR: - state->context().push_character(a.dwData); - state->actions().push_character(a.dwData); - break; - case QIT_DEADKEY: - state->context().push_marker(a.dwData); - state->actions().push_marker(a.dwData); - break; - case QIT_BELL: - state->actions().push_alert(); - break; - case QIT_BACK: - switch (a.dwData) { - case BK_DEFAULT: - // This only happens if we know we have context to delete. Last item must be a character - assert(!state->context().empty() && state->context().back().type != KM_KBP_IT_MARKER); - if (!state->context().empty()) state->context().pop_back(); - state->actions().push_backspace(); - break; - case BK_DEADKEY: - // This only happens if we know we have context to delete. Last item must be a deadkey - assert(!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER); - if (!state->context().empty()) state->context().pop_back(); - break; - case BK_BACKSPACE: - // User-initiated backspace. We need to delete deadkeys from context, both sides of the character deleted - while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); - if (!state->context().empty()) { - state->context().pop_back(); - while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); - } - // Even if context is empty, we send the backspace event, because we may not - // know the context. - state->actions().push_backspace(); - break; - default: - assert(false); - } - break; - case QIT_INVALIDATECONTEXT: - state->actions().push_invalidate_context(); - break; - default: - //std::cout << "Unexpected item type " << a.ItemType << ", " << a.dwData << std::endl; - assert(false); + for(; *p && *q; p = incxstr(p)) + { + *indexp = 0; + + if(*p == UC_SENTINEL) + { + switch(*(p+1)) + { + case CODE_DEADKEY: + if(*q != UC_SENTINEL || *(q+1) != CODE_DEADKEY || *(q+2) != *(p+2)) + { + return FALSE; } + break; + case CODE_ANY: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + + temp = xstrchr(s->dpString, q); + + if(temp != NULL) + *indexp = (KMX_WORD) xstrpos(temp, s->dpString); + + else + return FALSE; + break; + case CODE_NOTANY: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + + if((temp = xstrchr(s->dpString, q)) != NULL) + return FALSE; + + break; + case CODE_INDEX: + s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; + *indexp = n = m_indexStack[(*(p+3))-1]; + + for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); + if(n != 0) return FALSE; + if(xchrcmp(temp, q) != 0) return FALSE; + break; + case CODE_CONTEXTEX: + // only the nth character + for(n = *(p+2) - 1, temp = qbuf; temp < q && n > 0; n--, temp = incxstr(temp)); + if(n == 0) + if(xchrcmp(temp, q) != 0) return FALSE; + break; + case CODE_IFOPT: + case CODE_IFSYSTEMSTORE: + indexp++; + continue; // don't increment q + default: + return FALSE; } - - state->actions().commit(); - - return KM_KBP_STATUS_OK; } - - constexpr km_kbp_attr const engine_attrs = { - 256, - KM_KBP_LIB_CURRENT, - KM_KBP_LIB_AGE, - KM_KBP_LIB_REVISION, - KM_KBP_TECH_KMX, - "SIL International" - }; - - km_kbp_attr const & kmx_processor::attributes() const { - return engine_attrs; + else if(xchrcmp(p, q) != 0) + { + return FALSE; } + indexp++; + q = incxstr(q); + } - } // namespace kbp -} // namespace km + while(*p == UC_SENTINEL && (*(p+1) == CODE_IFOPT || *(p+1) == CODE_IFSYSTEMSTORE)) p = incxstr(p); // already tested + + return *p == *q; /*at least one must ==0 at this point*/ +} + +KMX_Actions *KMX_ProcessEvent::GetActions() { + return &m_actions; +} + +KMX_Context *KMX_ProcessEvent::GetContext() { + return &m_context; +} + +KMX_Options *KMX_ProcessEvent::GetOptions() { + return &m_options; +} + +KMX_Options const *KMX_ProcessEvent::GetOptions() const { + return &m_options; +} + +KMX_Environment *KMX_ProcessEvent::GetEnvironment() { + return &m_environment; +} + +KMX_Environment const *KMX_ProcessEvent::GetEnvironment() const { + return &m_environment; +} + +LPINTKEYBOARDINFO KMX_ProcessEvent::GetKeyboard() { + return &m_keyboard; +} + + +KMX_BOOL KMX_ProcessEvent::ReleaseKeyboardMemory(LPKEYBOARD kbd) +{ + if (!kbd) return TRUE; + delete kbd; + return TRUE; +} + +PKMX_WCHAR KMX_ProcessEvent::GetSystemStore(LPKEYBOARD kb, KMX_DWORD SystemID) +{ + for (KMX_DWORD i = 0; i < kb->cxStoreArray; i++) + if (kb->dpStoreArray[i].dwSystemID == SystemID) return kb->dpStoreArray[i].dpString; + + return NULL; +} diff --git a/common/core/desktop/src/kmx/kmx_processor.h b/common/core/desktop/src/kmx/kmx_processevent.h similarity index 98% rename from common/core/desktop/src/kmx/kmx_processor.h rename to common/core/desktop/src/kmx/kmx_processevent.h index 22ca170e84..029d5508ee 100644 --- a/common/core/desktop/src/kmx/kmx_processor.h +++ b/common/core/desktop/src/kmx/kmx_processevent.h @@ -24,7 +24,7 @@ namespace kmx { #define GLOBAL_ContextStackSize 80 -class KMX_Processor { +class KMX_ProcessEvent { private: PKMX_WORD m_indexStack; PKMX_WCHAR m_miniContext; @@ -78,8 +78,8 @@ private: KMX_BOOL IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift); public: - KMX_Processor(); - ~KMX_Processor(); + KMX_ProcessEvent(); + ~KMX_ProcessEvent(); KMX_BOOL Load(km_kbp_path_name keyboardName); KMX_BOOL ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWORD modifiers); // returns FALSE on error or key not matched diff --git a/common/core/desktop/src/kmx/kmx_processor.cpp b/common/core/desktop/src/kmx/kmx_processor.cpp index 01dfbc6e59..14bc4b2756 100644 --- a/common/core/desktop/src/kmx/kmx_processor.cpp +++ b/common/core/desktop/src/kmx/kmx_processor.cpp @@ -1,682 +1,189 @@ -/* - Copyright: Copyright (C) 2003-2018 SIL International. - Authors: mcdurdin -*/ -#include "kmx_processor.h" +#include #include "state.hpp" +#include "kmx/kmx_processor.hpp" -using namespace km::kbp; -using namespace kmx; - -/* Globals */ - -KMX_BOOL km::kbp::kmx::g_debug_ToConsole = FALSE; -KMX_BOOL km::kbp::kmx::g_debug_KeymanLog = TRUE; -KMX_BOOL km::kbp::kmx::g_silent = FALSE; - -/* -* KMX_Processor -*/ - -KMX_Processor::KMX_Processor() : m_actions(&m_context), m_options(&m_keyboard) { - m_indexStack = new KMX_WORD[GLOBAL_ContextStackSize]; - m_miniContext = new KMX_WCHAR[GLOBAL_ContextStackSize]; - m_miniContextIfLen = 0; -} - -KMX_Processor::~KMX_Processor() { - delete[] m_indexStack; - delete[] m_miniContext; -} - -char VKeyToChar(KMX_UINT modifiers, KMX_UINT vk) { - // We only map SHIFT and UNSHIFTED, and CAPS LOCK - - if ((modifiers & ~(K_SHIFTFLAG | CAPITALFLAG)) != 0) { - return 0; - } - - bool - shifted = (modifiers & K_SHIFTFLAG) == K_SHIFTFLAG, - caps = (modifiers & CAPITALFLAG) == CAPITALFLAG; - - if (vk == KM_KBP_VKEY_SPACE) { - // Override for space because it is the same for - // shifted and unshifted. - return 32; - } - - for (int i = 0; s_char_to_vkey[i].vk; i++) { - if (s_char_to_vkey[i].caps && caps) { - if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == !shifted) return i + 32; - } - else { - if (s_char_to_vkey[i].vk == vk && s_char_to_vkey[i].shifted == shifted) return i + 32; - } - } - return 0; -} - -/* -* KMX_BOOL ProcessEvent(); -* -* Parameters: none -* -* Returns: TRUE if keystroke should be eaten -* -* Called by: FilterFunc -* -* ProcessEvent organizes the messages and gives them to the appropriate routines to -* process, and checks the state of Windows for the keyboard handling. -*/ - -KMX_BOOL KMX_Processor::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWORD modifiers) -{ - LPKEYBOARD kbd = m_keyboard.Keyboard; - - m_kbp_state = state; - - if (m_environment.capsLock()) - modifiers |= CAPITALFLAG; - - m_state.vkey = vkey; - m_state.charCode = VKeyToChar(modifiers, vkey); - m_modifiers = modifiers; - m_state.LoopTimes = 0; - - if (kbd->StartGroup[BEGIN_UNICODE] == (KMX_DWORD) -1) { - DebugLog("Non-Unicode keyboards are not supported."); - return FALSE; - } - - LPGROUP gp = &kbd->dpGroupArray[kbd->StartGroup[BEGIN_UNICODE]]; - - KMX_BOOL fOutputKeystroke = FALSE; - - ProcessGroup(gp, &fOutputKeystroke); - - m_kbp_state = nullptr; - - return !fOutputKeystroke; -} - -/* -* PRIVATE KMX_BOOL ProcessGroup(LPGROUP gp); -* -* Parameters: gp Pointer to group to process inside -* -* Returns: TRUE if messages are to be sent, -* and FALSE if no messages are to be sent. -* -* Called by: ProcessEvent, recursive inside groups -* -* ProcessKey is where the keystroke conversion and output takes place. This routine -* has a lot of crucial code in it! -*/ - -KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) -{ - KMX_DWORD i; - LPKEY kkp = NULL; - PKMX_WCHAR p; - int sdmfI; - - /* - If the number of nested groups goes higher than 50, then break out - this is - a limitation of stack size. This is basically a catch-all for freaky apps that - cause message loopbacks and nasty things like that. Okay, it's really a catch all - for bugs! This means the user's system shouldn't hang. - */ - - LPKEYBOARD kbd = m_keyboard.Keyboard; - - sdmfI = -1; - - for(i = 0; i < kbd->cxGroupArray; i++) - if(gp == &kbd->dpGroupArray[i]) - { - sdmfI = i; - break; - } - - if(++m_state.LoopTimes > 50) +namespace km { + namespace kbp { - DebugLog("Aborting output: m_state.LoopTimes exceeded."); - m_state.StopOutput = TRUE; - return FALSE; - } - /* - The rule matching loop. + km_kbp_status kmx_processor::validate() const { + return _valid ? KM_KBP_STATUS_OK : KM_KBP_STATUS_INVALID_KEYBOARD; + } - This loop iterates through all the rules in the group that is currently being - processed. Each rule in a group can be of three different types: - 1. A virtual key rule, where the key to be matched is a virtual key - 2. A normal key rule (WM_CHAR), where the key to be matched is an Ascii char. - 3. A rule in a keyless group, where only the context is matched. - - The loop goes through and checks the rules like that. This loop could be optimized - with standard searching techniques - the ContextMatch may be difficult. - */ - - DebugLog("m_state.vkey: %s shiftFlags: %x; charCode: %X", Debug_VirtualKey(m_state.vkey), m_modifiers, m_state.charCode); // I4582 - DebugLog("m_context: %s", Debug_UnicodeString(m_context.GetFullContext())); - - if(gp) - { - for(kkp = gp->dpKeyArray, i=0; i < gp->cxKeyArray; i++, kkp++) + kmx_processor::kmx_processor(kbp::path p) { - if(!ContextMatch(kkp)) continue; - if(!gp->fUsingKeys) + p.replace_extension(".kmx"); + _valid = bool(_kmx.Load(p.c_str())); + + if (!_valid) + return; + + keyboard_attributes::options_store defaults; + _kmx.GetOptions()->Init(defaults); + + for (auto const & opt: defaults) { - if(kkp->dpContext[0] != 0) break; else continue; + if (!opt.empty() && opt.scope == KM_KBP_OPT_KEYBOARD ) + persisted_store()[opt.key] = opt.value; } + // Fill out attributes + auto v = _kmx.GetKeyboard()->Keyboard->version; + auto vs = std::to_string(v >> 16) + "." + std::to_string(v & 0xffff); - //if(kkp->Key == m_state.vkey) - //SendDebugMessageFormat(m_state.msg.hwnd, sdmKeyboard, 0, "kkp->Key: %d kkp->ShiftFlags: %x", - // kkp->Key, kkp->ShiftFlags); + _attributes = keyboard_attributes(static_cast(p.stem()), + std::u16string(vs.begin(), vs.end()), p.parent(), defaults); + } - /* Keyman 6.0: support Virtual Characters */ - if(IsEquivalentShift(kkp->ShiftFlags, m_modifiers)) + + char16_t const * kmx_processor::lookup_option(km_kbp_option_scope scope, std::u16string const & key) const + { + char16_t const * pValue = nullptr; + switch(scope) { - if(kkp->Key > VK__MAX && kkp->Key == m_state.vkey) break; // I3438 // I4582 - else if(kkp->Key == m_state.vkey) break; // I4169 + case KM_KBP_OPT_KEYBOARD: + pValue = _kmx.GetOptions()->LookUp(key); + break; + case KM_KBP_OPT_ENVIRONMENT: + pValue = _kmx.GetEnvironment()->LookUp(key); + break; + default: + break; } - else if(kkp->ShiftFlags == 0 && kkp->Key == m_state.charCode && m_state.charCode != 0) break; + + return pValue ? pValue : nullptr; } - } - if(!gp || i == gp->cxKeyArray) - { - /* - No rule was found that corresponded to the current state of the context and - keyboard. NoMatch should be checked for everything except virtual keys; and - context should also be kept. - - If the message was a virtual key, then just return without checking NoMatch. - NoMatch shouldn't be used for virtual keys because it will mean that no key - can ever get through that isn't matched - including arrows, func. keys, etc !! - Context is not kept for virtual keys being output. - */ - - DebugLog("No match was found in group %d of %d", sdmfI, m_keyboard.Keyboard->cxGroupArray); - - if(!gp || (m_state.charCode == 0 && gp->fUsingKeys)) // I4585 - // 7.0.241.0: I1133 - Fix mismatched parentheses on m_state.charCode - ie. we don't want to output this letter if !gp->fUsingKeys + option kmx_processor::update_option(km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) { - KMX_BOOL fIsBackspace = m_state.vkey == KM_KBP_VKEY_BKSP && (m_modifiers & (LCTRLFLAG|RCTRLFLAG|LALTFLAG|RALTFLAG)) == 0; // I4128 - - if(fIsBackspace) { // I4838 // I4933 - PKMX_WCHAR pdeletecontext = m_context.Buf(1); // I4933 - if(!pdeletecontext || *pdeletecontext == 0) { // I4933 - m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); - *pOutputKeystroke = TRUE; // I4933 - return FALSE; // I4933 - } - m_actions.QueueAction(QIT_BACK, BK_BACKSPACE); // I4933 - } else { // I4024 // I4128 // I4287 // I4290 - DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128 - m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); - *pOutputKeystroke = TRUE; - return FALSE; + switch(scope) { + case KM_KBP_OPT_KEYBOARD: + _kmx.GetOptions()->Set(key, value); + persisted_store()[key] = value; + break; + case KM_KBP_OPT_ENVIRONMENT: + _kmx.GetEnvironment()->Load(key, value); + break; + default: + return option(); + break; } - } - else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0) - { - /* NoMatch rule found, and is a character key */ - PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); - } - else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys) - { - /* No rule found, is a character key */ - m_actions.QueueAction(QIT_CHAR, m_state.charCode); + + return option(scope, key, value); } - return TRUE; - } + km_kbp_status kmx_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { + // Construct a context buffer from the items - DebugLog("match found in rule %d", i); - - /* - Save the context that will be used for output when the 'context' keyword is used. - For each deadkey, we need to add 2 characters; look in related stores as well... - */ - - assert(kkp != NULL); - - m_miniContextIfLen = xstrlen(kkp->dpContext) - xstrlen_ignoreifopt(kkp->dpContext); - - // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support - if(*kkp->dpContext == UC_SENTINEL && *(kkp->dpContext+1) == CODE_NUL) - u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)-1) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 - else - u16cpy(m_miniContext, /*GLOBAL_ContextStackSize,*/ m_context.Buf(xstrlen_ignoreifopt(kkp->dpContext)) /*, GLOBAL_ContextStackSize*/); // I3162 // I3536 - - m_miniContext[GLOBAL_ContextStackSize-1] = 0; - - /* - The next section includes several optimizations that make the code a little harder - to read, but are probably worth it in the time that they save. - - If the output string doesn't have a "context" byte at the start, post backspaces - to erase the appropriate number of characters in the application. If it does have - a "context" byte at the start, then the string won't change, and no backspaces are - necessary. You could go one step further with this optimization, in PostAllKeys, - by comparing the starts of the strings to see what is same, and not backspacing - that, but it is probably not necessary. - */ - - p = kkp->dpOutput; - if(*p != UC_SENTINEL || *(p+1) != CODE_CONTEXT) - { - for(p = decxstr((KMX_WCHAR *) u16chr(m_miniContext, 0), m_miniContext); p != NULL; p = decxstr(p, m_miniContext)) - { - if(*p == UC_SENTINEL) - switch(*(p+1)) - { - case CODE_DEADKEY: m_actions.QueueAction(QIT_BACK, BK_DEADKEY); break; - case CODE_NUL: break; // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support - } - else - { - m_actions.QueueAction(QIT_BACK, 0); - } - } - p = kkp->dpOutput; - } - else - p = incxstr(p); // otherwise, the "context" entry has to be jumped over - - /* Use PostString to post the rest of the output string. */ - - if(PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches) - { - if(gp->dpMatch && *gp->dpMatch) - { - PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); - } - } - - return TRUE; -} - -/* -* int PostString( PKMX_CHAR str, KMX_BOOL *useMode, LPMSG mp, -* LPKEYBOARD lpkb ); -* -* Parameters: str Pointer to string to send -* useMode Pointer to KMX_BOOL about whether a "use" command was found -* mp Pointer to MSG structure to copy in outputting messages -* lpkb Pointer to global keyboard structure -* -* Returns: 0 to continue, 1 and 2 to return. -* -* Called by: ProcessKey -* -* PostString posts a string of "context", "index", "beep", characters and virtual keys -* to the active application, via the Keyman PostKey buffer. -*/ - -int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr, KMX_BOOL *pOutputKeystroke) -{ - PKMX_WCHAR p, q, temp; - LPSTORE s; - int n1, n2; - int i, n, shift; - KMX_BOOL FoundUse = FALSE; - - // TODO: Refactor to use incxstr - for(p = str; *p && (p < endstr || !endstr); p++) - { - if(*p == UC_SENTINEL) - switch(*(++p)) - { - case CODE_EXTENDED: // Start of a virtual key section w/shift codes - p++; - - shift = *p; //(*p<<8) | *(p+1); - m_actions.QueueAction(QIT_VSHIFTDOWN, shift); - - p++; - - m_actions.QueueAction(QIT_VKEYDOWN, *p); - m_actions.QueueAction(QIT_VKEYUP, *p); - - m_actions.QueueAction(QIT_VSHIFTUP, shift); - - p++; // CODE_EXTENDEDEND - ////// CODE_EXTENDEDEND will be incremented by loop - - break; - - case CODE_DEADKEY: // A deadkey to be output - p++; - m_actions.QueueAction(QIT_DEADKEY, *p); - break; - case CODE_BEEP: // Sound an 'iconasterisk' beep - m_actions.QueueAction(QIT_BELL, 0); - break; - case CODE_CONTEXT: // copy the context to the output - { - KMX_BOOL ignoreOutputKeystroke; - PostString(m_miniContext, lpkb, (PKMX_WCHAR) u16chr(m_miniContext, 0), &ignoreOutputKeystroke); - break; - } - case CODE_CONTEXTEX: - p++; - for (q = m_miniContext, i = m_miniContextIfLen; *q && i < *p - 1; i++, q = incxstr(q)); - if (*q) { - KMX_BOOL ignoreOutputKeystroke; - temp = incxstr(q); - PostString(q, lpkb, temp, &ignoreOutputKeystroke); - } - break; - case CODE_RETURN: // stop processing and start PostAllKeys - m_state.StopOutput = TRUE; - return psrPostMessages; - - case CODE_CALL: - p++; - DebugLog("CallDLL not supported [store=%d].\n", *p-1); - FoundUse = TRUE; - break; - case CODE_USE: // use another group - p++; - ProcessGroup(&lpkb->dpGroupArray[*p-1], pOutputKeystroke); - if(m_state.StopOutput) return psrPostMessages; - FoundUse = TRUE; - break; - case CODE_CLEARCONTEXT: - // no longer supported, no-op - break; - case CODE_INDEX: - p++; - s = &m_keyboard.Keyboard->dpStoreArray[*p - 1]; - p++; - - n = m_indexStack[*p - 1]; - for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); - PostString(temp, lpkb, incxstr(temp), pOutputKeystroke); - break; - case CODE_SETOPT: - p++; - n1 = *p - 1; - p++; - n2 = *p - 1; - GetOptions()->Set(n1, n2); - break; - case CODE_RESETOPT: - p++; - n1 = *p - 1; - GetOptions()->Reset(m_kbp_state->processor(), n1); - break; - case CODE_SAVEOPT: - p++; - n1 = *p - 1; - GetOptions()->Save(*m_kbp_state, n1); - break; - case CODE_IFSYSTEMSTORE: - p+=3; - break; - case CODE_SETSYSTEMSTORE: - p+=2; - break; - } - else { - if(Uni_IsSurrogate1(*p) && Uni_IsSurrogate2(*(p+1))) { - m_actions.QueueAction(QIT_CHAR, Uni_SurrogateToUTF32(*p, *(p+1))); - p++; - } else { - m_actions.QueueAction(QIT_CHAR, *p); - } - } - } - return FoundUse ? psrPostMessages : psrCheckMatches; -} - - -KMX_BOOL KMX_Processor::IsMatchingBaseLayout(PKMX_WCHAR layoutName) // I3432 -{ - KMX_BOOL bEqual = u16icmp(layoutName, static_cast(m_environment.baseLayout().c_str())) == 0 || // I4583 - u16icmp(layoutName, static_cast(m_environment.baseLayoutAlt().c_str())) == 0; // I4583 - - return bEqual; -} - -KMX_BOOL KMX_Processor::IsMatchingPlatformString(PKMX_WCHAR platform) // I3432 -{ - // TODO retrieve platform string from client environment - // TODO cleanup to use a vector. - PKMX_WCHAR t = new KMX_WCHAR[u16len(m_environment.platform().c_str()) + 1], context = NULL; - u16cpy(t, /*wcslen(s->dpString)+1,*/ m_environment.platform().c_str()); - PKMX_WCHAR p = u16tok(t, u' ', &context); - while (p != NULL) { - if (u16icmp(platform, p) == 0) { - delete [] t; - return TRUE; - } - p = u16tok(NULL, u' ', &context); - } - - delete [] t; - return FALSE; -} - -KMX_BOOL KMX_Processor::IsMatchingPlatform(LPSTORE s) // I3432 -{ - PKMX_WCHAR t = new KMX_WCHAR[u16len(s->dpString)+1]; - u16cpy(t, /*wcslen(s->dpString)+1,*/ s->dpString); - PKMX_WCHAR context = NULL; - PKMX_WCHAR platform = u16tok(t, u' ', &context); - while(platform != NULL) - { - if(!IsMatchingPlatformString(platform)) - { - s->dwSystemID = TSS_PLATFORM_NOMATCH; - delete [] t; - return FALSE; - } - platform = u16tok(NULL, u' ', &context); - } - - s->dwSystemID = TSS_PLATFORM_MATCH; - delete [] t; - return TRUE; -} - -/* -* KMX_BOOL ContextMatch( LPKEY kkp ); -* -* Parameters: kkp Rule to compare -* -* Returns: 0 on OK, 1 on not equal -* -* Called by: ProcessKey -* -* ContextMatch compares the context of a rule with the current context. -*/ - -KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) -{ - KMX_WORD /*i,*/ n; - PKMX_WCHAR p, q, qbuf, temp; - PKMX_WORD indexp; - LPSTORE s, t; - KMX_BOOL bEqual; - - memset(m_indexStack, 0, GLOBAL_ContextStackSize*sizeof(KMX_WORD)); - - p = kkp->dpContext; - - if(*p == 0) - { - return TRUE; - } - - if(*p == UC_SENTINEL && *(p+1) == CODE_NUL) - { - // If context buf is longer than the context, then obviously not start of doc. - if(m_context.Buf(xstrlen_ignoreifopt(p))) return FALSE; - p = incxstr(p); - if(*p == 0) return TRUE; - } - - for(PKMX_WCHAR pp = p; pp && *pp; pp = incxstr(pp)) - { - if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFOPT) - { - s = &m_keyboard.Keyboard->dpStoreArray[(*(pp+2))-1]; - t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; - - bEqual = u16cmp(s->dpString, t->dpString) == 0; - if(*(pp+3) == 1 && bEqual) return FALSE; - if(*(pp+3) == 2 && !bEqual) return FALSE; - } - else if(*pp == UC_SENTINEL && *(pp+1) == CODE_IFSYSTEMSTORE) - { - KMX_DWORD dwSystemID = *(pp+2)-1; - t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; - switch(dwSystemID) - { - case TSS_PLATFORM_MATCH: // Cached platform result - a matching platform - bEqual = TRUE; - break; - case TSS_PLATFORM_NOMATCH: // Cached platform result - not a matching platform - bEqual = FALSE; - break; - case TSS_PLATFORM: - bEqual = IsMatchingPlatform(t); - break; - case TSS_BASELAYOUT: - bEqual = IsMatchingBaseLayout(t->dpString); - break; - default: - { - PKMX_WCHAR ss = GetSystemStore(m_keyboard.Keyboard, dwSystemID); - if(ss == NULL) return FALSE; - bEqual = u16cmp(ss, t->dpString) == 0; + std::u16string ctxt; + auto cp = state->context(); + for (auto c = cp.begin(); c != cp.end(); c++) { + switch (c->type) { + case KM_KBP_CT_CHAR: + if (Uni_IsSMP(c->character)) { + ctxt += Uni_UTF32ToSurrogate1(c->character); + ctxt += Uni_UTF32ToSurrogate2(c->character); + } + else { + ctxt += (km_kbp_cp) c->character; + } + break; + case KM_KBP_CT_MARKER: + assert(c->marker > 0); + ctxt += UC_SENTINEL; + ctxt += CODE_DEADKEY; + ctxt += c->marker; + break; } } - if(*(pp+3) == 1 && bEqual) return FALSE; - if(*(pp+3) == 2 && !bEqual) return FALSE; - } - } + _kmx.GetContext()->Set(ctxt.c_str()); + _kmx.GetActions()->ResetQueue(); + state->actions().clear(); - q = qbuf = m_context.Buf(xstrlen_ignoreifopt(p)); - if(!q) - { - return FALSE; // context buf is too short! - } - indexp = m_indexStack; - - for(; *p && *q; p = incxstr(p)) - { - *indexp = 0; - - if(*p == UC_SENTINEL) - { - switch(*(p+1)) - { - case CODE_DEADKEY: - if(*q != UC_SENTINEL || *(q+1) != CODE_DEADKEY || *(q+2) != *(p+2)) - { - return FALSE; - } - break; - case CODE_ANY: - s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; - - temp = xstrchr(s->dpString, q); - - if(temp != NULL) - *indexp = (KMX_WORD) xstrpos(temp, s->dpString); - - else - return FALSE; - break; - case CODE_NOTANY: - s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; - - if((temp = xstrchr(s->dpString, q)) != NULL) - return FALSE; - - break; - case CODE_INDEX: - s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; - *indexp = n = m_indexStack[(*(p+3))-1]; - - for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); - if(n != 0) return FALSE; - if(xchrcmp(temp, q) != 0) return FALSE; - break; - case CODE_CONTEXTEX: - // only the nth character - for(n = *(p+2) - 1, temp = qbuf; temp < q && n > 0; n--, temp = incxstr(temp)); - if(n == 0) - if(xchrcmp(temp, q) != 0) return FALSE; - break; - case CODE_IFOPT: - case CODE_IFSYSTEMSTORE: - indexp++; - continue; // don't increment q - default: - return FALSE; + if (!_kmx.ProcessEvent(state, vk, modifier_state)) { + // We need to output the default keystroke + state->actions().push_emit_keystroke(); } + + for (auto i = 0; i < _kmx.GetActions()->Length(); i++) { + auto a = _kmx.GetActions()->Get(i); + switch (a.ItemType) { + case QIT_CAPSLOCK: + //TODO: add Caps Event + //dwData = 0 == off; 1 == on + //state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CAPSLOCK, {0,}, {0} }); + break; + case QIT_VKEYDOWN: + case QIT_VKEYUP: + case QIT_VSHIFTDOWN: + case QIT_VSHIFTUP: + //TODO: eliminate?? + break; + case QIT_CHAR: + state->context().push_character(a.dwData); + state->actions().push_character(a.dwData); + break; + case QIT_DEADKEY: + state->context().push_marker(a.dwData); + state->actions().push_marker(a.dwData); + break; + case QIT_BELL: + state->actions().push_alert(); + break; + case QIT_BACK: + switch (a.dwData) { + case BK_DEFAULT: + // This only happens if we know we have context to delete. Last item must be a character + assert(!state->context().empty() && state->context().back().type != KM_KBP_IT_MARKER); + if (!state->context().empty()) state->context().pop_back(); + state->actions().push_backspace(); + break; + case BK_DEADKEY: + // This only happens if we know we have context to delete. Last item must be a deadkey + assert(!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER); + if (!state->context().empty()) state->context().pop_back(); + break; + case BK_BACKSPACE: + // User-initiated backspace. We need to delete deadkeys from context, both sides of the character deleted + while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); + if (!state->context().empty()) { + state->context().pop_back(); + while (!state->context().empty() && state->context().back().type == KM_KBP_IT_MARKER) state->context().pop_back(); + } + // Even if context is empty, we send the backspace event, because we may not + // know the context. + state->actions().push_backspace(); + break; + default: + assert(false); + } + break; + case QIT_INVALIDATECONTEXT: + state->actions().push_invalidate_context(); + break; + default: + //std::cout << "Unexpected item type " << a.ItemType << ", " << a.dwData << std::endl; + assert(false); + } + } + + state->actions().commit(); + + return KM_KBP_STATUS_OK; } - else if(xchrcmp(p, q) != 0) - { - return FALSE; + + constexpr km_kbp_attr const engine_attrs = { + 256, + KM_KBP_LIB_CURRENT, + KM_KBP_LIB_AGE, + KM_KBP_LIB_REVISION, + KM_KBP_TECH_KMX, + "SIL International" + }; + + km_kbp_attr const & kmx_processor::attributes() const { + return engine_attrs; } - indexp++; - q = incxstr(q); - } - while(*p == UC_SENTINEL && (*(p+1) == CODE_IFOPT || *(p+1) == CODE_IFSYSTEMSTORE)) p = incxstr(p); // already tested - - return *p == *q; /*at least one must ==0 at this point*/ -} - -KMX_Actions *KMX_Processor::GetActions() { - return &m_actions; -} - -KMX_Context *KMX_Processor::GetContext() { - return &m_context; -} - -KMX_Options *KMX_Processor::GetOptions() { - return &m_options; -} - -KMX_Options const *KMX_Processor::GetOptions() const { - return &m_options; -} - -KMX_Environment *KMX_Processor::GetEnvironment() { - return &m_environment; -} - -KMX_Environment const *KMX_Processor::GetEnvironment() const { - return &m_environment; -} - -LPINTKEYBOARDINFO KMX_Processor::GetKeyboard() { - return &m_keyboard; -} - - -KMX_BOOL KMX_Processor::ReleaseKeyboardMemory(LPKEYBOARD kbd) -{ - if (!kbd) return TRUE; - delete kbd; - return TRUE; -} - -PKMX_WCHAR KMX_Processor::GetSystemStore(LPKEYBOARD kb, KMX_DWORD SystemID) -{ - for (KMX_DWORD i = 0; i < kb->cxStoreArray; i++) - if (kb->dpStoreArray[i].dwSystemID == SystemID) return kb->dpStoreArray[i].dpString; - - return NULL; -} + } // namespace kbp +} // namespace km diff --git a/common/core/desktop/src/kmx/kmx_processevent.hpp b/common/core/desktop/src/kmx/kmx_processor.hpp similarity index 94% rename from common/core/desktop/src/kmx/kmx_processevent.hpp rename to common/core/desktop/src/kmx/kmx_processor.hpp index 29ba66e321..4c71db4922 100644 --- a/common/core/desktop/src/kmx/kmx_processevent.hpp +++ b/common/core/desktop/src/kmx/kmx_processor.hpp @@ -10,7 +10,7 @@ #include #include -#include "kmx/kmx_processor.h" +#include "kmx/kmx_processevent.h" #include "keyboard.hpp" #include "processor.hpp" @@ -21,7 +21,7 @@ namespace kbp { private: bool _valid; - kmx::KMX_Processor _kmx; + kmx::KMX_ProcessEvent _kmx; public: kmx_processor(path); km_kbp_status process_event(km_kbp_state *state, diff --git a/common/core/desktop/src/kmx/kmx_xstring.cpp b/common/core/desktop/src/kmx/kmx_xstring.cpp index 87267278a9..47cb7d5d50 100644 --- a/common/core/desktop/src/kmx/kmx_xstring.cpp +++ b/common/core/desktop/src/kmx/kmx_xstring.cpp @@ -6,7 +6,7 @@ #include #include #include -#include "kmx_processor.h" +#include "kmx_processevent.h" #include "utfcodec.hpp" diff --git a/common/core/desktop/tests/unit/kmx/kmx.cpp b/common/core/desktop/tests/unit/kmx/kmx.cpp index 2f4ec39945..f2f1b3b6ee 100644 --- a/common/core/desktop/tests/unit/kmx/kmx.cpp +++ b/common/core/desktop/tests/unit/kmx/kmx.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include "path.hpp" #include "state.hpp"