diff options
author | glasseyes <dglassey@gmail.com> | 2019-01-04 01:08:11 +0700 |
---|---|---|
committer | glasseyes <dglassey@gmail.com> | 2019-01-04 01:08:11 +0700 |
commit | 225ac56fac62c096faa2ac8a39ca2a1a121df0b7 (patch) | |
tree | 1d7403a00def6974653a7d5f56eaeec3866019bd | |
parent | a2822dddef89e155ebf558a26d2cbd825fd6d8ea (diff) |
New upstream version 11.0.101
72 files changed, 2696 insertions, 794 deletions
diff --git a/include/keyboardprocessor.h.in b/include/keyboardprocessor.h.in new file mode 100644 index 0000000..e7b4a40 --- /dev/null +++ b/include/keyboardprocessor.h.in @@ -0,0 +1,984 @@ +/* + Copyright: © 2018 SIL International. + Description: Cross platform API C/C++ declarations for libkmnkbp keyboard + processor. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 18 Oct 2018 - TSE - Finialised verion of API. + 6 Oct 2018 - TSE - Move into keyman folder. + +*/ +#pragma once +/* +# Keyman Keyboard Processor API + +## Requirements +1. Cross platform. +2. Cross language. +3. Facilitate stateless operation of the Engine. +4. Keyboard format agnostic -- support both KMN and future LDML based keyboards. +5. Support querying Engine attributes. +6. Support querying Keyboard attributes. +7. Idempotent + + +## Design decisions in support of requirements: +- Use C or C99 types and calling convention for the interface, it has the + broadest language FFI support. [1,2] +- Have client (Platform layer) code load keyboards, manage & pass state. [3,4,7] +- Provide query calls to return static attributes data for keyboards and + engine [5,6] +- Provide get/set calls for client accessible keyboard state information [3,4] + + +## Glossary +- __Platform layer:__ +The code that consumes the Keyman Keyboard Processor API, and provides the +operating system-specific handling of keystroke events and integration with +applications. +- __Client Application:__ +The application that has the focus and receives text events from the Platform +layer. +- __Context:__ Text preceding the insertion point +- __Marker:__ Positional state that can be placed in the Context. +- __Keyboard:__ A set of rules for execution my an Engine +- __Option:__ A variable in a dynamic or static key value store. +- __Processor:__ +The component that implements this API and can parse and execute a particular +keyboard. +- __State:__ An object that hold internal state of the Processor for a given +insertion point +- __Action:__ +A directive output by the processor detailing how the Platform layer should +transform the Client Application's text buffer. There may be several items +produced by a single keyboard event. +- __Keyboard Event:__ +A virtual key event and modifier map received from the platform to be +processed with the state object for this Client application. +- __Virtual Key:__ +A code based on the US English layout, with values matching the Windows +virtual key codes. See keyboardprocessor_vkeys.h for definitions. +- __Modifier Key:__ +The set of Control, Shift, Alt, Caps Lock keys. On some platforms these may +have other names (e.g. Alt is called Option on macOS); other platform-specific +modifiers such as Windows key are excluded from this set. Some modifiers are +transient, such as Control, and others have long-lasting state, such as +Caps Lock. + +## API +### Namespace +All calls, types and enums are prefixed with the namespace identifier `km_kbp_` + +### API idioms +Almost all calls marshalling variable length aggregate data in or out of an API +object take the form: +> km_kbp_status *fn_name*(object_ref, buffer_ptr, size_ptr) + +where the buffer is nullable and all other arguments are required (will result +in an `KM_KBP_STATUS_INVALID_ARGUMENT` status being returned if nulled). When +`buffer` is `nullptr` or `0` the function will place the size of the required +buffer in the variable pointed to by `size_ptr`. + +Calls which result in the allocation of resources, regardless of resulting +ownership, are of the form: +> km_kbp_status *fn_name*(object_ref, out_ptr) + +where `out_ptr` is a valid pointer to a caller allocated variable to hold the +resulting ouput. This is often a reference to a created object. All arguments +are required (will result in an `KM_KBP_STATUS_INVALID_ARGUMENT` status being +returned if nulled). + +For accessors to fixed size attributes of an object these will take the form: +> attr_value __fn_name__(object_ref) + +`object_ref` is required to be valid and will result in a nonsense value being +returned if `nullptr` or `0`. + +All dispose calls are designed to accept null as a valid value and will do +nothing in that event. +```c +*/ +#include <stdint.h> +#include <stdlib.h> +#include <keyman/keyboardprocessor_bits.h> +#include <keyman/keyboardprocessor_vkeys.h> + +#define KM_KBP_LIB_CURRENT @lib_curr@ +#define KM_KBP_LIB_AGE @lib_age@ +#define KM_KBP_LIB_REVISION @lib_rev@ + +#if defined(__cplusplus) +extern "C" +{ +#endif +// Basic types +// +#if defined(__cplusplus) +typedef char16_t km_kbp_cp; +typedef char32_t km_kbp_usv; +#else +typedef uint16_t km_kbp_cp; // code point +typedef uint32_t km_kbp_usv; // Unicode Scalar Value +#endif +typedef uint16_t km_kbp_virtual_key; // A virtual key code. +typedef uint32_t km_kbp_status; // Status return code. + +// Opaque object types. +// +typedef struct km_kbp_context km_kbp_context; +typedef struct km_kbp_keyboard km_kbp_keyboard; +typedef struct km_kbp_state km_kbp_state; +typedef struct km_kbp_options km_kbp_options; + +// Forward declarations +// +typedef struct km_kbp_option_item km_kbp_option_item; + +/*``` +### Error Handling +Error handling and success failure notification are communicated through a +general mechanism similar to COM’s `HRESULT` scheme (unlike COM, any non-zero +value is an error). Any functions that can fail will always return a status +value and all results are returned via outparams passed to the function. +```c +*/ +enum km_kbp_status_codes { + KM_KBP_STATUS_OK = 0, + KM_KBP_STATUS_NO_MEM = 1, + KM_KBP_STATUS_IO_ERROR = 2, + KM_KBP_STATUS_INVALID_ARGUMENT = 3, + KM_KBP_STATUS_KEY_ERROR = 4, + KM_KBP_STATUS_INSUFFICENT_BUFFER = 5, + KM_KBP_STATUS_INVALID_UTF = 6, + KM_KBP_STATUS_INVALID_KEYBOARD = 7, + KM_KBP_STATUS_OS_ERROR = 0x80000000 +}; + +/* +``` +The final status code KM_KBP_STATUS_OS_ERROR is intended to allow encapsulating +a platform error code; the remaining 31 low bits are the error code returned by +the OS for cases where the failure mode is platform specific. For HRESULT codes +this only permits failure codes to be passed. + + +### Context +The context is the text prior to the insertion point (caret, cursor). +The context is constructed by the Platform layer, typically by interrogating the +Client Application. The context will be updated by the engine for keystroke +events. If the Platform layer code caches the context, the context should be +reset when a context state change is detected. Context state changes can occur +when the user uses the mouse to move the insertion point, uses cursor keys, +switches applications or input fields, or presses hotkeys such as Ctrl+N to +start a new document. The full set of context state change triggers is up to the +Platform layer. + +Context can also contain positional Markers (also known as 'deadkeys' in kmn +keyboards), which are transitory state flags that are erased whenever a context +state change is detected. Markers are always controlled by the Engine. + +Contexts are always owned by their state. They may be set to a list of +context_items or interrogated for their current list of context items. +```c +*/ +enum km_kbp_context_type { + KM_KBP_CT_END, + KM_KBP_CT_CHAR, + KM_KBP_CT_MARKER +}; + +typedef struct { + uint8_t type; + uint8_t _reserved[3]; + union { + km_kbp_usv character; + uint32_t marker; + }; +} km_kbp_context_item; + +#define KM_KBP_CONTEXT_ITEM_END {KM_KBP_CT_END, {0,}, {0,}} +/* +``` +### `km_kbp_context_items_from_utf16` +##### Description: +Convert a UTF16 encoded Unicode string into an array of `km_kbp_context_item` +structures. Allocates memory as needed. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated for the + output buffer. +- `KM_KBP_STATUS_INVALID_UTF`: In the event the UTF16 string cannot be decoded + because it contains unpaired surrogate codeunits. +##### Parameters: +- __text__: a pointer to a null terminated array of utf16 encoded data. +- __out_ptr__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing the + representation of the input string. + Terminated with a type of `KM_KBP_CT_END`. Must be disposed of with + `km_kbp_context_items_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_from_utf16(km_kbp_cp const *text, + km_kbp_context_item **out_ptr); + +/* +``` +### `km_kbp_context_items_from_utf8` +##### Description: +Convert an UTF8 encoded Unicode string into an array of `km_kbp_context_item` +structures. Allocates memory as needed. +##### Status: +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event it cannot allocate enough memory for the + output buffer. +- `KM_KBP_STATUS_INVALID_UTF`: In the event the UTF8 string cannot be +decoded. +##### Parameters: +- __text__: a pointer to a null terminated array of utf8 encoded data. +- __out_ptr__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing the + representation of the input string. + Terminated with a type of `KM_KBP_CT_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_from_utf8(char const *text, + km_kbp_context_item **out_ptr); + +/* +``` +### `km_kbp_context_items_to_utf16` +##### Description: +Convert a context item array into a UTF-16 encoded string placing it into +the supplied buffer of specified size, and return the number codepoints +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. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_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_kbp_context_item`. + Must be terminated with a type of `KM_KBP_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-16 string into. May be be + null to request size calculation. +- __buf_size__: a pointer to the result variable: + A pointer the size of the supplied buffer in codeunits or filled with the + size required if `buf` is null. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_to_utf16(km_kbp_context_item const *item, + km_kbp_cp *buf, + size_t *buf_size); + +/* +``` +### `km_kbp_context_items_to_utf8` +##### Description: +Convert a context item array into a UTF-8 encoded string placing it into +the supplied buffer of specified size, and return the number codepoints +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. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_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_kbp_context_item`. + Must be terminated with a type of `KM_KBP_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-8 string into. May be be + null to request size calculation. +- __buf_size__: a pointer to the result variable: + A pointer the size of the supplied buffer in codeunits or filled with the + size required if `buf` is null. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_items_to_utf8(km_kbp_context_item const *item, + char *buf, + size_t *buf_size); + +/* +``` +### `km_kbp_context_items_dispose` +##### Description: +Free the allocated memory belonging to a `km_kbp_context_item` array previously +returned by `km_kbp_context_items_from_utf16` or `km_kbp_context_get` +##### Parameters: +- __context_items__: A pointer to the start of the `km_kbp_context_item` array + to be disposed of. + +```c +*/ +KMN_API +void +km_kbp_context_items_dispose(km_kbp_context_item *context_items); + +/* +``` +### `km_kbp_context_set` +##### Description: +Replace the contents of the current context with a new sequence of +`km_kbp_context_item` entries. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated to + grow the context buffer internally. +##### Parameters: +- __context__: A pointer to an opaque context object +- __context_items__: A pointer to the start of the `km_kbp_context_item` + array containing the new context. It must be terminated with an item + of type `KM_KBP_CT_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_set(km_kbp_context *context, + km_kbp_context_item const *context_items); + +/* +``` +### `km_kbp_context_get` +##### Description: +Copies all items in the context into a new array and returns the new array. +This must be disposed of by caller using `km_kbp_context_items_dispose`. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated for the + output buffer. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_kbp_context_item`. +- __out__: a pointer to the result variable: + A pointer to the start of the `km_kbp_context_item` array containing a + copy of the context. Terminated with a type of `KM_KBP_CT_END`. Must be + disposed of with `km_kbp_context_items_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_get(km_kbp_context const *context_items, + km_kbp_context_item **out); + +/* +``` +### `km_kbp_context_clear` +##### Description: +Removes all context_items from the internal array. If `context` is +null, has no effect. +##### Parameters: +- __context__: A pointer to an opaque context object + +```c +*/ +KMN_API +void +km_kbp_context_clear(km_kbp_context *); + +/* +``` +### `km_kbp_context_length` +##### Description: +Return the number of items in the context. +##### Return: +The number of items in the context, and will return 0 if passed a null `context` +pointer. +##### Parameters: +- __context__: A pointer to an opaque context object + +```c +*/ +KMN_API +size_t +km_kbp_context_length(km_kbp_context *); + +/* +``` +### `km_kbp_context_append` +##### Description: +Add more items to the end (insertion point) of the context. If these exceed the +maximum context length the same number of items will be dropped from the +beginning of the context. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event not enough memory can be allocated to + grow the context buffer internally. +##### Parameters: +- __context__: A pointer to an opaque context object. +- __context_items__: A pointer to the start of the `KM_KBP_CT_END` terminated + array of `km_kbp_context_item` to append. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_append(km_kbp_context *context, + km_kbp_context_item const *context_items); + +/* +``` +### `km_kbp_context_shrink` +##### Description: +Remove a specified number of items from the end of the context, optionally +add up to the same number of the supplied items to the front of the context. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: in the event it cannot allocated enough memory to grow + the context internally. +##### Parameters: +- __context__: A pointer to an opaque context object. +- __num__: The number of items to remove from the end of context. +- __context_items__: Pointer to the start of the `KM_KBP_CT_END` terminated + array of `km_kbp_context_item` to add to the front. Up to `num` items will + be prepended. This may be null if not required. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_context_shrink(km_kbp_context *context, + size_t num, + km_kbp_context_item const *prefix); + +/* +``` +### Action Items +These provide 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. +```c +*/ +typedef struct { + uint8_t type; + uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; + union { + uintptr_t marker; // MARKER type + km_kbp_option_item const * option; // OPT types + km_kbp_usv character; // CHAR type + km_kbp_virtual_key vkey; // VKEY types + size_t erased; // BACK type + }; +} km_kbp_action_item; + +enum km_kbp_action_type { + KM_KBP_IT_END = 0, // Marks end of action items list. + KM_KBP_IT_CHAR = 1, // A Unicode character has been generated. + KM_KBP_IT_MARKER = 2, // Correlates to kmn's "deadkey" markers. + KM_KBP_IT_ALERT = 3, // The keyboard has triggered a alert/beep/bell. + KM_KBP_IT_BACK = 4, // Delete the codepoint preceding the insertion point. + KM_KBP_IT_PERSIST_OPT = 5, // The indicated option needs to be stored. + KM_KBP_IT_EMIT_KEYSTROKE = 6, // Emit the current keystroke to the application + KM_KBP_IT_INVALIDATE_CONTEXT = 7, + // The processor requests that the context buffer be cleared; + // for applications where context is cached, this clears the context; + // for applications where context is read from the focused text store, + // the context is just re-read and markers flushed. + KM_KBP_IT_MAX_TYPE_ID +}; + + +/* +``` +### Options +A state’s default options are set from the keyboard at creation time and the +environment. The Platform layer is then is expected to apply any persisted +options it is maintaining. Options are passed into and out of API functions as +simple C arrays of `km_kbp_option_item` terminated with a `KM_KBP_OPTIONS_END` +sentinel value. A state's options are exposed and manipulatable via the +`km_kbp_options` API. All option values are of type C string. + +During processing when the Platform layer finds a PERSIST action type it should +store the updated option in the appropriate place, based on its scope. +For RESET the processor will apply the pristine value from the original scope, +the Platform layer should update that only if it manages a previously persisted +value. +```c +*/ + +enum km_kbp_option_scope { + KM_KBP_OPT_UNKNOWN = 0, + KM_KBP_OPT_KEYBOARD = 1, + KM_KBP_OPT_ENVIRONMENT = 2, + KM_KBP_OPT_MAX_SCOPES +}; + +struct km_kbp_option_item { + km_kbp_cp const * key; + km_kbp_cp const * value; + uint8_t scope; // Scope which an option belongs to. +}; + +#define KM_KBP_OPTIONS_END { 0, 0, 0 } + + +/* +``` +### `km_kbp_options_list_size` +##### Description: +Return the length of a terminated `km_kbp_option_item` array (options +list). +##### Return: +The number of items in the list or 0 if `opts` is null. +##### Parameters: +- __opts__: A pointer to a `KM_KBP_OPTIONS_END` terminated array of + `km_kbp_option_item` values. + +```c +*/ +KMN_API +size_t +km_kbp_options_list_size(km_kbp_option_item const *opts); + +/* +``` +### `km_kbp_options_lookup` +##### Description: +Lookup an option based on its key, in an options list. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null, or + if the scope is invalid. +- `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. +##### Parameters: +- __state__: An opaque pointer to a state object. +- __scope__: Which key-value store to interrogate. +- __key__: A UTF-16 string that matches the key in the target `km_kbp_option_item`. +- __value__: A pointer to the result variable: + A pointer to a copy of the UTF-16 string value; undefined if return status + is anything other than `KM_KBP_STATUS_OK`. This memory must be disposed of + with `km_kbp_cp_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_lookup(km_kbp_state const *state, + uint8_t scope, + km_kbp_cp const *key, + km_kbp_cp const **value); + +/* +``` +### `km_kbp_options_update` +##### Description: +Adds or updates one or more options from a list of `km_kbp_option_item`s. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +- `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. +##### Parameters: +- __state__: An opaque pointer to a `km_kbp_state`. +- __new_opts__: An array of `km_kbp_option_item` objects to update or add. Must be + terminated with `KM_KBP_OPTIONS_END`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_update(km_kbp_state *state, + km_kbp_option_item const *new_opts); + +/* +``` +### `km_kbp_options_to_json` +##### Description: +Export the contents of a `km_kbp_options` array to a JSON formatted document and +place it in the supplied buffer, reporting how much space was used. If null is +passed as the buffer the number of bytes required is returned in `space`. If +there is insufficent space to hold the document the contents of the buffer is +undefined. The returned buffer uses UTF-8 encoding. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +##### Parameters: +- __opts__: An opaque pointer to an options object. +- __buf__: A pointer to the buffer to place the C string containing the JSON +document into, can be null. +- __space__: A pointer to a size_t variable. This variable must contain the +number of bytes available in the buffer pointed to by `buf`, unless `buf` is +null. On return it will hold how many bytes were used. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_options_to_json(km_kbp_options const *opts, + char *buf, + size_t *space); + + +/* +``` +### Keyboards +A keyboard is a set of rules and transforms in a Processor specific format for +transforming key events into action items. The keyboard is parsed and loaded by +the processsor and made available in an immutable fashion for use with any number +of state objects. +```c +*/ +typedef struct { + km_kbp_cp const * version_string; // Processor specific version string. + km_kbp_cp const * id; // Keyman keyboard ID string. + km_kbp_path_name folder_path; // Path to the unpacked folder containing + // the keyboard and associated resources. + km_kbp_option_item const * default_options; +} km_kbp_keyboard_attrs; + +/* +``` +### `km_kbp_keyboard_load` +##### Description: +Parse and load keyboard from the supplied path and a pointer to the loaded keyboard +into the out paramter. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +- `KM_KBP_STATUS_IO_ERROR`: + In the event the keyboard file is unparseable for any reason +- `KM_KBP_STATUS_INVALID_ARGUMENT`: + In the event the file doesn't exist or is inaccesible or `keyboard` is null. +- `KM_KBP_STATUS_OS_ERROR`: Bit 31 (high bit) set, bits 0-30 are an OS-specific + error code. +##### Parameters: +- __kb_path__: On Windows, a UTF-16 string; on other platforms, a C string: + contains a valid path to the keyboard file. +- __keyboard__: A pointer to result variable: + A pointer to the opaque keyboard object returned by the Processor. This + memory must be freed with a call to `km_kbp_keyboard_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_keyboard_load(km_kbp_path_name kb_path, + km_kbp_keyboard **keyboard); + +/* +``` +### `km_kbp_keyboard_dispose` +##### Description: +Free the allocated memory belonging to an opaque keyboard object previously +returned by `km_kbp_keyboard_load`. +##### Parameters: +- __keyboard__: A pointer to the opaque keyboard object to be + disposed of. + +```c +*/ +KMN_API +void +km_kbp_keyboard_dispose(km_kbp_keyboard *keyboard); + +/* +``` +### `km_kbp_keyboard_get_attrs` +##### Description: +Returns the const internal attributes of the keyboard. This structure is valid +for the lifetime of the opaque keyboard object. Do not modify the returned data. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +##### Parameters: +- __keyboard__: A pointer to the opaque keyboard object to be queried. +- __out__: A pointer to the result: + A pointer to a `km_kbp_keyboard_attrs` structure. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, + km_kbp_keyboard_attrs const **out); + +/* +``` +### State +A State object maintains all per keyboard related state including context +and dynamic options ("option stores" in kmn format). + +```c +*/ + +/* +``` +### `km_kbp_state_create` +##### Description: +Create a keyboard processor state object, maintaining state for the keyboard in +the environment passed. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: + In the event memory is unavailable to allocate a state object. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: + In the event the `keyboard` or `out` pointer are null. +##### Parameters: +- __keyboard__: +A pointer to the opaque keyboard object this object will hold state for. +- __env__: +The array of `km_kbp_option_item` key/value pairs used to initialise the +environment, terminated by `KM_KBP_OPTIONS_END`. +- __out__: +A pointer to result variable: A pointer to the opaque state object +returned by the Processor, initalised to maintain state for `keyboard`. +This must be disposed of by a call to `km_kbp_state_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_create(km_kbp_keyboard *keyboard, + km_kbp_option_item const *env, + km_kbp_state **out); + +/* +``` +### `km_kbp_state_clone` +##### Description: +Clone an existing opaque state object. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: +In the event memory is unavailable to allocate a state object. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: +In the event the `state` or `out` pointer are null. +##### Parameters: +- __state__: +A pointer to the opaque statea object to be cloned. +- __out__: +A pointer to result variable: A pointer to the opaque state object +returned by the Processor, cloned from the existing object `state`. This +must be disposed of by a call to `km_kbp_state_dispose`. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_clone(km_kbp_state const *state, + km_kbp_state **out); + +/* +``` +### `km_kbp_state_dispose` +##### Description: +Free the allocated resources belonging to a `km_kbp_state` object previously +returned by `km_kbp_state_create` or `km_kbp_state_clone`. After this all +pointers previously returned by any km_kbp_state family of calls will become +invalid. +##### Parameters: +- __state__: A pointer to the opaque state object to be disposed. + +```c +*/ +KMN_API +void +km_kbp_state_dispose(km_kbp_state *state); + +/* +``` +### `km_kbp_state_context` +##### Description: +Get access to the state object's context. +##### Return: +A pointer to an opaque state object. This pointer is valid for the lifetime +of the state object. If null is passed in, then null is returned. +##### Parameters: +- __state__: A pointer to the opaque state object to be queried. + +```c +*/ +KMN_API +km_kbp_context * +km_kbp_state_context(km_kbp_state *state); + +/* +``` +### `km_kpb_state_options` +##### Description: +Get access to the state object's options. +##### Return: +A pointer to an opaque state object. This pointer is valid for the lifetime +of the state object. If null is passed in, then null is returned. +##### Parameters: +- __state__: A pointer to the opaque state object to be queried. + +```c +*/ +KMN_API +km_kbp_options * +km_kbp_state_options(km_kbp_state *state); + +/* +``` +### `km_kbp_state_action_items` +##### Description: +Get the list of action items generated by the last call to +`km_kbp_process_event`. +##### Return: +A pointer to a `km_kbp_action_item` list, of `*num_items` in length. This data +becomes invalid when the state object is destroyed, or after a call to +`km_kbp_process_event`. Do not modify the contents of this data. The returned +array is terminated with a `KM_KBP_IT_END` entry. +##### Parameters: +- __state__: A pointer to the opaque `km_kbp_state` object to be queried. +- __num_items__: +A pointer to a result variable: The number of items in the action item list +including the `KM_KBP_IT_END` terminator. May be null if not that +information is required. + +```c +*/ +KMN_API +km_kbp_action_item const * +km_kbp_state_action_items(km_kbp_state const *state, + size_t *num_items); + +/* +``` +### `km_kpb_state_to_json` +##### Description: +Export the internal state of a `km_kbp_state` object to a JSON format document +and place it in the supplied buffer, reporting how much space was used. If null +is passed as the buffer the number of bytes required is returned. If there is +insufficent space to hold the document, the contents of the buffer is undefined. +The encoding of the returned data is UTF-8. + +__WARNING__: The structure and format of the JSON document while independently +versioned is not part of this API and is intended solely for use in diagnostics +or by development and debugging tools which are aware of processor +implementation details. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. +##### Parameters: +- __state__: An pointer to an opaque state object. +- __buf__: A pointer to the buffer to place the C string containing the JSON +document into. May be null. +- __space__: A pointer to a size_t variable. This variable must contain the +number of bytes available in the buffer pointed to by `buf`, unless `buf` is +null. On return it will hold how many bytes were used. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_state_to_json(km_kbp_state const *state, + char *buf, + size_t *space); + +/* +``` +### Processor +```c +*/ +typedef struct { + size_t max_context; // Maximum context size supported by processor. + uint16_t current; // Current API number supported. + uint16_t revision; // Implementation number of current API. + uint16_t age; // current - age == Oldest API number supported. + uint16_t technology; // A bit field specifiying which Keyboard + // technologies the engine supports. + char const *vendor; // Implementor of the processor. +} km_kbp_attr; + +enum km_kbp_tech_value { + KM_KBP_TECH_UNSPECIFIED = 0, + KM_KBP_TECH_MOCK = 1 << 0, + KM_KBP_TECH_KMX = 1 << 1, + KM_KBP_TECH_LDML = 1 << 2 +}; + + +/* +``` +### `km_kbp_get_engine_attrs` +##### Description: +Get access processors attributes describing version and technology implemented. +##### Return: +A pointer to a `km_kbp_attr` structure. Do not modify the contents of this +structure. +##### Parameters: +- __state__: An opaque pointer to an `km_kbp_state`. +```c +*/ +KMN_API +km_kbp_attr const * +km_kbp_get_engine_attrs(km_kbp_state const *state); + +/* +``` +### `km_kbp_cp_dispose` +##### Description: +Free the allocated memory belonging to a `km_kbp_cp` array previously +returned by `km_kbp_options_lookup`. +##### Parameters: +- __cp__: A pointer to the start of the `km_kbp_cp` array + to be disposed of. + +```c +*/ +KMN_API +void +km_kbp_cp_dispose(km_kbp_cp const *cp); + +/* +``` +### `km_kbp_process_event` +##### Description: +Run the keyboard on an opaque state object with the provided virtual key and modifer +key state. Updates the state object as appropriate and fills out its action list. + +The action list will be cleared at the start of this call; options and context in +the state may also be modified. +##### Return status: +- `KM_KBP_STATUS_OK`: On success. +- `KM_KBP_STATUS_NO_MEM`: +In the event memory is unavailable to allocate internal buffers. +- `KM_KBP_STATUS_INVALID_ARGUMENT`: +In the event the `state` pointer is null or an invalid virtual key or modifier +state is passed. + +##### Parameters: +- __state__: A pointer to the opaque state object. +- __vk__: A virtual key to be processed. +- __modifier_state__: +The combinations of modifier keys set at the time key `vk` was pressed, bitmask +from the `km_kbp_modifier_state` enum. + +```c +*/ +KMN_API +km_kbp_status +km_kbp_process_event(km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state); + +#if defined(__cplusplus) +} // extern "C" +#endif +/*``` +*/ diff --git a/include/keyman/keyboardprocessor.h.in b/include/keyman/keyboardprocessor.h.in index e278144..5d1ba06 100644 --- a/include/keyman/keyboardprocessor.h.in +++ b/include/keyman/keyboardprocessor.h.in @@ -478,8 +478,6 @@ typedef struct { uintptr_t marker; // MARKER type km_kbp_option_item const * option; // OPT types km_kbp_usv character; // CHAR type - km_kbp_virtual_key vkey; // VKEY types - size_t erased; // BACK type }; } km_kbp_action_item; @@ -491,11 +489,11 @@ enum km_kbp_action_type { KM_KBP_IT_BACK = 4, // Delete the codepoint preceding the insertion point. KM_KBP_IT_PERSIST_OPT = 5, // The indicated option needs to be stored. KM_KBP_IT_EMIT_KEYSTROKE = 6, // Emit the current keystroke to the application - KM_KBP_IT_INVALIDATE_CONTEXT = 7, - // The processor requests that the context buffer be cleared; - // for applications where context is cached, this clears the context; - // for applications where context is read from the focused text store, - // the context is just re-read and markers flushed. + KM_KBP_IT_INVALIDATE_CONTEXT = 7, + // The processor requests that the context buffer be cleared; + // for applications where context is cached, this clears the context; + // for applications where context is read from the focused text store, + // the context is just re-read and markers flushed. KM_KBP_IT_MAX_TYPE_ID }; @@ -554,7 +552,7 @@ km_kbp_options_list_size(km_kbp_option_item const *opts); /* ``` -### `km_kbp_options_lookup` +### `km_kbp_state_option_lookup` ##### Description: Lookup an option based on its key, in an options list. ##### Return status: @@ -567,22 +565,21 @@ Lookup an option based on its key, in an options list. - __scope__: Which key-value store to interrogate. - __key__: A UTF-16 string that matches the key in the target `km_kbp_option_item`. - __value__: A pointer to the result variable: - A pointer to a copy of the UTF-16 string value; undefined if return status - is anything other than `KM_KBP_STATUS_OK`. This memory must be disposed of - with `km_kbp_cp_dispose`. - + A pointer to a UTF-16 string value owned by the state or keyboard object at + the time of the call. This pointer is only valid *until* the next call to any + function on this API and should be used immediately. ```c */ KMN_API km_kbp_status -km_kbp_options_lookup(km_kbp_state const *state, +km_kbp_state_option_lookup(km_kbp_state const *state, uint8_t scope, km_kbp_cp const *key, km_kbp_cp const **value); /* ``` -### `km_kbp_options_update` +### `km_kbp_state_options_update` ##### Description: Adds or updates one or more options from a list of `km_kbp_option_item`s. ##### Return status: @@ -591,7 +588,7 @@ Adds or updates one or more options from a list of `km_kbp_option_item`s. - `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. - `KM_KBP_STATUS_KEY_ERROR`: The key cannot be found. ##### Parameters: -- __state__: An opaque pointer to a `km_kbp_state`. +- __state__: An opaque pointer to a state object. - __new_opts__: An array of `km_kbp_option_item` objects to update or add. Must be terminated with `KM_KBP_OPTIONS_END`. @@ -599,12 +596,12 @@ Adds or updates one or more options from a list of `km_kbp_option_item`s. */ KMN_API km_kbp_status -km_kbp_options_update(km_kbp_state *state, +km_kbp_state_options_update(km_kbp_state *state, km_kbp_option_item const *new_opts); /* ``` -### `km_kbp_options_to_json` +### `km_kbp_state_options_to_json` ##### Description: Export the contents of a `km_kbp_options` array to a JSON formatted document and place it in the supplied buffer, reporting how much space was used. If null is @@ -616,7 +613,7 @@ undefined. The returned buffer uses UTF-8 encoding. - `KM_KBP_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. - `KM_KBP_STATUS_NO_MEM`: In the event an internal memory allocation fails. ##### Parameters: -- __opts__: An opaque pointer to an options object. +- __opts__: An opaque pointer to a state object. - __buf__: A pointer to the buffer to place the C string containing the JSON document into, can be null. - __space__: A pointer to a size_t variable. This variable must contain the @@ -627,7 +624,7 @@ null. On return it will hold how many bytes were used. */ KMN_API km_kbp_status -km_kbp_options_to_json(km_kbp_options const *opts, +km_kbp_state_options_to_json(km_kbp_state const *state, char *buf, size_t *space); @@ -751,7 +748,7 @@ This must be disposed of by a call to `km_kbp_state_dispose`. */ KMN_API km_kbp_status -km_kbp_state_create(km_kbp_keyboard const *keyboard, +km_kbp_state_create(km_kbp_keyboard *keyboard, km_kbp_option_item const *env, km_kbp_state **out); @@ -817,23 +814,6 @@ km_kbp_state_context(km_kbp_state *state); /* ``` -### `km_kpb_state_options` -##### Description: -Get access to the state object's options. -##### Return: -A pointer to an opaque state object. This pointer is valid for the lifetime -of the state object. If null is passed in, then null is returned. -##### Parameters: -- __state__: A pointer to the opaque state object to be queried. - -```c -*/ -KMN_API -km_kbp_options * -km_kbp_state_options(km_kbp_state *state); - -/* -``` ### `km_kbp_state_action_items` ##### Description: Get the list of action items generated by the last call to @@ -931,22 +911,6 @@ km_kbp_get_engine_attrs(km_kbp_state const *state); /* ``` -### `km_kbp_cp_dispose` -##### Description: -Free the allocated memory belonging to a `km_kbp_cp` array previously -returned by `km_kbp_options_lookup`. -##### Parameters: -- __cp__: A pointer to the start of the `km_kbp_cp` array - to be disposed of. - -```c -*/ -KMN_API -void -km_kbp_cp_dispose(km_kbp_cp const *cp); - -/* -``` ### `km_kbp_process_event` ##### Description: Run the keyboard on an opaque state object with the provided virtual key and modifer diff --git a/include/keyman/keyboardprocessor_bits.h b/include/keyman/keyboardprocessor_bits.h index ac34757..d844d4e 100644 --- a/include/keyman/keyboardprocessor_bits.h +++ b/include/keyman/keyboardprocessor_bits.h @@ -28,6 +28,8 @@ #if defined _WIN32 || defined __CYGWIN__ typedef wchar_t const * km_kbp_path_name; + #define _KM_KBP_PATH_SEPARATOR (L'\\') + #define _KM_KBP_EXT_SEPARATOR (L'.') #if defined __GNUC__ // These three will be redefined for Windows #undef _kmn_export_flag #undef _kmn_import_flag @@ -42,6 +44,8 @@ #define _kmn_static_flag #else typedef char const * km_kbp_path_name; + #define _KM_KBP_PATH_SEPARATOR ('/') + #define _KM_KBP_EXT_SEPARATOR ('.') #endif #if defined KMN_KBP_STATIC diff --git a/include/keyman/meson.build b/include/keyman/meson.build index 5e5f127..2b6c595 100644 --- a/include/keyman/meson.build +++ b/include/keyman/meson.build @@ -6,7 +6,7 @@ # History: 6 Oct 2018 - TSE - Move into keyman folder. # -ver = meson.project_version().split('.') +ver = lib_version.split('.') cfg = configuration_data() cfg.set('lib_curr', ver[0]) diff --git a/meson.build b/meson.build index 414578e..62ebb83 100644 --- a/meson.build +++ b/meson.build @@ -6,13 +6,15 @@ # project('keyboardprocessor', 'cpp', 'c', - version: '0.0.0', + version: '11.0.101', license: 'MIT', default_options : ['buildtype=release', 'cpp_std=c++14']) compiler = meson.get_compiler('cpp') +lib_version = '0.0.0' + if compiler.get_id() == 'msvc' add_global_arguments('/source-charset:utf-8', language: ['c', 'cpp']) endif diff --git a/src/context.hpp b/src/context.hpp index b2b7fc9..b344e06 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -20,7 +20,24 @@ namespace kbp // This will likely be replaced with a class implementing a more space // efficient data structure such as a ring buffer or bounded queue. -using context = std::list<km_kbp_context_item>; +class context: public std::list<km_kbp_context_item> +{ +public: + void push_character(km_kbp_usv); + void push_marker(uint32_t); +}; + + +inline +void context::push_character(km_kbp_usv usv) { + emplace_back(km_kbp_context_item { KM_KBP_CT_CHAR, {0,}, {usv} }); +} + + +inline +void context::push_marker(uint32_t marker) { + emplace_back(km_kbp_context_item { KM_KBP_CT_MARKER, {0,}, {marker} }); +} } // namespace kbp } // namespace km diff --git a/src/json.cpp b/src/json.cpp index cec8dd1..40cb861 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -8,11 +8,13 @@ 25 Oct 2018 - TSE - Relicensed under the MIT license for inclusion in the Keyman project. */ - -#include <cstdio> +#include <cassert> +#include <iomanip> #include <limits> + #include "json.hpp" + #if defined(_MSC_VER) #define FORMAT_INTMAX "%lli" #define FORMAT_UINTMAX "%llu" diff --git a/src/json.hpp b/src/json.hpp index 0a34a58..985e7af 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -11,12 +11,12 @@ */ #pragma once -#include <cassert> #include <cstdint> -#include <iomanip> #include <ostream> #include <vector> +#include "utfcodec.hpp" + class json { // Prevent copying @@ -113,8 +113,13 @@ json & json::operator << (json::_context_t ctxt) throw() return *this; } +template<typename C> +inline +json & operator << (json & j, std::basic_string<C> const & s) throw() { return j << json::string(convert<C,char>(s).c_str()); } + +template<typename C> inline -json & operator << (json & j, std::string const & s) throw() { return j << json::string(s.c_str()); } +json & operator << (json & j, C const * s) throw() { return j << json::string(convert<C,char>(s).c_str()); } inline json & operator << (json & j, signed char d) throw() { return j << json::integer(d); } diff --git a/src/keyboard.cpp b/src/keyboard.cpp index 0f44939..ac5e978 100644 --- a/src/keyboard.cpp +++ b/src/keyboard.cpp @@ -5,72 +5,62 @@ Authors: Tim Eves (TSE) History: 7 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp */ - -#include <codecvt> #include "keyboard.hpp" #include "json.hpp" -#include "processor.hpp" using namespace km::kbp; -std::string utf16_to_utf8(std::u16string utf16_string); -keyboard::keyboard(std::filesystem::path const & path) -: _keyboard_id(path.stem().u16string()), - _version_string(u"3.145"), - _folder_path(path.parent_path()), - _default_opts {KM_KBP_OPTIONS_END} +inline +void keyboard_attributes::render() { - version_string = _version_string.c_str(); - id = _keyboard_id.c_str(); - folder_path = _folder_path.native().c_str(); - - if (path.extension() == ".kmx" || - path.extension() == ".KMX") { // Some legacy packages may include upper-case file extensions - _processor = new kmx_processor(this); - } - else if (path.extension() == ".mock") { - _processor = new mock_processor(this); - } - else { - _processor = new null_processor(this); - } - + // Make attributes point to the stored values above. + id = _keyboard_id.c_str(); + version_string = _version_string.c_str(); + folder_path = _folder_path.c_str(); default_options = _default_opts.data(); } -json & km::kbp::operator << (json & j, km::kbp::keyboard const & kb) -{ - j << json::object - << "id" << utf16_to_utf8(kb.id) - << "folder" << kb._folder_path.string() - << "version" << utf16_to_utf8(kb.version_string) - << "rules" << json::array << json::close; - return j << json::close; +keyboard_attributes::keyboard_attributes(std::u16string const & kbid, + std::u16string const & version, + path_type const & path, + options_store const &opts) +: _keyboard_id(kbid), + _version_string(version), + _folder_path(path), + _default_opts(opts) +{ + // Ensure that the default_options array will be properly terminated. + _default_opts.emplace_back(); + render(); } -/* - This function exists because of a bug in Visual Studio 2015 and 2017: - https://social.msdn.microsoft.com/Forums/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error?forum=vcgeneral - https://stackoverflow.com/a/35103224/1836776 -*/ - -#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ -std::string utf16_to_utf8(std::u16string utf16_string) +keyboard_attributes::keyboard_attributes(keyboard_attributes &&rhs) +: _keyboard_id(std::move(rhs._keyboard_id)), + _version_string(std::move(rhs._version_string)), + _folder_path(std::move(rhs._folder_path)), + _default_opts(std::move(rhs._default_opts)) { - std::wstring_convert<std::codecvt_utf8_utf16<int16_t>, int16_t> convert; - auto p = reinterpret_cast<const int16_t *>(utf16_string.data()); - return convert.to_bytes(p, p + utf16_string.size()); + rhs.id = rhs.version_string = nullptr; + render(); } -#else -std::string utf16_to_utf8(std::u16string utf16_string) +keyboard_attributes & keyboard_attributes::operator = (keyboard_attributes &&rhs) { - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; - return convert.to_bytes(utf16_string); + return *new (this) keyboard_attributes(std::move(rhs)); } -#endif + +json & km::kbp::operator << (json & j, km::kbp::keyboard_attributes const & kb) +{ + j << json::object + << "id" << kb.id + << "folder" << kb._folder_path + << "version" << kb.version_string + << "rules" << json::array << json::close; + + return j << json::close; +} diff --git a/src/keyboard.hpp b/src/keyboard.hpp index 1692bdf..57f8128 100644 --- a/src/keyboard.hpp +++ b/src/keyboard.hpp @@ -8,17 +8,13 @@ #pragma once -#include <experimental/filesystem> #include <string> +#include <vector> #include <keyman/keyboardprocessor.h> #include "option.hpp" -#include "processor.hpp" - -namespace std { - namespace filesystem = std::experimental::filesystem; -} +#include "path.hpp" // Forward declarations class json; @@ -26,30 +22,41 @@ class json; namespace km { namespace kbp { - - class keyboard : public km_kbp_keyboard_attrs + class keyboard_attributes : public km_kbp_keyboard_attrs { - std::u16string const _keyboard_id; - std::u16string const _version_string; - std::filesystem::path const _folder_path; - std::vector<km_kbp_option_item> _default_opts; - abstract_processor *_processor; + std::u16string _keyboard_id; + std::u16string _version_string; + kbp::path _folder_path; + std::vector<option> _default_opts; + + void render(); + public: - keyboard(std::filesystem::path const &); - ~keyboard() { - delete _processor; - } + using options_store = decltype(_default_opts); + using path_type = decltype(_folder_path); - friend json & operator << (json &, km::kbp::keyboard const &); + keyboard_attributes() + : km_kbp_keyboard_attrs {nullptr, nullptr, nullptr, nullptr} {} + keyboard_attributes(keyboard_attributes const &) = delete; + keyboard_attributes(keyboard_attributes &&); - std::vector<km_kbp_option_item> *default_opts() { return & _default_opts; } + keyboard_attributes(std::u16string const & id, + std::u16string const & version, + path_type const & path, + options_store const &opts); - kbp::abstract_processor const & processor() const noexcept { return *_processor; } + keyboard_attributes & operator = (keyboard_attributes const &) = delete; + keyboard_attributes & operator = (keyboard_attributes &&); + + friend json & operator << (json &, km::kbp::keyboard_attributes const &); + + options_store const & default_opts_store() const noexcept { return _default_opts; } + options_store & default_opts_store() noexcept { return _default_opts; } + + path_type const & path() const noexcept { return _folder_path; } }; - json & operator << (json &, km::kbp::keyboard const &); + json & operator << (json &, km::kbp::keyboard_attributes const &); } // namespace kbp } // namespace km - -struct km_kbp_keyboard : public km::kbp::keyboard {}; diff --git a/src/km_kbp_context_api.cpp b/src/km_kbp_context_api.cpp index 334a2fa..23915ac 100644 --- a/src/km_kbp_context_api.cpp +++ b/src/km_kbp_context_api.cpp @@ -10,7 +10,6 @@ */ #include <cassert> #include <algorithm> -#include <iterator> #include <vector> #include <keyman/keyboardprocessor.h> diff --git a/src/km_kbp_keyboard_api.cpp b/src/km_kbp_keyboard_api.cpp index 8067e73..dbc6c17 100644 --- a/src/km_kbp_keyboard_api.cpp +++ b/src/km_kbp_keyboard_api.cpp @@ -9,46 +9,51 @@ into keyboard.hpp */ #include <cassert> -#include <algorithm> -#include <experimental/filesystem> -#include <iterator> -#include <sstream> -#include <unordered_map> -#include <string> -#include <vector> #include <keyman/keyboardprocessor.h> -#include <json.hpp> - #include "keyboard.hpp" -#include "option.hpp" +#include "processor.hpp" +#include "kmx/kmx_processevent.hpp" +#include "mock/mock_processor.hpp" + +using namespace km::kbp; +namespace +{ + abstract_processor * processor_factory(path const & kb_path) + { + // Some legacy packages may include upper-case file extensions + if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") { + return new kmx_processor(kb_path); + } + else if (kb_path.suffix() == ".mock") { + return new mock_processor(kb_path); + } + else { + return new null_processor(); + } + } +} km_kbp_status -km_kbp_keyboard_load(km_kbp_path_name kb_path, - km_kbp_keyboard **keyboard) +km_kbp_keyboard_load(km_kbp_path_name kb_path, km_kbp_keyboard **keyboard) { assert(keyboard); if (!keyboard) return KM_KBP_STATUS_INVALID_ARGUMENT; - //auto stat = std::filesystem::status(kb_path); - // - // if (stat.type() != std::filesystem::file_type::regular) - // return KM_KBP_STATUS_INVALID_ARGUMENT; - try { - km::kbp::keyboard *kp = new km::kbp::keyboard(kb_path); - km_kbp_status status = kp->processor().validate(); + abstract_processor *kp = processor_factory(kb_path); + km_kbp_status status = kp->validate(); if (status != KM_KBP_STATUS_OK) { delete kp; return status; } *keyboard = static_cast<km_kbp_keyboard *>(kp); } - catch (std::bad_alloc) + catch (std::bad_alloc) { return KM_KBP_STATUS_NO_MEM; } @@ -69,6 +74,6 @@ km_kbp_keyboard_get_attrs(km_kbp_keyboard const *keyboard, if (!keyboard || !out) return KM_KBP_STATUS_INVALID_ARGUMENT; - *out = keyboard; + *out = &keyboard->keyboard(); return KM_KBP_STATUS_OK; } diff --git a/src/km_kbp_options_api.cpp b/src/km_kbp_options_api.cpp index 944ba29..e50b0a3 100644 --- a/src/km_kbp_options_api.cpp +++ b/src/km_kbp_options_api.cpp @@ -9,15 +9,14 @@ into option.hpp */ #include <cassert> -#include <algorithm> -#include <iterator> #include <sstream> -#include <unordered_map> -#include <vector> + #include <keyman/keyboardprocessor.h> +#include "processor.hpp" -#include "option.hpp" #include "json.hpp" +#include "state.hpp" + size_t km_kbp_options_list_size(km_kbp_option_item const *opts) @@ -33,7 +32,7 @@ km_kbp_options_list_size(km_kbp_option_item const *opts) km_kbp_status -km_kbp_options_lookup(km_kbp_state const *state, +km_kbp_state_option_lookup(km_kbp_state const *state, uint8_t scope, km_kbp_cp const *key, km_kbp_cp const **value_out) { @@ -43,41 +42,22 @@ km_kbp_options_lookup(km_kbp_state const *state, if (scope == KM_KBP_OPT_UNKNOWN || scope > KM_KBP_OPT_MAX_SCOPES) return KM_KBP_STATUS_INVALID_ARGUMENT; - auto opts = km_kbp_state_options(const_cast<km_kbp_state *>(state)); + auto & processor = state->processor(); - // Copy the internal value to our new buffer - km_kbp_cp const *internal_value = opts->lookup(km_kbp_option_scope(scope), key); - if (!internal_value) - { - return KM_KBP_STATUS_KEY_ERROR; - } - std::u16string const &value = internal_value; - - km_kbp_cp *valuep; - try - { - valuep = new km_kbp_cp[value.size() + 1]; - } - catch (std::bad_alloc) - { - return KM_KBP_STATUS_NO_MEM; - } - std::copy(value.begin(), value.end(), valuep); - valuep[value.size()] = u'\0'; - - *value_out = valuep; + *value_out = processor.lookup_option(km_kbp_option_scope(scope), key); + if (!*value_out) return KM_KBP_STATUS_KEY_ERROR; return KM_KBP_STATUS_OK; } km_kbp_status -km_kbp_options_update(km_kbp_state *state, km_kbp_option_item const *opt) +km_kbp_state_options_update(km_kbp_state *state, km_kbp_option_item const *opt) { assert(state); assert(opt); if (!state|| !opt) return KM_KBP_STATUS_INVALID_ARGUMENT; - auto opts = km_kbp_state_options(state); + auto & processor = state->processor(); try { @@ -86,10 +66,13 @@ km_kbp_options_update(km_kbp_state *state, km_kbp_option_item const *opt) if (opt->scope == KM_KBP_OPT_UNKNOWN || opt->scope > KM_KBP_OPT_MAX_SCOPES) return KM_KBP_STATUS_INVALID_ARGUMENT; - if (!opts->assign(state, km_kbp_option_scope(opt->scope), opt->key, opt->value)) + if (processor.update_option( + km_kbp_option_scope(opt->scope), + opt->key, + opt->value).empty()) return KM_KBP_STATUS_KEY_ERROR; } - } + } catch (std::bad_alloc) { return KM_KBP_STATUS_NO_MEM; @@ -101,10 +84,10 @@ km_kbp_options_update(km_kbp_state *state, km_kbp_option_item const *opt) // This function doesn't need to use the json pretty printer for such a simple // list of key:value pairs but it's a good introduction to it. km_kbp_status -km_kbp_options_to_json(km_kbp_options const *opts, char *buf, size_t *space) +km_kbp_state_options_to_json(km_kbp_state const *state, char *buf, size_t *space) { - assert(opts); assert(space); - if (!opts || !space) + assert(state); assert(space); + if (!state || !space) return KM_KBP_STATUS_INVALID_ARGUMENT; std::stringstream _buf; @@ -112,7 +95,8 @@ km_kbp_options_to_json(km_kbp_options const *opts, char *buf, size_t *space) try { - jo << *opts; +// TODO: Fix +// jo << state->options(); } catch (std::bad_alloc) { @@ -132,9 +116,3 @@ km_kbp_options_to_json(km_kbp_options const *opts, char *buf, size_t *space) *space = doc.size()+1; return KM_KBP_STATUS_OK; } - -void -km_kbp_cp_dispose(km_kbp_cp const *cp) -{ - delete [] cp; -} diff --git a/src/km_kbp_processevent_api.cpp b/src/km_kbp_processevent_api.cpp index da9b049..698ae7b 100644 --- a/src/km_kbp_processevent_api.cpp +++ b/src/km_kbp_processevent_api.cpp @@ -16,14 +16,12 @@ km_kbp_status km_kbp_process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { - km::kbp::keyboard const & k = state->keyboard(); - return const_cast<km::kbp::abstract_processor&>(k.processor()).process_event(state, vk, modifier_state); + return state->processor().process_event(state, vk, modifier_state); } km_kbp_attr const * km_kbp_get_engine_attrs(km_kbp_state const *state) { - return state->keyboard().processor().get_attrs(); + return &state->processor().attributes(); } - diff --git a/src/km_kbp_state_api.cpp b/src/km_kbp_state_api.cpp index 7e8dd0b..7dc9c1f 100644 --- a/src/km_kbp_state_api.cpp +++ b/src/km_kbp_state_api.cpp @@ -10,24 +10,20 @@ */ #include <cassert> #include <algorithm> -#include <iterator> -#include <list> #include <sstream> -#include <utility> -#include <vector> #include <keyman/keyboardprocessor.h> -#include <utfcodec.hpp> -#include <json.hpp> +#include "json.hpp" -#include "context.hpp" -#include "option.hpp" -#include "keyboard.hpp" +#include "processor.hpp" #include "state.hpp" using namespace km::kbp; -km_kbp_status km_kbp_state_create(km_kbp_keyboard const * keyboard, +// Forward declarations +class context; + +km_kbp_status km_kbp_state_create(km_kbp_keyboard * keyboard, km_kbp_option_item const *env, km_kbp_state ** out) { @@ -37,9 +33,7 @@ km_kbp_status km_kbp_state_create(km_kbp_keyboard const * keyboard, try { - *out = new km_kbp_state(static_cast<km::kbp::keyboard const &>(*keyboard), - env); - + *out = new km_kbp_state(static_cast<abstract_processor&>(*keyboard), env); } catch (std::bad_alloc) { @@ -76,27 +70,19 @@ km_kbp_context *km_kbp_state_context(km_kbp_state *state) } -km_kbp_options *km_kbp_state_options(km_kbp_state *state) -{ - assert(state); - if (!state) return nullptr; - - return static_cast<km_kbp_options *>(&state->options()); -} - - km_kbp_action_item const * km_kbp_state_action_items(km_kbp_state const *state, size_t *num_items) { - assert(state); - if (!state) return nullptr; + assert(state && state->actions().size() > 0); + if (!state || state->actions().empty()) return nullptr; if (num_items) - *num_items = state->actions.size(); + *num_items = state->actions().size(); // Process events will ensure that the actions vector is always well // teminated - return state->actions.data(); + assert(state->actions().back().type == KM_KBP_IT_END); + return state->actions().data(); } namespace { @@ -136,15 +122,13 @@ json & operator << (json & j, km_kbp_action_item const &act) { case KM_KBP_IT_END: case KM_KBP_IT_ALERT: + case KM_KBP_IT_BACK: j << json::null; break; case KM_KBP_IT_CHAR: case KM_KBP_IT_MARKER: j << km_kbp_context_item {act.type, {0,}, {act.character}}; // TODO: is act.type correct here? it may map okay but this is bad practice to mix constants across types. Similarly using act.character instead of act.type break; - case KM_KBP_IT_BACK: - j << json::null; // act.erased; - break; case KM_KBP_IT_PERSIST_OPT: j << json::object << scope_names_lut[act.option->scope] @@ -153,7 +137,6 @@ json & operator << (json & j, km_kbp_action_item const &act) << json::close << json::close; break; - break; } j << json::close; @@ -161,7 +144,7 @@ json & operator << (json & j, km_kbp_action_item const &act) } -json & operator << (json & j, std::vector<km_kbp_action_item> const & acts) +json & operator << (json & j, actions const & acts) { j << json::array; for (auto & act: acts) @@ -192,10 +175,10 @@ km_kbp_status km_kbp_state_to_json(km_kbp_state const *state, // Pretty print the document. jo << json::object << "$schema" << "keyman/keyboardprocessor/doc/introspection.schema" - << "keyboard" << state->keyboard() - << "options" << state->options() + << "keyboard" << state->processor().keyboard() +// << "options" << state->options() TODO: Fix << "context" << state->context() - << "actions" << state->actions + << "actions" << state->actions() << json::close; } catch (std::bad_alloc) diff --git a/src/kmx/kmx_actions.cpp b/src/kmx/kmx_actions.cpp index 5182154..03c70eb 100644 --- a/src/kmx/kmx_actions.cpp +++ b/src/kmx/kmx_actions.cpp @@ -4,7 +4,8 @@ */ #include <kmx/kmx_processor.h> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; void KMX_Actions::ResetQueue() { @@ -31,7 +32,7 @@ KMX_BOOL KMX_Actions::QueueAction(int ItemType, KMX_DWORD dwData) QueueSize++; int result = TRUE; - + switch(ItemType) { case QIT_VKEYDOWN: diff --git a/src/kmx/kmx_capslock.cpp b/src/kmx/kmx_capslock.cpp index 8d509d3..999fa08 100644 --- a/src/kmx/kmx_capslock.cpp +++ b/src/kmx/kmx_capslock.cpp @@ -4,7 +4,8 @@ */ #include <kmx/kmx_processor.h> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; void KMX_Processor::ResetCapsLock(void) { diff --git a/src/kmx/kmx_context.cpp b/src/kmx/kmx_context.cpp index 0aa7fb7..77ab434 100644 --- a/src/kmx/kmx_context.cpp +++ b/src/kmx/kmx_context.cpp @@ -4,7 +4,8 @@ */ #include <kmx/kmx_processor.h> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; /* KMX_Context */ diff --git a/src/kmx/kmx_debug.cpp b/src/kmx/kmx_debug.cpp index a16dee4..05ef171 100644 --- a/src/kmx/kmx_debug.cpp +++ b/src/kmx/kmx_debug.cpp @@ -6,7 +6,8 @@ #include <stdarg.h> #include <iostream> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; #define TAB "\t" #define NL "\n" diff --git a/src/kmx/kmx_environment.cpp b/src/kmx/kmx_environment.cpp index 75ae0f8..4fac391 100644 --- a/src/kmx/kmx_environment.cpp +++ b/src/kmx/kmx_environment.cpp @@ -7,7 +7,8 @@ #include <state.hpp> #include <option.hpp> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; namespace { km_kbp_cp const @@ -20,30 +21,47 @@ namespace { } KMX_Environment::KMX_Environment() { + Load(KM_KBP_KMX_ENV_PLATFORM, DEFAULT_PLATFORM); + Load(KM_KBP_KMX_ENV_BASELAYOUT, DEFAULT_BASELAYOUT); + Load(KM_KBP_KMX_ENV_BASELAYOUTALT, DEFAULT_BASELAYOUTALT); + Load(KM_KBP_KMX_ENV_SIMULATEALTGR, DEFAULT_SIMULATEALTGR); + Load(KM_KBP_KMX_ENV_CAPSLOCK, DEFAULT_CAPSLOCK); + Load(KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT, DEFAULT_BASELAYOUTGIVESCTRLRALTFORRALT); } -void KMX_Environment::InitOption(std::vector<km_kbp_option_item> *default_env, km_kbp_cp const *key, km_kbp_cp const *default_value) { - km_kbp_option_item opt = { (km_kbp_cp*) key, (km_kbp_cp*) default_value, KM_KBP_OPT_ENVIRONMENT }; - default_env->emplace_back(opt); - Load(key, default_value); -} -void KMX_Environment::Init(std::vector<km_kbp_option_item> *default_env) { - InitOption(default_env, KM_KBP_KMX_ENV_PLATFORM, DEFAULT_PLATFORM); - InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUT, DEFAULT_BASELAYOUT); - InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUTALT, DEFAULT_BASELAYOUTALT); - InitOption(default_env, KM_KBP_KMX_ENV_SIMULATEALTGR, DEFAULT_SIMULATEALTGR); - InitOption(default_env, KM_KBP_KMX_ENV_CAPSLOCK, DEFAULT_CAPSLOCK); - InitOption(default_env, KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT, DEFAULT_BASELAYOUTGIVESCTRLRALTFORRALT); +char16_t const * KMX_Environment::LookUp(std::u16string const & key) const { + assert(!key.empty()); + if (!key.empty()) return nullptr; - // TODO refactor this and the keyboard option terminator into state.cpp and keyboard.cpp respectively - km_kbp_option_item opt = KM_KBP_OPTIONS_END; - default_env->emplace_back(opt); + if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_PLATFORM)) { + return _platform.c_str(); + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUT)) { + return _baseLayout.c_str(); + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUTALT)) { + return _baseLayoutAlt.c_str(); + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_SIMULATEALTGR)) { + return _simulateAltGr ? u"1" : u"0"; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_CAPSLOCK)) { + return _capsLock ? u"1" : u"0"; + } + else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_BASELAYOUTGIVESCTRLRALTFORRALT)) { + return _baseLayoutGivesCtrlRAltForRAlt ? u"1" : u"0"; + } + else { + // Unsupported key + return nullptr; + } } + void KMX_Environment::Load(std::u16string const & key, std::u16string const & value) { assert(!key.empty()); - + if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_PLATFORM)) { _platform = value; } @@ -55,7 +73,7 @@ void KMX_Environment::Load(std::u16string const & key, std::u16string const & va } else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_SIMULATEALTGR)) { _simulateAltGr = value == u"1"; - } + } else if (!u16icmp(key.c_str(), KM_KBP_KMX_ENV_CAPSLOCK)) { _capsLock = value == u"1"; } diff --git a/src/kmx/kmx_environment.h b/src/kmx/kmx_environment.h index 34148d8..c8c0e21 100644 --- a/src/kmx/kmx_environment.h +++ b/src/kmx/kmx_environment.h @@ -1,6 +1,7 @@ #pragma once #include <string> +#include "option.hpp" #include "kmx_base.h" namespace km { @@ -14,13 +15,13 @@ private: KMX_BOOL _capsLock; std::u16string _platform; void InitOption( - std::vector<km_kbp_option_item> * default_env, - km_kbp_cp const * key, + std::vector<option> & default_env, + km_kbp_cp const * key, km_kbp_cp const * default_value); public: KMX_Environment(); void Load(std::u16string const & key, std::u16string const & value); - void Init(std::vector<km_kbp_option_item> *default_env); + char16_t const * LookUp(std::u16string const & key) const; KMX_BOOL capsLock() const noexcept { return _capsLock; } KMX_BOOL simulateAltGr() const noexcept { return _simulateAltGr; } diff --git a/src/kmx/kmx_file.cpp b/src/kmx/kmx_file.cpp index 4c9715e..945186a 100644 --- a/src/kmx/kmx_file.cpp +++ b/src/kmx/kmx_file.cpp @@ -4,16 +4,17 @@ */ #include "kmx_processor.h" -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) || defined(_WIN64) #include <share.h> #endif KMX_BOOL KMX_Processor::Load(km_kbp_path_name KeyboardName) { if(!LoadKeyboard(KeyboardName, &m_keyboard.Keyboard)) return FALSE; // I5136 - + return TRUE; } @@ -70,13 +71,12 @@ unsigned long CalculateBufferCRC(unsigned long count, KMX_BYTE *p) temp2 = CRCTable[((int) crc ^ *p++) & 0xff]; crc = temp1 ^ temp2; } - + return crc; } KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard) { - long sz; PKMX_BYTE buf; FILE *fp; LPKEYBOARD kbp; @@ -89,7 +89,7 @@ KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKe return FALSE; } -#if defined(_WIN32) || defined(_WIN64) +#if defined(_WIN32) || defined(_WIN64) fp = _wfsopen(fileName, L"rb", _SH_DENYWR); #else fp = fopen(fileName, "rb"); @@ -106,7 +106,7 @@ KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKe return FALSE; } - sz = ftell(fp); + auto sz = ftell(fp); if (sz < 0) { fclose(fp); return FALSE; @@ -146,7 +146,7 @@ KMX_BOOL KMX_Processor::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKe if(*PKMX_DWORD(filebase) != KMX_DWORD(FILEID_COMPILED)) { - delete buf; + delete buf; DebugLog("Invalid file - signature is invalid"); return FALSE; } @@ -208,14 +208,14 @@ LPKEYBOARD KMX_Processor::CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) kbp->dpGroupArray = (LPGROUP) bufp; bufp += sizeof(GROUP) * kbp->cxGroupArray; - + PCOMP_STORE csp; LPSTORE sp; KMX_DWORD i; for( - csp = (PCOMP_STORE)(base + ckbp->dpStoreArray), sp = kbp->dpStoreArray, i = 0; - i < kbp->cxStoreArray; + csp = (PCOMP_STORE)(base + ckbp->dpStoreArray), sp = kbp->dpStoreArray, i = 0; + i < kbp->cxStoreArray; i++, sp++, csp++) { sp->dwSystemID = csp->dwSystemID; @@ -284,7 +284,7 @@ LPKEYBOARD KMX_Processor::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWOR for(gp = kbp->dpGroupArray, cgp = (PCOMP_GROUP) gp, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++) { - gp->dpName = StringOffset(base, cgp->dpName); + gp->dpName = StringOffset(base, cgp->dpName); gp->dpKeyArray = (LPKEY) (base + cgp->dpKeyArray); if(cgp->dpMatch != NULL) gp->dpMatch = (PKMX_WCHAR) (base + cgp->dpMatch); if(cgp->dpNoMatch != NULL) gp->dpNoMatch = (PKMX_WCHAR) (base + cgp->dpNoMatch); @@ -301,7 +301,7 @@ LPKEYBOARD KMX_Processor::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWOR #endif -KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, KMX_DWORD sz) +KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, size_t sz) { KMX_DWORD tempcs; PCOMP_KEYBOARD ckbp; @@ -314,20 +314,20 @@ KMX_BOOL KMX_Processor::VerifyChecksum(PKMX_BYTE buf, KMX_DWORD sz) return tempcs == CalculateBufferCRC(sz, buf); } -KMX_BOOL KMX_Processor::VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD sz) +KMX_BOOL KMX_Processor::VerifyKeyboard(PKMX_BYTE filebase, size_t sz) { KMX_DWORD i; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; PCOMP_STORE csp; - /* Check file version */ + /* Check file version */ - if(ckbp->dwFileVersion < VERSION_MIN || - ckbp->dwFileVersion > VERSION_MAX) - { - /* Old or new version -- identify the desired program version */ - if(VerifyChecksum(filebase, sz)) - { + if(ckbp->dwFileVersion < VERSION_MIN || + ckbp->dwFileVersion > VERSION_MAX) + { + /* Old or new version -- identify the desired program version */ + if(VerifyChecksum(filebase, sz)) + { for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) if(csp->dwSystemID == TSS_COMPILEDVERSION) { @@ -339,9 +339,9 @@ KMX_BOOL KMX_Processor::VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD sz) } } DebugLog("errWrongFileVersion"); - return FALSE; + return FALSE; } - + if(!VerifyChecksum(filebase, sz)) { DebugLog("errBadChecksum"); return FALSE; } return TRUE; diff --git a/src/kmx/kmx_modifiers.cpp b/src/kmx/kmx_modifiers.cpp index aafc0ab..1da7a8a 100644 --- a/src/kmx/kmx_modifiers.cpp +++ b/src/kmx/kmx_modifiers.cpp @@ -4,7 +4,8 @@ */ #include "kmx_processor.h" -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; #define MAX_RSHIFT 24 #define MAX_KSHIFT 18 diff --git a/src/kmx/kmx_options.cpp b/src/kmx/kmx_options.cpp index 9d553fa..6b48e59 100644 --- a/src/kmx/kmx_options.cpp +++ b/src/kmx/kmx_options.cpp @@ -2,14 +2,29 @@ Copyright: Copyright (C) 2003-2018 SIL International. Authors: mcdurdin */ +#include "processor.hpp" #include "kmx_processor.h" #include <option.hpp> #include <state.hpp> -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; + +int KMX_Options::_GetIndex(std::u16string const &key) const { + auto i = 0U; + ; + for (auto sp = _kp->Keyboard->dpStoreArray; + i != _kp->Keyboard->cxStoreArray; ++i, ++sp) + { + if (sp->dpName && sp->dpName == key) return i; + } + + return -1; +} + void KMX_Options::AddOptionsStoresFromXString(PKMX_WCHAR s) { - int idx; + auto idx = 0U; for (; s && *s; s = incxstr(s)) { if (*s == UC_SENTINEL) { switch (*(s + 1)) { @@ -18,7 +33,7 @@ void KMX_Options::AddOptionsStoresFromXString(PKMX_WCHAR s) { case CODE_SAVEOPT: case CODE_RESETOPT: idx = *(s + 2) - 1; - if (idx >= 0 && static_cast<KMX_DWORD>(idx) < _kp->Keyboard->cxStoreArray && _kp->Keyboard->dpStoreArray[idx].dpName != NULL) { + if (idx >= 0 && idx < _kp->Keyboard->cxStoreArray && _kp->Keyboard->dpStoreArray[idx].dpName != NULL) { _kp->KeyboardOptions[idx].OriginalStore = _kp->Keyboard->dpStoreArray[idx].dpString; } break; @@ -27,18 +42,19 @@ void KMX_Options::AddOptionsStoresFromXString(PKMX_WCHAR s) { } } -void KMX_Options::Load(km_kbp_options *options, std::u16string const &key) { +void KMX_Options::Load(abstract_processor & ap, std::u16string const &key) { LPSTORE sp; - KMX_DWORD i; + auto i = 0U; - assert(options != nullptr); assert(!key.empty()); - if (options == nullptr || key.empty()) return; - + if (key.empty()) return; + for (i = 0, sp = _kp->Keyboard->dpStoreArray; i < _kp->Keyboard->cxStoreArray; i++, sp++) { - if (_kp->KeyboardOptions[i].OriginalStore != NULL && sp->dpName != NULL && u16icmp(sp->dpName, key.c_str()) == 0) { - Reset(options, i); + if (_kp->KeyboardOptions[i].OriginalStore != NULL + && sp->dpName != NULL + && u16icmp(sp->dpName, key.c_str()) == 0) { + Reset(ap, i); return; } } @@ -47,20 +63,20 @@ void KMX_Options::Load(km_kbp_options *options, std::u16string const &key) { assert(false); } -void KMX_Options::Init(std::vector<km_kbp_option_item> *opts) { +void KMX_Options::Init(std::vector<option> &opts) { - opts->clear(); + opts.clear(); _kp->KeyboardOptions = new INTKEYBOARDOPTIONS[_kp->Keyboard->cxStoreArray]; memset(_kp->KeyboardOptions, 0, sizeof(INTKEYBOARDOPTIONS) * _kp->Keyboard->cxStoreArray); // Scan all rules to find options references. - KMX_DWORD i, j; + auto i = 0U, j = 0U; LPGROUP gp; LPKEY kkp; - for (i = 0, gp = _kp->Keyboard->dpGroupArray; i < _kp->Keyboard->cxGroupArray; i++, gp++) { - for (j = 0, kkp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kkp++) { + for (i = 0U, gp = _kp->Keyboard->dpGroupArray; i < _kp->Keyboard->cxGroupArray; i++, gp++) { + for (j = 0U, kkp = gp->dpKeyArray; j < gp->cxKeyArray; j++, kkp++) { AddOptionsStoresFromXString(kkp->dpContext); AddOptionsStoresFromXString(kkp->dpOutput); } @@ -77,8 +93,7 @@ void KMX_Options::Init(std::vector<km_kbp_option_item> *opts) { } if (n == 0) { - km_kbp_option_item opt = KM_KBP_OPTIONS_END; - opts->emplace_back(opt); + opts.emplace_back(); // Terminate the options array return; } @@ -87,16 +102,11 @@ void KMX_Options::Init(std::vector<km_kbp_option_item> *opts) { LPSTORE sp; for (n = 0, i = 0, ko = _kp->KeyboardOptions, sp = _kp->Keyboard->dpStoreArray; i < _kp->Keyboard->cxStoreArray; i++, sp++, ko++) { if (ko->OriginalStore == NULL) continue; - km_kbp_option_item opt; - opt.key = sp->dpName; - opt.value = sp->dpString; - opt.scope = KM_KBP_OPT_KEYBOARD; - opts->emplace_back(opt); + opts.emplace_back(KM_KBP_OPT_KEYBOARD, sp->dpName, sp->dpString); n++; } - km_kbp_option_item opt = KM_KBP_OPTIONS_END; - opts->emplace_back(opt); + opts.emplace_back(); // Terminate the options array } KMX_Options::~KMX_Options() @@ -114,6 +124,36 @@ KMX_Options::~KMX_Options() _kp->KeyboardOptions = NULL; } + +char16_t const * KMX_Options::LookUp(std::u16string const &key) const +{ + auto idx = _GetIndex(key); + if (idx < 0) return nullptr; + + return _kp->Keyboard->dpStoreArray[idx].dpString; +} + + +void KMX_Options::Set(int nStoreToSet, std::u16string const & rValueToSet) +{ + assert(_kp != NULL); + assert(_kp->Keyboard != NULL); + assert(_kp->KeyboardOptions != NULL); + assert(nStoreToSet >= 0); + assert(nStoreToSet < (int) _kp->Keyboard->cxStoreArray); + + auto & rStoreToSetValue = _kp->KeyboardOptions[nStoreToSet].Value; + if(rStoreToSetValue) + { + delete rStoreToSetValue; + } + + rStoreToSetValue = new std::u16string::value_type[rValueToSet.size()+1]; + u16cpy(rStoreToSetValue, /*u16len(sp->dpString)+1,*/ rValueToSet.c_str()); + _kp->Keyboard->dpStoreArray[nStoreToSet].dpString = rStoreToSetValue; +} + + void KMX_Options::Set(int nStoreToSet, int nStoreToRead) { assert(_kp != NULL); @@ -124,18 +164,11 @@ void KMX_Options::Set(int nStoreToSet, int nStoreToRead) assert(nStoreToRead >= 0); assert(nStoreToRead < (int) _kp->Keyboard->cxStoreArray); - LPSTORE sp = &_kp->Keyboard->dpStoreArray[nStoreToRead]; - if(_kp->KeyboardOptions[nStoreToSet].Value) - { - delete _kp->KeyboardOptions[nStoreToSet].Value; - } - - _kp->KeyboardOptions[nStoreToSet].Value = new KMX_WCHAR[u16len(sp->dpString)+1]; - u16cpy(_kp->KeyboardOptions[nStoreToSet].Value, /*u16len(sp->dpString)+1,*/ sp->dpString); - _kp->Keyboard->dpStoreArray[nStoreToSet].dpString = _kp->KeyboardOptions[nStoreToSet].Value; + std::u16string const & rStoreToReadValue = _kp->Keyboard->dpStoreArray[nStoreToRead].dpString; + Set(nStoreToSet, rStoreToReadValue); } -void KMX_Options::Reset(km_kbp_options *options, int nStoreToReset) +void KMX_Options::Reset(abstract_processor & ap, int nStoreToReset) { assert(_kp != NULL); assert(_kp->Keyboard != NULL); @@ -143,27 +176,29 @@ void KMX_Options::Reset(km_kbp_options *options, int nStoreToReset) assert(nStoreToReset >= 0); assert(nStoreToReset < (int) _kp->Keyboard->cxStoreArray); - if (_kp->KeyboardOptions[nStoreToReset].Value) + auto & rStoreToReset = _kp->Keyboard->dpStoreArray[nStoreToReset]; + auto & rOptionToReset = _kp->KeyboardOptions[nStoreToReset]; + if (rOptionToReset.Value) { - _kp->Keyboard->dpStoreArray[nStoreToReset].dpString = _kp->KeyboardOptions[nStoreToReset].OriginalStore; - delete _kp->KeyboardOptions[nStoreToReset].Value; - _kp->KeyboardOptions[nStoreToReset].Value = NULL; + rStoreToReset.dpString = rOptionToReset.OriginalStore; + delete rOptionToReset.Value; + rOptionToReset.Value = nullptr; } - if(_kp->Keyboard->dpStoreArray[nStoreToReset].dpName == NULL) return; + if(rStoreToReset.dpName == nullptr) return; // Now we need to go back and get any saved value from KPAPI. internal_value is owned by options api - km_kbp_cp const *internal_value = options->lookup(km_kbp_option_scope(KM_KBP_OPT_KEYBOARD), _kp->Keyboard->dpStoreArray[nStoreToReset].dpName); - if(internal_value) { + auto i = ap.persisted_store().find(rStoreToReset.dpName); + if(i != ap.persisted_store().end()) { // Copy the value from KPAPI - _kp->KeyboardOptions[nStoreToReset].Value = new KMX_WCHAR[u16len(internal_value) + 1]; - u16cpy(_kp->KeyboardOptions[nStoreToReset].Value, /*u16len(val) + 1,*/ internal_value); - _kp->Keyboard->dpStoreArray[nStoreToReset].dpString = _kp->KeyboardOptions[nStoreToReset].Value; + rOptionToReset.Value = new KMX_WCHAR[i->second.size() + 1]; + u16cpy(rOptionToReset.Value, /*u16len(val) + 1,*/ i->second.c_str()); + rStoreToReset.dpString = rOptionToReset.Value; } } -void KMX_Options::Save(km_kbp_state *state, int nStoreToSave) +void KMX_Options::Save(state & state, int nStoreToSave) { assert(_kp != NULL); assert(_kp->Keyboard != NULL); @@ -171,14 +206,10 @@ void KMX_Options::Save(km_kbp_state *state, int nStoreToSave) assert(nStoreToSave >= 0); assert(nStoreToSave < (int)_kp->Keyboard->cxStoreArray); - if (_kp->Keyboard->dpStoreArray[nStoreToSave].dpName == NULL) return; + auto const & rStoreToSave = _kp->Keyboard->dpStoreArray[nStoreToSave]; + if (rStoreToSave.dpName == nullptr) return; - // TSE QUERY: Does this happen here or with an ACTION? Or both? - state->options().assign(state, KM_KBP_OPT_KEYBOARD, _kp->Keyboard->dpStoreArray[nStoreToSave].dpName, _kp->Keyboard->dpStoreArray[nStoreToSave].dpString); - //TODO: GetActions()->QueueAction(QIT_SAVEOPTION, ...); - /*RegistryFullAccess r(HKEY_CURRENT_USER); - if(r.OpenKey(REGSZ_KeymanActiveKeyboards, TRUE) && r.OpenKey(_kp->Name, TRUE) && r.OpenKey(key, TRUE)) - { - r.WriteString(_kp->Keyboard->dpStoreArray[nStoreToSave].dpName, _kp->Keyboard->dpStoreArray[nStoreToSave].dpString); - }*/ + state.processor().persisted_store()[rStoreToSave.dpName] = rStoreToSave.dpString; + state.actions().push_persist( + option{KM_KBP_OPT_KEYBOARD, rStoreToSave.dpName, rStoreToSave.dpString}); } diff --git a/src/kmx/kmx_options.h b/src/kmx/kmx_options.h index 29df0d9..21a2873 100644 --- a/src/kmx/kmx_options.h +++ b/src/kmx/kmx_options.h @@ -3,11 +3,18 @@ #include <string> #include <vector> + #include <keyman/keyboardprocessor.h> +#include "option.hpp" + #include "kmx_base.h" namespace km { namespace kbp { + +class abstract_processor; +class state; + namespace kmx { class KMX_Options @@ -17,17 +24,41 @@ private: void AddOptionsStoresFromXString(PKMX_WCHAR s); + int _GetIndex(std::u16string const &key) const; + public: KMX_Options(LPINTKEYBOARDINFO kp) : _kp(kp) {} ~KMX_Options(); - void Init(std::vector<km_kbp_option_item> *opts); - void Load(km_kbp_options *options, std::u16string const &key); + void Init(std::vector<option> &opts); + void Load(abstract_processor &, std::u16string const &key); + char16_t const * LookUp(std::u16string const &key) const; void Set(int nStoreToSet, int nStoreToRead); - void Reset(km_kbp_options *options, int nStoreToReset); - void Save(km_kbp_state *state, int nStoreToSave); + void Set(int nStoreToSet, std::u16string const &value); + void Set(std::u16string const &key, std::u16string const &value); + void Reset(abstract_processor &, int nStoreToReset); + void Save(state & state, int nStoreToSave); + + STORE const * begin() const; + STORE const * end() const; }; +inline +void KMX_Options::Set(std::u16string const &key, std::u16string const &value) { + auto i = _GetIndex(key); + if (i >= 0) Set(i, value); +} + +inline +STORE const * KMX_Options::begin() const { + return _kp->Keyboard->dpStoreArray; +} + +inline +STORE const * KMX_Options::end() const { + return _kp->Keyboard->dpStoreArray + _kp->Keyboard->cxStoreArray; +} + } // namespace kmx } // namespace kbp } // namespace km diff --git a/src/kmx/kmx_processevent.cpp b/src/kmx/kmx_processevent.cpp index 2af29dc..e3b9291 100644 --- a/src/kmx/kmx_processevent.cpp +++ b/src/kmx/kmx_processevent.cpp @@ -1,7 +1,6 @@ #include <keyman/keyboardprocessor.h> -#include "processor.hpp" #include "state.hpp" -#include "keyboard.hpp" +#include "kmx/kmx_processevent.hpp" namespace km { namespace kbp @@ -11,34 +10,63 @@ namespace km { return _valid ? KM_KBP_STATUS_OK : KM_KBP_STATUS_INVALID_KEYBOARD; } - kmx_processor::kmx_processor(km_kbp_keyboard_attrs const * kb_) : abstract_processor(kb_) { - km::kbp::keyboard *kb = const_cast<km::kbp::keyboard *>(static_cast<km::kbp::keyboard const *>(kb_)); - - std::filesystem::path p = kb->folder_path; - p /= kb->id; + kmx_processor::kmx_processor(kbp::path p) + { p.replace_extension(".kmx"); - _valid = (bool) _kmx.Load(p.native().c_str()); + _valid = bool(_kmx.Load(p.c_str())); + + keyboard_attributes::options_store defaults; + if (_valid) + _kmx.GetOptions()->Init(defaults); - if (_valid) { - _kmx.GetOptions()->Init(kb->default_opts()); + for (auto const & opt: defaults) + { + if (!opt.empty() && opt.scope == KM_KBP_OPT_KEYBOARD ) + persisted_store()[opt.key] = opt.value; } + // Fill out attributes + auto v = _kmx.GetKeyboard()->Keyboard->version; + auto vs = std::to_string(v >> 16) + "." + std::to_string(v & 0xffff); + + _attributes = keyboard_attributes(static_cast<std::u16string>(p.stem()), + std::u16string(vs.begin(), vs.end()), p.parent(), defaults); } - void kmx_processor::init_state(std::vector<km_kbp_option_item> *default_env) { - _kmx.GetEnvironment()->Init(default_env); + + char16_t const * kmx_processor::lookup_option(km_kbp_option_scope scope, std::u16string const & key) const + { + char16_t const * pValue = nullptr; + switch(scope) + { + case KM_KBP_OPT_KEYBOARD: + pValue = _kmx.GetOptions()->LookUp(key); + break; + case KM_KBP_OPT_ENVIRONMENT: + pValue = _kmx.GetEnvironment()->LookUp(key); + break; + default: + break; + } + + return pValue ? pValue : nullptr; } - void kmx_processor::update_option(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) { + option kmx_processor::update_option(km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) + { switch(scope) { case KM_KBP_OPT_KEYBOARD: - _kmx.GetOptions()->Load(km_kbp_state_options(state), key); + _kmx.GetOptions()->Set(key, value); + persisted_store()[key] = value; break; case KM_KBP_OPT_ENVIRONMENT: _kmx.GetEnvironment()->Load(key, value); break; default: + return option(); break; } + + return option(scope, key, value); } km_kbp_status kmx_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { @@ -61,18 +89,18 @@ namespace km { assert(c->marker > 0); ctxt += UC_SENTINEL; ctxt += CODE_DEADKEY; - ctxt += c->marker; + ctxt += c->marker; break; } } _kmx.GetContext()->Set(ctxt.c_str()); _kmx.GetActions()->ResetQueue(); - state->actions.clear(); + state->actions().clear(); if (!_kmx.ProcessEvent(state, vk, modifier_state)) { // We need to output the default keystroke - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_EMIT_KEYSTROKE, {0,}, {0} }); + state->actions().push_emit_keystroke(); } for (auto i = 0; i < _kmx.GetActions()->Length(); i++) { @@ -89,16 +117,16 @@ namespace km { case QIT_VSHIFTUP: //TODO: eliminate?? break; - case QIT_CHAR: - state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_CHAR, {0,}, {(km_kbp_usv)a.dwData} }); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CHAR, {0,}, {(km_kbp_usv)a.dwData} }); + case QIT_CHAR: + state->context().push_character(a.dwData); + state->actions().push_character(a.dwData); break; case QIT_DEADKEY: - state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_MARKER, {0,}, {(km_kbp_usv)a.dwData} }); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_MARKER, {0,}, {(uintptr_t)a.dwData} }); + state->context().push_marker(a.dwData); + state->actions().push_marker(a.dwData); break; case QIT_BELL: - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_ALERT, {0,}, {0} }); + state->actions().push_alert(); break; case QIT_BACK: switch (a.dwData) { @@ -106,7 +134,7 @@ namespace km { // This only happens if we know we have context to delete. Last item must be a character assert(!state->context().empty() && state->context().back().type != KM_KBP_IT_MARKER); if (!state->context().empty()) state->context().pop_back(); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {0} }); + state->actions().push_backspace(); break; case BK_DEADKEY: // This only happens if we know we have context to delete. Last item must be a deadkey @@ -122,14 +150,14 @@ namespace km { } // Even if context is empty, we send the backspace event, because we may not // know the context. - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {0} }); + state->actions().push_backspace(); break; default: assert(false); } break; case QIT_INVALIDATECONTEXT: - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_INVALIDATE_CONTEXT, {0,}, {0} }); + state->actions().push_invalidate_context(); break; default: //std::cout << "Unexpected item type " << a.ItemType << ", " << a.dwData << std::endl; @@ -137,7 +165,7 @@ namespace km { } } - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + state->actions().commit(); return KM_KBP_STATUS_OK; } @@ -151,10 +179,9 @@ namespace km { "SIL International" }; - km_kbp_attr const * kmx_processor::get_attrs() const { - //TODO - return &engine_attrs; + km_kbp_attr const & kmx_processor::attributes() const { + return engine_attrs; } } // namespace kbp -} // namespace km
\ No newline at end of file +} // namespace km diff --git a/src/kmx/kmx_processevent.hpp b/src/kmx/kmx_processevent.hpp new file mode 100644 index 0000000..29ba66e --- /dev/null +++ b/src/kmx/kmx_processevent.hpp @@ -0,0 +1,41 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal keyboard class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp +*/ + +#pragma once + +#include <string> +#include <keyman/keyboardprocessor.h> +#include "kmx/kmx_processor.h" +#include "keyboard.hpp" +#include "processor.hpp" + +namespace km { +namespace kbp +{ + class kmx_processor : public abstract_processor + { + private: + bool _valid; + kmx::KMX_Processor _kmx; + public: + kmx_processor(path); + km_kbp_status process_event(km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state) override; + km_kbp_attr const & attributes() const override; + km_kbp_status validate() const override; + + char16_t const * lookup_option(km_kbp_option_scope, + std::u16string const & key) const override; + option update_option(km_kbp_option_scope scope, + std::u16string const & key, + std::u16string const & value) override; + }; + +} // namespace kbp +} // namespace km diff --git a/src/kmx/kmx_processor.cpp b/src/kmx/kmx_processor.cpp index c3f16a2..149cf99 100644 --- a/src/kmx/kmx_processor.cpp +++ b/src/kmx/kmx_processor.cpp @@ -3,8 +3,10 @@ Authors: mcdurdin */ #include "kmx_processor.h" +#include "state.hpp" -using namespace km::kbp::kmx; +using namespace km::kbp; +using namespace kmx; /* Globals */ @@ -88,7 +90,7 @@ KMX_BOOL KMX_Processor::ProcessEvent(km_kbp_state *state, KMX_UINT vkey, KMX_DWO LPGROUP gp = &kbd->dpGroupArray[kbd->StartGroup[BEGIN_UNICODE]]; KMX_BOOL fOutputKeystroke = FALSE; - + ProcessGroup(gp, &fOutputKeystroke); m_kbp_state = nullptr; @@ -166,12 +168,12 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) { if(kkp->dpContext[0] != 0) break; else continue; } - + //if(kkp->Key == m_state.vkey) - //SendDebugMessageFormat(m_state.msg.hwnd, sdmKeyboard, 0, "kkp->Key: %d kkp->ShiftFlags: %x", + //SendDebugMessageFormat(m_state.msg.hwnd, sdmKeyboard, 0, "kkp->Key: %d kkp->ShiftFlags: %x", // kkp->Key, kkp->ShiftFlags); - /* Keyman 6.0: support Virtual Characters */ + /* Keyman 6.0: support Virtual Characters */ if(IsEquivalentShift(kkp->ShiftFlags, m_modifiers)) { if(kkp->Key > VK__MAX && kkp->Key == m_state.vkey) break; // I3438 // I4582 @@ -218,12 +220,12 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) } else if (gp->dpNoMatch != NULL && *gp->dpNoMatch != 0) { - /* NoMatch rule found, and is a character key */ + /* NoMatch rule found, and is a character key */ PostString(gp->dpNoMatch, m_keyboard.Keyboard, NULL, pOutputKeystroke); } else if (m_state.charCode != 0 && m_state.charCode != 0xFFFF && gp->fUsingKeys) { - /* No rule found, is a character key */ + /* No rule found, is a character key */ m_actions.QueueAction(QIT_CHAR, m_state.charCode); } @@ -270,14 +272,14 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) case CODE_DEADKEY: m_actions.QueueAction(QIT_BACK, BK_DEADKEY); break; case CODE_NUL: break; // 11 Aug 2003 - I25(v6) - mcdurdin - CODE_NUL context support } - else + else { m_actions.QueueAction(QIT_BACK, 0); } } p = kkp->dpOutput; } - else + else p = incxstr(p); // otherwise, the "context" entry has to be jumped over /* Use PostString to post the rest of the output string. */ @@ -294,7 +296,7 @@ KMX_BOOL KMX_Processor::ProcessGroup(LPGROUP gp, KMX_BOOL *pOutputKeystroke) } /* -* int PostString( PKMX_CHAR str, KMX_BOOL *useMode, LPMSG mp, +* int PostString( PKMX_CHAR str, KMX_BOOL *useMode, LPMSG mp, * LPKEYBOARD lpkb ); * * Parameters: str Pointer to string to send @@ -326,7 +328,7 @@ int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr { case CODE_EXTENDED: // Start of a virtual key section w/shift codes p++; - + shift = *p; //(*p<<8) | *(p+1); m_actions.QueueAction(QIT_VSHIFTDOWN, shift); @@ -336,7 +338,7 @@ int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr m_actions.QueueAction(QIT_VKEYUP, *p); m_actions.QueueAction(QIT_VSHIFTUP, shift); - + p++; // CODE_EXTENDEDEND ////// CODE_EXTENDEDEND will be incremented by loop @@ -372,7 +374,7 @@ int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr p++; DebugLog("CallDLL not supported [store=%d].\n", *p-1); FoundUse = TRUE; - break; + break; case CODE_USE: // use another group p++; ProcessGroup(&lpkb->dpGroupArray[*p-1], pOutputKeystroke); @@ -401,12 +403,12 @@ int KMX_Processor::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR endstr case CODE_RESETOPT: p++; n1 = *p - 1; - GetOptions()->Reset(km_kbp_state_options(m_kbp_state), n1); + GetOptions()->Reset(m_kbp_state->processor(), n1); break; case CODE_SAVEOPT: p++; n1 = *p - 1; - GetOptions()->Save(m_kbp_state, n1); + GetOptions()->Save(*m_kbp_state, n1); break; case CODE_IFSYSTEMSTORE: p+=3; @@ -440,13 +442,13 @@ KMX_BOOL KMX_Processor::IsMatchingPlatformString(PKMX_WCHAR platform) // I3432 PKMX_WCHAR p = u16tok(t, u' ', &context); while (p != NULL) { if (u16icmp(platform, p) == 0) { - delete t; + delete [] t; return TRUE; } p = u16tok(NULL, u' ', &context); } - delete t; + delete [] t; return FALSE; } @@ -461,14 +463,14 @@ KMX_BOOL KMX_Processor::IsMatchingPlatform(LPSTORE s) // I3432 if(!IsMatchingPlatformString(platform)) { s->dwSystemID = TSS_PLATFORM_NOMATCH; - delete t; + delete [] t; return FALSE; } platform = u16tok(NULL, u' ', &context); } s->dwSystemID = TSS_PLATFORM_MATCH; - delete t; + delete [] t; return TRUE; } @@ -493,7 +495,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) KMX_BOOL bEqual; memset(m_indexStack, 0, GLOBAL_ContextStackSize*sizeof(KMX_WORD)); - + p = kkp->dpContext; if(*p == 0) @@ -515,7 +517,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) { s = &m_keyboard.Keyboard->dpStoreArray[(*(pp+2))-1]; t = &m_keyboard.Keyboard->dpStoreArray[(*(pp+4))-1]; - + bEqual = u16cmp(s->dpString, t->dpString) == 0; if(*(pp+3) == 1 && bEqual) return FALSE; if(*(pp+3) == 2 && !bEqual) return FALSE; @@ -545,7 +547,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) bEqual = u16cmp(ss, t->dpString) == 0; } } - + if(*(pp+3) == 1 && bEqual) return FALSE; if(*(pp+3) == 2 && !bEqual) return FALSE; } @@ -576,7 +578,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; temp = xstrchr(s->dpString, q); - + if(temp != NULL) *indexp = (KMX_WORD) xstrpos(temp, s->dpString); @@ -587,7 +589,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) s = &m_keyboard.Keyboard->dpStoreArray[(*(p+2))-1]; if((temp = xstrchr(s->dpString, q)) != NULL) - return FALSE; + return FALSE; break; case CODE_INDEX: @@ -596,7 +598,7 @@ KMX_BOOL KMX_Processor::ContextMatch(LPKEY kkp) for(temp = s->dpString; *temp && n > 0; temp = incxstr(temp), n--); if(n != 0) return FALSE; - if(xchrcmp(temp, q) != 0) return FALSE; + if(xchrcmp(temp, q) != 0) return FALSE; break; case CODE_CONTEXTEX: // only the nth character @@ -637,10 +639,18 @@ KMX_Options *KMX_Processor::GetOptions() { return &m_options; } +KMX_Options const *KMX_Processor::GetOptions() const { + return &m_options; +} + KMX_Environment *KMX_Processor::GetEnvironment() { return &m_environment; } +KMX_Environment const *KMX_Processor::GetEnvironment() const { + return &m_environment; +} + LPINTKEYBOARDINFO KMX_Processor::GetKeyboard() { return &m_keyboard; } diff --git a/src/kmx/kmx_processor.h b/src/kmx/kmx_processor.h index a29522d..f541c69 100644 --- a/src/kmx/kmx_processor.h +++ b/src/kmx/kmx_processor.h @@ -13,7 +13,7 @@ #include "kmx_options.h" #include "kmx_environment.h" -/***************************************************************************/ +/***************************************************************************/ namespace km { namespace kbp { @@ -42,8 +42,8 @@ private: /* File loading */ LPKEYBOARD FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize); KMX_BOOL LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard); - KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, KMX_DWORD sz); - KMX_BOOL VerifyChecksum(PKMX_BYTE buf, KMX_DWORD sz); + KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, size_t sz); + KMX_BOOL VerifyChecksum(PKMX_BYTE buf, size_t sz); PKMX_WCHAR StringOffset(PKMX_BYTE base, KMX_DWORD offset); #ifdef KMX_64BIT LPKEYBOARD CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base); @@ -85,7 +85,9 @@ public: KMX_Actions *GetActions(); KMX_Context *GetContext(); KMX_Options *GetOptions(); + KMX_Options const *GetOptions() const; KMX_Environment *GetEnvironment(); + KMX_Environment const *GetEnvironment() const; LPINTKEYBOARDINFO GetKeyboard(); }; @@ -113,7 +115,7 @@ extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; #define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) -#else +#else #define DebugLog(msg,...) (ShouldDebug() ? DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) diff --git a/src/kmx/kmx_xstring.cpp b/src/kmx/kmx_xstring.cpp index 45d8743..518e957 100644 --- a/src/kmx/kmx_xstring.cpp +++ b/src/kmx/kmx_xstring.cpp @@ -7,10 +7,12 @@ #include <codecvt> #include <locale> #include "kmx_processor.h" +#include "utfcodec.hpp" -std::string utf16_to_utf8(std::u16string utf16_string); // defined in keyboard.cpp -using namespace km::kbp::kmx; + +using namespace km::kbp; +using namespace kmx; const km_kbp_cp *km::kbp::kmx::u16chr(const km_kbp_cp *p, km_kbp_cp ch) { while (*p) { @@ -148,7 +150,7 @@ PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p) return p-1; } else if(*(p-1) == UC_SENTINEL) return p-1; - else if(*(p-2) == UC_SENTINEL) + else if(*(p-2) == UC_SENTINEL) { switch(*(p-1)) { @@ -159,17 +161,17 @@ PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p) case CODE_CLEARCONTEXT: case CODE_CALL: case CODE_CONTEXTEX: - case CODE_RESETOPT: - case CODE_SAVEOPT: + case CODE_RESETOPT: + case CODE_SAVEOPT: return p-2; } } - else if(*(p-3) == UC_SENTINEL) + else if(*(p-3) == UC_SENTINEL) { switch(*(p-2)) { case CODE_INDEX: - case CODE_SETOPT: + case CODE_SETOPT: case CODE_SETSYSTEMSTORE: return p-3; } @@ -227,20 +229,11 @@ int km::kbp::kmx::xchrcmp(PKMX_WCHAR ch1, PKMX_WCHAR ch2) PKMX_WCHAR km::kbp::kmx::strtowstr(PKMX_CHAR in) { - km_kbp_cp *result; + PKMX_WCHAR result; -#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ - std::wstring_convert<std::codecvt_utf8_utf16<int16_t>, int16_t> convert; -#else - std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert; -#endif - auto s = convert.from_bytes(in, strchr(in, 0)); - result = new KMX_WCHAR[s.length() + 1]; -#if _MSC_VER >= 1900 /* VS 2015 */ && _MSC_VER <= 1916 /* VS 2017 19.16 */ - s.copy(reinterpret_cast<int16_t *>(result), s.length()); -#else + auto s = convert<char,char16_t>(in); + result = new char16_t[s.length() + 1]; s.copy(result, s.length()); -#endif result[s.length()] = 0; return result; } @@ -250,7 +243,7 @@ PKMX_CHAR km::kbp::kmx::wstrtostr(PKMX_WCHAR in) { PKMX_CHAR result; - auto s = ::utf16_to_utf8(in); + auto s = convert<char16_t,char>(in); result = new char[s.length() + 1]; s.copy(result, s.length()); result[s.length()] = 0; diff --git a/src/meson.build b/src/meson.build index f9bf8ad..9bc326f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,7 +22,7 @@ if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang' '-fvisibility=hidden', '-fvisibility-inlines-hidden' ] - links = ['-lstdc++fs'] + links = [] if compiler.get_id() == 'clang' warns += [ @@ -74,7 +74,7 @@ lib = library('kmnkbp0', 'utfcodec.cpp', cpp_args: defns + warns + flags, link_args: links, - version: meson.project_version(), + version: lib_version, include_directories: inc, install: true) diff --git a/src/mock/mock_processor.cpp b/src/mock/mock_processor.cpp index 7033e20..d139a57 100644 --- a/src/mock/mock_processor.cpp +++ b/src/mock/mock_processor.cpp @@ -10,8 +10,7 @@ History: 17 Oct 2018 - TSE - Initial implementation. */ -#include <keyman/keyboardprocessor.h> -#include "processor.hpp" +#include "mock/mock_processor.hpp" #include "state.hpp" namespace @@ -73,18 +72,68 @@ namespace namespace km { namespace kbp { + mock_processor::mock_processor(kbp::path const & path) + : abstract_processor( + keyboard_attributes(path.stem(), u"3.145", path.parent(), { + option{KM_KBP_OPT_KEYBOARD, u"__test_point", u"not tiggered"}, + })), + _options({ + {u"\x01__test_point", u"not tiggered"}, + {u"\x02hello", u"-"} + }) + { + } + + char16_t const * mock_processor::lookup_option(km_kbp_option_scope scope, + std::u16string const & key) const + { + auto i = _options.find(char16_t(scope) + key); + return i != _options.end() ? i->second.c_str() : nullptr; + } + + option mock_processor::update_option(km_kbp_option_scope scope, + std::u16string const & key, + std::u16string const & value) + { + auto i = _options.find(char16_t(scope) + key); + if (i == _options.end()) return option(); + + i->second = value; + persisted_store()[key] = value; + return option(scope, key, i->second); + } + + + km_kbp_status mock_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) + { + assert(state); + if (!state) + return KM_KBP_STATUS_INVALID_ARGUMENT; - km_kbp_status mock_processor::process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) { try { - state->actions.clear(); + // At the start of every process_event allways clear the action_items + state->actions().clear(); switch (vk) { case KM_KBP_VKEY_BKSP: state->context().pop_back(); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_BACK, {0,}, {1} }); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + state->actions().push_backspace(); + break; + + case KM_KBP_VKEY_F2: + { + state->actions().push_persist( + update_option(KM_KBP_OPT_KEYBOARD, + u"__test_point", + u"F2 pressed test save.")); + break; + } + + case KM_KBP_VKEY_F4: + state->context().push_marker(KM_KBP_VKEY_QUOTE); + state->actions().push_marker(KM_KBP_VKEY_QUOTE); break; default: @@ -98,24 +147,25 @@ namespace km { for (auto c = char_seq; *c; ++c) { km_kbp_usv usv = *c; - state->context().emplace_back(km_kbp_context_item{ KM_KBP_CT_CHAR,{0,},{usv} }); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_CHAR, {0,}, {usv} }); + state->context().push_character(usv); + state->actions().push_character(usv); } - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + state->actions().commit(); return KM_KBP_STATUS_OK; } // Both shift states output nothing, generate an alert. - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_ALERT, {0,}, {0} }); - state->actions.emplace_back(km_kbp_action_item{ KM_KBP_IT_END, {0,}, {0} }); + state->actions().push_alert(); break; } } + + state->actions().commit(); } catch (std::bad_alloc) { - state->actions.clear(); + state->actions().clear(); return KM_KBP_STATUS_NO_MEM; } @@ -123,11 +173,12 @@ namespace km { } - km_kbp_attr const * mock_processor::get_attrs() const { - return &engine_attrs; + km_kbp_attr const & mock_processor::attributes() const { + return engine_attrs; } km_kbp_status mock_processor::validate() const { return KM_KBP_STATUS_OK; } + km_kbp_status null_processor::validate() const { return KM_KBP_STATUS_INVALID_ARGUMENT; } } // namespace kbp } // namespace km diff --git a/src/mock/mock_processor.hpp b/src/mock/mock_processor.hpp new file mode 100644 index 0000000..7cb2b4d --- /dev/null +++ b/src/mock/mock_processor.hpp @@ -0,0 +1,55 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal keyboard class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp +*/ + +#pragma once + +#include <string> +#include <unordered_map> +#include <keyman/keyboardprocessor.h> + +#include "processor.hpp" +#include "option.hpp" + +namespace km { +namespace kbp +{ + class mock_processor : public abstract_processor + { + std::unordered_map<std::u16string, std::u16string> _options; + + public: + mock_processor(km::kbp::path const &); +// ~mock_processor() override; + + km_kbp_status process_event(km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state) override; + + virtual km_kbp_attr const & attributes() const override; + km_kbp_status validate() const override; + + + + char16_t const * lookup_option(km_kbp_option_scope, + std::u16string const & key) const override; + option update_option(km_kbp_option_scope, + std::u16string const & key, + std::u16string const & value) override; + }; + + class null_processor : public mock_processor { + public: + null_processor(): mock_processor(path()) + { + _attributes = keyboard_attributes(u"null", u"0.0", path(), {}); + } + + km_kbp_status validate() const override; + }; +} // namespace kbp +} // namespace km diff --git a/src/option.cpp b/src/option.cpp index b4c12b5..e4c6620 100644 --- a/src/option.cpp +++ b/src/option.cpp @@ -7,13 +7,10 @@ History: 7 Nov 2018 - TSE - Refactored into option.hpp & option.cpp. */ #include <algorithm> -#include <memory> -#include "keyboard.hpp" #include "option.hpp" -#include "json.hpp" -#include "utfcodec.hpp" -#include "state.hpp" +#include "processor.hpp" + using namespace km::kbp; @@ -28,111 +25,48 @@ namespace // Forward declarations -option::option(km_kbp_option_scope s, std::u16string const & k, std::u16string const & v) -: km_kbp_option_item { new km_kbp_cp[k.size()+1], new km_kbp_cp[v.size()+1], - uint8_t(s) } -{ - std::copy_n(k.c_str(), k.size()+1, const_cast<km_kbp_cp *>(key)); - std::copy_n(v.c_str(), v.size()+1, const_cast<km_kbp_cp *>(value)); -} - - -char16_t const * options::lookup(km_kbp_option_scope scope, - std::u16string const & key) const noexcept -{ - // Search first in the updated values - for (auto & opt: _saved) - { - if (opt.key == key && opt.scope == scope) - return opt.value; - } - - // Then in the pristine copies. - km_kbp_option_item const * opt = _scopes[scope-1]; - while (opt->key && key != opt->key) ++opt; - return opt->key ? opt->value : nullptr; -} - - -km_kbp_option_item const * options::assign(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, - std::u16string const & value) +option::option(km_kbp_option_scope s, char16_t const *k, char16_t const *v) +: option() { - km_kbp_option_item const * opt = _scopes[scope-1]; - while (opt->key && key != opt->key) ++opt; - if (!opt->key) return nullptr; - - for (auto & save: _saved) + if (k && v) { - if (save.key == key && save.scope == scope) - { - save = option(scope, key, value); - - //((km::kbp::state *)state)->keyboard(). - const_cast<km::kbp::abstract_processor &>(static_cast<km::kbp::state *>(state)->keyboard().processor()).update_option(state, scope, key, value); - - return &save; - } + auto n_k = std::char_traits<char16_t>::length(k)+1, + n_v = std::char_traits<char16_t>::length(v)+1; + auto _key = new km_kbp_cp[n_k], + _val = new km_kbp_cp[n_v]; + std::copy_n(k, n_k, _key); + std::copy_n(v, n_v, _val); + + key = _key; + value = _val; + scope = s; } - - _saved.emplace_back(scope, key, value); - - const_cast<km::kbp::abstract_processor &>(static_cast<km::kbp::state *>(state)->keyboard().processor()).update_option(state, scope, key, value); - - return &_saved.back(); } -void options::reset(km_kbp_option_scope scope, std::u16string const & key) -{ - for (auto i = _saved.begin(); i == _saved.end(); ++i) - { - if (i->key == key && i->scope == scope) - { - _saved.erase(i); - break; - } - } -} - - - -json & operator << (json &j, char16_t const *u16str) { - char cps[5] = {0,}; - int8_t l; - std::string u8str; - - for (auto i = utf16::const_iterator(u16str); *i; ++i) - { - utf8::codec::put(reinterpret_cast<utf8::codeunit_t *>(cps), *i, l); - u8str.append(cps, size_t(l)); - } - j << u8str; - return j; -} - - -json & km::kbp::operator << (json &j, options const &opts) +// TODO: Relocate this and fix it +json & km::kbp::operator << (json &j, abstract_processor const &) { j << json::object; - auto n = 0; - for (auto scope: opts._scopes) - { - j << scope_names_lut[n++] << json::object; - for (auto opt = scope; opt->key; ++opt) - { - j << opt->key << opt->value; - } - j << json::close; - } + // auto n = 0; + // for (auto scope: opts._scopes) + // { + // j << scope_names_lut[n++] << json::object; + // for (auto opt = scope; opt->key; ++opt) + // { + // j << opt->key << opt->value; + // } + // j << json::close; + // } j << "saved" << json::object; for (auto scope: {KM_KBP_OPT_KEYBOARD, KM_KBP_OPT_ENVIRONMENT}) { j << scope_names_lut[scope-1] << json::object; - for (auto & opt: opts._saved) - { - if (opt.scope != scope) continue; - j << opt.key << opt.value; - } + // for (auto & opt: opts._saved) + // { + // if (opt.scope != scope) continue; + // j << opt.key << opt.value; + // } j << json::close; } j << json::close; diff --git a/src/option.hpp b/src/option.hpp index 6c9b664..70627bf 100644 --- a/src/option.hpp +++ b/src/option.hpp @@ -10,7 +10,6 @@ #pragma once -#include <vector> #include <string> #include <keyman/keyboardprocessor.h> @@ -26,25 +25,37 @@ namespace kbp option(): km_kbp_option_item KM_KBP_OPTIONS_END {} option(option const &); option(option &&); + option(km_kbp_option_scope, char16_t const *, char16_t const *); option(km_kbp_option_scope, std::u16string const &, std::u16string const &); ~option() noexcept; + option & operator=(option const & rhs); option & operator=(option && rhs); + + bool empty() const; }; + inline - option::option(option const & rhs) - : option(km_kbp_option_scope(rhs.scope), rhs.key, rhs.value) + option::option(km_kbp_option_scope s, + std::u16string const & k, std::u16string const & v) + : option(s, k.c_str(), v.c_str()) {} + + inline + option::option(option const & rhs) + : option(km_kbp_option_scope(rhs.scope), rhs.key, rhs.value) {} + + inline - option::option(option && rhs) - : km_kbp_option_item { rhs.key, rhs.value, rhs.scope } + option::option(option && rhs) : option() { - rhs.key = nullptr; - rhs.value = nullptr; + std::swap(key, rhs.key); + std::swap(value, rhs.value); + scope = rhs.scope; } inline @@ -54,54 +65,29 @@ namespace kbp delete [] value; } + inline - option & option::operator=(option && rhs) - { + option & option::operator=(option && rhs) { delete [] key; delete [] value; - key = rhs.key; - value = rhs.value; rhs.key = nullptr; - scope = rhs.scope; rhs.value = nullptr; - return *this; + return *new (this) option(std::move(rhs)); } - - class options + inline + option & option::operator=(option const & rhs) { - //km_kbp_keyboard_attrs const &_kb; - km_kbp_option_item const * _scopes[KM_KBP_OPT_MAX_SCOPES-1]; - std::vector<option> _saved; - - public: - options(km_kbp_option_item const * kb_default_options); - - void set_default_env(km_kbp_option_item const *env); - - char16_t const * lookup(km_kbp_option_scope scope, - std::u16string const & key) const noexcept; - km_kbp_option_item const * assign(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, - std::u16string const & value); - void reset(km_kbp_option_scope scope, - std::u16string const & key); - - friend json & operator << (json &j, km::kbp::options const &opts); - }; - - json & operator << (json &j, km::kbp::options const &opts); + delete [] key; + delete [] value; + return *new (this) option(rhs); + } inline - options::options(km_kbp_option_item const * kb_default_options) - : _scopes {kb_default_options, nullptr} - {} - - inline void options::set_default_env(km_kbp_option_item const *env) { - _scopes[KM_KBP_OPT_ENVIRONMENT - 1] = env; + bool option::empty() const { + return key == nullptr; } -} // namespace kbp -} // namespace km -// Adaptor between internal km::kbp::options object and API definitiion. -struct km_kbp_options: public km::kbp::options {}; +} // namespace kbp +} // namespace km diff --git a/src/path.hpp b/src/path.hpp new file mode 100644 index 0000000..f7600f3 --- /dev/null +++ b/src/path.hpp @@ -0,0 +1,146 @@ +/* + Copyright: © 2018 SIL International. + Description: Internal keyboard class and adaptor class for the API. + Create Date: 2 Oct 2018 + Authors: Tim Eves (TSE) + History: 2 Oct 2018 - TSE - Refactored out of km_kbp_keyboard_api.cpp +*/ + +#pragma once + +#include <algorithm> +#include <string> +#include <type_traits> + +#include <keyman/keyboardprocessor.h> +#include "json.hpp" +#include "utfcodec.hpp" + +// Forward declarations + +namespace km { +namespace kbp +{ + class path + { + public: + using char_type = std::remove_const_t< + std::remove_pointer_t<km_kbp_path_name>>; + using string_type = std::basic_string<char_type>; + static constexpr char_type const parent_separator = _KM_KBP_PATH_SEPARATOR, + suffix_separator = _KM_KBP_EXT_SEPARATOR; + private: + string_type _path; + + void normalise() { + #if '/' != _KM_KBP_PATH_SEPARATOR + std::replace(_path.begin(), _path.end(), char_type('/'), _KM_KBP_PATH_SEPARATOR); + #endif + } + + public: + template<class... Args> + static path join(path const &start, Args&&... args) { + auto r = start; + for (path p: {args...}) + r._path.append(1, path::parent_separator).append(p); + return r; + } + + path() = default; + path(path const &) = default; + path(path &&) = default; + path & operator = (path const &) = default; + path & operator = (path &&) = default; + + path(std::string const & p): _path(convert<char, char_type>(p)) { normalise(); } + path(std::u16string const & p): _path(convert<char16_t, char_type>(p)) { normalise(); } + path(std::wstring const & p): _path(convert<wchar_t, char_type>(p)) { normalise(); } + template<typename C> + path(C const * p): path(std::basic_string<C>(p)) {} + + + path parent() const { + auto i = _path.find_last_of(parent_separator); + return _path.substr(0, i == string_type::npos ? 0UL : i); + } + + path name() const { + auto i = _path.find_last_of(parent_separator); + return _path.substr(i == string_type::npos ? 0UL : i+1); + } + + path suffix() const { + auto i = _path.find_last_of(suffix_separator); + return _path.substr(i == string_type::npos ? _path.size() : i); + } + + path stem() const { + auto psep = _path.find_last_of(parent_separator), + ssep = _path.find_last_of(suffix_separator); + return _path.substr(psep == string_type::npos ? 0UL : psep+1, + ssep == string_type::npos ? _path.size() : ssep); + } + + void replace_extension(path const & replacment = path()) { + _path.resize(std::min(_path.find_last_of(suffix_separator), _path.size())); + _path += replacment; + } + + // template<typename T> + // bool operator == (T const * rhs) const { return _path == rhs; } + + bool operator == (path const &rhs) const { return _path == rhs._path; } + + // template<typename T> + // bool operator != (T const * rhs) const { return _path != rhs; } + + bool operator != (path const &rhs) const { return _path != rhs._path; } + + string_type const & native() const noexcept { return _path; } + + operator std::wstring () const { return convert<char_type,wchar_t>(_path); } + operator std::string() const { return convert<char_type,char>(_path); }; + operator std::u16string() const {return convert<char_type,char16_t>(_path); }; + + char_type const * c_str() const { return _path.c_str(); } + + path & operator += (path const & rhs) { + _path += rhs._path; return *this; + } + + path operator + (path const & rhs) const { + auto r = *this; return r += rhs; + } + + path & operator /= (path const & rhs) { + _path.append(1,path::parent_separator).append(rhs._path); + return *this; + } + + path operator / (path const & rhs) const { + auto r = *this; return r /= rhs; + } + + friend json & operator << (json &, path const &); + }; + + template<typename T> + bool operator == (T const * lhs, path const & rhs) { return rhs == lhs; } + + template<typename T> + bool operator != (T const * lhs, path const & rhs) { return rhs != lhs; } + + inline + json & operator << (json &j, path const &p) { + return j << static_cast<std::string>(p); + } + + template<typename C> + auto & operator << (std::basic_ostream<C> &os, path const &p) { + os << static_cast<std::basic_string<C>>(p); + return os; + } + +} // namespace kbp +} // namespace km diff --git a/src/processor.hpp b/src/processor.hpp index e3cef30..68a0559 100644 --- a/src/processor.hpp +++ b/src/processor.hpp @@ -9,73 +9,52 @@ #pragma once #include <string> +#include <unordered_map> + #include <keyman/keyboardprocessor.h> -#include <kmx/kmx_processor.h> + +#include "keyboard.hpp" namespace km { namespace kbp { - class abstract_processor { - private: - km_kbp_keyboard_attrs const *_kb; + std::unordered_map<std::u16string, std::u16string> _persisted; protected: - km_kbp_keyboard_attrs const * keyboard() const noexcept { return _kb; } + keyboard_attributes _attributes; + public: + abstract_processor() {} + abstract_processor(keyboard_attributes && kb) : _attributes(std::move(kb)) {} virtual ~abstract_processor() { }; - abstract_processor(km_kbp_keyboard_attrs const * kb) : _kb(kb) - {} + keyboard_attributes const & keyboard() const noexcept { + return _attributes; + } - virtual km_kbp_status process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state) = 0; - virtual km_kbp_attr const * get_attrs() const = 0; - virtual km_kbp_status validate() const = 0; - virtual void update_option(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value) = 0; - virtual void init_state(std::vector<km_kbp_option_item> *default_env) = 0; - }; + auto & persisted_store() const noexcept { return _persisted; } + auto & persisted_store() noexcept { return _persisted; } - class kmx_processor : public abstract_processor - { - private: - bool _valid; - kmx::KMX_Processor _kmx; - public: - kmx_processor(km_kbp_keyboard_attrs const * kb_); - km_kbp_status process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state); - km_kbp_attr const * get_attrs() const; - km_kbp_status validate() const; - void update_option(km_kbp_state *state, km_kbp_option_scope scope, std::u16string const & key, std::u16string const & value); - void init_state(std::vector<km_kbp_option_item> *default_env); - }; + virtual km_kbp_status process_event(km_kbp_state *, + km_kbp_virtual_key, + uint16_t modifier_state) = 0; - class mock_processor : public abstract_processor - { - public: - mock_processor(km_kbp_keyboard_attrs const * kb) : abstract_processor(kb) { - } - km_kbp_status process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state); - km_kbp_attr const * get_attrs() const; - km_kbp_status validate() const; - void update_option(km_kbp_state *_kmn_unused(state), km_kbp_option_scope _kmn_unused(scope), std::u16string const & _kmn_unused(key), std::u16string const & _kmn_unused(value)) {}; - void init_state(std::vector<km_kbp_option_item> *default_env) { - km_kbp_option_item opt = { u"hello", u"-", 0 }; - default_env->emplace_back(opt); - opt = KM_KBP_OPTIONS_END; - default_env->emplace_back(opt); - }; - }; + virtual km_kbp_attr const & attributes() const = 0; + virtual km_kbp_status validate() const = 0; - class null_processor : public mock_processor { - public: - null_processor(km_kbp_keyboard_attrs const * kb) : mock_processor(kb) { - } - km_kbp_status validate() const; - void init_state(std::vector<km_kbp_option_item> *default_env) { - km_kbp_option_item opt = KM_KBP_OPTIONS_END; - default_env->emplace_back(opt); - } + virtual char16_t const * lookup_option(km_kbp_option_scope, + std::u16string const & key) const = 0; + virtual option update_option(km_kbp_option_scope, + std::u16string const & key, + std::u16string const & value) = 0; + + friend json & operator << (json &j, abstract_processor const &opts); }; + json & operator << (json &j, abstract_processor const &opts); + } // namespace kbp } // namespace km + +struct km_kbp_keyboard : public km::kbp::abstract_processor {}; diff --git a/src/state.cpp b/src/state.cpp index ceaeeb2..eb93bca 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -10,14 +10,32 @@ using namespace km::kbp; -state::state(km::kbp::keyboard const & kb, km_kbp_option_item const * env) - : _options(kb.default_options), _kb(kb) -{ - const_cast<km::kbp::abstract_processor&>(_kb.processor()).init_state(&_env); - _options.set_default_env(_env.data()); +void actions::push_persist(option const &opt) { + assert(empty() || back().type != KM_KBP_IT_END); + _option_items_stack.emplace_back(opt); + km_kbp_action_item ai = {KM_KBP_IT_PERSIST_OPT, {0,}, {0}}; + ai.option = &_option_items_stack.back(); + emplace_back(std::move(ai)); +} + +void actions::push_persist(option const &&opt) { + assert(empty() || back().type != KM_KBP_IT_END); + _option_items_stack.emplace_back(opt); + km_kbp_action_item ai = {KM_KBP_IT_PERSIST_OPT, {0,}, {0}}; + ai.option = &_option_items_stack.back(); + emplace_back(std::move(ai)); +} + +state::state(km::kbp::abstract_processor & ap, km_kbp_option_item const *env) + : _processor(ap) +{ for (; env && env->key != nullptr; env++) { //assert(env->scope == KM_KBP_OPT_ENVIRONMENT); // todo do we need scope? or can we find a way to eliminate it? - assert(_options.assign(static_cast<km_kbp_state *>(this), (km_kbp_option_scope) KM_KBP_OPT_ENVIRONMENT, env->key, env->value) != nullptr); + ap.update_option(env->scope + ? km_kbp_option_scope(env->scope) + : KM_KBP_OPT_ENVIRONMENT, + env->key, + env->value); } } diff --git a/src/state.hpp b/src/state.hpp index 0e014de..503338e 100644 --- a/src/state.hpp +++ b/src/state.hpp @@ -7,40 +7,136 @@ */ #pragma once + +#include <cassert> #include <vector> #include <keyman/keyboardprocessor.h> #include "context.hpp" #include "option.hpp" -#include "keyboard.hpp" namespace km { namespace kbp { +//Forward declarations +class abstract_processor; + using action = km_kbp_action_item; +class actions : public std::vector<action> +{ + std::vector<option> _option_items_stack; + + template<km_kbp_action_type V> + void _push_vkey(km_kbp_virtual_key); + +public: + template<typename... Args> + actions(Args&&... args); + + void push_character(km_kbp_usv usv); + void push_marker(uintptr_t marker); + void push_alert(); + void push_backspace(); + void push_persist(option const &); + void push_persist(option const &&); + void push_emit_keystroke(km_kbp_virtual_key vk=0); + void push_invalidate_context(); + + void commit(); + void clear(); +}; + + +template<typename... Args> +actions::actions(Args&&... args) +: std::vector<action>(std::forward<Args>(args)...) +{ + // Ensure the action items list is terminated in case the client calls + // km_kbp_state_action_items before they call process_event. + commit(); +} + +inline +void actions::push_character(km_kbp_usv usv) { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item{ KM_KBP_IT_CHAR, {0,}, {usv} }); +} + + +inline +void actions::push_marker(uintptr_t marker) { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_MARKER, {0,}, {marker}}); +} + + +inline +void actions::push_alert() { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_ALERT, {0,}, {0}}); +} + + +inline +void actions::push_backspace() { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_BACK, {0,}, {0}}); +} + + +inline +void actions::push_emit_keystroke(km_kbp_virtual_key vk) { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_EMIT_KEYSTROKE, {0,}, {vk}}); +} + + +inline +void actions::push_invalidate_context() { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_INVALIDATE_CONTEXT, {0,}, {0}}); +} + + +inline +void actions::commit() { + assert(empty() || (!empty() && back().type != KM_KBP_IT_END)); + emplace_back(km_kbp_action_item {KM_KBP_IT_END, {0,}, {0}}); +} + + +inline +void actions::clear() { + std::vector<action>::clear(); + _option_items_stack.clear(); +} + + + class state { protected: - kbp::context _ctxt; - kbp::options _options; - kbp::keyboard const & _kb; - std::vector<km_kbp_option_item> _env; + kbp::context _ctxt; + kbp::abstract_processor & _processor; + kbp::actions _actions; public: - state(kbp::keyboard const & kb, km_kbp_option_item const * env); + state(kbp::abstract_processor & kb, km_kbp_option_item const *env); + state(state const &) = default; state(state const &&) = delete; kbp::context & context() noexcept { return _ctxt; } kbp::context const & context() const noexcept { return _ctxt; } - kbp::options & options() noexcept { return _options; } - kbp::options const & options() const noexcept { return _options; } + kbp::abstract_processor const & processor() const noexcept { return _processor; } + kbp::abstract_processor & processor() noexcept { return _processor; } - kbp::keyboard const & keyboard() const noexcept { return _kb; } + kbp::actions & actions() noexcept { return _actions; } + kbp::actions const & actions() const noexcept { return _actions; } }; } // namespace kbp @@ -51,6 +147,4 @@ struct km_kbp_state : public km::kbp::state template<typename... Args> km_kbp_state(Args&&... args) : km::kbp::state(std::forward<Args>(args)...) {} - - std::vector<km_kbp_action_item> actions; }; diff --git a/src/utfcodec.hpp b/src/utfcodec.hpp index 099a67f..3f22288 100644 --- a/src/utfcodec.hpp +++ b/src/utfcodec.hpp @@ -11,9 +11,10 @@ */ #pragma once -#include <cstddef> #include <cstdint> +#include <cstddef> #include <cstdlib> +#include <string> typedef uint32_t uchar_t; @@ -257,3 +258,48 @@ struct utf typedef utf<uint32_t> utf32; typedef utf<uint16_t> utf16; typedef utf<uint8_t> utf8; + + +template<typename F, typename T> +std::basic_string<T> convert(std::basic_string<F> const &src); + +template<> +inline +std::basic_string<char> convert(std::basic_string<char> const &src) { + return src; +} + +template<typename F, typename T> +std::basic_string<T> convert(std::basic_string<F> const &src) { + using utf_const_iter = typename utf<typename utf<F>::codeunit_t>::const_iterator; + using codeunit_t = typename utf<T>::codeunit_t; + auto r = std::basic_string<T>(); + codeunit_t buf[4]; + int8_t l = 1; + for (auto i = utf_const_iter(src.data()), + e = decltype(i)(src.data() + src.size()); i != e && l > 0; ++i) { + utf<T>::codec::put(buf, uchar_t(*i), l); + r.append(reinterpret_cast<T *>(&buf[0]), l); + } + return r; +} + +template<typename T> +auto & operator << (std::basic_ostream<T> &os, std::u16string const &p) { + return std::operator << (os, convert<char16_t,T>(p)); +} + +template<typename T> +auto & operator << (std::basic_ostream<T> &os, std::u32string const &p) { + return std::operator << (os, convert<char32_t,T>(p)); +} + +template<typename T> +auto & operator << (std::basic_ostream<T> &os, char16_t const *s) { + return std::operator << (os, convert<char16_t,T>(s)); +} + +template<typename T> +auto & operator << (std::basic_ostream<T> &os, char32_t const *s) { + return std::operator << (os, convert<char32_t,T>(s)); +} diff --git a/tests/unit/kmnkbd/keyboard_api.cpp b/tests/unit/kmnkbd/keyboard_api.cpp index ce1a408..67d59a5 100644 --- a/tests/unit/kmnkbd/keyboard_api.cpp +++ b/tests/unit/kmnkbd/keyboard_api.cpp @@ -7,18 +7,13 @@ #include <string> #include <keyman/keyboardprocessor.h> -#include <experimental/filesystem> - -namespace std { - namespace filesystem = std::experimental::filesystem; -} - +#include "path.hpp" //#include "keyboard.hpp" namespace { - std::filesystem::path const test_kb_path = "/a/dummy/keyboard.mock"; + km::kbp::path const test_kb_path = "/a/dummy/keyboard.mock"; } #define try_status(expr) \ @@ -31,7 +26,7 @@ int main(int, char *[]) try_status(km_kbp_keyboard_load(test_kb_path.c_str(), &test_kb)); try_status(km_kbp_keyboard_get_attrs(test_kb, &kb_attrs)); - if (kb_attrs->folder_path != test_kb_path.parent_path()) + if (kb_attrs->folder_path != test_kb_path.parent()) return __LINE__; km_kbp_keyboard_dispose(test_kb); diff --git a/tests/unit/kmnkbd/meson.build b/tests/unit/kmnkbd/meson.build index 726b115..92aed00 100644 --- a/tests/unit/kmnkbd/meson.build +++ b/tests/unit/kmnkbd/meson.build @@ -6,31 +6,19 @@ # defns=['-DKMN_KBP_STATIC'] +tests = [ + ['context-api', 'context_api.cpp'], + ['keyboard-api', 'keyboard_api.cpp'], +# ['options-api', 'options_api.cpp'], + ['state-api', 'state_api.cpp'] +] - -ctxt = executable('context-api', 'context_api.cpp', - cpp_args: defns, - include_directories: [inc, libsrc], - link_args: links, - objects: lib.extract_all_objects()) -keyb = executable('keyboard-api', 'keyboard_api.cpp', - cpp_args: defns, - include_directories: [inc, libsrc], - link_args: links, - #objects: lib.extract_objects('km_kbp_keyboard_api.cpp', 'json.cpp', 'keyboard.cpp', 'keyboardprocessor.cpp', 'mock/mock_processor.cpp')) - objects: lib.extract_all_objects()) -opts = executable('options-api', 'options_api.cpp', - cpp_args: defns, - include_directories: [inc, libsrc], - link_args: links, - objects: lib.extract_all_objects()) -state = executable('state-api', 'state_api.cpp', +foreach t : tests + bin = executable(t[0], t[1], cpp_args: defns, include_directories: [inc, libsrc], link_args: links, objects: lib.extract_all_objects()) -test('context-api', ctxt) -test('keyboard-api', keyb) -test('options-api', opts) -test('state-api', state) + test(t[0], bin) +endforeach diff --git a/tests/unit/kmnkbd/options_api.cpp b/tests/unit/kmnkbd/options_api.cpp index 82f4459..c2b287b 100644 --- a/tests/unit/kmnkbd/options_api.cpp +++ b/tests/unit/kmnkbd/options_api.cpp @@ -10,6 +10,7 @@ #include <keyman/keyboardprocessor.h> #include "option.hpp" +#include "state.hpp" #define try_status(expr) \ {auto __s = (expr); if (__s != KM_KBP_STATUS_OK) std::exit(100*__LINE__+__s);} @@ -51,20 +52,16 @@ namespace km_kbp_option_item const empty_options_list[] = {KM_KBP_OPTIONS_END}; #if 0 - km::kbp::options mock_options(test_kb, test_env); - km::kbp::options empty_options(empty_options_list, empty_options_list); - km_kbp_options * api_mock_options = static_cast<km_kbp_options *>( - &mock_options), - * api_empty_options = static_cast<km_kbp_options *>( - &empty_options); + km::kbp::state mock_state(test_kb, test_env); + km::kbp::state empty_state(empty_options_list, empty_options_list); #endif - std::string get_json_doc(km_kbp_options * const opts) + std::string get_json_doc(km_kbp_state * const state) { size_t sz = 0; - try_status(km_kbp_options_to_json(opts, nullptr, &sz)); + try_status(km_kbp_state_options_to_json(state, nullptr, &sz)); std::string buf(sz-1, 0); - try_status(km_kbp_options_to_json(opts, &buf[0], &sz)); + try_status(km_kbp_state_options_to_json(state, &buf[0], &sz)); return buf; } @@ -74,17 +71,14 @@ namespace { #if 0 km_kbp_cp const * ret = nullptr; - auto s = km_kbp_options_lookup(api_mock_options, scope, + auto s = km_kbp_state_option_lookup(api_mock_options, scope, key.c_str(), &ret); bool v = s == KM_KBP_STATUS_OK && ret == value; - if (s == KM_KBP_STATUS_OK) { - km_kbp_cp_dispose(ret); - } return v; #else return true; -#endif; +#endif } constexpr char const *empty_json = "\ diff --git a/tests/unit/kmnkbd/state_api.cpp b/tests/unit/kmnkbd/state_api.cpp index f81a2bd..8894187 100644 --- a/tests/unit/kmnkbd/state_api.cpp +++ b/tests/unit/kmnkbd/state_api.cpp @@ -6,9 +6,11 @@ */ #include <cstdlib> #include <cstring> +#include <iostream> #include <string> #include <keyman/keyboardprocessor.h> +#include "path.hpp" #include "state.hpp" @@ -42,18 +44,6 @@ constexpr char const *doc1_expected = u8"\ \"version\" : \"3.145\",\n\ \"rules\" : []\n\ },\n\ - \"options\" : {\n\ - \"keyboard\" : {},\n\ - \"environment\" : {\n\ - \"hello\" : \"-\"\n\ - },\n\ - \"saved\" : {\n\ - \"keyboard\" : {},\n\ - \"environment\" : {\n\ - \"hello\" : \"world\"\n\ - }\n\ - }\n\ - },\n\ \"context\" : [\n\ \"H\",\n\ \"e\",\n\ @@ -67,7 +57,7 @@ constexpr char const *doc1_expected = u8"\ \"L\"\n\ ],\n\ \"actions\" : [\n\ - { \"character\" : \"L\" }\n\ + { \"persist\" : { \"keyboard\" : { \"__test_point\" : \"F2 pressed test save.\" } } }\n\ ]\n\ }\n"; @@ -80,22 +70,41 @@ constexpr char const *doc2_expected = u8"\ \"version\" : \"3.145\",\n\ \"rules\" : []\n\ },\n\ - \"options\" : {\n\ - \"keyboard\" : {},\n\ - \"environment\" : {\n\ - \"hello\" : \"-\"\n\ - },\n\ - \"saved\" : {\n\ - \"keyboard\" : {},\n\ - \"environment\" : {\n\ - \"hello\" : \"globe\"\n\ - }\n\ - }\n\ - },\n\ \"context\" : [],\n\ \"actions\" : []\n\ }\n"; + +constexpr km_kbp_option_item const expected_persist_opt = { + u"__test_point", + u"F2 pressed test save.", + KM_KBP_OPT_KEYBOARD +}; + +inline +bool operator==(km_kbp_option_item const & lhs, km_kbp_option_item const & rhs) +{ + return lhs.scope == rhs.scope + && std::u16string(lhs.key) == rhs.key + && std::u16string(lhs.value) == rhs.value; +} + + +bool operator==(km_kbp_action_item const & lhs, + km_kbp_action_item const & rhs) +{ + if (lhs.type != rhs.type) return false; + switch(lhs.type) + { + case KM_KBP_IT_CHAR: return lhs.character == rhs.character; + case KM_KBP_IT_MARKER: return lhs.marker == rhs.marker; + case KM_KBP_IT_PERSIST_OPT: return *lhs.option == *rhs.option; + default: break; + } + + return true; +} + #ifdef assert #undef assert #endif @@ -107,7 +116,7 @@ bool action_items(km_kbp_state const * state, auto act = km_kbp_state_action_items(state, &n); for (auto &rhs: expected) - if (std::memcmp(act++, &rhs, sizeof rhs) != 0) return false; + if (!(*act++ == rhs)) return false; return true; } @@ -119,7 +128,7 @@ int main(int, char * []) km_kbp_keyboard * test_kb = nullptr; km_kbp_state * test_state = nullptr, * test_clone = nullptr; - try_status(km_kbp_keyboard_load(std::filesystem::path("dummy.mock").c_str(), &test_kb)); + try_status(km_kbp_keyboard_load(km::kbp::path("dummy.mock").c_str(), &test_kb)); // Simple sanity tests. try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); @@ -127,8 +136,6 @@ int main(int, char * []) // Check sub objects have been copied and not shared. if (km_kbp_state_context(test_state) == km_kbp_state_context(test_clone)) return __LINE__; - if (km_kbp_state_options(test_state) == km_kbp_state_options(test_clone)) - return __LINE__; size_t n_actions = 0; if (km_kbp_state_action_items(test_state, &n_actions) == nullptr && n_actions != 0) @@ -148,8 +155,7 @@ int main(int, char * []) km_kbp_option_item new_opt[] = { {u"hello", u"globe", KM_KBP_OPT_ENVIRONMENT}, KM_KBP_OPTIONS_END}; - try_status( - km_kbp_options_update(test_clone, new_opt)); + try_status(km_kbp_state_options_update(test_clone, new_opt)); // Test the engine auto attrs = km_kbp_get_engine_attrs(test_state); @@ -168,15 +174,21 @@ int main(int, char * []) assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('l')}}})); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0)); - assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {1}}})); + assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {0}}})); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, KM_KBP_MODIFIER_SHIFT)); assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('L')}}})); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2,0)); + assert(action_items(test_state, {{KM_KBP_IT_PERSIST_OPT, {0,}, + {uintptr_t(&expected_persist_opt)}}})); // Test debug dump auto doc1 = get_json_doc(*test_state), doc2 = get_json_doc(*test_clone); + std::cout << doc1 << std::endl; + std::cout << doc2 << std::endl; + // These should not be equal. if (doc1 == doc2) return __LINE__; // These should be. diff --git a/tests/unit/kmx/015 - ralt 2.kmn b/tests/unit/kmx/015 - ralt 2.kmn index f2908af..ee87dd6 100644 --- a/tests/unit/kmx/015 - ralt 2.kmn +++ b/tests/unit/kmx/015 - ralt 2.kmn @@ -1,5 +1,5 @@ c Description: Tests Right Alt processing with non-US kbds. -c keys: abc[RALT K_A] +c keys: [K_A][K_B][K_C][RALT K_A] c expected: abcd store(&VERSION) '9.0' diff --git a/tests/unit/kmx/017 - space mnemonic kbd.kmn b/tests/unit/kmx/017 - space mnemonic kbd.kmn index c26e71a..33b2153 100644 --- a/tests/unit/kmx/017 - space mnemonic kbd.kmn +++ b/tests/unit/kmx/017 - space mnemonic kbd.kmn @@ -1,5 +1,5 @@ c Description: Tests Space handling in mnemonic keyboards (failed with Win 98) -c keys: ab c d de +c keys: [K_A][K_B][K_SPACE][K_C][K_SPACE][K_D][K_SPACE][K_D][K_E] c expected: XYZ store(&VERSION) '9.0' diff --git a/tests/unit/kmx/020 - deadkeys and backspace.kmn b/tests/unit/kmx/020 - deadkeys and backspace.kmn index 6d13a0f..b5d34a1 100644 --- a/tests/unit/kmx/020 - deadkeys and backspace.kmn +++ b/tests/unit/kmx/020 - deadkeys and backspace.kmn @@ -6,8 +6,8 @@ c 4. Two deadkeys in a row in context dk(4a) dk(4b) + BKSP = nul c 5. One char and two deadkeys in context 'a' dk(5a) dk(5b) 'b' + BKSP = 'a' c 6. One char and two deadkeys and one char and two deadkeys in context 'a' dk(6a) dk(6b) 'b' dk(6c) dk(6d) + BKSP = 'a' c keys: [K_1][K_BKSP][K_2][K_BKSP][K_3][K_BKSP][K_4][K_BKSP][K_5][K_BKSP][K_6][K_BKSP] -c expected: aa -c context: +c expected: 12aa +c context: 1234 store(&VERSION) '9.0' diff --git a/tests/unit/kmx/020 - deadkeys and backspace.kmx b/tests/unit/kmx/020 - deadkeys and backspace.kmx Binary files differindex 45e24ab..e7b1577 100644 --- a/tests/unit/kmx/020 - deadkeys and backspace.kmx +++ b/tests/unit/kmx/020 - deadkeys and backspace.kmx diff --git a/tests/unit/kmx/022 - options with preset.kmn b/tests/unit/kmx/022 - options with preset.kmn index 26599fc..878acc6 100644 --- a/tests/unit/kmx/022 - options with preset.kmn +++ b/tests/unit/kmx/022 - options with preset.kmn @@ -4,6 +4,7 @@ c expected: foo.foo.no foo. c context: c option: foo=1 c expected option: foo=0 +c saved option: foo=0 store(&version) '10.0' diff --git a/tests/unit/kmx/022 - options with preset.kmx b/tests/unit/kmx/022 - options with preset.kmx Binary files differindex 3d01729..06b56bf 100644 --- a/tests/unit/kmx/022 - options with preset.kmx +++ b/tests/unit/kmx/022 - options with preset.kmx diff --git a/tests/unit/kmx/023 - options with save.kmn b/tests/unit/kmx/023 - options with save.kmn index 7e2a430..aa6c138 100644 --- a/tests/unit/kmx/023 - options with save.kmn +++ b/tests/unit/kmx/023 - options with save.kmn @@ -3,6 +3,7 @@ c keys: [K_A][K_1][K_A][K_0][K_A][K_2] c expected: no foo.foo.no foo. c context: c expected option: foo=0 +c saved option: foo=0 store(&version) '10.0' diff --git a/tests/unit/kmx/023 - options with save.kmx b/tests/unit/kmx/023 - options with save.kmx Binary files differindex bd74c38..cadbe2d 100644 --- a/tests/unit/kmx/023 - options with save.kmx +++ b/tests/unit/kmx/023 - options with save.kmx diff --git a/tests/unit/kmx/024 - options with save and preset.kmn b/tests/unit/kmx/024 - options with save and preset.kmn index e3f5bf9..e8ecd57 100644 --- a/tests/unit/kmx/024 - options with save and preset.kmn +++ b/tests/unit/kmx/024 - options with save and preset.kmn @@ -4,6 +4,7 @@ c expected: foo.foo.no foo. c context: c option: foo=1 c expected option: foo=0 +c saved option: foo=0 store(&version) '10.0' @@ -17,4 +18,4 @@ if(foo = '1') + 'a' > 'foo.' if(foo = '0') + 'a' > 'no foo.' + '1' > set(foo = '1') + '0' > set(foo = '0') -+ '2' > save(foo)
\ No newline at end of file +if(foo = '0') + '2' > save(foo) diff --git a/tests/unit/kmx/024 - options with save and preset.kmx b/tests/unit/kmx/024 - options with save and preset.kmx Binary files differindex bd74c38..75356f7 100644 --- a/tests/unit/kmx/024 - options with save and preset.kmx +++ b/tests/unit/kmx/024 - options with save and preset.kmx diff --git a/tests/unit/kmx/025 - options with reset.kmn b/tests/unit/kmx/025 - options with reset.kmn index 6201362..2fea1b0 100644 --- a/tests/unit/kmx/025 - options with reset.kmn +++ b/tests/unit/kmx/025 - options with reset.kmn @@ -17,5 +17,4 @@ if(foo = '1') + 'a' > 'foo.' if(foo = '0') + 'a' > 'no foo.' + '1' > set(foo = '1') + '0' > set(foo = '0') -+ '2' > save(foo) + '3' > reset(foo) diff --git a/tests/unit/kmx/025 - options with reset.kmx b/tests/unit/kmx/025 - options with reset.kmx Binary files differindex 97bb6a9..4f2ba8c 100644 --- a/tests/unit/kmx/025 - options with reset.kmx +++ b/tests/unit/kmx/025 - options with reset.kmx diff --git a/tests/unit/kmx/028 - smp.kmn b/tests/unit/kmx/028 - smp.kmn index e17a8c4..0a2a47a 100644 --- a/tests/unit/kmx/028 - smp.kmn +++ b/tests/unit/kmx/028 - smp.kmn @@ -1,6 +1,6 @@ c Description: Tests SMP characters c keys: [K_1][K_2] -c expected: \u1F642hi\u1F600 +c expected: \U0001F642hi\U0001F600 c context: store(&version) '10.0' diff --git a/tests/unit/kmx/034 - options double set reset.kmn b/tests/unit/kmx/034 - options double set reset.kmn new file mode 100644 index 0000000..2e8e754 --- /dev/null +++ b/tests/unit/kmx/034 - options double set reset.kmn @@ -0,0 +1,19 @@ +c Description: Tests basic option rules with save reset+set+reset +c keys: [K_A][K_A] +c expected: foo.foo. +c context: +c option: foo=1 +c expected option: foo=1 + +store(&version) '10.0' + +store(foo) '0' + +begin Unicode > use(Main) + +group(Main) using keys + +if(foo = '1') + 'a' > 'foo.' set(foo='2') reset(foo) set(foo='3') reset(foo) +if(foo = '2') + 'a' > 'bar.' +if(foo = '3') + 'a' > 'baz.' +if(foo = '0') + 'a' > 'no foo.' diff --git a/tests/unit/kmx/034 - options double set reset.kmx b/tests/unit/kmx/034 - options double set reset.kmx Binary files differnew file mode 100644 index 0000000..7cacb4c --- /dev/null +++ b/tests/unit/kmx/034 - options double set reset.kmx diff --git a/tests/unit/kmx/035 - options double set staged.kmn b/tests/unit/kmx/035 - options double set staged.kmn new file mode 100644 index 0000000..e92e9ba --- /dev/null +++ b/tests/unit/kmx/035 - options double set staged.kmn @@ -0,0 +1,21 @@ +c Description: Tests basic option rules with save reset+set+reset +c keys: [K_A][K_B][K_C][K_B][K_A] +c expected: foo.foo. +c context: +c option: foo=1 +c expected option: foo=2 + +store(&version) '10.0' + +store(foo) '0' + +begin Unicode > use(Main) + +group(Main) using keys + +if(foo = '1') + 'a' > 'foo.' set(foo='2') ++ 'b' > reset(foo) ++ 'c' > set(foo='3') +if(foo = '2') + 'a' > 'bar.' +if(foo = '3') + 'a' > 'baz.' +if(foo = '0') + 'a' > 'no foo.' diff --git a/tests/unit/kmx/035 - options double set staged.kmx b/tests/unit/kmx/035 - options double set staged.kmx Binary files differnew file mode 100644 index 0000000..5862a99 --- /dev/null +++ b/tests/unit/kmx/035 - options double set staged.kmx diff --git a/tests/unit/kmx/036 - options - double reset staged.kmn b/tests/unit/kmx/036 - options - double reset staged.kmn new file mode 100644 index 0000000..5a9144f --- /dev/null +++ b/tests/unit/kmx/036 - options - double reset staged.kmn @@ -0,0 +1,19 @@ +c Description: Tests basic option rules with save reset+reset +c keys: [K_A][K_B][K_B][K_A] +c expected: foo.foo. +c context: +c option: foo=1 +c expected option: foo=2 + +store(&version) '10.0' + +store(foo) '0' + +begin Unicode > use(Main) + +group(Main) using keys + +if(foo = '1') + 'a' > 'foo.' set(foo='2') ++ 'b' > reset(foo) +if(foo = '2') + 'a' > 'bar.' +if(foo = '0') + 'a' > 'no foo.' diff --git a/tests/unit/kmx/036 - options - double reset staged.kmx b/tests/unit/kmx/036 - options - double reset staged.kmx Binary files differnew file mode 100644 index 0000000..f8def7e --- /dev/null +++ b/tests/unit/kmx/036 - options - double reset staged.kmx diff --git a/tests/unit/kmx/037 - options - double reset.kmn b/tests/unit/kmx/037 - options - double reset.kmn new file mode 100644 index 0000000..5d2eb1d --- /dev/null +++ b/tests/unit/kmx/037 - options - double reset.kmn @@ -0,0 +1,18 @@ +c Description: Tests basic option rules with save reset+reset +c keys: [K_A][K_A] +c expected: foo.foo. +c context: +c option: foo=1 +c expected option: foo=1 + +store(&version) '10.0' + +store(foo) '0' + +begin Unicode > use(Main) + +group(Main) using keys + +if(foo = '1') + 'a' > 'foo.' set(foo='2') reset(foo) reset(foo) +if(foo = '2') + 'a' > 'bar.' +if(foo = '0') + 'a' > 'no foo.' diff --git a/tests/unit/kmx/037 - options - double reset.kmx b/tests/unit/kmx/037 - options - double reset.kmx Binary files differnew file mode 100644 index 0000000..00692e5 --- /dev/null +++ b/tests/unit/kmx/037 - options - double reset.kmx diff --git a/tests/unit/kmx/038 - punctkeys.kmn b/tests/unit/kmx/038 - punctkeys.kmn new file mode 100644 index 0000000..fc89399 --- /dev/null +++ b/tests/unit/kmx/038 - punctkeys.kmn @@ -0,0 +1,23 @@ +c Description: Tests punctuation keys (Unicode) +c keys: [K_A][K_COLON][K_A][K_EQUAL][K_A][K_COMMA][K_A][K_HYPHEN][K_A][K_PERIOD][K_A][K_SLASH][K_A][K_BKQUOTE][K_A][K_LBRKT][K_A][K_BKSLASH][K_A][K_RBRKT][K_A][K_QUOTE][K_A][K_oE2] +c expected: efghijklmnop +c context: + +store(&version) '6.0' + +begin Unicode > use(Main) + +group(Main) using keys + +'a' + [K_COLON] > 'e' +'a' + [K_EQUAL] > 'f' +'a' + [K_COMMA] > 'g' +'a' + [K_HYPHEN] > 'h' +'a' + [K_PERIOD] > 'i' +'a' + [K_SLASH] > 'j' +'a' + [K_BKQUOTE] > 'k' +'a' + [K_LBRKT] > 'l' +'a' + [K_BKSLASH] > 'm' +'a' + [K_RBRKT] > 'n' +'a' + [K_QUOTE] > 'o' +'a' + [K_oE2] > 'p' diff --git a/tests/unit/kmx/038 - punctkeys.kmx b/tests/unit/kmx/038 - punctkeys.kmx Binary files differnew file mode 100644 index 0000000..a632d70 --- /dev/null +++ b/tests/unit/kmx/038 - punctkeys.kmx diff --git a/tests/unit/kmx/039 - generic ctrlalt.kmn b/tests/unit/kmx/039 - generic ctrlalt.kmn new file mode 100644 index 0000000..d540413 --- /dev/null +++ b/tests/unit/kmx/039 - generic ctrlalt.kmn @@ -0,0 +1,13 @@ +c Description: Tests generic alt and control (Unicode) +c keys: [K_A][K_A][LCTRL K_B][K_A][LALT K_C][K_A][RCTRL K_B][K_A][RALT K_C] +c expected: abcbc +c context: + +store(&version) '6.0' + +begin Unicode > use(Main) + +group(Main) using keys + +'a' + [CTRL K_B] > 'b' +'a' + [ALT K_C] > 'c' diff --git a/tests/unit/kmx/039 - generic ctrlalt.kmx b/tests/unit/kmx/039 - generic ctrlalt.kmx Binary files differnew file mode 100644 index 0000000..2eda691 --- /dev/null +++ b/tests/unit/kmx/039 - generic ctrlalt.kmx diff --git a/tests/unit/kmx/kmp.json b/tests/unit/kmx/kmp.json new file mode 100644 index 0000000..82cd8bf --- /dev/null +++ b/tests/unit/kmx/kmp.json @@ -0,0 +1,383 @@ +{ + "system": { + "keymanDeveloperVersion": "10.0.1099.0", + "fileVersion": "7.0" + }, + "options": { + "readmeFile": "kmx.cpp" + }, + "info": { + "name": { + "description": "Test Keyboards" + }, + "version": { + "description": "0.0" + }, + "copyright": { + "description": "\u00A9 2018 SIL International" + }, + "author": { + "description": "" + } + }, + "files": [ + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "keyboards": [ + { + "name": "000 - null keyboard", + "id": "000 - null keyboard", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "001 - basic input UnicodeI", + "id": "001 - basic input UnicodeI", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "002 - basic input Unicode", + "id": "002 - basic input Unicode", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "003 - nul", + "id": "003 - nul", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "004 - basic input (shift 2)", + "id": "004 - basic input (shift 2)", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "005 - nul with initial context", + "id": "005 - nul with initial context", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "006 - vkey input (shift ctrl)", + "id": "006 - vkey input (shift ctrl)", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "007 - vkey input (ctrl alt)", + "id": "007 - vkey input (ctrl alt)", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "008 - vkey input (ctrl alt 2)", + "id": "008 - vkey input (ctrl alt 2)", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "012 - ralt", + "id": "012 - ralt", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "013 - deadkeys", + "id": "013 - deadkeys", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "014 - groups and virtual keys", + "id": "014 - groups and virtual keys", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "015 - ralt 2", + "id": "015 - ralt 2", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "017 - space mnemonic kbd", + "id": "017 - space mnemonic kbd", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "018 - nul testing", + "id": "018 - nul testing", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "019 - multiple deadkeys", + "id": "019 - multiple deadkeys", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "020 - deadkeys and backspace", + "id": "020 - deadkeys and backspace", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "021 - options", + "id": "021 - options", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "022 - options with preset", + "id": "022 - options with preset", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "023 - options with save", + "id": "023 - options with save", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "024 - options with save and preset", + "id": "024 - options with save and preset", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "025 - options with reset", + "id": "025 - options with reset", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "026 - system stores", + "id": "026 - system stores", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "027 - system stores 2", + "id": "027 - system stores 2", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "028 - smp", + "id": "028 - smp", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "029 - beep", + "id": "029 - beep", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "030 - multiple groups", + "id": "030 - multiple groups", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "031 - caps lock", + "id": "031 - caps lock", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "032 - caps control", + "id": "032 - caps control", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "033 - caps always off", + "id": "033 - caps always off", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "038 - punctkeys", + "id": "038 - punctkeys", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + }, + { + "name": "039 - generic ctrlalt", + "id": "039 - generic ctrlalt", + "version": "0.0", + "languages": [ + { + "name": "Undetermined", + "id": "und" + } + ] + } + ] +} diff --git a/tests/unit/kmx/kmx.cpp b/tests/unit/kmx/kmx.cpp index aaf96ef..a876b46 100644 --- a/tests/unit/kmx/kmx.cpp +++ b/tests/unit/kmx/kmx.cpp @@ -13,9 +13,11 @@ #include <string> #include <type_traits> -#include <keyman/keyboardprocessor.h> +#include <kmx/kmx_processor.h> +#include "path.hpp" #include "state.hpp" +#include "utfcodec.hpp" #define try_status(expr) \ {auto __s = (expr); if (__s != KM_KBP_STATUS_OK) std::exit(100*__LINE__+__s);} @@ -25,11 +27,8 @@ #endif #define assert(expr) {if (!(expr)) std::exit(100*__LINE__); } -std::string utf16_to_utf8(std::u16string utf16_string); // defined in keyboard.cpp - namespace { - bool g_beep_found = false; struct key_event { @@ -39,17 +38,18 @@ struct key_event { typedef enum { KOT_INPUT, - KOT_OUTPUT + KOT_OUTPUT, + KOT_SAVED } kmx_option_type; struct kmx_option { kmx_option_type type; - std::u16string key, value; + std::u16string key, value, saved_value; }; using kmx_options = std::vector<kmx_option>; -int load_source(const std::string &, std::string &, std::u16string &, +int load_source(const km::kbp::path &, std::string &, std::u16string &, std::u16string &, kmx_options &, bool &); km_kbp_option_item test_env_opts[] = @@ -86,7 +86,7 @@ key_event char_to_event(char ch) { }; } -uint16_t const get_modifier(std::string const m) { +uint16_t get_modifier(std::string const m) { for (int i = 0; km::kbp::kmx::s_modifier_names[i].name; i++) { if (m == km::kbp::kmx::s_modifier_names[i].name) { return km::kbp::kmx::s_modifier_names[i].modifier; @@ -95,7 +95,7 @@ uint16_t const get_modifier(std::string const m) { return 0; } -km_kbp_virtual_key const get_vk(std::string const & vk) { +km_kbp_virtual_key get_vk(std::string const & vk) { for (int i = 1; i < 256; i++) { if (vk == km::kbp::kmx::s_key_names[i]) { return i; @@ -104,7 +104,7 @@ km_kbp_virtual_key const get_vk(std::string const & vk) { return 0; } -key_event const vkey_to_event(std::string const & vk_event) { +key_event vkey_to_event(std::string const & vk_event) { // vkey format is MODIFIER MODIFIER K_NAME //std::cout << "VK=" << vk_event << std::endl; @@ -135,7 +135,7 @@ key_event const vkey_to_event(std::string const & vk_event) { key_event next_key(std::string &keys) { // Parse the next element of the string, chop it off, and return it - if (keys.length() == 0) return { 0 }; + if (keys.length() == 0) return { 0, 0 }; char ch = keys[0]; if (ch == '[') { if (keys.length() > 1 && keys[1] == '[') { @@ -154,7 +154,7 @@ key_event next_key(std::string &keys) { } } -void apply_action(km_kbp_state const * state, km_kbp_action_item const & act, std::u16string & text_store) { +void apply_action(km_kbp_state const *, km_kbp_action_item const & act, std::u16string & text_store, kmx_options &options) { switch (act.type) { case KM_KBP_IT_END: @@ -189,7 +189,28 @@ void apply_action(km_kbp_state const * state, km_kbp_action_item const & act, st } break; case KM_KBP_IT_PERSIST_OPT: - assert(false); // TODO + { + bool found = false; + for (auto it = options.begin(); it != options.end(); it++) { + if (it->type == KOT_SAVED) { + if (it->key.compare(act.option->key) == 0) { + found = true; + it->saved_value = act.option->value; + break; + } + } + } + std::cout << "action: option " + << (act.option->scope == KM_KBP_OPT_ENVIRONMENT ? "environment " : "keyboard ") + << act.option->key + << "=" << act.option->value + << " persistence requested" << std::endl; + if (!found) { + std::cout << "option " + << act.option->key + << " saved but no expected output found. Suggestion: update test to include saved option value." << std::endl; + } + } break; case KM_KBP_IT_INVALIDATE_CONTEXT: std::cout << "action: context invalidated (markers cleared)" << std::endl; @@ -203,32 +224,9 @@ void apply_action(km_kbp_state const * state, km_kbp_action_item const & act, st } } -template<typename P> -std::basic_string< - typename std::remove_const< - typename std::remove_pointer<P>::type>::type -> -utf8_to(const std::string &); - -template<> -inline -std::basic_string<wchar_t> utf8_to<const wchar_t *>(const std::string & s) -{ - std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> convert; - return convert.from_bytes(s); -} - -template<> -inline -std::basic_string<char> utf8_to<const char *>(const std::string & s) -{ - return s; -} - -int run_test(const std::string & source, const std::string & _compiled) { +int run_test(const km::kbp::path & source, const km::kbp::path & compiled) { std::string keys = ""; std::u16string expected = u"", context = u""; - auto compiled = utf8_to<km_kbp_path_name>(_compiled); kmx_options options; bool expected_beep = false; @@ -236,7 +234,7 @@ int run_test(const std::string & source, const std::string & _compiled) { if (result != 0) return result; std::cout << "source file = " << source << std::endl - << "compiled file = " << _compiled << std::endl; + << "compiled file = " << compiled << std::endl; km_kbp_keyboard * test_kb = nullptr; km_kbp_state * test_state = nullptr; @@ -256,7 +254,7 @@ int run_test(const std::string & source, const std::string & _compiled) { for (auto it = options.begin(); it != options.end(); it++) { if (it->type != KOT_INPUT) continue; - std::cout << "input option-key: " << utf16_to_utf8(it->key) << std::endl; + std::cout << "input option-key: " << it->key << std::endl; std::u16string key = it->key; if (key[0] == u'&') { @@ -285,7 +283,7 @@ int run_test(const std::string & source, const std::string & _compiled) { keyboard_opts[i] = KM_KBP_OPTIONS_END; - try_status(km_kbp_options_update(test_state, keyboard_opts)); + try_status(km_kbp_state_options_update(test_state, keyboard_opts)); delete [] keyboard_opts; } @@ -304,7 +302,7 @@ int run_test(const std::string & source, const std::string & _compiled) { try_status(km_kbp_process_event(test_state, p.vk, p.modifier_state)); for (auto act = km_kbp_state_action_items(test_state, nullptr); act->type != KM_KBP_IT_END; act++) { - apply_action(test_state, *act, text_store); + apply_action(test_state, *act, text_store, options); } } @@ -319,9 +317,9 @@ int run_test(const std::string & source, const std::string & _compiled) { try_status(km_kbp_context_items_to_utf16(citems, buf, &n)); km_kbp_context_items_dispose(citems); - std::cout << "expected: " << utf16_to_utf8(expected) << std::endl; - std::cout << "text store: " << utf16_to_utf8(text_store) << std::endl; - std::cout << "result: " << utf16_to_utf8(buf) << std::endl; + std::cout << "expected: " << expected << std::endl; + std::cout << "text store: " << text_store << std::endl; + std::cout << "result: " << buf << std::endl; // Compare internal context with expected result if (buf != expected) return __LINE__; @@ -330,16 +328,18 @@ int run_test(const std::string & source, const std::string & _compiled) { if (text_store != expected) return __LINE__; // Test resultant options - // TODO: test also KM_KBP_IT_PERSIST_OPT and KM_KBP_IT_RESET_OPT actions - for (auto it = options.begin(); it != options.end(); it++) { - if (it->type != KOT_OUTPUT) continue; - std::cout << "output option-key: " << utf16_to_utf8(it->key) << " expected: " << utf16_to_utf8(it->value); - km_kbp_cp const *value; - try_status(km_kbp_options_lookup(test_state, KM_KBP_OPT_KEYBOARD, it->key.c_str(), &value)); - std::cout << " actual: " << utf16_to_utf8(value) << std::endl; - if (it->value.compare(value) != 0) return __LINE__; - km_kbp_cp_dispose(value); + if (it->type == KOT_OUTPUT) { + std::cout << "output option-key: " << it->key << " expected: " << it->value; + km_kbp_cp const *value; + try_status(km_kbp_state_option_lookup(test_state, KM_KBP_OPT_KEYBOARD, it->key.c_str(), &value)); + std::cout << " actual: " << value << std::endl; + if (it->value.compare(value) != 0) return __LINE__; + } + else if (it->type == KOT_SAVED) { + std::cout << "persisted option-key: " << it->key << " expected: " << it->value << " actual: " << it->saved_value << std::endl; + if (it->value.compare(it->saved_value) != 0) return __LINE__; + } } // Destroy them @@ -356,11 +356,11 @@ std::u16string parse_source_string(std::string const & s) { p++; km_kbp_usv v; assert(p != s.end()); - if (*p == 'u') { + if (*p == 'u' || *p == 'U') { // Unicode value p++; size_t n; - std::string s1 = s.substr(p - s.begin(), 6); + std::string s1 = s.substr(p - s.begin(), 8); v = std::stoul(s1, &n, 16); assert(v >= 0x20 && v <= 0x10FFFF); p += n-1; @@ -408,16 +408,18 @@ bool is_token(const std::string token, std::string &line) { return false; } -int load_source(const std::string & path, std::string & keys, std::u16string & expected, std::u16string & context, kmx_options &options, bool &expected_beep) { +int load_source(const km::kbp::path & path, std::string & keys, std::u16string & expected, std::u16string & context, kmx_options &options, bool &expected_beep) { const std::string s_keys = "c keys: ", s_expected = "c expected: ", s_context = "c context: ", s_option = "c option: ", - s_option_expected = "c expected option: "; + s_option_expected = "c expected option: ", + s_option_saved = "c saved option: "; // Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected result - std::ifstream kmn(path); + std::ifstream kmn(path.native()); if (!kmn.good()) { + std::cerr << "could not open file: " << path << std::endl; return __LINE__; } std::string line; @@ -446,6 +448,9 @@ int load_source(const std::string & path, std::string & keys, std::u16string & e else if (is_token(s_option_expected, line)) { if (!parse_option_string(line, options, KOT_OUTPUT)) return __LINE__; } + else if (is_token(s_option_saved, line)) { + if (!parse_option_string(line, options, KOT_SAVED)) return __LINE__; + } } if (keys == "") { diff --git a/tests/unit/kmx/meson.build b/tests/unit/kmx/meson.build index 47e4ba5..f4df7c8 100644 --- a/tests/unit/kmx/meson.build +++ b/tests/unit/kmx/meson.build @@ -46,13 +46,17 @@ tests = [ '029 - beep', '030 - multiple groups', - '031 - caps lock' + '031 - caps lock', # '032 - caps control', # TODO: support capsononly, capsalwaysoff, shiftfreescaps # '033 - caps alwyas off' # TODO: support capsononly, capsalwaysoff, shiftfreescaps + '034 - options double set reset', + '035 - options double set staged', + '036 - options - double reset staged', + '037 - options - double reset', + '038 - punctkeys', + '039 - generic ctrlalt' ] -# todo: if kmcomp is not found, use the .kmx in the source folder -# therefore make kmcomp compile to the source folder and add to repo? if build_machine.system() == 'windows' kmcomp_cmd = [kmcomp] else |