spiegel-keyman/web/source/dom/preProcessor.ts
2022-05-31 09:11:18 +07:00

312 lines
No EOL
13 KiB
TypeScript

namespace com.keyman.dom {
export class PreProcessor {
/**
* Function _GetEventKeyCode
* Scope Private
* @param {Event} e Event object
* Description Finds the key code represented by the event.
*/
static _GetEventKeyCode(e: KeyboardEvent) {
if (e.keyCode) {
return e.keyCode;
} else if (e.which) {
return e.which;
} else {
return null;
}
}
/**
* Function _GetKeyEventProperties
* Scope Private
* @param {Event} e Event object
* @param {boolean=} keyState true if call results from a keyDown event, false if keyUp, undefined if keyPress
* @return {Object.<string,*>} KMW keyboard event object:
* Description Get object with target element, key code, shift state, virtual key state
* Lcode=keyCode
* Lmodifiers=shiftState
* LisVirtualKeyCode e.g. ctrl/alt key
* LisVirtualKey e.g. Virtual key or non-keypress event
*/
static _GetKeyEventProperties(e: KeyboardEvent, keyState?: boolean): text.KeyEvent {
let keyman = com.keyman.singleton;
let core = keyman.core;
var s = new text.KeyEvent();
if(e.cancelBubble === true) {
return null; // I2457 - Facebook meta-event generation mess -- two events generated for a keydown in Facebook contentEditable divs
}
s.Lcode = this._GetEventKeyCode(e);
if (s.Lcode == null) {
return null;
}
// Stage 1 - track the true state of the keyboard's modifiers.
var prevModState = core.keyboardProcessor.modStateFlags, curModState = 0x0000;
var ctrlEvent = false, altEvent = false;
let keyCodes = text.Codes.keyCodes;
switch(s.Lcode) {
case keyCodes['K_CTRL']: // The 3 shorter "K_*CTRL" entries exist in some legacy keyboards.
case keyCodes['K_LCTRL']:
case keyCodes['K_RCTRL']:
case keyCodes['K_CONTROL']:
case keyCodes['K_LCONTROL']:
case keyCodes['K_RCONTROL']:
ctrlEvent = true;
break;
case keyCodes['K_LMENU']: // The 2 "K_*MENU" entries exist in some legacy keyboards.
case keyCodes['K_RMENU']:
case keyCodes['K_ALT']:
case keyCodes['K_LALT']:
case keyCodes['K_RALT']:
altEvent = true;
break;
}
/**
* Two separate conditions exist that should trigger chiral modifier detection. Examples below use CTRL but also work for ALT.
*
* 1. The user literally just pressed CTRL, so the event has a valid `location` property we can utilize.
* Problem: its layer isn't presently activated within the OSK.
*
* 2. CTRL has been held a while, so the OSK layer is valid, but the key event doesn't tell us the chirality of the active CTRL press.
* Bonus issue: RAlt simulation may cause erasure of this location property, but it should ONLY be empty if pressed in this case.
* We default to the 'left' variants since they're more likely to exist and cause less issues with RAlt simulation handling.
*
* In either case, `e.getModifierState("Control")` is set to true, but as a result does nothing to tell us which case is active.
*
* `e.location != 0` if true matches condition 1 and matches condition 2 if false.
*/
curModState |= (e.getModifierState("Shift") ? 0x10 : 0);
let modifierCodes = text.Codes.modifierCodes;
if(e.getModifierState("Control")) {
curModState |= ((e.location != 0 && ctrlEvent) ?
(e.location == 1 ? modifierCodes['LCTRL'] : modifierCodes['RCTRL']) : // Condition 1
prevModState & 0x0003); // Condition 2
}
if(e.getModifierState("Alt")) {
curModState |= ((e.location != 0 && altEvent) ?
(e.location == 1 ? modifierCodes['LALT'] : modifierCodes['RALT']) : // Condition 1
prevModState & 0x000C); // Condition 2
}
// Stage 2 - detect state key information. It can be looked up per keypress with no issue.
s.Lstates = 0;
s.Lstates |= e.getModifierState('CapsLock') ? modifierCodes['CAPS'] : modifierCodes['NO_CAPS'];
s.Lstates |= e.getModifierState('NumLock') ? modifierCodes['NUM_LOCK'] : modifierCodes['NO_NUM_LOCK'];
s.Lstates |= (e.getModifierState('ScrollLock'))
? modifierCodes['SCROLL_LOCK'] : modifierCodes['NO_SCROLL_LOCK'];
// We need these states to be tracked as well for proper OSK updates.
curModState |= s.Lstates;
// Stage 3 - Set our modifier state tracking variable and perform basic AltGr-related management.
s.LmodifierChange = core.keyboardProcessor.modStateFlags != curModState;
core.keyboardProcessor.modStateFlags = curModState;
// For European keyboards, not all browsers properly send both key-up events for the AltGr combo.
var altGrMask = modifierCodes['RALT'] | modifierCodes['LCTRL'];
if((prevModState & altGrMask) == altGrMask && (curModState & altGrMask) != altGrMask) {
// We just released AltGr - make sure it's all released.
curModState &= ~ altGrMask;
}
// Perform basic filtering for Windows-based ALT_GR emulation on European keyboards.
if(curModState & modifierCodes['RALT']) {
curModState &= ~modifierCodes['LCTRL'];
}
let modifierBitmasks = text.Codes.modifierBitmasks;
// Stage 4 - map the modifier set to the appropriate keystroke's modifiers.
var activeKeyboard = core.activeKeyboard;
if(activeKeyboard && activeKeyboard.isChiral) {
s.Lmodifiers = curModState & modifierBitmasks.CHIRAL;
// Note for future - embedding a kill switch here would facilitate disabling AltGr / Right-alt simulation.
if(activeKeyboard.emulatesAltGr && (s.Lmodifiers & modifierBitmasks['ALT_GR_SIM']) == modifierBitmasks['ALT_GR_SIM']) {
s.Lmodifiers ^= modifierBitmasks['ALT_GR_SIM'];
s.Lmodifiers |= modifierCodes['RALT'];
}
} else {
// No need to sim AltGr here; we don't need chiral ALTs.
s.Lmodifiers =
(curModState & 0x10) | // SHIFT
((curModState & (modifierCodes['LCTRL'] | modifierCodes['RCTRL'])) ? 0x20 : 0) |
((curModState & (modifierCodes['LALT'] | modifierCodes['RALT'])) ? 0x40 : 0);
}
/* Tweak the modifiers if an OS meta key is detected; this will allow meta-key-based
* hotkeys to bypass Keyman processing. We do this AFTER the chiral modifier filtering
* because some keyboards specify their own modifierBitmask, which won't include it.
* We don't currently use that reference in this method, but that may change in the future.
*/
s.Lmodifiers |= (e.metaKey ? modifierCodes['META']: 0);
// Physically-typed keys require use of a 'desktop' form factor and thus are based on a virtual "physical" Device.
s.device = keyman.util.physicalDevice.coreSpec;
// Perform any browser-specific key remapping before other remaps and mnemonic transforms.
// (See https://github.com/keymanapp/keyman/issues/1125.)
if(!keyman.isEmbedded && s.device.browser == utils.Browser.Firefox) {
// Browser key identifiers are not completely consistent; Firefox has a few (for US punctuation)
// that differ from the norm. Refer to https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode.
if(KeyMapping.browserMap.FF['k'+s.Lcode]) {
s.Lcode = KeyMapping.browserMap.FF['k'+s.Lcode];
}
}
// Mnemonic handling.
if(activeKeyboard && activeKeyboard.isMnemonic) {
// The following will never set a code corresponding to a modifier key, so it's fine to do this,
// which may change the value of Lcode, here.
text.KeyboardProcessor.setMnemonicCode(s, e.getModifierState("Shift"), e.getModifierState("CapsLock"));
}
// The 0x6F used to be 0x60 - this adjustment now includes the chiral alt and ctrl modifiers in that check.
var LisVirtualKeyCode = (typeof e.charCode != 'undefined' && e.charCode != null && (e.charCode == 0 || (s.Lmodifiers & 0x6F) != 0));
s.LisVirtualKey = LisVirtualKeyCode || e.type != 'keypress';
// This is based on a KeyboardEvent, so it's not considered 'synthetic' within web-core.
s.isSynthetic = false;
// Other minor physical-keyboard adjustments
if(activeKeyboard && !activeKeyboard.isMnemonic) {
// Positional Layout
/* 13/03/2007 MCD: Swedish: Start mapping of keystroke to US keyboard */
var Lbase = KeyMapping.languageMap[core.keyboardProcessor.baseLayout];
if(Lbase && Lbase['k'+s.Lcode]) {
s.Lcode=Lbase['k'+s.Lcode];
}
/* 13/03/2007 MCD: Swedish: End mapping of keystroke to US keyboard */
if(!activeKeyboard.definesPositionalOrMnemonic && !(s.Lmodifiers & 0x60)) {
// Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic
s = {
Lcode: KeyMapping._USKeyCodeToCharCode(s),
Lmodifiers: 0,
LisVirtualKey: false,
vkCode: s.Lcode, // Helps to merge OSK and physical keystroke control paths.
Lstates: s.Lstates,
kName: '',
device: keyman.util.physicalDevice.coreSpec,
isSynthetic: false
};
}
}
return s;
}
public static getEventOutputTarget(e: Event): text.OutputTarget {
let keyman = com.keyman.singleton;
let target = keyman.util.eventTarget(e) as HTMLElement;
if (target == null) {
return null;
} else if (target.nodeType == 3) {// defeat Safari bug
target = target.parentNode as HTMLElement;
}
return dom.Utils.getOutputTarget(target);
}
/**
* Function keyDown
* Scope Public
* Description Processes keydown event and passes data to keyboard.
*
* Note that the test-case oriented 'recorder' stubs this method to facilitate keystroke
* recording for use in test cases. If changing this function, please ensure the recorder is
* not affected.
*/
static keyDown(e: KeyboardEvent): boolean {
let core = com.keyman.singleton.core;
DOMEventHandlers.states.swallowKeypress = false;
// Get event properties
var Levent = this._GetKeyEventProperties(e, true);
if(Levent == null) {
return true;
}
let outputTarget = PreProcessor.getEventOutputTarget(e);
const ruleBehavior = core.processKeyEvent(Levent, outputTarget);
const LeventMatched = ruleBehavior && !ruleBehavior.triggerKeyDefault;
if(LeventMatched) {
if(e && e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}
DOMEventHandlers.states.swallowKeypress = !!Levent.Lcode;
// Don't swallow backspaces on keypresses; this allows physical BKSP presses to repeat.
if(Levent.Lcode == 8) {
DOMEventHandlers.states.swallowKeypress = false;
}
} else {
DOMEventHandlers.states.swallowKeypress = false;
}
return !LeventMatched;
}
// KeyUp basically exists for two purposes:
// 1) To detect browser form submissions (handled in kmwdomevents.ts)
// 2) To detect modifier state changes.
static keyUp(e: KeyboardEvent): boolean {
let core = com.keyman.singleton.core;
var Levent = this._GetKeyEventProperties(e, false);
if(Levent == null) {
return true;
}
let outputTarget = PreProcessor.getEventOutputTarget(e);
return core.keyboardProcessor.doModifierPress(Levent, outputTarget, false);
}
static keyPress(e: KeyboardEvent): boolean {
let keyman = com.keyman.singleton;
let core = keyman.core;
var Levent = this._GetKeyEventProperties(e);
if(Levent == null || Levent.LisVirtualKey) {
return true;
}
// _Debug('KeyPress code='+Levent.Lcode+'; Ltarg='+Levent.Ltarg.tagName+'; LisVirtualKey='+Levent.LisVirtualKey+'; _KeyPressToSwallow='+keymanweb._KeyPressToSwallow+'; keyCode='+(e?e.keyCode:'nothing'));
/* I732 START - 13/03/2007 MCD: Swedish: Start positional keyboard layout code: prevent keystroke */
if(!core.activeKeyboard.isMnemonic) {
if(!DOMEventHandlers.states.swallowKeypress) {
return true;
}
if(Levent.Lcode < 0x20 || ((<any>keyman)._BrowserIsSafari && (Levent.Lcode > 0xF700 && Levent.Lcode < 0xF900))) {
return true;
}
return false;
}
/* I732 END - 13/03/2007 MCD: Swedish: End positional keyboard layout code */
// Only reached if it's a mnemonic keyboard.
let outputTarget = PreProcessor.getEventOutputTarget(e);
if(DOMEventHandlers.states.swallowKeypress || core.keyboardInterface.processKeystroke(outputTarget, Levent)) {
DOMEventHandlers.states.swallowKeypress = false;
if(e && e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}
return false;
}
DOMEventHandlers.states.swallowKeypress = false;
return true;
}
}
}