|
@@ -0,0 +1,511 @@
|
|
|
+var kbdUtil = (function() {
|
|
|
+ "use strict";
|
|
|
+
|
|
|
+ function isMac() {
|
|
|
+ return navigator && !!(/macintosh/i).exec(navigator.appVersion);
|
|
|
+ }
|
|
|
+ function isWindows() {
|
|
|
+ return navigator && !!(/windows/i).exec(navigator.appVersion);
|
|
|
+ }
|
|
|
+ function isLinux() {
|
|
|
+ return navigator && !!(/linux/i).exec(navigator.appVersion);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Return true if a modifier which is not the specified char modifier (and is not shift) is down
|
|
|
+ function hasShortcutModifier(charModifier, currentModifiers) {
|
|
|
+ var mods = {};
|
|
|
+ for (var key in currentModifiers) {
|
|
|
+ if (key !== 0xffe1) {
|
|
|
+ mods[key] = currentModifiers[key];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var sum = 0;
|
|
|
+ for (var k in currentModifiers) {
|
|
|
+ if (mods[k]) {
|
|
|
+ ++sum;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (hasCharModifier(charModifier, mods)) {
|
|
|
+ return sum > charModifier.length;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return sum > 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Return true if the specified char modifier is currently down
|
|
|
+ function hasCharModifier(charModifier, currentModifiers) {
|
|
|
+ if (charModifier.length === 0) { return false; }
|
|
|
+
|
|
|
+ for (var i = 0; i < charModifier.length; ++i) {
|
|
|
+ if (!currentModifiers[charModifier[i]]) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Helper object tracking modifier key state
|
|
|
+ // and generates fake key events to compensate if it gets out of sync
|
|
|
+ function ModifierSync(charModifier) {
|
|
|
+ var ctrl = 0xffe3;
|
|
|
+ var alt = 0xffe9;
|
|
|
+ var altGr = 0xfe03;
|
|
|
+ var shift = 0xffe1;
|
|
|
+ var meta = 0xffe7;
|
|
|
+
|
|
|
+ if (!charModifier) {
|
|
|
+ if (isMac()) {
|
|
|
+ // on Mac, Option (AKA Alt) is used as a char modifier
|
|
|
+ charModifier = [alt];
|
|
|
+ }
|
|
|
+ else if (isWindows()) {
|
|
|
+ // on Windows, Ctrl+Alt is used as a char modifier
|
|
|
+ charModifier = [alt, ctrl];
|
|
|
+ }
|
|
|
+ else if (isLinux()) {
|
|
|
+ // on Linux, AltGr is used as a char modifier
|
|
|
+ charModifier = [altGr];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ charModifier = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var state = {};
|
|
|
+ state[ctrl] = false;
|
|
|
+ state[alt] = false;
|
|
|
+ state[altGr] = false;
|
|
|
+ state[shift] = false;
|
|
|
+ state[meta] = false;
|
|
|
+
|
|
|
+ function sync(evt, keysym) {
|
|
|
+ var result = [];
|
|
|
+ function syncKey(keysym) {
|
|
|
+ return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
|
|
|
+ }
|
|
|
+
|
|
|
+ if (evt.ctrlKey !== undefined && evt.ctrlKey !== state[ctrl] && keysym !== ctrl) {
|
|
|
+ state[ctrl] = evt.ctrlKey;
|
|
|
+ result.push(syncKey(ctrl));
|
|
|
+ }
|
|
|
+ if (evt.altKey !== undefined && evt.altKey !== state[alt] && keysym !== alt) {
|
|
|
+ state[alt] = evt.altKey;
|
|
|
+ result.push(syncKey(alt));
|
|
|
+ }
|
|
|
+ if (evt.altGraphKey !== undefined && evt.altGraphKey !== state[altGr] && keysym !== altGr) {
|
|
|
+ state[altGr] = evt.altGraphKey;
|
|
|
+ result.push(syncKey(altGr));
|
|
|
+ }
|
|
|
+ if (evt.shiftKey !== undefined && evt.shiftKey !== state[shift] && keysym !== shift) {
|
|
|
+ state[shift] = evt.shiftKey;
|
|
|
+ result.push(syncKey(shift));
|
|
|
+ }
|
|
|
+ if (evt.metaKey !== undefined && evt.metaKey !== state[meta] && keysym !== meta) {
|
|
|
+ state[meta] = evt.metaKey;
|
|
|
+ result.push(syncKey(meta));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ function syncKeyEvent(evt, down) {
|
|
|
+ var obj = getKeysym(evt);
|
|
|
+ var keysym = obj ? obj.keysym : null;
|
|
|
+
|
|
|
+ // first, apply the event itself, if relevant
|
|
|
+ if (keysym !== null && state[keysym] !== undefined) {
|
|
|
+ state[keysym] = down;
|
|
|
+ }
|
|
|
+ return sync(evt, keysym);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ // sync on the appropriate keyboard event
|
|
|
+ keydown: function(evt) { return syncKeyEvent(evt, true);},
|
|
|
+ keyup: function(evt) { return syncKeyEvent(evt, false);},
|
|
|
+ // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
|
|
|
+ syncAny: function(evt) { return sync(evt);},
|
|
|
+
|
|
|
+ // is a shortcut modifier down?
|
|
|
+ hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
|
|
|
+ // if a char modifier is down, return the keys it consists of, otherwise return null
|
|
|
+ activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get a key ID from a keyboard event
|
|
|
+ // May be a string or an integer depending on the available properties
|
|
|
+ function getKey(evt){
|
|
|
+ if (evt.key) {
|
|
|
+ return evt.key;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return evt.keyCode;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the most reliable keysym value we can get from a key event
|
|
|
+ // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
|
|
|
+ function getKeysym(evt){
|
|
|
+ var codepoint;
|
|
|
+ if (evt.char && evt.char.length === 1) {
|
|
|
+ codepoint = evt.char.charCodeAt();
|
|
|
+ }
|
|
|
+ else if (evt.charCode) {
|
|
|
+ codepoint = evt.charCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (codepoint) {
|
|
|
+ var res = keysyms.fromUnicode(codepoint);
|
|
|
+ if (res) {
|
|
|
+ return res;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // we could check evt.key here.
|
|
|
+ // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
|
|
|
+ // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
|
|
|
+ // so we don't *need* it yet
|
|
|
+ if (evt.keyCode) {
|
|
|
+ return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
|
|
|
+ }
|
|
|
+ if (evt.which) {
|
|
|
+ return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Given a keycode, try to predict which keysym it might be.
|
|
|
+ // If the keycode is unknown, null is returned.
|
|
|
+ function keysymFromKeyCode(keycode, shiftPressed) {
|
|
|
+ if (typeof(keycode) !== 'number') {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ // won't be accurate for azerty
|
|
|
+ if (keycode >= 0x30 && keycode <= 0x39) {
|
|
|
+ return keycode; // digit
|
|
|
+ }
|
|
|
+ if (keycode >= 0x41 && keycode <= 0x5a) {
|
|
|
+ // remap to lowercase unless shift is down
|
|
|
+ return shiftPressed ? keycode : keycode + 32; // A-Z
|
|
|
+ }
|
|
|
+ if (keycode >= 0x60 && keycode <= 0x69) {
|
|
|
+ return 0xffb0 + (keycode - 0x60); // numpad 0-9
|
|
|
+ }
|
|
|
+
|
|
|
+ switch(keycode) {
|
|
|
+ case 0x20: return 0x20; // space
|
|
|
+ case 0x6a: return 0xffaa; // multiply
|
|
|
+ case 0x6b: return 0xffab; // add
|
|
|
+ case 0x6c: return 0xffac; // separator
|
|
|
+ case 0x6d: return 0xffad; // subtract
|
|
|
+ case 0x6e: return 0xffae; // decimal
|
|
|
+ case 0x6f: return 0xffaf; // divide
|
|
|
+ case 0xbb: return 0x2b; // +
|
|
|
+ case 0xbc: return 0x2c; // ,
|
|
|
+ case 0xbd: return 0x2d; // -
|
|
|
+ case 0xbe: return 0x2e; // .
|
|
|
+ }
|
|
|
+
|
|
|
+ return nonCharacterKey({keyCode: keycode});
|
|
|
+ }
|
|
|
+
|
|
|
+ // if the key is a known non-character key (any key which doesn't generate character data)
|
|
|
+ // return its keysym value. Otherwise return null
|
|
|
+ function nonCharacterKey(evt) {
|
|
|
+ // evt.key not implemented yet
|
|
|
+ if (!evt.keyCode) { return null; }
|
|
|
+ var keycode = evt.keyCode;
|
|
|
+
|
|
|
+ if (keycode >= 0x70 && keycode <= 0x87) {
|
|
|
+ return 0xffbe + keycode - 0x70; // F1-F24
|
|
|
+ }
|
|
|
+ switch (keycode) {
|
|
|
+
|
|
|
+ case 8 : return 0xFF08; // BACKSPACE
|
|
|
+ case 13 : return 0xFF0D; // ENTER
|
|
|
+
|
|
|
+ case 9 : return 0xFF09; // TAB
|
|
|
+
|
|
|
+ case 27 : return 0xFF1B; // ESCAPE
|
|
|
+ case 46 : return 0xFFFF; // DELETE
|
|
|
+
|
|
|
+ case 36 : return 0xFF50; // HOME
|
|
|
+ case 35 : return 0xFF57; // END
|
|
|
+ case 33 : return 0xFF55; // PAGE_UP
|
|
|
+ case 34 : return 0xFF56; // PAGE_DOWN
|
|
|
+ case 45 : return 0xFF63; // INSERT
|
|
|
+
|
|
|
+ case 37 : return 0xFF51; // LEFT
|
|
|
+ case 38 : return 0xFF52; // UP
|
|
|
+ case 39 : return 0xFF53; // RIGHT
|
|
|
+ case 40 : return 0xFF54; // DOWN
|
|
|
+ case 16 : return 0xFFE1; // SHIFT
|
|
|
+ case 17 : return 0xFFE3; // CONTROL
|
|
|
+ case 18 : return 0xFFE9; // Left ALT (Mac Option)
|
|
|
+
|
|
|
+ case 224 : return 0xFE07; // Meta
|
|
|
+ case 225 : return 0xFE03; // AltGr
|
|
|
+ case 91 : return 0xFFEC; // Super_L (Win Key)
|
|
|
+ case 92 : return 0xFFED; // Super_R (Win Key)
|
|
|
+ case 93 : return 0xFF67; // Menu (Win Menu), Mac Command
|
|
|
+ default: return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ hasShortcutModifier : hasShortcutModifier,
|
|
|
+ hasCharModifier : hasCharModifier,
|
|
|
+ ModifierSync : ModifierSync,
|
|
|
+ getKey : getKey,
|
|
|
+ getKeysym : getKeysym,
|
|
|
+ keysymFromKeyCode : keysymFromKeyCode,
|
|
|
+ nonCharacterKey : nonCharacterKey
|
|
|
+ };
|
|
|
+})();
|
|
|
+
|
|
|
+// Takes a DOM keyboard event and:
|
|
|
+// - determines which keysym it represents
|
|
|
+// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
|
|
|
+// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
|
|
|
+// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
|
|
|
+// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
|
|
|
+// This information is collected into an object which is passed to the next() function. (one call per event)
|
|
|
+function KeyEventDecoder(modifierState, next) {
|
|
|
+ "use strict";
|
|
|
+ function sendAll(evts) {
|
|
|
+ for (var i = 0; i < evts.length; ++i) {
|
|
|
+ next(evts[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function process(evt, type) {
|
|
|
+ var result = {type: type};
|
|
|
+ var keyId = kbdUtil.getKey(evt);
|
|
|
+ if (keyId) {
|
|
|
+ result.keyId = keyId;
|
|
|
+ }
|
|
|
+
|
|
|
+ var keysym = kbdUtil.getKeysym(evt);
|
|
|
+
|
|
|
+ var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
|
|
|
+ // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
|
|
|
+ // "special" keys like enter, tab or backspace don't send keypress events,
|
|
|
+ // and some browsers don't send keypresses at all if a modifier is down
|
|
|
+ if (keysym && (type !== 'keydown' || kbdUtil.nonCharacterKey(evt) || hasModifier)) {
|
|
|
+ result.keysym = keysym;
|
|
|
+ }
|
|
|
+
|
|
|
+ var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
|
|
|
+
|
|
|
+ // Should we prevent the browser from handling the event?
|
|
|
+ // Doing so on a keydown (in most browsers) prevents keypress from being generated
|
|
|
+ // so only do that if we have to.
|
|
|
+ var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!kbdUtil.nonCharacterKey(evt));
|
|
|
+
|
|
|
+ // If a char modifier is down on a keydown, we need to insert a stall,
|
|
|
+ // so VerifyCharModifier knows to wait and see if a keypress is comnig
|
|
|
+ var stall = type === 'keydown' && modifierState.activeCharModifier() && !kbdUtil.nonCharacterKey(evt);
|
|
|
+
|
|
|
+ // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
|
|
+ var active = modifierState.activeCharModifier();
|
|
|
+
|
|
|
+ // If we have a char modifier down, and we're able to determine a keysym reliably
|
|
|
+ // then (a) we know to treat the modifier as a char modifier,
|
|
|
+ // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
|
|
|
+ if (active && keysym) {
|
|
|
+ var isCharModifier = false;
|
|
|
+ for (var i = 0; i < active.length; ++i) {
|
|
|
+ if (active[i] === keysym.keysym) {
|
|
|
+ isCharModifier = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === 'keypress' && !isCharModifier) {
|
|
|
+ result.escape = modifierState.activeCharModifier();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stall) {
|
|
|
+ // insert a fake "stall" event
|
|
|
+ next({type: 'stall'});
|
|
|
+ }
|
|
|
+ next(result);
|
|
|
+
|
|
|
+ return suppress;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ keydown: function(evt) {
|
|
|
+ sendAll(modifierState.keydown(evt));
|
|
|
+ return process(evt, 'keydown');
|
|
|
+ },
|
|
|
+ keypress: function(evt) {
|
|
|
+ return process(evt, 'keypress');
|
|
|
+ },
|
|
|
+ keyup: function(evt) {
|
|
|
+ sendAll(modifierState.keyup(evt));
|
|
|
+ return process(evt, 'keyup');
|
|
|
+ },
|
|
|
+ syncModifiers: function(evt) {
|
|
|
+ sendAll(modifierState.syncAny(evt));
|
|
|
+ },
|
|
|
+ releaseAll: function() { next({type: 'releaseall'}); }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// Combines keydown and keypress events where necessary to handle char modifiers.
|
|
|
+// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
|
|
|
+// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
|
|
|
+// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
|
|
|
+// The only way we can distinguish these cases is to wait and see if a keypress event arrives
|
|
|
+// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
|
|
|
+function VerifyCharModifier(next) {
|
|
|
+ "use strict";
|
|
|
+ var queue = [];
|
|
|
+ var timer = null;
|
|
|
+ function process() {
|
|
|
+ if (timer) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ while (queue.length !== 0) {
|
|
|
+ var cur = queue[0];
|
|
|
+ queue = queue.splice(1);
|
|
|
+ switch (cur.type) {
|
|
|
+ case 'stall':
|
|
|
+ // insert a delay before processing available events.
|
|
|
+ timer = setTimeout(function() {
|
|
|
+ clearTimeout(timer);
|
|
|
+ timer = null;
|
|
|
+ process();
|
|
|
+ }, 5);
|
|
|
+ return;
|
|
|
+ case 'keydown':
|
|
|
+ // is the next element a keypress? Then we should merge the two
|
|
|
+ if (queue.length !== 0 && queue[0].type === 'keypress') {
|
|
|
+ // Firefox sends keypress even when no char is generated.
|
|
|
+ // so, if keypress keysym is the same as we'd have guessed from keydown,
|
|
|
+ // the modifier didn't have any effect, and should not be escaped
|
|
|
+ if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
|
|
|
+ cur.escape = queue[0].escape;
|
|
|
+ }
|
|
|
+ cur.keysym = queue[0].keysym;
|
|
|
+ queue = queue.splice(1);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // swallow stall events, and pass all others to the next stage
|
|
|
+ if (cur.type !== 'stall') {
|
|
|
+ next(cur);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return function(evt) {
|
|
|
+ queue.push(evt);
|
|
|
+ process();
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// Keeps track of which keys we (and the server) believe are down
|
|
|
+// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
|
|
|
+// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
|
|
|
+// key repeat events should be merged into a single entry.
|
|
|
+// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
|
|
|
+function TrackKeyState(next) {
|
|
|
+ "use strict";
|
|
|
+ var state = [];
|
|
|
+
|
|
|
+ return function (evt) {
|
|
|
+ var last = state.length !== 0 ? state[state.length-1] : null;
|
|
|
+
|
|
|
+ switch (evt.type) {
|
|
|
+ case 'keydown':
|
|
|
+ // insert a new entry if last seen key was different.
|
|
|
+ if (!last || !evt.keyId || last.keyId !== evt.keyId) {
|
|
|
+ last = {keyId: evt.keyId, keysyms: {}};
|
|
|
+ state.push(last);
|
|
|
+ }
|
|
|
+ if (evt.keysym) {
|
|
|
+ // make sure last event contains this keysym (a single "logical" keyevent
|
|
|
+ // can cause multiple key events to be sent to the VNC server)
|
|
|
+ last.keysyms[evt.keysym.keysym] = evt.keysym;
|
|
|
+ last.ignoreKeyPress = true;
|
|
|
+ next(evt);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'keypress':
|
|
|
+ if (!last) {
|
|
|
+ last = {keyId: evt.keyId, keysyms: {}};
|
|
|
+ state.push(last);
|
|
|
+ }
|
|
|
+ if (!evt.keysym) {
|
|
|
+ console.log('keypress with no keysym:', evt);
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we didn't expect a keypress, and already sent a keydown to the VNC server
|
|
|
+ // based on the keydown, make sure to skip this event.
|
|
|
+ if (evt.keysym && !last.ignoreKeyPress) {
|
|
|
+ last.keysyms[evt.keysym.keysym] = evt.keysym;
|
|
|
+ evt.type = 'keydown';
|
|
|
+ next(evt);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'keyup':
|
|
|
+ if (state.length === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var idx = null;
|
|
|
+ // do we have a matching key tracked as being down?
|
|
|
+ for (var i = 0; i !== state.length; ++i) {
|
|
|
+ if (state[i].keyId === evt.keyId) {
|
|
|
+ idx = i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // if we couldn't find a match (it happens), assume it was the last key pressed
|
|
|
+ if (idx === null) {
|
|
|
+ idx = state.length - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ var item = state.splice(idx, 1)[0];
|
|
|
+ // for each keysym tracked by this key entry, clone the current event and override the keysym
|
|
|
+ for (var key in item.keysyms) {
|
|
|
+ var clone = (function(){
|
|
|
+ function Clone(){}
|
|
|
+ return function (obj) { Clone.prototype=obj; return new Clone(); };
|
|
|
+ }());
|
|
|
+ var out = clone(evt);
|
|
|
+ out.keysym = item.keysyms[key];
|
|
|
+ next(out);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'releaseall':
|
|
|
+ for (var i = 0; i < state.length; ++i) {
|
|
|
+ for (var key in state[i].keysyms) {
|
|
|
+ var keysym = state[i].keysyms[key];
|
|
|
+ next({keyId: 0, keysym: keysym, type: 'keyup'});
|
|
|
+ }
|
|
|
+ }
|
|
|
+ state = [];
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
|
|
|
+// then the modifier must be "undone" before sending the @, and "redone" afterwards.
|
|
|
+function EscapeModifiers(next) {
|
|
|
+ "use strict";
|
|
|
+ return function(evt) {
|
|
|
+ if (evt.type !== 'keydown' || evt.escape === undefined) {
|
|
|
+ next(evt);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // undo modifiers
|
|
|
+ for (var i = 0; i < evt.escape.length; ++i) {
|
|
|
+ next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
|
|
|
+ }
|
|
|
+ // send the character event
|
|
|
+ next(evt);
|
|
|
+ // redo modifiers
|
|
|
+ for (var i = 0; i < evt.escape.length; ++i) {
|
|
|
+ next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|