Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions web/docs/internal/keystroke-processing.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,13 @@ Note that for JS-keyboards, in 18.0 and before the true keystroke is processed a

#### Keystroke-default emulation

Note that browser-default keystroke processing is defined within the `DefaultRules` class found at [web/src/engine/keyboard/src/defaultRules.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/defaultRules.ts). It currently produces a (possibly `null`) string output rather than a `ProcessorAction`, but the latter can be easily constructed based on the returned string and the existing context state.
- For `epic/web-core`, it may be wise to spin the `DefaultRules` component off as its own processor, serving as a backup for _all_ keystroke processing variants.
Note that browser-default keystroke processing is defined within the `DefaultOutputRules` class
found at
[web/src/engine/keyboard/src/defaultOutputRules.ts](https://github.com/keymanapp/keyman/blob/master/web/src/engine/keyboard/src/defaultOutputRules.ts).
It currently produces a (possibly `null`) string output rather than a `ProcessorAction`, but the
latter can be easily constructed based on the returned string and the existing context state.
- For `epic/web-core`, it may be wise to spin the `DefaultOutputRules` component off as its own
processor, serving as a backup for _all_ keystroke processing variants.

----

Expand Down
4 changes: 2 additions & 2 deletions web/src/app/browser/src/defaultBrowserRules.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ModifierKeyConstants } from '@keymanapp/common-types';
import {
Codes,
DefaultRules,
DefaultOutputRules,
type KeyEvent,
type TextStore
} from 'keyman/engine/keyboard';

import { ContextManager } from './contextManager.js';

export class DefaultBrowserRules extends DefaultRules {
export class DefaultBrowserRules extends DefaultOutputRules {
private contextManager: ContextManager;

constructor(contextManager: ContextManager) {
Expand Down
4 changes: 2 additions & 2 deletions web/src/app/webview/src/keymanEngine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DeviceSpec } from 'keyman/common/web-utils';
import { DefaultRules, ProcessorAction } from 'keyman/engine/keyboard';
import { DefaultOutputRules, ProcessorAction } from 'keyman/engine/keyboard';
import { KeymanEngineBase, KeyboardInterfaceBase } from 'keyman/engine/main';
import { AnchoredOSKView, ViewConfiguration, StaticActivator } from 'keyman/engine/osk';
import { getAbsoluteX, getAbsoluteY } from 'keyman/engine/dom-utils';
Expand Down Expand Up @@ -36,7 +36,7 @@ export class KeymanEngine extends KeymanEngineBase<WebviewConfiguration, Context
// The `engine` parameter cannot be supplied with the constructing instance before calling
// `super`, hence the 'fun' rigging to supply it _from_ `super` via this closure.
keyboardInterface: new KeyboardInterfaceBase(window, engine, config.stubNamespacer),
defaultOutputRules: new DefaultRules()
defaultOutputRules: new DefaultOutputRules()
};
});

