257 lines
8.8 KiB
JavaScript
257 lines
8.8 KiB
JavaScript
import KeyTable from "./keysym.js";
|
|
import keysyms from "./keysymdef.js";
|
|
import vkeys from "./vkeys.js";
|
|
import fixedkeys from "./fixedkeys.js";
|
|
|
|
function isMac() {
|
|
return navigator && !!(/mac/i).exec(navigator.platform);
|
|
}
|
|
function isWindows() {
|
|
return navigator && !!(/win/i).exec(navigator.platform);
|
|
}
|
|
function isLinux() {
|
|
return navigator && !!(/linux/i).exec(navigator.platform);
|
|
}
|
|
|
|
// Return true if the specified char modifier is currently down
|
|
export 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
|
|
export function ModifierSync(charModifier) {
|
|
if (!charModifier) {
|
|
if (isMac()) {
|
|
// on Mac, Option (AKA Alt) is used as a char modifier
|
|
charModifier = [KeyTable.XK_Alt_L];
|
|
}
|
|
else if (isWindows()) {
|
|
// on Windows, Ctrl+Alt is used as a char modifier
|
|
charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L];
|
|
}
|
|
else if (isLinux()) {
|
|
// on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
|
|
charModifier = [KeyTable.XK_ISO_Level3_Shift];
|
|
}
|
|
else {
|
|
charModifier = [];
|
|
}
|
|
}
|
|
|
|
var state = {};
|
|
state[KeyTable.XK_Control_L] = false;
|
|
state[KeyTable.XK_Alt_L] = false;
|
|
state[KeyTable.XK_ISO_Level3_Shift] = false;
|
|
state[KeyTable.XK_Shift_L] = false;
|
|
state[KeyTable.XK_Meta_L] = false;
|
|
|
|
function sync(evt, keysym) {
|
|
var result = [];
|
|
function syncKey(keysym) {
|
|
return {keysym: keysym, type: state[keysym] ? 'keydown' : 'keyup'};
|
|
}
|
|
|
|
if (evt.ctrlKey !== undefined &&
|
|
evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) {
|
|
state[KeyTable.XK_Control_L] = evt.ctrlKey;
|
|
result.push(syncKey(KeyTable.XK_Control_L));
|
|
}
|
|
if (evt.altKey !== undefined &&
|
|
evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) {
|
|
state[KeyTable.XK_Alt_L] = evt.altKey;
|
|
result.push(syncKey(KeyTable.XK_Alt_L));
|
|
}
|
|
if (evt.altGraphKey !== undefined &&
|
|
evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) {
|
|
state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey;
|
|
result.push(syncKey(KeyTable.XK_ISO_Level3_Shift));
|
|
}
|
|
if (evt.shiftKey !== undefined &&
|
|
evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) {
|
|
state[KeyTable.XK_Shift_L] = evt.shiftKey;
|
|
result.push(syncKey(KeyTable.XK_Shift_L));
|
|
}
|
|
if (evt.metaKey !== undefined &&
|
|
evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) {
|
|
state[KeyTable.XK_Meta_L] = evt.metaKey;
|
|
result.push(syncKey(KeyTable.XK_Meta_L));
|
|
}
|
|
return result;
|
|
}
|
|
function syncKeyEvent(evt, down) {
|
|
var keysym = getKeysym(evt);
|
|
|
|
// 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);},
|
|
|
|
// if a char modifier is down, return the keys it consists of, otherwise return null
|
|
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
|
|
};
|
|
}
|
|
|
|
// Get 'KeyboardEvent.code', handling legacy browsers
|
|
export function getKeycode(evt){
|
|
// Are we getting proper key identifiers?
|
|
// (unfortunately Firefox and Chrome are crappy here and gives
|
|
// us an empty string on some platforms, rather than leaving it
|
|
// undefined)
|
|
if (evt.code) {
|
|
// Mozilla isn't fully in sync with the spec yet
|
|
switch (evt.code) {
|
|
case 'OSLeft': return 'MetaLeft';
|
|
case 'OSRight': return 'MetaRight';
|
|
}
|
|
|
|
return evt.code;
|
|
}
|
|
|
|
// The de-facto standard is to use Windows Virtual-Key codes
|
|
// in the 'keyCode' field for non-printable characters. However
|
|
// Webkit sets it to the same as charCode in 'keypress' events.
|
|
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
|
var code = vkeys[evt.keyCode];
|
|
|
|
// macOS has messed up this code for some reason
|
|
if (isMac() && (code === 'ContextMenu')) {
|
|
code = 'MetaRight';
|
|
}
|
|
|
|
// The keyCode doesn't distinguish between left and right
|
|
// for the standard modifiers
|
|
if (evt.location === 2) {
|
|
switch (code) {
|
|
case 'ShiftLeft': return 'ShiftRight';
|
|
case 'ControlLeft': return 'ControlRight';
|
|
case 'AltLeft': return 'AltRight';
|
|
}
|
|
}
|
|
|
|
// Nor a bunch of the numpad keys
|
|
if (evt.location === 3) {
|
|
switch (code) {
|
|
case 'Delete': return 'NumpadDecimal';
|
|
case 'Insert': return 'Numpad0';
|
|
case 'End': return 'Numpad1';
|
|
case 'ArrowDown': return 'Numpad2';
|
|
case 'PageDown': return 'Numpad3';
|
|
case 'ArrowLeft': return 'Numpad4';
|
|
case 'ArrowRight': return 'Numpad6';
|
|
case 'Home': return 'Numpad7';
|
|
case 'ArrowUp': return 'Numpad8';
|
|
case 'PageUp': return 'Numpad9';
|
|
case 'Enter': return 'NumpadEnter';
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
return 'Unidentified';
|
|
}
|
|
|
|
// Get the most reliable keysym value we can get from a key event
|
|
export function getKeysym(evt){
|
|
|
|
// We start with layout independent keys
|
|
var code = getKeycode(evt);
|
|
if (code in fixedkeys) {
|
|
return fixedkeys[code];
|
|
}
|
|
|
|
// Next with mildly layout or state sensitive stuff
|
|
|
|
// Like AltGraph
|
|
if (code === 'AltRight') {
|
|
if (evt.key === 'AltGraph') {
|
|
return KeyTable.XK_ISO_Level3_Shift;
|
|
} else {
|
|
return KeyTable.XK_Alt_R;
|
|
}
|
|
}
|
|
|
|
// Or the numpad
|
|
if (evt.location === 3) {
|
|
var key = evt.key;
|
|
|
|
// IE and Edge use some ancient version of the spec
|
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
|
|
switch (key) {
|
|
case 'Up': key = 'ArrowUp'; break;
|
|
case 'Left': key = 'ArrowLeft'; break;
|
|
case 'Right': key = 'ArrowRight'; break;
|
|
case 'Down': key = 'ArrowDown'; break;
|
|
case 'Del': key = 'Delete'; break;
|
|
}
|
|
|
|
// Safari doesn't support KeyboardEvent.key yet
|
|
if ((key === undefined) && (evt.charCode)) {
|
|
key = String.fromCharCode(evt.charCode);
|
|
}
|
|
|
|
switch (key) {
|
|
case '0': return KeyTable.XK_KP_0;
|
|
case '1': return KeyTable.XK_KP_1;
|
|
case '2': return KeyTable.XK_KP_2;
|
|
case '3': return KeyTable.XK_KP_3;
|
|
case '4': return KeyTable.XK_KP_4;
|
|
case '5': return KeyTable.XK_KP_5;
|
|
case '6': return KeyTable.XK_KP_6;
|
|
case '7': return KeyTable.XK_KP_7;
|
|
case '8': return KeyTable.XK_KP_8;
|
|
case '9': return KeyTable.XK_KP_9;
|
|
// There is utter mayhem in the world when it comes to which
|
|
// character to use as a decimal separator...
|
|
case '.': return KeyTable.XK_KP_Decimal;
|
|
case ',': return KeyTable.XK_KP_Separator;
|
|
case 'Home': return KeyTable.XK_KP_Home;
|
|
case 'End': return KeyTable.XK_KP_End;
|
|
case 'PageUp': return KeyTable.XK_KP_Prior;
|
|
case 'PageDown': return KeyTable.XK_KP_Next;
|
|
case 'Insert': return KeyTable.XK_KP_Insert;
|
|
case 'Delete': return KeyTable.XK_KP_Delete;
|
|
case 'ArrowUp': return KeyTable.XK_KP_Up;
|
|
case 'ArrowLeft': return KeyTable.XK_KP_Left;
|
|
case 'ArrowRight': return KeyTable.XK_KP_Right;
|
|
case 'ArrowDown': return KeyTable.XK_KP_Down;
|
|
}
|
|
}
|
|
|
|
// Now we need to look at the Unicode symbol instead
|
|
|
|
var codepoint;
|
|
|
|
if ('key' in evt) {
|
|
// Special key? (FIXME: Should have been caught earlier)
|
|
if (evt.key.length !== 1) {
|
|
return null;
|
|
}
|
|
|
|
codepoint = evt.key.charCodeAt();
|
|
} else if ('charCode' in evt) {
|
|
codepoint = evt.charCode;
|
|
}
|
|
|
|
if (codepoint) {
|
|
return keysyms.lookup(codepoint);
|
|
}
|
|
|
|
return null;
|
|
}
|