noVNC/core/input/util.js

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;
}