chore(web): Merge branch 'epic/web-core' into feat/web/13926_kbdprocessing

This commit is contained in:
Eberhard Beilharz 2025-11-18 15:31:02 +01:00
commit bb2e6e6466
No known key found for this signature in database
GPG key ID: E9140597606020D3
4 changed files with 296 additions and 57 deletions

View file

@ -14,23 +14,37 @@ import {
export class CoreKeyboardInterface implements KeyboardMinimalInterface {
public activeKeyboard: Keyboard;
constructor() {
}
}
/**
* 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 = new 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.
* @returns {Promise<void>} A promise that resolves when initialization is complete.
*/
public async init(basePath: string): Promise<void> {
await KM_Core.createCoreProcessor(basePath);
}
// Tracks the simulated value for supported state keys, allowing the OSK to mirror a physical keyboard for them.
// Using the exact keyCode name from the Codes definitions will allow for certain optimizations elsewhere in the code.
/**
* 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,
@ -38,47 +52,96 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
}
/**
* Indicates the device (platform) to be used for non-keystroke events,
* such as those sent to `begin postkeystroke` and `begin newcontext`
* entry points.
*/
* 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;
// Tracks the most recent modifier state information in order to quickly detect changes
// in keyboard state not otherwise captured by the hosting page in the browser.
// Needed for AltGr simulation.
/**
* 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
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
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
return this._oldLayerStore;
}
/**
* Identifier of the currently active layer.
*
* @type {string}
*/
public get layerId(): string {
return this._layerStore.value;
}
@ -86,11 +149,6 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
this._layerStore.set(value);
}
public processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction {
// TODO-web-core: Implement this method
return null;
}
/**
* Retrieve context including deadkeys from TextStore and apply to Core's context
*
@ -169,6 +227,15 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
}
}
/**
* 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);
@ -204,29 +271,72 @@ export class CoreKeyboardProcessor extends EventEmitter<EventMap> implements Key
}
/**
* Select the OSK's next keyboard layer based upon layer switching keys as a default
* The next layer will be determined from the key name unless otherwise specifed
* Processes post-keystroke actions for the given device and text store.
* Handles any actions that should occur after a keystroke is processed.
*
* @param {string} keyName key identifier
* @return {boolean} return true if keyboard layer changed
* @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-web-core: Implement this method
return null;
}
/**
* 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 {
// TODO-web-core: Implement this method
return false;
}
/**
* 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-web-core: Implement this method
return false;
}
// Returns true if the key event is a modifier press, allowing keyPress to return selectively
// in those cases.
public doModifierPress(Levent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean {
// TODO-web-core: Implement this method
return false;
}
public resetContext(textStore?: TextStore): void {}
/**
* 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 {}
public finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void {}
/** @internal */
public unitTestEndPoints = {

View file

@ -17,67 +17,196 @@ export interface EventMap {
export type BeepHandler = (textStore: TextStore) => void;
/**
* Interface for the keyboard processing engine used by the web runtime to
* translate low-level key events and device/context signals into high-level
* keyboard actions and text-store updates.
*
* This interface extends an EventEmitter of EventMap and centralizes responsibilities
* such as:
* - tracking simulated state keys so the onscreen keyboard (OSK) can mirror hardware state,
* - selecting and switching keyboard layers for the OSK,
* - handling modifier simulation (including AltGr),
* - routing non-keystroke device/context events,
* - coordinating mutable layer state stores,
* - performing keystroke and postkeystroke processing and finalizing ProcessorAction
* results against a TextStore.
*/
export interface KeyboardProcessor extends EventEmitter<EventMap> {
// public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {
// baseLayout: 'us',
// defaultOutputRules: new DefaultRules()
// };
// Tracks the simulated value for supported state keys, allowing the OSK to mirror a physical keyboard for them.
// Using the exact keyCode name from the Codes definitions will allow for certain optimizations elsewhere in the code.
/**
* Tracks the simulated values for supported state keys (e.g. CapsLock, NumLock)
* so that the OSK can reflect a physical keyboard's state. Keys are referenced
* using the exact keyCode names from the Codes definitions to allow for optimized
* handling elsewhere.
*
* @type {StateKeyMap}
*/
stateKeys: StateKeyMap;
/**
* Indicates the device (platform) to be used for non-keystroke events,
* such as those sent to `begin postkeystroke` and `begin newcontext`
* entry points.
*/
* Indicates the device/platform to be used for non-keystroke events (for example,
* events dispatched to "begin postkeystroke" and "begin newcontext" entry points).
* This lets the processor adapt behavior or rule evaluation to the active device.
*
* @type {DeviceSpec}
*/
contextDevice: DeviceSpec;
/**
* Optional handler used to produce an audible beep or other feedback when a rule
* or keyboard action requests it.
*
* @type {BeepHandler | undefined}
*/
beepHandler?: BeepHandler;
/**
* Stores the identifier for the base physical layout in use (for
* example "us"). Used to determine the default layout for key mapping
* and processing.
*
* @type {string}
*/
baseLayout: string;
// Tracks the most recent modifier state information in order to quickly detect changes
// in keyboard state not otherwise captured by the hosting page in the browser.
// Needed for AltGr simulation.
/**
* 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}
*/
modStateFlags: number;
/**
* The currently active Keyboard instance. Implementations provide a getter and
* setter to change the active keyboard at runtime. Setting a new keyboard should
* update any associated stores and clear or reinitialize processor state as needed.
*
* @type {Keyboard}
*/
get activeKeyboard(): Keyboard;
set activeKeyboard(keyboard: Keyboard);
/**
* Read-only minimal interface for interacting with the current keyboard.
*
* @type {KeyboardMinimalInterface}
*/
get keyboardInterface(): KeyboardMinimalInterface
/**
* The store representing the currently active keyboard layer.
*
* @type {MutableSystemStore}
*/
get layerStore(): MutableSystemStore;
/**
* A writable store used when transitioning to a new layer; allows
* accumulation of changes before committing them to layerStore.
*
* @type {MutableSystemStore}
*/
get newLayerStore(): MutableSystemStore;
/**
* A store representing the previously active layer; useful for reverting or
* comparing layer states when switching layers.
*
* @type {MutableSystemStore}
*/
get oldLayerStore(): MutableSystemStore;
/**
* Identifier of the currently active layer. Implementations provide getter and
* setter access. Setting this value should trigger the appropriate layer store
* updates and notify any listeners of the change.
*
* @type {string}
*/
get layerId(): string;
set layerId(value: string);
processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction;
/**
* Process a keystroke, i.e. the `begin Unicode` group.
* Evaluates keyboard rules, updates internal state, and returns a ProcessorAction
* describing the changes to apply to the TextStore.
*
* @param {KeyEvent} keyEvent The key event to process.
* @param {TextStore} textStore The text store representing current context.
*
* @returns {ProcessorAction} The resulting processor action.
*/
processKeystroke(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction;
/**
* Select the OSK's next keyboard layer based upon layer switching keys as a default
* The next layer will be determined from the key name unless otherwise specifed
* Processes the `begin PostKeystroke` group.
* This is used to evaluate rules that run after a keystroke has been
* processed and can produce actions that affect the TextStore, layer state,
* or other side effects.
*
* @param {string} keyName key identifier
* @return {boolean} return true if keyboard layer changed
* @param {DeviceSpec} device The device context.
* @param {TextStore} textStore The text store representing current context.
*
* @returns {ProcessorAction} The resulting processor action.
*/
processPostKeystroke(device: DeviceSpec, textStore: TextStore): ProcessorAction;
/**
* 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.
*/
doModifierPress(keyEvent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean;
/**
* Resets the processor's context to a clean state.
* May clear stores, simulated state keys, and modifier flags. If a TextStore is provided,
* it will also be reset.
*
* @param {TextStore} [textStore] - The optional text store to use for resetting context.
*/
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.
*/
finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void;
/**
* Selects the OSK's next keyboard layer based upon layer switching keys.
* By default, the next layer is determined from the key name unless otherwise
* specified. Returns true if the active keyboard layer changed.
*
* @param {KeyEvent} keyEvent - Key identifier or event used to determine layer change.
*
* @returns {boolean} True if the keyboard layer changed.
*/
selectLayer(keyEvent: KeyEvent): boolean;
// Returns true if the key event is a modifier press, allowing keyPress to return selectively
// in those cases.
doModifierPress(Levent: KeyEvent, textStore: TextStore, isKeyDown: boolean): boolean;
resetContext(textStore?: TextStore): void;
/**
*
* Select the numeric layer if the provided device contains one.
*
* @param {DeviceSpec} device - The device for which the numeric layer should be set.
*/
setNumericLayer(device: DeviceSpec): void;
finalizeProcessorAction(data: ProcessorAction, textStore: TextStore): void;
}

View file

@ -19,7 +19,7 @@ builder_parse "$@"
DEST="web/build/test-resources"
builder_describe_outputs \
build "${KEYMAN_ROOT}/${DEST}/sentry-manager.js"
build "/${DEST}/sentry-manager.js"
#### Build action definitions ####
@ -30,7 +30,7 @@ function do_copy() {
cp "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/index.js" "${KEYMAN_ROOT}/${DEST}/sentry-manager.js"
cp "${KEYMAN_ROOT}/common/web/sentry-manager/build/lib/index.js.map" "${KEYMAN_ROOT}/${DEST}/sentry-manager.js.map"
cp "${KEYMAN_ROOT}/common/web/types/tests/fixtures/kmx/khmer_angkor.kmx" "${KEYMAN_ROOT}/${DEST}/"
cp "${KEYMAN_ROOT}/common/web/types/tests/fixtures/kmx/khmer_angkor.kmx" "${KEYMAN_ROOT}/${DEST}/keyboards/"
# copy common test (resources) keyboards
cp -f "${KEYMAN_ROOT}/common/test/keyboards/platform-rules/platformtest.js" "${KEYMAN_ROOT}/${DEST}/keyboards/"

View file

@ -45,7 +45,7 @@
id: 'km',
name: 'Khmer'
},
filename: '/build/test-resources/khmer_angkor.kmx'
filename: '/build/test-resources/keyboards/khmer_angkor.kmx'
});
});
});