/* * Keyman is copyright (C) SIL Global. MIT License. */ #ifdef __EMSCRIPTEN__ #include #include #include #include #ifdef __cplusplus #define EXTERN extern "C" EMSCRIPTEN_KEEPALIVE #else #define EXTERN EMSCRIPTEN_KEEPALIVE #endif #include #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`, and comes from: // https://github.com/emscripten-core/emscripten/issues/11070#issuecomment-717675128 namespace emscripten { namespace internal { // Enable for std::vector template struct TypeID< T, typename std::enable_if_t::type, std::vector::type::value_type, typename Canonicalized::type::allocator_type>>::value>> { static constexpr TYPEID get() { return TypeID::get(); } }; template struct BindingType> { using ValBinding = BindingType; 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 &vec, rvp::default_tag) { return ValBinding::toWireType(val::array(vec), rvp::default_tag{}); } #else static WireType toWireType(const std::vector &vec) { return ValBinding::toWireType(val::array(vec)); } #endif static std::vector fromWireType(WireType value) { return vecFromJSArray(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 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 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 items = std::vector(); 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 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& buf, km_core_keyboard** keyboard); const CoreReturn* km_core_keyboard_load_from_blob_wasm( std::string kb_name, const emscripten::val& blob_val ) { std::vector blob = emscripten::convertJSArrayToNumberVector(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(retVal, keyboard_ptr); } const CoreReturn* 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(retVal, attrs_wasm); } 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); } 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); } 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(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(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& 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(state), options_c); delete[] options_c; return status; } const CoreReturn* 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(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(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") .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") .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") // .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"); em::class_>("CoreKeyboardReturn") .property("status", &CoreReturn::getStatus) .property("object", &CoreReturn::getObject, em::allow_raw_pointers()); em::class_>("CoreKeyboardAttrsReturn") .property("status", &CoreReturn::getStatus) .property("object", &CoreReturn::getObject, em::allow_raw_pointers()); em::class_>("CoreContextReturn") .property("status", &CoreReturn::getStatus) .property("object", &CoreReturn::getObject, em::allow_raw_pointers()); em::class_("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") #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"); em::class_>("CoreStateReturn") .property("status", &CoreReturn::getStatus) .property("object", &CoreReturn::getObject, em::allow_raw_pointers()); em::class_("km_core_context"); // Since we use it in CoreReturn it has to be bound as class. em::class_("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") .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