diff --git a/HISTORY.md b/HISTORY.md index 713cc4cfc3..a779c84f55 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,17 @@ # Keyman Version History +## 17.0.199 alpha 2023-10-26 + +* fix(developer): handle xml errors in package compiler (#9821) +* fix(developer): server download Keyman link (#9822) +* chore(common): handle invalid XML in kpj-file-reader (#9824) +* fix(developer): reduce confusion in Unicode fields in touch layout editor (#9839) + +## 17.0.198 alpha 2023-10-25 + +* chore(common): Add entries from 16.0 HISTORY.md (#9826) +* feat(core): new actions APIs (#9828) + ## 17.0.197 alpha 2023-10-24 * chore(linux): Rename (lib)kmnkbp to (lib)keymancore ️ (#9793) diff --git a/VERSION.md b/VERSION.md index aebc076d9d..0de5a09ae5 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.198 \ No newline at end of file +17.0.200 \ No newline at end of file diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index cffa2069c6..4457992e22 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -21,7 +21,12 @@ export class KPJFileReader { emptyTag: '' }); - parser.parseString(file, (e: unknown, r: unknown) => { data = r as KPJFile }); + parser.parseString(file, (e: unknown, r: unknown) => { + if(e) { + throw e; + } + data = r as KPJFile; + }); data = this.boxArrays(data); for(let file of data.KeymanDeveloperProject?.Files?.File) { // xml2js imports
as '' so we will just delete the empty string diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index 666895b520..309fdb20e1 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -253,8 +253,8 @@ km_core_context_items_from_utf8(char const *text, Convert a context item array into a UTF-16 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -285,8 +285,8 @@ km_core_context_items_to_utf16(km_core_context_item const *item, Convert a context item array into a UTF-8 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -310,6 +310,38 @@ km_core_context_items_to_utf8(km_core_context_item const *item, char *buf, size_t *buf_size); +/* +``` +### `km_core_context_items_to_utf32` +##### Description: +Convert a context item array into a UTF-32 encoded string placing it into +the supplied buffer of specified size, and return the number of codepoints +actually used in the conversion. If null is passed as the buffer the +number of codepoints required is returned. Any markers in the context will +not be included in the output buffer. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_CORE_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough. + `buf_size` will contain the space required. The contents of the buffer are + undefined. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_core_context_item`. + Must be terminated with a type of `KM_CORE_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-32 string into. + May be null to request size calculation. +- __buf_size__: a pointer to the result variable: + The size of the supplied buffer in codepoints if `buf` is given. + On return will be the size required if `buf` is null. + +```c +*/ +KMN_API +km_core_status +km_core_context_items_to_utf32(km_core_context_item const *item, + km_core_usv *buf, + size_t *buf_size); + /* ``` ### `km_core_context_items_dispose` @@ -501,10 +533,10 @@ typedef struct { uint8_t type; uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; union { - uintptr_t marker; // MARKER type + uint32_t marker; // MARKER type km_core_option_item const * option; // OPT types km_core_usv character; // CHAR type - uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off + uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off; re name see #9833 km_core_backspace_item backspace; // BACKSPACE type }; } km_core_action_item; @@ -526,6 +558,161 @@ enum km_core_action_type { KM_CORE_IT_MAX_TYPE_ID }; +/* +``` +### Actions +This structure provides the results of processing a key event to the Platform layer and +should be processed by the Platform layer to issue commands to the os text +services framework to transform the text store in the Client Application, among +other actions. + +This API replaces the Action items APIs, which is now deprecated and will be +removed in the future. +```c +*/ + +typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool; +typedef enum { KM_CORE_CAPS_UNCHANGED = -1, KM_CORE_CAPS_OFF = 0, KM_CORE_CAPS_ON = 1 } km_core_caps_state; + +typedef struct { + // number of codepoints (not codeunits!) to delete from app context. + unsigned int code_points_to_delete; + + // null-term string of characters to insert into document + km_core_usv* output; + + // list of options to persist, terminated with KM_CORE_OPTIONS_END + km_core_option_item* persist_options; + + // issue a beep, 0 = no, 1 = yes + km_core_bool do_alert; + + // emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes + km_core_bool emit_keystroke; + + // -1=unchanged, 0=off, 1=on + km_core_caps_state new_caps_lock_state; +} km_core_actions; + +/* +``` +### `km_core_state_get_actions` +##### Description: +Returns a pointer to an actions object which details all the actions +that the Platform layer must take after a keystroke. The `code_points_to_delete` +action must be performed before the `output` action, but the other +actions may be performed in any order. +##### Return: +A pointer to a `km_core_actions` object, which must be freed with +`km_core_actions_dispose`. +##### Parameters: +- __state__: An opaque pointer to a state object. + +```c +*/ +KMN_API +km_core_actions* +km_core_state_get_actions( + km_core_state const *state +); + +/* +``` +### `km_core_actions_dispose` +##### Description: +Free the allocated memory belonging to an actions object previously +returned by `km_core_state_get_actions`. +##### Parameters: +- __actions__: A pointer to the actions object to be disposed of. + +```c +*/ +KMN_API +km_core_status +km_core_actions_dispose( + km_core_actions* actions +); + +/* +``` +### `km_core_context_status` +##### Description: +Return values for `km_core_state_context_set_if_needed`. + +```c +*/ + +typedef enum { + KM_CORE_CONTEXT_STATUS_UNCHANGED = 0, // Cached context change was not needed + KM_CORE_CONTEXT_STATUS_UPDATED = 1, // Cached context was set to application context + KM_CORE_CONTEXT_STATUS_CLEARED = 2, // Application context was invalid, context was cleared + KM_CORE_CONTEXT_STATUS_ERROR = 3, // Internal error + KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT = 4, // Invalid arguments +} km_core_context_status; + +/* +``` +### `km_core_state_context_set_if_needed` +##### Description: +Sets the internal cached context for the state object, to the passed-in +application context string, if it differs from the codepoints in the +cached context. For the purposes of comparison, (1) cached markers are +ignored, (2) if the cached context is shorter than the application +context, it is considered identical, but (3) if the cached context is +longer, then it is considered different. + +If a difference is found, then the cached context will be set to the +application context, and thus any cached markers will be cleared. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +- __application_context__: A pointer to an null-terminated `km_core_cp` + string representing the current context from the application. +##### Return status: +- `KM_CORE_CONTEXT_STATUS_UNCHANGED`: Cached context change was not needed +- `KM_CORE_CONTEXT_STATUS_UPDATED`: Cached context was set to application + context +- `KM_CORE_CONTEXT_STATUS_CLEARED`: Application context was invalid, perhaps + had unpaired surrogates, and so cached context was cleared instead +- `KM_CORE_CONTEXT_STATUS_ERROR`: Internal error +- `KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT`: One or more parameters was null + +```c +*/ + +KMN_API +km_core_context_status +km_core_state_context_set_if_needed( + km_core_state *state, + km_core_cp const *application_context +); + +/* +``` +### `km_core_state_context_clear` +##### Description: +Clears the internal cached context for the state. This is the same as +`km_core_context_clear(km_core_state_context(&state))`. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If any parameters are null. + +```c +*/ +KMN_API +km_core_status +km_core_state_context_clear( + km_core_state *state +); /* ``` diff --git a/core/src/action.cpp b/core/src/action.cpp new file mode 100644 index 0000000000..3387d8f85d --- /dev/null +++ b/core/src/action.cpp @@ -0,0 +1,138 @@ +/* + Copyright: © 2023 SIL International. + Description: Implementation of the action API functions using internal + data structures and functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation from #9720 +*/ +#include +#include +#include +#include + +#include + +#include "action.hpp" +#include "state.hpp" +#include "option.hpp" + +km_core_actions * km::core::action_item_list_to_actions_object( + km_core_action_item const *action_items +) { + assert(action_items != nullptr); + if(action_items == nullptr) { + return nullptr; + } + + km_core_status status = KM_CORE_STATUS_OK; + + std::unique_ptr actions(new km_core_actions); + + // Set actions default values + std::vector output; + std::vector options; + actions->code_points_to_delete = 0; + actions->do_alert = KM_CORE_FALSE; + actions->emit_keystroke = KM_CORE_FALSE; + actions->new_caps_lock_state = KM_CORE_CAPS_UNCHANGED; + + // Clear output pointers, will be set later once we have sizes + actions->output = nullptr; + actions->persist_options = nullptr; + + for (; action_items->type != KM_CORE_IT_END; ++action_items) { + assert(action_items->type < KM_CORE_IT_MAX_TYPE_ID); + + switch(action_items->type) { + case KM_CORE_IT_ALERT: + actions->do_alert = KM_CORE_TRUE; + break; + case KM_CORE_IT_BACK: + switch(action_items->backspace.expected_type) { + case KM_CORE_BT_UNKNOWN: + // this is equivalent to emit_keystroke, because the only time we + // are allowed to do an unknown bksp is when a bksp is passed in + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_BT_CHAR: + if(output.empty()) { + actions->code_points_to_delete++; + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_CHAR); + assert(last_context_item.character == action_items->backspace.expected_value); + } + break; + case KM_CORE_BT_MARKER: + if(output.empty()) { + // deleting a marker has no effect on the application + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_MARKER); + assert(last_context_item.marker == action_items->backspace.expected_value); + } + break; + default: + assert(false); + } + break; + case KM_CORE_IT_CAPSLOCK: + actions->new_caps_lock_state = action_items->capsLock ? KM_CORE_CAPS_ON : KM_CORE_CAPS_OFF; + break; + case KM_CORE_IT_CHAR: + output.push_back({KM_CORE_CT_CHAR,{0},{action_items->character}}); + break; + case KM_CORE_IT_EMIT_KEYSTROKE: + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_IT_INVALIDATE_CONTEXT: + // no-op + break; + case KM_CORE_IT_MARKER: + output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}}); + break; + case KM_CORE_IT_PERSIST_OPT: + // TODO: lowpri: replace existing item if already present in options vector? + options.push_back(km::core::option( + static_cast(action_items->option->scope), + action_items->option->key, + action_items->option->value + )); + break; + default: + assert(false); + } + } + + + // Strip the markers from the output, and convert to an string of UTF-32 + + output.push_back(KM_CORE_CONTEXT_ITEM_END); + + size_t buf_size; + + if((status = km_core_context_items_to_utf32(output.data(), nullptr, &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + std::unique_ptr output_usv(new km_core_usv[buf_size]); + + if((status = km_core_context_items_to_utf32(output.data(), output_usv.get(), &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + actions->output = output_usv.release(); + + // Create an array of the persisted options + + options.push_back(KM_CORE_OPTIONS_END); + actions->persist_options = new km_core_option_item[options.size()]; + std::copy(options.begin(), options.end(), actions->persist_options); + + // We now have a complete set of actions + + return actions.release(); +} diff --git a/core/src/action.hpp b/core/src/action.hpp new file mode 100644 index 0000000000..4cc0aabdf2 --- /dev/null +++ b/core/src/action.hpp @@ -0,0 +1,20 @@ +/* + Copyright: © 2023 SIL International. + Description: Internal actions methods for Keyman Core + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation +*/ + +#pragma once + +#include + +namespace km { +namespace core +{ + km_core_actions* action_item_list_to_actions_object( + km_core_action_item const *action_items + ); +} // namespace core +} // namespace km diff --git a/core/src/km_core_action_api.cpp b/core/src/km_core_action_api.cpp new file mode 100644 index 0000000000..d552d41059 --- /dev/null +++ b/core/src/km_core_action_api.cpp @@ -0,0 +1,64 @@ +/* + Copyright: © 2023 SIL International. + Description: Implementation of the action API functions using internal + data structures and functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation. +*/ +#include +#include +#include + +#include +#include "jsonpp.hpp" + +#include "processor.hpp" +#include "state.hpp" +#include "action.hpp" + +using namespace km::core; + +km_core_actions* km_core_state_get_actions( + km_core_state const *state +) { + assert(state); + if(!state) { + return nullptr; + } + + km_core_actions* actions = nullptr; + auto action_items = km_core_state_action_items(state, nullptr); + if(!action_items) { + return nullptr; + } + + actions = action_item_list_to_actions_object(action_items); + return actions; +} + +km_core_status km_core_actions_dispose( + km_core_actions* actions +) { + if(actions == nullptr) { + return KM_CORE_STATUS_OK; + } + + if(actions->output) { + delete[] actions->output; + } + + if(actions->persist_options) { + for(auto option = actions->persist_options; option->scope; option++) { + delete[] option->key; + delete[] option->value; + } + delete[] actions->persist_options; + } + + delete actions; + + return KM_CORE_STATUS_OK; +} + + diff --git a/core/src/km_core_context_api.cpp b/core/src/km_core_context_api.cpp index 2bebcf97d0..46522710c0 100644 --- a/core/src/km_core_context_api.cpp +++ b/core/src/km_core_context_api.cpp @@ -140,6 +140,13 @@ km_core_status km_core_context_items_to_utf16(km_core_context_item const *ci, sz_ptr); } +km_core_status km_core_context_items_to_utf32(km_core_context_item const *ci, + km_core_usv *buf, size_t * sz_ptr) +{ + return _context_items_to(ci, + reinterpret_cast(buf), + sz_ptr); +} void km_core_context_items_dispose(km_core_context_item *ci) { diff --git a/core/src/km_core_state_api.cpp b/core/src/km_core_state_api.cpp index e5ccccd795..8a8165c9c0 100644 --- a/core/src/km_core_state_api.cpp +++ b/core/src/km_core_state_api.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "jsonpp.hpp" @@ -259,3 +260,99 @@ void km_core_state_imx_deregister_callback(km_core_state *state) } state->imx_deregister_callback(); } + +bool is_context_valid(km_core_cp const * context, km_core_cp const * cached_context) { + km_core_cp const* context_p = context; + while(*context_p) { + context_p++; + } + + km_core_cp const* cached_context_p = cached_context; + while(*cached_context_p) { + cached_context_p++; + } + + // we need to compare from the end of the cached context + for(; context_p >= context && cached_context_p >= cached_context; context_p--, cached_context_p--) { + if(*context_p != *cached_context_p) { + // The cached context doesn't match the application context, so it is + // invalid + return false; + } + } + + if(cached_context_p > cached_context) { + // if the cached context is longer than the application context, then we also + // assume that it is invalid + return false; + } + + // It's acceptable for the application context to be longer than the cached + // context, so if we match the whole cached context, we can safely return true + return true; +} + +km_core_context_status km_core_state_context_set_if_needed( + km_core_state *state, + km_core_cp const *application_context +) { + assert(state != nullptr); + assert(application_context != nullptr); + if(state == nullptr || application_context == nullptr) { + return KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT; + } + + size_t buf_size; + km_core_context_item* context_items = nullptr; + + auto context = km_core_state_context(state); + if(km_core_context_get(context, &context_items) != KM_CORE_STATUS_OK) { + return KM_CORE_CONTEXT_STATUS_ERROR; + } + + if(km_core_context_items_to_utf16(context_items, nullptr, &buf_size) != KM_CORE_STATUS_OK) { + km_core_context_items_dispose(context_items); + return KM_CORE_CONTEXT_STATUS_ERROR; + } + + std::unique_ptr cached_context(new km_core_cp[buf_size]); + + km_core_status status = km_core_context_items_to_utf16(context_items, cached_context.get(), &buf_size); + km_core_context_items_dispose(context_items); + + if(status != KM_CORE_STATUS_OK) { + return KM_CORE_CONTEXT_STATUS_ERROR; + } + + bool is_valid = is_context_valid(application_context, cached_context.get()); + + if(is_valid) { + // We keep the context as is + return KM_CORE_CONTEXT_STATUS_UNCHANGED; + } + + km_core_context_item* new_context_items = nullptr; + + // We replace the cached context with the current application context + status = km_core_context_items_from_utf16(application_context, &new_context_items); + if (status != KM_CORE_STATUS_OK) { + km_core_context_clear(context); + return KM_CORE_CONTEXT_STATUS_CLEARED; + } + + km_core_context_set(context, new_context_items); + km_core_context_items_dispose(new_context_items); + return KM_CORE_CONTEXT_STATUS_UPDATED; +} + + +km_core_status km_core_state_context_clear( + km_core_state *state +) { + assert(state != nullptr); + if(state == nullptr) { + return KM_CORE_STATUS_INVALID_ARGUMENT; + } + km_core_context_clear(km_core_state_context(state)); + return KM_CORE_STATUS_OK; +} \ No newline at end of file diff --git a/core/src/meson.build b/core/src/meson.build index c5ace69697..fce381f22e 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -42,10 +42,12 @@ endif kmx_files = files( + 'action.cpp', 'option.cpp', 'keyboard.cpp', 'state.cpp', 'debuglog.cpp', + 'km_core_action_api.cpp', 'km_core_context_api.cpp', 'km_core_keyboard_api.cpp', 'km_core_options_api.cpp', @@ -74,6 +76,7 @@ kmx_files = files( ) api_files = files( + 'km_core_action_api.cpp', 'km_core_context_api.cpp', 'km_core_keyboard_api.cpp', 'km_core_options_api.cpp', @@ -83,6 +86,7 @@ api_files = files( ) core_files = files( + 'action.cpp', 'option.cpp', 'keyboard.cpp', 'state.cpp', diff --git a/core/src/state.hpp b/core/src/state.hpp index 7543cdf94e..fadfa61fc8 100644 --- a/core/src/state.hpp +++ b/core/src/state.hpp @@ -37,7 +37,7 @@ public: actions(Args&&... args); void push_character(km_core_usv usv); - void push_marker(uintptr_t marker); + void push_marker(uint32_t marker); void push_alert(); void push_backspace(km_core_backspace_type expected_type, uintptr_t expected_value = 0); void push_persist(option const &); @@ -68,7 +68,7 @@ void actions::push_character(km_core_usv usv) { inline -void actions::push_marker(uintptr_t marker) { +void actions::push_marker(uint32_t marker) { assert(empty() || (!empty() && back().type != KM_CORE_IT_END)); emplace_back(km_core_action_item {KM_CORE_IT_MARKER, {0,}, {marker}}); } diff --git a/core/tests/unit/kmnkbd/action_api.cpp b/core/tests/unit/kmnkbd/action_api.cpp new file mode 100644 index 0000000000..79512352a3 --- /dev/null +++ b/core/tests/unit/kmnkbd/action_api.cpp @@ -0,0 +1,445 @@ +/* + Copyright: © 2018 SIL International. + Description: Tests for the context API family of functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin + History: 23 Oct 2023 - MCD - Initial implementation. +*/ +#include +#include + +#include "path.hpp" +#include "action.hpp" + +#include +#include "../emscripten_filesystem.h" + +const km_core_action_item alert_action_item(); +const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value); +const km_core_action_item caps_action_item(uint8_t capsLock); +const km_core_action_item char_action_item(km_core_usv chr); +const km_core_action_item emit_keystroke_action_item(); +const km_core_action_item persist_opt_action_item(km_core_option_item const *option); +const km_core_action_item end_action_item(); +const km_core_action_item invalidate_context_action_item(); +const km_core_action_item marker_action_item(uint32_t marker); + +//------------------------------------------------------------------------------------- + +void test_two_backspaces() { + const km_core_action_item action_items[] = { + char_action_item('D'), + bksp_action_item(KM_CORE_BT_CHAR, 'D'), + bksp_action_item(KM_CORE_BT_CHAR, 'E'), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 1); + assert(std::u32string(actions->output) == U""); + assert(actions->persist_options != nullptr); + assert(actions->persist_options[0].key == nullptr); + assert(actions->persist_options[0].value == nullptr); + assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN); + + assert(actions->do_alert == false); + assert(actions->emit_keystroke == false); + assert(actions->new_caps_lock_state == -1); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- + +void test_marker_text_interleaved() { + const km_core_action_item action_items[] = { + char_action_item('A'), + marker_action_item(1), + char_action_item('B'), + marker_action_item(2), + char_action_item('C'), + bksp_action_item(KM_CORE_BT_CHAR, 'C'), + bksp_action_item(KM_CORE_BT_MARKER, 2), + char_action_item('D'), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 0); + assert(std::u32string(actions->output) == U"ABD"); + assert(actions->persist_options != nullptr); + assert(actions->persist_options[0].key == nullptr); + assert(actions->persist_options[0].value == nullptr); + assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN); + assert(actions->do_alert == false); + assert(actions->emit_keystroke == false); + assert(actions->new_caps_lock_state == -1); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- + +void test_alert() { + const km_core_action_item action_items[] = { + alert_action_item(), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 0); + assert(std::u32string(actions->output) == U""); + assert(actions->persist_options != nullptr); + assert(actions->persist_options[0].key == nullptr); + assert(actions->persist_options[0].value == nullptr); + assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN); + assert(actions->do_alert == KM_CORE_TRUE); + assert(actions->emit_keystroke == KM_CORE_FALSE); + assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- + +void test_emit_keystroke() { + const km_core_action_item action_items[] = { + emit_keystroke_action_item(), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 0); + assert(std::u32string(actions->output) == U""); + assert(actions->persist_options != nullptr); + assert(actions->persist_options[0].key == nullptr); + assert(actions->persist_options[0].value == nullptr); + assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN); + assert(actions->do_alert == KM_CORE_FALSE); + assert(actions->emit_keystroke == KM_CORE_TRUE); + assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- + +void test_invalidate_context() { + // note, this generates a no-op + const km_core_action_item action_items[] = { + invalidate_context_action_item(), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 0); + assert(std::u32string(actions->output) == U""); + assert(actions->persist_options != nullptr); + assert(actions->persist_options[0].key == nullptr); + assert(actions->persist_options[0].value == nullptr); + assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN); + assert(actions->do_alert == KM_CORE_FALSE); + assert(actions->emit_keystroke == KM_CORE_FALSE); + assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- + +void test_persist_opt() { + const km_core_option_item option = { + u"key", + u"value", + KM_CORE_OPT_KEYBOARD + }; + + const km_core_action_item action_items[] = { + persist_opt_action_item(&option), + end_action_item() + }; + + km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items); + + assert(actions->code_points_to_delete == 0); + assert(std::u32string(actions->output) == U""); + assert(actions->persist_options != nullptr); + assert(std::u16string(actions->persist_options[0].key) == u"key"); + assert(std::u16string(actions->persist_options[0].value) == u"value"); + assert(actions->persist_options[0].scope == KM_CORE_OPT_KEYBOARD); + + // verify that data is copied + assert(actions->persist_options[0].key != option.key); + assert(actions->persist_options[0].value != option.value); + + // verify that we have a KM_CORE_OPTIONS_END term + assert(actions->persist_options[1].key == nullptr); + assert(actions->persist_options[1].value == nullptr); + assert(actions->persist_options[1].scope == KM_CORE_OPT_UNKNOWN); + + assert(actions->do_alert == KM_CORE_FALSE); + assert(actions->emit_keystroke == KM_CORE_FALSE); + assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED); + + try_status(km_core_actions_dispose(actions)); +} + +//------------------------------------------------------------------------------------- +// Context tests +//------------------------------------------------------------------------------------- + +km_core_option_item test_env_opts[] = +{ + KM_CORE_OPTIONS_END +}; + +km_core_keyboard * test_kb = nullptr; +km_core_state * test_state = nullptr; +km_core_context_item * citems = nullptr; +std::string arg_path; + +void teardown() { + if(citems) { + km_core_context_items_dispose(citems); + citems = nullptr; + } + if(test_state) { + km_core_state_dispose(test_state); + test_state = nullptr; + } + if(test_kb) { + km_core_keyboard_dispose(test_kb); + test_kb = nullptr; + } +} + +void setup(const char *keyboard, const km_core_cp* context) { + teardown(); + + km::core::path path = km::core::path::join(arg_path, keyboard); + try_status(km_core_keyboard_load(path.native().c_str(), &test_kb)); + try_status(km_core_state_create(test_kb, test_env_opts, &test_state)); + try_status(km_core_context_items_from_utf16(context, &citems)); + try_status(km_core_context_set(km_core_state_context(test_state), citems)); +} + +bool is_identical_context(km_core_cp const *cached_context) { + size_t buf_size; + try_status(km_core_context_get(km_core_state_context(test_state), &citems)); + try_status(km_core_context_items_to_utf16(citems, nullptr, &buf_size)); + km_core_cp* new_cached_context = new km_core_cp[buf_size]; + try_status(km_core_context_items_to_utf16(citems, new_cached_context, &buf_size)); + bool result = std::u16string(cached_context) == new_cached_context; + delete[] new_cached_context; + return result; +} + +void test_context_set_if_needed_identical_context() { + km_core_cp const *application_context = u"This is a test"; + km_core_cp const *cached_context = u"This is a test"; + setup("k_000___null_keyboard.kmx", cached_context); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED); + assert(is_identical_context(cached_context)); + teardown(); +} + +void test_context_set_if_needed_different_context() { + km_core_cp const *application_context = u"This is a test"; + km_core_cp const *cached_context = u"This isn't a test"; + setup("k_000___null_keyboard.kmx", cached_context); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED); + assert(!is_identical_context(cached_context)); + assert(is_identical_context(application_context)); + teardown(); +} + +void test_context_set_if_needed_app_context_is_longer() { + km_core_cp const *application_context = u"Longer This is a test"; + km_core_cp const *cached_context = u"This is a test"; + setup("k_000___null_keyboard.kmx", cached_context); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED); + // Should be true -- longer, but what exists is identical to cached + assert(is_identical_context(cached_context)); + teardown(); +} + +void test_context_set_if_needed_app_context_is_shorter() { + km_core_cp const *application_context = u"is a test"; + km_core_cp const *cached_context = u"This is a test"; + setup("k_000___null_keyboard.kmx", cached_context); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED); + // Should be false -- app ctxt is shorter, so doesn't matter that what we have + // matches + assert(!is_identical_context(cached_context)); + assert(is_identical_context(application_context)); + teardown(); +} + +void test_context_set_if_needed_cached_context_has_markers() { + km_core_cp const *application_context = u"123"; + km_core_cp const *cached_context = u"123"; + setup("k_000___null_keyboard.kmx", cached_context); + + km_core_context_item const citems[] = { + { KM_CORE_CT_MARKER, {0}, { 5 } }, + { KM_CORE_CT_CHAR, {0}, { '1' } }, + { KM_CORE_CT_MARKER, {0}, { 1 } }, + { KM_CORE_CT_CHAR, {0}, { '2' } }, + { KM_CORE_CT_MARKER, {0}, { 2 } }, + { KM_CORE_CT_CHAR, {0}, { '3' } }, + { KM_CORE_CT_MARKER, {0}, { 3 } }, + { KM_CORE_CT_MARKER, {0}, { 4 } }, + KM_CORE_CONTEXT_ITEM_END + }; + + try_status(km_core_context_set(km_core_state_context(test_state), citems)); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED); + + km_core_context_item* citems_new; + + try_status(km_core_context_get(km_core_state_context(test_state), &citems_new)); + + for(int i = 0; citems[i].type || citems_new[i].type; i++) { + assert(citems_new[i].type == citems[i].type); + if(citems[i].type == KM_CORE_CT_CHAR) { + assert(citems_new[i].character == citems[i].character); + } else { + assert(citems_new[i].marker == citems[i].marker); + } + } + + teardown(); +} + +void test_context_set_if_needed() { + test_context_set_if_needed_identical_context(); + test_context_set_if_needed_different_context(); + test_context_set_if_needed_app_context_is_longer(); + test_context_set_if_needed_app_context_is_shorter(); + test_context_set_if_needed_cached_context_has_markers(); +} + +void test_context_clear() { + km_core_cp const *cached_context = u"This is a test"; + setup("k_000___null_keyboard.kmx", cached_context); + try_status(km_core_state_context_clear(test_state)); + assert(!is_identical_context(cached_context)); + assert(is_identical_context(u"")); + teardown(); +} + +//------------------------------------------------------------------------------------- +// Launcher +//------------------------------------------------------------------------------------- + +constexpr const auto help_str = "\ +action_api [--color] \n\ +\n\ + --color Force color output\n\ + SOURCE_PATH Path where debug_api.cpp is found; kmx files are\n\ + located relative to this path.\n"; + +int error_args() { + std::cerr << "debug_api: Invalid arguments." << std::endl; + std::cout << help_str; + return 1; +} + +int main(int argc, char *argv []) { + + if(argc < 2) { + return error_args(); + } + + auto arg_color = std::string(argv[1]) == "--color"; + if(arg_color && argc < 3) { + return error_args(); + } + console_color::enabled = console_color::isaterminal() || arg_color; + +#ifdef __EMSCRIPTEN__ + arg_path = get_wasm_file_path(argv[arg_color ? 2 : 1]); +#else + arg_path = argv[arg_color ? 2 : 1]; +#endif + + // actions + test_two_backspaces(); + test_marker_text_interleaved(); + test_alert(); + test_emit_keystroke(); + test_invalidate_context(); + + // context -- todo move to another file + test_context_set_if_needed(); + test_context_clear(); +} + +//------------------------------------------------------------------------------------- +// Helper functions +//------------------------------------------------------------------------------------- + +const km_core_action_item alert_action_item() { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_ALERT; + return res; +} + +const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value) { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_BACK; + res.backspace.expected_type = type; + res.backspace.expected_value = value; + return res; +} + +const km_core_action_item caps_action_item(uint8_t capsLock) { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_CAPSLOCK; + res.capsLock = capsLock; + return res; +} + +const km_core_action_item char_action_item(km_core_usv chr) { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_CHAR; + res.character = chr; + return res; +} + +const km_core_action_item emit_keystroke_action_item() { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_EMIT_KEYSTROKE; + return res; +} + +const km_core_action_item persist_opt_action_item(km_core_option_item const *option) { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_PERSIST_OPT; + res.option = option; + return res; +} + +const km_core_action_item end_action_item() { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_END; + return res; +} + +const km_core_action_item invalidate_context_action_item() { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_INVALIDATE_CONTEXT; + return res; +} + +const km_core_action_item marker_action_item(uint32_t marker) { + km_core_action_item res = {0}; + res.type = KM_CORE_IT_MARKER; + res.character = marker; + return res; +} diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.cpp index 6763bd339a..de0cfce8e8 100644 --- a/core/tests/unit/kmnkbd/debug_api.cpp +++ b/core/tests/unit/kmnkbd/debug_api.cpp @@ -402,8 +402,11 @@ void test_save_option() { km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 1}}, })); + km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, }; + action.option = &opt; + assert(action_items(test_state, { - {KM_CORE_IT_PERSIST_OPT, {0,}, {uintptr_t(&opt)}}, + action, {KM_CORE_IT_END} })); } diff --git a/core/tests/unit/kmnkbd/meson.build b/core/tests/unit/kmnkbd/meson.build index 3142089042..dc94227bef 100644 --- a/core/tests/unit/kmnkbd/meson.build +++ b/core/tests/unit/kmnkbd/meson.build @@ -16,6 +16,7 @@ endif local_defns = ['-DKM_CORE_LIBRARY_STATIC'] tests = [ + ['action-api', 'action_api.cpp'], ['context-api', 'context_api.cpp'], ['keyboard-api', 'keyboard_api.cpp'], ['options-api', 'options_api.cpp'], diff --git a/core/tests/unit/kmnkbd/state_api.cpp b/core/tests/unit/kmnkbd/state_api.cpp index eec539e6e5..e3d1717589 100644 --- a/core/tests/unit/kmnkbd/state_api.cpp +++ b/core/tests/unit/kmnkbd/state_api.cpp @@ -170,8 +170,10 @@ int main(int argc, char * argv[]) KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('L')}}, {KM_CORE_IT_END}})); try_status(km_core_process_event(test_state, KM_CORE_VKEY_F2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT)); - assert(action_items(test_state, {{KM_CORE_IT_PERSIST_OPT, {0,}, - {uintptr_t(&expected_persist_opt)}}, {KM_CORE_IT_END}})); + + km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, }; + action.option = &expected_persist_opt; + assert(action_items(test_state, {action, {KM_CORE_IT_END}})); // Test debug dump auto doc1 = get_json_doc(*test_state), diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b26643e178..f2df4c881f 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -29,6 +29,7 @@ export class KmpCompiler { public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { const kps = this.loadKpsFile(kpsFilename); if(!kps) { + // errors will already have been reported by loadKpsFile return null; } return this.transformKpsFileToKmpObject(kpsFilename, kps); @@ -48,11 +49,19 @@ export class KmpCompiler { let parser = new xml2js.Parser({ explicitArray: false }); - // TODO: add unit test for xml errors parsing .kps file - parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); + + try { + parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); + } catch(e) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidPackageFile({e})); + } return a; })(); + if(!kpsPackage) { + return null; + } + const kps: KpsFile.KpsFile = kpsPackage.Package; return kps; } diff --git a/developer/src/kmc-package/src/compiler/messages.ts b/developer/src/kmc-package/src/compiler/messages.ts index 2eeaa6e061..cec733d3d8 100644 --- a/developer/src/kmc-package/src/compiler/messages.ts +++ b/developer/src/kmc-package/src/compiler/messages.ts @@ -123,5 +123,9 @@ export class CompilerMessages { static Hint_PackageContainsSourceFile = (o:{filename:string}) => m(this.HINT_PackageContainsSourceFile, `The source file ${o.filename} should not be included in the package; instead include the compiled result.`); static HINT_PackageContainsSourceFile = SevHint | 0x001D; + + static Error_InvalidPackageFile = (o:{e:any}) => m(this.ERROR_InvalidPackageFile, + `Package source file is invalid: ${(o.e ?? 'unknown error').toString()}`); + static ERROR_InvalidPackageFile = SevError | 0x001E; } diff --git a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts index 8f920535bb..1a5f4ebe6b 100644 --- a/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts +++ b/developer/src/kmc-package/src/compiler/windows-package-installer-compiler.ts @@ -39,6 +39,10 @@ export class WindowsPackageInstallerCompiler { public async compile(kpsFilename: string, sources: WindowsPackageInstallerSources): Promise { const kps = this.kmpCompiler.loadKpsFile(kpsFilename); + if(!kps) { + // errors will already have been reported by loadKpsFile + return null; + } // Check existence of required files for(const filename of [sources.licenseFilename, sources.msiFilename, sources.setupExeFilename]) { diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps new file mode 100644 index 0000000000..e18eaa26a0 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + SENĆOŦEN (Saanich Dialect) Keyboard + + © 2019 National Research Council Canada & this test + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Khmer + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index f9649e9c09..24fb658b59 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -226,4 +226,11 @@ describe('CompilerMessages', function () { CompilerMessages.HINT_PackageContainsSourceFile); }); + // ERROR_InvalidPackageFile + + it('should generate ERROR_InvalidPackageFile if package source file contains invalid XML', async function() { + testForMessage(this, ['invalid', 'error_invalid_package_file.kps'], + CompilerMessages.ERROR_InvalidPackageFile); + }); + }); diff --git a/developer/src/kmc/src/util/projectLoader.ts b/developer/src/kmc/src/util/projectLoader.ts index 078f2e31de..10cd58fc81 100644 --- a/developer/src/kmc/src/util/projectLoader.ts +++ b/developer/src/kmc/src/util/projectLoader.ts @@ -44,8 +44,9 @@ function loadDefaultProjectFromFolder(infile: string, callbacks: CompilerCallbac function loadProjectFromFile(infile: string, callbacks: CompilerCallbacks): KeymanDeveloperProject { const kpjData = callbacks.loadFile(infile); const reader = new KPJFileReader(callbacks); - const kpj = reader.read(kpjData); + let kpj = null; try { + kpj = reader.read(kpjData); reader.validate(kpj); } catch(e) { callbacks.reportMessage(InfrastructureMessages.Error_InvalidProjectFile({message: (e??'').toString()})); diff --git a/developer/src/server/src/site/packages.js b/developer/src/server/src/site/packages.js index 9a13b1afac..2255fe9ebd 100644 --- a/developer/src/server/src/site/packages.js +++ b/developer/src/server/src/site/packages.js @@ -11,12 +11,12 @@ menuDropdown.onclick = (value) => { menuDropdown.set(''); // we never show an 'active' package if(value == '#install-keyman') { let href = ''; - switch(keyman.util.device.OS) { - case 'iOS': href = 'https://keyman.com/go/developer/'+versionMajor+'/ios-app'; break; - case 'Android': href = 'https://keyman.com/go/developer/'+versionMajor+'/android-app'; break; - case 'Linux': href = 'https://keyman.com/linux/download'; break; - case 'Windows': href = 'https://keyman.com/go/download/keyman-windows'; break; - case 'MacOSX': href = 'https://keyman.com/go/download/keyman-mac'; break; + switch(keyman.config.hostDevice.OS) { // note: KeymanWeb internal API + case 'ios': href = 'https://keyman.com/go/developer/'+versionMajor+'/ios-app'; break; + case 'android': href = 'https://keyman.com/go/developer/'+versionMajor+'/android-app'; break; + case 'linux': href = 'https://keyman.com/linux/download'; break; + case 'windows': href = 'https://keyman.com/go/download/keyman-windows'; break; + case 'macosx': href = 'https://keyman.com/go/download/keyman-mac'; break; default: href = 'https://keyman.com/downloads'; break; } location.href = href; diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index 60a27cc484..6014c8ab26 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -216,14 +216,14 @@ window.onload = function() { if(newOSK) { document.getElementById('osk-host').removeChild(newOSK.element); - keyman.osk = null; + keyman.osk = null; // Note: undocumented KeymanWeb API } // Create a new on screen keyboard view and tell KeymanWeb that // we are using the targetDevice for context input. - newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); - keyman.core.contextDevice = targetDevice; - keyman.osk = newOSK; + newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); // Note: KeymanWeb internal API + keyman.core.contextDevice = targetDevice; // Note: KeymanWeb internal API + keyman.osk = newOSK; // Note: undocumented KeymanWeb API if(document.body.offsetWidth < targetDevice.dimensions[0]) { newOSK.setSize('320px', '200px'); @@ -237,8 +237,8 @@ window.onload = function() { keyman.addEventListener('keyboardchange', function(keyboardProperties) { if(newOSK) { - keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Private API refs on both sides + keyman.osk = newOSK; // Note: undocumented KeymanWeb API + newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Note: undocumented KeymanWeb API refs on both sides } keyboardDropdown.set(keyboardProperties.internalName); window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName); @@ -284,7 +284,7 @@ function unloadKeyboardsAndModels() { const lastModel = keyman.core.activeModel; if(lastModel) { console.log('Unregistering model '+lastModel.id); - keyman.removeModel(lastModel.id); + keyman.removeModel(lastModel.id); // Note: undocumented KeymanWeb API } modelDropdown.removeAll(); diff --git a/developer/src/tike/main/Keyman.System.KeymanCore.pas b/developer/src/tike/main/Keyman.System.KeymanCore.pas index e086980d0d..64cadfad23 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCore.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCore.pas @@ -192,7 +192,7 @@ type _reserved: array[0..2] of uint8_t; {$ENDIF} case Integer of - 0: (marker: uintptr_t); + 0: (marker: uint32_t); 1: (option: pkm_core_option_item); 2: (character: km_core_usv); 3: (backspace: km_core_backspace_item); diff --git a/developer/src/tike/xml/layoutbuilder/builder.xsl b/developer/src/tike/xml/layoutbuilder/builder.xsl index 2d6f5299cd..3b75cd81a3 100644 --- a/developer/src/tike/xml/layoutbuilder/builder.xsl +++ b/developer/src/tike/xml/layoutbuilder/builder.xsl @@ -108,7 +108,7 @@
- +
@@ -118,7 +118,7 @@
- +
@@ -221,7 +221,7 @@
- +
diff --git a/linux/debian/libkeymancore.symbols b/linux/debian/libkeymancore.symbols index 210008e910..9482ded8ae 100644 --- a/linux/debian/libkeymancore.symbols +++ b/linux/debian/libkeymancore.symbols @@ -1,6 +1,7 @@ libkeymancore.so.1 libkeymancore #MINVER# * Build-Depends-Package: libkeymancore-dev + km_core_actions_dispose@Base 17.0.197 km_core_context_append@Base 17.0.195 km_core_context_clear@Base 17.0.195 km_core_context_get@Base 17.0.195 @@ -9,6 +10,7 @@ libkeymancore.so.1 libkeymancore #MINVER# km_core_context_items_from_utf16@Base 17.0.195 km_core_context_items_from_utf8@Base 17.0.195 km_core_context_items_to_utf16@Base 17.0.195 + km_core_context_items_to_utf32@Base 17.0.197 km_core_context_items_to_utf8@Base 17.0.195 km_core_context_length@Base 17.0.195 km_core_context_set@Base 17.0.195 @@ -28,11 +30,14 @@ libkeymancore.so.1 libkeymancore #MINVER# km_core_state_action_items@Base 17.0.195 km_core_state_clone@Base 17.0.195 km_core_state_context@Base 17.0.195 + km_core_state_context_clear@Base 17.0.197 + km_core_state_context_set_if_needed@Base 17.0.197 km_core_state_create@Base 17.0.195 km_core_state_debug_get@Base 17.0.195 km_core_state_debug_items@Base 17.0.195 km_core_state_debug_set@Base 17.0.195 km_core_state_dispose@Base 17.0.195 + km_core_state_get_actions@Base 17.0.197 km_core_state_get_intermediate_context@Base 17.0.195 km_core_state_imx_deregister_callback@Base 17.0.195 km_core_state_imx_register_callback@Base 17.0.195 diff --git a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp index 7af218cd77..362bc0497f 100644 --- a/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp +++ b/windows/src/engine/keyman32/tests/keyman-engine-tests/kmprocessactionstests.cpp @@ -49,7 +49,7 @@ TEST_F(KMPROCESSACTIONS, processMarkertest) { WCHAR callbuf[MAXCONTEXT]; AITIP testApp; WCHAR expectedContext[] = {UC_SENTINEL, CODE_DEADKEY, 2, 0}; - uintptr_t marker = 2; + uint32_t marker = 2; km_core_action_item itemAddMarker = {KM_CORE_IT_MARKER, {0,}, {marker}}; processMarker(&testApp, &itemAddMarker); @@ -67,7 +67,7 @@ TEST_F(KMPROCESSACTIONS, processBackDeadkeytest) { km_core_action_item itemAddChar = {KM_CORE_IT_CHAR, {0,}, {'A'}}; processUnicodeChar(&testApp, &itemAddChar); - uintptr_t marker = 2; + uint32_t marker = 2; km_core_action_item itemAddMarker = {KM_CORE_IT_MARKER, {0,}, {marker}}; processMarker(&testApp, &itemAddMarker); km_core_action_item itemBackSpace = {KM_CORE_IT_BACK};