mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 00:15:32 +00:00
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
This commit is contained in:
parent
729c71d64d
commit
fc82f28b2d
24 changed files with 169 additions and 1555 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BrowserConfiguration, Context
|
|||
): HTMLElement {
|
||||
const PKbd = (PInternalName != null ? this.keyboardRequisitioner.cache.getKeyboard(PInternalName) : null) || this.core.activeKeyboard;
|
||||
|
||||
if (PKbd instanceof KMXKeyboard) {
|
||||
// TODO-embed-osk-in-kmx: implement for KMX keyboards
|
||||
return null;
|
||||
}
|
||||
const jsKbd = PKbd as JSKeyboard;
|
||||
const Pstub = this.keyboardRequisitioner.cache.getStub(jsKbd);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,6 @@ compile_and_copy() {
|
|||
mkdir -p "$KEYMAN_ROOT/web/build/app/resources/ui"
|
||||
cp -R "$KEYMAN_ROOT/web/src/resources/ui/." "$KEYMAN_ROOT/web/build/app/resources/ui/"
|
||||
|
||||
# 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/ui/debug/"
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/ui/release/"
|
||||
|
||||
# Update the build/publish copy of our build artifacts
|
||||
prepare
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,10 +61,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/webview/debug/"
|
||||
cp "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"km-core.{js,wasm} "${KEYMAN_ROOT}/web/build/app/webview/release/"
|
||||
|
||||
# Clean the sourcemaps of .. and . components
|
||||
for script in "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME/debug/"*.js; do
|
||||
if [[ "${script}" == *"/km-core.js" ]]; then
|
||||
|
|
|
|||
|
|
@ -38,16 +38,6 @@ builder_parse "$@"
|
|||
#### Build action definitions ####
|
||||
|
||||
copy_deps() {
|
||||
mkdir -p "${KEYMAN_ROOT}/web/src/engine/src/core-adapter/import/core/"
|
||||
# we don't need this file for release builds, but it's nice to have
|
||||
# for reference and auto-completion
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/keymancore.d.ts" "${KEYMAN_ROOT}/web/src/engine/src/core-adapter/import/core/"
|
||||
|
||||
mkdir -p "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/keymancore.d.ts" "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/"km-core{.js,.wasm} "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"
|
||||
cp "${KEYMAN_ROOT}/core/build/wasm/${BUILDER_CONFIGURATION}/src/"km-core-node{.mjs,.wasm} "${KEYMAN_ROOT}/web/build/engine/obj/core-adapter/import/core/"
|
||||
|
||||
cp "${KEYMAN_ROOT}/common/resources/fonts/keymanweb-osk.ttf" "${KEYMAN_ROOT}/web/src/resources/osk/"
|
||||
}
|
||||
|
||||
|
|
|
|||
1
web/src/engine/src/core-adapter/.gitignore
vendored
1
web/src/engine/src/core-adapter/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
import/
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
// Keyman is copyright (C) SIL International. MIT License.
|
||||
|
||||
import { type MainModule as KMXCoreModule } from './import/core/keymancore.js';
|
||||
|
||||
// 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.
|
||||
// So we have to re-define the enum here.
|
||||
// See https://github.com/emscripten-core/emscripten/issues/18585
|
||||
// NOTE: Keep in sync with core/include/keyman/keyman_core_api.h#L311
|
||||
export enum KM_CORE_STATUS {
|
||||
OK = 0,
|
||||
NO_MEM = 1,
|
||||
IO_ERROR = 2,
|
||||
INVALID_ARGUMENT = 3,
|
||||
KEY_ERROR = 4,
|
||||
INSUFFICENT_BUFFER = 5,
|
||||
INVALID_UTF = 6,
|
||||
INVALID_KEYBOARD = 7,
|
||||
NOT_IMPLEMENTED = 8,
|
||||
OS_ERROR = 0x80000000
|
||||
}
|
||||
|
||||
export enum KM_CORE_OPTION_SCOPE {
|
||||
OPT_UNKNOWN = 0,
|
||||
OPT_KEYBOARD = 1,
|
||||
OPT_ENVIRONMENT = 2,
|
||||
OPT_MAX_SCOPES
|
||||
}
|
||||
|
||||
export enum KM_CORE_KMX_ENV {
|
||||
PLATFORM = "platform",
|
||||
BASELAYOUT = "baseLayout",
|
||||
BASELAYOUTALT = "baseLayoutAlt",
|
||||
SIMULATEALTGR = "simulateAltgr",
|
||||
CAPSLOCK = "capsLock",
|
||||
BASELAYOUTGIVESCTRLRALTFORRALT = "baseLayoutGivesCtrlRAltForRAlt",
|
||||
}
|
||||
|
||||
export enum KM_CORE_CT {
|
||||
END = 0,
|
||||
CHAR = 1,
|
||||
MARKER = 2,
|
||||
}
|
||||
|
||||
export enum KM_CORE_EVENT_FLAG {
|
||||
DEFAULT = 0,
|
||||
TOUCH = 1,
|
||||
};
|
||||
|
||||
export class KM_Core {
|
||||
private static km_core: KMXCoreModule = null;
|
||||
|
||||
static get instance(): KMXCoreModule {
|
||||
return this.km_core;
|
||||
}
|
||||
|
||||
private static isNode() {
|
||||
// from https://stackoverflow.com/a/31456668
|
||||
return (typeof process !== "undefined" && process?.versions?.node);
|
||||
}
|
||||
|
||||
public static async createCoreProcessor(baseurl: string): Promise<KMXCoreModule> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
157
web/src/engine/src/core-adapter/import/core/keymancore.d.ts
vendored
Normal file
157
web/src/engine/src/core-adapter/import/core/keymancore.d.ts
vendored
Normal file
|
|
@ -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<MainModule>;
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<EventMap> 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<void>} A promise that resolves when initialization is complete.
|
||||
*/
|
||||
public async init(basePath: string, storeSerializer: VariableStoreSerializer): Promise<void> {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { CoreKeyboardProcessor } from './coreKeyboardProcessor.js';
|
||||
|
|
@ -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<EventMap> {
|
|||
}
|
||||
|
||||
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<EventMap> {
|
|||
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<EventMap> {
|
|||
* 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];
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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<Uint8Array>;
|
||||
|
||||
protected abstract loadKeyboardFromScript(scriptSrc: string, errorBuilder: KeyboardLoadErrorBuilder): Promise<Keyboard>;
|
||||
|
|
|
|||
|
|
@ -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 '';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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<void> {
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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" ],
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue