blob: f6cf6a5f5c5cbafa7cba4e89606750560cb16a13 [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
41 /**
Robert Ginda034ffa72015-02-26 14:02:37 -080042 * none: Disable any AltGr related munging.
43 * ctrl-alt: Assume Ctrl+Alt means AltGr.
44 * left-alt: Assume left Alt means AltGr.
45 * right-alt: Assume right Alt means AltGr.
46 */
47 this.altGrMode = 'none';
48
49 /**
rginda4bba5e12012-06-20 16:15:30 -070050 * If true, Shift-Insert will fall through to the browser as a paste.
51 * If false, the keystroke will be sent to the host.
52 */
Robert Ginda57f03b42012-09-13 11:02:48 -070053 this.shiftInsertPaste = true;
rginda4bba5e12012-06-20 16:15:30 -070054
55 /**
rgindafeaf3142012-01-31 15:14:20 -080056 * If true, home/end will control the terminal scrollbar and shift home/end
57 * will send the VT keycodes. If false then home/end sends VT codes and
58 * shift home/end scrolls.
59 */
Robert Ginda57f03b42012-09-13 11:02:48 -070060 this.homeKeysScroll = false;
rgindafeaf3142012-01-31 15:14:20 -080061
62 /**
63 * Same as above, except for page up/page down.
64 */
Robert Ginda57f03b42012-09-13 11:02:48 -070065 this.pageKeysScroll = false;
rgindafeaf3142012-01-31 15:14:20 -080066
67 /**
Robert Ginda7e5e9522014-03-14 12:23:58 -070068 * If true, Ctrl-Plus/Minus/Zero controls zoom.
69 * If false, Ctrl-Shift-Plus/Minus/Zero controls zoom, Ctrl-Minus sends ^_,
70 * Ctrl-Plus/Zero do nothing.
71 */
72 this.ctrlPlusMinusZeroZoom = true;
73
74 /**
Robert Gindafb5a3f92014-05-13 14:12:00 -070075 * Ctrl+C copies if true, sends ^C to host if false.
76 * Ctrl+Shift+C sends ^C to host if true, copies if false.
77 */
78 this.ctrlCCopy = false;
79
80 /**
81 * Ctrl+V pastes if true, sends ^V to host if false.
82 * Ctrl+Shift+V sends ^V to host if true, pastes if false.
Leonardo Mesquita61e7c312014-01-04 12:53:12 +010083 */
84 this.ctrlVPaste = false;
85
86 /**
rgindafeaf3142012-01-31 15:14:20 -080087 * Enable/disable application keypad.
88 *
89 * This changes the way numeric keys are sent from the keyboard.
90 */
91 this.applicationKeypad = false;
92
93 /**
94 * Enable/disable the application cursor mode.
95 *
96 * This changes the way cursor keys are sent from the keyboard.
97 */
98 this.applicationCursor = false;
99
100 /**
101 * If true, the backspace should send BS ('\x08', aka ^H). Otherwise
102 * the backspace key should send '\x7f'.
103 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700104 this.backspaceSendsBackspace = false;
rgindafeaf3142012-01-31 15:14:20 -0800105
106 /**
Robert Ginda8cb7d902013-06-20 14:37:18 -0700107 * The encoding method for data sent to the host.
108 */
109 this.characterEncoding = 'utf-8';
110
111 /**
rgindafeaf3142012-01-31 15:14:20 -0800112 * Set whether the meta key sends a leading escape or not.
113 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700114 this.metaSendsEscape = true;
rginda30f20f62012-04-05 16:36:19 -0700115
116 /**
Marius Schilder77857b32014-05-14 16:21:26 -0700117 * Set whether meta-V gets passed to host.
118 */
119 this.passMetaV = true;
120
121 /**
rginda39bdf6f2012-04-10 16:50:55 -0700122 * Controls how the alt key is handled.
123 *
124 * escape....... Send an ESC prefix.
125 * 8-bit........ Add 128 to the unshifted character as in xterm.
126 * browser-key.. Wait for the keypress event and see what the browser says.
127 * (This won't work well on platforms where the browser
128 * performs a default action for some alt sequences.)
rginda30f20f62012-04-05 16:36:19 -0700129 *
130 * This setting only matters when alt is distinct from meta (altIsMeta is
131 * false.)
132 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700133 this.altSendsWhat = 'escape';
rginda42ad71d2012-02-16 14:06:28 -0800134
135 /**
136 * Set whether the alt key acts as a meta key, instead of producing 8-bit
137 * characters.
rginda30f20f62012-04-05 16:36:19 -0700138 *
139 * True to enable, false to disable, null to autodetect based on platform.
rginda42ad71d2012-02-16 14:06:28 -0800140 */
Robert Ginda57f03b42012-09-13 11:02:48 -0700141 this.altIsMeta = false;
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700142
143 /**
144 * If true, tries to detect DEL key events that are from alt-backspace on
145 * Chrome OS vs from a true DEL key press.
146 *
147 * Background: At the time of writing, on Chrome OS, alt-backspace is mapped
148 * to DEL. Some users may be happy with this, but others may be frustrated
149 * that it's impossible to do meta-backspace. If the user enables this pref,
150 * we use a trick to tell a true DEL keypress from alt-backspace: on
151 * alt-backspace, we will see the alt key go down, then get a DEL keystroke
152 * that indicates that alt is not pressed. See http://crbug.com/174410 .
153 */
154 this.altBackspaceIsMetaBackspace = false;
155
156 /**
157 * Used to keep track of the current alt-key state, which is necessary for
Robert Ginda034ffa72015-02-26 14:02:37 -0800158 * the altBackspaceIsMetaBackspace preference above and for the altGrMode
159 * preference. This is a bitmap with where bit positions correspond to the
160 * "location" property of the key event.
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700161 */
Robert Ginda034ffa72015-02-26 14:02:37 -0800162 this.altKeyPressed = 0;
Andrew de los Reyes6af23ae2013-04-04 14:17:50 -0700163
164 /**
165 * If true, Chrome OS media keys will be mapped to their F-key equivalent.
166 * E.g. "Back" will be mapped to F1. If false, Chrome will handle the keys.
167 */
168 this.mediaKeysAreFKeys = false;
rgindafeaf3142012-01-31 15:14:20 -0800169};
170
171/**
rgindafeaf3142012-01-31 15:14:20 -0800172 * Special handling for keyCodes in a keyboard layout.
173 */
174hterm.Keyboard.KeyActions = {
175 /**
176 * Call preventDefault and stopPropagation for this key event and nothing
177 * else.
178 */
179 CANCEL: new String('CANCEL'),
180
181 /**
182 * This performs the default terminal action for the key. If used in the
183 * 'normal' action and the the keystroke represents a printable key, the
184 * character will be sent to the host. If used in one of the modifier
185 * actions, the terminal will perform the normal action after (possibly)
186 * altering it.
187 *
188 * - If the normal sequence starts with CSI, the sequence will be adjusted
189 * to include the modifier parameter as described in [XTERM] in the final
190 * table of the "PC-Style Function Keys" section.
191 *
192 * - If the control key is down and the key represents a printable character,
193 * and the uppercase version of the unshifted keycap is between
194 * 64 (ASCII '@') and 95 (ASCII '_'), then the uppercase version of the
195 * unshifted keycap minus 64 is sent. This makes '^@' send '\x00' and
196 * '^_' send '\x1f'. (Note that one higher that 0x1f is 0x20, which is
197 * the first printable ASCII value.)
198 *
199 * - If the alt key is down and the key represents a printable character then
200 * the value of the character is shifted up by 128.
201 *
202 * - If meta is down and configured to send an escape, '\x1b' will be sent
203 * before the normal action is performed.
204 */
205 DEFAULT: new String('DEFAULT'),
206
207 /**
208 * Causes the terminal to opt out of handling the key event, instead letting
209 * the browser deal with it.
210 */
211 PASS: new String('PASS'),
212
213 /**
214 * Insert the first or second character of the keyCap, based on e.shiftKey.
215 * The key will be handled in onKeyDown, and e.preventDefault() will be
216 * called.
217 *
218 * It is useful for a modified key action, where it essentially strips the
219 * modifier while preventing the browser from reacting to the key.
220 */
221 STRIP: new String('STRIP')
222};
223
224/**
Robert Ginda8cb7d902013-06-20 14:37:18 -0700225 * Encode a string according to the 'send-encoding' preference.
226 */
227hterm.Keyboard.prototype.encode = function(str) {
228 if (this.characterEncoding == 'utf-8')
229 return this.terminal.vt.encodeUTF8(str);
230
231 return str;
232};
233
234/**
rgindafeaf3142012-01-31 15:14:20 -0800235 * Capture keyboard events sent to the associated element.
236 *
237 * This enables the keyboard. Captured events are consumed by this class
238 * and will not perform their default action or bubble to other elements.
239 *
240 * Passing a null element will uninstall the keyboard handlers.
241 *
242 * @param {HTMLElement} element The element whose events should be captured, or
243 * null to disable the keyboard.
244 */
245hterm.Keyboard.prototype.installKeyboard = function(element) {
246 if (element == this.keyboardElement_)
247 return;
248
249 if (element && this.keyboardElement_)
250 this.installKeyboard(null);
251
252 for (var i = 0; i < this.handlers_.length; i++) {
253 var handler = this.handlers_[i];
254 if (element) {
255 element.addEventListener(handler[0], handler[1]);
256 } else {
257 this.keyboardElement_.removeEventListener(handler[0], handler[1]);
258 }
259 }
260
261 this.keyboardElement_ = element;
262};
263
264/**
265 * Disable keyboard event capture.
266 *
267 * This will allow the browser to process key events normally.
268 */
269hterm.Keyboard.prototype.uninstallKeyboard = function() {
270 this.installKeyboard(null);
271};
272
273/**
Robert Ginda11390c52012-09-13 14:53:34 -0700274 * Handle onTextInput events.
275 *
276 * We're not actually supposed to get these, but we do on the Mac in the case
277 * where a third party app sends synthetic keystrokes to Chrome.
278 */
279hterm.Keyboard.prototype.onTextInput_ = function(e) {
280 if (!e.data)
281 return;
282
283 e.data.split('').forEach(this.terminal.onVTKeystroke.bind(this.terminal));
284};
285
286/**
rgindafeaf3142012-01-31 15:14:20 -0800287 * Handle onKeyPress events.
288 */
289hterm.Keyboard.prototype.onKeyPress_ = function(e) {
rginda39bdf6f2012-04-10 16:50:55 -0700290 var code;
291
Rob Spies0bec09b2014-06-06 15:58:09 -0700292 var key = String.fromCharCode(e.which);
293 var lowerKey = key.toLowerCase();
294 if ((e.ctrlKey || e.metaKey) && (lowerKey == 'c' || lowerKey == 'v')) {
295 // On FF the key press (not key down) event gets fired for copy/paste.
296 // Let it fall through for the default browser behaviour.
297 return;
298 }
299
rginda39bdf6f2012-04-10 16:50:55 -0700300 if (e.altKey && this.altSendsWhat == 'browser-key' && e.charCode == 0) {
301 // If we got here because we were expecting the browser to handle an
302 // alt sequence but it didn't do it, then we might be on an OS without
303 // an enabled IME system. In that case we fall back to xterm-like
304 // behavior.
305 //
306 // This happens here only as a fallback. Typically these platforms should
307 // set altSendsWhat to either 'escape' or '8-bit'.
308 var ch = String.fromCharCode(e.keyCode);
309 if (!e.shiftKey)
310 ch = ch.toLowerCase();
311 code = ch.charCodeAt(0) + 128;
312
313 } else if (e.charCode >= 32) {
314 ch = e.charCode;
315 }
316
Robert Ginda8cb7d902013-06-20 14:37:18 -0700317 if (ch)
318 this.terminal.onVTKeystroke(String.fromCharCode(ch));
rgindafeaf3142012-01-31 15:14:20 -0800319
320 e.preventDefault();
321 e.stopPropagation();
322};
323
Marius Schilder45703172014-12-22 17:10:01 -0800324/**
325 * Prevent default handling for non-shifted event.
326 *
327 * When combined with Chrome permission 'app.window.fullscreen.overrideEsc',
328 * and called for both key down and key up events,
329 * the ESC key remains usable within fullscreen Chrome app windows.
330 */
331hterm.Keyboard.prototype.preventChromeAppNonShiftDefault_ = function(e) {
332 if (!window.chrome || !window.chrome.app || !window.chrome.app.window)
333 return;
334 if (!e.shiftKey)
335 e.preventDefault();
336};
337
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700338hterm.Keyboard.prototype.onBlur_ = function(e) {
Robert Ginda034ffa72015-02-26 14:02:37 -0800339 this.altKeyPressed = 0;
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700340};
341
342hterm.Keyboard.prototype.onKeyUp_ = function(e) {
343 if (e.keyCode == 18)
Robert Ginda034ffa72015-02-26 14:02:37 -0800344 this.altKeyPressed = this.altKeyPressed & ~(1 << (e.location - 1));
345
Marius Schilder45703172014-12-22 17:10:01 -0800346 if (e.keyCode == 27)
347 this.preventChromeAppNonShiftDefault_(e);
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700348};
349
rgindafeaf3142012-01-31 15:14:20 -0800350/**
351 * Handle onKeyDown events.
352 */
353hterm.Keyboard.prototype.onKeyDown_ = function(e) {
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700354 if (e.keyCode == 18)
Robert Ginda034ffa72015-02-26 14:02:37 -0800355 this.altKeyPressed = this.altKeyPressed | (1 << (e.location - 1));
356
Marius Schilder45703172014-12-22 17:10:01 -0800357 if (e.keyCode == 27)
358 this.preventChromeAppNonShiftDefault_(e);
Andrew de los Reyes574e10e2013-04-04 09:31:57 -0700359
rgindafeaf3142012-01-31 15:14:20 -0800360 var keyDef = this.keyMap.keyDefs[e.keyCode];
361 if (!keyDef) {
362 console.warn('No definition for keyCode: ' + e.keyCode);
363 return;
364 }
365
Robert Ginda21de3762012-09-20 16:38:18 -0700366 // The type of action we're going to use.
367 var resolvedActionType = null;
368
rgindafeaf3142012-01-31 15:14:20 -0800369 var self = this;
370 function getAction(name) {
371 // Get the key action for the given action name. If the action is a
372 // function, dispatch it. If the action defers to the normal action,
373 // resolve that instead.
374
Robert Ginda21de3762012-09-20 16:38:18 -0700375 resolvedActionType = name;
376
rgindafeaf3142012-01-31 15:14:20 -0800377 var action = keyDef[name];
378 if (typeof action == 'function')
379 action = action.apply(self.keyMap, [e, keyDef]);
380
381 if (action === DEFAULT && name != 'normal')
382 action = getAction('normal');
383
384 return action;
385 }
386
387 // Note that we use the triple-equals ('===') operator to test equality for
388 // these constants, in order to distingush usage of the constant from usage
389 // of a literal string that happens to contain the same bytes.
390 var CANCEL = hterm.Keyboard.KeyActions.CANCEL;
391 var DEFAULT = hterm.Keyboard.KeyActions.DEFAULT;
392 var PASS = hterm.Keyboard.KeyActions.PASS;
393 var STRIP = hterm.Keyboard.KeyActions.STRIP;
394
rgindafeaf3142012-01-31 15:14:20 -0800395 var control = e.ctrlKey;
rginda42ad71d2012-02-16 14:06:28 -0800396 var alt = this.altIsMeta ? false : e.altKey;
397 var meta = this.altIsMeta ? (e.altKey || e.metaKey) : e.metaKey;
rgindafeaf3142012-01-31 15:14:20 -0800398
Robert Ginda4c66c9a2015-02-18 13:16:21 -0800399 // In the key-map, we surround the keyCap for non-printables in "[...]"
400 var isPrintable = !(/^\[\w+\]$/.test(keyDef.keyCap));
401
Robert Ginda034ffa72015-02-26 14:02:37 -0800402 switch (this.altGrMode) {
403 case 'ctrl-alt':
404 if (isPrintable && control && alt) {
405 // ctrl-alt-printable means altGr. We clear out the control and
406 // alt modifiers and wait to see the charCode in the keydown event.
407 control = false;
408 alt = false;
409 }
410 break;
411
412 case 'right-alt':
413 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 2)) {
414 control = false;
415 alt = false;
416 }
417 break;
418
419 case 'left-alt':
420 if (isPrintable && (this.terminal.keyboard.altKeyPressed & 1)) {
421 control = false;
422 alt = false;
423 }
424 break;
Robert Ginda4c66c9a2015-02-18 13:16:21 -0800425 }
426
rgindafeaf3142012-01-31 15:14:20 -0800427 var action;
428
429 if (control) {
430 action = getAction('control');
431 } else if (alt) {
432 action = getAction('alt');
rginda42ad71d2012-02-16 14:06:28 -0800433 } else if (meta) {
434 action = getAction('meta');
rgindafeaf3142012-01-31 15:14:20 -0800435 } else {
436 action = getAction('normal');
437 }
438
Robert Ginda75b0c6e2013-06-19 12:27:06 -0700439 // The action may have cleared the e.shiftKey, so we wait until after
440 // getAction to read it.
441 var shift = e.shiftKey;
442
rginda39bdf6f2012-04-10 16:50:55 -0700443 if (alt && this.altSendsWhat == 'browser-key' && action == DEFAULT) {
444 // When altSendsWhat is 'browser-key', we wait for the keypress event.
445 // In keypress, the browser should have set the event.charCode to the
446 // appropriate character.
447 // TODO(rginda): Character compositions will need some black magic.
448 action = PASS;
449 }
450
rgindafeaf3142012-01-31 15:14:20 -0800451 if (action === PASS || (action === DEFAULT && !(control || alt || meta))) {
452 // If this key is supposed to be handled by the browser, or it is an
453 // unmodified key with the default action, then exit this event handler.
454 // If it's an unmodified key, it'll be handled in onKeyPress where we
455 // can tell for sure which ASCII code to insert.
456 //
457 // This block needs to come before the STRIP test, otherwise we'll strip
458 // the modifier and think it's ok to let the browser handle the keypress.
459 // The browser won't know we're trying to ignore the modifiers and might
460 // perform some default action.
461 return;
462 }
463
464 if (action === STRIP) {
465 alt = control = false;
466 action = keyDef.normal;
467 if (typeof action == 'function')
468 action = action.apply(this.keyMap, [e, keyDef]);
469
470 if (action == DEFAULT && keyDef.keyCap.length == 2)
471 action = keyDef.keyCap.substr((e.shiftKey ? 1 : 0), 1);
472 }
473
474 e.preventDefault();
475 e.stopPropagation();
476
477 if (action === CANCEL)
478 return;
479
rginda42ad71d2012-02-16 14:06:28 -0800480 if (action !== DEFAULT && typeof action != 'string') {
481 console.warn('Invalid action: ' + JSON.stringify(action));
482 return;
483 }
484
Robert Gindacc6d3a72012-09-24 14:06:08 -0700485 // Strip the modifier that is associated with the action, since we assume that
486 // modifier has already been accounted for in the action.
487 if (resolvedActionType == 'control') {
488 control = false;
489 } else if (resolvedActionType == 'alt') {
490 alt = false;
491 } else if (resolvedActionType == 'meta') {
492 meta = false;
493 }
494
495 if (action.substr(0, 2) == '\x1b[' && (alt || control || shift)) {
496 // The action is an escape sequence that and it was triggered in the
497 // presence of a keyboard modifier, we may need to alter the action to
498 // include the modifier before sending it.
rginda42ad71d2012-02-16 14:06:28 -0800499
rgindafeaf3142012-01-31 15:14:20 -0800500 var mod;
501
502 if (shift && !(alt || control)) {
503 mod = ';2';
504 } else if (alt && !(shift || control)) {
505 mod = ';3';
506 } else if (shift && alt && !control) {
507 mod = ';4';
508 } else if (control && !(shift || alt)) {
509 mod = ';5';
510 } else if (shift && control && !alt) {
511 mod = ';6';
512 } else if (alt && control && !shift) {
513 mod = ';7';
514 } else if (shift && alt && control) {
515 mod = ';8';
516 }
517
518 if (action.length == 3) {
519 // Some of the CSI sequences have zero parameters unless modified.
520 action = '\x1b[1' + mod + action.substr(2, 1);
521 } else {
522 // Others always have at least one parameter.
Robert Ginda21de3762012-09-20 16:38:18 -0700523 action = action.substr(0, action.length - 1) + mod +
rgindafeaf3142012-01-31 15:14:20 -0800524 action.substr(action.length - 1);
525 }
Robert Ginda21de3762012-09-20 16:38:18 -0700526
527 } else {
Robert Ginda21de3762012-09-20 16:38:18 -0700528 if (action === DEFAULT) {
Robert Ginda8cb7d902013-06-20 14:37:18 -0700529 action = keyDef.keyCap.substr((e.shiftKey ? 1 : 0), 1);
530
Robert Ginda21de3762012-09-20 16:38:18 -0700531 if (control) {
532 var unshifted = keyDef.keyCap.substr(0, 1);
533 var code = unshifted.charCodeAt(0);
534 if (code >= 64 && code <= 95) {
Robert Ginda8cb7d902013-06-20 14:37:18 -0700535 action = String.fromCharCode(code - 64);
Robert Ginda21de3762012-09-20 16:38:18 -0700536 }
Robert Ginda21de3762012-09-20 16:38:18 -0700537 }
538 }
539
Robert Ginda8cb7d902013-06-20 14:37:18 -0700540 if (alt && this.altSendsWhat == '8-bit' && action.length == 1) {
541 var code = action.charCodeAt(0) + 128;
542 action = String.fromCharCode(code);
543 }
544
Robert Ginda21de3762012-09-20 16:38:18 -0700545 // We respect alt/metaSendsEscape even if the keymap action was a literal
546 // string. Otherwise, every overridden alt/meta action would have to
547 // check alt/metaSendsEscape.
Robert Gindacc6d3a72012-09-24 14:06:08 -0700548 if ((alt && this.altSendsWhat == 'escape') ||
549 (meta && this.metaSendsEscape)) {
Robert Ginda21de3762012-09-20 16:38:18 -0700550 action = '\x1b' + action;
551 }
rgindafeaf3142012-01-31 15:14:20 -0800552 }
553
554 this.terminal.onVTKeystroke(action);
555};