diff --git a/core/input/fixedkeys.js b/core/input/fixedkeys.js new file mode 100644 index 00000000..2a2594e8 --- /dev/null +++ b/core/input/fixedkeys.js @@ -0,0 +1,112 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/* + * Mapping between HTML key codes and VNC/X11 keysyms for the + * subset of keys that have the same mapping on every keyboard + * layout. Keys that vary between layouts must never be included + * in this list. + */ + +import KeyTable from "./keysym.js"; + +export default { + 'Backspace': KeyTable.XK_BackSpace, + 'AltLeft': KeyTable.XK_Alt_L, + // AltRight is special + 'CapsLock': KeyTable.XK_Caps_Lock, + 'ContextMenu': KeyTable.XK_Menu, + 'ControlLeft': KeyTable.XK_Control_L, + 'ControlRight': KeyTable.XK_Control_R, + 'Enter': KeyTable.XK_Return, + 'MetaLeft': KeyTable.XK_Super_L, + 'MetaRight': KeyTable.XK_Super_R, + 'ShiftLeft': KeyTable.XK_Shift_L, + 'ShiftRight': KeyTable.XK_Shift_R, + 'Space': KeyTable.XK_space, + 'Tab': KeyTable.XK_Tab, + // FIXME: Japanese/Korean keys + 'Delete': KeyTable.XK_Delete, + 'End': KeyTable.XK_End, + 'Help': KeyTable.XK_Help, + 'Home': KeyTable.XK_Home, + 'Insert': KeyTable.XK_Insert, + 'PageDown': KeyTable.XK_Next, + 'PageUp': KeyTable.XK_Prior, + 'ArrowDown': KeyTable.XK_Down, + 'ArrowLeft': KeyTable.XK_Left, + 'ArrowRight': KeyTable.XK_Right, + 'ArrowUp': KeyTable.XK_Up, + 'NumLock': KeyTable.XK_Num_Lock, + 'NumpadAdd': KeyTable.XK_KP_Add, + 'NumpadBackspace': KeyTable.XK_KP_Delete, + 'NumpadClear': KeyTable.XK_Clear, + // NumpadDecimal is special + 'NumpadDivide': KeyTable.XK_KP_Divide, + 'NumpadEnter': KeyTable.XK_KP_Enter, + 'NumpadEqual': KeyTable.XK_KP_Equal, + 'NumpadMultiply': KeyTable.XK_KP_Multiply, + 'NumpadSubtract': KeyTable.XK_KP_Subtract, + 'Escape': KeyTable.XK_Escape, + 'F1': KeyTable.XK_F1, + 'F2': KeyTable.XK_F2, + 'F3': KeyTable.XK_F3, + 'F4': KeyTable.XK_F4, + 'F5': KeyTable.XK_F5, + 'F6': KeyTable.XK_F6, + 'F7': KeyTable.XK_F7, + 'F8': KeyTable.XK_F8, + 'F9': KeyTable.XK_F9, + 'F10': KeyTable.XK_F10, + 'F11': KeyTable.XK_F11, + 'F12': KeyTable.XK_F12, + 'F13': KeyTable.XK_F13, + 'F14': KeyTable.XK_F14, + 'F15': KeyTable.XK_F15, + 'F16': KeyTable.XK_F16, + 'F17': KeyTable.XK_F17, + 'F18': KeyTable.XK_F18, + 'F19': KeyTable.XK_F19, + 'F20': KeyTable.XK_F20, + 'F21': KeyTable.XK_F21, + 'F22': KeyTable.XK_F22, + 'F23': KeyTable.XK_F23, + 'F24': KeyTable.XK_F24, + 'F25': KeyTable.XK_F25, + 'F26': KeyTable.XK_F26, + 'F27': KeyTable.XK_F27, + 'F28': KeyTable.XK_F28, + 'F29': KeyTable.XK_F29, + 'F30': KeyTable.XK_F30, + 'F31': KeyTable.XK_F31, + 'F32': KeyTable.XK_F32, + 'F33': KeyTable.XK_F33, + 'F34': KeyTable.XK_F34, + 'F35': KeyTable.XK_F35, + 'PrintScreen': KeyTable.XK_Print, + 'ScrollLock': KeyTable.XK_Scroll_Lock, + 'Pause': KeyTable.XK_Pause, + 'BrowserBack': KeyTable.XF86XK_Back, + 'BrowserFavorites': KeyTable.XF86XK_Favorites, + 'BrowserForward': KeyTable.XF86XK_Forward, + 'BrowserHome': KeyTable.XF86XK_HomePage, + 'BrowserRefresh': KeyTable.XF86XK_Refresh, + 'BrowserSearch': KeyTable.XF86XK_Search, + 'BrowserStop': KeyTable.XF86XK_Stop, + 'LaunchApp1': KeyTable.XF86XK_Explorer, + 'LaunchApp2': KeyTable.XF86XK_Calculator, + 'LaunchMail': KeyTable.XF86XK_Mail, + 'MediaPlayPause': KeyTable.XF86XK_AudioPlay, + 'MediaStop': KeyTable.XF86XK_AudioStop, + 'MediaTrackNext': KeyTable.XF86XK_AudioNext, + 'MediaTrackPrevious': KeyTable.XF86XK_AudioPrev, + 'Power': KeyTable.XF86XK_PowerOff, + 'Sleep': KeyTable.XF86XK_Sleep, + 'AudioVolumeDown': KeyTable.XF86XK_AudioLowerVolume, + 'AudioVolumeMute': KeyTable.XF86XK_AudioMute, + 'AudioVolumeUp': KeyTable.XF86XK_AudioRaiseVolume, + 'WakeUp': KeyTable.XF86XK_WakeUp, +}; diff --git a/core/input/keysym.js b/core/input/keysym.js index f3a247fd..06ea70f8 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -377,4 +377,188 @@ export default { XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */ XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + + /* + * XFree86 vendor specific keysyms. + * + * The XFree86 keysym range is 0x10080001 - 0x1008FFFF. + */ + + XF86XK_ModeLock: 0x1008FF01, + XF86XK_MonBrightnessUp: 0x1008FF02, + XF86XK_MonBrightnessDown: 0x1008FF03, + XF86XK_KbdLightOnOff: 0x1008FF04, + XF86XK_KbdBrightnessUp: 0x1008FF05, + XF86XK_KbdBrightnessDown: 0x1008FF06, + XF86XK_Standby: 0x1008FF10, + XF86XK_AudioLowerVolume: 0x1008FF11, + XF86XK_AudioMute: 0x1008FF12, + XF86XK_AudioRaiseVolume: 0x1008FF13, + XF86XK_AudioPlay: 0x1008FF14, + XF86XK_AudioStop: 0x1008FF15, + XF86XK_AudioPrev: 0x1008FF16, + XF86XK_AudioNext: 0x1008FF17, + XF86XK_HomePage: 0x1008FF18, + XF86XK_Mail: 0x1008FF19, + XF86XK_Start: 0x1008FF1A, + XF86XK_Search: 0x1008FF1B, + XF86XK_AudioRecord: 0x1008FF1C, + XF86XK_Calculator: 0x1008FF1D, + XF86XK_Memo: 0x1008FF1E, + XF86XK_ToDoList: 0x1008FF1F, + XF86XK_Calendar: 0x1008FF20, + XF86XK_PowerDown: 0x1008FF21, + XF86XK_ContrastAdjust: 0x1008FF22, + XF86XK_RockerUp: 0x1008FF23, + XF86XK_RockerDown: 0x1008FF24, + XF86XK_RockerEnter: 0x1008FF25, + XF86XK_Back: 0x1008FF26, + XF86XK_Forward: 0x1008FF27, + XF86XK_Stop: 0x1008FF28, + XF86XK_Refresh: 0x1008FF29, + XF86XK_PowerOff: 0x1008FF2A, + XF86XK_WakeUp: 0x1008FF2B, + XF86XK_Eject: 0x1008FF2C, + XF86XK_ScreenSaver: 0x1008FF2D, + XF86XK_WWW: 0x1008FF2E, + XF86XK_Sleep: 0x1008FF2F, + XF86XK_Favorites: 0x1008FF30, + XF86XK_AudioPause: 0x1008FF31, + XF86XK_AudioMedia: 0x1008FF32, + XF86XK_MyComputer: 0x1008FF33, + XF86XK_VendorHome: 0x1008FF34, + XF86XK_LightBulb: 0x1008FF35, + XF86XK_Shop: 0x1008FF36, + XF86XK_History: 0x1008FF37, + XF86XK_OpenURL: 0x1008FF38, + XF86XK_AddFavorite: 0x1008FF39, + XF86XK_HotLinks: 0x1008FF3A, + XF86XK_BrightnessAdjust: 0x1008FF3B, + XF86XK_Finance: 0x1008FF3C, + XF86XK_Community: 0x1008FF3D, + XF86XK_AudioRewind: 0x1008FF3E, + XF86XK_BackForward: 0x1008FF3F, + XF86XK_Launch0: 0x1008FF40, + XF86XK_Launch1: 0x1008FF41, + XF86XK_Launch2: 0x1008FF42, + XF86XK_Launch3: 0x1008FF43, + XF86XK_Launch4: 0x1008FF44, + XF86XK_Launch5: 0x1008FF45, + XF86XK_Launch6: 0x1008FF46, + XF86XK_Launch7: 0x1008FF47, + XF86XK_Launch8: 0x1008FF48, + XF86XK_Launch9: 0x1008FF49, + XF86XK_LaunchA: 0x1008FF4A, + XF86XK_LaunchB: 0x1008FF4B, + XF86XK_LaunchC: 0x1008FF4C, + XF86XK_LaunchD: 0x1008FF4D, + XF86XK_LaunchE: 0x1008FF4E, + XF86XK_LaunchF: 0x1008FF4F, + XF86XK_ApplicationLeft: 0x1008FF50, + XF86XK_ApplicationRight: 0x1008FF51, + XF86XK_Book: 0x1008FF52, + XF86XK_CD: 0x1008FF53, + XF86XK_Calculater: 0x1008FF54, + XF86XK_Clear: 0x1008FF55, + XF86XK_Close: 0x1008FF56, + XF86XK_Copy: 0x1008FF57, + XF86XK_Cut: 0x1008FF58, + XF86XK_Display: 0x1008FF59, + XF86XK_DOS: 0x1008FF5A, + XF86XK_Documents: 0x1008FF5B, + XF86XK_Excel: 0x1008FF5C, + XF86XK_Explorer: 0x1008FF5D, + XF86XK_Game: 0x1008FF5E, + XF86XK_Go: 0x1008FF5F, + XF86XK_iTouch: 0x1008FF60, + XF86XK_LogOff: 0x1008FF61, + XF86XK_Market: 0x1008FF62, + XF86XK_Meeting: 0x1008FF63, + XF86XK_MenuKB: 0x1008FF65, + XF86XK_MenuPB: 0x1008FF66, + XF86XK_MySites: 0x1008FF67, + XF86XK_New: 0x1008FF68, + XF86XK_News: 0x1008FF69, + XF86XK_OfficeHome: 0x1008FF6A, + XF86XK_Open: 0x1008FF6B, + XF86XK_Option: 0x1008FF6C, + XF86XK_Paste: 0x1008FF6D, + XF86XK_Phone: 0x1008FF6E, + XF86XK_Q: 0x1008FF70, + XF86XK_Reply: 0x1008FF72, + XF86XK_Reload: 0x1008FF73, + XF86XK_RotateWindows: 0x1008FF74, + XF86XK_RotationPB: 0x1008FF75, + XF86XK_RotationKB: 0x1008FF76, + XF86XK_Save: 0x1008FF77, + XF86XK_ScrollUp: 0x1008FF78, + XF86XK_ScrollDown: 0x1008FF79, + XF86XK_ScrollClick: 0x1008FF7A, + XF86XK_Send: 0x1008FF7B, + XF86XK_Spell: 0x1008FF7C, + XF86XK_SplitScreen: 0x1008FF7D, + XF86XK_Support: 0x1008FF7E, + XF86XK_TaskPane: 0x1008FF7F, + XF86XK_Terminal: 0x1008FF80, + XF86XK_Tools: 0x1008FF81, + XF86XK_Travel: 0x1008FF82, + XF86XK_UserPB: 0x1008FF84, + XF86XK_User1KB: 0x1008FF85, + XF86XK_User2KB: 0x1008FF86, + XF86XK_Video: 0x1008FF87, + XF86XK_WheelButton: 0x1008FF88, + XF86XK_Word: 0x1008FF89, + XF86XK_Xfer: 0x1008FF8A, + XF86XK_ZoomIn: 0x1008FF8B, + XF86XK_ZoomOut: 0x1008FF8C, + XF86XK_Away: 0x1008FF8D, + XF86XK_Messenger: 0x1008FF8E, + XF86XK_WebCam: 0x1008FF8F, + XF86XK_MailForward: 0x1008FF90, + XF86XK_Pictures: 0x1008FF91, + XF86XK_Music: 0x1008FF92, + XF86XK_Battery: 0x1008FF93, + XF86XK_Bluetooth: 0x1008FF94, + XF86XK_WLAN: 0x1008FF95, + XF86XK_UWB: 0x1008FF96, + XF86XK_AudioForward: 0x1008FF97, + XF86XK_AudioRepeat: 0x1008FF98, + XF86XK_AudioRandomPlay: 0x1008FF99, + XF86XK_Subtitle: 0x1008FF9A, + XF86XK_AudioCycleTrack: 0x1008FF9B, + XF86XK_CycleAngle: 0x1008FF9C, + XF86XK_FrameBack: 0x1008FF9D, + XF86XK_FrameForward: 0x1008FF9E, + XF86XK_Time: 0x1008FF9F, + XF86XK_Select: 0x1008FFA0, + XF86XK_View: 0x1008FFA1, + XF86XK_TopMenu: 0x1008FFA2, + XF86XK_Red: 0x1008FFA3, + XF86XK_Green: 0x1008FFA4, + XF86XK_Yellow: 0x1008FFA5, + XF86XK_Blue: 0x1008FFA6, + XF86XK_Suspend: 0x1008FFA7, + XF86XK_Hibernate: 0x1008FFA8, + XF86XK_TouchpadToggle: 0x1008FFA9, + XF86XK_TouchpadOn: 0x1008FFB0, + XF86XK_TouchpadOff: 0x1008FFB1, + XF86XK_AudioMicMute: 0x1008FFB2, + XF86XK_Switch_VT_1: 0x1008FE01, + XF86XK_Switch_VT_2: 0x1008FE02, + XF86XK_Switch_VT_3: 0x1008FE03, + XF86XK_Switch_VT_4: 0x1008FE04, + XF86XK_Switch_VT_5: 0x1008FE05, + XF86XK_Switch_VT_6: 0x1008FE06, + XF86XK_Switch_VT_7: 0x1008FE07, + XF86XK_Switch_VT_8: 0x1008FE08, + XF86XK_Switch_VT_9: 0x1008FE09, + XF86XK_Switch_VT_10: 0x1008FE0A, + XF86XK_Switch_VT_11: 0x1008FE0B, + XF86XK_Switch_VT_12: 0x1008FE0C, + XF86XK_Ungrab: 0x1008FE20, + XF86XK_ClearGrab: 0x1008FE21, + XF86XK_Next_VMode: 0x1008FE22, + XF86XK_Prev_VMode: 0x1008FE23, + XF86XK_LogWindowTree: 0x1008FE24, + XF86XK_LogGrabInfo: 0x1008FE25, }; diff --git a/core/input/util.js b/core/input/util.js index 18867af8..b8fd83ab 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -1,6 +1,7 @@ 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); @@ -12,29 +13,6 @@ function isLinux() { return navigator && !!(/linux/i).exec(navigator.platform); } -// Return true if a modifier which is not the specified char modifier (and is not shift) is down -export function hasShortcutModifier(charModifier, currentModifiers) { - var mods = {}; - for (var key in currentModifiers) { - if (parseInt(key) !== KeyTable.XK_Shift_L) { - 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 export function hasCharModifier(charModifier, currentModifiers) { if (charModifier.length === 0) { return false; } @@ -125,8 +103,6 @@ export function ModifierSync(charModifier) { // 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; } }; @@ -193,15 +169,83 @@ export function getKeycode(evt){ } // 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 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) { - // Ignore special keys - if (evt.key.length === 1) { - codepoint = evt.key.charCodeAt(); + // 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; } @@ -210,94 +254,9 @@ export function getKeysym(evt){ return keysyms.lookup(codepoint); } - if (evt.keyCode) { - return keysymFromKeyCode(evt.keyCode, evt.shiftKey); - } - if (evt.which) { - return 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 KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9 - } - - switch(keycode) { - case 0x20: return KeyTable.XK_space; - case 0x6a: return KeyTable.XK_KP_Multiply; - case 0x6b: return KeyTable.XK_KP_Add; - case 0x6c: return KeyTable.XK_KP_Separator; - case 0x6d: return KeyTable.XK_KP_Subtract; - case 0x6e: return KeyTable.XK_KP_Decimal; - case 0x6f: return KeyTable.XK_KP_Divide; - case 0xbb: return KeyTable.XK_plus; - case 0xbc: return KeyTable.XK_comma; - case 0xbd: return KeyTable.XK_minus; - case 0xbe: return KeyTable.XK_period; - } - - 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 KeyTable.XK_F1 + keycode - 0x70; // F1-F24 - } - switch (keycode) { - - case 8 : return KeyTable.XK_BackSpace; - case 13 : return KeyTable.XK_Return; - - case 9 : return KeyTable.XK_Tab; - - case 27 : return KeyTable.XK_Escape; - case 46 : return KeyTable.XK_Delete; - - case 36 : return KeyTable.XK_Home; - case 35 : return KeyTable.XK_End; - case 33 : return KeyTable.XK_Page_Up; - case 34 : return KeyTable.XK_Page_Down; - case 45 : return KeyTable.XK_Insert; - - case 37 : return KeyTable.XK_Left; - case 38 : return KeyTable.XK_Up; - case 39 : return KeyTable.XK_Right; - case 40 : return KeyTable.XK_Down; - - case 16 : return KeyTable.XK_Shift_L; - case 17 : return KeyTable.XK_Control_L; - case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac - - case 224 : return KeyTable.XK_Meta_L; - case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr - case 91 : return KeyTable.XK_Super_L; // also: Windows-key - case 92 : return KeyTable.XK_Super_R; // also: Windows-key - case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac - default: return null; - } -} - export function QEMUKeyEventDecoder (modifierState, next) { "use strict"; @@ -341,10 +300,7 @@ export function QEMUKeyEventDecoder (modifierState, next) { result.keysym = getNumPadKeySym(evt); } - var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); - var isShift = result.code === 'ShiftLeft' || result.code === 'ShiftRight'; - - var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt)); + var suppress = type !== 'keydown' || !!getKeysym(evt); next(result); return suppress; @@ -457,20 +413,17 @@ export function KeyEventDecoder (modifierState, next) { var keysym = 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' || nonCharacterKey(evt) || hasModifier)) { + if (keysym) { result.keysym = keysym; } - var isShift = code === 'ShiftLeft' || code === 'ShiftRight'; - // 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() || !!nonCharacterKey(evt)); + var suppress = type !== 'keydown' || !!keysym; // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt) var active = modifierState.activeCharModifier(); diff --git a/tests/test.helper.js b/tests/test.helper.js index b66c5e57..ce742f0a 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -108,26 +108,50 @@ describe('Helpers', function() { describe('Non-character keys', function() { it('should recognize the right keys', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0x0d})).to.be.equal(0xFF0D); - expect(KeyboardUtil.getKeysym({keyCode: 0x08})).to.be.equal(0xFF08); - expect(KeyboardUtil.getKeysym({keyCode: 0x09})).to.be.equal(0xFF09); - expect(KeyboardUtil.getKeysym({keyCode: 0x10})).to.be.equal(0xFFE1); - expect(KeyboardUtil.getKeysym({keyCode: 0x11})).to.be.equal(0xFFE3); - expect(KeyboardUtil.getKeysym({keyCode: 0x12})).to.be.equal(0xFFE9); - expect(KeyboardUtil.getKeysym({keyCode: 0xe0})).to.be.equal(0xFFE7); - expect(KeyboardUtil.getKeysym({keyCode: 0xe1})).to.be.equal(0xFE03); - expect(KeyboardUtil.getKeysym({keyCode: 0x1b})).to.be.equal(0xFF1B); - expect(KeyboardUtil.getKeysym({keyCode: 0x26})).to.be.equal(0xFF52); + expect(KeyboardUtil.getKeysym({code: 'Enter'})).to.be.equal(0xFF0D); + expect(KeyboardUtil.getKeysym({code: 'Backspace'})).to.be.equal(0xFF08); + expect(KeyboardUtil.getKeysym({code: 'Tab'})).to.be.equal(0xFF09); + expect(KeyboardUtil.getKeysym({code: 'ShiftLeft'})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({code: 'ControlLeft'})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({code: 'AltLeft'})).to.be.equal(0xFFE9); + expect(KeyboardUtil.getKeysym({code: 'MetaLeft'})).to.be.equal(0xFFEB); + expect(KeyboardUtil.getKeysym({code: 'Escape'})).to.be.equal(0xFF1B); + expect(KeyboardUtil.getKeysym({code: 'ArrowUp'})).to.be.equal(0xFF52); }); - it('should return null for unknown keycodes', function() { - expect(KeyboardUtil.getKeysym({keyCode: 0xc0})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: 0xde})).to.be.null; + it('should handle AltGraph', function() { + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltRight'})).to.be.equal(0xFFEA); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph'})).to.be.equal(0xFE03); + }); + it('should return null for unknown codes', function() { + expect(KeyboardUtil.getKeysym({code: 'Semicolon'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'BracketRight'})).to.be.null; }); it('should not recognize character keys', function() { - expect(KeyboardUtil.getKeysym({keyCode: 'A'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: '1'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: '.'})).to.be.null; - expect(KeyboardUtil.getKeysym({keyCode: ' '})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'KeyA'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Digit1'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Period'})).to.be.null; + expect(KeyboardUtil.getKeysym({code: 'Numpad1'})).to.be.null; + }); + }); + + describe('Numpad', function() { + it('should handle Numpad numbers', function() { + expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035); + expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5); + }); + it('should handle Numpad non-character keys', function() { + expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50); + expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95); + expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F); + }); + it('should handle IE/Edge key names', function() { + expect(KeyboardUtil.getKeysym({code: 'Numpad6', key: 'Right', location: 3})).to.be.equal(0xFF98); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Del', location: 3})).to.be.equal(0xFF9F); + }); + it('should handle Numpad Decimal key', function() { + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE); + expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC); }); }); }); @@ -137,7 +161,7 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it ('should do nothing if all modifiers are up as expected', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: false, altKey: false, altGraphKey: false, @@ -147,7 +171,7 @@ describe('Helpers', function() { }); it ('should synthesize events if all keys are unexpectedly down', function() { var result = sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, altKey: true, altGraphKey: true, @@ -167,7 +191,7 @@ describe('Helpers', function() { }); it ('should do nothing if all modifiers are down as expected', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, altKey: true, altGraphKey: true, @@ -180,13 +204,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, })).to.be.deep.equal([{keysym: 0xffe3, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: false })).to.be.deep.equal([{keysym: 0xffe3, type: 'keyup'}]); }); @@ -195,13 +219,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altKey: true, })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altKey: false })).to.be.deep.equal([{keysym: 0xffe9, type: 'keyup'}]); }); @@ -210,13 +234,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altGraphKey: true, })).to.be.deep.equal([{keysym: 0xfe03, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', altGraphKey: false })).to.be.deep.equal([{keysym: 0xfe03, type: 'keyup'}]); }); @@ -225,13 +249,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', shiftKey: true, })).to.be.deep.equal([{keysym: 0xffe1, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', shiftKey: false })).to.be.deep.equal([{keysym: 0xffe1, type: 'keyup'}]); }); @@ -240,13 +264,13 @@ describe('Helpers', function() { var sync = KeyboardUtil.ModifierSync(); it('should sync if modifier is suddenly down', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', metaKey: true, })).to.be.deep.equal([{keysym: 0xffe7, type: 'keydown'}]); }); it('should sync if modifier is suddenly up', function() { expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', metaKey: false })).to.be.deep.equal([{keysym: 0xffe7, type: 'keyup'}]); }); @@ -254,27 +278,27 @@ describe('Helpers', function() { describe('Modifier keyevents', function() { it('should not sync a modifier on its own events', function() { expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', ctrlKey: false })).to.be.deep.equal([]); expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', ctrlKey: true }), 'B').to.be.deep.equal([]); }) it('should update state on modifier keyevents', function() { var sync = KeyboardUtil.ModifierSync(); sync.keydown({ - keyCode: 0x11, + code: 'ControlLeft', }); expect(sync.keydown({ - keyCode: 0x41, + code: 'KeyA', ctrlKey: true, })).to.be.deep.equal([]); }); it('should sync other modifiers on ctrl events', function() { expect(KeyboardUtil.ModifierSync().keydown({ - keyCode: 0x11, + code: 'ControlLeft', altKey: true })).to.be.deep.equal([{keysym: 0xffe9, type: 'keydown'}]); }) @@ -287,9 +311,6 @@ describe('Helpers', function() { }); }); describe('do not treat shift as a modifier key', function() { - it('should not treat shift as a shortcut modifier', function() { - expect(KeyboardUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false; - }); it('should not treat shift as a char modifier', function() { expect(KeyboardUtil.hasCharModifier([], {0xffe1 : true})).to.be.false; }); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index f29a3394..19a8a66a 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -51,7 +51,7 @@ describe('Key Event Pipeline Stages', function() { }); it('should forward keydown events with the right type', function(done) { KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); + expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); done(); }).keydown({code: 'KeyA', key: 'a'}); }); @@ -71,65 +71,57 @@ describe('Key Event Pipeline Stages', function() { it('should suppress anything while a shortcut modifier is down', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - obj.keydown({keyCode: 0x11}); // press ctrl - expect(obj.keydown({key: 'A'})).to.be.true; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true; - expect(obj.keydown({key: '1'})).to.be.true; - expect(obj.keydown({key: '<'})).to.be.true; - expect(obj.keydown({key: 'ø'})).to.be.true; + obj.keydown({code: 'ControlLeft'}); + expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keydown({code: 'Space', key: ' '})).to.be.true; + expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; + expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; }); it('should suppress non-character keys', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true; - expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true; - expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true; - expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true; - }); - it('should not suppress shift', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - - expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false; + expect(obj.keydown({code: 'Backspace'}), 'a').to.be.true; + expect(obj.keydown({code: 'Tab'}), 'b').to.be.true; + expect(obj.keydown({code: 'ControlLeft'}), 'd').to.be.true; + expect(obj.keydown({code: 'AltLeft'}), 'e').to.be.true; }); it('should generate event for shift keydown', function() { var called = false; var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { expect(evt).to.have.property('keysym'); called = true; - }).keydown({keyCode: 0x10}); + }).keydown({code: 'ShiftLeft'}); expect(called).to.be.true; }); - it('should not suppress character keys', function() { + it('should suppress character keys with key', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keydown({key: 'A'})).to.be.false; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({key: '1'})).to.be.false; - expect(obj.keydown({key: '<'})).to.be.false; // < key on DK Windows - expect(obj.keydown({key: 'ø'})).to.be.false; // Ø key on DK + expect(obj.keydown({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keydown({code: 'Digit1', key: '1'})).to.be.true; + expect(obj.keydown({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keydown({code: 'Semicolon', key: 'ø'})).to.be.true; }); - it('should not suppress if a char modifier is down', function() { - var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {}); + it('should not suppress character keys without key', function() { + var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - obj.keydown({keyCode: 0xe1}); // press altgr - expect(obj.keydown({key: 'A'})).to.be.false; - expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false; - expect(obj.keydown({key: '1'})).to.be.false; - expect(obj.keydown({key: '<'})).to.be.false; - expect(obj.keydown({key: 'ø'})).to.be.false; + expect(obj.keydown({code: 'KeyA'})).to.be.false; + expect(obj.keydown({code: 'Digit1'})).to.be.false; + expect(obj.keydown({code: 'IntlBackslash'})).to.be.false; + expect(obj.keydown({code: 'Semicolon'})).to.be.false; }); }); describe('Keypress and keyup events', function() { it('should always suppress event propagation', function() { var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {}); - expect(obj.keypress({key: 'A'})).to.be.true; - expect(obj.keypress({key: '<'})).to.be.true; - expect(obj.keypress({keyCode: 0x11})).to.be.true; + expect(obj.keypress({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keypress({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keypress({code: 'ControlLeft', key: 'Control'})).to.be.true; - expect(obj.keyup({key: 'A'})).to.be.true; - expect(obj.keyup({key: '<'})).to.be.true; - expect(obj.keyup({keyCode: 0x11})).to.be.true; + expect(obj.keyup({code: 'KeyA', key: 'a'})).to.be.true; + expect(obj.keyup({code: 'IntlBackslash', key: '<'})).to.be.true; + expect(obj.keyup({code: 'ControlLeft', key: 'Control'})).to.be.true; }); }); describe('mark events if a char modifier is down', function() { @@ -145,7 +137,7 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) obj.keydown({code: 'KeyA', key: 'a'}); }); @@ -167,7 +159,7 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) obj.keypress({code: 'KeyA', key: 'a'}); }); it('should indicate on events if a multi-key char modifier is down', function(done) { @@ -190,8 +182,8 @@ describe('Key Event Pipeline Stages', function() { } }); - obj.keydown({keyCode: 0x11}); // press ctrl - obj.keydown({keyCode: 0x12}); // press alt + obj.keydown({code: 'ControlLeft'}); + obj.keydown({code: 'AltLeft'}); obj.keypress({code: 'KeyA', key: 'a'}); }); it('should not consider a char modifier to be down on the modifier key itself', function() { @@ -199,60 +191,10 @@ describe('Key Event Pipeline Stages', function() { expect(evt).to.not.have.property('escape'); }); - obj.keydown({keyCode: 0xe1}); // press altgr + obj.keydown({code: 'AltRight', key: 'AltGraph'}) }); }); - describe('add/remove keysym', function() { - it('should remove keysym from keydown if a char key and no modifier', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', type: 'keydown'}); - }).keydown({code: 'KeyA', key: 'a'}); - }); - it('should not remove keysym from keydown if a shortcut modifier is down', function() { - var times_called = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - switch (times_called++) { - case 1: - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); - break; - } - }).keydown({code: 'KeyA', key: 'a', ctrlKey: true}); - expect(times_called).to.be.equal(2); - }); - it('should not remove keysym from keydown if a char modifier is down', function() { - var times_called = 0; - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) { - switch (times_called++) { - case 1: - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keydown'}); - break; - } - }).keydown({code: 'KeyA', key: 'a', altGraphKey: true}); - expect(times_called).to.be.equal(2); - }); - it('should not remove keysym from keydown if key is noncharacter', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'tab').to.be.deep.equal({code: 'Tab', keysym: 0xff09, type: 'keydown'}); - }).keydown({keyCode: 0x09}); - - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt, 'ctrl').to.be.deep.equal({code: 'ControlLeft', keysym: 0xffe3, type: 'keydown'}); - }).keydown({keyCode: 0x11}); - }); - it('should never remove keysym from keypress', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keypress'}); - }).keypress({code: 'KeyA', key: 'a'}); - }); - it('should never remove keysym from keyup', function() { - KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) { - expect(evt).to.be.deep.equal({code: 'KeyA', keysym: 0x61, type: 'keyup'}); - }).keyup({code: 'KeyA', key: 'a'}); - }); - }); - // on keypress, keyup(?), always set keysym - // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down }); describe('Track Key State', function() { diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html index 36331eb1..acae0d15 100644 --- a/tests/vnc_perf.html +++ b/tests/vnc_perf.html @@ -51,7 +51,7 @@ 'core': ["base64.js", "websock.js", "des.js", "input/keysym.js", "input/keysymdef.js", "input/xtscancodes.js", "input/util.js", "input/devices.js", "display.js", "rfb.js", "inflator.js", - "input/vkeys.js"], + "input/vkeys.js", "input/fixedkeys.js"], 'tests': ["playback.js"], 'recordings': [fname]}); } else {