From c297c66a64405303e5600a8d3c3dc022ca4d6d86 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Sat, 1 Mar 2025 18:33:17 +0100 Subject: [PATCH] feat(core): expose state and event API to WASM --- core/include/keyman/keyman_core_api.h | 10 ++-- core/src/km_core_keyboard_api.cpp | 2 +- core/src/km_core_processevent_api.cpp | 11 +++-- core/src/km_core_state_api.cpp | 6 +-- core/src/wasm.cpp | 37 ++++++++++++++- core/tests/unit/wasm/wasm.tests.js | 67 ++++++++++++++++++++++++++- 6 files changed, 116 insertions(+), 17 deletions(-) diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index c0e8de9627..b0d8eeaf73 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -1166,7 +1166,7 @@ returned by [km_core_keyboard_load_from_blob]. ```c */ KMN_API void -km_core_keyboard_dispose(km_core_keyboard *keyboard); +km_core_keyboard_dispose(km_core_keyboard const* keyboard); /* ``` @@ -1384,7 +1384,7 @@ the environment passed. ```c */ KMN_API km_core_status -km_core_state_create(km_core_keyboard *keyboard, +km_core_state_create(km_core_keyboard const *keyboard, km_core_option_item const *env, km_core_state **out); @@ -1470,7 +1470,7 @@ invalid. ```c */ KMN_API void -km_core_state_dispose(km_core_state *state); +km_core_state_dispose(km_core_state const *state); /* ``` @@ -1677,7 +1677,7 @@ the state may also be modified. ```c */ KMN_API km_core_status -km_core_process_event(km_core_state *state, +km_core_process_event(km_core_state const *state, km_core_virtual_key vk, uint16_t modifier_state, uint8_t is_key_down, @@ -1733,7 +1733,7 @@ the state may also be modified. KMN_API km_core_status km_core_event( - km_core_state *state, + km_core_state const *state, uint32_t event, void* data ); diff --git a/core/src/km_core_keyboard_api.cpp b/core/src/km_core_keyboard_api.cpp index cef9f99162..ab24e7b292 100644 --- a/core/src/km_core_keyboard_api.cpp +++ b/core/src/km_core_keyboard_api.cpp @@ -81,7 +81,7 @@ km_core_keyboard_load_from_blob( } void -km_core_keyboard_dispose(km_core_keyboard *keyboard) +km_core_keyboard_dispose(km_core_keyboard const* keyboard) { delete keyboard; } diff --git a/core/src/km_core_processevent_api.cpp b/core/src/km_core_processevent_api.cpp index a2bca95ea8..736e5ee6e0 100644 --- a/core/src/km_core_processevent_api.cpp +++ b/core/src/km_core_processevent_api.cpp @@ -15,7 +15,7 @@ km_core_status km_core_event( - km_core_state *state, + km_core_state const *state, uint32_t event, void* data ) { @@ -37,19 +37,20 @@ km_core_event( return KM_CORE_STATUS_INVALID_ARGUMENT; } - return state->processor().external_event(state, event, data); + return const_cast(state)->processor().external_event(const_cast(state), event, data); } km_core_status -km_core_process_event(km_core_state *state, +km_core_process_event(km_core_state const *state_, km_core_virtual_key vk, uint16_t modifier_state, uint8_t is_key_down, uint16_t event_flags) { - assert(state != nullptr); - if(state == nullptr) { + assert(state_ != nullptr); + if(state_ == nullptr) { return KM_CORE_STATUS_INVALID_ARGUMENT; } + km_core_state *state = const_cast(state_); km_core_status status = state->processor().process_event(state, vk, modifier_state, is_key_down, event_flags); if (state_should_invalidate_context(state, vk, modifier_state, is_key_down, event_flags)) { diff --git a/core/src/km_core_state_api.cpp b/core/src/km_core_state_api.cpp index 6c74cd159b..134067ca8d 100644 --- a/core/src/km_core_state_api.cpp +++ b/core/src/km_core_state_api.cpp @@ -28,7 +28,7 @@ using namespace km::core; // Forward declarations class context; -km_core_status km_core_state_create(km_core_keyboard * keyboard, +km_core_status km_core_state_create(km_core_keyboard const *keyboard, km_core_option_item const *env, km_core_state ** out) { @@ -38,7 +38,7 @@ km_core_status km_core_state_create(km_core_keyboard * keyboard, try { - *out = new km_core_state(static_cast(*keyboard), env); + *out = new km_core_state(static_cast(*const_cast(keyboard)), env); } catch (std::bad_alloc &) { @@ -60,7 +60,7 @@ km_core_status km_core_state_clone(km_core_state const *state, } -void km_core_state_dispose(km_core_state *state) +void km_core_state_dispose(km_core_state const *state) { delete state; } diff --git a/core/src/wasm.cpp b/core/src/wasm.cpp index 6b30dbfc52..8b50b79af7 100644 --- a/core/src/wasm.cpp +++ b/core/src/wasm.cpp @@ -9,8 +9,9 @@ #define EXTERN EMSCRIPTEN_KEEPALIVE #endif -#include "processor.hpp" #include +#include "processor.hpp" +#include "state.hpp" namespace em = emscripten; @@ -137,6 +138,30 @@ km_core_keyboard_get_attrs_wasm(const km_core_keyboard* keyboard) { return new CoreReturn(retVal, attrs_wasm); } +EMSCRIPTEN_KEEPALIVE const CoreReturn* +km_core_state_create_wasm(const km_core_keyboard* keyboard, + const std::vector& env) { + km_core_option_item* env_c = new km_core_option_item[env.size() + 1]; + for (size_t i = 0; i < env.size(); ++i) { + env_c[i].key = env[i].key.c_str(); + env_c[i].value = env[i].value.c_str(); + env_c[i].scope = env[i].scope; + } + env_c[env.size()] = {nullptr, nullptr, 0}; + + km_core_state* state = nullptr; + km_core_status retVal = km_core_state_create(keyboard, env_c, &state); + delete[] env_c; + return new CoreReturn(retVal, state); +} + +EMSCRIPTEN_KEEPALIVE const CoreReturn* +km_core_state_clone_wasm(const km_core_state* state) { + km_core_state* new_state = nullptr; + km_core_status retVal = km_core_state_clone(state, &new_state); + return new CoreReturn(retVal, new_state); +} + EMSCRIPTEN_BINDINGS(core_interface) { em::value_object("km_core_attr") @@ -184,7 +209,17 @@ EMSCRIPTEN_BINDINGS(core_interface) { .property("id", &km_core_keyboard_attrs_wasm::id) .property("default_options", &km_core_keyboard_attrs_wasm::default_options); + em::class_("km_core_state"); + em::class_>("CoreStateReturn") + .property("status", &CoreReturn::getStatus) + .property("object", &CoreReturn::getObject, em::allow_raw_pointers()); + em::function("keyboard_load_from_blob", &km_core_keyboard_load_from_blob_wasm, em::allow_raw_pointers()); + em::function("keyboard_dispose", &km_core_keyboard_dispose, em::allow_raw_pointers()); em::function("keyboard_get_attrs", &km_core_keyboard_get_attrs_wasm, em::allow_raw_pointers()); + em::function("state_create", &km_core_state_create_wasm, em::allow_raw_pointers()); + em::function("state_clone", &km_core_state_clone_wasm, em::allow_raw_pointers()); + em::function("state_dispose", &km_core_state_dispose, em::allow_raw_pointers()); + em::function("process_event", &km_core_process_event, em::allow_raw_pointers()); } #endif diff --git a/core/tests/unit/wasm/wasm.tests.js b/core/tests/unit/wasm/wasm.tests.js index a69dfc3404..2b5f86a3be 100644 --- a/core/tests/unit/wasm/wasm.tests.js +++ b/core/tests/unit/wasm/wasm.tests.js @@ -40,10 +40,25 @@ describe('Unit tests for wasm Core API', function () { assert.isOk(result.object); }); + function loadKeyboard(name) { + const kmxdata = fs.readFileSync(path.join(__dirname, `tests/unit/kmx/${name}.kmx`)); + const result = km_core.keyboard_load_from_blob(name, kmxdata); + assert.equal(result.status, 0); + assert.isOk(result.object); + return result.object; + } + + it('can dispose keyboard', function () { + // Setup + const keyboard = loadKeyboard('k_020___deadkeys_and_backspace'); + + // Execute + km_core.keyboard_dispose(keyboard); + }); + it('can get keyboard attributes', function () { // Setup - const kmxdata = fs.readFileSync(path.join(__dirname, 'tests/unit/kmx/k_022___options_with_preset.kmx')); - const keyboard = km_core.keyboard_load_from_blob('k_022___options_with_preset', kmxdata).object; + const keyboard = loadKeyboard('k_022___options_with_preset'); // Execute const result = km_core.keyboard_get_attrs(keyboard); @@ -55,4 +70,52 @@ describe('Unit tests for wasm Core API', function () { assert.equal(result.object.version_string, '0.0'); assert.deepEqual(result.object.default_options, [{ key: 'foo', value: '0', scope: 1 }]); }); + + it('can create the state object', function () { + // Setup + const keyboard = loadKeyboard('k_020___deadkeys_and_backspace'); + + // Execute + const result = km_core.state_create(keyboard, []); + + // Verify + assert.equal(result.status, 0); + assert.isOk(result.object); + }); + + it('can dispose the state object', function () { + // Setup + const keyboard = loadKeyboard('k_020___deadkeys_and_backspace'); + const state = km_core.state_create(keyboard, []); + + // Execute + km_core.state_dispose(state.object); + }); + + it('can clone the state object', function () { + // Setup + const keyboard = loadKeyboard('k_020___deadkeys_and_backspace'); + const state = km_core.state_create(keyboard, []); + + // Execute + const result = km_core.state_clone(state.object); + + // Verify + assert.equal(result.status, 0); + assert.isOk(result.object); + // TODO-web-core: add deeper tests + }); + + it('can process event', function () { + // Setup + const keyboard = loadKeyboard('k_020___deadkeys_and_backspace'); + const state = km_core.state_create(keyboard, []); + + // Execute + const status = km_core.process_event(state.object, 0x20, 0, 1, 0); + + // Verify + assert.equal(status, 0); + // TODO-web-core: add more asserts + }); });