mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-08 18:05:32 +00:00
510 lines
19 KiB
C++
510 lines
19 KiB
C++
/*
|
|
* Keyman is copyright (C) SIL Global. MIT License.
|
|
*/
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten/emscripten.h>
|
|
#include <emscripten/bind.h>
|
|
#include <vector>
|
|
#include <sstream>
|
|
|
|
#ifdef __cplusplus
|
|
#define EXTERN extern "C" EMSCRIPTEN_KEEPALIVE
|
|
#else
|
|
#define EXTERN EMSCRIPTEN_KEEPALIVE
|
|
#endif
|
|
|
|
#include <keyman_core.h>
|
|
#include "processor.hpp"
|
|
#include "state.hpp"
|
|
|
|
namespace em = emscripten;
|
|
|
|
// This little bit of magic gives us implicit bindings for any `std::vector`,
|
|
// so long as we bind `emscripten::value_object<T>`, and comes from:
|
|
// https://github.com/emscripten-core/emscripten/issues/11070#issuecomment-717675128
|
|
namespace emscripten {
|
|
namespace internal {
|
|
|
|
// Enable for std::vector<T>
|
|
template <typename T>
|
|
struct TypeID<
|
|
T,
|
|
typename std::enable_if_t<std::is_same<
|
|
typename Canonicalized<T>::type,
|
|
std::vector<typename Canonicalized<T>::type::value_type, typename Canonicalized<T>::type::allocator_type>>::value>> {
|
|
static constexpr TYPEID get() {
|
|
return TypeID<val>::get();
|
|
}
|
|
};
|
|
|
|
template <typename T, typename Allocator> struct BindingType<std::vector<T, Allocator>> {
|
|
using ValBinding = BindingType<val>;
|
|
using WireType = ValBinding::WireType;
|
|
|
|
#if (__EMSCRIPTEN_major__ == 3 && __EMSCRIPTEN_minor__ == 1 && __EMSCRIPTEN_tiny__ >= 60) || (__EMSCRIPTEN_major__ > 3)
|
|
// emscripten-core/emscripten#21692
|
|
static WireType toWireType(const std::vector<T, Allocator> &vec, rvp::default_tag) {
|
|
return ValBinding::toWireType(val::array(vec), rvp::default_tag{});
|
|
}
|
|
#else
|
|
static WireType toWireType(const std::vector<T, Allocator> &vec) {
|
|
return ValBinding::toWireType(val::array(vec));
|
|
}
|
|
#endif
|
|
|
|
static std::vector<T, Allocator> fromWireType(WireType value) {
|
|
return vecFromJSArray<T>(ValBinding::fromWireType(value));
|
|
}
|
|
};
|
|
|
|
} // namespace internal
|
|
} // namespace emscripten
|
|
|
|
constexpr km_core_attr const engine_attrs = {
|
|
256,
|
|
KM_CORE_LIB_CURRENT,
|
|
KM_CORE_LIB_AGE,
|
|
KM_CORE_LIB_REVISION,
|
|
KM_CORE_TECH_KMX,
|
|
"SIL International"
|
|
};
|
|
|
|
km_core_attr const& tmp_wasm_attributes() {
|
|
return engine_attrs;
|
|
}
|
|
|
|
struct km_core_option_item_wasm {
|
|
std::u16string key;
|
|
std::u16string value;
|
|
uint8_t scope;
|
|
};
|
|
|
|
|
|
class km_core_keyboard_attrs_wasm {
|
|
public:
|
|
std::u16string version_string;
|
|
std::u16string id;
|
|
std::vector<km_core_option_item_wasm> default_options;
|
|
};
|
|
|
|
class km_core_actions_wasm {
|
|
private:
|
|
std::string getCodePoints(const std::u32string& str) const {
|
|
std::stringstream result;
|
|
for (char32_t codePoint : str) {
|
|
result << "U+" << std::hex << codePoint << " ";
|
|
}
|
|
return result.str();
|
|
}
|
|
|
|
public:
|
|
unsigned int code_points_to_delete;
|
|
std::u32string output;
|
|
std::vector<km_core_option_item_wasm> persist_options;
|
|
bool do_alert;
|
|
bool emit_keystroke;
|
|
int new_caps_lock_state;
|
|
std::u32string deleted_context;
|
|
|
|
#ifndef NDEBUG
|
|
std::string toString() const {
|
|
std::string result = "Actions(delete=" + std::to_string(this->code_points_to_delete) +
|
|
", output=\"" + std::string(this->output.begin(), this->output.end()) + "\" (" + getCodePoints(this->output) + ")" +
|
|
", do_alert=" + (this->do_alert ? "true" : "false") +
|
|
", emit_keystroke=" + (this->emit_keystroke ? "true" : "false") +
|
|
", new_caps_lock_state=" + std::to_string(this->new_caps_lock_state) +
|
|
", deleted_context=\"" + std::string(this->deleted_context.begin(), this->deleted_context.end()) + "\"" +
|
|
", persist_options=[";
|
|
for (size_t i = 0; i < this->persist_options.size(); ++i) {
|
|
result += "{" + std::string(this->persist_options[i].key.begin(), this->persist_options[i].key.end()) +
|
|
": " + std::string(this->persist_options[i].value.begin(), this->persist_options[i].value.end()) +
|
|
", scope=" + std::to_string(this->persist_options[i].scope) + "}";
|
|
if (i + 1 < this->persist_options.size()) {
|
|
result += ", ";
|
|
}
|
|
}
|
|
result += "])";
|
|
return result;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
class km_core_context_item_wasm: public km_core_context_item {
|
|
// Binding a union is not directly supported by emscripten, so we
|
|
// provide getters and setters.
|
|
// See https://github.com/emscripten-core/emscripten/issues/5381
|
|
|
|
public:
|
|
km_core_context_item_wasm() {}
|
|
km_core_context_item_wasm(km_core_context_item const& item) : km_core_context_item(item) {}
|
|
|
|
unsigned int getType() const {
|
|
return this->type;
|
|
}
|
|
|
|
unsigned int getCharacter() const {
|
|
return this->character;
|
|
}
|
|
void setCharacter(unsigned int character) {
|
|
this->character = character;
|
|
this->type = KM_CORE_CT_CHAR;
|
|
}
|
|
|
|
unsigned int getMarker() const {
|
|
return this->marker;
|
|
}
|
|
void setMarker(unsigned int marker) {
|
|
this->marker = marker;
|
|
this->type = KM_CORE_CT_MARKER;
|
|
}
|
|
|
|
std::string toString() const {
|
|
switch (this->type) {
|
|
case KM_CORE_CT_CHAR:
|
|
return "CHAR(" + std::to_string(this->character) + ")";
|
|
case KM_CORE_CT_MARKER:
|
|
return "MARKER(" + std::to_string(this->marker) + ")";
|
|
case KM_CORE_CT_END:
|
|
return "END";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
};
|
|
|
|
struct km_core_context_items {
|
|
private:
|
|
std::vector<km_core_context_item_wasm> items = std::vector<km_core_context_item_wasm>();
|
|
public:
|
|
void push_back(const km_core_context_item_wasm& item) {
|
|
items.push_back(km_core_context_item_wasm(item));
|
|
}
|
|
void resize(const size_t size) {
|
|
items.resize(size);
|
|
}
|
|
size_t size() const {
|
|
return items.size();
|
|
}
|
|
const km_core_context_item_wasm& get(size_t index) const {
|
|
if (index >= items.size()) {
|
|
throw std::out_of_range("index out of range");
|
|
}
|
|
return items[index];
|
|
}
|
|
void set(size_t index, const km_core_context_item_wasm& item) {
|
|
if (index >= items.size()) {
|
|
throw std::out_of_range("index out of range");
|
|
}
|
|
items[index] = item;
|
|
}
|
|
|
|
const km_core_context_item_wasm* data() const {
|
|
return items.data();
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::string result = "(" + std::to_string(items.size()) + ") [";
|
|
for (size_t i = 0; i < items.size(); ++i) {
|
|
result += items[i].toString();
|
|
if (i + 1 < items.size()) {
|
|
result += ", ";
|
|
}
|
|
}
|
|
result += "]";
|
|
return result;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
class CoreReturn {
|
|
public:
|
|
CoreReturn(int status = 0, const T* obj = nullptr) : status(status), object(obj) {
|
|
}
|
|
void setStatus(int status) {
|
|
this->status = status;
|
|
}
|
|
int getStatus() const {
|
|
return status;
|
|
}
|
|
void setObject(const T* obj) {
|
|
object = obj;
|
|
}
|
|
const T* getObject() const {
|
|
return object;
|
|
}
|
|
|
|
private:
|
|
int status;
|
|
const T* object;
|
|
};
|
|
|
|
km_core_context_item_wasm* km_core_context_item_create_end() {
|
|
return new km_core_context_item_wasm({KM_CORE_CT_END, {0,}, {0,}});
|
|
}
|
|
|
|
km_core_status
|
|
keyboard_load_from_blob_internal(const km_core_path_name kb_name, const std::vector<uint8_t>& buf, km_core_keyboard** keyboard);
|
|
|
|
const CoreReturn<km_core_keyboard>*
|
|
km_core_keyboard_load_from_blob_wasm(
|
|
std::string kb_name,
|
|
const emscripten::val& blob_val
|
|
) {
|
|
std::vector<uint8_t> blob = emscripten::convertJSArrayToNumberVector<uint8_t>(blob_val);
|
|
km_core_keyboard* keyboard_ptr = nullptr;
|
|
|
|
km_core_status retVal = ::keyboard_load_from_blob_internal(kb_name.c_str(), blob, &keyboard_ptr);
|
|
return new CoreReturn<km_core_keyboard>(retVal, keyboard_ptr);
|
|
}
|
|
|
|
const CoreReturn<km_core_keyboard_attrs_wasm>*
|
|
km_core_keyboard_get_attrs_wasm(const km_core_keyboard* keyboard) {
|
|
const km_core_keyboard_attrs* attrs = nullptr;
|
|
km_core_status retVal = km_core_keyboard_get_attrs(keyboard, &attrs);
|
|
km_core_keyboard_attrs_wasm* attrs_wasm = nullptr;
|
|
if (attrs) {
|
|
attrs_wasm = new km_core_keyboard_attrs_wasm();
|
|
attrs_wasm->version_string = std::u16string(attrs->version_string);
|
|
attrs_wasm->id = std::u16string(attrs->id);
|
|
for (const km_core_option_item* item = attrs->default_options; item->key; ++item) {
|
|
km_core_option_item_wasm item_wasm;
|
|
item_wasm.key = std::u16string(item->key);
|
|
item_wasm.value = std::u16string(item->value);
|
|
item_wasm.scope = item->scope;
|
|
attrs_wasm->default_options.push_back(item_wasm);
|
|
}
|
|
}
|
|
return new CoreReturn<km_core_keyboard_attrs_wasm>(retVal, attrs_wasm);
|
|
}
|
|
|
|
const CoreReturn<km_core_state>*
|
|
km_core_state_create_wasm(const km_core_keyboard* keyboard,
|
|
const std::vector<km_core_option_item_wasm>& 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<km_core_state>(retVal, state);
|
|
}
|
|
|
|
const CoreReturn<km_core_state>*
|
|
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<km_core_state>(retVal, new_state);
|
|
}
|
|
|
|
int
|
|
km_core_state_context_set_if_needed_wasm(
|
|
km_core_state const* state,
|
|
std::u16string application_context
|
|
) {
|
|
return km_core_state_context_set_if_needed(const_cast<km_core_state*>(state), application_context.c_str());
|
|
}
|
|
|
|
std::u16string
|
|
km_core_state_context_debug_wasm(const km_core_state* state, int context_type) {
|
|
std::u16string context;
|
|
km_core_cu* context_c = km_core_state_context_debug(state, static_cast<km_core_debug_context_type>(context_type));
|
|
if (context_c) {
|
|
context = std::u16string(context_c);
|
|
km_core_cu_dispose(context_c);
|
|
}
|
|
return context;
|
|
}
|
|
|
|
km_core_actions_wasm*
|
|
km_core_state_get_actions_wasm(km_core_state const *state) {
|
|
km_core_actions const* actions = km_core_state_get_actions(state);
|
|
km_core_actions_wasm* actions_wasm = new km_core_actions_wasm();
|
|
actions_wasm->code_points_to_delete = actions->code_points_to_delete;
|
|
actions_wasm->output = std::u32string(actions->output);
|
|
actions_wasm->do_alert = actions->do_alert;
|
|
actions_wasm->emit_keystroke = actions->emit_keystroke;
|
|
actions_wasm->new_caps_lock_state = actions->new_caps_lock_state;
|
|
actions_wasm->deleted_context = std::u32string(actions->deleted_context);
|
|
for (const km_core_option_item* item = actions->persist_options; item->key; ++item) {
|
|
km_core_option_item_wasm item_wasm;
|
|
item_wasm.key = std::u16string(item->key);
|
|
item_wasm.value = std::u16string(item->value);
|
|
item_wasm.scope = item->scope;
|
|
actions_wasm->persist_options.push_back(item_wasm);
|
|
}
|
|
return actions_wasm;
|
|
}
|
|
|
|
int
|
|
km_core_state_options_update_wasm(
|
|
km_core_state const *state,
|
|
const std::vector<km_core_option_item_wasm>& new_options
|
|
) {
|
|
km_core_option_item* options_c = new km_core_option_item[new_options.size() + 1];
|
|
for (size_t i = 0; i < new_options.size(); ++i) {
|
|
options_c[i].key = new_options[i].key.c_str();
|
|
options_c[i].value = new_options[i].value.c_str();
|
|
options_c[i].scope = new_options[i].scope;
|
|
}
|
|
options_c[new_options.size()] = KM_CORE_OPTIONS_END;
|
|
km_core_status status = km_core_state_options_update(const_cast<km_core_state*>(state), options_c);
|
|
delete[] options_c;
|
|
return status;
|
|
}
|
|
|
|
const CoreReturn<km_core_context_items>*
|
|
km_core_context_get_wasm(
|
|
km_core_context const* context
|
|
) {
|
|
km_core_context_item* items = nullptr;
|
|
km_core_status status = km_core_context_get(context, &items);
|
|
km_core_context_items* items_wasm = new km_core_context_items();
|
|
if (status == KM_CORE_STATUS_OK) {
|
|
for (const km_core_context_item* item = items; item && item->type != KM_CORE_CT_END; item++) {
|
|
items_wasm->push_back(km_core_context_item_wasm(*item));
|
|
}
|
|
items_wasm->push_back(km_core_context_item_wasm({KM_CORE_CT_END, {0,}, {0,}}));
|
|
km_core_context_items_dispose(items);
|
|
}
|
|
return new CoreReturn<km_core_context_items>(status, items_wasm);
|
|
}
|
|
|
|
km_core_status
|
|
km_core_context_set_wasm(
|
|
km_core_context const* context,
|
|
km_core_context_items const *context_items
|
|
) {
|
|
if (!context_items) {
|
|
return KM_CORE_STATUS_INVALID_ARGUMENT;
|
|
}
|
|
auto items = std::vector<km_core_context_item>(context_items->size());
|
|
for (size_t i = 0; i < context_items->size(); ++i) {
|
|
items[i] = context_items->get(i);
|
|
}
|
|
return km_core_context_set((km_core_context*)context, items.data());
|
|
}
|
|
|
|
km_core_status
|
|
km_core_process_event_wasm(
|
|
km_core_state const *state_,
|
|
km_core_virtual_key vk,
|
|
uint16_t modifier_state,
|
|
bool is_key_down,
|
|
uint16_t event_flags
|
|
) {
|
|
return km_core_process_event(state_, vk, modifier_state, is_key_down ? 1 : 0, event_flags);
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(core_interface) {
|
|
|
|
em::value_object<km_core_attr>("km_core_attr")
|
|
.field("max_context", &km_core_attr::max_context)
|
|
.field("current", &km_core_attr::current)
|
|
.field("revision", &km_core_attr::revision)
|
|
.field("age", &km_core_attr::age)
|
|
.field("technology", &km_core_attr::technology)
|
|
//.field("vendor", &km_core_attr::vendor, em::allow_raw_pointers())
|
|
;
|
|
em::value_object<km_core_option_item_wasm>("km_core_option_item")
|
|
.field("key", &km_core_option_item_wasm::key)
|
|
.field("value", &km_core_option_item_wasm::value)
|
|
.field("scope", &km_core_option_item_wasm::scope);
|
|
|
|
em::function("tmp_wasm_attributes", &tmp_wasm_attributes);
|
|
|
|
|
|
// Unfortunately embind has an open issue with enums and typescript where it
|
|
// only generates a type for the enum, but not the values in a usable way.
|
|
// Therefore it's not much use to expose the enum here.
|
|
// See https://github.com/emscripten-core/emscripten/issues/18585
|
|
|
|
// em::enum_<km_core_status_codes>("km_core_status_codes")
|
|
// .value("OK", KM_CORE_STATUS_OK)
|
|
// .value("NO_MEM", KM_CORE_STATUS_NO_MEM)
|
|
// .value("IO_ERROR", KM_CORE_STATUS_IO_ERROR)
|
|
// .value("INVALID_ARGUMENT", KM_CORE_STATUS_INVALID_ARGUMENT)
|
|
// .value("KEY_ERROR", KM_CORE_STATUS_KEY_ERROR)
|
|
// .value("INSUFFICENT_BUFFER", KM_CORE_STATUS_INSUFFICENT_BUFFER)
|
|
// .value("INVALID_UTF", KM_CORE_STATUS_INVALID_UTF)
|
|
// .value("INVALID_KEYBOARD", KM_CORE_STATUS_INVALID_KEYBOARD)
|
|
// .value("NOT_IMPLEMENTED", KM_CORE_STATUS_NOT_IMPLEMENTED)
|
|
// .value("OS_ERROR", KM_CORE_STATUS_OS_ERROR);
|
|
|
|
em::class_<km_core_keyboard>("km_core_keyboard");
|
|
em::class_<CoreReturn<km_core_keyboard>>("CoreKeyboardReturn")
|
|
.property("status", &CoreReturn<km_core_keyboard>::getStatus)
|
|
.property("object", &CoreReturn<km_core_keyboard>::getObject, em::allow_raw_pointers());
|
|
em::class_<CoreReturn<km_core_keyboard_attrs_wasm>>("CoreKeyboardAttrsReturn")
|
|
.property("status", &CoreReturn<km_core_keyboard_attrs_wasm>::getStatus)
|
|
.property("object", &CoreReturn<km_core_keyboard_attrs_wasm>::getObject, em::allow_raw_pointers());
|
|
em::class_<CoreReturn<km_core_context_items>>("CoreContextReturn")
|
|
.property("status", &CoreReturn<km_core_context_items>::getStatus)
|
|
.property("object", &CoreReturn<km_core_context_items>::getObject, em::allow_raw_pointers());
|
|
em::class_<km_core_keyboard_attrs_wasm>("km_core_keyboard_attrs")
|
|
.property("version_string", &km_core_keyboard_attrs_wasm::version_string)
|
|
.property("id", &km_core_keyboard_attrs_wasm::id)
|
|
.property("default_options", &km_core_keyboard_attrs_wasm::default_options);
|
|
em::class_<km_core_actions_wasm>("km_core_actions")
|
|
#ifndef NDEBUG
|
|
.function("toString", &km_core_actions_wasm::toString)
|
|
#endif
|
|
.property("code_points_to_delete", &km_core_actions_wasm::code_points_to_delete)
|
|
.property("output", &km_core_actions_wasm::output)
|
|
.property("persist_options", &km_core_actions_wasm::persist_options)
|
|
.property("do_alert", &km_core_actions_wasm::do_alert)
|
|
.property("emit_keystroke", &km_core_actions_wasm::emit_keystroke)
|
|
.property("new_caps_lock_state", &km_core_actions_wasm::new_caps_lock_state)
|
|
.property("deleted_context", &km_core_actions_wasm::deleted_context);
|
|
|
|
em::class_<km_core_state>("km_core_state");
|
|
em::class_<CoreReturn<km_core_state>>("CoreStateReturn")
|
|
.property("status", &CoreReturn<km_core_state>::getStatus)
|
|
.property("object", &CoreReturn<km_core_state>::getObject, em::allow_raw_pointers());
|
|
|
|
em::class_<km_core_context>("km_core_context");
|
|
// Since we use it in CoreReturn it has to be bound as class.
|
|
em::class_<km_core_context_items>("km_core_context_items")
|
|
.constructor()
|
|
.function("push_back(item)", &km_core_context_items::push_back)
|
|
.function("resize(size)", &km_core_context_items::resize)
|
|
.function("size", &km_core_context_items::size)
|
|
.function("get(index)", &km_core_context_items::get)
|
|
.function("set(index, item)", &km_core_context_items::set)
|
|
.function("data", &km_core_context_items::data, em::allow_raw_pointers())
|
|
.function("toString", &km_core_context_items::toString);
|
|
|
|
em::class_<km_core_context_item_wasm>("km_core_context_item")
|
|
.constructor<>()
|
|
.property("type", &km_core_context_item_wasm::getType)
|
|
.property("character", &km_core_context_item_wasm::getCharacter, &km_core_context_item_wasm::setCharacter)
|
|
.property("marker", &km_core_context_item_wasm::getMarker, &km_core_context_item_wasm::setMarker)
|
|
.function("toString", &km_core_context_item_wasm::toString);
|
|
|
|
em::function("create_end_context", &km_core_context_item_create_end, em::allow_raw_pointers());
|
|
em::function("keyboard_load_from_blob(kb_name, blob)", &km_core_keyboard_load_from_blob_wasm, em::allow_raw_pointers());
|
|
em::function("keyboard_dispose(keyboard)", &km_core_keyboard_dispose, em::allow_raw_pointers());
|
|
em::function("keyboard_get_attrs(keyboard)", &km_core_keyboard_get_attrs_wasm, em::allow_raw_pointers());
|
|
em::function("state_create(keyboard, env)", &km_core_state_create_wasm, em::allow_raw_pointers());
|
|
em::function("state_clone(state)", &km_core_state_clone_wasm, em::allow_raw_pointers());
|
|
em::function("state_dispose(state)", &km_core_state_dispose, em::allow_raw_pointers());
|
|
em::function("process_event(state, vk, modifier_state, is_key_down, event_flags)", &km_core_process_event_wasm, em::allow_raw_pointers());
|
|
|
|
em::function("state_context(state)", &km_core_state_context, em::allow_raw_pointers());
|
|
em::function("state_context_set_if_needed(state, application_context)", &km_core_state_context_set_if_needed_wasm, em::allow_raw_pointers());
|
|
em::function("state_context_clear(state)", &km_core_state_context_clear, em::allow_raw_pointers());
|
|
em::function("state_context_debug(state, context_type)", &km_core_state_context_debug_wasm, em::allow_raw_pointers());
|
|
|
|
em::function("state_get_actions(state)", &km_core_state_get_actions_wasm, em::allow_raw_pointers());
|
|
|
|
em::function("state_options_update(state, new_options)", &km_core_state_options_update_wasm, em::allow_raw_pointers());
|
|
|
|
em::function("context_get(context)", &km_core_context_get_wasm, em::allow_raw_pointers());
|
|
em::function("context_set(context, context_items)", &km_core_context_set_wasm, em::allow_raw_pointers());
|
|
}
|
|
|
|
#endif
|