254 lines
7.9 KiB
JavaScript
254 lines
7.9 KiB
JavaScript
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);
|
|
}
|
|
|
|
// on Mac, Option (AKA Alt) is used as a char modifier
|
|
var charModifierListMac = [0xffe9];
|
|
// on Windows, Ctrl+Alt is used as a char modifier
|
|
var charModifierListWin = [0xffe9, 0xffe3];
|
|
// on Linux, AltGr is used as a char modifier
|
|
var charModifierListLinux = [0xfe03]
|
|
|
|
// 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 == []) { 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) {
|
|
if (!charModifier) {
|
|
if (isMac()) {
|
|
charModifier = charModifierListMac;
|
|
}
|
|
else if (isWindows()) {
|
|
charModifier = charModifierListWin;
|
|
}
|
|
else if (isLinux()) {
|
|
charModifier = charModifierListLinux;
|
|
}
|
|
else {
|
|
charModifier = [];
|
|
}
|
|
}
|
|
var ctrl = 0xffe3;
|
|
var alt = 0xffe9;
|
|
var altGr = 0xfe03;
|
|
var shift = 0xffe1;
|
|
var meta = 0xffe7;
|
|
|
|
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){
|
|
if (evt.char && evt.char.length === 1) {
|
|
var codepoint = evt.char.charCodeAt();
|
|
var res = keysyms.fromUnicode(codepoint);
|
|
if (res) {
|
|
return res;
|
|
}
|
|
}
|
|
if (evt.charCode) {
|
|
var res = keysyms.fromUnicode(evt.charCode);
|
|
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;
|
|
}
|
|
}
|