From fc82f28b2d9bd6f734714fa0c1f752c6981d668c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 1 Jun 2026 14:44:45 +0200 Subject: [PATCH] chore(web): web-core preflight - strip core references In order to maintain roughly the current build artifact size, strip out the unfinished core integration from what we merge with preflight. Test-bot: skip Build-bot: release:android,ios,developer,web --- common/web/build.sh | 1 + web/package.json | 10 - web/src/app/browser/build.sh | 4 - web/src/app/browser/src/keymanEngine.ts | 6 +- web/src/app/ui/build.sh | 4 - web/src/app/webview/build.sh | 4 - web/src/engine/build.sh | 10 - web/src/engine/src/core-adapter/.gitignore | 1 - web/src/engine/src/core-adapter/KM_Core.ts | 76 --- .../core-adapter/import/core/keymancore.d.ts | 157 +++++ web/src/engine/src/core-adapter/index.ts | 3 - .../core-processor/coreKeyboardInterface.ts | 47 -- .../core-processor/coreKeyboardProcessor.ts | 496 -------------- web/src/engine/src/core-processor/index.ts | 1 - .../keyboard-storage/stubAndKeyboardCache.ts | 13 +- web/src/engine/src/keyboard/index.ts | 1 - .../keyboard/keyboards/keyboardLoaderBase.ts | 15 +- .../src/keyboard/keyboards/kmxKeyboard.ts | 179 ----- .../src/main/headless/inputProcessor.ts | 13 +- web/src/engine/tsconfig.json | 1 + .../engine/core-adapter/core-adapter.tests.ts | 46 -- .../coreKeyboardProcessor.tests.ts | 610 ------------------ .../engine/keyboard/kmxkeyboard.tests.ts | 23 - .../headless/engine/loadKeyboardHelper.ts | 3 - 24 files changed, 169 insertions(+), 1555 deletions(-) delete mode 100644 web/src/engine/src/core-adapter/.gitignore delete mode 100644 web/src/engine/src/core-adapter/KM_Core.ts create mode 100644 web/src/engine/src/core-adapter/import/core/keymancore.d.ts delete mode 100644 web/src/engine/src/core-adapter/index.ts delete mode 100644 web/src/engine/src/core-processor/coreKeyboardInterface.ts delete mode 100644 web/src/engine/src/core-processor/coreKeyboardProcessor.ts delete mode 100644 web/src/engine/src/core-processor/index.ts delete mode 100644 web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts delete mode 100644 web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts delete mode 100644 web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts delete mode 100644 web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts diff --git a/common/web/build.sh b/common/web/build.sh index 182de85d62..052061946f 100755 --- a/common/web/build.sh +++ b/common/web/build.sh @@ -11,6 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Keyman common web modules" \ :keyman-version \ :langtags \ + :sentry-manager \ :types \ clean \ configure \ diff --git a/web/package.json b/web/package.json index 712a8558a2..e7bd81e1fa 100644 --- a/web/package.json +++ b/web/package.json @@ -17,16 +17,6 @@ "types": "./build/engine/obj/attachment/index.d.ts", "import": "./build/engine/obj/attachment/index.js" }, - "./engine/core-adapter": { - "es6-bundling": "./src/engine/src/core-adapter/index.ts", - "types": "./build/engine/obj/core-adapter/index.d.ts", - "import": "./build/engine/obj/core-adapter/index.js" - }, - "./engine/core-processor": { - "es6-bundling": "./src/engine/src/core-processor/index.ts", - "types": "./build/engine/obj/core-processor/index.d.ts", - "import": "./build/engine/obj/core-processor/index.js" - }, "./engine/dom-utils": { "es6-bundling": "./src/engine/src/dom-utils/index.ts", "types": "./build/engine/obj/dom-utils/index.d.ts", diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index d23b67987a..760d0fea7e 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -72,10 +72,6 @@ compile_and_copy() { mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" - # Copy Keyman Core build artifacts for local reference - cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/debug/" - cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/browser/release/" - # Update the build/publish copy of our build artifacts prepare diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index b9663ca374..9120c7c07f 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -8,7 +8,7 @@ import { } from 'keyman/engine/osk'; import { ErrorStub, KeyboardStub, CloudQueryResult, toPrefixedKeyboardId as prefixed } from 'keyman/engine/keyboard-storage'; import { DeviceSpec } from 'keyman/common/web-utils'; -import { JSKeyboard, Keyboard, KMXKeyboard } from "keyman/engine/keyboard"; +import { JSKeyboard, Keyboard } from "keyman/engine/keyboard"; import KeyboardObject = KeymanWebKeyboard.KeyboardObject; import * as views from './viewsAnchorpoint.js'; @@ -691,10 +691,6 @@ export class KeymanEngine extends KeymanEngineBase { - const coreModuleName = this.isNode() ? 'km-core-node.mjs' : 'km-core.js'; - const module = await import(`${baseurl}/${coreModuleName}`); - const createCoreProcessor = module.default; - const km_core = createCoreProcessor({ - locateFile: function (path: string, scriptDirectory: string) { - return baseurl + '/' + path; - } - }); - km_core.then((core: KMXCoreModule) => { - this.km_core = core; - }); - return km_core; - } -} \ No newline at end of file diff --git a/web/src/engine/src/core-adapter/import/core/keymancore.d.ts b/web/src/engine/src/core-adapter/import/core/keymancore.d.ts new file mode 100644 index 0000000000..4955b40307 --- /dev/null +++ b/web/src/engine/src/core-adapter/import/core/keymancore.d.ts @@ -0,0 +1,157 @@ +// TypeScript bindings for emscripten-generated code. Automatically generated at compile time. +declare namespace RuntimeExports { + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index (i.e. maxBytesToRead will not + * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing + * frequent uses of UTF8ToString() with and without maxBytesToRead may throw + * JS JIT optimizations off, so it is worth to consider consistently using one + * @return {string} + */ + function UTF8ToString(ptr: number, maxBytesToRead?: number): string; + function stringToNewUTF8(str: any): any; + let wasmExports: any; + let HEAPF32: any; + let HEAPF64: any; + let HEAP_DATA_VIEW: any; + let HEAP8: any; + let HEAPU8: any; + let HEAP16: any; + let HEAPU16: any; + let HEAP32: any; + let HEAPU32: any; + let HEAP64: any; + let HEAPU64: any; +} +interface WasmModule { +} + +type EmbindString = ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string; +export interface km_core_keyboard { + delete(): void; +} + +export interface CoreKeyboardReturn { + readonly object: km_core_keyboard; + readonly status: number; + delete(): void; +} + +export interface CoreKeyboardAttrsReturn { + readonly object: km_core_keyboard_attrs; + readonly status: number; + delete(): void; +} + +export interface CoreContextReturn { + readonly object: km_core_context_items; + readonly status: number; + delete(): void; +} + +export interface km_core_keyboard_attrs { + version_string: string; + id: string; + default_options: any; + delete(): void; +} + +export interface km_core_actions { + do_alert: boolean; + emit_keystroke: boolean; + new_caps_lock_state: number; + code_points_to_delete: number; + output: string; + deleted_context: string; + persist_options: any; + toString(): string; + delete(): void; +} + +export interface km_core_state { + delete(): void; +} + +export interface CoreStateReturn { + readonly object: km_core_state; + readonly status: number; + delete(): void; +} + +export interface km_core_context { + delete(): void; +} + +export interface km_core_context_items { + data(): km_core_context_item; + push_back(item: km_core_context_item): void; + resize(size: number): void; + size(): number; + get(index: number): km_core_context_item; + set(index: number, item: km_core_context_item): void; + toString(): string; + delete(): void; +} + +export interface km_core_context_item { + readonly type: number; + character: number; + marker: number; + toString(): string; + delete(): void; +} + +export type km_core_attr = { + max_context: number, + current: number, + revision: number, + age: number, + technology: number +}; + +export type km_core_option_item = { + key: string, + value: string, + scope: number +}; + +interface EmbindModule { + km_core_keyboard: {}; + CoreKeyboardReturn: {}; + CoreKeyboardAttrsReturn: {}; + CoreContextReturn: {}; + km_core_keyboard_attrs: {}; + km_core_actions: {}; + km_core_state: {}; + CoreStateReturn: {}; + km_core_context: {}; + km_core_context_items: {new(): km_core_context_items}; + km_core_context_item: {new(): km_core_context_item}; + create_end_context(): km_core_context_item; + keyboard_get_attrs(keyboard: km_core_keyboard): CoreKeyboardAttrsReturn; + state_clone(state: km_core_state): CoreStateReturn; + state_context(state: km_core_state): km_core_context; + state_get_actions(state: km_core_state): km_core_actions; + context_get(context: km_core_context): CoreContextReturn; + keyboard_dispose(keyboard: km_core_keyboard): void; + state_dispose(state: km_core_state): void; + process_event(state: km_core_state, vk: number, modifier_state: number, is_key_down: boolean, event_flags: number): number; + state_context_clear(state: km_core_state): number; + context_set(context: km_core_context, context_items: km_core_context_items): number; + tmp_wasm_attributes(): km_core_attr; + state_context_set_if_needed(state: km_core_state, application_context: string): number; + state_context_debug(state: km_core_state, context_type: number): string; + keyboard_load_from_blob(kb_name: EmbindString, blob: any): CoreKeyboardReturn; + state_create(keyboard: km_core_keyboard, env: any): CoreStateReturn; + state_options_update(state: km_core_state, new_options: any): number; +} + +export type MainModule = WasmModule & typeof RuntimeExports & EmbindModule; +export default function MainModuleFactory (options?: unknown): Promise; diff --git a/web/src/engine/src/core-adapter/index.ts b/web/src/engine/src/core-adapter/index.ts deleted file mode 100644 index f5a4ff2c78..0000000000 --- a/web/src/engine/src/core-adapter/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { KM_Core, KM_CORE_STATUS, KM_CORE_OPTION_SCOPE, KM_CORE_KMX_ENV, KM_CORE_CT } from './KM_Core.js'; -import { type km_core_keyboard, type km_core_state, type km_core_context, type km_core_context_item, type km_core_context_items, type km_core_option_item } from './import/core/keymancore.js'; -export { km_core_keyboard, km_core_state, km_core_context, km_core_context_item, km_core_context_items, km_core_option_item }; diff --git a/web/src/engine/src/core-processor/coreKeyboardInterface.ts b/web/src/engine/src/core-processor/coreKeyboardInterface.ts deleted file mode 100644 index fbee9956e5..0000000000 --- a/web/src/engine/src/core-processor/coreKeyboardInterface.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ -import { KM_Core, km_core_option_item } from 'keyman/engine/core-adapter'; -import { KeyboardMinimalInterface, Keyboard, VariableStoreSerializer, KMXKeyboard } from 'keyman/engine/keyboard'; - -export class CoreKeyboardInterface implements KeyboardMinimalInterface { - private _activeKeyboard: Keyboard; - - public constructor(public variableStoreSerializer: VariableStoreSerializer) { - } - - public get activeKeyboard(): Keyboard { - return this._activeKeyboard; - } - public set activeKeyboard(keyboard: Keyboard) { - this._activeKeyboard = keyboard; - const options = this.loadSerializedOptions(keyboard); - - if (options.length > 0) { - const kmxKeyboard = this._activeKeyboard as KMXKeyboard; - KM_Core.instance.state_options_update(kmxKeyboard.state, options); - } - } - - - private loadSerializedOptions(keyboard: Keyboard): km_core_option_item[] { - // TODO-WEB-CORE: use km_core_keyboard_get_attrs to get list of all variable - // store names and then iterate through those rather than reading from - // cookie props - const options: km_core_option_item[] = []; - /*const stores = this.variableStoreSerializer.findStores(toPrefixedKeyboardId(keyboard.id)); - for (const store of stores) { - for (const key in store) { - if (store.hasOwnProperty(key)) { - const item: km_core_option_item = { - key: key, - value: store[key], - scope: KM_CORE_OPTION_SCOPE.OPT_KEYBOARD - }; - options.push(item); - } - } - }*/ - return options; - } -} diff --git a/web/src/engine/src/core-processor/coreKeyboardProcessor.ts b/web/src/engine/src/core-processor/coreKeyboardProcessor.ts deleted file mode 100644 index 429a83f5ed..0000000000 --- a/web/src/engine/src/core-processor/coreKeyboardProcessor.ts +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { EventEmitter } from 'eventemitter3'; -import { KM_Core, KM_CORE_STATUS, KM_CORE_CT, km_core_context, km_core_context_items, km_core_option_item } from 'keyman/engine/core-adapter'; -import { - BeepHandler, - EventMap, Keyboard, KeyboardMinimalInterface, KeyboardProcessor, - KeyEvent, KMXKeyboard, SyntheticTextStore, MutableSystemStore, TextStore, ProcessorAction, - StateKeyMap, - Deadkey, - Codes, - VariableStoreSerializer -} from "keyman/engine/keyboard"; -import { KM_CORE_EVENT_FLAG, KM_CORE_OPTION_SCOPE } from '../core-adapter/KM_Core.js'; -import { ModifierKeyConstants } from '@keymanapp/common-types'; -import { DeviceSpec } from 'keyman/common/web-utils'; -import { CoreKeyboardInterface } from './coreKeyboardInterface.js'; -import { toPrefixedKeyboardId } from 'keyman/engine/keyboard-storage'; - -const lockNames = ['CAPS', 'NUM_LOCK', 'SCROLL_LOCK'] as const; -const lockKeys = ['K_CAPS', 'K_NUMLOCK', 'K_SCROLL'] as const; -const lockModifiers = [ModifierKeyConstants.CAPITALFLAG, ModifierKeyConstants.NUMLOCKFLAG, ModifierKeyConstants.SCROLLFLAG] as const; -const noLockModifers = [ModifierKeyConstants.NOTCAPITALFLAG, ModifierKeyConstants.NOTNUMLOCKFLAG, ModifierKeyConstants.NOTSCROLLFLAG] as const; - -/** - * Implements the core keyboard processing engine that interacts with the - * shared Keyman Core component which handles .kmx keyboards. - */ -export class CoreKeyboardProcessor extends EventEmitter implements KeyboardProcessor { - private _newLayerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _oldLayerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _layerStore: MutableSystemStore = new MutableSystemStore(0, 'default'); - private _keyboardInterface: CoreKeyboardInterface; - - /** - * Initialize the core processor with the provided base path. - * Sets up the necessary environment for processing keyboard events. - * - * @param {string} basePath The path for the core processor - * resources, i.e. where the - * km-core.js file is located. - * @param {VariableStoreSerializer} storeSerializer Optional serializer for variable stores. - * @returns {Promise} A promise that resolves when initialization is complete. - */ - public async init(basePath: string, storeSerializer: VariableStoreSerializer): Promise { - await KM_Core.createCoreProcessor(basePath); - this._keyboardInterface = new CoreKeyboardInterface(storeSerializer); - } - - /** - * Tracks the simulated value for supported state keys, allowing the OSK to - * mirror a physical keyboard for them. Uses the exact keyCode name from the - * Codes definitions to enable certain optimizations elsewhere in the code. - * - * @type {StateKeyMap} - */ - public stateKeys: StateKeyMap = { - "K_CAPS": false, - "K_NUMLOCK": false, - "K_SCROLL": false - } - - /** - * Indicates the device (platform) to be used for non-keystroke events. - * Used for events such as those sent to `begin postkeystroke` and - * `begin newcontext` entry points. - * - * @type {DeviceSpec} - */ - public contextDevice: DeviceSpec; - - /** - * Optional handler for beep events triggered by the processor. - * Allows custom handling of beep feedback, such as for alerts or errors. - * - * @type {BeepHandler} - */ - public beepHandler?: BeepHandler; - - /** - * Bitfield representing the most recent modifier state (Alt, Ctrl, Shift, etc.) - * as observed or simulated by the processor. Used to quickly detect changes in - * modifier state not otherwise captured by the hosting page (important for AltGr). - * - * @type {number} - */ - public modStateFlags: number = 0; - - /** - * Stores the identifier for the base keyboard layout in use. - * Used to determine the default layout for key mapping and processing. - * - * @type {string} - */ - public baseLayout: string; - - /** - * The currently active keyboard. - * - * @type {Keyboard} - */ - public get activeKeyboard(): Keyboard { - return this.keyboardInterface.activeKeyboard; - } - public set activeKeyboard(keyboard: Keyboard) { - this.keyboardInterface.activeKeyboard = keyboard; - } - - /** - * The keyboard interface used by the processor. - * Provides access to the keyboard interface implementation. - * - * @type {KeyboardMinimalInterface} - */ - get keyboardInterface(): KeyboardMinimalInterface { - return this._keyboardInterface; - } - - /** - * The store representing the currently active keyboard layer. - * - * @type {MutableSystemStore} - */ - public get layerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx layer store (#15284) - return this._layerStore; - } - - /** - * A writable store used when transitioning to a new layer - * - * @type {MutableSystemStore} - */ - public get newLayerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx new-layer store (#15284) - return this._newLayerStore; - } - - /** - * A store representing the previously active layer - * - * @type {MutableSystemStore} - */ - public get oldLayerStore(): MutableSystemStore { - // TODO-web-core: link to .kmx old-layer store (#15284) - return this._oldLayerStore; - } - - /** - * Identifier of the currently active layer. - * - * @type {string} - */ - public get layerId(): string { - return this._layerStore.value; - } - public set layerId(value: string) { - this._layerStore.set(value); - } - - private getLayerId(modifier: number): string { - // TODO-web-core: implement - // return Layouts.getLayerId(modifier); - return 'default'; // TODO-web-core: put into LayerNames enum - } - - /** - * Retrieve context including deadkeys from TextStore and apply to Core's context - * - * @param context Context from Keyman Core - * @param textStore Web TextStore - */ - private applyContextFromTextStore(context: km_core_context, textStore: TextStore): void { - // Unlike the desktop Engines, we still track markers (deadkeys) in Engine - // for Web at this time. This is for two reasons: - // 1. We still have the legacy JSKeyboard code paths which manage deadkey - // state - // 2. SyntheticTextStores which are used for rewinding and replaying key - // events in predictive text and multitap need to also replay deadkeys - // - // TODO: Once we make CoreKeyboardProcessor the primary keyboard processor - // and fully deprecate JSKeyboardProcessor, we should consider moving the - // ownership of context back into opaque Core objects within - // SyntheticTextStore, so ownership of context and marker state can be - // managed entirely within Core, KeymanWeb does not need to have knowledge - // of markers, and then we better align with the desktop Engines. - - const caretPosition = textStore.getCaret(); - const text = textStore.getText().substring(0, caretPosition); - const deadkeys = textStore.deadkeys().dks.sort((a, b) => a.p != b.p ? a.p - b.p : a.o - b.o); - const contextItems = new KM_Core.instance.km_core_context_items(); - - const deadkeyIterator = deadkeys.values(); - let deadkey = deadkeyIterator.next(); - let textIndex = 0; - while (!deadkey.done || textIndex < text.length) { - // flush out invalid deadkeys - while (!deadkey.done && (deadkey.value.p < textIndex || deadkey.value.p > text.length)) { - // this should never happen -- it would mean that a deadkey position was < 0, in the - // middle of a surrogate pair, or after the caret. - console.warn(`invalid deadkey '${deadkey.value.d}' position ${deadkey.value.p}`); - deadkey = deadkeyIterator.next(); - } - - // insert 0 or more deadkeys at current index - while (!deadkey.done && deadkey.value.p == textIndex) { - const contextItem = new KM_Core.instance.km_core_context_item(); - contextItem.marker = deadkey.value.d; - contextItems.push_back(contextItem); - deadkey = deadkeyIterator.next(); - } - - // insert next character - if (textIndex < text.length) { - const contextItem = new KM_Core.instance.km_core_context_item(); - contextItem.character = text.codePointAt(textIndex); - contextItems.push_back(contextItem); - textIndex++; - if (contextItem.character > 0xFFFF) { - // we have a surrogate pair, skip other half of surrogate, codePointAt() - // already handled that for us - textIndex++; - } - } - } - - // Add end element - contextItems.push_back(KM_Core.instance.create_end_context()); - - KM_Core.instance.context_set(context, contextItems); - } - - /** - * Saves marker entries from Core's context into a TextStore's deadkey list. - * - * @param context Context from Keyman Core - * @param textStore Web TextStore - */ - private saveMarkersToTextStore(context: km_core_context, textStore: TextStore): void { - const { status, object } = KM_Core.instance.context_get(context); - if (status != KM_CORE_STATUS.OK) { - console.error('KeymanWeb: km_core_context_get failed with status: ' + status); - return; - } - textStore.deadkeys().clear(); - let textIndex = 0; - const contextItems: km_core_context_items = object; - for (let i = 0; i < contextItems.size(); i++) { - const contextItem = contextItems.get(i); - if (contextItem.type !== KM_CORE_CT.MARKER) { - textIndex++; - if (contextItem.character > 0xFFFF) { - // character will be a surrogate pair in the text store - textIndex++; - } - continue; - } - textStore.deadkeys().add(new Deadkey(textIndex, contextItem.marker)); - } - } - - /** - * Processes a keystroke event and updates the text store accordingly. - * Handles the main logic for interpreting and applying keyboard input. - * - * @param {KeyEvent} keyEvent The key event to process. - * @param {TextStore} textStore The current text store context. - * - * @returns {ProcessorAction} The resulting processor action. - */ - public processKeystroke(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { - - const preInput = SyntheticTextStore.from(textStore, true); - const activeKeyboard = this.activeKeyboard as KMXKeyboard; - const coreContext = KM_Core.instance.state_context(activeKeyboard.state); - - this.applyContextFromTextStore(coreContext, textStore); - - const status = KM_Core.instance.process_event(activeKeyboard.state, keyEvent.Lcode, keyEvent.Lmodifiers, keyEvent.source?.type === 'keydown', KM_CORE_EVENT_FLAG.DEFAULT); - // TODO-web-core: properly set flags (#15283) - if (status != KM_CORE_STATUS.OK) { - console.error('KeymanWeb: km_core_process_event failed with status: ' + status); - return null; - } - const processorAction = new ProcessorAction(); - const core_actions = KM_Core.instance.state_get_actions(activeKeyboard.state); - - textStore.deleteCharsBeforeCaret(core_actions.code_points_to_delete); - textStore.insertTextBeforeCaret(core_actions.output); - this.saveMarkersToTextStore(coreContext, textStore); - - processorAction.beep = core_actions.do_alert; - if (this.beepHandler && processorAction.beep) { - this.beepHandler(textStore); - } - - processorAction.triggerKeyDefault = core_actions.emit_keystroke; - - this.process_persist_action(core_actions.persist_options); - - // TODO-web-core: do we have to do anything with the new_caps_lock_state? (#15285) - // process_capslock_action(actions->new_caps_lock_state); - - processorAction.transcription = textStore.buildTranscriptionFrom(preInput, keyEvent, false); - - return processorAction; - } - - private process_persist_action(options: km_core_option_item[]): void { - if (this.keyboardInterface.variableStoreSerializer) { - for (const option of options) { - if (option.scope !== KM_CORE_OPTION_SCOPE.OPT_KEYBOARD) { - console.error(`Unsupported option scope: ${option.scope}`); - continue; - } - this.keyboardInterface.variableStoreSerializer.saveStore(toPrefixedKeyboardId(this.activeKeyboard.id), option.key, option.value); - } - } - } - - /** - * Processes post-keystroke actions for the given device and text store. - * Handles any actions that should occur after a keystroke is processed - * or after applying suggestions. - * - * @param {DeviceSpec} device The device specification. - * @param {TextStore} textStore The current text store context. - * - * @returns {ProcessorAction} The resulting processor action, or null if not applicable. - */ - public processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction { - // TODO-embed-osk-in-kmx: Implement this method (#15286) - // This gets called after processing a keystroke to process the PostKeystroke group - // (irrelevant for web-core since that is handled in Core), but also after - // applying a suggestion, which we do need to handle. - return null; - } - - // TODO-web-core: this could be shared with JsKeyboardProcessor - /** - * Determines if the given key event is a modifier key press. - * Returns true if the event corresponds to a modifier key, otherwise false. - * - * @param {KeyEvent} keyEvent The key event to evaluate. - * @param {TextStore} textStore The current text store context. - * @param {boolean} isKeyDown Indicates if the key event is a key down event. - * - * @returns {boolean} True if the event is a modifier key press, false otherwise. - */ - public doModifierPress(keyEvent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean { - if(!this.activeKeyboard) { - return false; - } - - if(keyEvent.isModifier) { - this.activeKeyboard.notify(keyEvent.Lcode, textStore, isKeyDown ? 1 : 0); - // For eventual integration - we bypass an OSK update for physical keystrokes when in touch mode. - if(!keyEvent.device.touchable) { - return this._UpdateVKShift(keyEvent); // I2187 - } else { - return true; - } - } - - if(keyEvent.LmodifierChange) { - this.activeKeyboard.notify(0, textStore, 1); - if(!keyEvent.device.touchable) { - this._UpdateVKShift(keyEvent); - } - } - - // No modifier keypresses detected. - return false; - } - - - // TODO-web-core: this could be shared with JsKeyboardProcessor - /** - * Updates the virtual keyboard shift state based on the provided key event. - * Handles modifier key simulation, state key updates, and layer selection for the OSK. - * - * @param {KeyEvent} e - The key event used to update the shift state. - * - * @returns {boolean} True if the update was processed, otherwise true if no active keyboard. - */ - private _UpdateVKShift(e: KeyEvent): boolean { - let keyShiftState=0; - - if(!this.activeKeyboard) { - return true; - } - - if(e) { - // read shift states from event - keyShiftState = e.Lmodifiers; - - // Are we simulating AltGr? If it's a simulation and not real, time to un-simulate for the OSK. - if(this.activeKeyboard.isChiral && this.activeKeyboard.emulatesAltGr && - (this.modStateFlags & Codes.modifierBitmasks['ALT_GR_SIM']) == Codes.modifierBitmasks['ALT_GR_SIM']) { - keyShiftState |= Codes.modifierBitmasks['ALT_GR_SIM']; - keyShiftState &= ~ModifierKeyConstants.RALTFLAG; - } - - // Set stateKeys where corresponding value is passed in e.Lstates - let stateMutation = false; - for(let i=0; i < lockNames.length; i++) { - if((e.Lstates & Codes.stateBitmasks[lockNames[i]]) != 0) { - this.stateKeys[lockKeys[i]] = ((e.Lstates & lockModifiers[i]) != 0); - stateMutation = true; - } - } - - if(stateMutation) { - this.emit('statekeychange', this.stateKeys); - } - } - - this.updateStates(); - - if (this.activeKeyboard.isMnemonic && this.stateKeys['K_CAPS'] && (!e || !e.isModifier)) { - // Modifier keypresses don't trigger mnemonic manipulation of modifier state. - // Only an output key does; active use of Caps will also flip the SHIFT flag. - // Mnemonic keystrokes manipulate the SHIFT property based on CAPS state. - // We need to unflip them when tracking the OSK layer. - keyShiftState ^= ModifierKeyConstants.K_SHIFTFLAG; - } - - this.layerId = this.getLayerId(keyShiftState); - return true; - } - - // TODO-web-core: this could be shared with JsKeyboardProcessor - private updateStates(): void { - for (let i = 0; i < lockKeys.length; i++) { - const key = lockKeys[i]; - const flag = this.stateKeys[key]; - - // Ensures that the current mod-state info properly matches the currently-simulated - // state key states. - if (flag) { - this.modStateFlags |= lockModifiers[i]; - this.modStateFlags &= ~noLockModifers[i]; - } else { - this.modStateFlags &= ~lockModifiers[i]; - this.modStateFlags |= noLockModifers[i]; - } - } - } - - /** - * Resets the keyboard context, optionally using the provided text store. - * Clears or reinitializes the context for subsequent keyboard processing. - * - * @param {TextStore} [textStore] - The optional text store to use for resetting context. - */ - public resetContext(textStore?: TextStore): void {} - - /** - * Finalizes the processor action and applies any final changes to the text store. - * Ensures that all necessary updates are completed after processing a key event. - * - * @param {ProcessorAction} data The processor action to finalize. - * @param {TextStore} textStore The text store to update. - */ - public finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void { } - - /** - * Selects the next keyboard layer based on the provided key event. - * Determines and applies the appropriate layer switch for the OSK. - * - * @param {KeyEvent} keyEvent The key event used to determine the next layer. - * - * @returns {boolean} True if the keyboard layer changed, false otherwise. - */ - public selectLayer(keyEvent: KeyEvent): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15284) - return false; - } - - /** - * Sets the numeric layer for the given device. - * Switches the keyboard to a numeric input layer if supported. - * - * @param {DeviceSpec} device - The device for which to set the numeric layer. - */ - public setNumericLayer(device: DeviceSpec): void {} - - - /** @internal */ - public unitTestEndPoints = { - saveMarkersToTextStore: this.saveMarkersToTextStore.bind(this), - applyContextFromTextStore: this.applyContextFromTextStore.bind(this), - }; -} diff --git a/web/src/engine/src/core-processor/index.ts b/web/src/engine/src/core-processor/index.ts deleted file mode 100644 index 3376653ad8..0000000000 --- a/web/src/engine/src/core-processor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CoreKeyboardProcessor } from './coreKeyboardProcessor.js'; diff --git a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts index 1c9117a433..8564e80ef4 100644 --- a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts +++ b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts @@ -1,4 +1,4 @@ -import { Keyboard, JSKeyboard, KeyboardLoaderBase as KeyboardLoader, KMXKeyboard } from "keyman/engine/keyboard"; +import { Keyboard, JSKeyboard, KeyboardLoaderBase as KeyboardLoader } from "keyman/engine/keyboard"; import { EventEmitter } from "eventemitter3"; import { KeyboardStub } from "./keyboardStub.js"; @@ -49,13 +49,6 @@ export class StubAndKeyboardCache extends EventEmitter { } shutdown(): void { - for (const kbdId in this.keyboardTable) { - const kbd = this.keyboardTable[kbdId]; - if (kbd instanceof KMXKeyboard) { - (kbd as KMXKeyboard).shutdown(); - } - // TODO-web-core: do we have to do something if instanceof Promise? - } } getKeyboardForStub(stub: KeyboardStub): Keyboard { @@ -203,7 +196,7 @@ export class StubAndKeyboardCache extends EventEmitter { let keyboardID: string; const languageID = arg1 || '---'; - if (arg0 instanceof JSKeyboard || arg0 instanceof KMXKeyboard) { + if (arg0 instanceof JSKeyboard) { keyboardID = arg0.id; } else { keyboardID = arg0 as string; @@ -235,7 +228,7 @@ export class StubAndKeyboardCache extends EventEmitter { * If `false`, only forgets the metadata (stubs). */ forgetKeyboard(keyboard: string | Keyboard, purge: boolean = false) { - const id: string = (keyboard instanceof JSKeyboard || keyboard instanceof KMXKeyboard) ? keyboard.id : toPrefixedKeyboardId(keyboard as string); + const id: string = (keyboard instanceof JSKeyboard) ? keyboard.id : toPrefixedKeyboardId(keyboard as string); if(this.stubSetTable[id]) { delete this.stubSetTable[id]; diff --git a/web/src/engine/src/keyboard/index.ts b/web/src/engine/src/keyboard/index.ts index 9a0c29ec76..40fd493a05 100644 --- a/web/src/engine/src/keyboard/index.ts +++ b/web/src/engine/src/keyboard/index.ts @@ -3,7 +3,6 @@ export { ButtonClass, ButtonClasses, LayoutLayer, LayoutFormFactor, LayoutRow, L export { JSKeyboard, LayoutState } from "./keyboards/jsKeyboard.js"; export { Keyboard } from './keyboards/keyboard.js'; export { KeyboardMinimalInterface } from './keyboards/keyboardMinimalInterface.js'; -export { KMXKeyboard } from './keyboards/kmxKeyboard.js'; export { KeyboardHarness, KeyboardKeymanGlobal, MinimalCodesInterface, MinimalKeymanGlobal } from "./keyboards/keyboardHarness.js"; export { NotifyEventCode, KeyboardLoaderBase } from "./keyboards/keyboardLoaderBase.js"; export { KeyboardLoadErrorBuilder, KeyboardMissingError, KeyboardScriptError, KeyboardDownloadError, InvalidKeyboardError } from './keyboards/keyboardLoadError.js' diff --git a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts index 2236d69653..ee11213012 100644 --- a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts +++ b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts @@ -1,5 +1,3 @@ -import { KM_Core, KM_CORE_STATUS } from 'keyman/engine/core-adapter'; -import { KMXKeyboard } from './kmxKeyboard.js'; import { KeyboardHarness } from "./keyboardHarness.js"; import { KeyboardProperties } from "./keyboardProperties.js"; import { KeyboardLoadErrorBuilder, StubBasedErrorBuilder, UriBasedErrorBuilder } from './keyboardLoadError.js'; @@ -59,12 +57,8 @@ export abstract class KeyboardLoaderBase { if (this.isKMXKeyboard(byteArray)) { // KMX or LDML (KMX+) keyboard - const name = this.extractIdFromUrl(uri); - const result = KM_Core.instance.keyboard_load_from_blob(name, byteArray); - if (result.status == KM_CORE_STATUS.OK) { - return new KMXKeyboard(result.object); - } - throw errorBuilder.invalidKeyboard(new Error(`Loading KMX keyboard from ${uri} failed with status ${result.status}`)); + // For version 19, disable loading of .kmx keyboards + throw new Error("TODO: .kmx files are not currently supported"); } let script: string; @@ -78,11 +72,6 @@ export abstract class KeyboardLoaderBase { return await this.loadKeyboardFromScript(script, errorBuilder); } - private extractIdFromUrl(uri: string): string { - // Extract filename without extension from the URL - return uri.split('/').pop().replace(/\.[^/.]+$/, ''); - } - protected abstract loadKeyboardBlob(uri: string, errorBuilder: KeyboardLoadErrorBuilder): Promise; protected abstract loadKeyboardFromScript(scriptSrc: string, errorBuilder: KeyboardLoadErrorBuilder): Promise; diff --git a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts b/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts deleted file mode 100644 index dfce5943a7..0000000000 --- a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { DeviceSpec } from 'keyman/common/web-utils'; -import { - KM_Core, km_core_keyboard, KM_CORE_KMX_ENV, - KM_CORE_OPTION_SCOPE, km_core_state, KM_CORE_STATUS -} from 'keyman/engine/core-adapter'; -import { ActiveKey, ActiveLayout, ActiveSubKey } from './activeLayout.js'; -import { StateKeyMap } from './stateKeyMap.js'; -import { KeyEvent } from '../keyEvent.js'; -import { TextStore } from '../textStore.js'; -import { NotifyEventCode } from './keyboardLoaderBase.js'; -import { Keyboard } from './keyboard.js'; - -/** - * Acts as a wrapper class for KMX(+) Keyman keyboards - */ -export class KMXKeyboard implements Keyboard { - private _state: km_core_state | null = null; - - public constructor(private _keyboard: km_core_keyboard) { - const environment_opts = - [ - { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.PLATFORM, - // TODO-web-core: Turn touch off for non-touch targets; read proper platform string from web (#15289) - value: "web iphone ipad androidphone androidtablet mobile touch hardware android phone" - }, { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.BASELAYOUT, - value: "kbdus.dll" // TODO: Base layout assumed to be US in v19 - }, { - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.BASELAYOUTALT, - value: "us", // TODO: Base layout assumed to be US in v19 - },{ - scope: KM_CORE_OPTION_SCOPE.OPT_ENVIRONMENT, - key: KM_CORE_KMX_ENV.SIMULATEALTGR, - value: "0" // TODO: We won't support simulating AltGr option in v19 - see also emulatesAltGr - } - ] - const result = KM_Core.instance.state_create(_keyboard, environment_opts); - if (result.status == KM_CORE_STATUS.OK) { - this._state = result.object; - } - } - - public shutdown() { - if (this._state) { - this._state.delete(); - this._state = null; - } - if (this._keyboard) { - KM_Core.instance.keyboard_dispose(this._keyboard); - this._keyboard = null; - } - } - - public constructKeyEvent(key: ActiveKey | ActiveSubKey, device: DeviceSpec, stateKeys: StateKeyMap): KeyEvent { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return null; - } - - public get isMnemonic(): boolean { - return false; - } - - public get version(): string { - const attrs = KM_Core.instance.keyboard_get_attrs(this._keyboard); - const version = attrs.object.version_string; - attrs.delete(); - return version; - } - - public get id(): string { - const attrs = KM_Core.instance.keyboard_get_attrs(this._keyboard); - const id = attrs.object.id; - attrs.delete(); - return id; - } - - public get name(): string { - // TODO-WEB-CORE: get keyboard name from Core API (to be implemented) - return this.id; - } - - public get keyboard(): km_core_keyboard { - return this._keyboard; - } - - public get state(): km_core_state { - return this._state; - } - - public get isChiral(): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - // Only relevant for OSK - return false; - } - - /** - * Signifies whether or not a layout or OSK should include AltGr / Right-alt emulation for this keyboard. - * @return {boolean} - */ - public get emulatesAltGr(): boolean { - // TODO: We won't support simulating AltGr option in v19 - see also c'tor - return false; - } - - /** - * Notifies keyboard of keystroke or other event - * - * @param {NotifyEventCode} eventCode key code (16-18: Shift, Control or Alt), - * or 0 for focus - * @param {TextStore} textStore textStore - * @param {number} data 1 for KeyDown or FocusReceived, - * 0 for KeyUp or FocusLost - */ - public notify(eventCode: NotifyEventCode, textStore: TextStore, data: number): void { - // TODO: implement for IMX (cf #2239 and #7928) - } - - /** - * Indicates whether the keyboard layout is designed for right-to-left scripts. - * This can be used by consumers to adjust UI layout and text direction for RTL keyboards. - * - * @returns {boolean} True if the keyboard is right-to-left, otherwise false. - */ - public get isRTL(): boolean { - // TODO-embed-osk-in-kmx: Hook up with Core API (#15482) - return false; - } - - /** - * Returns always false because (legacy) pick lists (Chinese, Japanese, Korean, etc.) - * are not supported when using KMX keyboards. - */ - public get isCJK(): boolean { - // always return false - return false; - } - - /** - * Returns an ActiveLayout object representing the keyboard's layout for this - * form factor. May return null if a custom desktop "help" OSK is defined, - * as with sil_euro_latin. - * - * In such cases, please use either `helpText` or `insertHelpHTML` instead. - * @param formFactor {string} The desired form factor for the layout. - */ - public layout(formFactor: DeviceSpec.FormFactor): ActiveLayout { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return null; - } - - /** - * Indicates whether the keyboard's desktop layout should be used for the - * specified device. - * - * @param device {DeviceSpec} The device specification to check - * @returns {boolean} True if the desktop layout should be used for - * this device, otherwise false. - */ - public usesDesktopLayoutOnDevice(device: DeviceSpec): boolean { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return true; - } - - /** - * CSS styling for the on-screen keyboard. - * - * @returns {string} CSS style string for the OSK - */ - public get oskStyling(): string { - // TODO-embed-osk-in-kmx: Implement this method (#15290) - return ''; - } - - -} diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts index 983dc1bdcf..633f121835 100644 --- a/web/src/engine/src/main/headless/inputProcessor.ts +++ b/web/src/engine/src/main/headless/inputProcessor.ts @@ -5,8 +5,6 @@ import { LanguageProcessor } from "./languageProcessor.js"; import type { ModelSpec, PathConfiguration } from "keyman/engine/interfaces"; import { globalObject, DeviceSpec, isEmptyTransform } from "keyman/common/web-utils"; -import { CoreKeyboardProcessor } from 'keyman/engine/core-processor'; - import { Codes, JSKeyboard, // required to be able to distinguish between JS and Core kbd processor @@ -38,7 +36,6 @@ export class InputProcessor { */ private contextDevice: DeviceSpec; private jsKbdProcessor: JSKeyboardProcessor; - private coreKbdProcessor: CoreKeyboardProcessor; private _keyboardProcessor: KeyboardProcessor; private _languageProcessor: LanguageProcessor; @@ -53,12 +50,10 @@ export class InputProcessor { this.contextDevice = device; this.jsKbdProcessor = new JSKeyboardProcessor(device, options); this._keyboardProcessor = this.jsKbdProcessor; - this.coreKbdProcessor = new CoreKeyboardProcessor(); this._languageProcessor = new LanguageProcessor(predictiveWorkerFactory, this.contextCache); } public async init(paths: PathConfiguration, storeSerializer: VariableStoreSerializer): Promise { - await this.coreKbdProcessor.init(paths.basePath, storeSerializer); } public get languageProcessor(): LanguageProcessor { @@ -79,12 +74,12 @@ export class InputProcessor { public set activeKeyboard(keyboard: Keyboard) { - if (keyboard instanceof JSKeyboard || keyboard == null) { + // TODO-web-core if (keyboard instanceof JSKeyboard || keyboard == null) { // TODO-web-core: consider keyboard==null scenario; which keyboardProcessor should be active? this._keyboardProcessor = this.jsKbdProcessor; - } else { - this._keyboardProcessor = this.coreKbdProcessor; - } + // TODO-web-core } else { + // TODO-web-core this._keyboardProcessor = this.coreKbdProcessor; + // TODO-web-core } this.keyboardInterface.activeKeyboard = keyboard; diff --git a/web/src/engine/tsconfig.json b/web/src/engine/tsconfig.json index bf7f732dcb..3698afeb04 100644 --- a/web/src/engine/tsconfig.json +++ b/web/src/engine/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "../../build/engine/obj/", "tsBuildInfoFile": "../../build/engine/obj/tsconfig.tsbuildinfo", "rootDir": "./src", + "strictNullChecks": false }, "include": [ "src/**/*.ts", "src/core-processor/import/*.js" ], diff --git a/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts b/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts deleted file mode 100644 index c688214d25..0000000000 --- a/web/src/test/auto/headless/engine/core-adapter/core-adapter.tests.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { assert } from 'chai'; -import { KM_Core, KM_CORE_STATUS } from 'keyman/engine/core-adapter'; -import { coreurl, loadKeyboardBlob } from '../loadKeyboardHelper.js'; - -// Test the KM_Core interface. -describe('KM_Core', function () { - this.timeout(5000); // increased timeout for async loading - - it('can initialize without errors', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - assert.isNotNull(km_core); - }); - - it('can call temp function', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const a = km_core.tmp_wasm_attributes(); - assert.isOk(a); - assert.isNumber(a.max_context); - console.dir(a); - }); - - it('can load a keyboard from blob', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const blob = await loadKeyboardBlob('/common/test/resources/keyboards/test_8568_deadkeys.kmx') - const result = km_core.keyboard_load_from_blob('test', blob); - assert.equal(result.status, KM_CORE_STATUS.OK); - assert.isOk(result.object); - result.delete(); - }); - - it('can get version from keyboard', async function () { - const km_core = await KM_Core.createCoreProcessor(coreurl); - const blob = await loadKeyboardBlob('/common/test/resources/keyboards/test_8568_deadkeys.kmx') - const keyboard = km_core.keyboard_load_from_blob('test', blob); - const result = km_core.keyboard_get_attrs(keyboard.object); - - assert.equal(result.status, KM_CORE_STATUS.OK); - assert.isOk(result.object); - assert.equal(result.object.version_string, '0.0'); - result.delete(); - }); -}); diff --git a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts b/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts deleted file mode 100644 index 7c4e3690fe..0000000000 --- a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ - -import { assert } from 'chai'; -import sinon from 'sinon'; -import { KM_Core, km_core_context, km_core_keyboard, km_core_state, KM_CORE_CT, KM_CORE_STATUS, km_core_context_items } from 'keyman/engine/core-adapter'; -import { coreurl, loadKeyboardBlob } from '../loadKeyboardHelper.js'; -import { DeviceSpec } from 'keyman/common/web-utils'; -import { Codes, Deadkey, KeyEvent, KMXKeyboard, SyntheticTextStore } from 'keyman/engine/keyboard'; -import { CoreKeyboardProcessor } from 'keyman/engine/core-processor'; -import { VariableStoreCookieSerializer } from 'keyman/engine/main'; - -describe('CoreKeyboardProcessor', function () { - const loadKeyboard = function (name: string): km_core_keyboard { - const blob = loadKeyboardBlob(name); - const result = KM_Core.instance.keyboard_load_from_blob(name, blob); - assert.equal(result.status, 0); - assert.isOk(result.object); - return result.object; - }; - - const createState = function (keyboardName: string): km_core_state { - const keyboard = loadKeyboard(keyboardName); - const state = KM_Core.instance.state_create(keyboard, []); - assert.equal(state.status, 0); - assert.isOk(state.object); - return state.object; - }; - - const addContextItem = function (contextItems: km_core_context_items, c: string | number, isMarker: boolean) { - const item = new KM_Core.instance.km_core_context_item(); - if (isMarker) { - item.marker = c as number; - } else if (typeof c == 'number') { - item.character = c; - } else { - item.character = c.codePointAt(0); - } - contextItems.push_back(item); - }; - - let coreProcessor: CoreKeyboardProcessor; - let state: km_core_state; - let context: km_core_context; - let textStore: SyntheticTextStore; - let sandbox: sinon.SinonSandbox; - - describe('saveMarkersToTextStore', function () { - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - Deadkey.ordinalSeed = 0; - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - it('saves markers to TextStore (BMP)', function() { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - // Text index : 0 1 1 1 2 3 3 - // context index: 0 1 2 3 4 5 6 - // ContextItems : a dk1 dk2 b c dk3 d - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 'a', false); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - addContextItem(contextItems, 'b', false); - addContextItem(contextItems, 'c', false); - addContextItem(contextItems, 3, true); // deadkey 3 - addContextItem(contextItems, 'd', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void {} - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 3, 'Should have 3 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 1, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 1, d: 2, o: 1, matched: 0}', 'dks[1]'); - assert.equal(textStore.deadkeys().dks[2].toString(), 'Deadkey { p: 3, d: 3, o: 2, matched: 0}', 'dks[2]'); - }); - - it('saves markers to TextStore (SMP)', function () { - // Setup - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - // Text index : 0 1 2 2 2 3 4 5 6 6 7 - // context index: 0 1 2 3 4 5 6 - // ContextItems : 𐌀 dk1 dk2 𐌁 𐌂 dk3 END - // ContextItem.character is a UTF-32 value! - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, '𐌀', false); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - addContextItem(contextItems, '𐌁', false); - addContextItem(contextItems, '𐌂', false); - addContextItem(contextItems, 3, true); // deadkey 3 - addContextItem(contextItems, '𐌃', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 3, 'Should have 3 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 2, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 2, d: 2, o: 1, matched: 0}', 'dks[1]'); - assert.equal(textStore.deadkeys().dks[2].toString(), 'Deadkey { p: 6, d: 3, o: 2, matched: 0}', 'dks[2]'); - }); - - it('can save to TextStore without markers (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - // Text index : 0 1 2 3 - // context index: 0 1 2 3 - // ContextItems : a b c d - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 'a', false); - addContextItem(contextItems, 'b', false); - addContextItem(contextItems, 'c', false); - addContextItem(contextItems, 'd', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 0, 'Should have 0 deadkeys'); - }); - - it('can save to TextStore without markers (SMP)', function () { - // Setup - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - // Text index : 0 1 2 3 4 5 - // context index: 0 1 2 3 - // ContextItems : 𐌀 𐌁 𐌂 END - // ContextItem.character is a UTF-32 value! - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, '𐌀', false); - addContextItem(contextItems, '𐌁', false); - addContextItem(contextItems, '𐌂', false); - addContextItem(contextItems, '𐌃', false); - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 0, 'Should have 0 deadkeys'); - }); - - it('saves markers to TextStore for empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - // Text index : 0 0 0 - // context index: 0 1 2 - // ContextItems : dk1 dk2 END - const contextItems = new KM_Core.instance.km_core_context_items(); - addContextItem(contextItems, 1, true); // deadkey 1 - addContextItem(contextItems, 2, true); // deadkey 2 - contextItems.push_back(KM_Core.instance.create_end_context()); - - sandbox.stub(KM_Core.instance, 'context_get').returns({ - status: KM_CORE_STATUS.OK, - object: contextItems, - delete: function (): void { } - }); - - // Execute - coreProcessor.unitTestEndPoints.saveMarkersToTextStore(context, textStore); - - // Verify - assert.equal(textStore.deadkeys().count(), 2, 'Should have 2 deadkeys'); - assert.equal(textStore.deadkeys().dks[0].toString(), 'Deadkey { p: 0, d: 1, o: 0, matched: 0}', 'dks[0]'); - assert.equal(textStore.deadkeys().dks[1].toString(), 'Deadkey { p: 0, d: 2, o: 1, matched: 0}', 'dks[1]'); - }); - }); - - - describe('applyContextFromTextStore', function () { - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - }); - - it('applies deadkeys from TextStore to Core context (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(1, 1)); // before 'b' - textStore.deadkeys().add(new Deadkey(1, 2)); - textStore.deadkeys().add(new Deadkey(3, 3)); // before 'd' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 1 2 3 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 5 6 - // ContextItems : a dk1 dk2 b c dk3 END - assert.equal(items.size(), 7, 'Should have 7 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.MARKER, 'Item 2 should be MARKER'); - assert.equal(items.get(2).marker, 2, 'Item 2 should be marker 2'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'b'.charCodeAt(0), 'Item 3 should be "b"'); - assert.equal(items.get(4).type, KM_CORE_CT.CHAR, 'Item 4 should be CHAR'); - assert.equal(items.get(4).character, 'c'.charCodeAt(0), 'Item 4 should be "c"'); - assert.equal(items.get(5).type, KM_CORE_CT.MARKER, 'Item 5 should be MARKER'); - assert.equal(items.get(5).marker, 3, 'Item 5 should be marker 3'); - assert.equal(items.get(6).type, KM_CORE_CT.END, 'Item 6 should be END'); - result.delete(); - }); - - it('applies deadkeys from TextStore to Core context (SMP)', function () { - // Setup - // Use a string with Old Italic letters which will consist of surrogate pairs - // in a UTF-16 string. - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - textStore.deadkeys().add(new Deadkey(2, 1)); // before '𐌁' - textStore.deadkeys().add(new Deadkey(2, 2)); - textStore.deadkeys().add(new Deadkey(6, 3)); // before '𐌃' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 2 2 3 4 5 6 6 7 - // Text: : 𐌀 𐌁 𐌂 | 𐌃 - // context index: 0 1 2 3 4 5 6 - // ContextItems : 𐌀 dk1 dk2 𐌁 𐌂 dk3 END - // ContextItem.character is a UTF-32 value! - assert.equal(items.size(), 7, 'Should have 7 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 0x10300, 'Item 0 should be "𐌀"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.MARKER, 'Item 2 should be MARKER'); - assert.equal(items.get(2).marker, 2, 'Item 2 should be marker 2'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 0x10301, 'Item 3 should be "𐌁"'); - assert.equal(items.get(4).type, KM_CORE_CT.CHAR, 'Item 4 should be CHAR'); - assert.equal(items.get(4).character, 0x10302, 'Item 4 should be "𐌂"'); - assert.equal(items.get(5).type, KM_CORE_CT.MARKER, 'Item 5 should be MARKER'); - assert.equal(items.get(5).marker, 3, 'Item 5 should be marker 3'); - assert.equal(items.get(6).type, KM_CORE_CT.END, 'Item 6 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context without deadkeys (BMP)', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 - // ContextItems : a b c END - assert.equal(items.size(), 4, 'Should have 4 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 'b'.charCodeAt(0), 'Item 1 should be "b"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'c'.charCodeAt(0), 'Item 2 should be "c"'); - assert.equal(items.get(3).type, KM_CORE_CT.END, 'Item 3 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context without deadkeys (SMP)', function () { - // Setup - // Use a string with Old Italic letters which will consist of surrogate pairs - // in a UTF-16 string. - textStore = new SyntheticTextStore('𐌀𐌁𐌂𐌃', 6); // U+10300 U+10301 U+10302 U+10303 - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 2 3 4 5 6 7 - // Text: : 𐌀 𐌁 𐌂 | 𐌃 - // context index: 0 1 2 3 - // ContextItems : 𐌀 𐌁 𐌂 END - // ContextItem.character is a UTF-32 value! - assert.equal(items.size(), 4, 'Should have 4 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 0x10300, 'Item 0 should be "𐌀"'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 0x10301, 'Item 1 should be "𐌁"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 0x10302, 'Item 2 should be "𐌂"'); - assert.equal(items.get(3).type, KM_CORE_CT.END, 'Item 3 should be END'); - result.delete(); - }); - - it('applies text from TextStore to Core context also after deadkeys', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(1, 1)); // before 'b' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 - // ContextItems : a dk1 b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 1, 'Item 1 should be marker 1'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - - it('works with empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 - // Text: : | - // context index: 0 - // ContextItems : END - assert.equal(items.size(), 1, 'Should have 1 context item'); - assert.equal(items.get(0).type, KM_CORE_CT.END, 'Item 0 should be END'); - result.delete(); - }); - - it('works with deadkey before the text', function () { - // Setup - textStore = new SyntheticTextStore('abcd', 3); - textStore.deadkeys().add(new Deadkey(0, 1)); // before 'a' - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 0 1 2 3 - // Text: : a b c | d - // context index: 0 1 2 3 4 - // ContextItems : dk1 a b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.MARKER, 'Item 0 should be MARKER'); - assert.equal(items.get(0).marker, 1, 'Item 0 should be marker 1'); - assert.equal(items.get(1).type, KM_CORE_CT.CHAR, 'Item 1 should be CHAR'); - assert.equal(items.get(1).character, 'a'.charCodeAt(0), 'Item 1 should be "a"'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - - it('works with deadkeys before empty text', function () { - // Setup - textStore = new SyntheticTextStore('', 0); - textStore.deadkeys().add(new Deadkey(0, 1)); // before caret - textStore.deadkeys().add(new Deadkey(0, 2)); // before caret - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 - // Text: : | - // context index: 0 1 2 - // ContextItems : dk1 dk2 END - assert.equal(items.size(), 3, 'Should have 3 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.MARKER, 'Item 0 should be MARKER'); - assert.equal(items.get(0).marker, 1, 'Item 0 should be marker 1'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 2, 'Item 1 should be marker 2'); - assert.equal(items.get(2).type, KM_CORE_CT.END, 'Item 2 should be END'); - result.delete(); - }); - - it('skips invalid deadkeys', function () { - // Setup - textStore = new SyntheticTextStore('abcde', 3); - textStore.deadkeys().add(new Deadkey(-1, 1)); // invalid - textStore.deadkeys().add(new Deadkey(1, 2)); // after 'b' - textStore.deadkeys().add(new Deadkey(4, 3)); // invalid (after caret) - - // Execute - coreProcessor.unitTestEndPoints.applyContextFromTextStore(context, textStore); - - // Verify - const result = KM_Core.instance.context_get(context); - assert.equal(result.status, KM_CORE_STATUS.OK); - const items = result.object; - - // Text index : 0 1 1 2 3 - // Text: : | - // context index: 0 1 2 3 4 - // ContextItems : a dk2 b c END - assert.equal(items.size(), 5, 'Should have 5 context items'); - assert.equal(items.get(0).type, KM_CORE_CT.CHAR, 'Item 0 should be CHAR'); - assert.equal(items.get(0).character, 'a'.charCodeAt(0), 'Item 0 should be "a"'); - assert.equal(items.get(1).type, KM_CORE_CT.MARKER, 'Item 1 should be MARKER'); - assert.equal(items.get(1).marker, 2, 'Item 1 should be marker 2'); - assert.equal(items.get(2).type, KM_CORE_CT.CHAR, 'Item 2 should be CHAR'); - assert.equal(items.get(2).character, 'b'.charCodeAt(0), 'Item 2 should be "b"'); - assert.equal(items.get(3).type, KM_CORE_CT.CHAR, 'Item 3 should be CHAR'); - assert.equal(items.get(3).character, 'c'.charCodeAt(0), 'Item 3 should be "c"'); - assert.equal(items.get(4).type, KM_CORE_CT.END, 'Item 4 should be END'); - result.delete(); - }); - }); - - describe('processKeystroke', function () { - let process_event_spy: any; - - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - for (const eventType of ['keydown', 'keyup']) { - const isKeyDown = eventType === 'keydown'; - - it(`passes the correct value for a ${eventType} event`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: Codes.keyCodes.K_A, - Lmodifiers: Codes.modifierCodes.SHIFT, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: null, - kName: 'K_A' - }); - keyEvent.source = { type: eventType }; - - const coreKeyboard = loadKeyboard('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - const kmxKeyboard = new KMXKeyboard(coreKeyboard); - sandbox.replaceGetter(coreProcessor, 'activeKeyboard', () => { return kmxKeyboard; }); - // We return a non-ok value just so that we can return early from - // processKeyStroke() - process_event_spy = sandbox.spy(KM_Core.instance, 'process_event'); - - // Execute - coreProcessor.processKeystroke(keyEvent, new SyntheticTextStore()); - - // Verify - check fourth argument (is_key_down) - assert.equal(process_event_spy.args[0][3], isKeyDown); - }); - } - }); - - describe('doModifierPress', function () { - // const touchable = true; - const nonTouchable = false; - - beforeEach(async function () { - coreProcessor = new CoreKeyboardProcessor(); - await coreProcessor.init(coreurl, new VariableStoreCookieSerializer()); - state = createState('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - context = KM_Core.instance.state_context(state); - sandbox = sinon.createSandbox(); - const coreKeyboard = loadKeyboard('/common/test/resources/keyboards/test_8568_deadkeys.kmx'); - coreProcessor.activeKeyboard = new KMXKeyboard(coreKeyboard); - }); - - afterEach(() => { - sandbox.restore(); - sandbox = null; - }) - - for (const key of [ - { code: Codes.keyCodes.K_SHIFT, name: 'Shift' }, - { code: Codes.keyCodes.K_CONTROL, name: 'Control' }, - { code: Codes.keyCodes.K_ALT, name: 'Alt' }, - { code: Codes.keyCodes.K_CAPS, name: 'CapsLock' }, - { code: Codes.keyCodes.K_NUMLOCK, name: 'NumLock' }, - { code: Codes.keyCodes.K_SCROLL, name: 'ScrollLock' }, - ]) { - it(`recognizes ${key.name} as modifier`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: key.code, - Lmodifiers: 0, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: new DeviceSpec('chrome', 'desktop', 'windows', nonTouchable), - kName: key.name - }); - keyEvent.source = { type: 'keydown' }; - - // Execute - const result = coreProcessor.doModifierPress(keyEvent, new SyntheticTextStore(), true); - - // Verify - assert.isTrue(result); - }); - } - - for (const key of [ - { modifiers: 0, name: 'a' }, - { modifiers: Codes.modifierCodes.SHIFT, name: 'A' } - ]) { - it(`recognizes ${key.name} not as modifier`, function () { - // Setup - const keyEvent = new KeyEvent({ - Lcode: Codes.keyCodes.K_A, - Lmodifiers: key.modifiers, - Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK, - LisVirtualKey: true, - device: new DeviceSpec('chrome', 'desktop', 'windows', nonTouchable), - kName: 'K_A' - }); - keyEvent.source = { type: 'keydown' }; - - // Execute - const result = coreProcessor.doModifierPress(keyEvent, new SyntheticTextStore(), true); - - // Verify - assert.isFalse(result); - }); - } - - }); -}); diff --git a/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts b/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts deleted file mode 100644 index 9754e17977..0000000000 --- a/web/src/test/auto/headless/engine/keyboard/kmxkeyboard.tests.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Keyman is copyright (C) SIL Global. MIT License. - */ -import { assert } from 'chai'; -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); - -import { KeyboardHarness, MinimalKeymanGlobal } from 'keyman/engine/keyboard'; -import { NodeKeyboardLoader } from 'keyman/test/resources'; - -describe('KMXKeyboard tests', function () { - // TODO-embed-osk-in-kmx: Enable when hooking up with RTL Core API (#15482) - it.skip('supports RTL layouts', async () => { - const rtlPath = require.resolve('@keymanapp/common-test-resources/keyboards/test_rtl.kmx'); - // -- START: Standard Recorder-based unit test loading boilerplate -- - const harness = new KeyboardHarness({}, MinimalKeymanGlobal); - const keyboardLoader = new NodeKeyboardLoader(harness); - const keyboard = await keyboardLoader.loadKeyboardFromPath(rtlPath); - // -- END: Standard Recorder-based unit test loading boilerplate -- - - assert.isTrue(keyboard.isRTL); - }); -}); diff --git a/web/src/test/auto/headless/engine/loadKeyboardHelper.ts b/web/src/test/auto/headless/engine/loadKeyboardHelper.ts index 5d1dc8b838..5ffaed719e 100644 --- a/web/src/test/auto/headless/engine/loadKeyboardHelper.ts +++ b/web/src/test/auto/headless/engine/loadKeyboardHelper.ts @@ -3,14 +3,11 @@ */ import fs from 'node:fs'; -import { pathToFileURL } from 'node:url'; import { getKeymanRoot } from 'keyman/test/resources'; const KEYMAN_ROOT = getKeymanRoot(); -export const coreurl = pathToFileURL(`${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core`).toString(); - export function loadKeyboardBlob(filename: string) { const data = fs.readFileSync(`${KEYMAN_ROOT}${filename}`, null); return new Uint8Array(data);