]> andersk Git - test.git/blame - demo/vt100.js
Sanitize the SSH command line a little more.
[test.git] / demo / vt100.js
CommitLineData
ce845548
MG
1// VT100.js -- JavaScript based terminal emulator
2// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
3//
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.
7//
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.
12//
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.
16//
17// In addition to these license terms, the author grants the following
18// additional rights:
19//
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.
28//
29// You may at your option choose to remove this additional permission from
30// the work, or from any part of it.
31//
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:
35//
36// This product includes software developed by the OpenSSL Project
37// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
38//
39// This product includes cryptographic software written by Eric Young
40// (eay@cryptsoft.com)
41//
42//
43// The most up-to-date version of this program is always available from
44// http://shellinabox.com
45//
46//
47// Notes:
48//
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.
56//
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.
62//
63// If in doubt, consult a legal professional familiar with the laws that
64// apply in your country.
65
66// #define ESnormal 0
67// #define ESesc 1
68// #define ESsquare 2
69// #define ESgetpars 3
70// #define ESgotpars 4
71// #define ESdeviceattr 5
72// #define ESfunckey 6
73// #define EShash 7
74// #define ESsetG0 8
75// #define ESsetG1 9
76// #define ESsetG2 10
77// #define ESsetG3 11
78// #define ESbang 12
79// #define ESpercent 13
80// #define ESignore 14
81// #define ESnonstd 15
82// #define ESpalette 16
83// #define ESstatus 17
84// #define ESss2 18
85// #define ESss3 19
86
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
93
94// #define MOUSE_DOWN 0
95// #define MOUSE_UP 1
96// #define MOUSE_CLICK 2
97
98function VT100(container) {
f4f914a4 99 if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
f0c6fd39
MG
100 this.urlRE = null;
101 } else {
102 this.urlRE = new RegExp(
103 // Known URL protocol are "http", "https", and "ftp".
104 '(?:http|https|ftp)://' +
105
106 // Optionally allow username and passwords.
ce0cf224 107 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
f0c6fd39
MG
108
109 // Hostname.
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})+|' +
a7164199 112 '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
f0c6fd39
MG
113
114 // Port
115 '(?::[1-9][0-9]*)?' +
116
117 // Path.
4ad8e70f 118 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
f0c6fd39
MG
119
120 (linkifyURLs <= 1 ? '' :
121 // Also support URLs without a protocol (assume "http").
122 // Optional username and password.
ce0cf224 123 '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
f0c6fd39
MG
124
125 // Hostnames must end with a well-known top-level domain or must be
126 // numeric.
127 '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
128 'localhost|' +
a7164199
MG
129 '(?:(?!-)' +
130 '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
f0c6fd39
MG
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|' +
a7164199 143 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
f0c6fd39
MG
144
145 // Port
146 '(?::[1-9][0-9]{0,4})?' +
147
148 // Path.
4ad8e70f 149 '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
f0c6fd39
MG
150
151 // In addition, support e-mail address. Optionally, recognize "mailto:"
152 '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
153
154 // Username:
155 '[-_.+a-zA-Z0-9]+@' +
156
157 // Hostname.
158 '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
a7164199 159 '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
f0c6fd39
MG
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|' +
a7164199 171 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
f0c6fd39
MG
172
173 // Optional arguments
4ad8e70f 174 '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
f0c6fd39 175 }
ce845548
MG
176 this.initializeElements(container);
177 this.initializeAnsiColors();
178 this.maxScrollbackLines = 500;
179 this.npar = 0;
180 this.par = [ ];
181 this.isQuestionMark = false;
182 this.savedX = [ ];
183 this.savedY = [ ];
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;
192 this.reset(true);
193}
194
195VT100.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 = true;
208 this.visualBell = typeof suppressAllAudio !=
209 'undefined' &&
210 suppressAllAudio;
211 this.utfCount = 0;
212 this.utfChar = 0;
213 this.style = '';
214 this.attr = 0x00F0 /* ATTR_DEFAULT */;
215 this.useGMap = 0;
216 this.GMap = [ this.Latin1Map,
217 this.VT100GraphicsMap,
218 this.CodePage437Map,
219 this.DirectToFontMap ];
220 this.translate = this.GMap[this.useGMap];
221 this.top = 0;
222 this.bottom = this.terminalHeight;
223 this.lastCharacter = ' ';
224 this.userTabStop = [ ];
225
226 if (clearHistory) {
227 for (var i = 0; i < 2; i++) {
228 while (this.console[i].firstChild) {
229 this.console[i].removeChild(this.console[i].firstChild);
230 }
231 }
232 }
233
234 this.enableAlternateScreen(false);
235 this.gotoXY(0, 0);
236 this.showCursor();
237 this.isInverted = false;
238 this.refreshInvertedState();
239 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight, this.style);
240};
241
242VT100.prototype.initializeAnsiColors = function() {
243 var elem = document.createElement('pre');
244 this.container.appendChild(elem);
245 this.setTextContent(elem, ' ');
246 this.ansi = [ ];
247 for (var i = 0; i < 16; i++) {
248 elem.id = 'ansi' + i;
249 this.ansi[i] = this.getCurrentComputedStyle(elem, 'backgroundColor');
250 }
251 this.container.removeChild(elem);
252};
253
254VT100.prototype.addListener = function(elem, event, listener) {
255 if (elem.addEventListener) {
256 elem.addEventListener(event, listener, false);
257 } else {
258 elem.attachEvent('on' + event, listener);
259 }
260};
261
262VT100.prototype.initializeElements = function(container) {
263 // If the necessary objects have not already been defined in the HTML
264 // page, create them now.
265 if (container) {
266 this.container = container;
267 } else if (!(this.container = document.getElementById('vt100'))) {
268 this.container = document.createElement('div');
269 this.container.id = 'vt100';
270 document.body.appendChild(this.container);
271 }
272
273 if (!this.getChildById(this.container, 'reconnect') ||
274 !this.getChildById(this.container, 'menu') ||
275 !this.getChildById(this.container, 'scrollable') ||
276 !this.getChildById(this.container, 'console') ||
277 !this.getChildById(this.container, 'alt_console') ||
278 !this.getChildById(this.container, 'ieprobe') ||
279 !this.getChildById(this.container, 'padding') ||
280 !this.getChildById(this.container, 'cursor') ||
281 !this.getChildById(this.container, 'lineheight') ||
282 !this.getChildById(this.container, 'space') ||
283 !this.getChildById(this.container, 'input') ||
284 !this.getChildById(this.container, 'cliphelper') ||
285 !this.getChildById(this.container, 'attrib')) {
286 // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
287 // we might get a pointless warning that a suitable plugin is not yet
288 // installed. If in doubt, we'd rather just stay silent.
289 var embed = '';
290 try {
291 if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
292 'undefined') {
293 embed = typeof suppressAllAudio != 'undefined' &&
294 suppressAllAudio ? "" :
295 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
296 'id="beep_embed" ' +
297 'src="beep.wav" ' +
298 'autostart="false" ' +
299 'volume="100" ' +
300 'enablejavascript="true" ' +
301 'type="audio/x-wav" ' +
302 'height="16" ' +
303 'width="200" ' +
304 'style="position:absolute;left:-1000px;top:-1000px" />';
305 }
306 } catch (e) {
307 }
308
309 this.container.innerHTML =
310 '<div id="reconnect" style="visibility: hidden">' +
311 '<input type="button" value="Connect" ' +
312 'onsubmit="return false" />' +
313 '</div>' +
a7164199
MG
314 '<div id="cursize" style="visibility: hidden">' +
315 '</div>' +
ce845548
MG
316 '<div id="menu"></div>' +
317 '<div id="scrollable">' +
318 '<pre id="lineheight">&nbsp;</pre>' +
319 '<pre id="console">' +
320 '<pre></pre>' +
321 '<div id="ieprobe"><span>&nbsp;</span></div>' +
322 '</pre>' +
323 '<pre id="alt_console" style="display: none"></pre>' +
324 '<div id="padding"></div>' +
325 '<pre id="cursor">&nbsp;</pre>' +
326 '</div>' +
327 '<div class="hidden">' +
328 '<pre><div><span id="space"></span></div></pre>' +
329 '<input type="textfield" id="input" />' +
330 '<input type="textfield" id="cliphelper" />' +
331 '<span id="attrib">&nbsp;</span>' +
332 (typeof suppressAllAudio != 'undefined' &&
333 suppressAllAudio ? "" :
334 embed + '<bgsound id="beep_bgsound" loop=1 />') +
335 '</div>';
336 }
337
338 // Find the object used for playing the "beep" sound, if any.
339 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
340 this.beeper = undefined;
341 } else {
342 this.beeper = this.getChildById(this.container,
343 'beep_embed');
344 if (!this.beeper || !this.beeper.Play) {
345 this.beeper = this.getChildById(this.container,
346 'beep_bgsound');
347 if (!this.beeper || typeof this.beeper.src == 'undefined') {
348 this.beeper = undefined;
349 }
350 }
351 }
352
353 // Initialize the variables for finding the text console and the
354 // cursor.
355 this.reconnectBtn = this.getChildById(this.container,'reconnect');
a7164199 356 this.curSizeBox = this.getChildById(this.container, 'cursize');
ce845548
MG
357 this.menu = this.getChildById(this.container, 'menu');
358 this.scrollable = this.getChildById(this.container,
359 'scrollable');
360 this.lineheight = this.getChildById(this.container,
361 'lineheight');
362 this.console =
363 [ this.getChildById(this.container, 'console'),
364 this.getChildById(this.container, 'alt_console') ];
365 var ieProbe = this.getChildById(this.container, 'ieprobe');
366 this.padding = this.getChildById(this.container, 'padding');
367 this.cursor = this.getChildById(this.container, 'cursor');
368 this.space = this.getChildById(this.container, 'space');
369 this.input = this.getChildById(this.container, 'input');
370 this.cliphelper = this.getChildById(this.container,
371 'cliphelper');
372 this.attributeHelper = this.getChildById(this.container, 'attrib');
373
374 // Remember the dimensions of a standard character glyph. We would
375 // expect that we could just check cursor.clientWidth/Height at any time,
376 // but it turns out that browsers sometimes invalidate these values
377 // (e.g. while displaying a print preview screen).
378 this.cursorWidth = this.cursor.clientWidth;
379 this.cursorHeight = this.lineheight.clientHeight;
380
381 // IE has a slightly different boxing model, that we need to compensate for
382 this.isIE = ieProbe.offsetTop > 1;
383 ieProbe = undefined;
384 this.console.innerHTML = '';
385
386 // Determine if the terminal window is positioned at the beginning of the
387 // page, or if it is embedded somewhere else in the page. For full-screen
388 // terminals, automatically resize whenever the browser window changes.
389 var marginTop = parseInt(this.getCurrentComputedStyle(
390 document.body, 'marginTop'));
391 var marginLeft = parseInt(this.getCurrentComputedStyle(
392 document.body, 'marginLeft'));
393 var marginRight = parseInt(this.getCurrentComputedStyle(
394 document.body, 'marginRight'));
395 var x = this.container.offsetLeft;
396 var y = this.container.offsetTop;
397 for (var parent = this.container; parent = parent.offsetParent; ) {
398 x += parent.offsetLeft;
399 y += parent.offsetTop;
400 }
401 this.isEmbedded = marginTop != y ||
402 marginLeft != x ||
403 (window.innerWidth ||
404 document.documentElement.clientWidth ||
405 document.body.clientWidth) -
406 marginRight != x + this.container.offsetWidth;
407 if (!this.isEmbedded) {
a7164199
MG
408 // Some browsers generate resize events when the terminal is first
409 // shown. Disable showing the size indicator until a little bit after
410 // the terminal has been rendered the first time.
411 this.indicateSize = false;
412 setTimeout(function(vt100) {
413 return function() {
414 vt100.indicateSize = true;
415 };
416 }(this), 100);
ce845548
MG
417 this.addListener(window, 'resize',
418 function(vt100) {
419 return function() {
420 vt100.hideContextMenu();
421 vt100.resizer();
a7164199 422 vt100.showCurrentSize();
ce845548
MG
423 }
424 }(this));
425
426 // Hide extra scrollbars attached to window
427 document.body.style.margin = '0px';
428 try { document.body.style.overflow ='hidden'; } catch (e) { }
429 try { document.body.oncontextmenu = function() {return false;};} catch(e){}
430 }
431
432 // Hide context menu
433 this.hideContextMenu();
434
435 // Add listener to reconnect button
436 this.addListener(this.reconnectBtn.firstChild, 'click',
437 function(vt100) {
438 return function() {
439 var rc = vt100.reconnect();
440 vt100.input.focus();
441 return rc;
442 }
443 }(this));
444
445 // Add input listeners
446 this.addListener(this.input, 'blur',
447 function(vt100) {
448 return function() { vt100.blurCursor(); } }(this));
449 this.addListener(this.input, 'focus',
450 function(vt100) {
451 return function() { vt100.focusCursor(); } }(this));
452 this.addListener(this.input, 'keydown',
453 function(vt100) {
454 return function(e) {
455 if (!e) e = window.event;
456 return vt100.keyDown(e); } }(this));
457 this.addListener(this.input, 'keypress',
458 function(vt100) {
459 return function(e) {
460 if (!e) e = window.event;
461 return vt100.keyPressed(e); } }(this));
462 this.addListener(this.input, 'keyup',
463 function(vt100) {
464 return function(e) {
465 if (!e) e = window.event;
466 return vt100.keyUp(e); } }(this));
467
468 // Attach listeners that move the focus to the <input> field. This way we
469 // can make sure that we can receive keyboard input.
470 var mouseEvent = function(vt100, type) {
471 return function(e) {
472 if (!e) e = window.event;
473 return vt100.mouseEvent(e, type);
474 };
475 };
476 this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
477 this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
478 this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
479
480 // Initialize the blank terminal window.
481 this.currentScreen = 0;
482 this.cursorX = 0;
483 this.cursorY = 0;
484 this.numScrollbackLines = 0;
485 this.top = 0;
486 this.bottom = 0x7FFFFFFF;
487 this.resizer();
488 this.focusCursor();
489 this.input.focus();
490};
491
492VT100.prototype.getChildById = function(parent, id) {
493 var nodeList = parent.all || parent.getElementsByTagName('*');
494 if (typeof nodeList.namedItem == 'undefined') {
495 for (var i = 0; i < nodeList.length; i++) {
496 if (nodeList[i].id == id) {
497 return nodeList[i];
498 }
499 }
500 return null;
501 } else {
502 var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
503 return elem ? elem[0] || elem : null;
504 }
505};
506
507VT100.prototype.getCurrentComputedStyle = function(elem, style) {
508 if (typeof elem.currentStyle != 'undefined') {
509 return elem.currentStyle[style];
510 } else {
511 return document.defaultView.getComputedStyle(elem, null)[style];
512 }
513};
514
515VT100.prototype.reconnect = function() {
516 return false;
517};
518
519VT100.prototype.showReconnect = function(state) {
520 if (state) {
521 this.reconnectBtn.style.visibility = '';
522 } else {
523 this.reconnectBtn.style.visibility = 'hidden';
524 }
525};
526
527VT100.prototype.repairElements = function(console) {
528 for (var line = console.firstChild; line; line = line.nextSibling) {
529 if (!line.clientHeight) {
530 var newLine = document.createElement(line.tagName);
531 newLine.style.cssText = line.style.cssText;
532 newLine.className = line.className;
533 if (line.tagName == 'DIV') {
534 for (var span = line.firstChild; span; span = span.nextSibling) {
535 var newSpan = document.createElement(span.tagName);
536 newSpan.style.cssText = span.style.cssText;
537 this.setTextContent(newSpan, this.getTextContent(span));
538 newLine.appendChild(newSpan);
539 }
540 } else {
541 this.setTextContent(newLine, this.getTextContent(line));
542 }
543 line.parentNode.replaceChild(newLine, line);
544 line = newLine;
545 }
546 }
547};
548
549VT100.prototype.resized = function(w, h) {
550};
551
552VT100.prototype.resizer = function() {
553 // The cursor can get corrupted if the print-preview is displayed in Firefox.
554 // Recreating it, will repair it.
555 var newCursor = document.createElement('pre');
556 this.setTextContent(newCursor, ' ');
557 newCursor.id = 'cursor';
558 newCursor.style.cssText = this.cursor.style.cssText;
559 this.cursor.parentNode.insertBefore(newCursor, this.cursor);
560 if (!newCursor.clientHeight) {
561 // Things are broken right now. This is probably because we are
562 // displaying the print-preview. Just don't change any of our settings
563 // until the print dialog is closed again.
564 newCursor.parentNode.removeChild(newCursor);
565 return;
566 } else {
567 // Swap the old broken cursor for the newly created one.
568 this.cursor.parentNode.removeChild(this.cursor);
569 this.cursor = newCursor;
570 }
571
572 // Really horrible things happen if the contents of the terminal changes
573 // while the print-preview is showing. We get HTML elements that show up
574 // in the DOM, but that do not take up any space. Find these elements and
575 // try to fix them.
576 this.repairElements(this.console[0]);
577 this.repairElements(this.console[1]);
578
579 // Lock the cursor size to the size of a normal character. This helps with
580 // characters that are taller/shorter than normal. Unfortunately, we will
581 // still get confused if somebody enters a character that is wider/narrower
582 // than normal. This can happen if the browser tries to substitute a
583 // characters from a different font.
584 this.cursor.style.width = this.cursorWidth + 'px';
585 this.cursor.style.height = this.cursorHeight + 'px';
586
587 // Adjust height for one pixel padding of the #vt100 element.
588 // The latter is necessary to properly display the inactive cursor.
589 var console = this.console[this.currentScreen];
590 var height = (this.isEmbedded ? this.container.clientHeight
591 : (window.innerHeight ||
592 document.documentElement.clientHeight ||
593 document.body.clientHeight))-1;
594 var partial = height % this.cursorHeight;
595 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
596 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
597 var oldTerminalHeight = this.terminalHeight;
598 this.updateWidth();
599 this.updateHeight();
600
601 // Clip the cursor to the visible screen.
602 var cx = this.cursorX;
603 var cy = this.cursorY + this.numScrollbackLines;
604
605 // The alternate screen never keeps a scroll back buffer.
606 this.updateNumScrollbackLines();
607 while (this.currentScreen && this.numScrollbackLines > 0) {
608 console.removeChild(console.firstChild);
609 this.numScrollbackLines--;
610 }
611 cy -= this.numScrollbackLines;
612 if (cx < 0) {
613 cx = 0;
614 } else if (cx > this.terminalWidth) {
615 cx = this.terminalWidth - 1;
616 if (cx < 0) {
617 cx = 0;
618 }
619 }
620 if (cy < 0) {
621 cy = 0;
622 } else if (cy > this.terminalHeight) {
623 cy = this.terminalHeight - 1;
624 if (cy < 0) {
625 cy = 0;
626 }
627 }
a7164199 628
ce845548
MG
629 // Clip the scroll region to the visible screen.
630 if (this.bottom > this.terminalHeight ||
631 this.bottom == oldTerminalHeight) {
632 this.bottom = this.terminalHeight;
633 }
634 if (this.top >= this.bottom) {
635 this.top = this.bottom-1;
636 if (this.top < 0) {
637 this.top = 0;
638 }
639 }
a7164199 640
ce845548
MG
641 // Truncate lines, if necessary. Explicitly reposition cursor (this is
642 // particularly important after changing the screen number), and reset
643 // the scroll region to the default.
644 this.truncateLines(this.terminalWidth);
645 this.putString(cx, cy, '', undefined);
646 this.scrollable.scrollTop = this.numScrollbackLines *
647 this.cursorHeight + 1;
648
649 // Update classNames for lines in the scrollback buffer
650 var line = console.firstChild;
651 for (var i = 0; i < this.numScrollbackLines; i++) {
652 line.className = 'scrollback';
653 line = line.nextSibling;
654 }
655 while (line) {
656 line.className = '';
657 line = line.nextSibling;
658 }
659
660 // Reposition the reconnect button
661 this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth -
662 this.reconnectBtn.clientWidth)/2 + 'px';
663 this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
664 this.reconnectBtn.clientHeight)/2 + 'px';
665
666 // Send notification that the window size has been changed
667 this.resized(this.terminalWidth, this.terminalHeight);
668};
669
a7164199
MG
670VT100.prototype.showCurrentSize = function() {
671 if (!this.indicateSize) {
672 return;
673 }
674 this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
675 this.terminalHeight;
676 this.curSizeBox.style.left =
677 (this.terminalWidth*this.cursorWidth -
678 this.curSizeBox.clientWidth)/2 + 'px';
679 this.curSizeBox.style.top =
680 (this.terminalHeight*this.cursorHeight -
681 this.curSizeBox.clientHeight)/2 + 'px';
682 this.curSizeBox.style.visibility = '';
683 if (this.curSizeTimeout) {
684 clearTimeout(this.curSizeTimeout);
685 }
686
687 // Only show the terminal size for a short amount of time after resizing.
688 // Then hide this information, again. Some browsers generate resize events
689 // throughout the entire resize operation. This is nice, and we will show
690 // the terminal size while the user is dragging the window borders.
691 // Other browsers only generate a single event when the user releases the
692 // mouse. In those cases, we can only show the terminal size once at the
693 // end of the resize operation.
694 this.curSizeTimeout = setTimeout(function(vt100) {
695 return function() {
696 vt100.curSizeTimeout = null;
697 vt100.curSizeBox.style.visibility = 'hidden';
698 };
699 }(this), 1000);
700};
701
ce845548
MG
702VT100.prototype.selection = function() {
703 try {
704 return '' + (window.getSelection && window.getSelection() ||
705 document.selection && document.selection.type == 'Text' &&
706 document.selection.createRange().text || '');
707 } catch (e) {
708 }
709 return '';
710};
711
712VT100.prototype.cancelEvent = function(event) {
713 try {
714 // For non-IE browsers
715 event.stopPropagation();
716 event.preventDefault();
717 } catch (e) {
718 }
719 try {
720 // For IE
721 event.cancelBubble = true;
722 event.returnValue = false;
723 event.button = 0;
724 event.keyCode = 0;
725 } catch (e) {
726 }
727 return false;
728};
729
730VT100.prototype.mouseEvent = function(event, type) {
731 // If any text is currently selected, do not move the focus as that would
732 // invalidate the selection.
733 var selection = this.selection();
734 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
735 this.input.focus();
736 }
737
738 // Compute mouse position in characters.
739 var offsetX = this.container.offsetLeft;
740 var offsetY = this.container.offsetTop;
741 for (var e = this.container; e = e.offsetParent; ) {
742 offsetX += e.offsetLeft;
743 offsetY += e.offsetTop;
744 }
745 var x = (event.clientX - offsetX) / this.cursorWidth;
746 var y = ((event.clientY - offsetY) + this.scrollable.offsetTop) /
747 this.cursorHeight - this.numScrollbackLines;
748 var inside = true;
749 if (x >= this.terminalWidth) {
750 x = this.terminalWidth - 1;
751 inside = false;
752 }
753 if (x < 0) {
754 x = 0;
755 inside = false;
756 }
757 if (y >= this.terminalHeight) {
758 y = this.terminalHeight - 1;
759 inside = false;
760 }
761 if (y < 0) {
762 y = 0;
763 inside = false;
764 }
765
766 // Compute button number and modifier keys.
767 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
768 typeof event.pageX != 'undefined' ? event.button :
769 [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
770 if (button != undefined) {
771 if (event.shiftKey) {
772 button |= 0x04;
773 }
774 if (event.altKey || event.metaKey) {
775 button |= 0x08;
776 }
777 if (event.ctrlKey) {
778 button |= 0x10;
779 }
780 }
781
782 // Report mouse events if they happen inside of the current screen and
783 // with the SHIFT key unpressed. Both of these restrictions do not apply
784 // for button releases, as we always want to report those.
785 if (this.mouseReporting && !selection.length &&
786 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
787 if (inside || type != 0 /* MOUSE_DOWN */) {
788 if (button != undefined) {
789 var report = '\u001B[M' + String.fromCharCode(button + 32) +
790 String.fromCharCode(x + 33) +
791 String.fromCharCode(y + 33);
792 if (type != 2 /* MOUSE_CLICK */) {
793 this.keysPressed(report);
794 }
795
796 // If we reported the event, stop propagating it (not sure, if this
797 // actually works on most browsers; blocking the global "oncontextmenu"
798 // even is still necessary).
799 return this.cancelEvent(event);
800 }
801 }
802 }
803
804 // Bring up context menu.
805 if (button == 2 && !event.shiftKey) {
806 if (type == 0 /* MOUSE_DOWN */) {
807 this.showContextMenu(event.clientX - offsetX, event.clientY - offsetY);
808 }
809 return this.cancelEvent(event);
810 }
811
812 if (this.mouseReporting) {
813 try {
814 event.shiftKey = false;
815 } catch (e) {
816 }
817 }
818
819 return true;
820};
821
f0c6fd39
MG
822VT100.prototype.replaceChar = function(s, ch, repl) {
823 for (var i = -1;;) {
824 i = s.indexOf(ch, i + 1);
825 if (i < 0) {
826 break;
827 }
828 s = s.substr(0, i) + repl + s.substr(i + 1);
829 }
830 return s;
831};
832
833VT100.prototype.htmlEscape = function(s) {
a7164199
MG
834 return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
835 s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;'), ' ', '\u00A0');
f0c6fd39
MG
836};
837
ce845548
MG
838VT100.prototype.getTextContent = function(elem) {
839 return elem.textContent ||
840 (typeof elem.textContent == 'undefined' ? elem.innerText : '');
841};
842
843VT100.prototype.setTextContent = function(elem, s) {
f0c6fd39
MG
844 // Check if we find any URLs in the text. If so, automatically convert them
845 // to links.
846 if (this.urlRE && this.urlRE.test(s)) {
847 var inner = '';
848 for (;;) {
849 var consumed = 0;
850 if (RegExp.leftContext != null) {
851 inner += this.htmlEscape(RegExp.leftContext);
852 consumed += RegExp.leftContext.length;
853 }
854 var url = this.htmlEscape(RegExp.lastMatch);
855 var fullUrl = url;
856
857 // If no protocol was specified, try to guess a reasonable one.
858 if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
859 url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
860 var slash = url.indexOf('/');
861 var at = url.indexOf('@');
862 var question = url.indexOf('?');
863 if (at > 0 &&
864 (at < question || question < 0) &&
865 (slash < 0 || (question > 0 && slash > question))) {
866 fullUrl = 'mailto:' + url;
867 } else {
868 fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
869 url;
870 }
871 }
872
873 inner += '<a target="vt100Link" href="' + fullUrl +
874 '">' + url + '</a>';
875 consumed += RegExp.lastMatch.length;
876 s = s.substr(consumed);
877 if (!this.urlRE.test(s)) {
878 if (RegExp.rightContext != null) {
879 inner += this.htmlEscape(RegExp.rightContext);
880 }
881 break;
882 }
883 }
884 elem.innerHTML = inner;
885 return;
886 }
887
ce845548
MG
888 // Updating the content of an element is an expensive operation. It actually
889 // pays off to first check whether the element is still unchanged.
890 if (typeof elem.textContent == 'undefined') {
891 if (elem.innerText != s) {
a7164199
MG
892 try {
893 elem.innerText = s;
894 } catch (e) {
895 // Very old versions of IE do not allow setting innerText. Instead,
896 // remove all children, by setting innerHTML and then set the text
897 // using DOM methods.
898 elem.innerHTML = '';
899 elem.appendChild(document.createTextNode(
900 this.replaceChar(s, ' ', '\u00A0')));
901 }
ce845548
MG
902 }
903 } else {
904 if (elem.textContent != s) {
905 elem.textContent = s;
906 }
907 }
908};
909
910VT100.prototype.insertBlankLine = function(y, style) {
911 // Insert a blank line a position y. This method ignores the scrollback
912 // buffer. The caller has to add the length of the scrollback buffer to
913 // the position, if necessary.
914 // If the position is larger than the number of current lines, this
915 // method just adds a new line right after the last existing one. It does
916 // not add any missing lines in between. It is the caller's responsibility
917 // to do so.
918 if (style == undefined) {
919 style = '';
920 }
921 var line;
922 if (!style) {
923 line = document.createElement('pre');
924 this.setTextContent(line, '\n');
925 } else {
926 line = document.createElement('div');
927 var span = document.createElement('span');
928 span.style.cssText = style;
929 this.setTextContent(span, this.spaces(this.terminalWidth));
930 line.appendChild(span);
931 }
932 line.style.height = this.cursorHeight + 'px';
933 var console = this.console[this.currentScreen];
934 if (console.childNodes.length > y) {
935 console.insertBefore(line, console.childNodes[y]);
936 } else {
937 console.appendChild(line);
938 }
939};
940
941VT100.prototype.updateWidth = function() {
942 this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
943 this.cursorWidth);
944 return this.terminalWidth;
945};
946
947VT100.prototype.updateHeight = function() {
948 // We want to be able to display either a terminal window that fills the
949 // entire browser window, or a terminal window that is contained in a
950 // <div> which is embededded somewhere in the web page.
951 if (this.isEmbedded) {
952 // Embedded terminal. Use size of the containing <div> (id="vt100").
953 this.terminalHeight = Math.floor((this.container.clientHeight-1) /
954 this.cursorHeight);
955 } else {
956 // Use the full browser window.
957 this.terminalHeight = Math.floor(((window.innerHeight ||
958 document.documentElement.clientHeight ||
959 document.body.clientHeight)-1)/
960 this.cursorHeight);
961 }
962 return this.terminalHeight;
963};
964
965VT100.prototype.updateNumScrollbackLines = function() {
966 var scrollback = Math.floor(
967 this.console[this.currentScreen].offsetHeight /
968 this.cursorHeight) -
969 this.terminalHeight;
970 this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
971 return this.numScrollbackLines;
972};
973
974VT100.prototype.truncateLines = function(width) {
975 if (width < 0) {
976 width = 0;
977 }
978 for (var line = this.console[this.currentScreen].firstChild; line;
979 line = line.nextSibling) {
980 if (line.tagName == 'DIV') {
981 var x = 0;
982
ce0cf224 983 // Traverse current line and truncate it once we saw "width" characters
ce845548
MG
984 for (var span = line.firstChild; span;
985 span = span.nextSibling) {
986 var s = this.getTextContent(span);
987 var l = s.length;
988 if (x + l > width) {
989 this.setTextContent(span, s.substr(0, width - x));
990 while (span.nextSibling) {
991 line.removeChild(line.lastChild);
992 }
993 break;
994 }
995 x += l;
996 }
997 // Prune white space from the end of the current line
998 var span = line.lastChild;
999 while (span && !span.style.cssText.length) {
1000 // Scan backwards looking for first non-space character
1001 var s = this.getTextContent(span);
1002 for (var i = s.length; i--; ) {
ce0cf224 1003 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
ce845548
MG
1004 if (i+1 != s.length) {
1005 this.setTextContent(s.substr(0, i+1));
1006 }
1007 span = null;
1008 break;
1009 }
1010 }
1011 if (span) {
1012 var sibling = span;
1013 span = span.previousSibling;
1014 if (span) {
1015 // Remove blank <span>'s from end of line
1016 line.removeChild(sibling);
1017 } else {
1018 // Remove entire line (i.e. <div>), if empty
1019 var blank = document.createElement('pre');
1020 blank.style.height = this.cursorHeight + 'px';
1021 this.setTextContent(blank, '\n');
1022 line.parentNode.replaceChild(blank, line);
1023 }
1024 }
1025 }
1026 }
1027 }
1028};
1029
1030VT100.prototype.putString = function(x, y, text, style) {
1031 if (!style) {
1032 style = '';
1033 }
1034 var yIdx = y + this.numScrollbackLines;
1035 var line;
1036 var sibling;
1037 var s;
1038 var span;
1039 var xPos = 0;
1040 var console = this.console[this.currentScreen];
1041 if (!text.length && (yIdx >= console.childNodes.length ||
1042 console.childNodes[yIdx].tagName != 'DIV')) {
1043 // Positioning cursor to a blank location
1044 span = null;
1045 } else {
1046 // Create missing blank lines at end of page
1047 while (console.childNodes.length <= yIdx) {
1048 // In order to simplify lookups, we want to make sure that each line
1049 // is represented by exactly one element (and possibly a whole bunch of
1050 // children).
1051 // For non-blank lines, we can create a <div> containing one or more
1052 // <span>s. For blank lines, this fails as browsers tend to optimize them
1053 // away. But fortunately, a <pre> tag containing a newline character
1054 // appears to work for all browsers (a &nbsp; would also work, but then
1055 // copying from the browser window would insert superfluous spaces into
1056 // the clipboard).
1057 this.insertBlankLine(yIdx);
1058 }
1059 line = console.childNodes[yIdx];
1060
1061 // If necessary, promote blank '\n' line to a <div> tag
1062 if (line.tagName != 'DIV') {
1063 var div = document.createElement('div');
1064 div.style.height = this.cursorHeight + 'px';
1065 div.innerHTML = '<span></span>';
1066 console.replaceChild(div, line);
1067 line = div;
1068 }
1069
1070 // Scan through list of <span>'s until we find the one where our text
1071 // starts
1072 span = line.firstChild;
1073 var len;
1074 while (span.nextSibling && xPos < x) {
1075 len = this.getTextContent(span).length;
1076 if (xPos + len > x) {
1077 break;
1078 }
1079 xPos += len;
1080 span = span.nextSibling;
1081 }
1082
1083 if (text.length) {
1084 // If current <span> is not long enough, pad with spaces or add new
1085 // span
1086 s = this.getTextContent(span);
1087 var oldStyle = span.style.cssText;
1088 if (xPos + s.length < x) {
1089 if (oldStyle != '') {
1090 span = document.createElement('span');
1091 line.appendChild(span);
1092 span.style.cssText = '';
1093 oldStyle = '';
1094 xPos += s.length;
1095 s = '';
1096 }
1097 do {
1098 s += ' ';
1099 } while (xPos + s.length < x);
1100 }
1101
1102 // If styles do not match, create a new <span>
1103 var del = text.length - s.length + x - xPos;
1104 if (oldStyle != style && (oldStyle || style)) {
1105 if (xPos == x) {
1106 // Replacing text at beginning of existing <span>
1107 if (text.length >= s.length) {
1108 // New text is equal or longer than existing text
1109 s = text;
1110 } else {
1111 // Insert new <span> before the current one, then remove leading
1112 // part of existing <span>, adjust style of new <span>, and finally
1113 // set its contents
1114 sibling = document.createElement('span');
1115 line.insertBefore(sibling, span);
1116 this.setTextContent(span, s.substr(text.length));
1117 span = sibling;
1118 s = text;
1119 }
1120 } else {
1121 // Replacing text some way into the existing <span>
1122 var remainder = s.substr(x + text.length - xPos);
1123 this.setTextContent(span, s.substr(0, x - xPos));
1124 xPos = x;
1125 sibling = document.createElement('span');
1126 if (span.nextSibling) {
1127 line.insertBefore(sibling, span.nextSibling);
1128 span = sibling;
1129 if (remainder.length) {
1130 sibling = document.createElement('span');
1131 sibling.style.cssText = oldStyle;
1132 this.setTextContent(sibling, remainder);
1133 line.insertBefore(sibling, span.nextSibling);
1134 }
1135 } else {
1136 line.appendChild(sibling);
1137 span = sibling;
1138 if (remainder.length) {
1139 sibling = document.createElement('span');
1140 sibling.style.cssText = oldStyle;
1141 this.setTextContent(sibling, remainder);
1142 line.appendChild(sibling);
1143 }
1144 }
1145 s = text;
1146 }
1147 span.style.cssText = style;
1148 } else {
1149 // Overwrite (partial) <span> with new text
1150 s = s.substr(0, x - xPos) +
1151 text +
1152 s.substr(x + text.length - xPos);
1153 }
1154 this.setTextContent(span, s);
1155
1156
1157 // Delete all subsequent <span>'s that have just been overwritten
1158 sibling = span.nextSibling;
1159 while (del > 0 && sibling) {
1160 s = this.getTextContent(sibling);
1161 len = s.length;
1162 if (len <= del) {
1163 line.removeChild(sibling);
1164 del -= len;
1165 sibling = span.nextSibling;
1166 } else {
1167 this.setTextContent(sibling, s.substr(del));
1168 break;
1169 }
1170 }
1171
1172 // Merge <span> with next sibling, if styles are identical
1173 if (sibling && span.style.cssText == sibling.style.cssText) {
1174 this.setTextContent(span,
1175 this.getTextContent(span) +
1176 this.getTextContent(sibling));
1177 line.removeChild(sibling);
1178 }
1179 }
1180 }
1181
1182 // Position cursor
1183 this.cursorX = x + text.length;
1184 if (this.cursorX >= this.terminalWidth) {
1185 this.cursorX = this.terminalWidth - 1;
1186 if (this.cursorX < 0) {
1187 this.cursorX = 0;
1188 }
1189 }
1190 var pixelX = -1;
1191 var pixelY = -1;
1192 if (!this.cursor.style.visibility) {
1193 var idx = this.cursorX - xPos;
1194 if (span) {
1195 // If we are in a non-empty line, take the cursor Y position from the
1196 // other elements in this line. If dealing with broken, non-proportional
1197 // fonts, this is likely to yield better results.
1198 pixelY = span.offsetTop +
1199 span.offsetParent.offsetTop;
1200 s = this.getTextContent(span);
1201 var nxtIdx = idx - s.length;
1202 if (nxtIdx < 0) {
1203 this.setTextContent(this.cursor, s.charAt(idx));
1204 pixelX = span.offsetLeft +
1205 idx*span.offsetWidth / s.length;
1206 } else {
1207 if (nxtIdx == 0) {
1208 pixelX = span.offsetLeft + span.offsetWidth;
1209 }
1210 if (span.nextSibling) {
1211 s = this.getTextContent(span.nextSibling);
1212 this.setTextContent(this.cursor, s.charAt(nxtIdx));
1213 if (pixelX < 0) {
1214 pixelX = span.nextSibling.offsetLeft +
1215 nxtIdx*span.offsetWidth / s.length;
1216 }
1217 } else {
1218 this.setTextContent(this.cursor, ' ');
1219 }
1220 }
1221 } else {
1222 this.setTextContent(this.cursor, ' ');
1223 }
1224 }
1225 if (pixelX >= 0) {
1226 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
1227 } else {
1228 this.setTextContent(this.space, this.spaces(this.cursorX));
1229 this.cursor.style.left = this.space.offsetWidth +
1230 console.offsetLeft + 'px';
1231 }
1232 this.cursorY = yIdx - this.numScrollbackLines;
1233 if (pixelY >= 0) {
1234 this.cursor.style.top = pixelY + 'px';
1235 } else {
1236 this.cursor.style.top = yIdx*this.cursorHeight +
1237 console.offsetTop + 'px';
1238 }
1239
1240 if (text.length) {
1241 // Merge <span> with previous sibling, if styles are identical
1242 if ((sibling = span.previousSibling) &&
1243 span.style.cssText == sibling.style.cssText) {
1244 this.setTextContent(span,
1245 this.getTextContent(sibling) +
1246 this.getTextContent(span));
1247 line.removeChild(sibling);
1248 }
1249
1250 // Prune white space from the end of the current line
1251 span = line.lastChild;
1252 while (span && !span.style.cssText.length) {
1253 // Scan backwards looking for first non-space character
1254 s = this.getTextContent(span);
1255 for (var i = s.length; i--; ) {
ce0cf224 1256 if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
ce845548
MG
1257 if (i+1 != s.length) {
1258 this.setTextContent(s.substr(0, i+1));
1259 }
1260 span = null;
1261 break;
1262 }
1263 }
1264 if (span) {
1265 sibling = span;
1266 span = span.previousSibling;
1267 if (span) {
1268 // Remove blank <span>'s from end of line
1269 line.removeChild(sibling);
1270 } else {
1271 // Remove entire line (i.e. <div>), if empty
1272 var blank = document.createElement('pre');
1273 blank.style.height = this.cursorHeight + 'px';
1274 this.setTextContent(blank, '\n');
1275 line.parentNode.replaceChild(blank, line);
1276 }
1277 }
1278 }
1279 }
1280};
1281
1282VT100.prototype.gotoXY = function(x, y) {
1283 if (x >= this.terminalWidth) {
1284 x = this.terminalWidth - 1;
1285 }
1286 if (x < 0) {
1287 x = 0;
1288 }
1289 var minY, maxY;
1290 if (this.offsetMode) {
1291 minY = this.top;
1292 maxY = this.bottom;
1293 } else {
1294 minY = 0;
1295 maxY = this.terminalHeight;
1296 }
1297 if (y >= maxY) {
1298 y = maxY - 1;
1299 }
1300 if (y < minY) {
1301 y = minY;
1302 }
1303 this.putString(x, y, '', undefined);
1304 this.needWrap = false;
1305};
1306
1307VT100.prototype.gotoXaY = function(x, y) {
1308 this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1309};
1310
1311VT100.prototype.refreshInvertedState = function() {
1312 if (this.isInverted) {
1313 this.scrollable.style.color = this.ansi[15];
1314 this.scrollable.style.backgroundColor = this.ansi[0];
1315 } else {
1316 this.scrollable.style.color = '';
1317 this.scrollable.style.backgroundColor = '';
1318 }
1319};
1320
1321VT100.prototype.enableAlternateScreen = function(state) {
1322 // Don't do anything, if we are already on the desired screen
1323 if ((state ? 1 : 0) == this.currentScreen) {
1324 // Calling the resizer is not actually necessary. But it is a good way
1325 // of resetting state that might have gotten corrupted.
1326 this.resizer();
1327 return;
1328 }
1329
1330 // We save the full state of the normal screen, when we switch away from it.
1331 // But for the alternate screen, no saving is necessary. We always reset
1332 // it when we switch to it.
1333 if (state) {
1334 this.saveCursor();
1335 }
1336
1337 // Display new screen, and initialize state (the resizer does that for us).
1338 this.currentScreen = state ? 1 : 0;
1339 this.console[1-this.currentScreen].style.display = 'none';
1340 this.console[this.currentScreen].style.display = '';
1341 this.resizer();
1342
1343 // If we switched to the alternate screen, reset it completely. Otherwise,
1344 // restore the saved state.
1345 if (state) {
1346 this.gotoXY(0, 0);
1347 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1348 } else {
1349 this.restoreCursor();
1350 }
1351};
1352
1353VT100.prototype.hideCursor = function() {
1354 var hidden = this.cursor.style.visibility == 'hidden';
1355 if (!hidden) {
1356 this.cursor.style.visibility = 'hidden';
1357 return true;
1358 }
1359 return false;
1360};
1361
1362VT100.prototype.showCursor = function(x, y) {
1363 if (this.cursor.style.visibility) {
1364 this.cursor.style.visibility = '';
1365 this.putString(x == undefined ? this.cursorX : x,
1366 y == undefined ? this.cursorY : y,
1367 '', undefined);
1368 return true;
1369 }
1370 return false;
1371};
1372
1373VT100.prototype.scrollBack = function() {
1374 var i = this.scrollable.scrollTop -
1375 this.scrollable.clientHeight;
1376 this.scrollable.scrollTop = i < 0 ? 0 : i;
1377};
1378
1379VT100.prototype.scrollFore = function() {
1380 var i = this.scrollable.scrollTop +
1381 this.scrollable.clientHeight;
1382 this.scrollable.scrollTop = i > this.numScrollbackLines *
1383 this.cursorHeight + 1
1384 ? this.numScrollbackLines *
1385 this.cursorHeight + 1
1386 : i;
1387};
1388
1389VT100.prototype.spaces = function(i) {
1390 var s = '';
1391 while (i-- > 0) {
1392 s += ' ';
1393 }
1394 return s;
1395};
1396
1397VT100.prototype.clearRegion = function(x, y, w, h, style) {
1398 w += x;
1399 if (x < 0) {
1400 x = 0;
1401 }
1402 if (w > this.terminalWidth) {
1403 w = this.terminalWidth;
1404 }
1405 if ((w -= x) <= 0) {
1406 return;
1407 }
1408 h += y;
1409 if (y < 0) {
1410 y = 0;
1411 }
1412 if (h > this.terminalHeight) {
1413 h = this.terminalHeight;
1414 }
1415 if ((h -= y) <= 0) {
1416 return;
1417 }
1418
1419 // Special case the situation where we clear the entire screen, and we do
1420 // not have a scrollback buffer. In that case, we should just remove all
1421 // child nodes.
1422 if (!this.numScrollbackLines &&
1423 w == this.terminalWidth && h == this.terminalHeight &&
1424 !style) {
1425 var console = this.console[this.currentScreen];
1426 while (console.lastChild) {
1427 console.removeChild(console.lastChild);
1428 }
1429 this.putString(this.cursorX, this.cursorY, '', undefined);
1430 } else {
1431 var hidden = this.hideCursor();
1432 var cx = this.cursorX;
1433 var cy = this.cursorY;
1434 var s = this.spaces(w);
1435 for (var i = y+h; i-- > y; ) {
1436 this.putString(x, i, s, style);
1437 }
1438 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1439 }
1440};
1441
1442VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
1443 var text = [ ];
1444 var style = [ ];
1445 var console = this.console[this.currentScreen];
1446 if (sY >= console.childNodes.length) {
1447 text[0] = this.spaces(w);
1448 style[0] = null;
1449 } else {
1450 var line = console.childNodes[sY];
1451 if (line.tagName != 'DIV' || !line.childNodes.length) {
1452 text[0] = this.spaces(w);
1453 style[0] = null;
1454 } else {
1455 var x = 0;
1456 for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
1457 var s = this.getTextContent(span);
1458 var len = s.length;
1459 if (x + len > sX) {
1460 var o = sX > x ? sX - x : 0;
1461 text[text.length] = s.substr(o, w);
1462 style[style.length] = span.style.cssText;
1463 w -= len - o;
1464 }
1465 x += len;
1466 }
1467 if (w > 0) {
1468 text[text.length] = this.spaces(w);
1469 style[style.length] = null;
1470 }
1471 }
1472 }
1473 var hidden = this.hideCursor();
1474 var cx = this.cursorX;
1475 var cy = this.cursorY;
1476 for (var i = 0; i < text.length; i++) {
1477 this.putString(dX, dY - this.numScrollbackLines, text[i], style[i]);
1478 dX += text[i].length;
1479 }
1480 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1481};
1482
1483VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY, style) {
1484 var left = incX < 0 ? -incX : 0;
1485 var right = incX > 0 ? incX : 0;
1486 var up = incY < 0 ? -incY : 0;
1487 var down = incY > 0 ? incY : 0;
1488
1489 // Clip region against terminal size
1490 var dontScroll = null;
1491 w += x;
1492 if (x < left) {
1493 x = left;
1494 }
1495 if (w > this.terminalWidth - right) {
1496 w = this.terminalWidth - right;
1497 }
1498 if ((w -= x) <= 0) {
1499 dontScroll = 1;
1500 }
1501 h += y;
1502 if (y < up) {
1503 y = up;
1504 }
1505 if (h > this.terminalHeight - down) {
1506 h = this.terminalHeight - down;
1507 }
1508 if ((h -= y) < 0) {
1509 dontScroll = 1;
1510 }
1511 if (!dontScroll) {
1512 if (style && style.indexOf('underline')) {
1513 // Different terminal emulators disagree on the attributes that
1514 // are used for scrolling. The consensus seems to be, never to
1515 // fill with underlined spaces. N.B. this is different from the
1516 // cases when the user blanks a region. User-initiated blanking
1517 // always fills with all of the current attributes.
1518 this.attributeHelper.cssText
1519 = style.replace(/text-decoration:underline;/, "");
1520 style = this.attributeHelper.cssText;
1521 }
1522
1523 // Compute current scroll position
1524 var scrollPos = this.numScrollbackLines -
1525 (this.scrollable.scrollTop-1) / this.cursorHeight;
1526
1527 // Determine original cursor position. Hide cursor temporarily to avoid
1528 // visual artifacts.
1529 var hidden = this.hideCursor();
1530 var cx = this.cursorX;
1531 var cy = this.cursorY;
1532 var console = this.console[this.currentScreen];
1533
1534 if (!incX && !x && w == this.terminalWidth) {
1535 // Scrolling entire lines
1536 if (incY < 0) {
1537 // Scrolling up
1538 if (!this.currentScreen && y == -incY &&
1539 h == this.terminalHeight + incY) {
1540 // Scrolling up with adding to the scrollback buffer. This is only
1541 // possible if there are at least as many lines in the console,
1542 // as the terminal is high
1543 while (console.childNodes.length < this.terminalHeight) {
1544 this.insertBlankLine(this.terminalHeight);
1545 }
1546
1547 // Add new lines at bottom in order to force scrolling
1548 for (var i = 0; i < y; i++) {
1549 this.insertBlankLine(console.childNodes.length, style);
1550 }
1551
1552 // Adjust the number of lines in the scrollback buffer by
1553 // removing excess entries.
1554 this.updateNumScrollbackLines();
1555 while (this.numScrollbackLines >
1556 (this.currentScreen ? 0 : this.maxScrollbackLines)) {
1557 console.removeChild(console.firstChild);
1558 this.numScrollbackLines--;
1559 }
1560
1561 // Mark lines in the scrollback buffer, so that they do not get
1562 // printed.
1563 for (var i = this.numScrollbackLines, j = -incY;
1564 i-- > 0 && j-- > 0; ) {
1565 console.childNodes[i].className = 'scrollback';
1566 }
1567 } else {
1568 // Scrolling up without adding to the scrollback buffer.
1569 for (var i = -incY;
1570 i-- > 0 &&
1571 console.childNodes.length >
1572 this.numScrollbackLines + y + incY; ) {
1573 console.removeChild(console.childNodes[
1574 this.numScrollbackLines + y + incY]);
1575 }
1576
1577 // If we used to have a scrollback buffer, then we must make sure
1578 // that we add back blank lines at the bottom of the terminal.
1579 // Similarly, if we are scrolling in the middle of the screen,
1580 // we must add blank lines to ensure that the bottom of the screen
1581 // does not move up.
1582 if (this.numScrollbackLines > 0 ||
1583 console.childNodes.length > this.numScrollbackLines+y+h+incY) {
1584 for (var i = -incY; i-- > 0; ) {
1585 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1586 style);
1587 }
1588 }
1589 }
1590 } else {
1591 // Scrolling down
1592 for (var i = incY;
1593 i-- > 0 &&
1594 console.childNodes.length > this.numScrollbackLines + y + h; ) {
1595 console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
1596 }
1597 for (var i = incY; i--; ) {
1598 this.insertBlankLine(this.numScrollbackLines + y, style);
1599 }
1600 }
1601 } else {
1602 // Scrolling partial lines
1603 if (incY <= 0) {
1604 // Scrolling up or horizontally within a line
1605 for (var i = y + this.numScrollbackLines;
1606 i < y + this.numScrollbackLines + h;
1607 i++) {
1608 this.copyLineSegment(x + incX, i + incY, x, i, w);
1609 }
1610 } else {
1611 // Scrolling down
1612 for (var i = y + this.numScrollbackLines + h;
1613 i-- > y + this.numScrollbackLines; ) {
1614 this.copyLineSegment(x + incX, i + incY, x, i, w);
1615 }
1616 }
1617
1618 // Clear blank regions
1619 if (incX > 0) {
1620 this.clearRegion(x, y, incX, h, style);
1621 } else if (incX < 0) {
1622 this.clearRegion(x + w + incX, y, -incX, h, style);
1623 }
1624 if (incY > 0) {
1625 this.clearRegion(x, y, w, incY, style);
1626 } else if (incY < 0) {
1627 this.clearRegion(x, y + h + incY, w, -incY, style);
1628 }
1629 }
1630
1631 // Reset scroll position
1632 this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
1633 this.cursorHeight + 1;
1634
1635 // Move cursor back to its original position
1636 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
1637 }
1638};
1639
1640VT100.prototype.copy = function(selection) {
1641 if (selection == undefined) {
1642 selection = this.selection();
1643 }
1644 this.internalClipboard = undefined;
1645 if (selection.length) {
1646 try {
1647 // IE
1648 this.cliphelper.value = selection;
1649 this.cliphelper.select();
1650 this.cliphelper.createTextRange().execCommand('copy');
1651 } catch (e) {
1652 this.internalClipboard = selection;
1653 }
1654 this.cliphelper.value = '';
1655 }
1656};
1657
1658VT100.prototype.copyLast = function() {
1659 // Opening the context menu can remove the selection. We try to prevent this
1660 // from happening, but that is not possible for all browsers. So, instead,
1661 // we compute the selection before showing the menu.
1662 this.copy(this.lastSelection);
1663};
1664
1665VT100.prototype.pasteFnc = function() {
1666 var clipboard = undefined;
1667 if (this.internalClipboard != undefined) {
1668 clipboard = this.internalClipboard;
1669 } else {
1670 try {
1671 this.cliphelper.value = '';
1672 this.cliphelper.createTextRange().execCommand('paste');
1673 clipboard = this.cliphelper.value;
1674 } catch (e) {
1675 }
1676 }
1677 this.cliphelper.value = '';
1678 if (clipboard && this.menu.style.visibility == 'hidden') {
1679 return function() {
1680 this.keysPressed('' + clipboard);
1681 };
1682 } else {
1683 return undefined;
1684 }
1685};
1686
1687VT100.prototype.toggleUTF = function() {
1688 this.utfEnabled = !this.utfEnabled;
1689};
1690
1691VT100.prototype.toggleBell = function() {
1692 this.visualBell = !this.visualBell;
1693};
1694
1695VT100.prototype.about = function() {
8ac38fe6 1696 alert("VT100 Terminal Emulator " + "2.9 (revision 164)" +
ce845548
MG
1697 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1698 "For more information check http://shellinabox.com");
1699};
1700
1701VT100.prototype.hideContextMenu = function() {
1702 this.menu.style.visibility = 'hidden';
1703 this.menu.style.top = '-100px';
1704 this.menu.style.left = '-100px';
1705 this.menu.style.width = '0px';
1706 this.menu.style.height = '0px';
1707};
1708
1709VT100.prototype.extendContextMenu = function(entries, actions) {
1710};
1711
1712VT100.prototype.showContextMenu = function(x, y) {
1713 this.menu.innerHTML =
1714 '<table class="popup" ' +
1715 'cellpadding="0" cellspacing="0">' +
1716 '<tr><td>' +
1717 '<ul id="menuentries">' +
1718 '<li id="beginclipboard">Copy</li>' +
1719 '<li id="endclipboard">Paste</li>' +
1720 '<hr />' +
1721 '<li id="reset">Reset</li>' +
1722 '<hr />' +
1723 '<li id="beginconfig">' +
1724 (this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' +
1725 '<li id="endconfig">' +
1726 (this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+
1727 '<hr />' +
1728 '<li id="about">About...</li>' +
1729 '</ul>' +
1730 '</td></tr>' +
1731 '</table>';
1732
1733 var popup = this.menu.firstChild;
1734 var menuentries = this.getChildById(popup, 'menuentries');
1735
1736 // Determine menu entries that should be disabled
1737 this.lastSelection = this.selection();
1738 if (!this.lastSelection.length) {
1739 menuentries.firstChild.className
1740 = 'disabled';
1741 }
1742 var p = this.pasteFnc();
1743 if (!p) {
1744 menuentries.childNodes[1].className
1745 = 'disabled';
1746 }
1747 var actions = [ this.copyLast, p, this.reset,
1748 this.toggleUTF, this.toggleBell,
1749 this.about ];
1750
1751 // Allow subclasses to dynamically add entries to the context menu
1752 this.extendContextMenu(menuentries, actions);
1753
1754 // Hook up event listeners
1755 for (var node = menuentries.firstChild, i = 0; node;
1756 node = node.nextSibling) {
1757 if (node.tagName == 'LI') {
1758 if (node.className != 'disabled') {
1759 this.addListener(node, 'mouseover',
1760 function(vt100, node) {
1761 return function() {
1762 node.className = 'hover';
1763 }
1764 }(this, node));
1765 this.addListener(node, 'mouseout',
1766 function(vt100, node) {
1767 return function() {
1768 node.className = '';
1769 }
1770 }(this, node));
1771 this.addListener(node, 'mousedown',
1772 function(vt100, action) {
1773 return function(event) {
1774 vt100.hideContextMenu();
1775 action.call(vt100);
1776 return vt100.cancelEvent(event || window.event);
1777 }
1778 }(this, actions[i]));
1779 this.addListener(node, 'mouseup',
1780 function(vt100) {
1781 return function(event) {
1782 return vt100.cancelEvent(event || window.event);
1783 }
1784 }(this));
1785 this.addListener(node, 'mouseclick',
1786 function(vt100) {
1787 return function(event) {
1788 return vt100.cancelEvent(event || window.event);
1789 }
1790 }());
1791 }
1792 i++;
1793 }
1794 }
1795
1796 // Position menu next to the mouse pointer
1797 if (x + popup.clientWidth > this.container.offsetWidth) {
1798 x = this.container.offsetWidth - popup.clientWidth;
1799 }
1800 if (x < 0) {
1801 x = 0;
1802 }
1803 if (y + popup.clientHeight > this.container.offsetHeight) {
1804 y = this.container.offsetHeight-popup.clientHeight;
1805 }
1806 if (y < 0) {
1807 y = 0;
1808 }
1809 popup.style.left = x + 'px';
1810 popup.style.top = y + 'px';
1811
1812 // Block all other interactions with the terminal emulator
1813 this.menu.style.left = '0px';
1814 this.menu.style.top = '0px';
1815 this.menu.style.width = this.container.offsetWidth + 'px';
1816 this.menu.style.height = this.container.offsetHeight + 'px';
1817 this.addListener(this.menu, 'click', function(vt100) {
1818 return function() {
1819 vt100.hideContextMenu();
1820 }
1821 }(this));
1822
1823 // Show the menu
1824 this.menu.style.visibility = '';
1825};
1826
1827VT100.prototype.keysPressed = function(ch) {
1828 for (var i = 0; i < ch.length; i++) {
1829 var c = ch.charCodeAt(i);
1830 this.vt100(c >= 7 && c <= 15 ||
1831 c == 24 || c == 26 || c == 27 || c >= 32
1832 ? String.fromCharCode(c) : '<' + c + '>');
1833 }
1834};
1835
1836VT100.prototype.handleKey = function(event) {
1837 var ch, key;
1838 if (typeof event.charCode != 'undefined') {
1839 // non-IE keypress events have a translated charCode value. Also, our
1840 // fake events generated when receiving keydown events include this data
1841 // on all browsers.
1842 ch = event.charCode;
1843 key = event.keyCode;
1844 } else {
1845 // When sending a keypress event, IE includes the translated character
1846 // code in the keyCode field.
1847 ch = event.keyCode;
1848 key = undefined;
1849 }
1850
1851 // Apply modifier keys (ctrl and shift)
1852 if (ch) {
1853 key = undefined;
1854 if (event.ctrlKey) {
1855 if (ch >= 32 && ch <= 127) {
1856 ch &= 0x1F;
1857 }
1858 } else {
1859 if (event.shiftKey) {
1860 if (ch >= 97 && ch <= 122) {
1861 ch -= 32;
1862 }
1863 } else {
1864 if (ch >= 65 && ch <= 90) {
1865 ch += 32;
1866 }
1867 }
1868 }
1869 } else {
1870 ch = undefined;
1871 }
1872
1873 // By this point, "ch" is either defined and contains the character code, or
1874 // it is undefined and "key" defines the code of a function key
1875 if (ch != undefined) {
1876 ch = String.fromCharCode(ch);
1877 this.scrollable.scrollTop = this.numScrollbackLines *
1878 this.cursorHeight + 1;
1879 } else {
1880 if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
1881 // Many programs have difficulties dealing with parametrized escape
1882 // sequences for function keys. Thus, if ALT is the only modifier
1883 // key, return Emacs-style keycodes for commonly used keys.
1884 switch (key) {
1885 case 33: /* Page Up */ ch = '\u001B<'; break;
1886 case 34: /* Page Down */ ch = '\u001B>'; break;
1887 case 37: /* Left */ ch = '\u001Bb'; break;
1888 case 38: /* Up */ ch = '\u001Bp'; break;
1889 case 39: /* Right */ ch = '\u001Bf'; break;
1890 case 40: /* Down */ ch = '\u001Bn'; break;
1891 case 46: /* Delete */ ch = '\u001Bd'; break;
1892 default: break;
1893 }
1894 } else if (event.shiftKey && !event.ctrlKey &&
1895 !event.altKey && !event.metaKey) {
1896 switch (key) {
1897 case 33: /* Page Up */ this.scrollBack(); return;
1898 case 34: /* Page Down */ this.scrollFore(); return;
1899 default: break;
1900 }
1901 }
1902 if (ch == undefined) {
1903 switch (key) {
1904 case 8: /* Backspace */ ch = '\u007f'; break;
1905 case 9: /* Tab */ ch = '\u0009'; break;
1906 case 10: /* Return */ ch = '\u000A'; break;
1907 case 13: /* Enter */ ch = this.crLfMode ?
1908 '\r\n' : '\r'; break;
1909 case 16: /* Shift */ return;
1910 case 17: /* Ctrl */ return;
1911 case 18: /* Alt */ return;
1912 case 19: /* Break */ return;
1913 case 20: /* Caps Lock */ return;
1914 case 27: /* Escape */ ch = '\u001B'; break;
1915 case 33: /* Page Up */ ch = '\u001B[5~'; break;
1916 case 34: /* Page Down */ ch = '\u001B[6~'; break;
1917 case 35: /* End */ ch = '\u001BOF'; break;
1918 case 36: /* Home */ ch = '\u001BOH'; break;
1919 case 37: /* Left */ ch = this.cursorKeyMode ?
1920 '\u001BOD' : '\u001B[D'; break;
1921 case 38: /* Up */ ch = this.cursorKeyMode ?
1922 '\u001BOA' : '\u001B[A'; break;
1923 case 39: /* Right */ ch = this.cursorKeyMode ?
1924 '\u001BOC' : '\u001B[C'; break;
1925 case 40: /* Down */ ch = this.cursorKeyMode ?
1926 '\u001BOB' : '\u001B[B'; break;
1927 case 45: /* Insert */ ch = '\u001B[2~'; break;
1928 case 46: /* Delete */ ch = '\u001B[3~'; break;
1929 case 91: /* Left Window */ return;
1930 case 92: /* Right Window */ return;
1931 case 93: /* Select */ return;
1932 case 96: /* 0 */ ch = '0'; break;
1933 case 97: /* 1 */ ch = '1'; break;
1934 case 98: /* 2 */ ch = '2'; break;
1935 case 99: /* 3 */ ch = '3'; break;
1936 case 100: /* 4 */ ch = '4'; break;
1937 case 101: /* 5 */ ch = '5'; break;
1938 case 102: /* 6 */ ch = '6'; break;
1939 case 103: /* 7 */ ch = '7'; break;
1940 case 104: /* 8 */ ch = '8'; break;
1941 case 105: /* 9 */ ch = '9'; break;
1942 case 106: /* * */ ch = '*'; break;
1943 case 107: /* + */ ch = '+'; break;
1944 case 109: /* - */ ch = '-'; break;
1945 case 110: /* . */ ch = '.'; break;
1946 case 111: /* / */ ch = '/'; break;
1947 case 112: /* F1 */ ch = '\u001BOP'; break;
1948 case 113: /* F2 */ ch = '\u001BOQ'; break;
1949 case 114: /* F3 */ ch = '\u001BOR'; break;
1950 case 115: /* F4 */ ch = '\u001BOS'; break;
1951 case 116: /* F5 */ ch = '\u001B[15~'; break;
1952 case 117: /* F6 */ ch = '\u001B[17~'; break;
1953 case 118: /* F7 */ ch = '\u001B[18~'; break;
1954 case 119: /* F8 */ ch = '\u001B[19~'; break;
1955 case 120: /* F9 */ ch = '\u001B[20~'; break;
1956 case 121: /* F10 */ ch = '\u001B[21~'; break;
1957 case 122: /* F11 */ ch = '\u001B[23~'; break;
1958 case 123: /* F12 */ ch = '\u001B[24~'; break;
1959 case 144: /* Num Lock */ return;
1960 case 145: /* Scroll Lock */ return;
1961 default: return;
1962 }
1963 this.scrollable.scrollTop = this.numScrollbackLines *
1964 this.cursorHeight + 1;
1965 }
1966 }
1967
1968 // "ch" now contains the sequence of keycodes to send. But we might still
1969 // have to apply the effects of modifier keys.
1970 if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
1971 var start, digit, part1, part2;
1972 if ((start = ch.substr(0, 2)) == '\u001B[') {
1973 for (part1 = start;
1974 part1.length < ch.length &&
1975 (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
1976 part1 = ch.substr(0, part1.length + 1);
1977 }
1978 part2 = ch.substr(part1.length);
1979 if (part1.length > 2) {
1980 part1 += ';';
1981 }
1982 } else if (start == '\u001BO') {
1983 part1 = start;
1984 part2 = ch.substr(2);
1985 }
1986 if (part1 != undefined) {
1987 ch = part1 +
1988 ((event.shiftKey ? 1 : 0) +
1989 (event.altKey|event.metaKey ? 2 : 0) +
1990 (event.ctrlKey ? 4 : 0)) +
1991 part2;
1992 } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
1993 ch = '\u001B' + ch;
1994 }
1995 }
1996
1997 if (this.menu.style.visibility == 'hidden') {
8ac38fe6
MG
1998 // this.vt100('R: c=');
1999 // for (var i = 0; i < ch.length; i++)
2000 // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2001 // this.vt100('\r\n');
ce845548
MG
2002 this.keysPressed(ch);
2003 }
2004};
2005
2006VT100.prototype.inspect = function(o, d) {
2007 if (d == undefined) {
a7164199 2008 d = 0;
ce845548 2009 }
a7164199 2010 var rc = '';
ce845548 2011 if (typeof o == 'object' && ++d < 2) {
a7164199 2012 rc = '[\r\n';
ce845548 2013 for (i in o) {
a7164199 2014 rc += this.spaces(d * 2) + i + ' -> ';
ce845548 2015 try {
a7164199 2016 rc += this.inspect(o[i], d);
ce845548 2017 } catch (e) {
a7164199 2018 rc += '?' + '?' + '?\r\n';
ce845548
MG
2019 }
2020 }
a7164199 2021 rc += ']\r\n';
ce845548 2022 } else {
a7164199 2023 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
ce845548 2024 }
a7164199 2025 return rc;
ce845548
MG
2026};
2027
2028VT100.prototype.checkComposedKeys = function(event) {
2029 // Composed keys (at least on Linux) do not generate normal events.
2030 // Instead, they get entered into the text field. We normally catch
2031 // this on the next keyup event.
2032 var s = this.input.value;
2033 if (s.length) {
2034 this.input.value = '';
2035 if (this.menu.style.visibility == 'hidden') {
2036 this.keysPressed(s);
2037 }
2038 }
2039};
2040
2041VT100.prototype.fixEvent = function(event) {
0cf0bd3d
MG
2042 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2043 // is used as a second-level selector, clear the modifier bits before
2044 // handling the event.
2045 if (event.ctrlKey && event.altKey) {
2046 var fake = [ ];
2047 fake.charCode = event.charCode;
2048 fake.keyCode = event.keyCode;
2049 fake.ctrlKey = false;
2050 fake.shiftKey = event.shiftKey;
2051 fake.altKey = false;
2052 fake.metaKey = event.metaKey;
2053 return fake;
2054 }
2055
ce845548
MG
2056 // Some browsers fail to translate keys, if both shift and alt/meta is
2057 // pressed at the same time. We try to translate those cases, but that
2058 // only works for US keyboard layouts.
2059 if (event.shiftKey) {
2060 var u = undefined;
2061 var s = undefined;
2062 switch (this.lastNormalKeyDownEvent.keyCode) {
2063 case 39: /* ' -> " */ u = 39; s = 34; break;
2064 case 44: /* , -> < */ u = 44; s = 60; break;
2065 case 45: /* - -> _ */ u = 45; s = 95; break;
2066 case 46: /* . -> > */ u = 46; s = 62; break;
2067 case 47: /* / -> ? */ u = 47; s = 63; break;
2068
2069 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2070 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2071 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2072 case 51: /* 3 -> # */ u = 51; s = 35; break;
2073 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2074 case 53: /* 5 -> % */ u = 53; s = 37; break;
2075 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2076 case 55: /* 7 -> & */ u = 55; s = 38; break;
2077 case 56: /* 8 -> * */ u = 56; s = 42; break;
2078 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2079
2080 case 59: /* ; -> : */ u = 59; s = 58; break;
2081 case 61: /* = -> + */ u = 61; s = 43; break;
2082 case 91: /* [ -> { */ u = 91; s = 123; break;
2083 case 92: /* \ -> | */ u = 92; s = 124; break;
2084 case 93: /* ] -> } */ u = 93; s = 125; break;
2085 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2086
2087 case 109: /* - -> _ */ u = 45; s = 95; break;
2088 case 111: /* / -> ? */ u = 47; s = 63; break;
2089
2090 case 186: /* ; -> : */ u = 59; s = 58; break;
2091 case 187: /* = -> + */ u = 61; s = 43; break;
2092 case 188: /* , -> < */ u = 44; s = 60; break;
2093 case 189: /* - -> _ */ u = 45; s = 95; break;
2094 case 190: /* . -> > */ u = 46; s = 62; break;
2095 case 191: /* / -> ? */ u = 47; s = 63; break;
2096 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2097 case 219: /* [ -> { */ u = 91; s = 123; break;
2098 case 220: /* \ -> | */ u = 92; s = 124; break;
2099 case 221: /* ] -> } */ u = 93; s = 125; break;
2100 case 222: /* ' -> " */ u = 39; s = 34; break;
2101 default: break;
2102 }
2103 if (s && (event.charCode == u || event.charCode == 0)) {
2104 var fake = [ ];
2105 fake.charCode = s;
2106 fake.keyCode = event.keyCode;
2107 fake.ctrlKey = event.ctrlKey;
2108 fake.shiftKey = event.shiftKey;
2109 fake.altKey = event.altKey;
2110 fake.metaKey = event.metaKey;
2111 return fake;
2112 }
2113 }
2114 return event;
2115};
2116
2117VT100.prototype.keyDown = function(event) {
8ac38fe6
MG
2118 // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2119 // (event.shiftKey || event.ctrlKey || event.altKey ||
2120 // event.metaKey ? ', ' +
2121 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2122 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2123 // '\r\n');
ce845548
MG
2124 this.checkComposedKeys(event);
2125 this.lastKeyPressedEvent = undefined;
2126 this.lastKeyDownEvent = undefined;
2127 this.lastNormalKeyDownEvent = event;
2128
2129 var asciiKey =
2130 event.keyCode == 32 ||
2131 event.keyCode >= 48 && event.keyCode <= 57 ||
2132 event.keyCode >= 65 && event.keyCode <= 90;
2133 var alphNumKey =
2134 asciiKey ||
2135 event.keyCode >= 96 && event.keyCode <= 105;
2136 var normalKey =
2137 alphNumKey ||
2138 event.keyCode == 59 || event.keyCode == 61 ||
2139 event.keyCode == 106 || event.keyCode == 107 ||
2140 event.keyCode >= 109 && event.keyCode <= 111 ||
2141 event.keyCode >= 186 && event.keyCode <= 192 ||
2142 event.keyCode >= 219 && event.keyCode <= 222 ||
86db43f5 2143 event.keyCode == 226 || event.keyCode == 252;
ce845548
MG
2144 try {
2145 if (navigator.appName == 'Konqueror') {
2146 normalKey |= event.keyCode < 128;
2147 }
2148 } catch (e) {
2149 }
2150
2151 // We normally prefer to look at keypress events, as they perform the
2152 // translation from keyCode to charCode. This is important, as the
2153 // translation is locale-dependent.
2154 // But for some keys, we must intercept them during the keydown event,
2155 // as they would otherwise get interpreted by the browser.
2156 // Even, when doing all of this, there are some keys that we can never
2157 // intercept. This applies to some of the menu navigation keys in IE.
2158 // In fact, we see them, but we cannot stop IE from seeing them, too.
33eb7a7d
MG
2159 if ((event.charCode || event.keyCode) &&
2160 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
0cf0bd3d
MG
2161 !event.shiftKey &&
2162 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2163 // interpret this sequence ourselves, as some keyboard layouts use
2164 // it for second-level layouts.
2165 !(event.ctrlKey && event.altKey)) ||
33eb7a7d
MG
2166 this.catchModifiersEarly && normalKey && !alphNumKey &&
2167 (event.ctrlKey || event.altKey || event.metaKey) ||
2168 !normalKey)) {
ce845548
MG
2169 this.lastKeyDownEvent = event;
2170 var fake = [ ];
2171 fake.ctrlKey = event.ctrlKey;
2172 fake.shiftKey = event.shiftKey;
2173 fake.altKey = event.altKey;
2174 fake.metaKey = event.metaKey;
2175 if (asciiKey) {
2176 fake.charCode = event.keyCode;
2177 fake.keyCode = 0;
2178 } else {
2179 fake.charCode = 0;
2180 fake.keyCode = event.keyCode;
2181 if (!alphNumKey && event.shiftKey) {
2182 fake = this.fixEvent(fake);
2183 }
2184 }
2185
2186 this.handleKey(fake);
2187 this.lastNormalKeyDownEvent = undefined;
2188
2189 try {
2190 // For non-IE browsers
2191 event.stopPropagation();
2192 event.preventDefault();
2193 } catch (e) {
2194 }
2195 try {
2196 // For IE
2197 event.cancelBubble = true;
2198 event.returnValue = false;
2199 event.keyCode = 0;
2200 } catch (e) {
2201 }
2202
2203 return false;
2204 }
2205 return true;
2206};
2207
2208VT100.prototype.keyPressed = function(event) {
8ac38fe6
MG
2209 // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2210 // (event.shiftKey || event.ctrlKey || event.altKey ||
2211 // event.metaKey ? ', ' +
2212 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2213 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2214 // '\r\n');
ce845548
MG
2215 if (this.lastKeyDownEvent) {
2216 // If we already processed the key on keydown, do not process it
2217 // again here. Ideally, the browser should not even have generated a
2218 // keypress event in this case. But that does not appear to always work.
2219 this.lastKeyDownEvent = undefined;
2220 } else {
2221 this.handleKey(event.altKey || event.metaKey
2222 ? this.fixEvent(event) : event);
2223 }
2224
2225 try {
2226 // For non-IE browsers
2227 event.preventDefault();
2228 } catch (e) {
2229 }
2230
2231 try {
2232 // For IE
2233 event.cancelBubble = true;
2234 event.returnValue = false;
2235 event.keyCode = 0;
2236 } catch (e) {
2237 }
2238
2239 this.lastNormalKeyDownEvent = undefined;
2240 this.lastKeyPressedEvent = event;
2241 return false;
2242};
2243
2244VT100.prototype.keyUp = function(event) {
8ac38fe6
MG
2245 // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
2246 // (event.shiftKey || event.ctrlKey || event.altKey ||
2247 // event.metaKey ? ', ' +
2248 // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2249 // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2250 // '\r\n');
ce845548
MG
2251 if (this.lastKeyPressedEvent) {
2252 // The compose key on Linux occasionally confuses the browser and keeps
2253 // inserting bogus characters into the input field, even if just a regular
2254 // key has been pressed. Detect this case and drop the bogus characters.
2255 (event.target ||
2256 event.srcElement).value = '';
2257 } else {
2258 // This is usually were we notice that a key has been composed and
2259 // thus failed to generate normal events.
2260 this.checkComposedKeys(event);
2261
2262 // Some browsers don't report keypress events if ctrl or alt is pressed
2263 // for non-alphanumerical keys. Patch things up for now, but in the
2264 // future we will catch these keys earlier (in the keydown handler).
2265 if (this.lastNormalKeyDownEvent) {
2266 this.catchModifiersEarly = true;
2267 var asciiKey =
2268 event.keyCode == 32 ||
2269 event.keyCode >= 48 && event.keyCode <= 57 ||
2270 event.keyCode >= 65 && event.keyCode <= 90;
2271 var alphNumKey =
2272 asciiKey ||
2273 event.keyCode >= 96 && event.keyCode <= 105;
2274 var normalKey =
2275 alphNumKey ||
2276 event.keyCode == 59 || event.keyCode == 61 ||
2277 event.keyCode == 106 || event.keyCode == 107 ||
2278 event.keyCode >= 109 && event.keyCode <= 111 ||
2279 event.keyCode >= 186 && event.keyCode <= 192 ||
2280 event.keyCode >= 219 && event.keyCode <= 222 ||
2281 event.keyCode == 252;
2282 var fake = [ ];
2283 fake.ctrlKey = event.ctrlKey;
2284 fake.shiftKey = event.shiftKey;
2285 fake.altKey = event.altKey;
2286 fake.metaKey = event.metaKey;
2287 if (asciiKey) {
2288 fake.charCode = event.keyCode;
2289 fake.keyCode = 0;
2290 } else {
2291 fake.charCode = 0;
2292 fake.keyCode = event.keyCode;
2293 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2294 fake = this.fixEvent(fake);
2295 }
2296 }
2297 this.lastNormalKeyDownEvent = undefined;
2298 this.handleKey(fake);
2299 }
2300 }
2301
2302 try {
2303 // For IE
2304 event.cancelBubble = true;
2305 event.returnValue = false;
2306 event.keyCode = 0;
2307 } catch (e) {
2308 }
2309
2310 this.lastKeyDownEvent = undefined;
2311 this.lastKeyPressedEvent = undefined;
2312 return false;
2313};
2314
2315VT100.prototype.animateCursor = function(inactive) {
2316 if (!this.cursorInterval) {
2317 this.cursorInterval = setInterval(
2318 function(vt100) {
2319 return function() {
2320 vt100.animateCursor();
2321
2322 // Use this opportunity to check whether the user entered a composed
2323 // key, or whether somebody pasted text into the textfield.
2324 vt100.checkComposedKeys();
2325 }
2326 }(this), 500);
2327 }
2328 if (inactive != undefined || this.cursor.className != 'inactive') {
2329 if (inactive) {
2330 this.cursor.className = 'inactive';
2331 } else {
2332 this.cursor.className = this.cursor.className == 'bright'
2333 ? 'dim' : 'bright';
2334 }
2335 }
2336};
2337
2338VT100.prototype.blurCursor = function() {
2339 this.animateCursor(true);
2340};
2341
2342VT100.prototype.focusCursor = function() {
2343 this.animateCursor(false);
2344};
2345
2346VT100.prototype.flashScreen = function() {
2347 this.isInverted = !this.isInverted;
2348 this.refreshInvertedState();
2349 this.isInverted = !this.isInverted;
2350 setTimeout(function(vt100) {
2351 return function() {
2352 vt100.refreshInvertedState();
2353 };
2354 }(this), 100);
2355};
2356
2357VT100.prototype.beep = function() {
2358 if (this.visualBell) {
2359 this.flashScreen();
2360 } else {
2361 try {
2362 this.beeper.Play();
2363 } catch (e) {
2364 try {
2365 this.beeper.src = 'beep.wav';
2366 } catch (e) {
2367 }
2368 }
2369 }
2370};
2371
2372VT100.prototype.bs = function() {
2373 if (this.cursorX > 0) {
2374 this.gotoXY(this.cursorX - 1, this.cursorY);
2375 this.needWrap = false;
2376 }
2377};
2378
2379VT100.prototype.ht = function(count) {
2380 if (count == undefined) {
2381 count = 1;
2382 }
2383 var cx = this.cursorX;
2384 while (count-- > 0) {
2385 while (cx++ < this.terminalWidth) {
2386 var tabState = this.userTabStop[cx];
2387 if (tabState == false) {
2388 // Explicitly cleared tab stop
2389 continue;
2390 } else if (tabState) {
2391 // Explicitly set tab stop
2392 break;
2393 } else {
2394 // Default tab stop at each eighth column
2395 if (cx % 8 == 0) {
2396 break;
2397 }
2398 }
2399 }
2400 }
2401 if (cx > this.terminalWidth - 1) {
2402 cx = this.terminalWidth - 1;
2403 }
2404 if (cx != this.cursorX) {
2405 this.gotoXY(cx, this.cursorY);
2406 }
2407};
2408
2409VT100.prototype.rt = function(count) {
2410 if (count == undefined) {
2411 count = 1 ;
2412 }
2413 var cx = this.cursorX;
2414 while (count-- > 0) {
2415 while (cx-- > 0) {
2416 var tabState = this.userTabStop[cx];
2417 if (tabState == false) {
2418 // Explicitly cleared tab stop
2419 continue;
2420 } else if (tabState) {
2421 // Explicitly set tab stop
2422 break;
2423 } else {
2424 // Default tab stop at each eighth column
2425 if (cx % 8 == 0) {
2426 break;
2427 }
2428 }
2429 }
2430 }
2431 if (cx < 0) {
2432 cx = 0;
2433 }
2434 if (cx != this.cursorX) {
2435 this.gotoXY(cx, this.cursorY);
2436 }
2437};
2438
2439VT100.prototype.cr = function() {
2440 this.gotoXY(0, this.cursorY);
2441 this.needWrap = false;
2442};
2443
2444VT100.prototype.lf = function(count) {
2445 if (count == undefined) {
2446 count = 1;
2447 } else {
2448 if (count > this.terminalHeight) {
2449 count = this.terminalHeight;
2450 }
2451 if (count < 1) {
2452 count = 1;
2453 }
2454 }
2455 while (count-- > 0) {
2456 if (this.cursorY == this.bottom - 1) {
2457 this.scrollRegion(0, this.top + 1,
2458 this.terminalWidth, this.bottom - this.top - 1,
2459 0, -1, this.style);
2460 offset = undefined;
2461 } else if (this.cursorY < this.terminalHeight - 1) {
2462 this.gotoXY(this.cursorX, this.cursorY + 1);
2463 }
2464 }
2465};
2466
2467VT100.prototype.ri = function(count) {
2468 if (count == undefined) {
2469 count = 1;
2470 } else {
2471 if (count > this.terminalHeight) {
2472 count = this.terminalHeight;
2473 }
2474 if (count < 1) {
2475 count = 1;
2476 }
2477 }
2478 while (count-- > 0) {
2479 if (this.cursorY == this.top) {
2480 this.scrollRegion(0, this.top,
2481 this.terminalWidth, this.bottom - this.top - 1,
2482 0, 1, this.style);
2483 } else if (this.cursorY > 0) {
2484 this.gotoXY(this.cursorX, this.cursorY - 1);
2485 }
2486 }
2487 this.needWrap = false;
2488};
2489
2490VT100.prototype.respondID = function() {
2491 this.respondString += '\u001B[?6c';
2492};
2493
2494VT100.prototype.respondSecondaryDA = function() {
2495 this.respondString += '\u001B[>0;0;0c';
2496};
2497
2498VT100.prototype.updateStyle = function() {
2499 var style = '';
2500 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
2501 style += 'text-decoration:underline;';
2502 }
2503 var bg = (this.attr >> 4) & 0xF;
2504 var fg = this.attr & 0xF;
2505 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
2506 var tmp = bg;
2507 bg = fg;
2508 fg = tmp;
2509 }
2510 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
2511 fg = 8; // Dark grey
2512 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
2513 fg |= 8;
2514 }
2515 if (this.attr & 0x1000 /* ATTR_BLINK */) {
2516 bg ^= 8;
2517 }
2518 // Make some readability enhancements. Most notably, disallow identical
2519 // background and foreground colors.
2520 if (bg == fg) {
2521 if ((fg ^= 8) == 7) {
2522 fg = 8;
2523 }
2524 }
2525 // And disallow bright colors on a light-grey background.
2526 if (bg == 7 && fg >= 8) {
2527 if ((fg -= 8) == 7) {
2528 fg = 8;
2529 }
2530 }
2531
2532 if (fg != 0) {
2533 style += 'color:' + this.ansi[fg] + ';';
2534 }
2535 if (bg != 15) {
2536 style += 'background-color:' + this.ansi[bg] + ';';
2537 }
2538 this.attributeHelper.cssText = style;
2539 this.style = this.attributeHelper.cssText;
2540};
2541
2542VT100.prototype.setAttrColors = function(attr) {
2543 if (attr != this.attr) {
2544 this.attr = attr;
2545 this.updateStyle();
2546 }
2547};
2548
2549VT100.prototype.saveCursor = function() {
2550 this.savedX[this.currentScreen] = this.cursorX;
2551 this.savedY[this.currentScreen] = this.cursorY;
2552 this.savedAttr[this.currentScreen] = this.attr;
2553 this.savedUseGMap = this.useGMap;
2554 for (var i = 0; i < 4; i++) {
2555 this.savedGMap[i] = this.GMap[i];
2556 }
2557 this.savedValid[this.currentScreen] = true;
2558};
2559
2560VT100.prototype.restoreCursor = function() {
2561 if (!this.savedValid[this.currentScreen]) {
2562 return;
2563 }
2564 this.attr = this.savedAttr[this.currentScreen];
2565 this.updateStyle();
2566 this.useGMap = this.savedUseGMap;
2567 for (var i = 0; i < 4; i++) {
2568 this.GMap[i] = this.savedGMap[i];
2569 }
2570 this.translate = this.GMap[this.useGMap];
2571 this.needWrap = false;
2572 this.gotoXY(this.savedX[this.currentScreen],
2573 this.savedY[this.currentScreen]);
2574};
2575
2576VT100.prototype.setMode = function(state) {
2577 for (var i = 0; i <= this.npar; i++) {
2578 if (this.isQuestionMark) {
2579 switch (this.par[i]) {
2580 case 1: this.cursorKeyMode = state; break;
2581 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2582 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2583 case 6: this.offsetMode = state; break;
2584 case 7: this.autoWrapMode = state; break;
2585 case 1000:
2586 case 9: this.mouseReporting = state; break;
2587 case 25: this.cursorNeedsShowing = state;
2588 if (state) { this.showCursor(); }
2589 else { this.hideCursor(); } break;
2590 case 1047:
2591 case 1049:
2592 case 47: this.enableAlternateScreen(state); break;
2593 default: break;
2594 }
2595 } else {
2596 switch (this.par[i]) {
2597 case 3: this.dispCtrl = state; break;
2598 case 4: this.insertMode = state; break;
2599 case 20:this.crLfMode = state; break;
2600 default: break;
2601 }
2602 }
2603 }
2604};
2605
2606VT100.prototype.statusReport = function() {
2607 // Ready and operational.
2608 this.respondString += '\u001B[0n';
2609};
2610
2611VT100.prototype.cursorReport = function() {
2612 this.respondString += '\u001B[' +
2613 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2614 ';' +
2615 (this.cursorX + 1) +
2616 'R';
2617};
2618
2619VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2620 // Changing of cursor color is not implemented.
2621};
2622
2623VT100.prototype.csiAt = function(number) {
2624 // Insert spaces
2625 if (number == 0) {
2626 number = 1;
2627 }
2628 if (number > this.terminalWidth - this.cursorX) {
2629 number = this.terminalWidth - this.cursorX;
2630 }
2631 this.scrollRegion(this.cursorX, this.cursorY,
2632 this.terminalWidth - this.cursorX - number, 1,
2633 number, 0, this.style);
2634 this.needWrap = false;
2635};
2636
2637VT100.prototype.csiJ = function(number) {
2638 switch (number) {
2639 case 0: // Erase from cursor to end of display
2640 this.clearRegion(this.cursorX, this.cursorY,
2641 this.terminalWidth - this.cursorX, 1, this.style);
2642 if (this.cursorY < this.terminalHeight-2) {
2643 this.clearRegion(0, this.cursorY+1,
2644 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2645 this.style);
2646 }
2647 break;
2648 case 1: // Erase from start to cursor
2649 if (this.cursorY > 0) {
2650 this.clearRegion(0, 0,
2651 this.terminalWidth, this.cursorY, this.style);
2652 }
2653 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2654 break;
2655 case 2: // Erase whole display
2656 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2657 break;
2658 default:
2659 return;
2660 }
2661 needWrap = false;
2662};
2663
2664VT100.prototype.csiK = function(number) {
2665 switch (number) {
2666 case 0: // Erase from cursor to end of line
2667 this.clearRegion(this.cursorX, this.cursorY,
2668 this.terminalWidth - this.cursorX, 1, this.style);
2669 break;
2670 case 1: // Erase from start of line to cursor
2671 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2672 break;
2673 case 2: // Erase whole line
2674 this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2675 break;
2676 default:
2677 return;
2678 }
2679 needWrap = false;
2680};
2681
2682VT100.prototype.csiL = function(number) {
2683 // Open line by inserting blank line(s)
2684 if (this.cursorY >= this.bottom) {
2685 return;
2686 }
2687 if (number == 0) {
2688 number = 1;
2689 }
2690 if (number > this.bottom - this.cursorY) {
2691 number = this.bottom - this.cursorY;
2692 }
2693 this.scrollRegion(0, this.cursorY,
2694 this.terminalWidth, this.bottom - this.cursorY - number,
2695 0, number, this.style);
2696 needWrap = false;
2697};
2698
2699VT100.prototype.csiM = function(number) {
2700 // Delete line(s), scrolling up the bottom of the screen.
2701 if (this.cursorY >= this.bottom) {
2702 return;
2703 }
2704 if (number == 0) {
2705 number = 1;
2706 }
2707 if (number > this.bottom - this.cursorY) {
2708 number = bottom - cursorY;
2709 }
2710 this.scrollRegion(0, this.cursorY + number,
2711 this.terminalWidth, this.bottom - this.cursorY - number,
2712 0, -number, this.style);
2713 needWrap = false;
2714};
2715
2716VT100.prototype.csim = function() {
2717 for (var i = 0; i <= this.npar; i++) {
2718 switch (this.par[i]) {
2719 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2720 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2721 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2722 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2723 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2724 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
2725 case 10:
2726 this.translate = this.GMap[this.useGMap];
2727 this.dispCtrl = false;
2728 this.toggleMeta = false;
2729 break;
2730 case 11:
2731 this.translate = this.CodePage437Map;
2732 this.dispCtrl = true;
2733 this.toggleMeta = false;
2734 break;
2735 case 12:
2736 this.translate = this.CodePage437Map;
2737 this.dispCtrl = true;
2738 this.toggleMeta = true;
2739 break;
2740 case 21:
2741 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2742 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2743 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2744 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2745 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2746 0x0200 /* ATTR_UNDERLINE */; break;
2747 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
2748 case 49: this.attr |= 0xF0; break;
2749 default:
2750 if (this.par[i] >= 30 && this.par[i] <= 37) {
2751 var fg = this.par[i] - 30;
2752 this.attr = (this.attr & ~0x0F) | fg;
2753 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2754 var bg = this.par[i] - 40;
2755 this.attr = (this.attr & ~0xF0) | (bg << 4);
2756 }
2757 break;
2758 }
2759 }
2760 this.updateStyle();
2761};
2762
2763VT100.prototype.csiP = function(number) {
2764 // Delete character(s) following cursor
2765 if (number == 0) {
2766 number = 1;
2767 }
2768 if (number > this.terminalWidth - this.cursorX) {
2769 number = this.terminalWidth - this.cursorX;
2770 }
2771 this.scrollRegion(this.cursorX + number, this.cursorY,
2772 this.terminalWidth - this.cursorX - number, 1,
2773 -number, 0, this.style);
2774 needWrap = false;
2775};
2776
2777VT100.prototype.csiX = function(number) {
2778 // Clear characters following cursor
2779 if (number == 0) {
2780 number++;
2781 }
2782 if (number > this.terminalWidth - this.cursorX) {
2783 number = this.terminalWidth - this.cursorX;
2784 }
2785 this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2786 needWrap = false;
2787};
2788
2789VT100.prototype.settermCommand = function() {
2790 // Setterm commands are not implemented
2791};
2792
2793VT100.prototype.doControl = function(ch) {
2794 var lineBuf = '';
2795 switch (ch) {
2796 case 0x00: /* ignored */ break;
2797 case 0x08: this.bs(); break;
2798 case 0x09: this.ht(); break;
2799 case 0x0A:
2800 case 0x0B:
2801 case 0x0C:
2802 case 0x84: this.lf(); if (!this.crLfMode) break;
2803 case 0x0D: this.cr(); break;
2804 case 0x85: this.cr(); this.lf(); break;
2805 case 0x0E: this.useGMap = 1;
2806 this.translate = this.GMap[1];
2807 this.dispCtrl = true; break;
2808 case 0x0F: this.useGMap = 0;
2809 this.translate = this.GMap[0];
2810 this.dispCtrl = false; break;
2811 case 0x18:
2812 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2813 case 0x1B: this.isEsc = 1 /* ESesc */; break;
2814 case 0x7F: /* ignored */ break;
2815 case 0x88: this.userTabStop[this.cursorX] = true; break;
2816 case 0x8D: this.ri(); break;
2817 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2818 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
2819 case 0x9A: this.respondID(); break;
2820 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2821 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
2822 this.beep(); break;
2823 }
2824 /* fall thru */
2825 default: switch (this.isEsc) {
2826 case 1 /* ESesc */:
2827 this.isEsc = 0 /* ESnormal */;
2828 switch (ch) {
2829/*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2830/*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
2831/*-*/ case 0x2D:
2832/*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
2833/*.*/ case 0x2E:
2834/***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
2835/*/*/ case 0x2F:
2836/*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2837/*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
2838/*7*/ case 0x37: this.saveCursor(); break;
2839/*8*/ case 0x38: this.restoreCursor(); break;
2840/*>*/ case 0x3E: this.applKeyMode = false; break;
2841/*=*/ case 0x3D: this.applKeyMode = true; break;
2842/*D*/ case 0x44: this.lf(); break;
2843/*E*/ case 0x45: this.cr(); this.lf(); break;
2844/*M*/ case 0x4D: this.ri(); break;
2845/*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2846/*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
2847/*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
2848/*Z*/ case 0x5A: this.respondID(); break;
2849/*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
2850/*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
2851/*c*/ case 0x63: this.reset(); break;
2852/*g*/ case 0x67: this.flashScreen(); break;
2853 default: break;
2854 }
2855 break;
2856 case 15 /* ESnonstd */:
2857 switch (ch) {
2858/*0*/ case 0x30:
2859/*1*/ case 0x31:
2860/*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
2861/*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
2862 this.isEsc = 16 /* ESpalette */; break;
2863/*R*/ case 0x52: // Palette support is not implemented
2864 this.isEsc = 0 /* ESnormal */; break;
2865 default: this.isEsc = 0 /* ESnormal */; break;
2866 }
2867 break;
2868 case 16 /* ESpalette */:
2869 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2870 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2871 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2872 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
2873 : (ch & 0xF);
2874 if (this.npar == 7) {
2875 // Palette support is not implemented
2876 this.isEsc = 0 /* ESnormal */;
2877 }
2878 } else {
2879 this.isEsc = 0 /* ESnormal */;
2880 }
2881 break;
2882 case 2 /* ESsquare */:
2883 this.npar = 0;
2884 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2885 0, 0, 0, 0, 0, 0, 0, 0 ];
2886 this.isEsc = 3 /* ESgetpars */;
2887/*[*/ if (ch == 0x5B) { // Function key
2888 this.isEsc = 6 /* ESfunckey */;
2889 break;
2890 } else {
2891/*?*/ this.isQuestionMark = ch == 0x3F;
2892 if (this.isQuestionMark) {
2893 break;
2894 }
2895 }
2896 // Fall through
2897 case 5 /* ESdeviceattr */:
2898 case 3 /* ESgetpars */:
2899/*;*/ if (ch == 0x3B) {
2900 this.npar++;
2901 break;
2902 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
2903 var par = this.par[this.npar];
2904 if (par == undefined) {
2905 par = 0;
2906 }
2907 this.par[this.npar] = 10*par + (ch & 0xF);
2908 break;
2909 } else if (this.isEsc == 5 /* ESdeviceattr */) {
2910 switch (ch) {
2911/*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
2912/*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
2913/*n*/ case 0x6E: /* disable key modifier resource values */ break;
2914/*p*/ case 0x70: /* set pointer mode resource value */ break;
2915 default: break;
2916 }
2917 this.isEsc = 0 /* ESnormal */;
2918 break;
2919 } else {
2920 this.isEsc = 4 /* ESgotpars */;
2921 }
2922 // Fall through
2923 case 4 /* ESgotpars */:
2924 this.isEsc = 0 /* ESnormal */;
2925 if (this.isQuestionMark) {
2926 switch (ch) {
2927/*h*/ case 0x68: this.setMode(true); break;
2928/*l*/ case 0x6C: this.setMode(false); break;
2929/*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
2930 default: break;
2931 }
2932 this.isQuestionMark = false;
2933 break;
2934 }
2935 switch (ch) {
2936/*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
2937/*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
2938/*G*/ case 0x47:
2939/*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
2940/*A*/ case 0x41: this.gotoXY(this.cursorX,
2941 this.cursorY - (this.par[0] ? this.par[0] : 1));
2942 break;
2943/*B*/ case 0x42:
2944/*e*/ case 0x65: this.gotoXY(this.cursorX,
2945 this.cursorY + (this.par[0] ? this.par[0] : 1));
2946 break;
2947/*C*/ case 0x43:
2948/*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
2949 this.cursorY); break;
2950/*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
2951 this.cursorY); break;
2952/*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
2953 break;
2954/*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
2955 break;
2956/*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
2957/*H*/ case 0x48:
2958/*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
2959/*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
2960/*@*/ case 0x40: this.csiAt(this.par[0]); break;
2961/*J*/ case 0x4A: this.csiJ(this.par[0]); break;
2962/*K*/ case 0x4B: this.csiK(this.par[0]); break;
2963/*L*/ case 0x4C: this.csiL(this.par[0]); break;
2964/*M*/ case 0x4D: this.csiM(this.par[0]); break;
2965/*m*/ case 0x6D: this.csim(); break;
2966/*P*/ case 0x50: this.csiP(this.par[0]); break;
2967/*X*/ case 0x58: this.csiX(this.par[0]); break;
2968/*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
2969/*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
2970/*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
2971/*g*/ case 0x67: if (this.par[0] == 0) {
2972 this.userTabStop[this.cursorX] = false;
2973 } else if (this.par[0] == 2 || this.par[0] == 3) {
2974 this.userTabStop = [ ];
2975 for (var i = 0; i < this.terminalWidth; i++) {
2976 this.userTabStop[i] = false;
2977 }
2978 }
2979 break;
2980/*h*/ case 0x68: this.setMode(true); break;
2981/*l*/ case 0x6C: this.setMode(false); break;
2982/*n*/ case 0x6E: switch (this.par[0]) {
2983 case 5: this.statusReport(); break;
2984 case 6: this.cursorReport(); break;
2985 default: break;
2986 }
2987 break;
2988/*q*/ case 0x71: // LED control not implemented
2989 break;
2990/*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
2991 var b = this.par[1] ? this.par[1]
2992 : this.terminalHeight;
2993 if (t < b && b <= this.terminalHeight) {
2994 this.top = t - 1;
2995 this.bottom= b;
2996 this.gotoXaY(0, 0);
2997 }
2998 break;
2999/*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
3000 if (c > this.terminalWidth * this.terminalHeight) {
3001 c = this.terminalWidth * this.terminalHeight;
3002 }
3003 while (c-- > 0) {
3004 lineBuf += this.lastCharacter;
3005 }
3006 break;
3007/*s*/ case 0x73: this.saveCursor(); break;
3008/*u*/ case 0x75: this.restoreCursor(); break;
3009/*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
3010/*]*/ case 0x5D: this.settermCommand(); break;
3011 default: break;
3012 }
3013 break;
3014 case 12 /* ESbang */:
3015 if (ch == 'p') {
3016 this.reset();
3017 }
3018 this.isEsc = 0 /* ESnormal */;
3019 break;
3020 case 13 /* ESpercent */:
3021 this.isEsc = 0 /* ESnormal */;
3022 switch (ch) {
3023/*@*/ case 0x40: this.utfEnabled = false; break;
3024/*G*/ case 0x47:
3025/*8*/ case 0x38: this.utfEnabled = true; break;
3026 default: break;
3027 }
3028 break;
3029 case 6 /* ESfunckey */:
3030 this.isEsc = 0 /* ESnormal */; break;
3031 case 7 /* EShash */:
3032 this.isEsc = 0 /* ESnormal */;
3033/*8*/ if (ch == 0x38) {
3034 // Screen alignment test not implemented
3035 }
3036 break;
3037 case 8 /* ESsetG0 */:
3038 case 9 /* ESsetG1 */:
3039 case 10 /* ESsetG2 */:
3040 case 11 /* ESsetG3 */:
3041 var g = this.isEsc - 8 /* ESsetG0 */;
3042 this.isEsc = 0 /* ESnormal */;
3043 switch (ch) {
3044/*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3045/*A*/ case 0x42:
3046/*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3047/*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3048/*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3049 default: break;
3050 }
3051 if (this.useGMap == g) {
3052 this.translate = this.GMap[g];
3053 }
3054 break;
3055 case 17 /* ESstatus */:
3056 if (ch == 0x07) {
3057 if (this.statusString && this.statusString.charAt(0) == ';') {
3058 this.statusString = this.statusString.substr(1);
3059 }
3060 try {
3061 window.status = this.statusString;
3062 } catch (e) {
3063 }
3064 this.isEsc = 0 /* ESnormal */;
3065 } else {
3066 this.statusString += String.fromCharCode(ch);
3067 }
3068 break;
3069 case 18 /* ESss2 */:
3070 case 19 /* ESss3 */:
3071 if (ch < 256) {
3072 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
3073 [this.toggleMeta ? (ch | 0x80) : ch];
3074 if ((ch & 0xFF00) == 0xF000) {
3075 ch = ch & 0xFF;
3076 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3077 this.isEsc = 0 /* ESnormal */; break;
3078 }
3079 }
3080 this.lastCharacter = String.fromCharCode(ch);
3081 lineBuf += this.lastCharacter;
3082 this.isEsc = 0 /* ESnormal */; break;
3083 default:
3084 this.isEsc = 0 /* ESnormal */; break;
3085 }
3086 break;
3087 }
3088 return lineBuf;
3089};
3090
3091VT100.prototype.renderString = function(s, showCursor) {
3092 // We try to minimize the number of DOM operations by coalescing individual
3093 // characters into strings. This is a significant performance improvement.
3094 var incX = s.length;
3095 if (incX > this.terminalWidth - this.cursorX) {
3096 incX = this.terminalWidth - this.cursorX;
3097 if (incX <= 0) {
3098 return;
3099 }
3100 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3101 }
3102 if (showCursor) {
3103 // Minimize the number of calls to putString(), by avoiding a direct
3104 // call to this.showCursor()
3105 this.cursor.style.visibility = '';
3106 }
3107 this.putString(this.cursorX, this.cursorY, s, this.style);
3108};
3109
3110VT100.prototype.vt100 = function(s) {
3111 this.cursorNeedsShowing = this.hideCursor();
3112 this.respondString = '';
3113 var lineBuf = '';
3114 for (var i = 0; i < s.length; i++) {
3115 var ch = s.charCodeAt(i);
3116 if (this.utfEnabled) {
3117 // Decode UTF8 encoded character
3118 if (ch > 0x7F) {
3119 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3120 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3121 if (--this.utfCount <= 0) {
3122 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3123 ch = 0xFFFD;
3124 } else {
3125 ch = this.utfChar;
3126 }
3127 } else {
3128 continue;
3129 }
3130 } else {
3131 if ((ch & 0xE0) == 0xC0) {
3132 this.utfCount = 1;
3133 this.utfChar = ch & 0x1F;
3134 } else if ((ch & 0xF0) == 0xE0) {
3135 this.utfCount = 2;
3136 this.utfChar = ch & 0x0F;
3137 } else if ((ch & 0xF8) == 0xF0) {
3138 this.utfCount = 3;
3139 this.utfChar = ch & 0x07;
3140 } else if ((ch & 0xFC) == 0xF8) {
3141 this.utfCount = 4;
3142 this.utfChar = ch & 0x03;
3143 } else if ((ch & 0xFE) == 0xFC) {
3144 this.utfCount = 5;
3145 this.utfChar = ch & 0x01;
3146 } else {
3147 this.utfCount = 0;
3148 }
3149 continue;
3150 }
3151 } else {
3152 this.utfCount = 0;
3153 }
3154 }
3155 var isNormalCharacter =
3156 (ch >= 32 && ch <= 127 || ch >= 160 ||
3157 this.utfEnabled && ch >= 128 ||
3158 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3159 (ch != 0x7F || this.dispCtrl);
3160
3161 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
3162 if (ch < 256) {
3163 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3164 }
3165 if ((ch & 0xFF00) == 0xF000) {
3166 ch = ch & 0xFF;
3167 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3168 continue;
3169 }
3170 if (this.needWrap || this.insertMode) {
3171 if (lineBuf) {
3172 this.renderString(lineBuf);
3173 lineBuf = '';
3174 }
3175 }
3176 if (this.needWrap) {
3177 this.cr(); this.lf();
3178 }
3179 if (this.insertMode) {
3180 this.scrollRegion(this.cursorX, this.cursorY,
3181 this.terminalWidth - this.cursorX - 1, 1,
3182 1, 0, this.style);
3183 }
3184 this.lastCharacter = String.fromCharCode(ch);
3185 lineBuf += this.lastCharacter;
3186 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3187 this.needWrap = this.autoWrapMode;
3188 }
3189 } else {
3190 if (lineBuf) {
3191 this.renderString(lineBuf);
3192 lineBuf = '';
3193 }
3194 var expand = this.doControl(ch);
3195 if (expand.length) {
3196 var r = this.respondString;
3197 this.respondString= r + this.vt100(expand);
3198 }
3199 }
3200 }
3201 if (lineBuf) {
3202 this.renderString(lineBuf, this.cursorNeedsShowing);
3203 } else if (this.cursorNeedsShowing) {
3204 this.showCursor();
3205 }
3206 return this.respondString;
3207};
3208
3209VT100.prototype.Latin1Map = [
32100x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
32110x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
32120x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
32130x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
32140x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
32150x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
32160x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
32170x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
32180x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
32190x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
32200x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
32210x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
32220x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
32230x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
32240x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
32250x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
32260x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
32270x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
32280x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
32290x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
32300x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
32310x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
32320x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
32330x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
32340x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
32350x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
32360x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
32370x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
32380x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
32390x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
32400x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
32410x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3242];
3243
3244VT100.prototype.VT100GraphicsMap = [
32450x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
32460x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
32470x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
32480x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
32490x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
32500x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
32510x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
32520x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
32530x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
32540x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
32550x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
32560x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
32570x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
32580x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
32590xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
32600x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
32610x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
32620x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
32630x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
32640x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
32650x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
32660x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
32670x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
32680x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
32690x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
32700x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
32710x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
32720x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
32730x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
32740x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
32750x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
32760x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3277];
3278
3279VT100.prototype.CodePage437Map = [
32800x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
32810x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
32820x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
32830x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
32840x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
32850x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
32860x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
32870x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
32880x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
32890x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
32900x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
32910x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
32920x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
32930x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
32940x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
32950x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
32960x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
32970x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
32980x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
32990x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
33000x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
33010x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
33020x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
33030x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
33040x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
33050x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
33060x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
33070x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
33080x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
33090x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
33100x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
33110x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3312];
3313
3314VT100.prototype.DirectToFontMap = [
33150xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
33160xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
33170xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
33180xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
33190xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
33200xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
33210xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
33220xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
33230xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
33240xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
33250xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
33260xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
33270xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
33280xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
33290xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
33300xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
33310xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
33320xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
33330xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
33340xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
33350xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
33360xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
33370xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
33380xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
33390xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
33400xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
33410xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
33420xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
33430xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
33440xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
33450xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
33460xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3347];
3348
3349VT100.prototype.ctrlAction = [
3350 true, false, false, false, false, false, false, true,
3351 true, true, true, true, true, true, true, true,
3352 false, false, false, false, false, false, false, false,
3353 true, false, true, true, false, false, false, false
3354];
3355
3356VT100.prototype.ctrlAlways = [
3357 true, false, false, false, false, false, false, false,
3358 true, false, true, false, true, true, true, true,
3359 false, false, false, false, false, false, false, false,
3360 false, false, false, true, false, false, false, false
3361];
3362
This page took 6.11928 seconds and 5 git commands to generate.