diff --git a/core/input/domkeytable.js b/core/input/domkeytable.js new file mode 100644 index 00000000..6758c089 --- /dev/null +++ b/core/input/domkeytable.js @@ -0,0 +1,310 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +import KeyTable from "./keysym.js" + +/* + * Mapping between HTML key values and VNC/X11 keysyms for "special" + * keys that cannot be handled via their Unicode codepoint. + * + * See https://www.w3.org/TR/uievents-key/ for possible values. + */ + +var DOMKeyTable = {}; + +function addStandard(key, standard) +{ + if (standard === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [standard, standard, standard, standard]; +} + +function addLeftRight(key, left, right) +{ + if (left === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (right === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [left, left, right, left]; +} + +function addNumpad(key, standard, numpad) +{ + if (standard === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\""; + if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\""; + DOMKeyTable[key] = [standard, standard, standard, numpad]; +} + +// 2.2. Modifier Keys + +addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R); +addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift); +addStandard("CapsLock", KeyTable.XK_Caps_Lock); +addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R); +// - Fn +// - FnLock +addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +addStandard("NumLock", KeyTable.XK_Num_Lock); +addStandard("ScrollLock", KeyTable.XK_Scroll_Lock); +addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R); +addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R); +// - Symbol +// - SymbolLock + +// 2.3. Whitespace Keys + +addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter); +addStandard("Tab", KeyTable.XK_Tab); +addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space); + +// 2.4. Navigation Keys + +addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down); +addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up); +addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left); +addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right); +addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End); +addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home); +addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next); +addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior); + +// 2.5. Editing Keys + +addStandard("Backspace", KeyTable.XK_BackSpace); +addStandard("Clear", KeyTable.XK_Clear); +addStandard("Copy", KeyTable.XF86XK_Copy); +// - CrSel +addStandard("Cut", KeyTable.XF86XK_Cut); +addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete); +// - EraseEof +// - ExSel +addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert); +addStandard("Paste", KeyTable.XF86XK_Paste); +addStandard("Redo", KeyTable.XK_Redo); +addStandard("Undo", KeyTable.XK_Undo); + +// 2.6. UI Keys + +// - Accept +// - Again (could just be XK_Redo) +// - Attn +addStandard("Cancel", KeyTable.XK_Cancel); +addStandard("ContextMenu", KeyTable.XK_Menu); +addStandard("Escape", KeyTable.XK_Escape); +addStandard("Execute", KeyTable.XK_Execute); +addStandard("Find", KeyTable.XK_Find); +addStandard("Help", KeyTable.XK_Help); +addStandard("Pause", KeyTable.XK_Pause); +// - Play +// - Props +addStandard("Select", KeyTable.XK_Select); +addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn); +addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut); + +// 2.7. Device Keys + +addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown); +addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp); +addStandard("Eject", KeyTable.XF86XK_Eject); +addStandard("LogOff", KeyTable.XF86XK_LogOff); +addStandard("Power", KeyTable.XF86XK_PowerOff); +addStandard("PowerOff", KeyTable.XF86XK_PowerDown); +addStandard("PrintScreen", KeyTable.XK_Print); +addStandard("Hibernate", KeyTable.XF86XK_Hibernate); +addStandard("Standby", KeyTable.XF86XK_Standby); +addStandard("WakeUp", KeyTable.XF86XK_WakeUp); + +// 2.8. IME and Composition Keys + +addStandard("AllCandidates", KeyTable.XK_MultipleCandidate); +addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle +addStandard("CodeInput", KeyTable.XK_Codeinput); +addStandard("Compose", KeyTable.XK_Multi_key); +addStandard("Convert", KeyTable.XK_Henkan); +// - Dead +// - FinalMode +addStandard("GroupFirst", KeyTable.XK_ISO_First_Group); +addStandard("GroupLast", KeyTable.XK_ISO_Last_Group); +addStandard("GroupNext", KeyTable.XK_ISO_Next_Group); +addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group); +// - ModeChange (XK_Mode_switch is often used for AltGr) +// - NextCandidate +addStandard("NonConvert", KeyTable.XK_Muhenkan); +addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate); +// - Process +addStandard("SingleCandidate", KeyTable.XK_SingleCandidate); +addStandard("HangulMode", KeyTable.XK_Hangul); +addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja); +addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja); +addStandard("Eisu", KeyTable.XK_Eisu_toggle); +addStandard("Hankaku", KeyTable.XK_Hankaku); +addStandard("Hiragana", KeyTable.XK_Hiragana); +addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana); +addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock +addStandard("KanjiMode", KeyTable.XK_Kanji); +addStandard("Katakana", KeyTable.XK_Katakana); +addStandard("Romaji", KeyTable.XK_Romaji); +addStandard("Zenkaku", KeyTable.XK_Zenkaku); +addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku); + +// 2.9. General-Purpose Function Keys + +addStandard("F1", KeyTable.XK_F1); +addStandard("F2", KeyTable.XK_F2); +addStandard("F3", KeyTable.XK_F3); +addStandard("F4", KeyTable.XK_F4); +addStandard("F5", KeyTable.XK_F5); +addStandard("F6", KeyTable.XK_F6); +addStandard("F7", KeyTable.XK_F7); +addStandard("F8", KeyTable.XK_F8); +addStandard("F9", KeyTable.XK_F9); +addStandard("F10", KeyTable.XK_F10); +addStandard("F11", KeyTable.XK_F11); +addStandard("F12", KeyTable.XK_F12); +addStandard("F13", KeyTable.XK_F13); +addStandard("F14", KeyTable.XK_F14); +addStandard("F15", KeyTable.XK_F15); +addStandard("F16", KeyTable.XK_F16); +addStandard("F17", KeyTable.XK_F17); +addStandard("F18", KeyTable.XK_F18); +addStandard("F19", KeyTable.XK_F19); +addStandard("F20", KeyTable.XK_F20); +addStandard("F21", KeyTable.XK_F21); +addStandard("F22", KeyTable.XK_F22); +addStandard("F23", KeyTable.XK_F23); +addStandard("F24", KeyTable.XK_F24); +addStandard("F25", KeyTable.XK_F25); +addStandard("F26", KeyTable.XK_F26); +addStandard("F27", KeyTable.XK_F27); +addStandard("F28", KeyTable.XK_F28); +addStandard("F29", KeyTable.XK_F29); +addStandard("F30", KeyTable.XK_F30); +addStandard("F31", KeyTable.XK_F31); +addStandard("F32", KeyTable.XK_F32); +addStandard("F33", KeyTable.XK_F33); +addStandard("F34", KeyTable.XK_F34); +addStandard("F35", KeyTable.XK_F35); +// - Soft1... + +// 2.10. Multimedia Keys + +// - ChannelDown +// - ChannelUp +addStandard("Close", KeyTable.XF86XK_Close); +addStandard("MailForward", KeyTable.XF86XK_MailForward); +addStandard("MailReply", KeyTable.XF86XK_Reply); +addStandard("MainSend", KeyTable.XF86XK_Send); +addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward); +addStandard("MediaPause", KeyTable.XF86XK_AudioPause); +addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay); +addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord); +addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind); +addStandard("MediaStop", KeyTable.XF86XK_AudioStop); +addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext); +addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev); +addStandard("New", KeyTable.XF86XK_New); +addStandard("Open", KeyTable.XF86XK_Open); +addStandard("Print", KeyTable.XK_Print); +addStandard("Save", KeyTable.XF86XK_Save); +addStandard("SpellCheck", KeyTable.XF86XK_Spell); + +// 2.11. Multimedia Numpad Keys + +// - Key11 +// - Key12 + +// 2.12. Audio Keys + +// - AudioBalanceLeft +// - AudioBalanceRight +// - AudioBassDown +// - AudioBassBoostDown +// - AudioBassBoostToggle +// - AudioBassBoostUp +// - AudioBassUp +// - AudioFaderFront +// - AudioFaderRear +// - AudioSurroundModeNext +// - AudioTrebleDown +// - AudioTrebleUp +addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume); +addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume); +addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute); +// - MicrophoneToggle +// - MicrophoneVolumeDown +// - MicrophoneVolumeUp +addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute); + +// 2.13. Speech Keys + +// - SpeechCorrectionList +// - SpeechInputToggle + +// 2.14. Application Keys + +addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator); +addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar); +addStandard("LaunchMail", KeyTable.XF86XK_Mail); +addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia); +addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music); +addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer); +addStandard("LaunchPhone", KeyTable.XF86XK_Phone); +addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver); +addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel); +addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW); +addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam); +addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word); + +// 2.15. Browser Keys + +addStandard("BrowserBack", KeyTable.XF86XK_Back); +addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites); +addStandard("BrowserForward", KeyTable.XF86XK_Forward); +addStandard("BrowserHome", KeyTable.XF86XK_HomePage); +addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh); +addStandard("BrowserSearch", KeyTable.XF86XK_Search); +addStandard("BrowserStop", KeyTable.XF86XK_Stop); + +// 2.16. Mobile Phone Keys + +// - A whole bunch... + +// 2.17. TV Keys + +// - A whole bunch... + +// 2.18. Media Controller Keys + +// - A whole bunch... +addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust); +addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack); +addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay); +addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen); +addStandard("Subtitle", KeyTable.XF86XK_Subtitle); +addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode); + +// Extra: Numpad + +addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal); +addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add); +addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract); +addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply); +addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide); +addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal); +addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator); +addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0); +addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1); +addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2); +addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3); +addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4); +addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5); +addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6); +addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7); +addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8); +addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9); + +export default DOMKeyTable; diff --git a/core/input/fixedkeys.js b/core/input/fixedkeys.js index 2a2594e8..6dd42223 100644 --- a/core/input/fixedkeys.js +++ b/core/input/fixedkeys.js @@ -5,108 +5,123 @@ */ /* - * 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. + * Fallback mapping between HTML key codes (physical keys) and + * HTML key values. This only works for keys that don't vary + * between layouts. We also omit those who manage fine by mapping the + * Unicode representation. + * + * See https://www.w3.org/TR/uievents-code/ for possible codes. + * See https://www.w3.org/TR/uievents-key/ for possible values. */ -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, + +// 3.1.1.1. Writing System Keys + + 'Backspace': 'Backspace', + +// 3.1.1.2. Functional Keys + + 'AltLeft': 'Alt', + 'AltRight': 'Alt', // This could also be 'AltGraph' + 'CapsLock': 'CapsLock', + 'ContextMenu': 'ContextMenu', + 'ControlLeft': 'Control', + 'ControlRight': 'Control', + 'Enter': 'Enter', + 'MetaLeft': 'Meta', + 'MetaRight': 'Meta', + 'ShiftLeft': 'Shift', + 'ShiftRight': 'Shift', + 'Tab': '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, + +// 3.1.2. Control Pad Section + + 'Delete': 'Delete', + 'End': 'End', + 'Help': 'Help', + 'Home': 'Home', + 'Insert': 'Insert', + 'PageDown': 'PageDown', + 'PageUp': 'PageUp', + +// 3.1.3. Arrow Pad Section + + 'ArrowDown': 'ArrowDown', + 'ArrowLeft': 'ArrowLeft', + 'ArrowRight': 'ArrowRight', + 'ArrowUp': 'ArrowUp', + +// 3.1.4. Numpad Section + + 'NumLock': 'NumLock', + 'NumpadBackspace': 'Backspace', + 'NumpadClear': 'Clear', + +// 3.1.5. Function Section + + 'Escape': 'Escape', + 'F1': 'F1', + 'F2': 'F2', + 'F3': 'F3', + 'F4': 'F4', + 'F5': 'F5', + 'F6': 'F6', + 'F7': 'F7', + 'F8': 'F8', + 'F9': 'F9', + 'F10': 'F10', + 'F11': 'F11', + 'F12': 'F12', + 'F13': 'F13', + 'F14': 'F14', + 'F15': 'F15', + 'F16': 'F16', + 'F17': 'F17', + 'F18': 'F18', + 'F19': 'F19', + 'F20': 'F20', + 'F21': 'F21', + 'F22': 'F22', + 'F23': 'F23', + 'F24': 'F24', + 'F25': 'F25', + 'F26': 'F26', + 'F27': 'F27', + 'F28': 'F28', + 'F29': 'F29', + 'F30': 'F30', + 'F31': 'F31', + 'F32': 'F32', + 'F33': 'F33', + 'F34': 'F34', + 'F35': 'F35', + 'PrintScreen': 'PrintScreen', + 'ScrollLock': 'ScrollLock', + 'Pause': 'Pause', + +// 3.1.6. Media Keys + + 'BrowserBack': 'BrowserBack', + 'BrowserFavorites': 'BrowserFavorites', + 'BrowserForward': 'BrowserForward', + 'BrowserHome': 'BrowserHome', + 'BrowserRefresh': 'BrowserRefresh', + 'BrowserSearch': 'BrowserSearch', + 'BrowserStop': 'BrowserStop', + 'Eject': 'Eject', + 'LaunchApp1': 'LaunchMyComputer', + 'LaunchApp2': 'LaunchCalendar', + 'LaunchMail': 'LaunchMail', + 'MediaPlayPause': 'MediaPlay', + 'MediaStop': 'MediaStop', + 'MediaTrackNext': 'MediaTrackNext', + 'MediaTrackPrevious': 'MediaTrackPrevious', + 'Power': 'Power', + 'Sleep': 'Sleep', + 'AudioVolumeDown': 'AudioVolumeDown', + 'AudioVolumeMute': 'AudioVolumeMute', + 'AudioVolumeUp': 'AudioVolumeUp', + 'WakeUp': 'WakeUp', }; diff --git a/core/input/keysym.js b/core/input/keysym.js index 06ea70f8..ba58be68 100644 --- a/core/input/keysym.js +++ b/core/input/keysym.js @@ -12,6 +12,37 @@ export default { XK_Escape: 0xff1b, XK_Delete: 0xffff, /* Delete, rubout */ + /* International & multi-key character composition */ + + XK_Multi_key: 0xff20, /* Multi-key character compose */ + XK_Codeinput: 0xff37, + XK_SingleCandidate: 0xff3c, + XK_MultipleCandidate: 0xff3d, + XK_PreviousCandidate: 0xff3e, + + /* Japanese keyboard support */ + + XK_Kanji: 0xff21, /* Kanji, Kanji convert */ + XK_Muhenkan: 0xff22, /* Cancel Conversion */ + XK_Henkan_Mode: 0xff23, /* Start/Stop Conversion */ + XK_Henkan: 0xff23, /* Alias for Henkan_Mode */ + XK_Romaji: 0xff24, /* to Romaji */ + XK_Hiragana: 0xff25, /* to Hiragana */ + XK_Katakana: 0xff26, /* to Katakana */ + XK_Hiragana_Katakana: 0xff27, /* Hiragana/Katakana toggle */ + XK_Zenkaku: 0xff28, /* to Zenkaku */ + XK_Hankaku: 0xff29, /* to Hankaku */ + XK_Zenkaku_Hankaku: 0xff2a, /* Zenkaku/Hankaku toggle */ + XK_Touroku: 0xff2b, /* Add to Dictionary */ + XK_Massyo: 0xff2c, /* Delete from Dictionary */ + XK_Kana_Lock: 0xff2d, /* Kana Lock */ + XK_Kana_Shift: 0xff2e, /* Kana Shift */ + XK_Eisu_Shift: 0xff2f, /* Alphanumeric Shift */ + XK_Eisu_toggle: 0xff30, /* Alphanumeric toggle */ + XK_Kanji_Bangou: 0xff37, /* Codeinput */ + XK_Zen_Koho: 0xff3d, /* Multiple/All Candidate(s) */ + XK_Mae_Koho: 0xff3e, /* Previous Candidate */ + /* Cursor control & motion */ XK_Home: 0xff50, @@ -171,7 +202,17 @@ export default { XK_Hyper_L: 0xffed, /* Left hyper */ XK_Hyper_R: 0xffee, /* Right hyper */ + /* + * Keyboard (XKB) Extension function and modifier keys + * (from Appendix C of "The X Keyboard Extension: Protocol Specification") + * Byte 3 = 0xfe + */ + XK_ISO_Level3_Shift: 0xfe03, /* AltGr */ + XK_ISO_Next_Group: 0xfe08, + XK_ISO_Prev_Group: 0xfe0a, + XK_ISO_First_Group: 0xfe0c, + XK_ISO_Last_Group: 0xfe0e, /* * Latin 1 @@ -378,6 +419,15 @@ export default { XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ + /* + * Korean + * Byte 3 = 0x0e + */ + + XK_Hangul: 0xff31, /* Hangul start/stop(toggle) */ + XK_Hangul_Hanja: 0xff34, /* Start Hangul->Hanja Conversion */ + XK_Hangul_Jeonja: 0xff38, /* Jeonja mode */ + /* * XFree86 vendor specific keysyms. * diff --git a/core/input/util.js b/core/input/util.js index d755c20f..4335b0bf 100644 --- a/core/input/util.js +++ b/core/input/util.js @@ -2,10 +2,17 @@ import KeyTable from "./keysym.js"; import keysyms from "./keysymdef.js"; import vkeys from "./vkeys.js"; import fixedkeys from "./fixedkeys.js"; +import DOMKeyTable from "./domkeytable.js"; function isMac() { return navigator && !!(/mac/i).exec(navigator.platform); } +function isIE() { + return navigator && !!(/trident/i).exec(navigator.userAgent); +} +function isEdge() { + return navigator && !!(/edge/i).exec(navigator.userAgent); +} // Get 'KeyboardEvent.code', handling legacy browsers export function getKeycode(evt){ @@ -67,88 +74,91 @@ export function getKeycode(evt){ return 'Unidentified'; } -// Get the most reliable keysym value we can get from a key event -export function getKeysym(evt){ +// Get 'KeyboardEvent.key', handling legacy browsers +export function getKey(evt) { + // Are we getting a proper key value? + if (evt.key !== undefined) { + // IE and Edge use some ancient version of the spec + // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/ + switch (evt.key) { + case 'Spacebar': return ' '; + case 'Esc': return 'Escape'; + case 'Scroll': return 'ScrollLock'; + case 'Win': return 'Meta'; + case 'Apps': return 'ContextMenu'; + case 'Up': return 'ArrowUp'; + case 'Left': return 'ArrowLeft'; + case 'Right': return 'ArrowRight'; + case 'Down': return 'ArrowDown'; + case 'Del': return 'Delete'; + case 'Divide': return '/'; + case 'Multiply': return '*'; + case 'Subtract': return '-'; + case 'Add': return '+'; + case 'Decimal': return evt.char; + } - // We start with layout independent keys + // Mozilla isn't fully in sync with the spec yet + switch (evt.key) { + case 'OS': return 'Meta'; + } + + // IE and Edge have broken handling of AltGraph so we cannot + // trust them for printable characters + if ((evt.key.length !== 1) || (!isIE() && !isEdge())) { + return evt.key; + } + } + + // Try to deduce it based on the physical key 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; - } + // If that failed, then see if we have a printable character + if (evt.charCode) { + return String.fromCharCode(evt.charCode); } - // Or the numpad - if (evt.location === 3) { - var key = evt.key; + // At this point we have nothing left to go on + return 'Unidentified'; +} - // 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; +// Get the most reliable keysym value we can get from a key event +export function getKeysym(evt){ + var key = getKey(evt); + + if (key === 'Unidentified') { + return null; + } + + // First look up special keys + if (key in DOMKeyTable) { + var location = evt.location; + + // Safari screws up location for the right cmd key + if ((key === 'Meta') && (location === 0)) { + location = 2; } - // Safari doesn't support KeyboardEvent.key yet - if ((key === undefined) && (evt.charCode)) { - key = String.fromCharCode(evt.charCode); + if ((location === undefined) || (location > 3)) { + location = 0; } - 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; - } + return DOMKeyTable[key][location]; } // 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; + // Special key? (FIXME: Should have been caught earlier) + if (key.length !== 1) { + return null; } + codepoint = key.charCodeAt(); if (codepoint) { return keysyms.lookup(codepoint); } diff --git a/tests/test.helper.js b/tests/test.helper.js index f705cff0..e0ae80ac 100644 --- a/tests/test.helper.js +++ b/tests/test.helper.js @@ -98,39 +98,104 @@ describe('Helpers', function() { }); }); - describe('getKeysym', function() { + describe('getKey', function() { it('should prefer key', function() { - expect(KeyboardUtil.getKeysym({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x61); + expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a'); + }); + it('should map legacy values', function() { + expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' '); + expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft'); + expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta'); + expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta'); + }); + it('should use code if no key', function() { + expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace'); + }); + it('should not use code fallback for character keys', function() { + expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified'); + expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified'); }); it('should use charCode if no key', function() { - expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal(0x01a9); + expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š'); + }); + it('should return Unidentified when it cannot map the key', function() { + expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified'); }); + describe('Broken key AltGraph on IE/Edge', function() { + var origNavigator; + beforeEach(function () { + // window.navigator is a protected read-only property in many + // environments, so we need to redefine it whilst running these + // tests. + origNavigator = Object.getOwnPropertyDescriptor(window, "navigator"); + if (origNavigator === undefined) { + // Object.getOwnPropertyDescriptor() doesn't work + // properly in any version of IE + this.skip(); + } + + Object.defineProperty(window, "navigator", {value: {}}); + if (window.navigator.platform !== undefined) { + // Object.defineProperty() doesn't work properly in old + // versions of Chrome + this.skip(); + } + }); + afterEach(function () { + Object.defineProperty(window, "navigator", origNavigator); + }); + + it('should ignore printable character key on IE', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; + expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); + }); + it('should ignore printable character key on Edge', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; + expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified'); + }); + it('should allow non-printable character key on IE', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"; + expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); + }); + it('should allow non-printable character key on Edge', function() { + window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393"; + expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift'); + }); + }); + }); + + describe('getKeysym', function() { describe('Non-character keys', function() { it('should recognize the right keys', function() { - 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); + expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D); + expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08); + expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09); + expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9); + expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB); + expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B); + expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52); + }); + it('should map left/right side', function() { + expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1); + expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2); + expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3); + expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4); }); 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); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA); + expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).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 return null for unknown keys', function() { + expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null; + expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null; }); - it('should not recognize character keys', function() { - 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; + it('should handle remappings', function() { + expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09); }); }); @@ -145,10 +210,6 @@ describe('Helpers', function() { 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); diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 1ebbbd4a..a4ac6304 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -187,8 +187,8 @@ describe('Key Event Handling', function() { break; } }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); expect(count).to.be.equal(2); }); it('should change left Super to Alt', function(done) { @@ -198,7 +198,7 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('MetaLeft'); done(); }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); }); it('should change right Super to left Super', function(done) { var kbd = new Keyboard({ @@ -207,7 +207,7 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('MetaRight'); done(); }}); - kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); }); }); @@ -280,8 +280,8 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); expect(times_called).to.be.equal(7); @@ -299,8 +299,8 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); // Next a normal character kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); @@ -329,10 +329,10 @@ describe('Key Event Handling', function() { } }}); // First the modifier combo - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); - kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); + kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); // Then one of the keys again - kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control'})); + kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); expect(times_called).to.be.equal(3); }); });