// Establishes key-code definitions. /// // Defines our generalized "KeyEvent" class. /// namespace com.keyman.text { export class LegacyKeyEvent { Ltarg: HTMLElement; Lcode: number; Lmodifiers: number; LisVirtualKey: number; } export class Processor { // 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. stateKeys = { "K_CAPS":false, "K_NUMLOCK":false, "K_SCROLL":false }; // 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. modStateFlags: number = 0; // Denotes whether or not KMW needs to 'swallow' the next keypress. swallowKeypress: boolean = false; /** * Get the default key string. If keyName is U_xxxxxx, use that Unicode codepoint. * Otherwise, lookup the virtual key code (physical keyboard mapping) * * @param {object} Lkc The pre-analyzed key event object * @param {number} keyShiftState * @param {boolean} usingOSK * @return {string} */ defaultKeyOutput(Lkc: KeyEvent, keyShiftState: number, usingOSK: boolean, disableDOM: boolean): string { var Lkc: KeyEvent; let keyName = Lkc.kName; let n = Lkc.Lcode; let Lelem = Lkc.Ltarg; let keyman = com.keyman.singleton; let domManager = keyman.domManager; let activeKeyboard = keyman.keyboardManager.activeKeyboard; var ch = '', checkCodes = false; var touchAlias = (Lelem && typeof(Lelem.base) != 'undefined'); // check if exact match to SHIFT's code. Only the 'default' and 'shift' layers should have default key outputs. if(keyShiftState == 0) { checkCodes = true; } else if (keyShiftState == Codes.modifierCodes['SHIFT']) { checkCodes = true; keyShiftState = 1; // It's used as an index. } else { console.warn("KMW only defines default key output for the 'default' and 'shift' layers!"); } // If this was triggered by the OSK -or- if it was triggered within a touch-aliased DIV element. if(touchAlias || usingOSK) { var code = Codes.keyCodes[keyName]; if(!code) { code = n; } switch(code) { case Codes.keyCodes['K_BKSP']: //Only desktop UI, not touch devices. TODO: add repeat while mouse down for desktop UI if(disableDOM) { return '\b'; // the escape sequence for backspace. } else { keyman.interface.defaultBackspace(); } return ''; case Codes.keyCodes['K_TAB']: if(!disableDOM) { domManager.moveToNext(keyShiftState); } break; case Codes.keyCodes['K_TABBACK']: if(!disableDOM) { domManager.moveToNext(true); } break; case Codes.keyCodes['K_TABFWD']: if(!disableDOM) { domManager.moveToNext(false); } break; case Codes.keyCodes['K_ENTER']: // Insert new line in text area fields if(Lelem.nodeName == 'TEXTAREA' || (typeof Lelem.base != 'undefined' && Lelem.base.nodeName == 'TEXTAREA')) { return '\n'; // Or move to next field from TEXT fields } else if(usingOSK) { var inputEle: HTMLInputElement; if(keyman.isEmbedded) { // In embedded mode, the OutputTarget may not meet other conditions. return '\n'; } else if(dom.Utils.instanceof(Lelem, "HTMLInputElement")) { inputEle = Lelem; } else if(typeof(Lelem.base) != 'undefined' && dom.Utils.instanceof(Lelem.base, "HTMLInputElement")) { inputEle = Lelem.base; } if(!disableDOM) { if (inputEle && (inputEle.type == 'search' || inputEle.type == 'submit')) { inputEle.disabled=false; inputEle.form.submit(); } else { domManager.moveToNext(false); } } } break; case Codes.keyCodes['K_SPACE']: return ' '; // break; // // // Problem: clusters, and doing them right. // // The commented-out code below should be a decent starting point, but clusters make it complex. // // case VisualKeyboard.keyCodes['K_LEFT']: // if(touchAlias) { // var caretPos = keymanweb.getTextCaret(Lelem); // keymanweb.setTextCaret(Lelem, caretPos - 1 >= 0 ? caretPos - 1 : 0); // } // break; // case VisualKeyboard.keyCodes['K_RIGHT']: // if(touchAlias) { // var caretPos = keymanweb.getTextCaret(Lelem); // keymanweb.setTextCaret(Lelem, caretPos + 1); // } // if(code == VisualKeyboard.keyCodes['K_RIGHT']) { // break; // } // // Should we include this? It could be tricky to do correctly... // case VisualKeyboard.keyCodes['K_DEL']: // // Move caret right one unit, then backspace. // if(touchAlias) { // var caretPos = keymanweb.getTextCaret(Lelem); // keymanweb.setTextCaret(Lelem, caretPos + 1); // if(caretPos == keymanweb.getTextCaret(Lelem)) { // // Failed to move right - there's nothing to delete. // break; // } // kbdInterface.defaultBackspace(); // } } // Only desktop UI, not touch devices. TODO: add repeat while mouse down for desktop UI // // Can easily occur from mnemonic keyboards, which create synthetic events without // the appropriate kName value. // // Not strictly if `Lkc.vkCode` is properly maintained, but it's good to have an // extra safety; this would have blocked the backspace bug as well. } else if(Lkc.Lcode == 8) { if(disableDOM) { return '\b'; // the escape sequence for backspace. } else { keyman.interface.defaultBackspace(); return ''; } } // Translate numpad keystrokes into their non-numpad equivalents // TODO: Make part of 'defaultKeyOutput'. if(Lkc.Lcode >= Codes.keyCodes["K_NP0"] && Lkc.Lcode <= Codes.keyCodes["K_NPSLASH"] && activeKeyboard && !activeKeyboard['KM']) { // Number pad, numlock on if(Lkc.Lcode < 106) { var Lch = Lkc.Lcode-48; } else { Lch = Lkc.Lcode-64; } ch = String._kmwFromCharCode(Lch); //I3319 return ch; } // TODO: Refactor the overloading of the 'n' parameter here into separate methods. // Test for fall back to U_xxxxxx key id // For this first test, we ignore the keyCode and use the keyName if(keyName && keyName.substr(0,2) == 'U_') { var codePoint = parseInt(keyName.substr(2,6), 16); if (((0x0 <= codePoint) && (codePoint <= 0x1F)) || ((0x80 <= codePoint) && (codePoint <= 0x9F))) { // Code points [U_0000 - U_001F] and [U_0080 - U_009F] refer to Unicode C0 and C1 control codes. // Check the codePoint number and do not allow output of these codes via U_xxxxxx shortcuts. console.log("Suppressing Unicode control code: U_00" + codePoint.toString(16)); return ch; } else { // String.fromCharCode() is inadequate to handle the entire range of Unicode // Someday after upgrading to ES2015, can use String.fromCodePoint() ch=String.kmwFromCharCode(codePoint); } // Hereafter, we refer to keyCodes. } else if(checkCodes) { // keyShiftState can only be '1' or '2'. try { if(n >= Codes.keyCodes['K_0'] && n <= Codes.keyCodes['K_9']) { // The number keys. ch = Codes.codesUS[keyShiftState][0][n-Codes.keyCodes['K_0']]; } else if(n >= Codes.keyCodes['K_A'] && n <= Codes.keyCodes['K_Z']) { // The base letter keys ch = String.fromCharCode(n+(keyShiftState?0:32)); // 32 is the offset from uppercase to lowercase. } else if(n >= Codes.keyCodes['K_COLON'] && n <= Codes.keyCodes['K_BKQUOTE']) { ch = Codes.codesUS[keyShiftState][1][n-Codes.keyCodes['K_COLON']]; } else if(n >= Codes.keyCodes['K_LBRKT'] && n <= Codes.keyCodes['K_QUOTE']) { ch = Codes.codesUS[keyShiftState][2][n-Codes.keyCodes['K_LBRKT']]; } } catch (e) { console.error("Error detected with default mapping for key: code = " + n + ", shift state = " + (keyShiftState == 1 ? 'shift' : 'default')); } } return ch; } static getOutputTarget(Lelem?: HTMLElement): OutputTarget { let keyman = com.keyman.singleton; if(!Lelem && !keyman.isHeadless) { Lelem = keyman.domManager.getLastActiveElement(); if(!Lelem) { // If we're trying to find an active target but one doesn't exist, just return null. return null; } } // If we were provided an element or found an active element but it's improperly attached, that should cause an error. if(Lelem._kmwAttachment && Lelem._kmwAttachment.interface) { return Lelem._kmwAttachment.interface; } else { throw new Error("OSK could not find element output target data!"); } } _GetClickEventProperties(e: osk.ActiveKey, Lelem: HTMLElement): KeyEvent { let keyman = com.keyman.singleton; var activeKeyboard = keyman.keyboardManager.activeKeyboard; let formFactor = keyman.util.device.formFactor; // Get key name and keyboard shift state (needed only for default layouts and physical keyboard handling) // Note - virtual keys should be treated case-insensitive, so we force uppercasing here. var layer = e.layer || e.displayLayer || '', keyName=e.id.toUpperCase(); // Start: mirrors _GetKeyEventProperties // Override key shift state if specified for key in layout (corrected for popup keys KMEW-93) var keyShiftState = this.getModifierState(layer); // First check the virtual key, and process shift, control, alt or function keys var Lkc: KeyEvent = { Ltarg: Lelem, Lmodifiers: keyShiftState, Lstates: 0, Lcode: Codes.keyCodes[keyName], LisVirtualKey: true, vkCode: 0, kName: keyName, kLayer: layer, kbdLayer: e.displayLayer, kNextLayer: e.nextlayer }; // If it's actually a state key modifier, trigger its effects immediately, as KeyboardEvents would do the same. switch(keyName) { case 'K_CAPS': case 'K_NUMLOCK': case 'K_SCROLL': this.stateKeys[keyName] = ! this.stateKeys[keyName]; } // Performs common pre-analysis for both 'native' and 'embedded' OSK key & subkey input events. this.commonClickEventPreprocessing(Lkc); // End - mirrors _GetKeyEventProperties // Include *limited* support for mnemonic keyboards (Sept 2012) // If a touch layout has been defined for a mnemonic keyout, do not perform mnemonic mapping for rules on touch devices. if(activeKeyboard && activeKeyboard['KM'] && !(activeKeyboard['KVKL'] && formFactor != 'desktop')) { if(Lkc.Lcode != Codes.keyCodes['K_SPACE']) { // exception required, March 2013 // Jan 2019 - interesting that 'K_SPACE' also affects the caps-state check... Lkc.vkCode = Lkc.Lcode; this.setMnemonicCode(Lkc, layer.indexOf('shift') != -1, this.stateKeys['K_CAPS']); } } else { Lkc.vkCode=Lkc.Lcode; } // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic if(typeof activeKeyboard['KM'] == 'undefined') { Lkc.Lcode=keyman.keyMapManager._USKeyCodeToCharCode(Lkc); Lkc.LisVirtualKey=false; } return Lkc; } commonClickEventPreprocessing(Lkc: KeyEvent) { // Set the flags for the state keys. Lkc.Lstates |= this.stateKeys['K_CAPS'] ? Codes.modifierCodes['CAPS'] : Codes.modifierCodes['NO_CAPS']; Lkc.Lstates |= this.stateKeys['K_NUMLOCK'] ? Codes.modifierCodes['NUM_LOCK'] : Codes.modifierCodes['NO_NUM_LOCK']; Lkc.Lstates |= this.stateKeys['K_SCROLL'] ? Codes.modifierCodes['SCROLL_LOCK'] : Codes.modifierCodes['NO_SCROLL_LOCK']; // Set LisVirtualKey to false to ensure that nomatch rule does fire for U_xxxx keys if(Lkc.kName.substr(0,2) == 'U_') { Lkc.LisVirtualKey=false; } // Get code for non-physical keys (T_KOKAI, U_05AB etc) if(typeof Lkc.Lcode == 'undefined') { Lkc.Lcode = this.getVKDictionaryCode(Lkc.kName);// Updated for Build 347 if(!Lkc.Lcode) { // Special case for U_xxxx keys. This vk code will never be used // in a keyboard, so we use this to ensure that keystroke processing // occurs for the key. Lkc.Lcode = 1; } } // Handles modifier states when the OSK is emulating rightalt through the leftctrl-leftalt layer. if((Lkc.Lmodifiers & Codes.modifierBitmasks['ALT_GR_SIM']) == Codes.modifierBitmasks['ALT_GR_SIM'] && osk.Layouts.emulatesAltGr()) { Lkc.Lmodifiers &= ~Codes.modifierBitmasks['ALT_GR_SIM']; Lkc.Lmodifiers |= Codes.modifierCodes['RALT']; } } processKeystroke(keyEvent: KeyEvent, outputTarget: OutputTarget, fromOSK: boolean, disableDOM: boolean): boolean { let keyman = com.keyman.singleton; var activeKeyboard = keyman.keyboardManager.activeKeyboard; let kbdInterface = keyman.interface; let keyMapManager = keyman.keyMapManager; if(!keyman.isEmbedded && !fromOSK && keyman.util.device.browser == 'firefox') { // I1466 - Convert the - keycode on mnemonic as well as positional layouts // FireFox, Mozilla Suite if(keyMapManager.browserMap.FF['k'+keyEvent.Lcode]) { keyEvent.Lcode=keyMapManager.browserMap.FF['k'+keyEvent.Lcode]; } } var LeventMatched = 0; this.swallowKeypress = false; // Pass this key code and state to the keyboard program if(activeKeyboard && keyEvent.Lcode != 0) { LeventMatched = LeventMatched || kbdInterface.processKeystroke(fromOSK ? keyman.util.device : keyman.util.physicalDevice, outputTarget, keyEvent); } if(!LeventMatched) { // Restore the virtual key code if a mnemonic keyboard is being used // If no vkCode value was stored, maintain the original Lcode value. keyEvent.Lcode=keyEvent.vkCode || keyEvent.Lcode; // Handle unmapped keys, including special keys // The following is physical layout dependent, so should be avoided if possible. All keys should be mapped. var ch = this.defaultKeyOutput(keyEvent, keyEvent.Lmodifiers, true, disableDOM); if(ch) { if(ch == '\b') { // Is only returned when disableDOM is true, which prevents automatic default backspace application. // defaultKeyOutput can't always find the outputTarget if we're working with alternates! kbdInterface.defaultBackspace(outputTarget); } else { kbdInterface.output(0, outputTarget, ch); } LeventMatched = 1; } else if(keyEvent.Lcode == 8) { // Backspace LeventMatched = 1; } } return LeventMatched == 1; } /** * Simulate a keystroke according to the touched keyboard button element * * Handles default output and keyboard processing for both OSK and physical keystrokes. * * @param {Object} e The abstracted KeyEvent to use for keystroke processing */ processKeyEvent(keyEvent: KeyEvent, e?: osk.KeyElement | boolean): boolean { let keyman = com.keyman.singleton; let fromOSK = !!e; // If specified, it's from the OSK. // Enables embedded-path OSK sourcing detection. if(typeof e == 'boolean') { e = null as osk.KeyElement; // Cast is necessary for TS type-checking later in the method. } let formFactor = keyman.util.device.formFactor; let keyMapManager = keyman.keyMapManager; this.swallowKeypress = false; if(fromOSK && !keyman.isEmbedded) { keyman.domManager.initActiveElement(keyEvent.Ltarg); // Turn off key highlighting (or preview) keyman['osk'].vkbd.highlightKey(e,false); } // Exclude menu and OSK hide keys from normal click processing if(keyEvent.kName == 'K_LOPT' || keyEvent.kName == 'K_ROPT') { keyman['osk'].vkbd.optionKey(e, keyEvent.kName, true); return true; } // The default OSK layout for desktop devices does not include nextlayer info, relying on modifier detection here. // It's the OSK equivalent to doModifierPress on 'desktop' form factors. if((formFactor == 'desktop' || keyman.keyboardManager.layoutIsDesktopBased()) && fromOSK) { // If it's a desktop OSK style and this triggers a layer change, // a modifier key was clicked. No output expected, so it's safe to instantly exit. if(this.selectLayer(keyEvent.kName, keyEvent.kNextLayer)) { return true; } } // Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version // of state management. if(!fromOSK && this.doModifierPress(keyEvent, !fromOSK)) { return true; } if(!keyman.isEmbedded && !fromOSK && keyman.util.device.browser == 'firefox') { // I1466 - Convert the - keycode on mnemonic as well as positional layouts // FireFox, Mozilla Suite if(keyMapManager.browserMap.FF['k'+keyEvent.Lcode]) { keyEvent.Lcode=keyMapManager.browserMap.FF['k'+keyEvent.Lcode]; } } //else //{ // Safari, IE, Opera? //} // If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke. // If a suggestion was just accepted AND backspace is pressed, revert the change and do not process the backspace. // We check the first condition here, while the prediction UI handles the second through the try__() methods below. if(keyman.modelManager.enabled) { // The following code relies on JS's logical operator "short-circuit" properties to prevent unwanted triggering of the second condition. // Can the suggestion UI revert a recent suggestion? If so, do that and swallow the backspace. if((keyEvent.kName == "K_BKSP" || keyEvent.Lcode == Codes.keyCodes["K_BKSP"]) && keyman.modelManager.tryRevertSuggestion()) { return; // Can the suggestion UI accept an existing suggestion? If so, do that and swallow the space character. } else if((keyEvent.kName == "K_SPACE" || keyEvent.Lcode == Codes.keyCodes["K_SPACE"]) && keyman.modelManager.tryAcceptSuggestion('space')) { return; } } if(fromOSK && !keyman.isEmbedded) { keyman.uiManager.setActivatingUI(true); com.keyman.DOMEventHandlers.states._IgnoreNextSelChange = 100; keyman.domManager.focusLastActiveElement(); com.keyman.DOMEventHandlers.states._IgnoreNextSelChange = 0; } // // ...end I3363 (Build 301) // Determine the current target for text output and create a "mock" backup // of its current, pre-input state. let outputTarget = Processor.getOutputTarget(keyEvent.Ltarg); let preInputMock = Mock.from(outputTarget); let LeventMatched = this.processKeystroke(keyEvent, outputTarget, fromOSK, false); // Swap layer as appropriate. if(keyEvent.kNextLayer) { this.selectLayer(keyEvent.kName, keyEvent.kNextLayer); } // Should we swallow any further processing of keystroke events for this keydown-keypress sequence? if(LeventMatched) { let alternates: Alternate[]; // Note - we don't yet do fat-fingering with longpress keys. if(keyEvent.keyDistribution && keyEvent.kbdLayer) { let activeLayout = keyman['osk'].vkbd.layout as osk.ActiveLayout; alternates = []; for(let pair of keyEvent.keyDistribution) { let mock = Mock.from(preInputMock); let altKey = activeLayout.getLayer(keyEvent.kbdLayer).getKey(pair.keyId); if(!altKey) { console.warn("Potential fat-finger key could not be found in layer!"); continue; } let altEvent = this._GetClickEventProperties(altKey, keyEvent.Ltarg); if(this.processKeystroke(altEvent, mock, fromOSK, true)) { alternates.push({sample: mock.buildTransformFrom(preInputMock), 'p': pair.p}); } } } // Notify the ModelManager of new input let transcription = outputTarget.buildTranscriptionFrom(preInputMock, keyEvent, alternates); keyman.modelManager.predict(transcription); // Since this method now performs changes for 'default' keystrokes, synthetic 'change' event generation // belongs here, rather than only in interface.processKeystroke() as in versions pre-12. if(outputTarget.getElement()) { keyman['interface'].doInputEvent(outputTarget.getElement()); } this.swallowKeypress = (e && keyEvent.Lcode != 8 ? keyEvent.Lcode != 0 : false); if(keyEvent.Lcode == 8) { this.swallowKeypress = false; } return false; } else { this.swallowKeypress = false; } /* I732 END - 13/03/2007 MCD: End Positional Layout support in OSK */ if(fromOSK && !keyman.isEmbedded) { keyman.uiManager.setActivatingUI(false); // I2498 - KeymanWeb OSK does not accept clicks in FF when using automatic UI } return !LeventMatched; } /** * Simulate a keystroke according to the touched keyboard button element * * Note that the test-case oriented 'recorder' stubs this method to facilitate OSK-based input * recording for use in test cases. If changing this function, please ensure the recorder is * not affected. * * @param {Object} e element touched (or clicked) */ clickKey(e: osk.KeyElement, touch?: Touch, layerId?: string, keyDistribution?: KeyDistribution) { let keyman = com.keyman.singleton; var Lelem = keyman.domManager.getLastActiveElement(); if(Lelem != null) { // Handle any DOM state management related to click inputs. let outputTarget = Processor.getOutputTarget(Lelem); keyman.domManager.initActiveElement(Lelem); // Turn off key highlighting (or preview) keyman['osk'].vkbd.highlightKey(e,false); // Clear any cached codepoint data; we can rebuild it if it's unchanged. outputTarget.invalidateSelection(); // Deadkey matching continues to be troublesome. // Deleting matched deadkeys here seems to correct some of the issues. (JD 6/6/14) outputTarget.deadkeys().deleteMatched(); // Delete any matched deadkeys before continuing let Lkc = this._GetClickEventProperties(e['key'].spec as osk.ActiveKey, Lelem); if(keyman.modelManager.enabled) { Lkc.source = touch; Lkc.keyDistribution = keyDistribution; } return this.processKeyEvent(Lkc, e); } else { return true; } } // FIXME: makes some bad assumptions. setMnemonicCode(Lkc: KeyEvent, shifted: boolean, capsActive: boolean) { // K_SPACE is not handled by defaultKeyOutput for physical keystrokes unless using touch-aliased elements. // It's also a "exception required, March 2013" for clickKey, so at least they both have this requirement. if(Lkc.Lcode != Codes.keyCodes['K_SPACE']) { // So long as the key name isn't prefixed with 'U_', we'll get a default mapping based on the Lcode value. // We need to determine the mnemonic base character - for example, SHIFT + K_PERIOD needs to map to '>'. let mappingEvent: KeyEvent = new KeyEvent(); for(var key in Lkc) { mappingEvent[key] = Lkc[key]; } // To facilitate storing relevant commands, we should probably reverse-lookup // the actual keyname instead. mappingEvent.kName = 'K_xxxx'; mappingEvent.Ltarg = null; var mappedChar: string = this.defaultKeyOutput(mappingEvent, (shifted ? 0x10 : 0), false, true); /* First, save a backup of the original code. This one won't needlessly trigger keyboard * rules, but allows us to replicate/emulate commands after rule processing if needed. * (Like backspaces) */ Lkc.vkCode = Lkc.Lcode; if(mappedChar) { // Will return 96 for 'a', which is a keycode corresponding to Codes.keyCodes('K_NP1') - a numpad key. // That stated, we're in mnemonic mode - this keyboard's rules are based on the char codes. Lkc.Lcode = mappedChar.charCodeAt(0); } else { // Don't let command-type keys (like K_DEL, which will output '.' otherwise!) // trigger keyboard rules. delete Lkc.Lcode; } } if(capsActive) { // TODO: Needs fixing - does not properly mirror physical keystrokes, as Lcode range 96-111 corresponds // to numpad keys! (Physical keyboard section has its own issues here.) if((Lkc.Lcode >= 65 && Lkc.Lcode <= 90) /* 'A' - 'Z' */ || (Lkc.Lcode >= 97 && Lkc.Lcode <= 122) /* 'a' - 'z' */) { Lkc.Lmodifiers ^= 0x10; // Flip the 'shifted' bit, so it'll act as the opposite key. Lkc.Lcode ^= 0x20; // Flips the 'upper' vs 'lower' bit for the base 'a'-'z' ASCII alphabetics. } } } /** * Get modifier key state from layer id * * @param {string} layerId layer id (e.g. ctrlshift) * @return {number} modifier key state (desktop keyboards) */ getModifierState(layerId: string): number { var modifier=0; if(layerId.indexOf('shift') >= 0) { modifier |= Codes.modifierCodes['SHIFT']; } // The chiral checks must not be directly exclusive due each other to visual OSK feedback. var ctrlMatched=false; if(layerId.indexOf('leftctrl') >= 0) { modifier |= Codes.modifierCodes['LCTRL']; ctrlMatched=true; } if(layerId.indexOf('rightctrl') >= 0) { modifier |= Codes.modifierCodes['RCTRL']; ctrlMatched=true; } if(layerId.indexOf('ctrl') >= 0 && !ctrlMatched) { modifier |= Codes.modifierCodes['CTRL']; } var altMatched=false; if(layerId.indexOf('leftalt') >= 0) { modifier |= Codes.modifierCodes['LALT']; altMatched=true; } if(layerId.indexOf('rightalt') >= 0) { modifier |= Codes.modifierCodes['RALT']; altMatched=true; } if(layerId.indexOf('alt') >= 0 && !altMatched) { modifier |= Codes.modifierCodes['ALT']; } return modifier; } /** * @summary Look up a custom virtual key code in the virtual key code dictionary KVKD. On first run, will build the dictionary. * * `VKDictionary` is constructed from the keyboard's `KVKD` member. This list is constructed * at compile-time and is a list of 'additional' virtual key codes, starting at 256 (i.e. * outside the range of standard virtual key codes). These additional codes are both * `[T_xxx]` and `[U_xxxx]` custom key codes from the Keyman keyboard language. However, * `[U_xxxx]` keys only generate an entry in `KVKD` if there is a corresponding rule that * is associated with them in the keyboard rules. If the `[U_xxxx]` key code is only * referenced as the id of a key in the touch layout, then it does not get an entry in * the `KVKD` property. * * @private * @param {string} keyName custom virtual key code to lookup in the dictionary * @return {number} key code > 255 on success, or 0 if not found */ getVKDictionaryCode(keyName: string) { let keyman = com.keyman.singleton; var activeKeyboard = keyman.keyboardManager.activeKeyboard; if(!activeKeyboard['VKDictionary']) { var a=[]; if(typeof activeKeyboard['KVKD'] == 'string') { // Build the VK dictionary // TODO: Move the dictionary build into the compiler -- so compiler generates code such as following. // Makes the VKDictionary member unnecessary. // this.KVKD={"K_ABC":256,"K_DEF":257,...}; var s=activeKeyboard['KVKD'].split(' '); for(var i=0; i} KMW keyboard event object: * Description Get object with target element, key code, shift state, virtual key state * Ltarg=target element * Lcode=keyCode * Lmodifiers=shiftState * LisVirtualKeyCode e.g. ctrl/alt key * LisVirtualKey e.g. Virtual key or non-keypress event */ _GetKeyEventProperties(e: KeyboardEvent, keyState?: boolean): KeyEvent { let keyman = com.keyman.singleton; var s = new KeyEvent(); e = keyman._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(e.cancelBubble === true) { return null; // I2457 - Facebook meta-event generation mess -- two events generated for a keydown in Facebook contentEditable divs } s.Ltarg = keyman.util.eventTarget(e) as HTMLElement; if (s.Ltarg == null) { return null; } else if (s.Ltarg.nodeType == 3) {// defeat Safari bug s.Ltarg = s.Ltarg.parentNode as HTMLElement; } s.Lcode = this._GetEventKeyCode(e); if (s.Lcode == null) { return null; } // Stage 1 - track the true state of the keyboard's modifiers. var prevModState = this.modStateFlags, curModState = 0x0000; var ctrlEvent = false, altEvent = false; let keyCodes = 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 = 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') || e.getModifierState("Scroll")) // "Scroll" for IE9. ? 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 = this.modStateFlags != curModState; this.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 = Codes.modifierBitmasks; // Stage 4 - map the modifier set to the appropriate keystroke's modifiers. if(keyman.keyboardManager.isChiral()) { s.Lmodifiers = curModState & modifierBitmasks.CHIRAL; // Note for future - embedding a kill switch here or in keymanweb.osk.emulatesAltGr would facilitate disabling // AltGr / Right-alt simulation. if(osk.Layouts.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); } // Mnemonic handling. var activeKeyboard = keyman.keyboardManager.activeKeyboard; if(activeKeyboard && activeKeyboard['KM']) { // 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. this.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'; let keyMapManager = keyman.keyMapManager; // Other minor physical-keyboard adjustments if(activeKeyboard && !activeKeyboard['KM']) { // Positional Layout /* 13/03/2007 MCD: Swedish: Start mapping of keystroke to US keyboard */ var Lbase=keyMapManager.languageMap[com.keyman.osk.Layouts._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(typeof(activeKeyboard['KM'])=='undefined' && !(s.Lmodifiers & 0x60)) { // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic s = { Lcode: keyMapManager._USKeyCodeToCharCode(s), Ltarg: s.Ltarg, Lmodifiers: 0, LisVirtualKey: false, vkCode: s.Lcode, // Helps to merge OSK and physical keystroke control paths. Lstates: s.Lstates, kName: '' }; } } return s; } /** * 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. */ keyDown(e: KeyboardEvent): boolean { this.swallowKeypress = false; // Get event properties var Levent = this._GetKeyEventProperties(e, true); if(Levent == null) { return true; } var LeventMatched = !this.processKeyEvent(Levent); if(LeventMatched) { if(e && e.preventDefault) { e.preventDefault(); e.stopPropagation(); } } return !LeventMatched; } // KeyUp basically exists for two purposes: // 1) To detect browser form submissions (handled in kmwdomevents.ts) // 2) To detect modifier state changes. keyUp(e: KeyboardEvent): boolean { var Levent = this._GetKeyEventProperties(e, false); if(Levent == null) { return true; } return this.doModifierPress(Levent, false); } keyPress(e: KeyboardEvent): boolean { let keyman = com.keyman.singleton; 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(!keyman.keyboardManager.activeKeyboard['KM']) { if(!this.swallowKeypress) { return true; } if(Levent.Lcode < 0x20 || ((keyman)._BrowserIsSafari && (Levent.Lcode > 0xF700 && Levent.Lcode < 0xF900))) { return true; } e = keyman._GetEventObject(e); // I2404 - Manage IE events in IFRAMEs if(e) { e.returnValue = false; } return false; } /* I732 END - 13/03/2007 MCD: Swedish: End positional keyboard layout code */ let outputTarget = Processor.getOutputTarget(Levent.Ltarg); // Only reached if it's a mnemonic keyboard. if(this.swallowKeypress || keyman['interface'].processKeystroke(keyman.util.physicalDevice, outputTarget, Levent)) { this.swallowKeypress = false; if(e && e.preventDefault) { e.preventDefault(); e.stopPropagation(); } return false; } this.swallowKeypress = false; return true; } } }