diff --git a/common/core/desktop/include/keyman/keyboardprocessor_debug.h b/common/core/desktop/include/keyman/keyboardprocessor_debug.h index e27b049a27..2b434b93f5 100644 --- a/common/core/desktop/include/keyman/keyboardprocessor_debug.h +++ b/common/core/desktop/include/keyman/keyboardprocessor_debug.h @@ -25,6 +25,21 @@ */ #define DEBUG_MAX_CONTEXT 80 +/** + * The number of stores that can be processed in a rule. This is taken from + * MAXSTOREOFFSETS in keyman32 (Windows) and is purely a convenience value. + * We can increase it if there is a demonstrated need. + */ +#define DEBUG_MAX_STORE_OFFSETS 20 +#define DEBUG_STORE_OFFSETS_SIZE (DEBUG_MAX_STORE_OFFSETS*2+1) + +/** + * These modifier flags are used internally in the kmx engine, so will be + * exposed only in debugging modifier states. + */ +#define KM_KBP_MODIFIER_VIRTUALKEY 0x4000 +#define KM_KBP_MODIFIER_VIRTUALCHARKEY 0x8000 + /** * Input key event data. The `character` member is derived from * a US English key event for vk + modifier_state, and is 0 if @@ -48,11 +63,12 @@ typedef struct { * * Used in all event types except KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_END. */ + typedef struct { km_kbp_cp context[DEBUG_MAX_CONTEXT]; void *group; // LPGROUP - void *store; // LPSTORE void *rule; // LPKEY + uint16_t store_offsets[DEBUG_STORE_OFFSETS_SIZE]; // pairs--store, char position, terminated by 0xFFFF // TODO use a better structure here } km_kbp_state_debug_kmx_info; /** @@ -61,10 +77,8 @@ typedef struct { typedef struct { uint32_t type; // 32 bits is better optimized than 8 bits uint32_t flags; - union { - km_kbp_state_debug_key_info key_info; - km_kbp_state_debug_kmx_info kmx_info; - }; + km_kbp_state_debug_key_info key_info; + km_kbp_state_debug_kmx_info kmx_info; } km_kbp_state_debug_item; /** diff --git a/common/core/desktop/src/kmx/kmx_debugger.cpp b/common/core/desktop/src/kmx/kmx_debugger.cpp index 6ef412784d..266fe81715 100644 --- a/common/core/desktop/src/kmx/kmx_debugger.cpp +++ b/common/core/desktop/src/kmx/kmx_debugger.cpp @@ -11,12 +11,56 @@ using namespace km::kbp; using namespace kmx; -void KMX_DebugItems::push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context) { +void KMX_DebugItems::push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context, PKMX_WORD index_stack) { _items->assert_push_entry(); km_kbp_state_debug_item item = {type, flags}; item.kmx_info.rule = key; - u16ncpy(item.kmx_info.context, context, DEBUG_MAX_CONTEXT - 1); + if(item.kmx_info.rule && index_stack) { + this->fill_store_offsets(&item.kmx_info, index_stack); + } + + if(context != nullptr) { + u16ncpy(item.kmx_info.context, context, DEBUG_MAX_CONTEXT - 1); + } item.kmx_info.context[DEBUG_MAX_CONTEXT-1] = 0; item.kmx_info.group = group; _items->emplace_back(item); } + +void KMX_DebugItems::fill_store_offsets(km_kbp_state_debug_kmx_info *info, PKMX_WORD index_stack) { + + int i, n; + + km_kbp_cp *p; + + // TODO turn this into a struct rather than interwoven values + for(i = n = 0, p = static_cast(info->rule)->dpContext; p && *p; p = incxstr(p), i++) { + if(*p == UC_SENTINEL && (*(p+1) == CODE_ANY || *(p+1) == CODE_NOTANY)) { + info->store_offsets[n++] = *(p+2) - 1; + info->store_offsets[n++] = index_stack[i]; + } + if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX) { + info->store_offsets[n++] = *(p+2) - 1; + info->store_offsets[n++] = index_stack[*(p+3) - 1]; + } + if(n == DEBUG_MAX_STORE_OFFSETS*2) { + break; + } + } + + // TODO split this into a separate variable + // TODO turn this into a struct rather than interwoven values + if(n < DEBUG_MAX_STORE_OFFSETS*2 - 1) { + for(p = static_cast(info->rule)->dpOutput; *p; p = incxstr(p)) { + if(*p == UC_SENTINEL && *(p+1) == CODE_INDEX) { + info->store_offsets[n++] = *(p+2) - 1; + info->store_offsets[n++] = index_stack[*(p+3) - 1]; + } + if(n == DEBUG_MAX_STORE_OFFSETS*2) { + break; + } + } + } + + info->store_offsets[n] = 0xFFFF; +} \ No newline at end of file diff --git a/common/core/desktop/src/kmx/kmx_debugger.h b/common/core/desktop/src/kmx/kmx_debugger.h index 5b2701f7fe..daddb3463a 100644 --- a/common/core/desktop/src/kmx/kmx_debugger.h +++ b/common/core/desktop/src/kmx/kmx_debugger.h @@ -21,7 +21,18 @@ class KMX_DebugItems { private: debug_items *_items; - void push_item(uint8_t type, uint32_t flags, LPGROUP group, LPKEY key, PKMX_WCHAR context); + void push_item( + uint8_t type, + uint32_t flags = 0, + LPGROUP group = nullptr, + LPKEY key = nullptr, + PKMX_WCHAR context = nullptr, + PKMX_WORD index_stack = nullptr + ); + void fill_store_offsets( + km_kbp_state_debug_kmx_info *info, + PKMX_WORD index_stack + ); public: KMX_DebugItems(debug_items *items); void push_begin(km_kbp_state_debug_key_info *key_info, uint32_t flags); @@ -32,8 +43,18 @@ public: void push_nomatch_exit(LPGROUP group); void push_match_enter(LPGROUP group); void push_match_exit(LPGROUP group); - void push_rule_enter(LPGROUP group, LPKEY key, PKMX_WCHAR context); - void push_rule_exit(LPGROUP group, LPKEY key, PKMX_WCHAR context); + void push_rule_enter( + LPGROUP group, + LPKEY key, + PKMX_WCHAR context, + PKMX_WORD index_stack + ); + void push_rule_exit( + LPGROUP group, + LPKEY key, + PKMX_WCHAR context, + PKMX_WORD index_stack + ); }; inline @@ -60,42 +81,52 @@ KMX_DebugItems::push_end(uint32_t flags) { inline void KMX_DebugItems::push_group_enter(LPGROUP group) { - push_item(KM_KBP_DEBUG_GROUP_ENTER, 0, group, NULL, NULL); + push_item(KM_KBP_DEBUG_GROUP_ENTER, 0, group); } inline void KMX_DebugItems::push_group_exit(LPGROUP group, uint32_t flags) { - push_item(KM_KBP_DEBUG_GROUP_EXIT, flags, group, NULL, NULL); + push_item(KM_KBP_DEBUG_GROUP_EXIT, flags, group); } inline void KMX_DebugItems::push_nomatch_enter(LPGROUP group) { - push_item(KM_KBP_DEBUG_NOMATCH_ENTER, 0, group, NULL, NULL); + push_item(KM_KBP_DEBUG_NOMATCH_ENTER, 0, group); } inline void KMX_DebugItems::push_nomatch_exit(LPGROUP group) { - push_item(KM_KBP_DEBUG_NOMATCH_EXIT, 0, group, NULL, NULL); + push_item(KM_KBP_DEBUG_NOMATCH_EXIT, 0, group); } inline void KMX_DebugItems::push_match_enter(LPGROUP group) { - push_item(KM_KBP_DEBUG_MATCH_ENTER, 0, group, NULL, NULL); + push_item(KM_KBP_DEBUG_MATCH_ENTER, 0, group); } inline void KMX_DebugItems::push_match_exit(LPGROUP group) { - push_item(KM_KBP_DEBUG_MATCH_EXIT, 0, group, NULL, NULL); + push_item(KM_KBP_DEBUG_MATCH_EXIT, 0, group); } inline void -KMX_DebugItems::push_rule_enter(LPGROUP group, LPKEY key, PKMX_WCHAR context) { - push_item(KM_KBP_DEBUG_RULE_ENTER, 0, group, key, context); +KMX_DebugItems::push_rule_enter( + LPGROUP group, + LPKEY key, + PKMX_WCHAR context, + PKMX_WORD index_stack +) { + push_item(KM_KBP_DEBUG_RULE_ENTER, 0, group, key, context, index_stack); } inline void -KMX_DebugItems::push_rule_exit(LPGROUP group, LPKEY key, PKMX_WCHAR context) { - push_item(KM_KBP_DEBUG_RULE_EXIT, 0, group, key, context); +KMX_DebugItems::push_rule_exit( + LPGROUP group, + LPKEY key, + PKMX_WCHAR context, + PKMX_WORD index_stack +) { + push_item(KM_KBP_DEBUG_RULE_EXIT, 0, group, key, context, index_stack); } } // namespace kmx diff --git a/common/core/desktop/src/kmx/kmx_processor.cpp b/common/core/desktop/src/kmx/kmx_processor.cpp index eec62cb805..cca2aab1a8 100644 --- a/common/core/desktop/src/kmx/kmx_processor.cpp +++ b/common/core/desktop/src/kmx/kmx_processor.cpp @@ -121,6 +121,8 @@ KMX_BOOL KMX_Processor::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWO if(m_debug_items) { m_debug_items->push_end(fOutputKeystroke ? KM_KBP_DEBUG_FLAG_OUTPUTKEYSTROKE : 0); + // m_debug_items is just a helper class that pushes to state->debug_items(), + // so we can throw it away after we are done with it delete m_debug_items; m_debug_items = nullptr; } @@ -151,6 +153,10 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) PKMX_WCHAR p; int sdmfI; + if(m_debug_items) { + m_debug_items->push_group_enter(gp); + } + /* 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 @@ -173,6 +179,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) { DebugLog("Aborting output: m_state.LoopTimes exceeded."); m_state.StopOutput = TRUE; + if(m_debug_items) { + m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_RECURSIVE_OVERFLOW); + } return FALSE; } @@ -240,6 +249,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) PKMX_WCHAR pdeletecontext = m_context.Buf(1); // I4933 if(!pdeletecontext || *pdeletecontext == 0) { // I4933 m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + if(m_debug_items) { + m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH); + } *pOutputKeystroke = TRUE; // I4933 return FALSE; // I4933 } @@ -247,6 +259,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) } else { // I4024 // I4128 // I4287 // I4290 DebugLog(" ... IsLegacy = FALSE; IsTIP = TRUE"); // I4128 m_actions.QueueAction(QIT_INVALIDATECONTEXT, 0); + if(m_debug_items) { + m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH); + } *pOutputKeystroke = TRUE; return FALSE; } @@ -254,7 +269,13 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0) { /* NoMatch rule found, and is a character key */ + if(m_debug_items) { + m_debug_items->push_nomatch_enter(gp); + } PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + if(m_debug_items) { + m_debug_items->push_nomatch_exit(gp); + } } else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys) { @@ -262,6 +283,9 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) m_actions.QueueAction(QIT_CHAR, m_state.charCode); } + if(m_debug_items) { + m_debug_items->push_group_exit(gp, KM_KBP_DEBUG_FLAG_NOMATCH); + } return TRUE; } @@ -284,6 +308,10 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) m_miniContext[GLOBAL_ContextStackSize-1] = 0; + if(m_debug_items) { + m_debug_items->push_rule_enter(gp, kkp, m_miniContext, m_indexStack); + } + /* 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. @@ -319,12 +347,24 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) /* 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); + KMX_BOOL shouldProcessNomatch = PostString(p, m_keyboard.Keyboard, NULL, pOutputKeystroke) == psrCheckMatches; + + if(m_debug_items) { + m_debug_items->push_rule_exit(gp, kkp, m_miniContext, m_indexStack); + } + + if(shouldProcessNomatch && gp->dpMatch && *gp->dpMatch) { + if(m_debug_items) { + m_debug_items->push_match_enter(gp); } + PostString(gp->dpMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); + if(m_debug_items) { + m_debug_items->push_match_exit(gp); + } + } + + if(m_debug_items) { + m_debug_items->push_group_exit(gp, 0); } return TRUE; diff --git a/common/core/desktop/tests/unit/kmnkbd/debug_api.cpp b/common/core/desktop/tests/unit/kmnkbd/debug_api.cpp index 59f0be6855..8f4297b432 100644 --- a/common/core/desktop/tests/unit/kmnkbd/debug_api.cpp +++ b/common/core/desktop/tests/unit/kmnkbd/debug_api.cpp @@ -7,43 +7,126 @@ #include #include #include +#include +#include #include #include #include "path.hpp" #include "state.hpp" #include "../test_assert.h" +#include "kmx/kmx_base.h" +#include "kmx/kmx_xstring.h" + +using namespace km::kbp::kmx; namespace { -// https://stackoverflow.com/a/29865/1836776 -void hexdump(void const *ptr, int buflen) { - unsigned char *buf = (unsigned char*)ptr; - int i, j; - for (i=0; i(item.kmx_info.group); + LPKEY rule = static_cast(item.kmx_info.rule); + + std::cout + << "debug_item " << title << std::endl + << " type: " << debug_item_types[item.type] << std::endl + << " flags: " << item.flags << std::endl + << " key_info: (vk: " << item.key_info.vk + << " mod: " << item.key_info.modifier_state + << " char: " << item.key_info.character << ")" << std::endl; + + std::cout + << " kmx_info: "<< std::endl + << " context: '" << item.kmx_info.context << "'" << std::endl; + + if(gp) std::cout + << " group: " << gp->dpName << std::endl; + if(rule) std::cout + << " rule: " << std::endl + << " key: " << rule->Key << std::endl + << " shift: " << rule->ShiftFlags << std::endl + << " line: " << rule->Line << std::endl; + std::cout + << " offsets: "; + std::copy( + std::begin(item.kmx_info.store_offsets), + std::end(item.kmx_info.store_offsets), + std::ostream_iterator(std::cout, " ") + ); + std::cout << std::endl; +} + +bool are_store_offsets_equal(const uint16_t (&lhs)[DEBUG_STORE_OFFSETS_SIZE], const uint16_t (&rhs)[DEBUG_STORE_OFFSETS_SIZE]) { + return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); + // !memcmp(lhs, rhs, sizeof(lhs)); } bool operator==(km_kbp_state_debug_item const & lhs, km_kbp_state_debug_item const & rhs) { - int result = memcmp(&lhs, &rhs, sizeof(km_kbp_state_debug_item)); - if(result != 0) { - hexdump(&lhs, sizeof(km_kbp_state_debug_item)); - hexdump(&rhs, sizeof(km_kbp_state_debug_item)); + auto result = (lhs.type == rhs.type && lhs.flags == rhs.flags); + if(result) { + LPGROUP lgp = static_cast(lhs.kmx_info.group), rgp = static_cast(rhs.kmx_info.group); + LPKEY lrule = static_cast(lhs.kmx_info.rule), rrule = static_cast(rhs.kmx_info.rule); + switch(lhs.type) { + case KM_KBP_DEBUG_BEGIN: + result = lhs.key_info.character == rhs.key_info.character && + lhs.key_info.modifier_state == rhs.key_info.modifier_state && + lhs.key_info.vk == rhs.key_info.vk; + break; + case KM_KBP_DEBUG_END: + break; + case KM_KBP_DEBUG_GROUP_ENTER: + case KM_KBP_DEBUG_GROUP_EXIT: + case KM_KBP_DEBUG_MATCH_ENTER: + case KM_KBP_DEBUG_MATCH_EXIT: + case KM_KBP_DEBUG_NOMATCH_ENTER: + case KM_KBP_DEBUG_NOMATCH_EXIT: + assert(lgp != nullptr); + assert(rgp != nullptr); + assert(lgp->dpName != nullptr); + assert(rgp->dpName != nullptr); + result = !u16cmp(lgp->dpName, rgp->dpName); + break; + case KM_KBP_DEBUG_RULE_ENTER: + case KM_KBP_DEBUG_RULE_EXIT: + assert(lgp != nullptr); + assert(rgp != nullptr); + assert(lgp->dpName != nullptr); + assert(rgp->dpName != nullptr); + assert(lrule != nullptr); + assert(rrule != nullptr); + result = !u16cmp(lgp->dpName, rgp->dpName) && + lrule->Line == rrule->Line && + lrule->Key == rrule->Key && + lrule->ShiftFlags == rrule->ShiftFlags && + u16cmp(lhs.kmx_info.context, rhs.kmx_info.context) == 0 && + are_store_offsets_equal(lhs.kmx_info.store_offsets, rhs.kmx_info.store_offsets); + break; + default: + assert(false); + result = false; + } } - return result == 0; + + if(!result) { + print_debug_item("actual", lhs); + print_debug_item("expected", rhs); + } + return result; } @@ -53,8 +136,18 @@ bool debug_items(km_kbp_state const * state, size_t n = 0; auto act = km_kbp_state_debug_items(state, &n); - for (auto &rhs: expected) + for (auto &rhs: expected) { + if(--n < 0) { + std::cout << "expected longer than actual" << std::endl; + return false; + } if (!(*act++ == rhs)) return false; + } + + if(n != 0) { + std::cout << "actual longer than expected" << std::endl; + return false; + } return true; } @@ -67,15 +160,73 @@ bool debug_items(km_kbp_state const * state, } // namespace +// TODO: consider using same const approach in kmx_base.h +// but this ripples through a whole lot of code +typedef struct tagDEBUG_GROUP +{ + KMX_WCHAR const * dpName; + KEY const * dpKeyArray; // [LPKEY] address of first item in key array + KMX_WCHAR const * dpMatch; + KMX_WCHAR const * dpNoMatch; + KMX_DWORD cxKeyArray; // in array entries + KMX_BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not +} DEBUG_GROUP, *LPDEBUG_GROUP; + +typedef struct tagDEBUG_KEY +{ + KMX_WCHAR Key; + KMX_DWORD Line; + KMX_DWORD ShiftFlags; + KMX_WCHAR const * dpOutput; + KMX_WCHAR const * dpContext; +} DEBUG_KEY, *LPDEBUG_KEY; + + km_kbp_keyboard * test_kb = nullptr; km_kbp_state * test_state = nullptr; km_kbp_context_item * citems = nullptr; +char * arg_path; + +void teardown() { + if(citems) { + km_kbp_context_items_dispose(citems); + citems = nullptr; + } + if(test_state) { + km_kbp_state_dispose(test_state); + test_state = nullptr; + } + if(test_kb) { + km_kbp_keyboard_dispose(test_kb); + test_kb = nullptr; + } +} + +void setup(const char *keyboard) { + teardown(); + + km::kbp::path path = km::kbp::path::join(arg_path, "..", "kmx", keyboard); + + try_status(km_kbp_keyboard_load(path.native().c_str(), &test_kb)); + try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); + try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems)); + + // Pre-test sanity: ensure debugging is disabled + assert(km_kbp_state_debug_get(test_state) == 0); + + // Ensure the pre-run debug item state is not empty + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_END} + })); -void setup() { try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems)); } +/** + * Test 1: Start with debugging disabled + */ void test_debugging_disabled() { + setup("000 - null keyboard.kmx"); try_status(km_kbp_state_debug_set(test_state, 0)); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT)); assert(debug_items(test_state, { @@ -83,24 +234,176 @@ void test_debugging_disabled() { })); } +/** + * Test 2: Debugging enabled, no rule match + */ void test_debugging_no_rule_match() { + setup("000 - null keyboard.kmx"); + DEBUG_GROUP gp = {u"Main"}; try_status(km_kbp_state_debug_set(test_state, 1)); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT)); assert(debug_items(test_state, { - km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {{KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 'S'}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 'S'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}}, km_kbp_state_debug_item{KM_KBP_DEBUG_END} })); } +/** + * Test 3: Debugging enabled, function key pressed, no rule match + */ void test_debugging_function_key() { + setup("000 - null keyboard.kmx"); + DEBUG_GROUP gp = {u"Main"}; try_status(km_kbp_state_debug_set(test_state, 1)); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F1, 0)); assert(debug_items(test_state, { - km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {{KM_KBP_VKEY_F1, 0, 0}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F1, 0, 0}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}}, km_kbp_state_debug_item{KM_KBP_DEBUG_END, KM_KBP_DEBUG_FLAG_OUTPUTKEYSTROKE} })); } +/** + * Test 4: basic rule match + */ +void test_basic_rule_matches() { + setup("002 - basic input Unicode.kmx"); + DEBUG_GROUP gp = {u"Main"}; + DEBUG_KEY kp = { 'F', /*line*/16, /*shift*/0 }; // vkey is a char + try_status(km_kbp_state_debug_set(test_state, 1)); + + // 'DE' + 'F' > U+0E04 U+0E05 U+0E06 + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT, 'D'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a default 'D' + })); + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT, 'E'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a default 'E' + })); + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT, 'F'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"DE", &gp, &kp, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"DE", &gp, &kp, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit rule transform + })); +} + +/** + * Test 5: Multiple groups + */ +void test_multiple_groups() { + setup("030 - multiple groups.kmx"); + DEBUG_GROUP gp = {u"Main"}, gpa = {u"a"}, gpb = {u"b"}; + DEBUG_KEY kp1 = { KM_KBP_VKEY_1, /*line*/12, /*shift*/KM_KBP_MODIFIER_VIRTUALKEY }, + kp2 = { KM_KBP_VKEY_2, /*line*/13, /*shift*/KM_KBP_MODIFIER_VIRTUALKEY }, + kpa = { 0, /*line*/19, /*shift*/0 }, + kpb = { 0, /*line*/23, /*shift*/0 }; + + try_status(km_kbp_state_debug_set(test_state, 1)); + + // '12' -> 'abc' + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_1, 0, '1'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"", &gp, &kp1, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"", &gp, &kp1, {0xFFFF}}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_ENTER, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpa}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"a", &gpa, &kpa, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"a", &gpa, &kpa, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gpa}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_EXIT, 0, {}, {u"", &gp}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a 'b' + })); + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_2, 0, '2'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"b", &gp, &kp2, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpb}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"b", &gpb, &kpb, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"b", &gpb, &kpb, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gpb}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"b", &gp, &kp2, {0xFFFF}}}, + + //See #5440 for why match does not fire here: + //km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_ENTER, 0, {}, {u"", &gp}}, + // km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gpa}}, + // km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, KM_KBP_DEBUG_FLAG_NOMATCH, {}, {u"", &gpa}}, + //km_kbp_state_debug_item{KM_KBP_DEBUG_MATCH_EXIT, 0, {}, {u"", &gp}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will "abc" + })); +} + +/** + * Test 6: store offsets + */ +void test_store_offsets() { + setup("044 - if and context.kmx"); + DEBUG_GROUP gp = {u"Main"}; + DEBUG_KEY kpa = { 'a', /*line*/15, }, + kpb = { 'b', /*line*/19, }; + + try_status(km_kbp_state_debug_set(test_state, 1)); + + // 'ab' -> 'ex' + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_A, 0)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_A, 0, 'a'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"", &gp, &kpa, {0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"", &gp, &kpa, {0xFFFF}}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, // action item will emit a 'exay' + })); + + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_B, 0)); + assert(debug_items(test_state, { + km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_B, 0, 'b'}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, + + // store_offsets: 6 = store #, 1 = index into store, 6 = store #, 0 = index into store + // store: store(diaeresisBase) 'ae' + // context: exay + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_ENTER, 0, {}, {u"exay", &gp, &kpb, {6, 1, 6, 0, 0xFFFF}}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_RULE_EXIT, 0, {}, {u"exay", &gp, &kpb, {6, 1, 6, 0, 0xFFFF}}}, + + km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_EXIT, 0, {}, {u"", &gp}}, + km_kbp_state_debug_item{KM_KBP_DEBUG_END, 0}, + })); +} + + + constexpr const auto help_str = "\ debug_api [--color] \n\ \n\ @@ -129,38 +432,18 @@ int main(int argc, char *argv []) { } console_color::enabled = console_color::isaterminal() || arg_color; - auto arg_path = argv[arg_color ? 2 : 1]; + arg_path = argv[arg_color ? 2 : 1]; - km::kbp::path path = km::kbp::path::join(arg_path, "..", "kmx", "000 - null keyboard.kmx"); - - try_status(km_kbp_keyboard_load(path.native().c_str(), &test_kb)); - try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); - try_status(km_kbp_context_items_from_utf16(u"Hello 😁", &citems)); - - // Pre-test sanity: ensure debugging is disabled - assert(km_kbp_state_debug_get(test_state) == 0); - - // Ensure the pre-run debug item state is not empty - assert(debug_items(test_state, { - km_kbp_state_debug_item{KM_KBP_DEBUG_END} - })); - - // Test 1: Start with debugging disabled - setup(); test_debugging_disabled(); - - // Test 2: Debugging enabled, no rule match - setup(); test_debugging_no_rule_match(); - - // Test 3: Debugging enabled, function key pressed, no rule match - setup(); test_debugging_function_key(); + test_basic_rule_matches(); + test_multiple_groups(); + test_store_offsets(); // Destroy them - km_kbp_context_items_dispose(citems); - km_kbp_state_dispose(test_state); - km_kbp_keyboard_dispose(test_kb); + teardown(); + return 0; }