| // Copyright 2012 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {hterm} from '../index.js'; |
| |
| /** |
| * @typedef {{ |
| * keyCap: string, |
| * normal: !hterm.Keyboard.KeyDefAction, |
| * control: !hterm.Keyboard.KeyDefAction, |
| * alt: !hterm.Keyboard.KeyDefAction, |
| * meta: !hterm.Keyboard.KeyDefAction, |
| * }} |
| */ |
| hterm.Keyboard.KeyDef; |
| |
| /** |
| * @typedef {function(!KeyboardEvent, !hterm.Keyboard.KeyDef): |
| * !hterm.Keyboard.KeyAction} |
| */ |
| hterm.Keyboard.KeyDefFunction; |
| |
| /** |
| * @typedef {function(!KeyboardEvent, !hterm.Keyboard.KeyDef): |
| * !hterm.Keyboard.KeyDefFunction|!hterm.Keyboard.KeyAction} |
| */ |
| hterm.Keyboard.KeyDefFunctionProvider; |
| |
| /** |
| * @typedef {( |
| * !hterm.Keyboard.KeyAction| |
| * !hterm.Keyboard.KeyDefFunction| |
| * !hterm.Keyboard.KeyDefFunctionProvider |
| * )} |
| */ |
| hterm.Keyboard.KeyDefAction; |
| |
| /** |
| * The default key map for hterm. |
| * |
| * Contains a mapping of keyCodes to keyDefs (aka key definitions). The key |
| * definition tells the hterm.Keyboard class how to handle keycodes. |
| * |
| * This should work for most cases, as the printable characters get handled |
| * in the keypress event. In that case, even if the keycap is wrong in the |
| * key map, the correct character should be sent. |
| * |
| * Different layouts, such as Dvorak should work with this keymap, as those |
| * layouts typically move keycodes around on the keyboard without disturbing |
| * the actual keycaps. |
| * |
| * There may be issues with control keys on non-US keyboards or with keyboards |
| * that very significantly from the expectations here, in which case we may |
| * have to invent new key maps. |
| * |
| * The sequences defined in this key map come from [XTERM] as referenced in |
| * vt.js, starting with the section titled "Alt and Meta Keys". |
| * |
| * @param {!hterm.Keyboard} keyboard |
| * @constructor |
| */ |
| hterm.Keyboard.KeyMap = function(keyboard) { |
| this.keyboard = keyboard; |
| /** @type {!Object<number, !hterm.Keyboard.KeyDef>} */ |
| this.keyDefs = {}; |
| this.reset(); |
| }; |
| |
| /** |
| * Add a single key definition. |
| * |
| * The definition is an object containing the following fields: 'keyCap', |
| * 'normal', 'control', 'alt', and 'meta'. |
| * |
| * - keyCap is a string identifying the key on the keyboard. For printable |
| * keys, the key cap should be exactly two characters, starting with the |
| * unshifted version. For example, 'aA', 'bB', '1!' and '=+'. For |
| * non-printable the key cap should be surrounded in square braces, as in |
| * '[INS]', '[LEFT]'. By convention, non-printable keycaps are in uppercase |
| * but this is not a strict requirement. |
| * |
| * - Normal is the action that should be performed when the key is pressed |
| * in the absence of any modifier. See below for the supported actions. |
| * |
| * - Control is the action that should be performed when the key is pressed |
| * along with the control modifier. See below for the supported actions. |
| * |
| * - Alt is the action that should be performed when the key is pressed |
| * along with the alt modifier. See below for the supported actions. |
| * |
| * - Meta is the action that should be performed when the key is pressed |
| * along with the meta modifier. See below for the supported actions. |
| * |
| * Actions can be one of the hterm.Keyboard.KeyActions as documented below, |
| * a literal string, or an array. If the action is a literal string then |
| * the string is sent directly to the host. If the action is an array it |
| * is taken to be an escape sequence that may be altered by modifier keys. |
| * The second-to-last element of the array will be overwritten with the |
| * state of the modifier keys, as specified in the final table of "PC-Style |
| * Function Keys" from [XTERM]. |
| * |
| * @param {number} keyCode The KeyboardEvent.keyCode to match against. |
| * @param {!hterm.Keyboard.KeyDef} def The actions this key triggers. |
| */ |
| hterm.Keyboard.KeyMap.prototype.addKeyDef = function(keyCode, def) { |
| if (keyCode in this.keyDefs) { |
| console.warn('Duplicate keyCode: ' + keyCode); |
| } |
| |
| this.keyDefs[keyCode] = def; |
| }; |
| |
| /** |
| * Set up the default state for this keymap. |
| */ |
| hterm.Keyboard.KeyMap.prototype.reset = function() { |
| this.keyDefs = {}; |
| |
| const CANCEL = hterm.Keyboard.KeyActions.CANCEL; |
| const DEFAULT = hterm.Keyboard.KeyActions.DEFAULT; |
| const PASS = hterm.Keyboard.KeyActions.PASS; |
| const STRIP = hterm.Keyboard.KeyActions.STRIP; |
| |
| /** |
| * This function is used by the "macro" functions below. It makes it |
| * possible to use the call() macro as an argument to any other macro. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} action |
| * @param {!KeyboardEvent} e |
| * @param {!hterm.Keyboard.KeyDef} k |
| * @return {!hterm.Keyboard.KeyAction} |
| */ |
| const resolve = (action, e, k) => { |
| if (typeof action == 'function') { |
| const keyDefFn = /** @type {!hterm.Keyboard.KeyDefFunction} */ (action); |
| return keyDefFn.call(this, e, k); |
| } |
| return action; |
| }; |
| |
| /** |
| * If not application keypad a, else b. The keys that care about |
| * application keypad ignore it when the key is modified. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| /* TODO(crbug.com/1065216): Delete this if no longer needed. |
| const ak = (a, b) => { |
| return (e, k) => { |
| const action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || |
| !this.keyboard.applicationKeypad) ? a : b; |
| return resolve(action, e, k); |
| }; |
| }; |
| */ |
| |
| /** |
| * If mod or not application cursor a, else b. The keys that care about |
| * application cursor ignore it when the key is modified. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| const ac = (a, b) => { |
| return (e, k) => { |
| const action = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || |
| !this.keyboard.applicationCursor) ? a : b; |
| return resolve(action, e, k); |
| }; |
| }; |
| |
| /** |
| * If not backspace-sends-backspace keypad a, else b. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| const bs = (a, b) => { |
| return (e, k) => { |
| const action = !this.keyboard.backspaceSendsBackspace ? a : b; |
| return resolve(action, e, k); |
| }; |
| }; |
| |
| /** |
| * If not e.shiftKey a, else b. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| const sh = (a, b) => { |
| return (e, k) => { |
| const action = !e.shiftKey ? a : b; |
| e.maskShiftKey = true; |
| return resolve(action, e, k); |
| }; |
| }; |
| |
| /** |
| * If not e.altKey a, else b. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| const alt = (a, b) => { |
| return (e, k) => { |
| const action = !e.altKey ? a : b; |
| return resolve(action, e, k); |
| }; |
| }; |
| |
| /** |
| * If no modifiers a, else b. |
| * |
| * @param {!hterm.Keyboard.KeyDefAction} a |
| * @param {!hterm.Keyboard.KeyDefAction} b |
| * @return {!hterm.Keyboard.KeyDefFunction} |
| */ |
| const mod = (a, b) => { |
| return (e, k) => { |
| const action = !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) ? |
| a : b; |
| return resolve(action, e, k); |
| }; |
| }; |
| |
| /** |
| * Compute a control character for a given character. |
| * |
| * @param {string} ch |
| * @return {string} |
| */ |
| const ctl = (ch) => String.fromCharCode(ch.charCodeAt(0) - 64); |
| |
| /** |
| * Call a method on the keymap instance. |
| * |
| * @param {string} m name of method to call. |
| * @return {( |
| * !hterm.Keyboard.KeyDefFunction| |
| * !hterm.Keyboard.KeyDefFunctionProvider |
| * )} |
| */ |
| const c = (m) => { |
| return (e, k) => this[m](e, k); |
| }; |
| |
| // Ignore if not trapping media keys. |
| const med = (fn) => { |
| return (e, k) => { |
| if (!this.keyboard.mediaKeysAreFKeys) { |
| // Block Back, Forward, and Reload keys to avoid navigating away from |
| // the current page. |
| return (e.keyCode == 166 || e.keyCode == 167 || e.keyCode == 168) ? |
| CANCEL : PASS; |
| } |
| return resolve(fn, e, k); |
| }; |
| }; |
| |
| /** |
| * @param {number} keyCode |
| * @param {string} keyCap |
| * @param {!hterm.Keyboard.KeyDefAction} normal |
| * @param {!hterm.Keyboard.KeyDefAction} control |
| * @param {!hterm.Keyboard.KeyDefAction} alt |
| * @param {!hterm.Keyboard.KeyDefAction} meta |
| */ |
| const add = (keyCode, keyCap, normal, control, alt, meta) => { |
| this.addKeyDef(keyCode, { |
| keyCap: keyCap, |
| normal: normal, |
| control: control, |
| alt: alt, |
| meta: meta, |
| }); |
| }; |
| |
| // Browser-specific differences. |
| // let keycapMute; |
| // let keycapVolDn; |
| // let keycapVolDn |
| let keycapSC; |
| let keycapEP; |
| let keycapMU; |
| if (globalThis.navigator?.userAgent && |
| globalThis.navigator.userAgent.includes('Firefox')) { |
| // Firefox defines some keys uniquely. No other browser defines these in |
| // this way. Some even conflict. The keyCode field isn't well documented |
| // as it isn't standardized. At some point we should switch to "key". |
| // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode |
| // http://unixpapa.com/js/key.html |
| // keycapMute = 181; // Mute |
| // keycapVolDn = 182; // Volume Down |
| // keycapVolUp = 183; // Volume Up |
| keycapSC = 59; // ;: |
| keycapEP = 61; // =+ |
| keycapMU = 173; // -_ |
| |
| // Firefox Italian +*. |
| add(171, '+*', DEFAULT, c('onZoom_'), DEFAULT, c('onZoom_')); |
| } else { |
| // All other browsers use these mappings. |
| // keycapMute = 173; // Mute |
| // keycapVolDn = 174; // Volume Down |
| // keycapVolUp = 175; // Volume Up |
| keycapSC = 186; // ;: |
| keycapEP = 187; // =+ |
| keycapMU = 189; // -_ |
| } |
| |
| const ESC = '\x1b'; |
| const CSI = '\x1b['; |
| const SS3 = '\x1bO'; |
| |
| // These fields are: [keycode, keycap, normal, control, alt, meta] |
| /* eslint-disable no-multi-spaces */ |
| |
| // The browser sends the keycode 0 for some keys. We'll just assume it's |
| // going to do the right thing by default for those keys. |
| add(0, '[UNKNOWN]', PASS, PASS, PASS, PASS); |
| |
| // First row. |
| // These bindings match xterm for lack of a better standard. The emitted |
| // values might look like they're skipping values, but it's what xterm does. |
| // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys |
| add(27, '[ESC]', ESC, DEFAULT, DEFAULT, DEFAULT); |
| add(112, '[F1]', mod(SS3 + 'P', CSI + 'P'), DEFAULT, CSI + '23~', DEFAULT); |
| add(113, '[F2]', mod(SS3 + 'Q', CSI + 'Q'), DEFAULT, CSI + '24~', DEFAULT); |
| add(114, '[F3]', mod(SS3 + 'R', CSI + 'R'), DEFAULT, CSI + '25~', DEFAULT); |
| add(115, '[F4]', mod(SS3 + 'S', CSI + 'S'), DEFAULT, CSI + '26~', DEFAULT); |
| add(116, '[F5]', CSI + '15~', DEFAULT, CSI + '28~', DEFAULT); |
| add(117, '[F6]', CSI + '17~', DEFAULT, CSI + '29~', DEFAULT); |
| add(118, '[F7]', CSI + '18~', DEFAULT, CSI + '31~', DEFAULT); |
| add(119, '[F8]', CSI + '19~', DEFAULT, CSI + '32~', DEFAULT); |
| add(120, '[F9]', CSI + '20~', DEFAULT, CSI + '33~', DEFAULT); |
| add(121, '[F10]', CSI + '21~', DEFAULT, CSI + '34~', DEFAULT); |
| add(122, '[F11]', c('onF11_'), DEFAULT, CSI + '42~', DEFAULT); |
| add(123, '[F12]', CSI + '24~', DEFAULT, CSI + '43~', DEFAULT); |
| |
| // Second row. |
| add(192, '`~', DEFAULT, sh(ctl('@'), ctl('^')), DEFAULT, PASS); |
| add(49, '1!', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(50, '2@', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(51, '3#', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(52, '4$', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(53, '5%', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(54, '6^', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(55, '7&', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(56, '8*', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(57, '9(', DEFAULT, c('onCtrlNum_'), c('onAltNum_'), c('onMetaNum_')); |
| add(48, '0)', DEFAULT, c('onZoom_'), c('onAltNum_'), c('onZoom_')); |
| add(keycapMU, '-_', DEFAULT, c('onZoom_'), DEFAULT, c('onZoom_')); |
| add(keycapEP, '=+', DEFAULT, c('onZoom_'), DEFAULT, c('onZoom_')); |
| |
| add(8, '[BKSP]', bs('\x7f', '\b'), bs('\b', '\x7f'), DEFAULT, DEFAULT); |
| |
| // Third row. |
| add(9, '[TAB]', sh('\t', CSI + 'Z'), c('onCtrlTab_'), PASS, DEFAULT); |
| add(81, 'qQ', DEFAULT, ctl('Q'), DEFAULT, DEFAULT); |
| add(87, 'wW', DEFAULT, c('onCtrlW_'), DEFAULT, DEFAULT); |
| add(69, 'eE', DEFAULT, ctl('E'), DEFAULT, DEFAULT); |
| add(82, 'rR', DEFAULT, ctl('R'), DEFAULT, DEFAULT); |
| add(84, 'tT', DEFAULT, c('onCtrlT_'), DEFAULT, DEFAULT); |
| add(89, 'yY', DEFAULT, ctl('Y'), DEFAULT, DEFAULT); |
| add(85, 'uU', DEFAULT, ctl('U'), DEFAULT, DEFAULT); |
| add(73, 'iI', DEFAULT, ctl('I'), DEFAULT, DEFAULT); |
| add(79, 'oO', DEFAULT, ctl('O'), DEFAULT, DEFAULT); |
| add(80, 'pP', DEFAULT, ctl('P'), DEFAULT, DEFAULT); |
| add(219, '[{', DEFAULT, ctl('['), DEFAULT, DEFAULT); |
| add(221, ']}', DEFAULT, ctl(']'), DEFAULT, DEFAULT); |
| add(220, '\\|', DEFAULT, ctl('\\'), DEFAULT, DEFAULT); |
| |
| // Fourth row. We let Ctrl+Shift+J pass for Chrome DevTools. |
| // To be compliant with xterm's behavior for modifiers on Enter |
| // would mean maximizing the window with Alt+Enter... so we don't |
| // want to do that. Our behavior on Enter is what most other |
| // modern emulators do. |
| add(20, '[CAPS]', PASS, PASS, PASS, DEFAULT); |
| add(65, 'aA', DEFAULT, sh(ctl('A'), c('onCtrlShiftA_')), DEFAULT, DEFAULT); |
| add(83, 'sS', DEFAULT, ctl('S'), DEFAULT, DEFAULT); |
| add(68, 'dD', DEFAULT, ctl('D'), DEFAULT, DEFAULT); |
| add(70, 'fF', DEFAULT, sh(ctl('F'), c('onCtrlShiftF_')), DEFAULT, DEFAULT); |
| add(71, 'gG', DEFAULT, ctl('G'), DEFAULT, DEFAULT); |
| add(72, 'hH', DEFAULT, ctl('H'), DEFAULT, DEFAULT); |
| add(74, 'jJ', DEFAULT, sh(ctl('J'), PASS), DEFAULT, DEFAULT); |
| add(75, 'kK', DEFAULT, sh(ctl('K'), c('onClear_')), DEFAULT, DEFAULT); |
| add(76, 'lL', DEFAULT, sh(ctl('L'), PASS), DEFAULT, DEFAULT); |
| add(keycapSC, ';:', DEFAULT, STRIP, DEFAULT, DEFAULT); |
| add(222, '\'"', DEFAULT, STRIP, DEFAULT, DEFAULT); |
| add(13, '[ENTER]', '\r', DEFAULT, DEFAULT, DEFAULT); |
| |
| // Fifth row. This includes the copy/paste shortcuts. On some |
| // platforms it's Ctrl+C/V, on others it's Meta+C/V. We assume either |
| // Ctrl+C/Meta+C should pass to the browser when there is a selection, |
| // and Ctrl+Shift+V/Meta+*+V should always pass to the browser (since |
| // these seem to be recognized as paste too). |
| add(16, '[SHIFT]', PASS, PASS, PASS, DEFAULT); |
| add(90, 'zZ', DEFAULT, ctl('Z'), DEFAULT, DEFAULT); |
| add(88, 'xX', DEFAULT, ctl('X'), DEFAULT, DEFAULT); |
| add(67, 'cC', DEFAULT, c('onCtrlC_'), DEFAULT, c('onMetaC_')); |
| add(86, 'vV', DEFAULT, c('onCtrlV_'), DEFAULT, c('onMetaV_')); |
| add(66, 'bB', DEFAULT, ctl('B'), DEFAULT, DEFAULT); |
| add(78, 'nN', DEFAULT, c('onCtrlN_'), DEFAULT, c('onMetaN_')); |
| add(77, 'mM', DEFAULT, ctl('M'), DEFAULT, DEFAULT); |
| add(188, ',<', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT); |
| add(190, '.>', DEFAULT, alt(STRIP, PASS), DEFAULT, DEFAULT); |
| add(191, '/?', DEFAULT, sh(ctl('_'), ctl('?')), DEFAULT, DEFAULT); |
| |
| // Sixth and final row. |
| add(17, '[CTRL]', PASS, PASS, PASS, PASS); |
| add(18, '[ALT]', PASS, PASS, PASS, PASS); |
| add(91, '[LAPL]', PASS, PASS, PASS, PASS); |
| add(32, ' ', DEFAULT, ctl('@'), DEFAULT, DEFAULT); |
| add(92, '[RAPL]', PASS, PASS, PASS, PASS); |
| add(93, '[RMENU]', PASS, PASS, PASS, PASS); |
| |
| // These things. |
| add(42, '[PRTSCR]', PASS, PASS, PASS, PASS); |
| add(145, '[SCRLK]', PASS, PASS, PASS, PASS); |
| add(19, '[BREAK]', PASS, PASS, PASS, PASS); |
| |
| // The block of six keys above the arrows. |
| add(45, '[INSERT]', c('onKeyInsert_'), DEFAULT, DEFAULT, DEFAULT); |
| add(36, '[HOME]', c('onKeyHome_'), DEFAULT, DEFAULT, DEFAULT); |
| add(33, '[PGUP]', c('onKeyPageUp_'), DEFAULT, DEFAULT, DEFAULT); |
| add(46, '[DEL]', c('onKeyDel_'), DEFAULT, DEFAULT, DEFAULT); |
| add(35, '[END]', c('onKeyEnd_'), DEFAULT, DEFAULT, DEFAULT); |
| add(34, '[PGDOWN]', c('onKeyPageDown_'), DEFAULT, DEFAULT, DEFAULT); |
| |
| // Arrow keys. When unmodified they respect the application cursor state, |
| // otherwise they always send the CSI codes. |
| add(38, '[UP]', c('onKeyArrowUp_'), DEFAULT, DEFAULT, DEFAULT); |
| add(40, '[DOWN]', c('onKeyArrowDown_'), DEFAULT, DEFAULT, DEFAULT); |
| add(39, '[RIGHT]', ac(CSI + 'C', SS3 + 'C'), DEFAULT, DEFAULT, DEFAULT); |
| add(37, '[LEFT]', ac(CSI + 'D', SS3 + 'D'), DEFAULT, DEFAULT, DEFAULT); |
| |
| add(144, '[NUMLOCK]', PASS, PASS, PASS, PASS); |
| |
| // On Apple keyboards, the NumLock key is a Clear key. It also tends to be |
| // what KP5 sends when numlock is off. Not clear if we could do anything |
| // useful with it, so just pass it along. |
| add(12, '[CLEAR]', PASS, PASS, PASS, PASS); |
| |
| // With numlock off, the keypad generates the same key codes as the arrows |
| // and 'block of six' for some keys, and null key codes for the rest. |
| |
| // Keypad with numlock on generates unique key codes... |
| add(96, '[KP0]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(97, '[KP1]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(98, '[KP2]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(99, '[KP3]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(100, '[KP4]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(101, '[KP5]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(102, '[KP6]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(103, '[KP7]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(104, '[KP8]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(105, '[KP9]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(107, '[KP+]', DEFAULT, c('onZoom_'), DEFAULT, c('onZoom_')); |
| add(109, '[KP-]', DEFAULT, c('onZoom_'), DEFAULT, c('onZoom_')); |
| add(106, '[KP*]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(111, '[KP/]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| add(110, '[KP.]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| |
| // OS-specific differences. |
| if (hterm.os == 'cros') { |
| // ChromeOS keyboard top row. The media-keys-are-fkeys preference allows |
| // users to make these always behave as function keys (see those bindings |
| // above for more details). |
| /* eslint-disable max-len */ |
| add(166, '[BACK]', med(mod(SS3 + 'P', CSI + 'P')), DEFAULT, CSI + '23~', DEFAULT); // F1 |
| add(167, '[FWD]', med(mod(SS3 + 'Q', CSI + 'Q')), DEFAULT, CSI + '24~', DEFAULT); // F2 |
| add(168, '[RELOAD]', med(mod(SS3 + 'R', CSI + 'R')), DEFAULT, CSI + '25~', DEFAULT); // F3 |
| add(183, '[FSCR]', med(mod(SS3 + 'S', CSI + 'S')), DEFAULT, CSI + '26~', DEFAULT); // F4 |
| add(182, '[WINS]', med(CSI + '15~'), DEFAULT, CSI + '28~', DEFAULT); // F5 |
| add(216, '[BRIT-]', med(CSI + '17~'), DEFAULT, CSI + '29~', DEFAULT); // F6 |
| add(217, '[BRIT+]', med(CSI + '18~'), DEFAULT, CSI + '31~', DEFAULT); // F7 |
| add(173, '[MUTE]', med(CSI + '19~'), DEFAULT, CSI + '32~', DEFAULT); // F8 |
| add(174, '[VOL-]', med(CSI + '20~'), DEFAULT, CSI + '33~', DEFAULT); // F9 |
| add(175, '[VOL+]', med(CSI + '21~'), DEFAULT, CSI + '34~', DEFAULT); // F10 |
| /* eslint-enable max-len */ |
| |
| // We could make this into F11, but it'd be a bit weird. Chrome allows us |
| // to see this and react, but it doesn't actually allow us to block or |
| // cancel it, so it makes the screen flash/lock still. |
| add(152, '[POWER]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| |
| // The Pixelbook has a slightly different layout. This means half the keys |
| // above are off by one. https://crbug.com/807513 |
| add(179, '[PLAY]', med(CSI + '18~'), DEFAULT, CSI + '31~', DEFAULT); // F7 |
| // The settings / hamburgers / three hot dogs / menu / whatever-it's-called. |
| add(154, '[DOGS]', med(CSI + '23~'), DEFAULT, CSI + '42~', DEFAULT); // F11 |
| |
| // We don't use this for anything, but keep it from popping up by default. |
| add(153, '[ASSIST]', DEFAULT, DEFAULT, DEFAULT, DEFAULT); |
| } |
| /* eslint-enable no-multi-spaces */ |
| }; |
| |
| /** |
| * Either allow the paste or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyInsert_ = function(e) { |
| if (this.keyboard.shiftInsertPaste && e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| return '\x1b[2~'; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyHome_ = function(e) { |
| if (this.keyboard.homeKeysScroll === e.shiftKey) { |
| if ((e.altKey || e.ctrlKey || e.shiftKey) || |
| !this.keyboard.applicationCursor) { |
| return '\x1b[H'; |
| } |
| |
| return '\x1bOH'; |
| } |
| |
| this.keyboard.terminal.scrollHome(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyEnd_ = function(e) { |
| if (this.keyboard.homeKeysScroll === e.shiftKey) { |
| if ((e.altKey || e.ctrlKey || e.shiftKey) || |
| !this.keyboard.applicationCursor) { |
| return '\x1b[F'; |
| } |
| |
| return '\x1bOF'; |
| } |
| |
| this.keyboard.terminal.scrollEnd(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyPageUp_ = function(e) { |
| if (this.keyboard.pageKeysScroll === e.shiftKey) { |
| return '\x1b[5~'; |
| } |
| |
| this.keyboard.terminal.scrollPageUp(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Either send a true DEL, or sub in meta-backspace. |
| * |
| * On ChromeOS, if we know the alt key is down, but we get a DEL event that |
| * claims that the alt key is not pressed, we know the DEL was a synthetic |
| * one from a user that hit alt-backspace. Based on a user pref, we can sub |
| * in meta-backspace in this case. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyDel_ = function(e) { |
| if (this.keyboard.altBackspaceIsMetaBackspace && |
| this.keyboard.altKeyPressed && !e.altKey) { |
| return '\x1b\x7f'; |
| } |
| return '\x1b[3~'; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyPageDown_ = function(e) { |
| if (this.keyboard.pageKeysScroll === e.shiftKey) { |
| return '\x1b[6~'; |
| } |
| |
| this.keyboard.terminal.scrollPageDown(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyArrowUp_ = function(e) { |
| if (!this.keyboard.applicationCursor && e.shiftKey) { |
| this.keyboard.terminal.scrollLineUp(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| |
| return (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || |
| !this.keyboard.applicationCursor) ? '\x1b[A' : '\x1bOA'; |
| }; |
| |
| /** |
| * Either scroll the scrollback buffer or send a key sequence. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onKeyArrowDown_ = function(e) { |
| if (!this.keyboard.applicationCursor && e.shiftKey) { |
| this.keyboard.terminal.scrollLineDown(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| |
| return (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || |
| !this.keyboard.applicationCursor) ? '\x1b[B' : '\x1bOB'; |
| }; |
| |
| /** |
| * Clear the primary/alternate screens and the scrollback buffer. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onClear_ = function(e) { |
| this.keyboard.terminal.wipeContents(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Handle F11 behavior (fullscreen) when not in a window. |
| * |
| * It would be nice to use the Fullscreen API, but the UX is slightly different |
| * a bad way: the Escape key is automatically registered for exiting. If we let |
| * the browser handle F11 directly though, we still get to capture Escape. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onF11_ = function(e) { |
| if (hterm.windowType !== 'popup' && hterm.windowType !== 'app' && |
| !e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } else { |
| return '\x1b[23~'; |
| } |
| }; |
| |
| /** |
| * Either pass Ctrl+1..9 to the browser or send them to the host. |
| * |
| * Note that Ctrl+1 and Ctrl+9 don't actually have special sequences mapped |
| * to them in xterm or gnome-terminal. The range is really Ctrl+2..8, but |
| * we handle 1..9 since Chrome treats the whole range special. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @param {!hterm.Keyboard.KeyDef} keyDef Key definition. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlNum_ = function(e, keyDef) { |
| // Compute a control character for a given character. |
| function ctl(ch) { return String.fromCharCode(ch.charCodeAt(0) - 64); } |
| |
| if (this.keyboard.terminal.passCtrlNumber && !e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| switch (keyDef.keyCap.substr(0, 1)) { |
| case '1': return '1'; |
| case '2': return ctl('@'); |
| case '3': return ctl('['); |
| case '4': return ctl('\\'); |
| case '5': return ctl(']'); |
| case '6': return ctl('^'); |
| case '7': return ctl('_'); |
| case '8': return '\x7f'; |
| case '9': return '9'; |
| } |
| return hterm.Keyboard.KeyActions.PASS; |
| }; |
| |
| /** |
| * Either pass Alt+1..9 to the browser or send them to the host. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onAltNum_ = function(e) { |
| if (this.keyboard.terminal.passAltNumber && !e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| return hterm.Keyboard.KeyActions.DEFAULT; |
| }; |
| |
| /** |
| * Either pass Meta+1..9 to the browser or send them to the host. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onMetaNum_ = function(e) { |
| if (this.keyboard.terminal.passMetaNumber && !e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| return hterm.Keyboard.KeyActions.DEFAULT; |
| }; |
| |
| /** |
| * Either pass ctrl+[shift]+tab to the browser or strip. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlTab_ = function(e) { |
| if (this.keyboard.terminal.passCtrlTab) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| return hterm.Keyboard.KeyActions.STRIP; |
| }; |
| |
| /** |
| * Either pass Ctrl & Shift W (close tab/window) to the browser or send it to |
| * the host. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlW_ = function(e) { |
| if (this.keyboard.terminal.passCtrlW) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| return '\x17'; |
| }; |
| |
| /** |
| * Either pass Ctrl & Shift T (new/reopen tab) to the browser or send it to the |
| * host. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlT_ = function(e) { |
| if (this.keyboard.terminal.passCtrlT) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| return '\x14'; |
| }; |
| |
| /** |
| * Select all lines. Returns a function which selects all lines when invoked |
| * rather than calling selectAll() directly so that users can override their own |
| * key binding for 'Ctrl+Shift+A' if desired. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlShiftA_ = function(e) { |
| this.keyboard.terminal.getScrollPort().selectAll(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Display the find bar. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlShiftF_ = function(e) { |
| this.keyboard.terminal.findBar.display(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |
| |
| /** |
| * Either send a ^C or interpret the keystroke as a copy command. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlC_ = function(e) { |
| const selection = this.keyboard.terminal.getDocument().getSelection(); |
| |
| if (!selection.isCollapsed) { |
| if (this.keyboard.ctrlCCopy && !e.shiftKey) { |
| // Ctrl+C should copy if there is a selection, send ^C otherwise. |
| // Perform the copy by letting the browser handle Ctrl+C. On most |
| // browsers, this is the *only* way to place text on the clipboard from |
| // the 'drive-by' web. |
| if (this.keyboard.terminal.clearSelectionAfterCopy) { |
| setTimeout(selection.collapseToEnd.bind(selection), 50); |
| } |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| if (!this.keyboard.ctrlCCopy && e.shiftKey) { |
| // Ctrl+Shift+C should copy if there is a selection, send ^C otherwise. |
| // Perform the copy manually. This only works in situations where |
| // document.execCommand('copy') is allowed. |
| if (this.keyboard.terminal.clearSelectionAfterCopy) { |
| setTimeout(selection.collapseToEnd.bind(selection), 50); |
| } |
| this.keyboard.terminal.copySelectionToClipboard(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| } |
| |
| return '\x03'; |
| }; |
| |
| /** |
| * Either send a ^N or open a new window to the same location. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlN_ = function(e) { |
| if (this.keyboard.terminal.passCtrlN) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| if (e.shiftKey) { |
| this.keyboard.terminal.onOpenNewSession(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| |
| return '\x0e'; |
| }; |
| |
| /** |
| * Either send a ^V or issue a paste command. |
| * |
| * The default behavior is to paste if the user presses Ctrl+Shift+V, and send |
| * a ^V if the user presses Ctrl+V. This can be flipped with the |
| * 'ctrl-v-paste' preference. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onCtrlV_ = function(e) { |
| if ((!e.shiftKey && this.keyboard.ctrlVPaste) || |
| (e.shiftKey && !this.keyboard.ctrlVPaste)) { |
| // We try to do the pasting ourselves as not all browsers/OSs bind Ctrl+V to |
| // pasting. Notably, on macOS, Ctrl+V/Ctrl+Shift+V do nothing. |
| // However, this might run into web restrictions, so if it fails, we still |
| // fallback to the letting the native behavior (hopefully) save us. |
| if (this.keyboard.terminal.paste() !== false) { |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } else { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| } |
| |
| return '\x16'; |
| }; |
| |
| /** |
| * Either the default action or open a new window to the same location. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onMetaN_ = function(e) { |
| if (e.shiftKey) { |
| this.keyboard.terminal.onOpenNewSession(); |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| |
| return hterm.Keyboard.KeyActions.DEFAULT; |
| }; |
| |
| /** |
| * Either send a Meta+C or allow the browser to interpret the keystroke as a |
| * copy command. |
| * |
| * If there is no selection, or if the user presses Meta+Shift+C, then we'll |
| * transmit an '\x1b' (if metaSendsEscape is on) followed by 'c' or 'C'. |
| * |
| * If there is a selection, we defer to the browser. In this case we clear out |
| * the selection so the user knows we heard them, and also to give them a |
| * chance to send a Meta+C by just hitting the key again. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @param {!hterm.Keyboard.KeyDef} keyDef Key definition. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onMetaC_ = function(e, keyDef) { |
| const document = this.keyboard.terminal.getDocument(); |
| if (e.shiftKey || document.getSelection().isCollapsed) { |
| // If the shift key is being held, or there is no document selection, send |
| // a Meta+C. The keyboard code will add the ESC if metaSendsEscape is true, |
| // we just have to decide between 'c' and 'C'. |
| return keyDef.keyCap.substr(e.shiftKey ? 1 : 0, 1); |
| } |
| |
| // Otherwise let the browser handle it as a copy command. |
| if (this.keyboard.terminal.clearSelectionAfterCopy) { |
| setTimeout(function() { document.getSelection().collapseToEnd(); }, 50); |
| } |
| return hterm.Keyboard.KeyActions.PASS; |
| }; |
| |
| /** |
| * Either PASS or DEFAULT Meta+V, depending on preference. |
| * |
| * Always PASS Meta+Shift+V to allow browser to interpret the keystroke as |
| * a paste command. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onMetaV_ = function(e) { |
| if (e.shiftKey) { |
| return hterm.Keyboard.KeyActions.PASS; |
| } |
| |
| return this.keyboard.passMetaV ? |
| hterm.Keyboard.KeyActions.PASS : |
| hterm.Keyboard.KeyActions.DEFAULT; |
| }; |
| |
| /** |
| * Handle font zooming. |
| * |
| * @param {!KeyboardEvent} e The event to process. |
| * @param {!hterm.Keyboard.KeyDef} keyDef Key definition. |
| * @return {symbol|string} Key action or sequence. |
| */ |
| hterm.Keyboard.KeyMap.prototype.onZoom_ = function(e, keyDef) { |
| if (this.keyboard.ctrlPlusMinusZeroZoom === e.shiftKey) { |
| // If ctrl-PMZ controls zoom and the shift key is pressed, or |
| // ctrl-shift-PMZ controls zoom and this shift key is not pressed, |
| // then we want to send the control code instead of affecting zoom. |
| if (keyDef.keyCap == '-_') { |
| // ^_ |
| return '\x1f'; |
| } |
| |
| // Only ^_ is valid, the other sequences have no meaning. |
| return hterm.Keyboard.KeyActions.CANCEL; |
| } |
| |
| const cap = keyDef.keyCap.substr(0, 1); |
| if (cap == '0') { |
| this.keyboard.terminal.setFontSize(0); |
| } else { |
| let size = this.keyboard.terminal.getFontSize(); |
| |
| if (cap == '-' || keyDef.keyCap == '[KP-]') { |
| size -= 1; |
| } else { |
| size += 1; |
| } |
| |
| this.keyboard.terminal.setFontSize(size); |
| } |
| |
| return hterm.Keyboard.KeyActions.CANCEL; |
| }; |