blob: 826484927c21703562656e94d68fe499cb7995f3 [file] [log] [blame]
rgindafeaf3142012-01-31 15:14:20 -08001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
rgindacbbd7482012-06-13 15:06:16 -07005'use strict';
6
7lib.rtdep('hterm.Keyboard.KeyMap');
8
rgindafeaf3142012-01-31 15:14:20 -08009/**
10 * Keyboard handler.
11 *
12 * Consumes onKey* events and invokes onVTKeystroke on the associated
13 * hterm.Terminal object.
14 *
15 * See also: [XTERM] as referenced in vt.js.
16 *
17 * @param {hterm.Terminal} The Terminal object associated with this keyboard.
18 */
19hterm.Keyboard = function(terminal) {
20 // The parent vt interpreter.
21 this.terminal = terminal;
22
23 // The element we're currently capturing keyboard events for.
24 this.keyboardElement_ = null;
25
26 // The event handlers we are interested in, and their bound callbacks, saved
27 // so they can be uninstalled with removeEventListener, when required.
28 this.handlers_ = [
Andrew de los Reyes574e10e2013-04-04 09:31:57 -070029 ['blur', this.onBlur_.bind(this)],
Robert Ginda11390c52012-09-13 14:53:34 -070030 ['keydown', this.onKeyDown_.bind(this)],
Andrew de los Reyes574e10e2013-04-04 09:31:57 -070031 ['keypress', this.onKeyPress_.bind(this)],
32 ['keyup', this.onKeyUp_.bind(this)],
Robert Ginda11390c52012-09-13 14:53:34 -070033 ['textInput', this.onTextInput_.bind(this)]
rgindafeaf3142012-01-31 15:14:20 -080034 ];
35
36 /**
37 * The current key map.
38 */
rgindacbbd7482012-06-13 15:06:16 -070039 this.keyMap = new hterm.Keyboard.KeyMap(this);
rgindafeaf3142012-01-31 15:14:20 -080040
Robert Gindaf82267d2015-06-09 15:32:04 -070041 this.bindings = new hterm.Keyboard.Bindings(this);
42
rgindafeaf3142012-01-31 15:14:20 -080043 /**
Robert Ginda034ffa72015-02-26 14:02:37 -080044 * none: Disable any AltGr related munging.
45 * ctrl-alt: Assume Ctrl+Alt means AltGr.
46 * left-alt: Assume left Alt means AltGr.
47 * right-alt: Assume right Alt means AltGr.
48 */
49 this.altGrMode = 'none';
50
51 /**
rginda4bba5e12012-06-20 16:15:30 -070052 * If true, Shift-Insert will fall through to the browser as a paste.
53 * If false, the keystroke will be sent to the host.
54 */
Robert Ginda57f03b42012-09-13 11:02:48 -070055 this.shiftInsertPaste = true;
rginda4bba5e12012-06-20 16:15:30 -070056
57 /**
rgindafeaf3142012-01-31 15:14:20 -080058 * If true, home/end will control the terminal scrollbar and shift home/end
59 * will send the VT keycodes. If false then home/end sends VT codes and
60 * shift home/end scrolls.
61 */
Robert Ginda57f03b42012-09-13 11:02:48 -070062 this.homeKeysScroll = false;
rgindafeaf3142012-01-31 15:14:20 -080063
64 /**
65 * Same as above, except for page up/page down.
66 */
Robert Ginda57f03b42012-09-13 11:02:48 -070067 this.pageKeysScroll = false;
rgindafeaf3142012-01-31 15:14:20 -080068
69 /**
Robert Ginda7e5e9522014-03-14 12:23:58 -070070 * If true, Ctrl-Plus/Minus/Zero controls zoom.
71 * If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_,
72 * Ctrl-Plus/Zero do nothing.
73 */
74 this.ctrlPlusMinusZeroZoom = true;
75
76 /**
Robert Gindafb5a3f92014-05-13 14:12:00 -070077 * Ctrl+C copies if true, sends ^C to host if false.
78 * Ctrl+Shift+C sends ^C to host if true, copies if false.
79 */
80 this.ctrlCCopy = false;
81
82 /**
83 * Ctrl+V pastes if true, sends ^V to host if false.
84 * Ctrl+Shift+V sends ^V to host if true, pastes if false.
Leonardo Mesquita61e7c312014-01-04 12:53:12 +010085 */
86 this.ctrlVPaste = false;
87
88 /**
rgindafeaf3142012-01-31 15:14:20 -080089 * Enable/disable application keypad.
90 *
91 * This changes the way numeric keys are sent from the keyboard.
92 */
93 this.applicationKeypad = false;
94
95 /**
96 * Enable/disable the application cursor mode.
97 *
98 * This changes the way cursor keys are sent from the keyboard.
99 */
100 this.applicationCursor = false;
101
102 /**
103 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
104 * the backspace key should send '\x7f'.
105 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700106 this.backspaceSendsBackspace = false;
rgindafeaf3142012-01-31 15:14:20 -0800107
108 /**
Robert Ginda8cb7d902013-06-20 14:37:18 -0700109 * The encoding method for data sent to the host.
110 */
111 this.characterEncoding = 'utf-8';
112
113 /**
rgindafeaf3142012-01-31 15:14:20 -0800114 * Set whether the meta key sends a leading escape or not.
115 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700116 this.metaSendsEscape = true;
rginda30f20f62012-04-05 16:36:19 -0700117
118 /**
Marius Schilder77857b32014-05-14 16:21:26 -0700119 * Set whether meta-V gets passed to host.
120 */
121 this.passMetaV = true;
122
123 /**
rginda39bdf6f2012-04-10 16:50:55 -0700124 * Controls how the alt key is handled.
125 *
126 * escape....... Send an ESC prefix.
127 * 8-bit........ Add 128 to the unshifted character as in xterm.
128 * browser-key.. Wait for the keypress event and see what the browser says.
129 * (This won't work well on platforms where the browser
130 * performs a default action for some alt sequences.)
rginda30f20f62012-04-05 16:36:19 -0700131 *
132 * This setting only matters when alt is distinct from meta (altIsMeta is
133 * false.)
134 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700135 this.altSendsWhat = 'escape';
rginda42ad71d2012-02-16 14:06:28 -0800136
137 /**
138 * Set whether the alt key acts as a meta key, instead of producing 8-bit
139 * characters.
rginda30f20f62012-04-05 16:36:19 -0700140 *
141 * True to enable, false to disable, null to autodetect based on platform.
rginda42ad71d2012-02-16 14:06:28 -0800142 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700143 this.altIsMeta = false;
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700144
145 /**
146 * If true, tries to detect DEL key events that are from alt-backspace on
147 * Chrome OS vs from a true DEL key press.
148 *
149 * Background: At the time of writing, on Chrome OS, alt-backspace is mapped
150 * to DEL. Some users may be happy with this, but others may be frustrated
151 * that it's impossible to do meta-backspace. If the user enables this pref,
152 * we use a trick to tell a true DEL keypress from alt-backspace: on
153 * alt-backspace, we will see the alt key go down, then get a DEL keystroke
154 * that indicates that alt is not pressed. See http://crbug.com/174410 .
155 */
156 this.altBackspaceIsMetaBackspace = false;
157
158 /**
159 * Used to keep track of the current alt-key state, which is necessary for
Robert Ginda034ffa72015-02-26 14:02:37 -0800160 * the altBackspaceIsMetaBackspace preference above and for the altGrMode
161 * preference. This is a bitmap with where bit positions correspond to the
162 * "location" property of the key event.
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700163 */
Robert Ginda034ffa72015-02-26 14:02:37 -0800164 this.altKeyPressed = 0;
Andrew de los Reyes6af23ae2013-04-04 14:17:50 -0700165
166 /**
167 * If true, Chrome OS media keys will be mapped to their F-key equivalent.
168 * E.g. "Back" will be mapped to F1. If false, Chrome will handle the keys.
169 */
170 this.mediaKeysAreFKeys = false;
Brad Town5f375a42015-03-12 01:30:58 -0700171
172 /**
173 * Holds the previous setting of altSendsWhat when DECSET 1039 is used. When
174 * DECRST 1039 is used, altSendsWhat is changed back to this and this is
175 * nulled out.
176 */
177 this.previousAltSendsWhat_ = null;
rgindafeaf3142012-01-31 15:14:20 -0800178};
179
180/**
rgindafeaf3142012-01-31 15:14:20 -0800181 * Special handling for keyCodes in a keyboard layout.
182 */
183hterm.Keyboard.KeyActions = {
184 /**
185 * Call preventDefault and stopPropagation for this key event and nothing
186 * else.
187 */
188 CANCEL: new String('CANCEL'),
189
190 /**
191 * This performs the default terminal action for the key. If used in the
192 * 'normal' action and the the keystroke represents a printable key, the
193 * character will be sent to the host. If used in one of the modifier
194 * actions, the terminal will perform the normal action after (possibly)
195 * altering it.
196 *
197 * - If the normal sequence starts with CSI, the sequence will be adjusted
198 * to include the modifier parameter as described in [XTERM] in the final
199 * table of the "PC-Style Function Keys" section.
200 *
201 * - If the control key is down and the key represents a printable character,
202 * and the uppercase version of the unshifted keycap is between
203 * 64 (ASCII '@') and 95 (ASCII '_'), then the uppercase version of the
204 * unshifted keycap minus 64 is sent. This makes '^@' send '\x00' and
205 * '^_' send '\x1f'. (Note that one higher that 0x1f is 0x20, which is
206 * the first printable ASCII value.)
207 *
208 * - If the alt key is down and the key represents a printable character then
209 * the value of the character is shifted up by 128.
210 *
211 * - If meta is down and configured to send an escape, '\x1b' will be sent
212 * before the normal action is performed.
213 */
214 DEFAULT: new String('DEFAULT'),
215
216 /**
217 * Causes the terminal to opt out of handling the key event, instead letting
218 * the browser deal with it.
219 */
220 PASS: new String('PASS'),
221
222 /**
223 * Insert the first or second character of the keyCap, based on e.shiftKey.
224 * The key will be handled in onKeyDown, and e.preventDefault() will be
225 * called.
226 *
227 * It is useful for a modified key action, where it essentially strips the
228 * modifier while preventing the browser from reacting to the key.
229 */
230 STRIP: new String('STRIP')
231};
232
233/**
Robert Ginda8cb7d902013-06-20 14:37:18 -0700234 * Encode a string according to the 'send-encoding' preference.
235 */
236hterm.Keyboard.prototype.encode = function(str) {
237 if (this.characterEncoding == 'utf-8')
238 return this.terminal.vt.encodeUTF8(str);
239
240 return str;
241};
242
243/**
rgindafeaf3142012-01-31 15:14:20 -0800244 * Capture keyboard events sent to the associated element.
245 *
246 * This enables the keyboard. Captured events are consumed by this class
247 * and will not perform their default action or bubble to other elements.
248 *
249 * Passing a null element will uninstall the keyboard handlers.
250 *
251 * @param {HTMLElement} element The element whose events should be captured, or
252 * null to disable the keyboard.
253 */
254hterm.Keyboard.prototype.installKeyboard = function(element) {
255 if (element == this.keyboardElement_)
256 return;
257
258 if (element && this.keyboardElement_)
259 this.installKeyboard(null);
260
261 for (var i = 0; i < this.handlers_.length; i++) {
262 var handler = this.handlers_[i];
263 if (element) {
264 element.addEventListener(handler[0], handler[1]);
265 } else {
266 this.keyboardElement_.removeEventListener(handler[0], handler[1]);
267 }
268 }
269
270 this.keyboardElement_ = element;
271};
272
273/**
274 * Disable keyboard event capture.
275 *
276 * This will allow the browser to process key events normally.
277 */
278hterm.Keyboard.prototype.uninstallKeyboard = function() {
279 this.installKeyboard(null);
280};
281
282/**
Robert Ginda11390c52012-09-13 14:53:34 -0700283 * Handle onTextInput events.
284 *
285 * We're not actually supposed to get these, but we do on the Mac in the case
286 * where a third party app sends synthetic keystrokes to Chrome.
287 */
288hterm.Keyboard.prototype.onTextInput_ = function(e) {
289 if (!e.data)
290 return;
291
292 e.data.split('').forEach(this.terminal.onVTKeystroke.bind(this.terminal));
293};
294
295/**
rgindafeaf3142012-01-31 15:14:20 -0800296 * Handle onKeyPress events.
297 */
298hterm.Keyboard.prototype.onKeyPress_ = function(e) {
rginda39bdf6f2012-04-10 16:50:55 -0700299 var code;
300
Rob Spies0bec09b2014-06-06 15:58:09 -0700301 var key = String.fromCharCode(e.which);
302 var lowerKey = key.toLowerCase();
303 if ((e.ctrlKey || e.metaKey) && (lowerKey == 'c' || lowerKey == 'v')) {
304 // On FF the key press (not key down) event gets fired for copy/paste.
305 // Let it fall through for the default browser behaviour.
306 return;
307 }
308
rginda39bdf6f2012-04-10 16:50:55 -0700309 if (e.altKey && this.altSendsWhat == 'browser-key' && e.charCode == 0) {
310 // If we got here because we were expecting the browser to handle an
311 // alt sequence but it didn't do it, then we might be on an OS without
312 // an enabled IME system. In that case we fall back to xterm-like
313 // behavior.
314 //
315 // This happens here only as a fallback. Typically these platforms should
316 // set altSendsWhat to either 'escape' or '8-bit'.
317 var ch = String.fromCharCode(e.keyCode);
318 if (!e.shiftKey)
319 ch = ch.toLowerCase();
320 code = ch.charCodeAt(0) + 128;
321
322 } else if (e.charCode >= 32) {
323 ch = e.charCode;
324 }
325
Robert Ginda8cb7d902013-06-20 14:37:18 -0700326 if (ch)
327 this.terminal.onVTKeystroke(String.fromCharCode(ch));
rgindafeaf3142012-01-31 15:14:20 -0800328
329 e.preventDefault();
330 e.stopPropagation();
331};
332
Marius Schilder45703172014-12-22 17:10:01 -0800333/**
Marius Schilder9dc111b2015-08-13 14:09:39 -0700334 * Prevent default handling for non-ctrl-shifted event.
Marius Schilder45703172014-12-22 17:10:01 -0800335 *
336 * When combined with Chrome permission 'app.window.fullscreen.overrideEsc',
337 * and called for both key down and key up events,
338 * the ESC key remains usable within fullscreen Chrome app windows.
339 */
Marius Schilder9dc111b2015-08-13 14:09:39 -0700340hterm.Keyboard.prototype.preventChromeAppNonCtrlShiftDefault_ = function(e) {
Marius Schilder45703172014-12-22 17:10:01 -0800341 if (!window.chrome || !window.chrome.app || !window.chrome.app.window)
342 return;
Marius Schilder9dc111b2015-08-13 14:09:39 -0700343 if (!e.ctrlKey || !e.shiftKey)
Marius Schilder45703172014-12-22 17:10:01 -0800344 e.preventDefault();
345};
346
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700347hterm.Keyboard.prototype.onBlur_ = function(e) {
Robert Ginda034ffa72015-02-26 14:02:37 -0800348 this.altKeyPressed = 0;
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700349};
350
351hterm.Keyboard.prototype.onKeyUp_ = function(e) {
352 if (e.keyCode == 18)
Robert Ginda034ffa72015-02-26 14:02:37 -0800353 this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
354
Marius Schilder45703172014-12-22 17:10:01 -0800355 if (e.keyCode == 27)
Marius Schilder9dc111b2015-08-13 14:09:39 -0700356 this.preventChromeAppNonCtrlShiftDefault_(e);
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700357};
358
rgindafeaf3142012-01-31 15:14:20 -0800359/**
360 * Handle onKeyDown events.
361 */
362hterm.Keyboard.prototype.onKeyDown_ = function(e) {
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700363 if (e.keyCode == 18)
Robert Ginda034ffa72015-02-26 14:02:37 -0800364 this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
365
Marius Schilder45703172014-12-22 17:10:01 -0800366 if (e.keyCode == 27)
Marius Schilder9dc111b2015-08-13 14:09:39 -0700367 this.preventChromeAppNonCtrlShiftDefault_(e);
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700368
rgindafeaf3142012-01-31 15:14:20 -0800369 var keyDef = this.keyMap.keyDefs[e.keyCode];
370 if (!keyDef) {
371 console.warn('No definition for keyCode: ' + e.keyCode);
372 return;
373 }
374
Robert Ginda21de3762012-09-20 16:38:18 -0700375 // The type of action we're going to use.
376 var resolvedActionType = null;
377
rgindafeaf3142012-01-31 15:14:20 -0800378 var self = this;
379 function getAction(name) {
380 // Get the key action for the given action name. If the action is a
381 // function, dispatch it. If the action defers to the normal action,
382 // resolve that instead.
383
Robert Ginda21de3762012-09-20 16:38:18 -0700384 resolvedActionType = name;
385
rgindafeaf3142012-01-31 15:14:20 -0800386 var action = keyDef[name];
387 if (typeof action == 'function')
388 action = action.apply(self.keyMap, [e, keyDef]);
389
390 if (action === DEFAULT && name != 'normal')
391 action = getAction('normal');
392
393 return action;
394 }
395
396 // Note that we use the triple-equals ('===') operator to test equality for
397 // these constants, in order to distingush usage of the constant from usage
398 // of a literal string that happens to contain the same bytes.
399 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
400 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
401 var PASS = hterm.Keyboard.KeyActions.PASS;
402 var STRIP = hterm.Keyboard.KeyActions.STRIP;
403
rgindafeaf3142012-01-31 15:14:20 -0800404 var control = e.ctrlKey;
rginda42ad71d2012-02-16 14:06:28 -0800405 var alt = this.altIsMeta ? false : e.altKey;
406 var meta = this.altIsMeta ? (e.altKey || e.metaKey) : e.metaKey;
rgindafeaf3142012-01-31 15:14:20 -0800407
Robert Ginda4c66c9a2015-02-18 13:16:21 -0800408 // In the key-map, we surround the keyCap for non-printables in "[...]"
409 var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
410
Robert Ginda034ffa72015-02-26 14:02:37 -0800411 switch (this.altGrMode) {
412 case 'ctrl-alt':
413 if (isPrintable && control && alt) {
414 // ctrl-alt-printable means altGr. We clear out the control and
415 // alt modifiers and wait to see the charCode in the keydown event.
416 control = false;
417 alt = false;
418 }
419 break;
420
421 case 'right-alt':
422 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
423 control = false;
424 alt = false;
425 }
426 break;
427
428 case 'left-alt':
429 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
430 control = false;
431 alt = false;
432 }
433 break;
Robert Ginda4c66c9a2015-02-18 13:16:21 -0800434 }
435
rgindafeaf3142012-01-31 15:14:20 -0800436 var action;
437
438 if (control) {
439 action = getAction('control');
440 } else if (alt) {
441 action = getAction('alt');
rginda42ad71d2012-02-16 14:06:28 -0800442 } else if (meta) {
443 action = getAction('meta');
rgindafeaf3142012-01-31 15:14:20 -0800444 } else {
445 action = getAction('normal');
446 }
447
Robert Gindaf82267d2015-06-09 15:32:04 -0700448 // If e.maskShiftKey was set (during getAction) it means the shift key is
449 // already accounted for in the action, and we should not act on it any
450 // further. This is currently only used for Ctrl-Shift-Tab, which should send
451 // "CSI Z", not "CSI 1 ; 2 Z".
452 var shift = !e.maskShiftKey && e.shiftKey;
453
454 var keyDown = {
455 keyCode: e.keyCode,
456 shift: e.shiftKey, // not `var shift` from above.
457 ctrl: control,
458 alt: alt,
459 meta: meta
460 };
461
462 var binding = this.bindings.getBinding(keyDown);
463
464 if (binding) {
465 // Clear out the modifier bits so we don't try to munge the sequence
466 // further.
467 shift = control = alt = meta = false;
468 resolvedActionType = 'normal';
469 action = binding.action;
470
471 if (typeof action == 'function')
472 action = action.call(this, this.terminal, keyDown);
473 }
474
rginda39bdf6f2012-04-10 16:50:55 -0700475 if (alt && this.altSendsWhat == 'browser-key' && action == DEFAULT) {
476 // When altSendsWhat is 'browser-key', we wait for the keypress event.
477 // In keypress, the browser should have set the event.charCode to the
478 // appropriate character.
479 // TODO(rginda): Character compositions will need some black magic.
480 action = PASS;
481 }
482
rgindafeaf3142012-01-31 15:14:20 -0800483 if (action === PASS || (action === DEFAULT && !(control || alt || meta))) {
484 // If this key is supposed to be handled by the browser, or it is an
485 // unmodified key with the default action, then exit this event handler.
486 // If it's an unmodified key, it'll be handled in onKeyPress where we
487 // can tell for sure which ASCII code to insert.
488 //
489 // This block needs to come before the STRIP test, otherwise we'll strip
490 // the modifier and think it's ok to let the browser handle the keypress.
491 // The browser won't know we're trying to ignore the modifiers and might
492 // perform some default action.
493 return;
494 }
495
496 if (action === STRIP) {
497 alt = control = false;
498 action = keyDef.normal;
499 if (typeof action == 'function')
500 action = action.apply(this.keyMap, [e, keyDef]);
501
502 if (action == DEFAULT && keyDef.keyCap.length == 2)
Robert Gindaf82267d2015-06-09 15:32:04 -0700503 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
rgindafeaf3142012-01-31 15:14:20 -0800504 }
505
506 e.preventDefault();
507 e.stopPropagation();
508
509 if (action === CANCEL)
510 return;
511
rginda42ad71d2012-02-16 14:06:28 -0800512 if (action !== DEFAULT && typeof action != 'string') {
513 console.warn('Invalid action: ' + JSON.stringify(action));
514 return;
515 }
516
Robert Gindacc6d3a72012-09-24 14:06:08 -0700517 // Strip the modifier that is associated with the action, since we assume that
518 // modifier has already been accounted for in the action.
519 if (resolvedActionType == 'control') {
520 control = false;
521 } else if (resolvedActionType == 'alt') {
522 alt = false;
523 } else if (resolvedActionType == 'meta') {
524 meta = false;
525 }
526
527 if (action.substr(0, 2) == '\x1b[' && (alt || control || shift)) {
528 // The action is an escape sequence that and it was triggered in the
529 // presence of a keyboard modifier, we may need to alter the action to
530 // include the modifier before sending it.
rginda42ad71d2012-02-16 14:06:28 -0800531
rgindafeaf3142012-01-31 15:14:20 -0800532 var mod;
533
534 if (shift && !(alt || control)) {
535 mod = ';2';
536 } else if (alt && !(shift || control)) {
537 mod = ';3';
538 } else if (shift && alt && !control) {
539 mod = ';4';
540 } else if (control && !(shift || alt)) {
541 mod = ';5';
542 } else if (shift && control && !alt) {
543 mod = ';6';
544 } else if (alt && control && !shift) {
545 mod = ';7';
546 } else if (shift && alt && control) {
547 mod = ';8';
548 }
549
550 if (action.length == 3) {
551 // Some of the CSI sequences have zero parameters unless modified.
552 action = '\x1b[1' + mod + action.substr(2, 1);
553 } else {
554 // Others always have at least one parameter.
Robert Ginda21de3762012-09-20 16:38:18 -0700555 action = action.substr(0, action.length - 1) + mod +
rgindafeaf3142012-01-31 15:14:20 -0800556 action.substr(action.length - 1);
557 }
Robert Ginda21de3762012-09-20 16:38:18 -0700558
559 } else {
Robert Ginda21de3762012-09-20 16:38:18 -0700560 if (action === DEFAULT) {
Robert Gindaf82267d2015-06-09 15:32:04 -0700561 action = keyDef.keyCap.substr((shift ? 1 : 0), 1);
Robert Ginda8cb7d902013-06-20 14:37:18 -0700562
Robert Ginda21de3762012-09-20 16:38:18 -0700563 if (control) {
564 var unshifted = keyDef.keyCap.substr(0, 1);
565 var code = unshifted.charCodeAt(0);
566 if (code >= 64 && code <= 95) {
Robert Ginda8cb7d902013-06-20 14:37:18 -0700567 action = String.fromCharCode(code - 64);
Robert Ginda21de3762012-09-20 16:38:18 -0700568 }
Robert Ginda21de3762012-09-20 16:38:18 -0700569 }
570 }
571
Robert Ginda8cb7d902013-06-20 14:37:18 -0700572 if (alt && this.altSendsWhat == '8-bit' && action.length == 1) {
573 var code = action.charCodeAt(0) + 128;
574 action = String.fromCharCode(code);
575 }
576
Robert Ginda21de3762012-09-20 16:38:18 -0700577 // We respect alt/metaSendsEscape even if the keymap action was a literal
578 // string. Otherwise, every overridden alt/meta action would have to
579 // check alt/metaSendsEscape.
Robert Gindacc6d3a72012-09-24 14:06:08 -0700580 if ((alt && this.altSendsWhat == 'escape') ||
581 (meta && this.metaSendsEscape)) {
Robert Ginda21de3762012-09-20 16:38:18 -0700582 action = '\x1b' + action;
583 }
rgindafeaf3142012-01-31 15:14:20 -0800584 }
585
586 this.terminal.onVTKeystroke(action);
587};