mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 16:35:33 +00:00
312 lines
No EOL
13 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
} |