mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 17:36:00 +00:00
chore(developer): Merge remote-tracking branch 'origin/master' into chore/developer/non-printing-chars
This commit is contained in:
commit
7bacbb2d8d
27 changed files with 1082 additions and 35 deletions
12
HISTORY.md
12
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)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
17.0.198
|
||||
17.0.200
|
||||
|
|
@ -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 <Details/> as '' so we will just delete the empty string
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
/*
|
||||
```
|
||||
|
|
|
|||
138
core/src/action.cpp
Normal file
138
core/src/action.cpp
Normal file
|
|
@ -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 <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
|
||||
#include <keyman/keyman_core_api.h>
|
||||
|
||||
#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<km_core_actions> actions(new km_core_actions);
|
||||
|
||||
// Set actions default values
|
||||
std::vector<km_core_context_item> output;
|
||||
std::vector<km_core_option_item> 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<km_core_option_scope>(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<km_core_usv[]> 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();
|
||||
}
|
||||
20
core/src/action.hpp
Normal file
20
core/src/action.hpp
Normal file
|
|
@ -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 <keyman/keyman_core_api.h>
|
||||
|
||||
namespace km {
|
||||
namespace core
|
||||
{
|
||||
km_core_actions* action_item_list_to_actions_object(
|
||||
km_core_action_item const *action_items
|
||||
);
|
||||
} // namespace core
|
||||
} // namespace km
|
||||
64
core/src/km_core_action_api.cpp
Normal file
64
core/src/km_core_action_api.cpp
Normal file
|
|
@ -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 <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <keyman/keyman_core_api.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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<utf32>(ci,
|
||||
reinterpret_cast<utf32::codeunit_t *>(buf),
|
||||
sz_ptr);
|
||||
}
|
||||
|
||||
void km_core_context_items_dispose(km_core_context_item *ci)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
|
||||
#include <keyman/keyman_core_api.h>
|
||||
#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<km_core_cp[]> 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;
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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}});
|
||||
}
|
||||
|
|
|
|||
445
core/tests/unit/kmnkbd/action_api.cpp
Normal file
445
core/tests/unit/kmnkbd/action_api.cpp
Normal file
|
|
@ -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 <string>
|
||||
#include <keyman/keyman_core_api.h>
|
||||
|
||||
#include "path.hpp"
|
||||
#include "action.hpp"
|
||||
|
||||
#include <test_assert.h>
|
||||
#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] <SOURCE_PATH>\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;
|
||||
}
|
||||
|
|
@ -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}
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ export class WindowsPackageInstallerCompiler {
|
|||
|
||||
public async compile(kpsFilename: string, sources: WindowsPackageInstallerSources): Promise<Uint8Array> {
|
||||
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]) {
|
||||
|
|
|
|||
32
developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps
vendored
Normal file
32
developer/src/kmc-package/test/fixtures/invalid/error_invalid_package_file.kps
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package>
|
||||
<System>
|
||||
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
|
||||
<FileVersion>7.0</FileVersion>
|
||||
</System>
|
||||
<Info>
|
||||
<Name URL="">SENĆOŦEN (Saanich Dialect) Keyboard</Name>
|
||||
<!-- error_invalid_package_file -->
|
||||
<Copyright URL="">© 2019 National Research Council Canada & this test</Copyright>
|
||||
<Author URL="mailto:Eddie.Santos@nrc-cnrc.gc.ca">Eddie Antonio Santos</Author>
|
||||
<Version>1.0</Version>
|
||||
</Info>
|
||||
<Files>
|
||||
<File>
|
||||
<Name>basic.kmx</Name>
|
||||
<Description>Keyboard Basic</Description>
|
||||
<CopyLocation>0</CopyLocation>
|
||||
<FileType>.kmx</FileType>
|
||||
</File>
|
||||
</Files>
|
||||
<Keyboards>
|
||||
<Keyboard>
|
||||
<Name>Basic</Name>
|
||||
<ID>basic</ID>
|
||||
<Version>1.0</Version>
|
||||
<Languages>
|
||||
<Language ID="KM">Khmer</Language>
|
||||
</Languages>
|
||||
</Keyboard>
|
||||
</Keyboards>
|
||||
</Package>
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()}));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@
|
|||
</div>
|
||||
|
||||
<div class='toolbar-item' id='key-cap-unicode-toolbar-item'>
|
||||
<label for='inpKeyCapUnicode'>Unicode:</label>
|
||||
<label for='inpKeyCapUnicode'>Text Unicode:</label>
|
||||
<input id='inpKeyCapUnicode' type='text' size='16' />
|
||||
</div>
|
||||
|
||||
|
|
@ -118,7 +118,7 @@
|
|||
</div>
|
||||
|
||||
<div class='toolbar-item' id='key-hint-unicode-toolbar-item'>
|
||||
<label for='inpKeyHintUnicode'>Unicode:</label>
|
||||
<label for='inpKeyHintUnicode'>Hint Unicode:</label>
|
||||
<input id='inpKeyHintUnicode' type='text' size='16' />
|
||||
</div>
|
||||
|
||||
|
|
@ -221,7 +221,7 @@
|
|||
<input id='inpSubKeyCap' type='text' size='8' />
|
||||
</div>
|
||||
<div class='toolbar-item' id='sub-key-cap-unicode-toolbar-item'>
|
||||
<label>Unicode:</label>
|
||||
<label>Text Unicode:</label>
|
||||
<input id='inpSubKeyCapUnicode' type='text' size='16' />
|
||||
</div>
|
||||
<div class='toolbar-item'>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue