/** * Copyright (c) 2014 The xterm.js authors. All rights reserved. * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) * @license MIT */ import { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types'; import { C0 } from 'common/data/EscapeSequences'; // reg + shift key mappings for digits and special chars const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = { // digits 0-9 48: ['0', ')'], 49: ['1', '!'], 50: ['2', '@'], 51: ['3', '#'], 52: ['4', '$'], 53: ['5', '%'], 54: ['6', '^'], 55: ['7', '&'], 56: ['8', '*'], 57: ['9', '('], // special chars 186: [';', ':'], 187: ['=', '+'], 188: [',', '<'], 189: ['-', '_'], 190: ['.', '>'], 191: ['/', '?'], 192: ['`', '~'], 219: ['[', '{'], 220: ['\\', '|'], 221: [']', '}'], 222: ['\'', '"'] }; export function evaluateKeyboardEvent( ev: IKeyboardEvent, applicationCursorMode: boolean, isMac: boolean, macOptionIsMeta: boolean ): IKeyboardResult { const result: IKeyboardResult = { type: KeyboardResultType.SEND_KEY, // Whether to cancel event propagation (NOTE: this may not be needed since the event is // canceled at the end of keyDown cancel: false, // The new key even to emit key: undefined }; const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0); switch (ev.keyCode) { case 0: if (ev.key === 'UIKeyInputUpArrow') { if (applicationCursorMode) { result.key = C0.ESC + 'OA'; } else { result.key = C0.ESC + '[A'; } } else if (ev.key === 'UIKeyInputLeftArrow') { if (applicationCursorMode) { result.key = C0.ESC + 'OD'; } else { result.key = C0.ESC + '[D'; } } else if (ev.key === 'UIKeyInputRightArrow') { if (applicationCursorMode) { result.key = C0.ESC + 'OC'; } else { result.key = C0.ESC + '[C'; } } else if (ev.key === 'UIKeyInputDownArrow') { if (applicationCursorMode) { result.key = C0.ESC + 'OB'; } else { result.key = C0.ESC + '[B'; } } break; case 8: // backspace if (ev.shiftKey) { result.key = C0.BS; // ^H break; } else if (ev.altKey) { result.key = C0.ESC + C0.DEL; // \e ^? break; } result.key = C0.DEL; // ^? break; case 9: // tab if (ev.shiftKey) { result.key = C0.ESC + '[Z'; break; } result.key = C0.HT; result.cancel = true; break; case 13: // return/enter result.key = ev.altKey ? C0.ESC + C0.CR : C0.CR; result.cancel = true; break; case 27: // escape result.key = C0.ESC; if (ev.altKey) { result.key = C0.ESC + C0.ESC; } result.cancel = true; break; case 37: // left-arrow if (ev.metaKey) { break; } if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D'; // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3D') { result.key = C0.ESC + (isMac ? 'b' : '[1;5D'); } } else if (applicationCursorMode) { result.key = C0.ESC + 'OD'; } else { result.key = C0.ESC + '[D'; } break; case 39: // right-arrow if (ev.metaKey) { break; } if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C'; // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (result.key === C0.ESC + '[1;3C') { result.key = C0.ESC + (isMac ? 'f' : '[1;5C'); } } else if (applicationCursorMode) { result.key = C0.ESC + 'OC'; } else { result.key = C0.ESC + '[C'; } break; case 38: // up-arrow if (ev.metaKey) { break; } if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A'; // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (!isMac && result.key === C0.ESC + '[1;3A') { result.key = C0.ESC + '[1;5A'; } } else if (applicationCursorMode) { result.key = C0.ESC + 'OA'; } else { result.key = C0.ESC + '[A'; } break; case 40: // down-arrow if (ev.metaKey) { break; } if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B'; // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow // http://unix.stackexchange.com/a/108106 // macOS uses different escape sequences than linux if (!isMac && result.key === C0.ESC + '[1;3B') { result.key = C0.ESC + '[1;5B'; } } else if (applicationCursorMode) { result.key = C0.ESC + 'OB'; } else { result.key = C0.ESC + '[B'; } break; case 45: // insert if (!ev.shiftKey && !ev.ctrlKey) { // or + are used to // copy-paste on some systems. result.key = C0.ESC + '[2~'; } break; case 46: // delete if (modifiers) { result.key = C0.ESC + '[3;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[3~'; } break; case 36: // home if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H'; } else if (applicationCursorMode) { result.key = C0.ESC + 'OH'; } else { result.key = C0.ESC + '[H'; } break; case 35: // end if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F'; } else if (applicationCursorMode) { result.key = C0.ESC + 'OF'; } else { result.key = C0.ESC + '[F'; } break; case 33: // page up if (ev.shiftKey) { result.type = KeyboardResultType.PAGE_UP; } else { result.key = C0.ESC + '[5~'; } break; case 34: // page down if (ev.shiftKey) { result.type = KeyboardResultType.PAGE_DOWN; } else { result.key = C0.ESC + '[6~'; } break; case 112: // F1-F12 if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P'; } else { result.key = C0.ESC + 'OP'; } break; case 113: if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q'; } else { result.key = C0.ESC + 'OQ'; } break; case 114: if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R'; } else { result.key = C0.ESC + 'OR'; } break; case 115: if (modifiers) { result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S'; } else { result.key = C0.ESC + 'OS'; } break; case 116: if (modifiers) { result.key = C0.ESC + '[15;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[15~'; } break; case 117: if (modifiers) { result.key = C0.ESC + '[17;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[17~'; } break; case 118: if (modifiers) { result.key = C0.ESC + '[18;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[18~'; } break; case 119: if (modifiers) { result.key = C0.ESC + '[19;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[19~'; } break; case 120: if (modifiers) { result.key = C0.ESC + '[20;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[20~'; } break; case 121: if (modifiers) { result.key = C0.ESC + '[21;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[21~'; } break; case 122: if (modifiers) { result.key = C0.ESC + '[23;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[23~'; } break; case 123: if (modifiers) { result.key = C0.ESC + '[24;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[24~'; } break; default: // a-z and space if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { result.key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { result.key = C0.NUL; } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { // escape, file sep, group sep, record sep, unit sep result.key = String.fromCharCode(ev.keyCode - 51 + 27); } else if (ev.keyCode === 56) { result.key = C0.DEL; } else if (ev.keyCode === 219) { result.key = C0.ESC; } else if (ev.keyCode === 220) { result.key = C0.FS; } else if (ev.keyCode === 221) { result.key = C0.GS; } } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) { // On macOS this is a third level shift when !macOptionIsMeta. Use instead. const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; const key = keyMapping?.[!ev.shiftKey ? 0 : 1]; if (key) { result.key = C0.ESC + key; } else if (ev.keyCode >= 65 && ev.keyCode <= 90) { const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; result.key = C0.ESC + String.fromCharCode(keyCode); } } else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a result.type = KeyboardResultType.SELECT_ALL; } } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) { // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc. result.key = ev.key; } else if (ev.key && ev.ctrlKey) { if (ev.key === '_') { // ^_ result.key = C0.US; } } break; } return result; }