1 // VT100.js -- JavaScript based terminal emulator
2 // Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License version 2 as
6 // published by the Free Software Foundation.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License along
14 // with this program; if not, write to the Free Software Foundation, Inc.,
15 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 // In addition to these license terms, the author grants the following
20 // If you modify this program, or any covered work, by linking or
21 // combining it with the OpenSSL project's OpenSSL library (or a
22 // modified version of that library), containing parts covered by the
23 // terms of the OpenSSL or SSLeay licenses, the author
24 // grants you additional permission to convey the resulting work.
25 // Corresponding Source for a non-source form of such a combination
26 // shall include the source code for the parts of OpenSSL used as well
27 // as that of the covered work.
29 // You may at your option choose to remove this additional permission from
30 // the work, or from any part of it.
32 // It is possible to build this program in a way that it loads OpenSSL
33 // libraries at run-time. If doing so, the following notices are required
34 // by the OpenSSL and SSLeay licenses:
36 // This product includes software developed by the OpenSSL Project
37 // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
39 // This product includes cryptographic software written by Eric Young
40 // (eay@cryptsoft.com)
43 // The most up-to-date version of this program is always available from
44 // http://shellinabox.com
49 // The author believes that for the purposes of this license, you meet the
50 // requirements for publishing the source code, if your web server publishes
51 // the source in unmodified form (i.e. with licensing information, comments,
52 // formatting, and identifier names intact). If there are technical reasons
53 // that require you to make changes to the source code when serving the
54 // JavaScript (e.g to remove pre-processor directives from the source), these
55 // changes should be done in a reversible fashion.
57 // The author does not consider websites that reference this script in
58 // unmodified form, and web servers that serve this script in unmodified form
59 // to be derived works. As such, they are believed to be outside of the
60 // scope of this license and not subject to the rights or restrictions of the
61 // GNU General Public License.
63 // If in doubt, consult a legal professional familiar with the laws that
64 // apply in your country.
69 // #define ESgetpars 3
70 // #define ESgotpars 4
71 // #define ESdeviceattr 5
72 // #define ESfunckey 6
79 // #define ESpercent 13
80 // #define ESignore 14
81 // #define ESnonstd 15
82 // #define ESpalette 16
83 // #define ESstatus 17
87 // #define ATTR_DEFAULT 0x00F0
88 // #define ATTR_REVERSE 0x0100
89 // #define ATTR_UNDERLINE 0x0200
90 // #define ATTR_DIM 0x0400
91 // #define ATTR_BRIGHT 0x0800
92 // #define ATTR_BLINK 0x1000
94 // #define MOUSE_DOWN 0
96 // #define MOUSE_CLICK 2
98 function VT100(container) {
99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
106 // Optionally allow username and passwords.
107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
110 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111 '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
115 '(?::[1-9][0-9]*)?' +
118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
125 // Hostnames must end with a well-known top-level domain or must be
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
146 '(?::[1-9][0-9]{0,4})?' +
149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
155 '[-_.+a-zA-Z0-9]+@' +
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
173 // Optional arguments
174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
176 this.getUserSettings();
177 this.initializeElements(container);
178 this.maxScrollbackLines = 500;
181 this.isQuestionMark = false;
184 this.savedAttr = [ ];
185 this.savedUseGMap = 0;
186 this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187 this.CodePage437Map, this.DirectToFontMap ];
188 this.savedValid = [ ];
189 this.respondString = '';
190 this.statusString = '';
191 this.internalClipboard = undefined;
195 VT100.prototype.reset = function(clearHistory) {
196 this.isEsc = 0 /* ESnormal */;
197 this.needWrap = false;
198 this.autoWrapMode = true;
199 this.dispCtrl = false;
200 this.toggleMeta = false;
201 this.insertMode = false;
202 this.applKeyMode = false;
203 this.cursorKeyMode = false;
204 this.crLfMode = false;
205 this.offsetMode = false;
206 this.mouseReporting = false;
207 this.utfEnabled = this.utfPreferred;
210 this.color = 'ansi0 bgAnsi15';
212 this.attr = 0x00F0 /* ATTR_DEFAULT */;
214 this.GMap = [ this.Latin1Map,
215 this.VT100GraphicsMap,
217 this.DirectToFontMap ];
218 this.translate = this.GMap[this.useGMap];
220 this.bottom = this.terminalHeight;
221 this.lastCharacter = ' ';
222 this.userTabStop = [ ];
225 for (var i = 0; i < 2; i++) {
226 while (this.console[i].firstChild) {
227 this.console[i].removeChild(this.console[i].firstChild);
232 this.enableAlternateScreen(false);
235 this.isInverted = false;
236 this.refreshInvertedState();
237 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
238 this.color, this.style);
241 VT100.prototype.addListener = function(elem, event, listener) {
242 if (elem.addEventListener) {
243 elem.addEventListener(event, listener, false);
245 elem.attachEvent('on' + event, listener);
249 VT100.prototype.getUserSettings = function() {
250 // Compute hash signature to identify the entries in the userCSS menu.
251 // If the menu is unchanged from last time, default values can be
252 // looked up in a cookie associated with this page.
254 this.utfPreferred = true;
255 this.visualBell = typeof suppressAllAudio != 'undefined' &&
257 if (this.visualBell) {
258 this.signature = Math.floor(16807*this.signature + 1) %
261 if (typeof userCSSList != 'undefined') {
262 for (var i = 0; i < userCSSList.length; ++i) {
263 var label = userCSSList[i][0];
264 for (var j = 0; j < label.length; ++j) {
265 this.signature = Math.floor(16807*this.signature+
266 label.charCodeAt(j)) %
269 if (userCSSList[i][1]) {
270 this.signature = Math.floor(16807*this.signature + 1) %
276 var key = 'shellInABox=' + this.signature + ':';
277 var settings = document.cookie.indexOf(key);
279 settings = document.cookie.substr(settings + key.length).
280 replace(/([0-1]*).*/, "$1");
281 if (settings.length == 2 + (typeof userCSSList == 'undefined' ?
282 0 : userCSSList.length)) {
283 this.utfPreferred = settings.charAt(0) != '0';
284 this.visualBell = settings.charAt(1) != '0';
285 if (typeof userCSSList != 'undefined') {
286 for (var i = 0; i < userCSSList.length; ++i) {
287 userCSSList[i][2] = settings.charAt(i + 2) != '0';
292 this.utfEnabled = this.utfPreferred;
295 VT100.prototype.storeUserSettings = function() {
296 var settings = 'shellInABox=' + this.signature + ':' +
297 (this.utfEnabled ? '1' : '0') +
298 (this.visualBell ? '1' : '0');
299 if (typeof userCSSList != 'undefined') {
300 for (var i = 0; i < userCSSList.length; ++i) {
301 settings += userCSSList[i][2] ? '1' : '0';
305 d.setDate(d.getDate() + 3653);
306 document.cookie = settings + ';expires=' + d.toGMTString();
309 VT100.prototype.initializeUserCSSStyles = function() {
310 this.usercssActions = [];
311 if (typeof userCSSList != 'undefined') {
314 var wasSingleSel = 1;
315 var beginOfGroup = 0;
316 for (var i = 0; i <= userCSSList.length; ++i) {
317 if (i < userCSSList.length) {
318 var label = userCSSList[i][0];
319 var newGroup = userCSSList[i][1];
320 var enabled = userCSSList[i][2];
322 // Add user style sheet to document
323 var style = document.createElement('link');
324 var id = document.createAttribute('id');
325 id.nodeValue = 'usercss-' + i;
326 style.setAttributeNode(id);
327 var rel = document.createAttribute('rel');
328 rel.nodeValue = 'stylesheet';
329 style.setAttributeNode(rel);
330 var href = document.createAttribute('href');
331 href.nodeValue = 'usercss-' + i + '.css';
332 style.setAttributeNode(href);
333 var type = document.createAttribute('type');
334 type.nodeValue = 'text/css';
335 style.setAttributeNode(type);
336 document.getElementsByTagName('head')[0].appendChild(style);
337 style.disabled = !enabled;
341 if (newGroup || i == userCSSList.length) {
342 if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
343 // The last group had multiple entries that are mutually exclusive;
344 // or the previous to last group did. In either case, we need to
345 // append a "<hr />" before we can add the last group to the menu.
348 wasSingleSel = i - beginOfGroup < 1;
352 for (var j = beginOfGroup; j < i; ++j) {
353 this.usercssActions[this.usercssActions.length] =
354 function(vt100, current, begin, count) {
356 // Deselect all other entries in the group, then either select
357 // (for multiple entries in group) or toggle (for on/off entry)
358 // the current entry.
360 var entry = vt100.getChildById(vt100.menu,
364 for (var c = count; c > 0; ++j) {
365 if (entry.tagName == 'LI') {
368 var label = vt100.usercss.childNodes[j];
370 // Restore label to just the text content
371 if (typeof label.textContent == 'undefined') {
372 var s = label.innerText;
373 label.innerHTML = '';
374 label.appendChild(document.createTextNode(s));
376 label.textContent= label.textContent;
379 // User style sheets are number sequentially
380 var sheet = document.getElementById(
384 sheet.disabled = !sheet.disabled;
386 sheet.disabled = false;
388 if (!sheet.disabled) {
389 label.innerHTML= '<img src="enabled.gif" />' +
393 sheet.disabled = true;
395 userCSSList[i][2] = !sheet.disabled;
398 entry = entry.nextSibling;
401 }(this, j, beginOfGroup, i - beginOfGroup);
404 if (i == userCSSList.length) {
410 // Collect all entries in a group, before attaching them to the menu.
411 // This is necessary as we don't know whether this is a group of
412 // mutually exclusive options (which should be separated by "<hr />" on
413 // both ends), or whether this is a on/off toggle, which can be grouped
414 // together with other on/off options.
416 '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
420 this.usercss.innerHTML = menu;
424 VT100.prototype.initializeElements = function(container) {
425 // If the necessary objects have not already been defined in the HTML
426 // page, create them now.
428 this.container = container;
429 } else if (!(this.container = document.getElementById('vt100'))) {
430 this.container = document.createElement('div');
431 this.container.id = 'vt100';
432 document.body.appendChild(this.container);
435 if (!this.getChildById(this.container, 'reconnect') ||
436 !this.getChildById(this.container, 'menu') ||
437 !this.getChildById(this.container, 'scrollable') ||
438 !this.getChildById(this.container, 'console') ||
439 !this.getChildById(this.container, 'alt_console') ||
440 !this.getChildById(this.container, 'ieprobe') ||
441 !this.getChildById(this.container, 'padding') ||
442 !this.getChildById(this.container, 'cursor') ||
443 !this.getChildById(this.container, 'lineheight') ||
444 !this.getChildById(this.container, 'usercss') ||
445 !this.getChildById(this.container, 'space') ||
446 !this.getChildById(this.container, 'input') ||
447 !this.getChildById(this.container, 'cliphelper')) {
448 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
449 // we might get a pointless warning that a suitable plugin is not yet
450 // installed. If in doubt, we'd rather just stay silent.
453 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
455 embed = typeof suppressAllAudio != 'undefined' &&
456 suppressAllAudio ? "" :
457 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
460 'autostart="false" ' +
462 'enablejavascript="true" ' +
463 'type="audio/x-wav" ' +
466 'style="position:absolute;left:-1000px;top:-1000px" />';
471 this.container.innerHTML =
472 '<div id="reconnect" style="visibility: hidden">' +
473 '<input type="button" value="Connect" ' +
474 'onsubmit="return false" />' +
476 '<div id="cursize" style="visibility: hidden">' +
478 '<div id="menu"></div>' +
479 '<div id="scrollable">' +
480 '<pre id="lineheight"> </pre>' +
481 '<pre id="console">' +
483 '<div id="ieprobe"><span> </span></div>' +
485 '<pre id="alt_console" style="display: none"></pre>' +
486 '<div id="padding"></div>' +
487 '<pre id="cursor"> </pre>' +
489 '<div class="hidden">' +
490 '<div id="usercss"></div>' +
491 '<pre><div><span id="space"></span></div></pre>' +
492 '<input type="textfield" id="input" />' +
493 '<input type="textfield" id="cliphelper" />' +
494 (typeof suppressAllAudio != 'undefined' &&
495 suppressAllAudio ? "" :
496 embed + '<bgsound id="beep_bgsound" loop=1 />') +
500 // Find the object used for playing the "beep" sound, if any.
501 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
502 this.beeper = undefined;
504 this.beeper = this.getChildById(this.container,
506 if (!this.beeper || !this.beeper.Play) {
507 this.beeper = this.getChildById(this.container,
509 if (!this.beeper || typeof this.beeper.src == 'undefined') {
510 this.beeper = undefined;
515 // Initialize the variables for finding the text console and the
517 this.reconnectBtn = this.getChildById(this.container,'reconnect');
518 this.curSizeBox = this.getChildById(this.container, 'cursize');
519 this.menu = this.getChildById(this.container, 'menu');
520 this.scrollable = this.getChildById(this.container,
522 this.lineheight = this.getChildById(this.container,
525 [ this.getChildById(this.container, 'console'),
526 this.getChildById(this.container, 'alt_console') ];
527 var ieProbe = this.getChildById(this.container, 'ieprobe');
528 this.padding = this.getChildById(this.container, 'padding');
529 this.cursor = this.getChildById(this.container, 'cursor');
530 this.usercss = this.getChildById(this.container, 'usercss');
531 this.space = this.getChildById(this.container, 'space');
532 this.input = this.getChildById(this.container, 'input');
533 this.cliphelper = this.getChildById(this.container,
536 // Add any user selectable style sheets to the menu
537 this.initializeUserCSSStyles();
539 // Remember the dimensions of a standard character glyph. We would
540 // expect that we could just check cursor.clientWidth/Height at any time,
541 // but it turns out that browsers sometimes invalidate these values
542 // (e.g. while displaying a print preview screen).
543 this.cursorWidth = this.cursor.clientWidth;
544 this.cursorHeight = this.lineheight.clientHeight;
546 // IE has a slightly different boxing model, that we need to compensate for
547 this.isIE = ieProbe.offsetTop > 1;
549 this.console.innerHTML = '';
551 // Determine if the terminal window is positioned at the beginning of the
552 // page, or if it is embedded somewhere else in the page. For full-screen
553 // terminals, automatically resize whenever the browser window changes.
554 var marginTop = parseInt(this.getCurrentComputedStyle(
555 document.body, 'marginTop'));
556 var marginLeft = parseInt(this.getCurrentComputedStyle(
557 document.body, 'marginLeft'));
558 var marginRight = parseInt(this.getCurrentComputedStyle(
559 document.body, 'marginRight'));
560 var x = this.container.offsetLeft;
561 var y = this.container.offsetTop;
562 for (var parent = this.container; parent = parent.offsetParent; ) {
563 x += parent.offsetLeft;
564 y += parent.offsetTop;
566 this.isEmbedded = marginTop != y ||
568 (window.innerWidth ||
569 document.documentElement.clientWidth ||
570 document.body.clientWidth) -
571 marginRight != x + this.container.offsetWidth;
572 if (!this.isEmbedded) {
573 // Some browsers generate resize events when the terminal is first
574 // shown. Disable showing the size indicator until a little bit after
575 // the terminal has been rendered the first time.
576 this.indicateSize = false;
577 setTimeout(function(vt100) {
579 vt100.indicateSize = true;
582 this.addListener(window, 'resize',
585 vt100.hideContextMenu();
587 vt100.showCurrentSize();
591 // Hide extra scrollbars attached to window
592 document.body.style.margin = '0px';
593 try { document.body.style.overflow ='hidden'; } catch (e) { }
594 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
598 this.hideContextMenu();
600 // Add listener to reconnect button
601 this.addListener(this.reconnectBtn.firstChild, 'click',
604 var rc = vt100.reconnect();
610 // Add input listeners
611 this.addListener(this.input, 'blur',
613 return function() { vt100.blurCursor(); } }(this));
614 this.addListener(this.input, 'focus',
616 return function() { vt100.focusCursor(); } }(this));
617 this.addListener(this.input, 'keydown',
620 if (!e) e = window.event;
621 return vt100.keyDown(e); } }(this));
622 this.addListener(this.input, 'keypress',
625 if (!e) e = window.event;
626 return vt100.keyPressed(e); } }(this));
627 this.addListener(this.input, 'keyup',
630 if (!e) e = window.event;
631 return vt100.keyUp(e); } }(this));
633 // Attach listeners that move the focus to the <input> field. This way we
634 // can make sure that we can receive keyboard input.
635 var mouseEvent = function(vt100, type) {
637 if (!e) e = window.event;
638 return vt100.mouseEvent(e, type);
641 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
642 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
643 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
645 // Initialize the blank terminal window.
646 this.currentScreen = 0;
649 this.numScrollbackLines = 0;
651 this.bottom = 0x7FFFFFFF;
657 VT100.prototype.getChildById = function(parent, id) {
658 var nodeList = parent.all || parent.getElementsByTagName('*');
659 if (typeof nodeList.namedItem == 'undefined') {
660 for (var i = 0; i < nodeList.length; i++) {
661 if (nodeList[i].id == id) {
667 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
668 return elem ? elem[0] || elem : null;
672 VT100.prototype.getCurrentComputedStyle = function(elem, style) {
673 if (typeof elem.currentStyle != 'undefined') {
674 return elem.currentStyle[style];
676 return document.defaultView.getComputedStyle(elem, null)[style];
680 VT100.prototype.reconnect = function() {
684 VT100.prototype.showReconnect = function(state) {
686 this.reconnectBtn.style.visibility = '';
688 this.reconnectBtn.style.visibility = 'hidden';
692 VT100.prototype.repairElements = function(console) {
693 for (var line = console.firstChild; line; line = line.nextSibling) {
694 if (!line.clientHeight) {
695 var newLine = document.createElement(line.tagName);
696 newLine.style.cssText = line.style.cssText;
697 newLine.className = line.className;
698 if (line.tagName == 'DIV') {
699 for (var span = line.firstChild; span; span = span.nextSibling) {
700 var newSpan = document.createElement(span.tagName);
701 newSpan.style.cssText = span.style.cssText;
702 newSpan.style.className = span.style.className;
703 this.setTextContent(newSpan, this.getTextContent(span));
704 newLine.appendChild(newSpan);
707 this.setTextContent(newLine, this.getTextContent(line));
709 line.parentNode.replaceChild(newLine, line);
715 VT100.prototype.resized = function(w, h) {
718 VT100.prototype.resizer = function() {
719 // The cursor can get corrupted if the print-preview is displayed in Firefox.
720 // Recreating it, will repair it.
721 var newCursor = document.createElement('pre');
722 this.setTextContent(newCursor, ' ');
723 newCursor.id = 'cursor';
724 newCursor.style.cssText = this.cursor.style.cssText;
725 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
726 if (!newCursor.clientHeight) {
727 // Things are broken right now. This is probably because we are
728 // displaying the print-preview. Just don't change any of our settings
729 // until the print dialog is closed again.
730 newCursor.parentNode.removeChild(newCursor);
733 // Swap the old broken cursor for the newly created one.
734 this.cursor.parentNode.removeChild(this.cursor);
735 this.cursor = newCursor;
738 // Really horrible things happen if the contents of the terminal changes
739 // while the print-preview is showing. We get HTML elements that show up
740 // in the DOM, but that do not take up any space. Find these elements and
742 this.repairElements(this.console[0]);
743 this.repairElements(this.console[1]);
745 // Lock the cursor size to the size of a normal character. This helps with
746 // characters that are taller/shorter than normal. Unfortunately, we will
747 // still get confused if somebody enters a character that is wider/narrower
748 // than normal. This can happen if the browser tries to substitute a
749 // characters from a different font.
750 this.cursor.style.width = this.cursorWidth + 'px';
751 this.cursor.style.height = this.cursorHeight + 'px';
753 // Adjust height for one pixel padding of the #vt100 element.
754 // The latter is necessary to properly display the inactive cursor.
755 var console = this.console[this.currentScreen];
756 var height = (this.isEmbedded ? this.container.clientHeight
757 : (window.innerHeight ||
758 document.documentElement.clientHeight ||
759 document.body.clientHeight))-1;
760 var partial = height % this.cursorHeight;
761 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
762 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
763 var oldTerminalHeight = this.terminalHeight;
767 // Clip the cursor to the visible screen.
768 var cx = this.cursorX;
769 var cy = this.cursorY + this.numScrollbackLines;
771 // The alternate screen never keeps a scroll back buffer.
772 this.updateNumScrollbackLines();
773 while (this.currentScreen && this.numScrollbackLines > 0) {
774 console.removeChild(console.firstChild);
775 this.numScrollbackLines--;
777 cy -= this.numScrollbackLines;
780 } else if (cx > this.terminalWidth) {
781 cx = this.terminalWidth - 1;
788 } else if (cy > this.terminalHeight) {
789 cy = this.terminalHeight - 1;
795 // Clip the scroll region to the visible screen.
796 if (this.bottom > this.terminalHeight ||
797 this.bottom == oldTerminalHeight) {
798 this.bottom = this.terminalHeight;
800 if (this.top >= this.bottom) {
801 this.top = this.bottom-1;
807 // Truncate lines, if necessary. Explicitly reposition cursor (this is
808 // particularly important after changing the screen number), and reset
809 // the scroll region to the default.
810 this.truncateLines(this.terminalWidth);
811 this.putString(cx, cy, '', undefined);
812 this.scrollable.scrollTop = this.numScrollbackLines *
813 this.cursorHeight + 1;
815 // Update classNames for lines in the scrollback buffer
816 var line = console.firstChild;
817 for (var i = 0; i < this.numScrollbackLines; i++) {
818 line.className = 'scrollback';
819 line = line.nextSibling;
823 line = line.nextSibling;
826 // Reposition the reconnect button
827 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
828 this.reconnectBtn.clientWidth)/2 + 'px';
829 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
830 this.reconnectBtn.clientHeight)/2 + 'px';
832 // Send notification that the window size has been changed
833 this.resized(this.terminalWidth, this.terminalHeight);
836 VT100.prototype.showCurrentSize = function() {
837 if (!this.indicateSize) {
840 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
842 this.curSizeBox.style.left =
843 (this.terminalWidth*this.cursorWidth -
844 this.curSizeBox.clientWidth)/2 + 'px';
845 this.curSizeBox.style.top =
846 (this.terminalHeight*this.cursorHeight -
847 this.curSizeBox.clientHeight)/2 + 'px';
848 this.curSizeBox.style.visibility = '';
849 if (this.curSizeTimeout) {
850 clearTimeout(this.curSizeTimeout);
853 // Only show the terminal size for a short amount of time after resizing.
854 // Then hide this information, again. Some browsers generate resize events
855 // throughout the entire resize operation. This is nice, and we will show
856 // the terminal size while the user is dragging the window borders.
857 // Other browsers only generate a single event when the user releases the
858 // mouse. In those cases, we can only show the terminal size once at the
859 // end of the resize operation.
860 this.curSizeTimeout = setTimeout(function(vt100) {
862 vt100.curSizeTimeout = null;
863 vt100.curSizeBox.style.visibility = 'hidden';
868 VT100.prototype.selection = function() {
870 return '' + (window.getSelection && window.getSelection() ||
871 document.selection && document.selection.type == 'Text' &&
872 document.selection.createRange().text || '');
878 VT100.prototype.cancelEvent = function(event) {
880 // For non-IE browsers
881 event.stopPropagation();
882 event.preventDefault();
887 event.cancelBubble = true;
888 event.returnValue = false;
896 VT100.prototype.mouseEvent = function(event, type) {
897 // If any text is currently selected, do not move the focus as that would
898 // invalidate the selection.
899 var selection = this.selection();
900 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
904 // Compute mouse position in characters.
905 var offsetX = this.container.offsetLeft;
906 var offsetY = this.container.offsetTop;
907 for (var e = this.container; e = e.offsetParent; ) {
908 offsetX += e.offsetLeft;
909 offsetY += e.offsetTop;
911 var x = (event.clientX - offsetX) / this.cursorWidth;
912 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
913 this.cursorHeight - this.numScrollbackLines;
915 if (x >= this.terminalWidth) {
916 x = this.terminalWidth - 1;
923 if (y >= this.terminalHeight) {
924 y = this.terminalHeight - 1;
932 // Compute button number and modifier keys.
933 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
934 typeof event.pageX != 'undefined' ? event.button :
935 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
936 if (button != undefined) {
937 if (event.shiftKey) {
940 if (event.altKey || event.metaKey) {
948 // Report mouse events if they happen inside of the current screen and
949 // with the SHIFT key unpressed. Both of these restrictions do not apply
950 // for button releases, as we always want to report those.
951 if (this.mouseReporting && !selection.length &&
952 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
953 if (inside || type != 0 /* MOUSE_DOWN */) {
954 if (button != undefined) {
955 var report = '\u001B[M' + String.fromCharCode(button + 32) +
956 String.fromCharCode(x + 33) +
957 String.fromCharCode(y + 33);
958 if (type != 2 /* MOUSE_CLICK */) {
959 this.keysPressed(report);
962 // If we reported the event, stop propagating it (not sure, if this
963 // actually works on most browsers; blocking the global "oncontextmenu"
964 // even is still necessary).
965 return this.cancelEvent(event);
970 // Bring up context menu.
971 if (button == 2 && !event.shiftKey) {
972 if (type == 0 /* MOUSE_DOWN */) {
973 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
975 return this.cancelEvent(event);
978 if (this.mouseReporting) {
980 event.shiftKey = false;
988 VT100.prototype.replaceChar = function(s, ch, repl) {
990 i = s.indexOf(ch, i + 1);
994 s = s.substr(0, i) + repl + s.substr(i + 1);
999 VT100.prototype.htmlEscape = function(s) {
1000 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1001 s, '&', '&'), '<', '<'), '"', '"'), ' ', '\u00A0');
1004 VT100.prototype.getTextContent = function(elem) {
1005 return elem.textContent ||
1006 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1009 VT100.prototype.setTextContent = function(elem, s) {
1010 // Check if we find any URLs in the text. If so, automatically convert them
1012 if (this.urlRE && this.urlRE.test(s)) {
1016 if (RegExp.leftContext != null) {
1017 inner += this.htmlEscape(RegExp.leftContext);
1018 consumed += RegExp.leftContext.length;
1020 var url = this.htmlEscape(RegExp.lastMatch);
1023 // If no protocol was specified, try to guess a reasonable one.
1024 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1025 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1026 var slash = url.indexOf('/');
1027 var at = url.indexOf('@');
1028 var question = url.indexOf('?');
1030 (at < question || question < 0) &&
1031 (slash < 0 || (question > 0 && slash > question))) {
1032 fullUrl = 'mailto:' + url;
1034 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1039 inner += '<a target="vt100Link" href="' + fullUrl +
1040 '">' + url + '</a>';
1041 consumed += RegExp.lastMatch.length;
1042 s = s.substr(consumed);
1043 if (!this.urlRE.test(s)) {
1044 if (RegExp.rightContext != null) {
1045 inner += this.htmlEscape(RegExp.rightContext);
1050 elem.innerHTML = inner;
1054 // Updating the content of an element is an expensive operation. It actually
1055 // pays off to first check whether the element is still unchanged.
1056 if (typeof elem.textContent == 'undefined') {
1057 if (elem.innerText != s) {
1061 // Very old versions of IE do not allow setting innerText. Instead,
1062 // remove all children, by setting innerHTML and then set the text
1063 // using DOM methods.
1064 elem.innerHTML = '';
1065 elem.appendChild(document.createTextNode(
1066 this.replaceChar(s, ' ', '\u00A0')));
1070 if (elem.textContent != s) {
1071 elem.textContent = s;
1076 VT100.prototype.insertBlankLine = function(y, color, style) {
1077 // Insert a blank line a position y. This method ignores the scrollback
1078 // buffer. The caller has to add the length of the scrollback buffer to
1079 // the position, if necessary.
1080 // If the position is larger than the number of current lines, this
1081 // method just adds a new line right after the last existing one. It does
1082 // not add any missing lines in between. It is the caller's responsibility
1085 color = 'ansi0 bgAnsi15';
1091 if (color != 'ansi0 bgAnsi15' && !style) {
1092 line = document.createElement('pre');
1093 this.setTextContent(line, '\n');
1095 line = document.createElement('div');
1096 var span = document.createElement('span');
1097 span.style.cssText = style;
1098 span.style.className = color;
1099 this.setTextContent(span, this.spaces(this.terminalWidth));
1100 line.appendChild(span);
1102 line.style.height = this.cursorHeight + 'px';
1103 var console = this.console[this.currentScreen];
1104 if (console.childNodes.length > y) {
1105 console.insertBefore(line, console.childNodes[y]);
1107 console.appendChild(line);
1111 VT100.prototype.updateWidth = function() {
1112 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1114 return this.terminalWidth;
1117 VT100.prototype.updateHeight = function() {
1118 // We want to be able to display either a terminal window that fills the
1119 // entire browser window, or a terminal window that is contained in a
1120 // <div> which is embededded somewhere in the web page.
1121 if (this.isEmbedded) {
1122 // Embedded terminal. Use size of the containing <div> (id="vt100").
1123 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1126 // Use the full browser window.
1127 this.terminalHeight = Math.floor(((window.innerHeight ||
1128 document.documentElement.clientHeight ||
1129 document.body.clientHeight)-1)/
1132 return this.terminalHeight;
1135 VT100.prototype.updateNumScrollbackLines = function() {
1136 var scrollback = Math.floor(
1137 this.console[this.currentScreen].offsetHeight /
1138 this.cursorHeight) -
1139 this.terminalHeight;
1140 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1141 return this.numScrollbackLines;
1144 VT100.prototype.truncateLines = function(width) {
1148 for (var line = this.console[this.currentScreen].firstChild; line;
1149 line = line.nextSibling) {
1150 if (line.tagName == 'DIV') {
1153 // Traverse current line and truncate it once we saw "width" characters
1154 for (var span = line.firstChild; span;
1155 span = span.nextSibling) {
1156 var s = this.getTextContent(span);
1158 if (x + l > width) {
1159 this.setTextContent(span, s.substr(0, width - x));
1160 while (span.nextSibling) {
1161 line.removeChild(line.lastChild);
1167 // Prune white space from the end of the current line
1168 var span = line.lastChild;
1170 span.className == 'ansi0 bgAnsi15' &&
1171 !span.style.cssText.length) {
1172 // Scan backwards looking for first non-space character
1173 var s = this.getTextContent(span);
1174 for (var i = s.length; i--; ) {
1175 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1176 if (i+1 != s.length) {
1177 this.setTextContent(s.substr(0, i+1));
1185 span = span.previousSibling;
1187 // Remove blank <span>'s from end of line
1188 line.removeChild(sibling);
1190 // Remove entire line (i.e. <div>), if empty
1191 var blank = document.createElement('pre');
1192 blank.style.height = this.cursorHeight + 'px';
1193 this.setTextContent(blank, '\n');
1194 line.parentNode.replaceChild(blank, line);
1202 VT100.prototype.putString = function(x, y, text, color, style) {
1204 color = 'ansi0 bgAnsi15';
1209 var yIdx = y + this.numScrollbackLines;
1215 var console = this.console[this.currentScreen];
1216 if (!text.length && (yIdx >= console.childNodes.length ||
1217 console.childNodes[yIdx].tagName != 'DIV')) {
1218 // Positioning cursor to a blank location
1221 // Create missing blank lines at end of page
1222 while (console.childNodes.length <= yIdx) {
1223 // In order to simplify lookups, we want to make sure that each line
1224 // is represented by exactly one element (and possibly a whole bunch of
1226 // For non-blank lines, we can create a <div> containing one or more
1227 // <span>s. For blank lines, this fails as browsers tend to optimize them
1228 // away. But fortunately, a <pre> tag containing a newline character
1229 // appears to work for all browsers (a would also work, but then
1230 // copying from the browser window would insert superfluous spaces into
1232 this.insertBlankLine(yIdx);
1234 line = console.childNodes[yIdx];
1236 // If necessary, promote blank '\n' line to a <div> tag
1237 if (line.tagName != 'DIV') {
1238 var div = document.createElement('div');
1239 div.style.height = this.cursorHeight + 'px';
1240 div.innerHTML = '<span></span>';
1241 console.replaceChild(div, line);
1245 // Scan through list of <span>'s until we find the one where our text
1247 span = line.firstChild;
1249 while (span.nextSibling && xPos < x) {
1250 len = this.getTextContent(span).length;
1251 if (xPos + len > x) {
1255 span = span.nextSibling;
1259 // If current <span> is not long enough, pad with spaces or add new
1261 s = this.getTextContent(span);
1262 var oldColor = span.className;
1263 var oldStyle = span.style.cssText;
1264 if (xPos + s.length < x) {
1265 if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1266 span = document.createElement('span');
1267 line.appendChild(span);
1268 span.className = 'ansi0 bgAnsi15';
1269 span.style.cssText = '';
1270 oldColor = 'ansi0 bgAnsi15';
1277 } while (xPos + s.length < x);
1280 // If styles do not match, create a new <span>
1281 var del = text.length - s.length + x - xPos;
1282 if (oldColor != color ||
1283 (oldStyle != style && (oldStyle || style))) {
1285 // Replacing text at beginning of existing <span>
1286 if (text.length >= s.length) {
1287 // New text is equal or longer than existing text
1290 // Insert new <span> before the current one, then remove leading
1291 // part of existing <span>, adjust style of new <span>, and finally
1293 sibling = document.createElement('span');
1294 line.insertBefore(sibling, span);
1295 this.setTextContent(span, s.substr(text.length));
1300 // Replacing text some way into the existing <span>
1301 var remainder = s.substr(x + text.length - xPos);
1302 this.setTextContent(span, s.substr(0, x - xPos));
1304 sibling = document.createElement('span');
1305 if (span.nextSibling) {
1306 line.insertBefore(sibling, span.nextSibling);
1308 if (remainder.length) {
1309 sibling = document.createElement('span');
1310 sibling.className = oldColor;
1311 sibling.style.cssText = oldStyle;
1312 this.setTextContent(sibling, remainder);
1313 line.insertBefore(sibling, span.nextSibling);
1316 line.appendChild(sibling);
1318 if (remainder.length) {
1319 sibling = document.createElement('span');
1320 sibling.className = oldColor;
1321 sibling.style.cssText = oldStyle;
1322 this.setTextContent(sibling, remainder);
1323 line.appendChild(sibling);
1328 span.className = color;
1329 span.style.cssText = style;
1331 // Overwrite (partial) <span> with new text
1332 s = s.substr(0, x - xPos) +
1334 s.substr(x + text.length - xPos);
1336 this.setTextContent(span, s);
1339 // Delete all subsequent <span>'s that have just been overwritten
1340 sibling = span.nextSibling;
1341 while (del > 0 && sibling) {
1342 s = this.getTextContent(sibling);
1345 line.removeChild(sibling);
1347 sibling = span.nextSibling;
1349 this.setTextContent(sibling, s.substr(del));
1354 // Merge <span> with next sibling, if styles are identical
1355 if (sibling && span.className == sibling.className &&
1356 span.style.cssText == sibling.style.cssText) {
1357 this.setTextContent(span,
1358 this.getTextContent(span) +
1359 this.getTextContent(sibling));
1360 line.removeChild(sibling);
1366 this.cursorX = x + text.length;
1367 if (this.cursorX >= this.terminalWidth) {
1368 this.cursorX = this.terminalWidth - 1;
1369 if (this.cursorX < 0) {
1375 if (!this.cursor.style.visibility) {
1376 var idx = this.cursorX - xPos;
1378 // If we are in a non-empty line, take the cursor Y position from the
1379 // other elements in this line. If dealing with broken, non-proportional
1380 // fonts, this is likely to yield better results.
1381 pixelY = span.offsetTop +
1382 span.offsetParent.offsetTop;
1383 s = this.getTextContent(span);
1384 var nxtIdx = idx - s.length;
1386 this.setTextContent(this.cursor, s.charAt(idx));
1387 pixelX = span.offsetLeft +
1388 idx*span.offsetWidth / s.length;
1391 pixelX = span.offsetLeft + span.offsetWidth;
1393 if (span.nextSibling) {
1394 s = this.getTextContent(span.nextSibling);
1395 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1397 pixelX = span.nextSibling.offsetLeft +
1398 nxtIdx*span.offsetWidth / s.length;
1401 this.setTextContent(this.cursor, ' ');
1405 this.setTextContent(this.cursor, ' ');
1409 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1411 this.setTextContent(this.space, this.spaces(this.cursorX));
1412 this.cursor.style.left = this.space.offsetWidth +
1413 console.offsetLeft + 'px';
1415 this.cursorY = yIdx - this.numScrollbackLines;
1417 this.cursor.style.top = pixelY + 'px';
1419 this.cursor.style.top = yIdx*this.cursorHeight +
1420 console.offsetTop + 'px';
1424 // Merge <span> with previous sibling, if styles are identical
1425 if ((sibling = span.previousSibling) &&
1426 span.className == sibling.className &&
1427 span.style.cssText == sibling.style.cssText) {
1428 this.setTextContent(span,
1429 this.getTextContent(sibling) +
1430 this.getTextContent(span));
1431 line.removeChild(sibling);
1434 // Prune white space from the end of the current line
1435 span = line.lastChild;
1437 span.className == 'ansi0 bgAnsi15' &&
1438 !span.style.cssText.length) {
1439 // Scan backwards looking for first non-space character
1440 s = this.getTextContent(span);
1441 for (var i = s.length; i--; ) {
1442 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1443 if (i+1 != s.length) {
1444 this.setTextContent(s.substr(0, i+1));
1452 span = span.previousSibling;
1454 // Remove blank <span>'s from end of line
1455 line.removeChild(sibling);
1457 // Remove entire line (i.e. <div>), if empty
1458 var blank = document.createElement('pre');
1459 blank.style.height = this.cursorHeight + 'px';
1460 this.setTextContent(blank, '\n');
1461 line.parentNode.replaceChild(blank, line);
1468 VT100.prototype.gotoXY = function(x, y) {
1469 if (x >= this.terminalWidth) {
1470 x = this.terminalWidth - 1;
1476 if (this.offsetMode) {
1481 maxY = this.terminalHeight;
1489 this.putString(x, y, '', undefined);
1490 this.needWrap = false;
1493 VT100.prototype.gotoXaY = function(x, y) {
1494 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1497 VT100.prototype.refreshInvertedState = function() {
1498 if (this.isInverted) {
1499 this.scrollable.className += ' inverted';
1501 this.scrollable.className = this.scrollable.className.
1502 replace(/ *inverted/, '');
1506 VT100.prototype.enableAlternateScreen = function(state) {
1507 // Don't do anything, if we are already on the desired screen
1508 if ((state ? 1 : 0) == this.currentScreen) {
1509 // Calling the resizer is not actually necessary. But it is a good way
1510 // of resetting state that might have gotten corrupted.
1515 // We save the full state of the normal screen, when we switch away from it.
1516 // But for the alternate screen, no saving is necessary. We always reset
1517 // it when we switch to it.
1522 // Display new screen, and initialize state (the resizer does that for us).
1523 this.currentScreen = state ? 1 : 0;
1524 this.console[1-this.currentScreen].style.display = 'none';
1525 this.console[this.currentScreen].style.display = '';
1528 // If we switched to the alternate screen, reset it completely. Otherwise,
1529 // restore the saved state.
1532 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1534 this.restoreCursor();
1538 VT100.prototype.hideCursor = function() {
1539 var hidden = this.cursor.style.visibility == 'hidden';
1541 this.cursor.style.visibility = 'hidden';
1547 VT100.prototype.showCursor = function(x, y) {
1548 if (this.cursor.style.visibility) {
1549 this.cursor.style.visibility = '';
1550 this.putString(x == undefined ? this.cursorX : x,
1551 y == undefined ? this.cursorY : y,
1558 VT100.prototype.scrollBack = function() {
1559 var i = this.scrollable.scrollTop -
1560 this.scrollable.clientHeight;
1561 this.scrollable.scrollTop = i < 0 ? 0 : i;
1564 VT100.prototype.scrollFore = function() {
1565 var i = this.scrollable.scrollTop +
1566 this.scrollable.clientHeight;
1567 this.scrollable.scrollTop = i > this.numScrollbackLines *
1568 this.cursorHeight + 1
1569 ? this.numScrollbackLines *
1570 this.cursorHeight + 1
1574 VT100.prototype.spaces = function(i) {
1582 VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
1587 if (w > this.terminalWidth) {
1588 w = this.terminalWidth;
1590 if ((w -= x) <= 0) {
1597 if (h > this.terminalHeight) {
1598 h = this.terminalHeight;
1600 if ((h -= y) <= 0) {
1604 // Special case the situation where we clear the entire screen, and we do
1605 // not have a scrollback buffer. In that case, we should just remove all
1607 if (!this.numScrollbackLines &&
1608 w == this.terminalWidth && h == this.terminalHeight &&
1609 (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
1610 var console = this.console[this.currentScreen];
1611 while (console.lastChild) {
1612 console.removeChild(console.lastChild);
1614 this.putString(this.cursorX, this.cursorY, '', undefined);
1616 var hidden = this.hideCursor();
1617 var cx = this.cursorX;
1618 var cy = this.cursorY;
1619 var s = this.spaces(w);
1620 for (var i = y+h; i-- > y; ) {
1621 this.putString(x, i, s, color, style);
1623 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1627 VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1629 var className = [ ];
1631 var console = this.console[this.currentScreen];
1632 if (sY >= console.childNodes.length) {
1633 text[0] = this.spaces(w);
1634 className[0] = undefined;
1635 style[0] = undefined;
1637 var line = console.childNodes[sY];
1638 if (line.tagName != 'DIV' || !line.childNodes.length) {
1639 text[0] = this.spaces(w);
1640 className[0] = undefined;
1641 style[0] = undefined;
1644 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1645 var s = this.getTextContent(span);
1648 var o = sX > x ? sX - x : 0;
1649 text[text.length] = s.substr(o, w);
1650 className[className.length] = span.className;
1651 style[style.length] = span.style.cssText;
1657 text[text.length] = this.spaces(w);
1658 className[className.length] = undefined;
1659 style[style.length] = undefined;
1663 var hidden = this.hideCursor();
1664 var cx = this.cursorX;
1665 var cy = this.cursorY;
1666 for (var i = 0; i < text.length; i++) {
1669 color = className[i];
1671 color = 'ansi0 bgAnsi15';
1673 this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
1674 dX += text[i].length;
1676 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1679 VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
1681 var left = incX < 0 ? -incX : 0;
1682 var right = incX > 0 ? incX : 0;
1683 var up = incY < 0 ? -incY : 0;
1684 var down = incY > 0 ? incY : 0;
1686 // Clip region against terminal size
1687 var dontScroll = null;
1692 if (w > this.terminalWidth - right) {
1693 w = this.terminalWidth - right;
1695 if ((w -= x) <= 0) {
1702 if (h > this.terminalHeight - down) {
1703 h = this.terminalHeight - down;
1709 if (style && style.indexOf('underline')) {
1710 // Different terminal emulators disagree on the attributes that
1711 // are used for scrolling. The consensus seems to be, never to
1712 // fill with underlined spaces. N.B. this is different from the
1713 // cases when the user blanks a region. User-initiated blanking
1714 // always fills with all of the current attributes.
1715 style = style.replace(/text-decoration:underline;/, '');
1718 // Compute current scroll position
1719 var scrollPos = this.numScrollbackLines -
1720 (this.scrollable.scrollTop-1) / this.cursorHeight;
1722 // Determine original cursor position. Hide cursor temporarily to avoid
1723 // visual artifacts.
1724 var hidden = this.hideCursor();
1725 var cx = this.cursorX;
1726 var cy = this.cursorY;
1727 var console = this.console[this.currentScreen];
1729 if (!incX && !x && w == this.terminalWidth) {
1730 // Scrolling entire lines
1733 if (!this.currentScreen && y == -incY &&
1734 h == this.terminalHeight + incY) {
1735 // Scrolling up with adding to the scrollback buffer. This is only
1736 // possible if there are at least as many lines in the console,
1737 // as the terminal is high
1738 while (console.childNodes.length < this.terminalHeight) {
1739 this.insertBlankLine(this.terminalHeight);
1742 // Add new lines at bottom in order to force scrolling
1743 for (var i = 0; i < y; i++) {
1744 this.insertBlankLine(console.childNodes.length, color, style);
1747 // Adjust the number of lines in the scrollback buffer by
1748 // removing excess entries.
1749 this.updateNumScrollbackLines();
1750 while (this.numScrollbackLines >
1751 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1752 console.removeChild(console.firstChild);
1753 this.numScrollbackLines--;
1756 // Mark lines in the scrollback buffer, so that they do not get
1758 for (var i = this.numScrollbackLines, j = -incY;
1759 i-- > 0 && j-- > 0; ) {
1760 console.childNodes[i].className = 'scrollback';
1763 // Scrolling up without adding to the scrollback buffer.
1766 console.childNodes.length >
1767 this.numScrollbackLines + y + incY; ) {
1768 console.removeChild(console.childNodes[
1769 this.numScrollbackLines + y + incY]);
1772 // If we used to have a scrollback buffer, then we must make sure
1773 // that we add back blank lines at the bottom of the terminal.
1774 // Similarly, if we are scrolling in the middle of the screen,
1775 // we must add blank lines to ensure that the bottom of the screen
1776 // does not move up.
1777 if (this.numScrollbackLines > 0 ||
1778 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1779 for (var i = -incY; i-- > 0; ) {
1780 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1789 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1790 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1792 for (var i = incY; i--; ) {
1793 this.insertBlankLine(this.numScrollbackLines + y, color, style);
1797 // Scrolling partial lines
1799 // Scrolling up or horizontally within a line
1800 for (var i = y + this.numScrollbackLines;
1801 i < y + this.numScrollbackLines + h;
1803 this.copyLineSegment(x + incX, i + incY, x, i, w);
1807 for (var i = y + this.numScrollbackLines + h;
1808 i-- > y + this.numScrollbackLines; ) {
1809 this.copyLineSegment(x + incX, i + incY, x, i, w);
1813 // Clear blank regions
1815 this.clearRegion(x, y, incX, h, color, style);
1816 } else if (incX < 0) {
1817 this.clearRegion(x + w + incX, y, -incX, h, color, style);
1820 this.clearRegion(x, y, w, incY, color, style);
1821 } else if (incY < 0) {
1822 this.clearRegion(x, y + h + incY, w, -incY, color, style);
1826 // Reset scroll position
1827 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1828 this.cursorHeight + 1;
1830 // Move cursor back to its original position
1831 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1835 VT100.prototype.copy = function(selection) {
1836 if (selection == undefined) {
1837 selection = this.selection();
1839 this.internalClipboard = undefined;
1840 if (selection.length) {
1843 this.cliphelper.value = selection;
1844 this.cliphelper.select();
1845 this.cliphelper.createTextRange().execCommand('copy');
1847 this.internalClipboard = selection;
1849 this.cliphelper.value = '';
1853 VT100.prototype.copyLast = function() {
1854 // Opening the context menu can remove the selection. We try to prevent this
1855 // from happening, but that is not possible for all browsers. So, instead,
1856 // we compute the selection before showing the menu.
1857 this.copy(this.lastSelection);
1860 VT100.prototype.pasteFnc = function() {
1861 var clipboard = undefined;
1862 if (this.internalClipboard != undefined) {
1863 clipboard = this.internalClipboard;
1866 this.cliphelper.value = '';
1867 this.cliphelper.createTextRange().execCommand('paste');
1868 clipboard = this.cliphelper.value;
1872 this.cliphelper.value = '';
1873 if (clipboard && this.menu.style.visibility == 'hidden') {
1875 this.keysPressed('' + clipboard);
1882 VT100.prototype.toggleUTF = function() {
1883 this.utfEnabled = !this.utfEnabled;
1885 // We always persist the last value that the user selected. Not necessarily
1886 // the last value that a random program requested.
1887 this.utfPreferred = this.utfEnabled;
1890 VT100.prototype.toggleBell = function() {
1891 this.visualBell = !this.visualBell;
1894 VT100.prototype.about = function() {
1895 alert("VT100 Terminal Emulator " + "2.9 (revision 174)" +
1896 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1897 "For more information check http://shellinabox.com");
1900 VT100.prototype.hideContextMenu = function() {
1901 this.menu.style.visibility = 'hidden';
1902 this.menu.style.top = '-100px';
1903 this.menu.style.left = '-100px';
1904 this.menu.style.width = '0px';
1905 this.menu.style.height = '0px';
1908 VT100.prototype.extendContextMenu = function(entries, actions) {
1911 VT100.prototype.showContextMenu = function(x, y) {
1912 this.menu.innerHTML =
1913 '<table class="popup" ' +
1914 'cellpadding="0" cellspacing="0">' +
1916 '<ul id="menuentries">' +
1917 '<li id="beginclipboard">Copy</li>' +
1918 '<li id="endclipboard">Paste</li>' +
1920 '<li id="reset">Reset</li>' +
1922 '<li id="beginconfig">' +
1923 (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
1925 '<li id="endconfig">' +
1926 (this.visualBell ? '<img src="enabled.gif" />' : '') +
1928 (this.usercss.firstChild ?
1929 '<hr id="beginusercss" />' +
1930 this.usercss.innerHTML +
1931 '<hr id="endusercss" />' :
1933 '<li id="about">About...</li>' +
1938 var popup = this.menu.firstChild;
1939 var menuentries = this.getChildById(popup, 'menuentries');
1941 // Determine menu entries that should be disabled
1942 this.lastSelection = this.selection();
1943 if (!this.lastSelection.length) {
1944 menuentries.firstChild.className
1947 var p = this.pasteFnc();
1949 menuentries.childNodes[1].className
1953 // Actions for default items
1954 var actions = [ this.copyLast, p, this.reset,
1955 this.toggleUTF, this.toggleBell ];
1957 // Actions for user CSS styles (if any)
1958 for (var i = 0; i < this.usercssActions.length; ++i) {
1959 actions[actions.length] = this.usercssActions[i];
1961 actions[actions.length] = this.about;
1963 // Allow subclasses to dynamically add entries to the context menu
1964 this.extendContextMenu(menuentries, actions);
1966 // Hook up event listeners
1967 for (var node = menuentries.firstChild, i = 0; node;
1968 node = node.nextSibling) {
1969 if (node.tagName == 'LI') {
1970 if (node.className != 'disabled') {
1971 this.addListener(node, 'mouseover',
1972 function(vt100, node) {
1974 node.className = 'hover';
1977 this.addListener(node, 'mouseout',
1978 function(vt100, node) {
1980 node.className = '';
1983 this.addListener(node, 'mousedown',
1984 function(vt100, action) {
1985 return function(event) {
1986 vt100.hideContextMenu();
1988 vt100.storeUserSettings();
1989 return vt100.cancelEvent(event || window.event);
1991 }(this, actions[i]));
1992 this.addListener(node, 'mouseup',
1994 return function(event) {
1995 return vt100.cancelEvent(event || window.event);
1998 this.addListener(node, 'mouseclick',
2000 return function(event) {
2001 return vt100.cancelEvent(event || window.event);
2009 // Position menu next to the mouse pointer
2010 if (x + popup.clientWidth > this.container.offsetWidth) {
2011 x = this.container.offsetWidth - popup.clientWidth;
2016 if (y + popup.clientHeight > this.container.offsetHeight) {
2017 y = this.container.offsetHeight-popup.clientHeight;
2022 popup.style.left = x + 'px';
2023 popup.style.top = y + 'px';
2025 // Block all other interactions with the terminal emulator
2026 this.menu.style.left = '0px';
2027 this.menu.style.top = '0px';
2028 this.menu.style.width = this.container.offsetWidth + 'px';
2029 this.menu.style.height = this.container.offsetHeight + 'px';
2030 this.addListener(this.menu, 'click', function(vt100) {
2032 vt100.hideContextMenu();
2037 this.menu.style.visibility = '';
2040 VT100.prototype.keysPressed = function(ch) {
2041 for (var i = 0; i < ch.length; i++) {
2042 var c = ch.charCodeAt(i);
2043 this.vt100(c >= 7 && c <= 15 ||
2044 c == 24 || c == 26 || c == 27 || c >= 32
2045 ? String.fromCharCode(c) : '<' + c + '>');
2049 VT100.prototype.handleKey = function(event) {
2051 if (typeof event.charCode != 'undefined') {
2052 // non-IE keypress events have a translated charCode value. Also, our
2053 // fake events generated when receiving keydown events include this data
2055 ch = event.charCode;
2056 key = event.keyCode;
2058 // When sending a keypress event, IE includes the translated character
2059 // code in the keyCode field.
2064 // Apply modifier keys (ctrl and shift)
2067 if (event.ctrlKey) {
2068 if (ch >= 32 && ch <= 127) {
2072 if (event.shiftKey) {
2073 if (ch >= 97 && ch <= 122) {
2077 if (ch >= 65 && ch <= 90) {
2086 // By this point, "ch" is either defined and contains the character code, or
2087 // it is undefined and "key" defines the code of a function key
2088 if (ch != undefined) {
2089 ch = String.fromCharCode(ch);
2090 this.scrollable.scrollTop = this.numScrollbackLines *
2091 this.cursorHeight + 1;
2093 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2094 // Many programs have difficulties dealing with parametrized escape
2095 // sequences for function keys. Thus, if ALT is the only modifier
2096 // key, return Emacs-style keycodes for commonly used keys.
2098 case 33: /* Page Up */ ch = '\u001B<'; break;
2099 case 34: /* Page Down */ ch = '\u001B>'; break;
2100 case 37: /* Left */ ch = '\u001Bb'; break;
2101 case 38: /* Up */ ch = '\u001Bp'; break;
2102 case 39: /* Right */ ch = '\u001Bf'; break;
2103 case 40: /* Down */ ch = '\u001Bn'; break;
2104 case 46: /* Delete */ ch = '\u001Bd'; break;
2107 } else if (event.shiftKey && !event.ctrlKey &&
2108 !event.altKey && !event.metaKey) {
2110 case 33: /* Page Up */ this.scrollBack(); return;
2111 case 34: /* Page Down */ this.scrollFore(); return;
2115 if (ch == undefined) {
2117 case 8: /* Backspace */ ch = '\u007f'; break;
2118 case 9: /* Tab */ ch = '\u0009'; break;
2119 case 10: /* Return */ ch = '\u000A'; break;
2120 case 13: /* Enter */ ch = this.crLfMode ?
2121 '\r\n' : '\r'; break;
2122 case 16: /* Shift */ return;
2123 case 17: /* Ctrl */ return;
2124 case 18: /* Alt */ return;
2125 case 19: /* Break */ return;
2126 case 20: /* Caps Lock */ return;
2127 case 27: /* Escape */ ch = '\u001B'; break;
2128 case 33: /* Page Up */ ch = '\u001B[5~'; break;
2129 case 34: /* Page Down */ ch = '\u001B[6~'; break;
2130 case 35: /* End */ ch = '\u001BOF'; break;
2131 case 36: /* Home */ ch = '\u001BOH'; break;
2132 case 37: /* Left */ ch = this.cursorKeyMode ?
2133 '\u001BOD' : '\u001B[D'; break;
2134 case 38: /* Up */ ch = this.cursorKeyMode ?
2135 '\u001BOA' : '\u001B[A'; break;
2136 case 39: /* Right */ ch = this.cursorKeyMode ?
2137 '\u001BOC' : '\u001B[C'; break;
2138 case 40: /* Down */ ch = this.cursorKeyMode ?
2139 '\u001BOB' : '\u001B[B'; break;
2140 case 45: /* Insert */ ch = '\u001B[2~'; break;
2141 case 46: /* Delete */ ch = '\u001B[3~'; break;
2142 case 91: /* Left Window */ return;
2143 case 92: /* Right Window */ return;
2144 case 93: /* Select */ return;
2145 case 96: /* 0 */ ch = '0'; break;
2146 case 97: /* 1 */ ch = '1'; break;
2147 case 98: /* 2 */ ch = '2'; break;
2148 case 99: /* 3 */ ch = '3'; break;
2149 case 100: /* 4 */ ch = '4'; break;
2150 case 101: /* 5 */ ch = '5'; break;
2151 case 102: /* 6 */ ch = '6'; break;
2152 case 103: /* 7 */ ch = '7'; break;
2153 case 104: /* 8 */ ch = '8'; break;
2154 case 105: /* 9 */ ch = '9'; break;
2155 case 106: /* * */ ch = '*'; break;
2156 case 107: /* + */ ch = '+'; break;
2157 case 109: /* - */ ch = '-'; break;
2158 case 110: /* . */ ch = '.'; break;
2159 case 111: /* / */ ch = '/'; break;
2160 case 112: /* F1 */ ch = '\u001BOP'; break;
2161 case 113: /* F2 */ ch = '\u001BOQ'; break;
2162 case 114: /* F3 */ ch = '\u001BOR'; break;
2163 case 115: /* F4 */ ch = '\u001BOS'; break;
2164 case 116: /* F5 */ ch = '\u001B[15~'; break;
2165 case 117: /* F6 */ ch = '\u001B[17~'; break;
2166 case 118: /* F7 */ ch = '\u001B[18~'; break;
2167 case 119: /* F8 */ ch = '\u001B[19~'; break;
2168 case 120: /* F9 */ ch = '\u001B[20~'; break;
2169 case 121: /* F10 */ ch = '\u001B[21~'; break;
2170 case 122: /* F11 */ ch = '\u001B[23~'; break;
2171 case 123: /* F12 */ ch = '\u001B[24~'; break;
2172 case 144: /* Num Lock */ return;
2173 case 145: /* Scroll Lock */ return;
2176 this.scrollable.scrollTop = this.numScrollbackLines *
2177 this.cursorHeight + 1;
2181 // "ch" now contains the sequence of keycodes to send. But we might still
2182 // have to apply the effects of modifier keys.
2183 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2184 var start, digit, part1, part2;
2185 if ((start = ch.substr(0, 2)) == '\u001B[') {
2187 part1.length < ch.length &&
2188 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2189 part1 = ch.substr(0, part1.length + 1);
2191 part2 = ch.substr(part1.length);
2192 if (part1.length > 2) {
2195 } else if (start == '\u001BO') {
2197 part2 = ch.substr(2);
2199 if (part1 != undefined) {
2201 ((event.shiftKey ? 1 : 0) +
2202 (event.altKey|event.metaKey ? 2 : 0) +
2203 (event.ctrlKey ? 4 : 0)) +
2205 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2210 if (this.menu.style.visibility == 'hidden') {
2211 // this.vt100('R: c=');
2212 // for (var i = 0; i < ch.length; i++)
2213 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2214 // this.vt100('\r\n');
2215 this.keysPressed(ch);
2219 VT100.prototype.inspect = function(o, d) {
2220 if (d == undefined) {
2224 if (typeof o == 'object' && ++d < 2) {
2227 rc += this.spaces(d * 2) + i + ' -> ';
2229 rc += this.inspect(o[i], d);
2231 rc += '?' + '?' + '?\r\n';
2236 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2241 VT100.prototype.checkComposedKeys = function(event) {
2242 // Composed keys (at least on Linux) do not generate normal events.
2243 // Instead, they get entered into the text field. We normally catch
2244 // this on the next keyup event.
2245 var s = this.input.value;
2247 this.input.value = '';
2248 if (this.menu.style.visibility == 'hidden') {
2249 this.keysPressed(s);
2254 VT100.prototype.fixEvent = function(event) {
2255 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2256 // is used as a second-level selector, clear the modifier bits before
2257 // handling the event.
2258 if (event.ctrlKey && event.altKey) {
2260 fake.charCode = event.charCode;
2261 fake.keyCode = event.keyCode;
2262 fake.ctrlKey = false;
2263 fake.shiftKey = event.shiftKey;
2264 fake.altKey = false;
2265 fake.metaKey = event.metaKey;
2269 // Some browsers fail to translate keys, if both shift and alt/meta is
2270 // pressed at the same time. We try to translate those cases, but that
2271 // only works for US keyboard layouts.
2272 if (event.shiftKey) {
2275 switch (this.lastNormalKeyDownEvent.keyCode) {
2276 case 39: /* ' -> " */ u = 39; s = 34; break;
2277 case 44: /* , -> < */ u = 44; s = 60; break;
2278 case 45: /* - -> _ */ u = 45; s = 95; break;
2279 case 46: /* . -> > */ u = 46; s = 62; break;
2280 case 47: /* / -> ? */ u = 47; s = 63; break;
2282 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2283 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2284 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2285 case 51: /* 3 -> # */ u = 51; s = 35; break;
2286 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2287 case 53: /* 5 -> % */ u = 53; s = 37; break;
2288 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2289 case 55: /* 7 -> & */ u = 55; s = 38; break;
2290 case 56: /* 8 -> * */ u = 56; s = 42; break;
2291 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2293 case 59: /* ; -> : */ u = 59; s = 58; break;
2294 case 61: /* = -> + */ u = 61; s = 43; break;
2295 case 91: /* [ -> { */ u = 91; s = 123; break;
2296 case 92: /* \ -> | */ u = 92; s = 124; break;
2297 case 93: /* ] -> } */ u = 93; s = 125; break;
2298 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2300 case 109: /* - -> _ */ u = 45; s = 95; break;
2301 case 111: /* / -> ? */ u = 47; s = 63; break;
2303 case 186: /* ; -> : */ u = 59; s = 58; break;
2304 case 187: /* = -> + */ u = 61; s = 43; break;
2305 case 188: /* , -> < */ u = 44; s = 60; break;
2306 case 189: /* - -> _ */ u = 45; s = 95; break;
2307 case 190: /* . -> > */ u = 46; s = 62; break;
2308 case 191: /* / -> ? */ u = 47; s = 63; break;
2309 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2310 case 219: /* [ -> { */ u = 91; s = 123; break;
2311 case 220: /* \ -> | */ u = 92; s = 124; break;
2312 case 221: /* ] -> } */ u = 93; s = 125; break;
2313 case 222: /* ' -> " */ u = 39; s = 34; break;
2316 if (s && (event.charCode == u || event.charCode == 0)) {
2319 fake.keyCode = event.keyCode;
2320 fake.ctrlKey = event.ctrlKey;
2321 fake.shiftKey = event.shiftKey;
2322 fake.altKey = event.altKey;
2323 fake.metaKey = event.metaKey;
2330 VT100.prototype.keyDown = function(event) {
2331 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2332 // (event.shiftKey || event.ctrlKey || event.altKey ||
2333 // event.metaKey ? ', ' +
2334 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2335 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2337 this.checkComposedKeys(event);
2338 this.lastKeyPressedEvent = undefined;
2339 this.lastKeyDownEvent = undefined;
2340 this.lastNormalKeyDownEvent = event;
2343 event.keyCode == 32 ||
2344 event.keyCode >= 48 && event.keyCode <= 57 ||
2345 event.keyCode >= 65 && event.keyCode <= 90;
2348 event.keyCode >= 96 && event.keyCode <= 105 ||
2349 event.keyCode == 226;
2352 event.keyCode == 59 || event.keyCode == 61 ||
2353 event.keyCode == 106 || event.keyCode == 107 ||
2354 event.keyCode >= 109 && event.keyCode <= 111 ||
2355 event.keyCode >= 186 && event.keyCode <= 192 ||
2356 event.keyCode >= 219 && event.keyCode <= 222 ||
2357 event.keyCode == 252;
2359 if (navigator.appName == 'Konqueror') {
2360 normalKey |= event.keyCode < 128;
2365 // We normally prefer to look at keypress events, as they perform the
2366 // translation from keyCode to charCode. This is important, as the
2367 // translation is locale-dependent.
2368 // But for some keys, we must intercept them during the keydown event,
2369 // as they would otherwise get interpreted by the browser.
2370 // Even, when doing all of this, there are some keys that we can never
2371 // intercept. This applies to some of the menu navigation keys in IE.
2372 // In fact, we see them, but we cannot stop IE from seeing them, too.
2373 if ((event.charCode || event.keyCode) &&
2374 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2376 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2377 // interpret this sequence ourselves, as some keyboard layouts use
2378 // it for second-level layouts.
2379 !(event.ctrlKey && event.altKey)) ||
2380 this.catchModifiersEarly && normalKey && !alphNumKey &&
2381 (event.ctrlKey || event.altKey || event.metaKey) ||
2383 this.lastKeyDownEvent = event;
2385 fake.ctrlKey = event.ctrlKey;
2386 fake.shiftKey = event.shiftKey;
2387 fake.altKey = event.altKey;
2388 fake.metaKey = event.metaKey;
2390 fake.charCode = event.keyCode;
2394 fake.keyCode = event.keyCode;
2395 if (!alphNumKey && event.shiftKey) {
2396 fake = this.fixEvent(fake);
2400 this.handleKey(fake);
2401 this.lastNormalKeyDownEvent = undefined;
2404 // For non-IE browsers
2405 event.stopPropagation();
2406 event.preventDefault();
2411 event.cancelBubble = true;
2412 event.returnValue = false;
2422 VT100.prototype.keyPressed = function(event) {
2423 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2424 // (event.shiftKey || event.ctrlKey || event.altKey ||
2425 // event.metaKey ? ', ' +
2426 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2427 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2429 if (this.lastKeyDownEvent) {
2430 // If we already processed the key on keydown, do not process it
2431 // again here. Ideally, the browser should not even have generated a
2432 // keypress event in this case. But that does not appear to always work.
2433 this.lastKeyDownEvent = undefined;
2435 this.handleKey(event.altKey || event.metaKey
2436 ? this.fixEvent(event) : event);
2440 // For non-IE browsers
2441 event.preventDefault();
2447 event.cancelBubble = true;
2448 event.returnValue = false;
2453 this.lastNormalKeyDownEvent = undefined;
2454 this.lastKeyPressedEvent = event;
2458 VT100.prototype.keyUp = function(event) {
2459 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2460 // (event.shiftKey || event.ctrlKey || event.altKey ||
2461 // event.metaKey ? ', ' +
2462 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2463 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2465 if (this.lastKeyPressedEvent) {
2466 // The compose key on Linux occasionally confuses the browser and keeps
2467 // inserting bogus characters into the input field, even if just a regular
2468 // key has been pressed. Detect this case and drop the bogus characters.
2470 event.srcElement).value = '';
2472 // This is usually were we notice that a key has been composed and
2473 // thus failed to generate normal events.
2474 this.checkComposedKeys(event);
2476 // Some browsers don't report keypress events if ctrl or alt is pressed
2477 // for non-alphanumerical keys. Patch things up for now, but in the
2478 // future we will catch these keys earlier (in the keydown handler).
2479 if (this.lastNormalKeyDownEvent) {
2480 this.catchModifiersEarly = true;
2482 event.keyCode == 32 ||
2483 event.keyCode >= 48 && event.keyCode <= 57 ||
2484 event.keyCode >= 65 && event.keyCode <= 90;
2487 event.keyCode >= 96 && event.keyCode <= 105;
2490 event.keyCode == 59 || event.keyCode == 61 ||
2491 event.keyCode == 106 || event.keyCode == 107 ||
2492 event.keyCode >= 109 && event.keyCode <= 111 ||
2493 event.keyCode >= 186 && event.keyCode <= 192 ||
2494 event.keyCode >= 219 && event.keyCode <= 222 ||
2495 event.keyCode == 252;
2497 fake.ctrlKey = event.ctrlKey;
2498 fake.shiftKey = event.shiftKey;
2499 fake.altKey = event.altKey;
2500 fake.metaKey = event.metaKey;
2502 fake.charCode = event.keyCode;
2506 fake.keyCode = event.keyCode;
2507 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2508 fake = this.fixEvent(fake);
2511 this.lastNormalKeyDownEvent = undefined;
2512 this.handleKey(fake);
2518 event.cancelBubble = true;
2519 event.returnValue = false;
2524 this.lastKeyDownEvent = undefined;
2525 this.lastKeyPressedEvent = undefined;
2529 VT100.prototype.animateCursor = function(inactive) {
2530 if (!this.cursorInterval) {
2531 this.cursorInterval = setInterval(
2534 vt100.animateCursor();
2536 // Use this opportunity to check whether the user entered a composed
2537 // key, or whether somebody pasted text into the textfield.
2538 vt100.checkComposedKeys();
2542 if (inactive != undefined || this.cursor.className != 'inactive') {
2544 this.cursor.className = 'inactive';
2546 this.cursor.className = this.cursor.className == 'bright'
2552 VT100.prototype.blurCursor = function() {
2553 this.animateCursor(true);
2556 VT100.prototype.focusCursor = function() {
2557 this.animateCursor(false);
2560 VT100.prototype.flashScreen = function() {
2561 this.isInverted = !this.isInverted;
2562 this.refreshInvertedState();
2563 this.isInverted = !this.isInverted;
2564 setTimeout(function(vt100) {
2566 vt100.refreshInvertedState();
2571 VT100.prototype.beep = function() {
2572 if (this.visualBell) {
2579 this.beeper.src = 'beep.wav';
2586 VT100.prototype.bs = function() {
2587 if (this.cursorX > 0) {
2588 this.gotoXY(this.cursorX - 1, this.cursorY);
2589 this.needWrap = false;
2593 VT100.prototype.ht = function(count) {
2594 if (count == undefined) {
2597 var cx = this.cursorX;
2598 while (count-- > 0) {
2599 while (cx++ < this.terminalWidth) {
2600 var tabState = this.userTabStop[cx];
2601 if (tabState == false) {
2602 // Explicitly cleared tab stop
2604 } else if (tabState) {
2605 // Explicitly set tab stop
2608 // Default tab stop at each eighth column
2615 if (cx > this.terminalWidth - 1) {
2616 cx = this.terminalWidth - 1;
2618 if (cx != this.cursorX) {
2619 this.gotoXY(cx, this.cursorY);
2623 VT100.prototype.rt = function(count) {
2624 if (count == undefined) {
2627 var cx = this.cursorX;
2628 while (count-- > 0) {
2630 var tabState = this.userTabStop[cx];
2631 if (tabState == false) {
2632 // Explicitly cleared tab stop
2634 } else if (tabState) {
2635 // Explicitly set tab stop
2638 // Default tab stop at each eighth column
2648 if (cx != this.cursorX) {
2649 this.gotoXY(cx, this.cursorY);
2653 VT100.prototype.cr = function() {
2654 this.gotoXY(0, this.cursorY);
2655 this.needWrap = false;
2658 VT100.prototype.lf = function(count) {
2659 if (count == undefined) {
2662 if (count > this.terminalHeight) {
2663 count = this.terminalHeight;
2669 while (count-- > 0) {
2670 if (this.cursorY == this.bottom - 1) {
2671 this.scrollRegion(0, this.top + 1,
2672 this.terminalWidth, this.bottom - this.top - 1,
2673 0, -1, this.color, this.style);
2675 } else if (this.cursorY < this.terminalHeight - 1) {
2676 this.gotoXY(this.cursorX, this.cursorY + 1);
2681 VT100.prototype.ri = function(count) {
2682 if (count == undefined) {
2685 if (count > this.terminalHeight) {
2686 count = this.terminalHeight;
2692 while (count-- > 0) {
2693 if (this.cursorY == this.top) {
2694 this.scrollRegion(0, this.top,
2695 this.terminalWidth, this.bottom - this.top - 1,
2696 0, 1, this.color, this.style);
2697 } else if (this.cursorY > 0) {
2698 this.gotoXY(this.cursorX, this.cursorY - 1);
2701 this.needWrap = false;
2704 VT100.prototype.respondID = function() {
2705 this.respondString += '\u001B[?6c';
2708 VT100.prototype.respondSecondaryDA = function() {
2709 this.respondString += '\u001B[>0;0;0c';
2713 VT100.prototype.updateStyle = function() {
2715 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2716 this.style = 'text-decoration:underline;';
2718 var bg = (this.attr >> 4) & 0xF;
2719 var fg = this.attr & 0xF;
2720 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2725 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2726 fg = 8; // Dark grey
2727 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2730 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2733 // Make some readability enhancements. Most notably, disallow identical
2734 // background and foreground colors.
2736 if ((fg ^= 8) == 7) {
2740 // And disallow bright colors on a light-grey background.
2741 if (bg == 7 && fg >= 8) {
2742 if ((fg -= 8) == 7) {
2747 this.color = 'ansi' + fg + ' bgAnsi' + bg;
2750 VT100.prototype.setAttrColors = function(attr) {
2751 if (attr != this.attr) {
2757 VT100.prototype.saveCursor = function() {
2758 this.savedX[this.currentScreen] = this.cursorX;
2759 this.savedY[this.currentScreen] = this.cursorY;
2760 this.savedAttr[this.currentScreen] = this.attr;
2761 this.savedUseGMap = this.useGMap;
2762 for (var i = 0; i < 4; i++) {
2763 this.savedGMap[i] = this.GMap[i];
2765 this.savedValid[this.currentScreen] = true;
2768 VT100.prototype.restoreCursor = function() {
2769 if (!this.savedValid[this.currentScreen]) {
2772 this.attr = this.savedAttr[this.currentScreen];
2774 this.useGMap = this.savedUseGMap;
2775 for (var i = 0; i < 4; i++) {
2776 this.GMap[i] = this.savedGMap[i];
2778 this.translate = this.GMap[this.useGMap];
2779 this.needWrap = false;
2780 this.gotoXY(this.savedX[this.currentScreen],
2781 this.savedY[this.currentScreen]);
2784 VT100.prototype.setMode = function(state) {
2785 for (var i = 0; i <= this.npar; i++) {
2786 if (this.isQuestionMark) {
2787 switch (this.par[i]) {
2788 case 1: this.cursorKeyMode = state; break;
2789 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2790 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2791 case 6: this.offsetMode = state; break;
2792 case 7: this.autoWrapMode = state; break;
2794 case 9: this.mouseReporting = state; break;
2795 case 25: this.cursorNeedsShowing = state;
2796 if (state) { this.showCursor(); }
2797 else { this.hideCursor(); } break;
2800 case 47: this.enableAlternateScreen(state); break;
2804 switch (this.par[i]) {
2805 case 3: this.dispCtrl = state; break;
2806 case 4: this.insertMode = state; break;
2807 case 20:this.crLfMode = state; break;
2814 VT100.prototype.statusReport = function() {
2815 // Ready and operational.
2816 this.respondString += '\u001B[0n';
2819 VT100.prototype.cursorReport = function() {
2820 this.respondString += '\u001B[' +
2821 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2823 (this.cursorX + 1) +
2827 VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2828 // Changing of cursor color is not implemented.
2831 VT100.prototype.csiAt = function(number) {
2836 if (number > this.terminalWidth - this.cursorX) {
2837 number = this.terminalWidth - this.cursorX;
2839 this.scrollRegion(this.cursorX, this.cursorY,
2840 this.terminalWidth - this.cursorX - number, 1,
2841 number, 0, this.color, this.style);
2842 this.needWrap = false;
2845 VT100.prototype.csiJ = function(number) {
2847 case 0: // Erase from cursor to end of display
2848 this.clearRegion(this.cursorX, this.cursorY,
2849 this.terminalWidth - this.cursorX, 1,
2850 this.color, this.style);
2851 if (this.cursorY < this.terminalHeight-2) {
2852 this.clearRegion(0, this.cursorY+1,
2853 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2854 this.color, this.style);
2857 case 1: // Erase from start to cursor
2858 if (this.cursorY > 0) {
2859 this.clearRegion(0, 0,
2860 this.terminalWidth, this.cursorY,
2861 this.color, this.style);
2863 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
2864 this.color, this.style);
2866 case 2: // Erase whole display
2867 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
2868 this.color, this.style);
2876 VT100.prototype.csiK = function(number) {
2878 case 0: // Erase from cursor to end of line
2879 this.clearRegion(this.cursorX, this.cursorY,
2880 this.terminalWidth - this.cursorX, 1,
2881 this.color, this.style);
2883 case 1: // Erase from start of line to cursor
2884 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
2885 this.color, this.style);
2887 case 2: // Erase whole line
2888 this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
2889 this.color, this.style);
2897 VT100.prototype.csiL = function(number) {
2898 // Open line by inserting blank line(s)
2899 if (this.cursorY >= this.bottom) {
2905 if (number > this.bottom - this.cursorY) {
2906 number = this.bottom - this.cursorY;
2908 this.scrollRegion(0, this.cursorY,
2909 this.terminalWidth, this.bottom - this.cursorY - number,
2910 0, number, this.color, this.style);
2914 VT100.prototype.csiM = function(number) {
2915 // Delete line(s), scrolling up the bottom of the screen.
2916 if (this.cursorY >= this.bottom) {
2922 if (number > this.bottom - this.cursorY) {
2923 number = bottom - cursorY;
2925 this.scrollRegion(0, this.cursorY + number,
2926 this.terminalWidth, this.bottom - this.cursorY - number,
2927 0, -number, this.color, this.style);
2931 VT100.prototype.csim = function() {
2932 for (var i = 0; i <= this.npar; i++) {
2933 switch (this.par[i]) {
2934 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2935 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2936 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2937 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2938 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2939 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2941 this.translate = this.GMap[this.useGMap];
2942 this.dispCtrl = false;
2943 this.toggleMeta = false;
2946 this.translate = this.CodePage437Map;
2947 this.dispCtrl = true;
2948 this.toggleMeta = false;
2951 this.translate = this.CodePage437Map;
2952 this.dispCtrl = true;
2953 this.toggleMeta = true;
2956 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2957 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2958 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2959 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2960 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2961 0x0200 /* ATTR_UNDERLINE */; break;
2962 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2963 case 49: this.attr |= 0xF0; break;
2965 if (this.par[i] >= 30 && this.par[i] <= 37) {
2966 var fg = this.par[i] - 30;
2967 this.attr = (this.attr & ~0x0F) | fg;
2968 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2969 var bg = this.par[i] - 40;
2970 this.attr = (this.attr & ~0xF0) | (bg << 4);
2978 VT100.prototype.csiP = function(number) {
2979 // Delete character(s) following cursor
2983 if (number > this.terminalWidth - this.cursorX) {
2984 number = this.terminalWidth - this.cursorX;
2986 this.scrollRegion(this.cursorX + number, this.cursorY,
2987 this.terminalWidth - this.cursorX - number, 1,
2988 -number, 0, this.color, this.style);
2992 VT100.prototype.csiX = function(number) {
2993 // Clear characters following cursor
2997 if (number > this.terminalWidth - this.cursorX) {
2998 number = this.terminalWidth - this.cursorX;
3000 this.clearRegion(this.cursorX, this.cursorY, number, 1,
3001 this.color, this.style);
3005 VT100.prototype.settermCommand = function() {
3006 // Setterm commands are not implemented
3009 VT100.prototype.doControl = function(ch) {
3012 case 0x00: /* ignored */ break;
3013 case 0x08: this.bs(); break;
3014 case 0x09: this.ht(); break;
3018 case 0x84: this.lf(); if (!this.crLfMode) break;
3019 case 0x0D: this.cr(); break;
3020 case 0x85: this.cr(); this.lf(); break;
3021 case 0x0E: this.useGMap = 1;
3022 this.translate = this.GMap[1];
3023 this.dispCtrl = true; break;
3024 case 0x0F: this.useGMap = 0;
3025 this.translate = this.GMap[0];
3026 this.dispCtrl = false; break;
3028 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3029 case 0x1B: this.isEsc = 1 /* ESesc */; break;
3030 case 0x7F: /* ignored */ break;
3031 case 0x88: this.userTabStop[this.cursorX] = true; break;
3032 case 0x8D: this.ri(); break;
3033 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3034 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3035 case 0x9A: this.respondID(); break;
3036 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3037 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
3041 default: switch (this.isEsc) {
3043 this.isEsc = 0 /* ESnormal */;
3045 /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3046 /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3048 /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3050 /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3052 /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3053 /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3054 /*7*/ case 0x37: this.saveCursor(); break;
3055 /*8*/ case 0x38: this.restoreCursor(); break;
3056 /*>*/ case 0x3E: this.applKeyMode = false; break;
3057 /*=*/ case 0x3D: this.applKeyMode = true; break;
3058 /*D*/ case 0x44: this.lf(); break;
3059 /*E*/ case 0x45: this.cr(); this.lf(); break;
3060 /*M*/ case 0x4D: this.ri(); break;
3061 /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3062 /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3063 /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3064 /*Z*/ case 0x5A: this.respondID(); break;
3065 /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3066 /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3067 /*c*/ case 0x63: this.reset(); break;
3068 /*g*/ case 0x67: this.flashScreen(); break;
3072 case 15 /* ESnonstd */:
3076 /*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
3077 /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3078 this.isEsc = 16 /* ESpalette */; break;
3079 /*R*/ case 0x52: // Palette support is not implemented
3080 this.isEsc = 0 /* ESnormal */; break;
3081 default: this.isEsc = 0 /* ESnormal */; break;
3084 case 16 /* ESpalette */:
3085 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3086 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3087 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3088 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3090 if (this.npar == 7) {
3091 // Palette support is not implemented
3092 this.isEsc = 0 /* ESnormal */;
3095 this.isEsc = 0 /* ESnormal */;
3098 case 2 /* ESsquare */:
3100 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3101 0, 0, 0, 0, 0, 0, 0, 0 ];
3102 this.isEsc = 3 /* ESgetpars */;
3103 /*[*/ if (ch == 0x5B) { // Function key
3104 this.isEsc = 6 /* ESfunckey */;
3107 /*?*/ this.isQuestionMark = ch == 0x3F;
3108 if (this.isQuestionMark) {
3113 case 5 /* ESdeviceattr */:
3114 case 3 /* ESgetpars */:
3115 /*;*/ if (ch == 0x3B) {
3118 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3119 var par = this.par[this.npar];
3120 if (par == undefined) {
3123 this.par[this.npar] = 10*par + (ch & 0xF);
3125 } else if (this.isEsc == 5 /* ESdeviceattr */) {
3127 /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3128 /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3129 /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3130 /*p*/ case 0x70: /* set pointer mode resource value */ break;
3133 this.isEsc = 0 /* ESnormal */;
3136 this.isEsc = 4 /* ESgotpars */;
3139 case 4 /* ESgotpars */:
3140 this.isEsc = 0 /* ESnormal */;
3141 if (this.isQuestionMark) {
3143 /*h*/ case 0x68: this.setMode(true); break;
3144 /*l*/ case 0x6C: this.setMode(false); break;
3145 /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3148 this.isQuestionMark = false;
3152 /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3153 /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3155 /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3156 /*A*/ case 0x41: this.gotoXY(this.cursorX,
3157 this.cursorY - (this.par[0] ? this.par[0] : 1));
3160 /*e*/ case 0x65: this.gotoXY(this.cursorX,
3161 this.cursorY + (this.par[0] ? this.par[0] : 1));
3164 /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3165 this.cursorY); break;
3166 /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3167 this.cursorY); break;
3168 /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3170 /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3172 /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3174 /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3175 /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3176 /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3177 /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3178 /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3179 /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3180 /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3181 /*m*/ case 0x6D: this.csim(); break;
3182 /*P*/ case 0x50: this.csiP(this.par[0]); break;
3183 /*X*/ case 0x58: this.csiX(this.par[0]); break;
3184 /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
3185 /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
3186 /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
3187 /*g*/ case 0x67: if (this.par[0] == 0) {
3188 this.userTabStop[this.cursorX] = false;
3189 } else if (this.par[0] == 2 || this.par[0] == 3) {
3190 this.userTabStop = [ ];
3191 for (var i = 0; i < this.terminalWidth; i++) {
3192 this.userTabStop[i] = false;
3196 /*h*/ case 0x68: this.setMode(true); break;
3197 /*l*/ case 0x6C: this.setMode(false); break;
3198 /*n*/ case 0x6E: switch (this.par[0]) {
3199 case 5: this.statusReport(); break;
3200 case 6: this.cursorReport(); break;
3204 /*q*/ case 0x71: // LED control not implemented
3206 /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
3207 var b = this.par[1] ? this.par[1]
3208 : this.terminalHeight;
3209 if (t < b && b <= this.terminalHeight) {
3215 /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3216 if (c > this.terminalWidth * this.terminalHeight) {
3217 c = this.terminalWidth * this.terminalHeight;
3220 lineBuf += this.lastCharacter;
3223 /*s*/ case 0x73: this.saveCursor(); break;
3224 /*u*/ case 0x75: this.restoreCursor(); break;
3225 /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3226 /*]*/ case 0x5D: this.settermCommand(); break;
3230 case 12 /* ESbang */:
3234 this.isEsc = 0 /* ESnormal */;
3236 case 13 /* ESpercent */:
3237 this.isEsc = 0 /* ESnormal */;
3239 /*@*/ case 0x40: this.utfEnabled = false; break;
3241 /*8*/ case 0x38: this.utfEnabled = true; break;
3245 case 6 /* ESfunckey */:
3246 this.isEsc = 0 /* ESnormal */; break;
3247 case 7 /* EShash */:
3248 this.isEsc = 0 /* ESnormal */;
3249 /*8*/ if (ch == 0x38) {
3250 // Screen alignment test not implemented
3253 case 8 /* ESsetG0 */:
3254 case 9 /* ESsetG1 */:
3255 case 10 /* ESsetG2 */:
3256 case 11 /* ESsetG3 */:
3257 var g = this.isEsc - 8 /* ESsetG0 */;
3258 this.isEsc = 0 /* ESnormal */;
3260 /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3262 /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3263 /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3264 /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3267 if (this.useGMap == g) {
3268 this.translate = this.GMap[g];
3271 case 17 /* ESstatus */:
3273 if (this.statusString && this.statusString.charAt(0) == ';') {
3274 this.statusString = this.statusString.substr(1);
3277 window.status = this.statusString;
3280 this.isEsc = 0 /* ESnormal */;
3282 this.statusString += String.fromCharCode(ch);
3285 case 18 /* ESss2 */:
3286 case 19 /* ESss3 */:
3288 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3289 [this.toggleMeta ? (ch | 0x80) : ch];
3290 if ((ch & 0xFF00) == 0xF000) {
3292 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3293 this.isEsc = 0 /* ESnormal */; break;
3296 this.lastCharacter = String.fromCharCode(ch);
3297 lineBuf += this.lastCharacter;
3298 this.isEsc = 0 /* ESnormal */; break;
3300 this.isEsc = 0 /* ESnormal */; break;
3307 VT100.prototype.renderString = function(s, showCursor) {
3308 // We try to minimize the number of DOM operations by coalescing individual
3309 // characters into strings. This is a significant performance improvement.
3310 var incX = s.length;
3311 if (incX > this.terminalWidth - this.cursorX) {
3312 incX = this.terminalWidth - this.cursorX;
3316 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3319 // Minimize the number of calls to putString(), by avoiding a direct
3320 // call to this.showCursor()
3321 this.cursor.style.visibility = '';
3323 this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
3326 VT100.prototype.vt100 = function(s) {
3327 this.cursorNeedsShowing = this.hideCursor();
3328 this.respondString = '';
3330 for (var i = 0; i < s.length; i++) {
3331 var ch = s.charCodeAt(i);
3332 if (this.utfEnabled) {
3333 // Decode UTF8 encoded character
3335 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3336 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3337 if (--this.utfCount <= 0) {
3338 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3347 if ((ch & 0xE0) == 0xC0) {
3349 this.utfChar = ch & 0x1F;
3350 } else if ((ch & 0xF0) == 0xE0) {
3352 this.utfChar = ch & 0x0F;
3353 } else if ((ch & 0xF8) == 0xF0) {
3355 this.utfChar = ch & 0x07;
3356 } else if ((ch & 0xFC) == 0xF8) {
3358 this.utfChar = ch & 0x03;
3359 } else if ((ch & 0xFE) == 0xFC) {
3361 this.utfChar = ch & 0x01;
3371 var isNormalCharacter =
3372 (ch >= 32 && ch <= 127 || ch >= 160 ||
3373 this.utfEnabled && ch >= 128 ||
3374 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3375 (ch != 0x7F || this.dispCtrl);
3377 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3379 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3381 if ((ch & 0xFF00) == 0xF000) {
3383 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3386 if (this.needWrap || this.insertMode) {
3388 this.renderString(lineBuf);
3392 if (this.needWrap) {
3393 this.cr(); this.lf();
3395 if (this.insertMode) {
3396 this.scrollRegion(this.cursorX, this.cursorY,
3397 this.terminalWidth - this.cursorX - 1, 1,
3398 1, 0, this.color, this.style);
3400 this.lastCharacter = String.fromCharCode(ch);
3401 lineBuf += this.lastCharacter;
3402 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3403 this.needWrap = this.autoWrapMode;
3407 this.renderString(lineBuf);
3410 var expand = this.doControl(ch);
3411 if (expand.length) {
3412 var r = this.respondString;
3413 this.respondString= r + this.vt100(expand);
3418 this.renderString(lineBuf, this.cursorNeedsShowing);
3419 } else if (this.cursorNeedsShowing) {
3422 return this.respondString;
3425 VT100.prototype.Latin1Map = [
3426 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3427 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3428 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3429 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3430 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3431 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3432 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3433 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3434 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3435 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3436 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3437 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3438 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3439 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3440 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3441 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
3442 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3443 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3444 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3445 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3446 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3447 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3448 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3449 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3450 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3451 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3452 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3453 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3454 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3455 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3456 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3457 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3460 VT100.prototype.VT100GraphicsMap = [
3461 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
3462 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
3463 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
3464 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
3465 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3466 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
3467 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3468 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3469 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3470 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3471 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3472 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
3473 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
3474 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
3475 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
3476 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
3477 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
3478 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
3479 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
3480 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
3481 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
3482 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
3483 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
3484 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
3485 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
3486 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
3487 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
3488 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
3489 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
3490 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
3491 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
3492 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3495 VT100.prototype.CodePage437Map = [
3496 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
3497 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
3498 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
3499 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
3500 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
3501 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
3502 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
3503 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
3504 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
3505 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
3506 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
3507 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
3508 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
3509 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
3510 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
3511 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
3512 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
3513 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
3514 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
3515 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
3516 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
3517 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
3518 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
3519 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
3520 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
3521 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
3522 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
3523 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
3524 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
3525 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
3526 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
3527 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3530 VT100.prototype.DirectToFontMap = [
3531 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
3532 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
3533 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
3534 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
3535 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
3536 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
3537 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
3538 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
3539 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
3540 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
3541 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
3542 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
3543 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
3544 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
3545 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
3546 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
3547 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
3548 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
3549 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
3550 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
3551 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
3552 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
3553 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
3554 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
3555 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
3556 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
3557 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
3558 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
3559 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
3560 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
3561 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
3562 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3565 VT100.prototype.ctrlAction = [
3566 true, false, false, false, false, false, false, true,
3567 true, true, true, true, true, true, true, true,
3568 false, false, false, false, false, false, false, false,
3569 true, false, true, true, false, false, false, false
3572 VT100.prototype.ctrlAlways = [
3573 true, false, false, false, false, false, false, false,
3574 true, false, true, false, true, true, true, true,
3575 false, false, false, false, false, false, false, false,
3576 false, false, false, true, false, false, false, false