Expand Down
22 changes: 11 additions & 11 deletions web/src/engine/src/js-processor/jsKeyboardProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { EventEmitter } from 'eventemitter3';
import { ModifierKeyConstants } from '@keymanapp/common-types';
import {
Codes, type JSKeyboard, MinimalKeymanGlobal, KeyEvent, Layouts,
DefaultRules, EmulationKeystrokes, type MutableSystemStore,
DefaultOutputRules, EmulationKeystrokes, type MutableSystemStore,
TextStore, ProcessorAction, SystemStoreIDs, SyntheticTextStore,
KeyboardProcessor,
EventMap,
Expand All @@ -26,13 +26,13 @@ export type LogMessageHandler = (str: string) => void;
export interface ProcessorInitOptions {
baseLayout?: string;
keyboardInterface?: JSKeyboardInterface; // for tests, replace keyboardInterface with a mock, TODO-web-core: refactor into a unit test pattern
defaultOutputRules?: DefaultRules; // Takes the class def object, not an instance thereof.
defaultOutputRules?: DefaultOutputRules; // Takes the class def object, not an instance thereof.
}

export class JSKeyboardProcessor extends EventEmitter<EventMap> implements KeyboardProcessor {
private static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {
baseLayout: 'us',
defaultOutputRules: new DefaultRules() // TODO-web-core: move this out of here and only in keymanEngine.ts, rename to DefaultOutputRules
defaultOutputRules: new DefaultOutputRules() // TODO-web-core: move this out of here and only in keymanEngine.ts, rename to DefaultOutputRules
};

// Tracks the simulated value for supported state keys, allowing the OSK to mirror a physical keyboard for them.
Expand All @@ -59,7 +59,7 @@ export class JSKeyboardProcessor extends EventEmitter<EventMap> implements Keybo

public baseLayout: string;

public defaultRules: DefaultRules;
public defaultOutputRules: DefaultOutputRules;

// Callbacks for various feedback types
public beepHandler?: BeepHandler;
Expand All @@ -77,7 +77,7 @@ export class JSKeyboardProcessor extends EventEmitter<EventMap> implements Keybo

this.baseLayout = options.baseLayout || JSKeyboardProcessor.DEFAULT_OPTIONS.baseLayout;
this.keyboardInterface = options.keyboardInterface || new JSKeyboardInterface(globalObject(), MinimalKeymanGlobal);
this.defaultRules = options.defaultOutputRules || JSKeyboardProcessor.DEFAULT_OPTIONS.defaultOutputRules;
this.defaultOutputRules = options.defaultOutputRules || JSKeyboardProcessor.DEFAULT_OPTIONS.defaultOutputRules;
}

public get activeKeyboard(): JSKeyboard {
Expand Down Expand Up @@ -133,13 +133,13 @@ export class JSKeyboardProcessor extends EventEmitter<EventMap> implements Keybo
matched = true; // All the conditions below result in matches until the final else, which restores the expected default
// if no match occurs.

if(this.defaultRules.isCommand(Lkc)) {
if(this.defaultOutputRules.isCommand(Lkc)) {
// Note this in the rule behavior, return successfully. We'll consider applying it later.
ruleBehavior.triggersDefaultCommand = true;

// We'd rather let the browser handle these keys, but we're using emulated keystrokes, forcing KMW
// to emulate default behavior here.
} else if((special = this.defaultRules.forSpecialEmulation(Lkc)) != null) {
} else if((special = this.defaultOutputRules.forSpecialEmulation(Lkc)) != null) {
switch(special) {
case EmulationKeystrokes.Backspace:
this.keyboardInterface.defaultBackspace(textStore);
Expand All @@ -163,12 +163,12 @@ export class JSKeyboardProcessor extends EventEmitter<EventMap> implements Keybo
const isMnemonic = this.activeKeyboard && this.activeKeyboard.isMnemonic;

if(!matched) {
if((char = this.defaultRules.forAny(Lkc, isMnemonic, ruleBehavior)) != null) {
special = this.defaultRules.forSpecialEmulation(Lkc)
if((char = this.defaultOutputRules.forAny(Lkc, isMnemonic, ruleBehavior)) != null) {
special = this.defaultOutputRules.forSpecialEmulation(Lkc)
if(special == EmulationKeystrokes.Backspace) {
// A browser's default backspace may fail to delete both parts of an SMP character.
this.keyboardInterface.defaultBackspace(textStore);
} else if(special || this.defaultRules.isCommand(Lkc)) { // Filters out 'commands' like TAB.
} else if(special || this.defaultOutputRules.isCommand(Lkc)) { // Filters out 'commands' like TAB.
// We only do the "for special emulation" cases under the condition above... aside from backspace
// Let the browser handle those.
return null;
Expand Down Expand Up @@ -638,7 +638,7 @@ export class JSKeyboardProcessor extends EventEmitter<EventMap> implements Keybo

if (data.triggersDefaultCommand) {
const keyEvent = data.transcription.keystroke;
this.defaultRules.applyCommand(keyEvent, textStore);
this.defaultOutputRules.applyCommand(keyEvent, textStore);
}

if (this.warningLogger && data.warningLog) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ export class LogMessages {
/**
* Defines a collection of static library functions that define KeymanWeb's default (implied) keyboard rule behaviors.
*/
export class DefaultRules {
codeForEvent(Lkc: KeyEvent) {
return Codes.keyCodes[Lkc.kName] || Lkc.Lcode;;
export class DefaultOutputRules {
protected codeForEvent(Lkc: KeyEvent) {
return Codes.keyCodes[Lkc.kName] || Lkc.Lcode;
}

/**
* Serves as a default keycode lookup table. This may be referenced safely by mnemonic handling without fear of side-effects.
* Also used by Processor.defaultRuleBehavior to generate output after filtering for special cases.
*/
public forAny(Lkc: KeyEvent, isMnemonic: boolean, logMessages?: LogMessages): string {
var char = '';
let char = '';

// A pretty simple table of lookups, corresponding VERY closely to the original defaultKeyOutput.
if((char = this.forSpecialEmulation(Lkc)) != null) {
Expand All @@ -48,7 +48,7 @@ export class DefaultRules {
// // Not originally defined for text output within defaultKeyOutput.
// // We can't enable it yet, as it'll cause hardware keystrokes in the DOM to output '\t' rather
// // than rely on the browser-default handling.
let code = this.codeForEvent(Lkc);
const code = this.codeForEvent(Lkc);
switch(code) {
// case Codes.keyCodes['K_TAB']:
// case Codes.keyCodes['K_TABBACK']:
Expand All @@ -64,7 +64,7 @@ export class DefaultRules {
* isCommand - returns a boolean indicating if a non-text event should be triggered by the keystroke.
*/
public isCommand(Lkc: KeyEvent): boolean {
let code = this.codeForEvent(Lkc);
const code = this.codeForEvent(Lkc);

switch(code) {
// Should we ever implement them:
Expand Down Expand Up @@ -115,7 +115,7 @@ export class DefaultRules {
* for 'synthetic' `TextStore`s like `SyntheticTextStore`s, which have no default text handling.
*/
public forSpecialEmulation(Lkc: KeyEvent): EmulationKeystrokes {
let code = this.codeForEvent(Lkc);
const code = this.codeForEvent(Lkc);

switch(code) {
case Codes.keyCodes['K_BKSP']:
Expand All @@ -141,13 +141,8 @@ export class DefaultRules {
// Translate numpad keystrokes into their non-numpad equivalents
if(Lkc.Lcode >= Codes.keyCodes["K_NP0"] && Lkc.Lcode <= Codes.keyCodes["K_NPSLASH"]) {
// Number pad, numlock on
if(Lkc.Lcode < 106) {
var Lch = Lkc.Lcode-48;
} else {
Lch = Lkc.Lcode-64;
}
let ch = String.fromCodePoint(Lch);
return ch;
const cp = (Lkc.Lcode < 106) ? Lkc.Lcode - 48 : Lkc.Lcode - 64;
return String.fromCodePoint(cp);
} else {
return null;
}
Expand All @@ -166,7 +161,7 @@ export class DefaultRules {

let result = '';
const codePoints = keyName.substr(2).split('_');
for(let codePoint of codePoints) {
for(const codePoint of codePoints) {
const codePointValue = parseInt(codePoint, 16);
if (((0x0 <= codePointValue) && (codePointValue <= 0x1F)) || ((0x80 <= codePointValue) && (codePointValue <= 0x9F)) || isNaN(codePointValue)) {
// Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes.
Expand All @@ -181,13 +176,13 @@ export class DefaultRules {
result += String.fromCodePoint(codePointValue);
}
}
return result ? result : null;
return result || null;
}

// Test for otherwise unimplemented keys on the the base default & shift layers.
// Those keys must be blocked by keyboard rules if intentionally unimplemented; otherwise, this function will trigger.
public forBaseKeys(Lkc: KeyEvent, logMessages?: LogMessages) {
let n = Lkc.Lcode;
const n = Lkc.Lcode;
let keyShiftState = Lkc.Lmodifiers;

// check if exact match to SHIFT's code. Only the 'default' and 'shift' layers should have default key outputs.
Expand Down
2 changes: 1 addition & 1 deletion web/src/engine/src/keyboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export { type Alternate, TextTransform } from "./keyboards/textTransform.js";
export { Transcription } from "./keyboards/transcription.js";

export { Codes } from "./codes.js";
export { EmulationKeystrokes, LogMessages, DefaultRules } from "./defaultRules.js";
export { EmulationKeystrokes, LogMessages, DefaultOutputRules } from "./defaultOutputRules.js";
export { type KeyDistribution, KeyEventSpec, KeyEvent } from "./keyEvent.js";
export { KeyMapping } from "./keyMapping.js";
export { type SystemStoreMutationHandler, MutableSystemStore, SystemStore, SystemStoreIDs, type SystemStoreDictionary } from "./systemStore.js";
Expand Down
6 changes: 3 additions & 3 deletions web/src/engine/src/keyboard/keyEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { type JSKeyboard } from "./keyboards/jsKeyboard.js";
import { type DeviceSpec } from "keyman/common/web-utils";

import { Codes } from './codes.js';
import { DefaultRules } from "./defaultRules.js";
import { DefaultOutputRules } from "./defaultOutputRules.js";
import { ActiveKeyBase } from './keyboards/activeLayout.js';

// Represents a probability distribution over a keyboard's keys.
Expand All @@ -19,9 +19,9 @@ export type KeyDistribution = { keySpec: ActiveKeyBase, p: number }[];

/**
* A simple instance of the standard 'default rules' for keystroke processing from the
* DefaultRules base class.
* DefaultOutputRules base class.
*/
const BASE_DEFAULT_RULES = new DefaultRules();
const BASE_DEFAULT_RULES = new DefaultOutputRules();

export interface KeyEventSpec {

Expand Down
2 changes: 1 addition & 1 deletion web/src/engine/src/keyboard/keyboards/keyboardProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type BeepHandler = (textStore: TextStore) => void;
export interface KeyboardProcessor extends EventEmitter<EventMap> {
// public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {
// baseLayout: 'us',
// defaultOutputRules: new DefaultRules()
// defaultOutputRules: new DefaultOutputRules()
// };

/**
Expand Down