]> andersk Git - test.git/blame - shellinabox/vt100.js
- Added the --css command line option to make incremental changes to the style sheet...
[test.git] / shellinabox / vt100.js
CommitLineData
7460295f 1// VT100.js -- JavaScript based terminal emulator
e0bb8a33 2// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
7460295f
MG
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
c27d0db9
MG
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
7460295f
MG
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 }
7460295f
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) {
c27d0db9 196 this.isEsc = 0 /* ESnormal */;
7460295f
MG
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;
b624088c
MG
208 this.visualBell = typeof suppressAllAudio !=
209 'undefined' &&
46f2036a 210 suppressAllAudio;
7460295f
MG
211 this.utfCount = 0;
212 this.utfChar = 0;
213 this.style = '';
c27d0db9 214 this.attr = 0x00F0 /* ATTR_DEFAULT */;
7460295f
MG
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') ||
bf57c5f9 278 !this.getChildById(this.container, 'ieprobe') ||
7460295f
MG
279 !this.getChildById(this.container, 'padding') ||
280 !this.getChildById(this.container, 'cursor') ||
281 !this.getChildById(this.container, 'lineheight') ||
79a640e0 282 !this.getChildById(this.container, 'space') ||
7460295f
MG
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') {
b624088c
MG
293 embed = typeof suppressAllAudio != 'undefined' &&
294 suppressAllAudio ? "" :
7460295f 295 '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
46f2036a
MG
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" />';
7460295f
MG
305 }
306 } catch (e) {
307 }
30046882 308
7460295f
MG
309 this.container.innerHTML =
310 '<div id="reconnect" style="visibility: hidden">' +
d1edcc0e
MG
311 '<input type="button" value="Connect" ' +
312 'onsubmit="return false" />' +
7460295f 313 '</div>' +
a7164199
MG
314 '<div id="cursize" style="visibility: hidden">' +
315 '</div>' +
7460295f
MG
316 '<div id="menu"></div>' +
317 '<div id="scrollable">' +
38d64ad8 318 '<pre id="lineheight">&nbsp;</pre>' +
bf57c5f9
MG
319 '<pre id="console">' +
320 '<pre></pre>' +
321 '<div id="ieprobe"><span>&nbsp;</span></div>' +
322 '</pre>' +
7460295f
MG
323 '<pre id="alt_console" style="display: none"></pre>' +
324 '<div id="padding"></div>' +
bf57c5f9 325 '<pre id="cursor">&nbsp;</pre>' +
7460295f
MG
326 '</div>' +
327 '<div class="hidden">' +
79a640e0 328 '<pre><div><span id="space"></span></div></pre>' +
7460295f
MG
329 '<input type="textfield" id="input" />' +
330 '<input type="textfield" id="cliphelper" />' +
331 '<span id="attrib">&nbsp;</span>' +
b624088c
MG
332 (typeof suppressAllAudio != 'undefined' &&
333 suppressAllAudio ? "" :
46f2036a 334 embed + '<bgsound id="beep_bgsound" loop=1 />') +
7460295f
MG
335 '</div>';
336 }
337
338 // Find the object used for playing the "beep" sound, if any.
46f2036a
MG
339 if (typeof suppressAllAudio != 'undefined' && suppressAllAudio) {
340 this.beeper = undefined;
341 } else {
7460295f 342 this.beeper = this.getChildById(this.container,
46f2036a
MG
343 'beep_embed');
344 if (!this.beeper || !this.beeper.Play) {
345 this.beeper = this.getChildById(this.container,
7460295f 346 'beep_bgsound');
46f2036a
MG
347 if (!this.beeper || typeof this.beeper.src == 'undefined') {
348 this.beeper = undefined;
349 }
7460295f
MG
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');
7460295f
MG
357 this.menu = this.getChildById(this.container, 'menu');
358 this.scrollable = this.getChildById(this.container,
359 'scrollable');
79a640e0
MG
360 this.lineheight = this.getChildById(this.container,
361 'lineheight');
7460295f
MG
362 this.console =
363 [ this.getChildById(this.container, 'console'),
364 this.getChildById(this.container, 'alt_console') ];
bf57c5f9 365 var ieProbe = this.getChildById(this.container, 'ieprobe');
7460295f 366 this.padding = this.getChildById(this.container, 'padding');
79a640e0
MG
367 this.cursor = this.getChildById(this.container, 'cursor');
368 this.space = this.getChildById(this.container, 'space');
7460295f
MG
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;
bf57c5f9
MG
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 = '';
7460295f
MG
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 ||
30046882
MG
404 document.documentElement.clientWidth ||
405 document.body.clientWidth) -
7460295f
MG
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);
7460295f
MG
417 this.addListener(window, 'resize',
418 function(vt100) {
419 return function() {
420 vt100.hideContextMenu();
421 vt100.resizer();
a7164199 422 vt100.showCurrentSize();
7460295f
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() {
d1edcc0e 439 var rc = vt100.reconnect();
7460295f 440 vt100.input.focus();
d1edcc0e 441 return rc;
7460295f
MG
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 };
c27d0db9
MG
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 */));
7460295f
MG
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() {
d1edcc0e 516 return false;
7460295f
MG
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 ||
30046882
MG
592 document.documentElement.clientHeight ||
593 document.body.clientHeight))-1;
7460295f 594 var partial = height % this.cursorHeight;
30046882
MG
595 this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
596 this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
7460295f
MG
597 var oldTerminalHeight = this.terminalHeight;
598 this.updateWidth();
599 this.updateHeight();
30046882 600
7460295f
MG
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
7460295f
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
7460295f
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
7460295f
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();
c27d0db9 734 if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
7460295f
MG
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.
c27d0db9 767 var button = type != 0 /* MOUSE_DOWN */ ? 3 :
7460295f
MG
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 &&
c27d0db9
MG
786 (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
787 if (inside || type != 0 /* MOUSE_DOWN */) {
7460295f
MG
788 if (button != undefined) {
789 var report = '\u001B[M' + String.fromCharCode(button + 32) +
790 String.fromCharCode(x + 33) +
791 String.fromCharCode(y + 33);
c27d0db9 792 if (type != 2 /* MOUSE_CLICK */) {
7460295f
MG
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) {
c27d0db9 806 if (type == 0 /* MOUSE_DOWN */) {
7460295f
MG
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
7460295f
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
7460295f
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 }
7460295f
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 ||
30046882
MG
958 document.documentElement.clientHeight ||
959 document.body.clientHeight)-1)/
7460295f
MG
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
7460295f
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') {
7460295f
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 }
38d64ad8
MG
1190 var pixelX = -1;
1191 var pixelY = -1;
7460295f
MG
1192 if (!this.cursor.style.visibility) {
1193 var idx = this.cursorX - xPos;
1194 if (span) {
38d64ad8
MG
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.
3a3b75e1
MG
1198 pixelY = span.offsetTop +
1199 span.offsetParent.offsetTop;
38d64ad8
MG
1200 s = this.getTextContent(span);
1201 var nxtIdx = idx - s.length;
7460295f 1202 if (nxtIdx < 0) {
38d64ad8
MG
1203 this.setTextContent(this.cursor, s.charAt(idx));
1204 pixelX = span.offsetLeft +
1205 idx*span.offsetWidth / s.length;
7460295f 1206 } else {
38d64ad8
MG
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 }
7460295f
MG
1220 }
1221 } else {
1222 this.setTextContent(this.cursor, ' ');
1223 }
1224 }
38d64ad8 1225 if (pixelX >= 0) {
bf57c5f9 1226 this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0)) + 'px';
38d64ad8 1227 } else {
79a640e0
MG
1228 this.setTextContent(this.space, this.spaces(this.cursorX));
1229 this.cursor.style.left = this.space.offsetWidth +
7460295f 1230 console.offsetLeft + 'px';
38d64ad8 1231 }
7460295f 1232 this.cursorY = yIdx - this.numScrollbackLines;
38d64ad8
MG
1233 if (pixelY >= 0) {
1234 this.cursor.style.top = pixelY + 'px';
1235 } else {
1236 this.cursor.style.top = yIdx*this.cursorHeight +
7460295f 1237 console.offsetTop + 'px';
38d64ad8 1238 }
7460295f
MG
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') {
7460295f
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 &&
bb335b40
MG
1423 w == this.terminalWidth && h == this.terminalHeight &&
1424 !style) {
7460295f
MG
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.
57e76178 1529 var hidden = this.hideCursor();
7460295f
MG
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; ) {
bb335b40
MG
1585 this.insertBlankLine(this.numScrollbackLines + y + h + incY,
1586 style);
7460295f
MG
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--; ) {
bb335b40 1598 this.insertBlankLine(this.numScrollbackLines + y, style);
7460295f
MG
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
57e76178 1636 hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
7460295f
MG
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() {
46f2036a 1692 this.visualBell = !this.visualBell;
7460295f
MG
1693};
1694
1695VT100.prototype.about = function() {
1f771613 1696 alert("VT100 Terminal Emulator " + "2.9 (revision 158)" +
79341530
MG
1697 "\nCopyright 2008-2009 by Markus Gutschke\n" +
1698 "For more information check http://shellinabox.com");
7460295f
MG
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">' +
46f2036a 1726 (this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+
7460295f
MG
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 ?
bb335b40 1908 '\r\n' : '\r'; break;
7460295f
MG
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') {
1998 this.keysPressed(ch);
1999 }
2000};
2001
2002VT100.prototype.inspect = function(o, d) {
2003 if (d == undefined) {
a7164199 2004 d = 0;
7460295f 2005 }
a7164199 2006 var rc = '';
7460295f 2007 if (typeof o == 'object' && ++d < 2) {
a7164199 2008 rc = '[\r\n';
7460295f 2009 for (i in o) {
a7164199 2010 rc += this.spaces(d * 2) + i + ' -> ';
7460295f 2011 try {
a7164199 2012 rc += this.inspect(o[i], d);
7460295f 2013 } catch (e) {
a7164199 2014 rc += '?' + '?' + '?\r\n';
7460295f
MG
2015 }
2016 }
a7164199 2017 rc += ']\r\n';
7460295f 2018 } else {
a7164199 2019 rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
7460295f 2020 }
a7164199 2021 return rc;
7460295f
MG
2022};
2023
2024VT100.prototype.checkComposedKeys = function(event) {
2025 // Composed keys (at least on Linux) do not generate normal events.
2026 // Instead, they get entered into the text field. We normally catch
2027 // this on the next keyup event.
2028 var s = this.input.value;
2029 if (s.length) {
2030 this.input.value = '';
2031 if (this.menu.style.visibility == 'hidden') {
2032 this.keysPressed(s);
2033 }
2034 }
2035};
2036
2037VT100.prototype.fixEvent = function(event) {
0cf0bd3d
MG
2038 // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2039 // is used as a second-level selector, clear the modifier bits before
2040 // handling the event.
2041 if (event.ctrlKey && event.altKey) {
2042 var fake = [ ];
2043 fake.charCode = event.charCode;
2044 fake.keyCode = event.keyCode;
2045 fake.ctrlKey = false;
2046 fake.shiftKey = event.shiftKey;
2047 fake.altKey = false;
2048 fake.metaKey = event.metaKey;
2049 return fake;
2050 }
2051
7460295f
MG
2052 // Some browsers fail to translate keys, if both shift and alt/meta is
2053 // pressed at the same time. We try to translate those cases, but that
2054 // only works for US keyboard layouts.
2055 if (event.shiftKey) {
2056 var u = undefined;
2057 var s = undefined;
2058 switch (this.lastNormalKeyDownEvent.keyCode) {
2059 case 39: /* ' -> " */ u = 39; s = 34; break;
2060 case 44: /* , -> < */ u = 44; s = 60; break;
2061 case 45: /* - -> _ */ u = 45; s = 95; break;
2062 case 46: /* . -> > */ u = 46; s = 62; break;
2063 case 47: /* / -> ? */ u = 47; s = 63; break;
2064
2065 case 48: /* 0 -> ) */ u = 48; s = 41; break;
2066 case 49: /* 1 -> ! */ u = 49; s = 33; break;
2067 case 50: /* 2 -> @ */ u = 50; s = 64; break;
2068 case 51: /* 3 -> # */ u = 51; s = 35; break;
2069 case 52: /* 4 -> $ */ u = 52; s = 36; break;
2070 case 53: /* 5 -> % */ u = 53; s = 37; break;
2071 case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2072 case 55: /* 7 -> & */ u = 55; s = 38; break;
2073 case 56: /* 8 -> * */ u = 56; s = 42; break;
2074 case 57: /* 9 -> ( */ u = 57; s = 40; break;
2075
2076 case 59: /* ; -> : */ u = 59; s = 58; break;
2077 case 61: /* = -> + */ u = 61; s = 43; break;
2078 case 91: /* [ -> { */ u = 91; s = 123; break;
2079 case 92: /* \ -> | */ u = 92; s = 124; break;
2080 case 93: /* ] -> } */ u = 93; s = 125; break;
2081 case 96: /* ` -> ~ */ u = 96; s = 126; break;
2082
2083 case 109: /* - -> _ */ u = 45; s = 95; break;
2084 case 111: /* / -> ? */ u = 47; s = 63; break;
2085
2086 case 186: /* ; -> : */ u = 59; s = 58; break;
2087 case 187: /* = -> + */ u = 61; s = 43; break;
2088 case 188: /* , -> < */ u = 44; s = 60; break;
2089 case 189: /* - -> _ */ u = 45; s = 95; break;
2090 case 190: /* . -> > */ u = 46; s = 62; break;
2091 case 191: /* / -> ? */ u = 47; s = 63; break;
2092 case 192: /* ` -> ~ */ u = 96; s = 126; break;
2093 case 219: /* [ -> { */ u = 91; s = 123; break;
2094 case 220: /* \ -> | */ u = 92; s = 124; break;
2095 case 221: /* ] -> } */ u = 93; s = 125; break;
2096 case 222: /* ' -> " */ u = 39; s = 34; break;
2097 default: break;
2098 }
2099 if (s && (event.charCode == u || event.charCode == 0)) {
2100 var fake = [ ];
2101 fake.charCode = s;
2102 fake.keyCode = event.keyCode;
2103 fake.ctrlKey = event.ctrlKey;
2104 fake.shiftKey = event.shiftKey;
2105 fake.altKey = event.altKey;
2106 fake.metaKey = event.metaKey;
2107 return fake;
2108 }
2109 }
2110 return event;
2111};
2112
2113VT100.prototype.keyDown = function(event) {
2114 this.checkComposedKeys(event);
2115 this.lastKeyPressedEvent = undefined;
2116 this.lastKeyDownEvent = undefined;
2117 this.lastNormalKeyDownEvent = event;
2118
2119 var asciiKey =
2120 event.keyCode == 32 ||
2121 event.keyCode >= 48 && event.keyCode <= 57 ||
2122 event.keyCode >= 65 && event.keyCode <= 90;
2123 var alphNumKey =
2124 asciiKey ||
2125 event.keyCode >= 96 && event.keyCode <= 105;
2126 var normalKey =
2127 alphNumKey ||
2128 event.keyCode == 59 || event.keyCode == 61 ||
2129 event.keyCode == 106 || event.keyCode == 107 ||
2130 event.keyCode >= 109 && event.keyCode <= 111 ||
2131 event.keyCode >= 186 && event.keyCode <= 192 ||
2132 event.keyCode >= 219 && event.keyCode <= 222 ||
86db43f5 2133 event.keyCode == 226 || event.keyCode == 252;
7460295f
MG
2134 try {
2135 if (navigator.appName == 'Konqueror') {
2136 normalKey |= event.keyCode < 128;
2137 }
2138 } catch (e) {
2139 }
2140
2141 // We normally prefer to look at keypress events, as they perform the
2142 // translation from keyCode to charCode. This is important, as the
2143 // translation is locale-dependent.
2144 // But for some keys, we must intercept them during the keydown event,
2145 // as they would otherwise get interpreted by the browser.
2146 // Even, when doing all of this, there are some keys that we can never
2147 // intercept. This applies to some of the menu navigation keys in IE.
2148 // In fact, we see them, but we cannot stop IE from seeing them, too.
33eb7a7d
MG
2149 if ((event.charCode || event.keyCode) &&
2150 ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
0cf0bd3d
MG
2151 !event.shiftKey &&
2152 // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2153 // interpret this sequence ourselves, as some keyboard layouts use
2154 // it for second-level layouts.
2155 !(event.ctrlKey && event.altKey)) ||
33eb7a7d
MG
2156 this.catchModifiersEarly && normalKey && !alphNumKey &&
2157 (event.ctrlKey || event.altKey || event.metaKey) ||
2158 !normalKey)) {
7460295f
MG
2159 this.lastKeyDownEvent = event;
2160 var fake = [ ];
2161 fake.ctrlKey = event.ctrlKey;
2162 fake.shiftKey = event.shiftKey;
2163 fake.altKey = event.altKey;
2164 fake.metaKey = event.metaKey;
2165 if (asciiKey) {
2166 fake.charCode = event.keyCode;
2167 fake.keyCode = 0;
2168 } else {
2169 fake.charCode = 0;
2170 fake.keyCode = event.keyCode;
2171 if (!alphNumKey && event.shiftKey) {
2172 fake = this.fixEvent(fake);
2173 }
2174 }
2175
2176 this.handleKey(fake);
2177 this.lastNormalKeyDownEvent = undefined;
2178
2179 try {
2180 // For non-IE browsers
2181 event.stopPropagation();
2182 event.preventDefault();
2183 } catch (e) {
2184 }
2185 try {
2186 // For IE
2187 event.cancelBubble = true;
2188 event.returnValue = false;
2189 event.keyCode = 0;
2190 } catch (e) {
2191 }
2192
2193 return false;
2194 }
2195 return true;
2196};
2197
2198VT100.prototype.keyPressed = function(event) {
2199 if (this.lastKeyDownEvent) {
2200 // If we already processed the key on keydown, do not process it
2201 // again here. Ideally, the browser should not even have generated a
2202 // keypress event in this case. But that does not appear to always work.
2203 this.lastKeyDownEvent = undefined;
2204 } else {
2205 this.handleKey(event.altKey || event.metaKey
2206 ? this.fixEvent(event) : event);
2207 }
2208
2209 try {
2210 // For non-IE browsers
2211 event.preventDefault();
2212 } catch (e) {
2213 }
2214
2215 try {
2216 // For IE
2217 event.cancelBubble = true;
2218 event.returnValue = false;
2219 event.keyCode = 0;
2220 } catch (e) {
2221 }
2222
2223 this.lastNormalKeyDownEvent = undefined;
2224 this.lastKeyPressedEvent = event;
2225 return false;
2226};
2227
2228VT100.prototype.keyUp = function(event) {
2229 if (this.lastKeyPressedEvent) {
2230 // The compose key on Linux occasionally confuses the browser and keeps
2231 // inserting bogus characters into the input field, even if just a regular
2232 // key has been pressed. Detect this case and drop the bogus characters.
2233 (event.target ||
2234 event.srcElement).value = '';
2235 } else {
2236 // This is usually were we notice that a key has been composed and
2237 // thus failed to generate normal events.
2238 this.checkComposedKeys(event);
2239
2240 // Some browsers don't report keypress events if ctrl or alt is pressed
2241 // for non-alphanumerical keys. Patch things up for now, but in the
2242 // future we will catch these keys earlier (in the keydown handler).
2243 if (this.lastNormalKeyDownEvent) {
2244 this.catchModifiersEarly = true;
2245 var asciiKey =
2246 event.keyCode == 32 ||
2247 event.keyCode >= 48 && event.keyCode <= 57 ||
2248 event.keyCode >= 65 && event.keyCode <= 90;
2249 var alphNumKey =
2250 asciiKey ||
2251 event.keyCode >= 96 && event.keyCode <= 105;
2252 var normalKey =
2253 alphNumKey ||
2254 event.keyCode == 59 || event.keyCode == 61 ||
2255 event.keyCode == 106 || event.keyCode == 107 ||
2256 event.keyCode >= 109 && event.keyCode <= 111 ||
2257 event.keyCode >= 186 && event.keyCode <= 192 ||
2258 event.keyCode >= 219 && event.keyCode <= 222 ||
2259 event.keyCode == 252;
2260 var fake = [ ];
2261 fake.ctrlKey = event.ctrlKey;
2262 fake.shiftKey = event.shiftKey;
2263 fake.altKey = event.altKey;
2264 fake.metaKey = event.metaKey;
2265 if (asciiKey) {
2266 fake.charCode = event.keyCode;
2267 fake.keyCode = 0;
2268 } else {
2269 fake.charCode = 0;
2270 fake.keyCode = event.keyCode;
2271 if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
2272 fake = this.fixEvent(fake);
2273 }
2274 }
2275 this.lastNormalKeyDownEvent = undefined;
2276 this.handleKey(fake);
2277 }
2278 }
2279
2280 try {
2281 // For IE
2282 event.cancelBubble = true;
2283 event.returnValue = false;
2284 event.keyCode = 0;
2285 } catch (e) {
2286 }
2287
2288 this.lastKeyDownEvent = undefined;
2289 this.lastKeyPressedEvent = undefined;
2290 return false;
2291};
2292
2293VT100.prototype.animateCursor = function(inactive) {
2294 if (!this.cursorInterval) {
2295 this.cursorInterval = setInterval(
2296 function(vt100) {
2297 return function() {
2298 vt100.animateCursor();
2299
2300 // Use this opportunity to check whether the user entered a composed
2301 // key, or whether somebody pasted text into the textfield.
2302 vt100.checkComposedKeys();
2303 }
2304 }(this), 500);
2305 }
2306 if (inactive != undefined || this.cursor.className != 'inactive') {
2307 if (inactive) {
2308 this.cursor.className = 'inactive';
2309 } else {
2310 this.cursor.className = this.cursor.className == 'bright'
2311 ? 'dim' : 'bright';
2312 }
2313 }
2314};
2315
2316VT100.prototype.blurCursor = function() {
2317 this.animateCursor(true);
2318};
2319
2320VT100.prototype.focusCursor = function() {
2321 this.animateCursor(false);
2322};
2323
2324VT100.prototype.flashScreen = function() {
2325 this.isInverted = !this.isInverted;
2326 this.refreshInvertedState();
2327 this.isInverted = !this.isInverted;
2328 setTimeout(function(vt100) {
2329 return function() {
2330 vt100.refreshInvertedState();
2331 };
2332 }(this), 100);
2333};
2334
2335VT100.prototype.beep = function() {
46f2036a 2336 if (this.visualBell) {
7460295f
MG
2337 this.flashScreen();
2338 } else {
2339 try {
2340 this.beeper.Play();
2341 } catch (e) {
2342 try {
2343 this.beeper.src = 'beep.wav';
2344 } catch (e) {
2345 }
2346 }
2347 }
2348};
2349
2350VT100.prototype.bs = function() {
2351 if (this.cursorX > 0) {
2352 this.gotoXY(this.cursorX - 1, this.cursorY);
2353 this.needWrap = false;
2354 }
2355};
2356
2357VT100.prototype.ht = function(count) {
2358 if (count == undefined) {
2359 count = 1;
2360 }
2361 var cx = this.cursorX;
2362 while (count-- > 0) {
2363 while (cx++ < this.terminalWidth) {
2364 var tabState = this.userTabStop[cx];
2365 if (tabState == false) {
2366 // Explicitly cleared tab stop
2367 continue;
2368 } else if (tabState) {
2369 // Explicitly set tab stop
2370 break;
2371 } else {
2372 // Default tab stop at each eighth column
2373 if (cx % 8 == 0) {
2374 break;
2375 }
2376 }
2377 }
2378 }
2379 if (cx > this.terminalWidth - 1) {
2380 cx = this.terminalWidth - 1;
2381 }
2382 if (cx != this.cursorX) {
2383 this.gotoXY(cx, this.cursorY);
2384 }
2385};
2386
2387VT100.prototype.rt = function(count) {
2388 if (count == undefined) {
2389 count = 1 ;
2390 }
2391 var cx = this.cursorX;
2392 while (count-- > 0) {
2393 while (cx-- > 0) {
2394 var tabState = this.userTabStop[cx];
2395 if (tabState == false) {
2396 // Explicitly cleared tab stop
2397 continue;
2398 } else if (tabState) {
2399 // Explicitly set tab stop
2400 break;
2401 } else {
2402 // Default tab stop at each eighth column
2403 if (cx % 8 == 0) {
2404 break;
2405 }
2406 }
2407 }
2408 }
2409 if (cx < 0) {
2410 cx = 0;
2411 }
2412 if (cx != this.cursorX) {
2413 this.gotoXY(cx, this.cursorY);
2414 }
2415};
2416
2417VT100.prototype.cr = function() {
2418 this.gotoXY(0, this.cursorY);
2419 this.needWrap = false;
2420};
2421
2422VT100.prototype.lf = function(count) {
2423 if (count == undefined) {
2424 count = 1;
2425 } else {
2426 if (count > this.terminalHeight) {
2427 count = this.terminalHeight;
2428 }
2429 if (count < 1) {
2430 count = 1;
2431 }
2432 }
2433 while (count-- > 0) {
2434 if (this.cursorY == this.bottom - 1) {
2435 this.scrollRegion(0, this.top + 1,
2436 this.terminalWidth, this.bottom - this.top - 1,
2437 0, -1, this.style);
2438 offset = undefined;
2439 } else if (this.cursorY < this.terminalHeight - 1) {
2440 this.gotoXY(this.cursorX, this.cursorY + 1);
2441 }
2442 }
2443};
2444
2445VT100.prototype.ri = function(count) {
2446 if (count == undefined) {
2447 count = 1;
2448 } else {
2449 if (count > this.terminalHeight) {
2450 count = this.terminalHeight;
2451 }
2452 if (count < 1) {
2453 count = 1;
2454 }
2455 }
2456 while (count-- > 0) {
2457 if (this.cursorY == this.top) {
2458 this.scrollRegion(0, this.top,
2459 this.terminalWidth, this.bottom - this.top - 1,
2460 0, 1, this.style);
2461 } else if (this.cursorY > 0) {
2462 this.gotoXY(this.cursorX, this.cursorY - 1);
2463 }
2464 }
2465 this.needWrap = false;
2466};
2467
2468VT100.prototype.respondID = function() {
2469 this.respondString += '\u001B[?6c';
2470};
2471
2472VT100.prototype.respondSecondaryDA = function() {
2473 this.respondString += '\u001B[>0;0;0c';
2474};
2475
2476VT100.prototype.updateStyle = function() {
2477 var style = '';
c27d0db9 2478 if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
7460295f
MG
2479 style += 'text-decoration:underline;';
2480 }
2481 var bg = (this.attr >> 4) & 0xF;
2482 var fg = this.attr & 0xF;
c27d0db9 2483 if (this.attr & 0x0100 /* ATTR_REVERSE */) {
7460295f
MG
2484 var tmp = bg;
2485 bg = fg;
2486 fg = tmp;
2487 }
c27d0db9 2488 if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
7460295f 2489 fg = 8; // Dark grey
c27d0db9 2490 } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
7460295f
MG
2491 fg |= 8;
2492 }
c27d0db9 2493 if (this.attr & 0x1000 /* ATTR_BLINK */) {
7460295f
MG
2494 bg ^= 8;
2495 }
2496 // Make some readability enhancements. Most notably, disallow identical
2497 // background and foreground colors.
2498 if (bg == fg) {
2499 if ((fg ^= 8) == 7) {
2500 fg = 8;
2501 }
2502 }
2503 // And disallow bright colors on a light-grey background.
2504 if (bg == 7 && fg >= 8) {
2505 if ((fg -= 8) == 7) {
2506 fg = 8;
2507 }
2508 }
2509
2510 if (fg != 0) {
2511 style += 'color:' + this.ansi[fg] + ';';
2512 }
2513 if (bg != 15) {
2514 style += 'background-color:' + this.ansi[bg] + ';';
2515 }
2516 this.attributeHelper.cssText = style;
2517 this.style = this.attributeHelper.cssText;
2518};
2519
2520VT100.prototype.setAttrColors = function(attr) {
2521 if (attr != this.attr) {
2522 this.attr = attr;
2523 this.updateStyle();
2524 }
2525};
2526
2527VT100.prototype.saveCursor = function() {
2528 this.savedX[this.currentScreen] = this.cursorX;
2529 this.savedY[this.currentScreen] = this.cursorY;
2530 this.savedAttr[this.currentScreen] = this.attr;
2531 this.savedUseGMap = this.useGMap;
2532 for (var i = 0; i < 4; i++) {
2533 this.savedGMap[i] = this.GMap[i];
2534 }
2535 this.savedValid[this.currentScreen] = true;
2536};
2537
2538VT100.prototype.restoreCursor = function() {
2539 if (!this.savedValid[this.currentScreen]) {
2540 return;
2541 }
2542 this.attr = this.savedAttr[this.currentScreen];
2543 this.updateStyle();
2544 this.useGMap = this.savedUseGMap;
2545 for (var i = 0; i < 4; i++) {
2546 this.GMap[i] = this.savedGMap[i];
2547 }
2548 this.translate = this.GMap[this.useGMap];
2549 this.needWrap = false;
2550 this.gotoXY(this.savedX[this.currentScreen],
2551 this.savedY[this.currentScreen]);
2552};
2553
2554VT100.prototype.setMode = function(state) {
2555 for (var i = 0; i <= this.npar; i++) {
2556 if (this.isQuestionMark) {
2557 switch (this.par[i]) {
2558 case 1: this.cursorKeyMode = state; break;
2559 case 3: /* Toggling between 80/132 mode is not implemented */ break;
2560 case 5: this.isInverted = state; this.refreshInvertedState(); break;
2561 case 6: this.offsetMode = state; break;
2562 case 7: this.autoWrapMode = state; break;
2563 case 1000:
2564 case 9: this.mouseReporting = state; break;
57e76178
MG
2565 case 25: this.cursorNeedsShowing = state;
2566 if (state) { this.showCursor(); }
7460295f 2567 else { this.hideCursor(); } break;
bb335b40
MG
2568 case 1047:
2569 case 1049:
7460295f
MG
2570 case 47: this.enableAlternateScreen(state); break;
2571 default: break;
2572 }
2573 } else {
2574 switch (this.par[i]) {
2575 case 3: this.dispCtrl = state; break;
2576 case 4: this.insertMode = state; break;
2577 case 20:this.crLfMode = state; break;
2578 default: break;
2579 }
2580 }
2581 }
2582};
2583
2584VT100.prototype.statusReport = function() {
2585 // Ready and operational.
2586 this.respondString += '\u001B[0n';
2587};
2588
2589VT100.prototype.cursorReport = function() {
2590 this.respondString += '\u001B[' +
2591 (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
2592 ';' +
2593 (this.cursorX + 1) +
2594 'R';
2595};
2596
2597VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
2598 // Changing of cursor color is not implemented.
2599};
2600
2601VT100.prototype.csiAt = function(number) {
2602 // Insert spaces
2603 if (number == 0) {
2604 number = 1;
2605 }
2606 if (number > this.terminalWidth - this.cursorX) {
2607 number = this.terminalWidth - this.cursorX;
2608 }
2609 this.scrollRegion(this.cursorX, this.cursorY,
2610 this.terminalWidth - this.cursorX - number, 1,
2611 number, 0, this.style);
2612 this.needWrap = false;
2613};
2614
2615VT100.prototype.csiJ = function(number) {
2616 switch (number) {
2617 case 0: // Erase from cursor to end of display
2618 this.clearRegion(this.cursorX, this.cursorY,
2619 this.terminalWidth - this.cursorX, 1, this.style);
2620 if (this.cursorY < this.terminalHeight-2) {
2621 this.clearRegion(0, this.cursorY+1,
2622 this.terminalWidth, this.terminalHeight-this.cursorY-1,
2623 this.style);
2624 }
2625 break;
2626 case 1: // Erase from start to cursor
2627 if (this.cursorY > 0) {
2628 this.clearRegion(0, 0,
2629 this.terminalWidth, this.cursorY, this.style);
2630 }
2631 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2632 break;
2633 case 2: // Erase whole display
2634 this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,this.style);
2635 break;
2636 default:
2637 return;
2638 }
2639 needWrap = false;
2640};
2641
2642VT100.prototype.csiK = function(number) {
2643 switch (number) {
2644 case 0: // Erase from cursor to end of line
2645 this.clearRegion(this.cursorX, this.cursorY,
2646 this.terminalWidth - this.cursorX, 1, this.style);
2647 break;
2648 case 1: // Erase from start of line to cursor
2649 this.clearRegion(0, this.cursorY, this.cursorX + 1, 1, this.style);
2650 break;
2651 case 2: // Erase whole line
2652 this.clearRegion(0, this.cursorY, this.terminalWidth, 1, this.style);
2653 break;
2654 default:
2655 return;
2656 }
2657 needWrap = false;
2658};
2659
2660VT100.prototype.csiL = function(number) {
2661 // Open line by inserting blank line(s)
2662 if (this.cursorY >= this.bottom) {
2663 return;
2664 }
2665 if (number == 0) {
2666 number = 1;
2667 }
2668 if (number > this.bottom - this.cursorY) {
2669 number = this.bottom - this.cursorY;
2670 }
2671 this.scrollRegion(0, this.cursorY,
2672 this.terminalWidth, this.bottom - this.cursorY - number,
2673 0, number, this.style);
2674 needWrap = false;
2675};
2676
2677VT100.prototype.csiM = function(number) {
2678 // Delete line(s), scrolling up the bottom of the screen.
2679 if (this.cursorY >= this.bottom) {
2680 return;
2681 }
2682 if (number == 0) {
2683 number = 1;
2684 }
2685 if (number > this.bottom - this.cursorY) {
2686 number = bottom - cursorY;
2687 }
2688 this.scrollRegion(0, this.cursorY + number,
2689 this.terminalWidth, this.bottom - this.cursorY - number,
2690 0, -number, this.style);
2691 needWrap = false;
2692};
2693
2694VT100.prototype.csim = function() {
2695 for (var i = 0; i <= this.npar; i++) {
2696 switch (this.par[i]) {
c27d0db9
MG
2697 case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
2698 case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
2699 case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
2700 case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
2701 case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
2702 case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
7460295f
MG
2703 case 10:
2704 this.translate = this.GMap[this.useGMap];
2705 this.dispCtrl = false;
2706 this.toggleMeta = false;
2707 break;
2708 case 11:
2709 this.translate = this.CodePage437Map;
2710 this.dispCtrl = true;
2711 this.toggleMeta = false;
2712 break;
2713 case 12:
2714 this.translate = this.CodePage437Map;
2715 this.dispCtrl = true;
2716 this.toggleMeta = true;
2717 break;
2718 case 21:
c27d0db9
MG
2719 case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
2720 case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
2721 case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
2722 case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
2723 case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
2724 0x0200 /* ATTR_UNDERLINE */; break;
2725 case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
7460295f
MG
2726 case 49: this.attr |= 0xF0; break;
2727 default:
2728 if (this.par[i] >= 30 && this.par[i] <= 37) {
2729 var fg = this.par[i] - 30;
2730 this.attr = (this.attr & ~0x0F) | fg;
2731 } else if (this.par[i] >= 40 && this.par[i] <= 47) {
2732 var bg = this.par[i] - 40;
2733 this.attr = (this.attr & ~0xF0) | (bg << 4);
2734 }
2735 break;
2736 }
2737 }
2738 this.updateStyle();
2739};
2740
2741VT100.prototype.csiP = function(number) {
2742 // Delete character(s) following cursor
2743 if (number == 0) {
2744 number = 1;
2745 }
2746 if (number > this.terminalWidth - this.cursorX) {
2747 number = this.terminalWidth - this.cursorX;
2748 }
2749 this.scrollRegion(this.cursorX + number, this.cursorY,
2750 this.terminalWidth - this.cursorX - number, 1,
2751 -number, 0, this.style);
2752 needWrap = false;
2753};
2754
2755VT100.prototype.csiX = function(number) {
2756 // Clear characters following cursor
2757 if (number == 0) {
2758 number++;
2759 }
2760 if (number > this.terminalWidth - this.cursorX) {
2761 number = this.terminalWidth - this.cursorX;
2762 }
2763 this.clearRegion(this.cursorX, this.cursorY, number, 1, this.style);
2764 needWrap = false;
2765};
2766
2767VT100.prototype.settermCommand = function() {
2768 // Setterm commands are not implemented
2769};
2770
2771VT100.prototype.doControl = function(ch) {
2772 var lineBuf = '';
2773 switch (ch) {
2774 case 0x00: /* ignored */ break;
2775 case 0x08: this.bs(); break;
2776 case 0x09: this.ht(); break;
2777 case 0x0A:
2778 case 0x0B:
2779 case 0x0C:
2780 case 0x84: this.lf(); if (!this.crLfMode) break;
2781 case 0x0D: this.cr(); break;
2782 case 0x85: this.cr(); this.lf(); break;
2783 case 0x0E: this.useGMap = 1;
2784 this.translate = this.GMap[1];
2785 this.dispCtrl = true; break;
2786 case 0x0F: this.useGMap = 0;
5db428be 2787 this.translate = this.GMap[0];
7460295f
MG
2788 this.dispCtrl = false; break;
2789 case 0x18:
c27d0db9
MG
2790 case 0x1A: this.isEsc = 0 /* ESnormal */; break;
2791 case 0x1B: this.isEsc = 1 /* ESesc */; break;
7460295f
MG
2792 case 0x7F: /* ignored */ break;
2793 case 0x88: this.userTabStop[this.cursorX] = true; break;
2794 case 0x8D: this.ri(); break;
c27d0db9
MG
2795 case 0x8E: this.isEsc = 18 /* ESss2 */; break;
2796 case 0x8F: this.isEsc = 19 /* ESss3 */; break;
7460295f 2797 case 0x9A: this.respondID(); break;
c27d0db9
MG
2798 case 0x9B: this.isEsc = 2 /* ESsquare */; break;
2799 case 0x07: if (this.isEsc != 17 /* ESstatus */) {
7460295f
MG
2800 this.beep(); break;
2801 }
2802 /* fall thru */
2803 default: switch (this.isEsc) {
c27d0db9
MG
2804 case 1 /* ESesc */:
2805 this.isEsc = 0 /* ESnormal */;
7460295f 2806 switch (ch) {
c27d0db9
MG
2807/*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
2808/*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
7460295f 2809/*-*/ case 0x2D:
c27d0db9 2810/*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
7460295f 2811/*.*/ case 0x2E:
c27d0db9 2812/***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
7460295f 2813/*/*/ case 0x2F:
c27d0db9
MG
2814/*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
2815/*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
7460295f
MG
2816/*7*/ case 0x37: this.saveCursor(); break;
2817/*8*/ case 0x38: this.restoreCursor(); break;
2818/*>*/ case 0x3E: this.applKeyMode = false; break;
2819/*=*/ case 0x3D: this.applKeyMode = true; break;
2820/*D*/ case 0x44: this.lf(); break;
2821/*E*/ case 0x45: this.cr(); this.lf(); break;
2822/*M*/ case 0x4D: this.ri(); break;
c27d0db9
MG
2823/*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
2824/*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
7460295f
MG
2825/*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
2826/*Z*/ case 0x5A: this.respondID(); break;
c27d0db9
MG
2827/*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
2828/*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
7460295f
MG
2829/*c*/ case 0x63: this.reset(); break;
2830/*g*/ case 0x67: this.flashScreen(); break;
2831 default: break;
2832 }
2833 break;
c27d0db9 2834 case 15 /* ESnonstd */:
7460295f
MG
2835 switch (ch) {
2836/*0*/ case 0x30:
2837/*1*/ case 0x31:
c27d0db9 2838/*2*/ case 0x32: this.statusString = ''; this.isEsc = 17 /* ESstatus */; break;
7460295f 2839/*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
c27d0db9 2840 this.isEsc = 16 /* ESpalette */; break;
7460295f 2841/*R*/ case 0x52: // Palette support is not implemented
c27d0db9
MG
2842 this.isEsc = 0 /* ESnormal */; break;
2843 default: this.isEsc = 0 /* ESnormal */; break;
7460295f
MG
2844 }
2845 break;
c27d0db9 2846 case 16 /* ESpalette */:
7460295f
MG
2847 if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
2848 (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
2849 (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
2850 this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
2851 : (ch & 0xF);
2852 if (this.npar == 7) {
2853 // Palette support is not implemented
c27d0db9 2854 this.isEsc = 0 /* ESnormal */;
7460295f
MG
2855 }
2856 } else {
c27d0db9 2857 this.isEsc = 0 /* ESnormal */;
7460295f
MG
2858 }
2859 break;
c27d0db9 2860 case 2 /* ESsquare */:
7460295f
MG
2861 this.npar = 0;
2862 this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
2863 0, 0, 0, 0, 0, 0, 0, 0 ];
c27d0db9 2864 this.isEsc = 3 /* ESgetpars */;
7460295f 2865/*[*/ if (ch == 0x5B) { // Function key
c27d0db9 2866 this.isEsc = 6 /* ESfunckey */;
7460295f
MG
2867 break;
2868 } else {
2869/*?*/ this.isQuestionMark = ch == 0x3F;
2870 if (this.isQuestionMark) {
2871 break;
2872 }
2873 }
2874 // Fall through
c27d0db9
MG
2875 case 5 /* ESdeviceattr */:
2876 case 3 /* ESgetpars */:
7460295f
MG
2877/*;*/ if (ch == 0x3B) {
2878 this.npar++;
2879 break;
2880 } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
2881 var par = this.par[this.npar];
2882 if (par == undefined) {
2883 par = 0;
2884 }
2885 this.par[this.npar] = 10*par + (ch & 0xF);
2886 break;
c27d0db9 2887 } else if (this.isEsc == 5 /* ESdeviceattr */) {
7460295f
MG
2888 switch (ch) {
2889/*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
2890/*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
2891/*n*/ case 0x6E: /* disable key modifier resource values */ break;
2892/*p*/ case 0x70: /* set pointer mode resource value */ break;
2893 default: break;
2894 }
c27d0db9 2895 this.isEsc = 0 /* ESnormal */;
7460295f
MG
2896 break;
2897 } else {
c27d0db9 2898 this.isEsc = 4 /* ESgotpars */;
7460295f
MG
2899 }
2900 // Fall through
c27d0db9
MG
2901 case 4 /* ESgotpars */:
2902 this.isEsc = 0 /* ESnormal */;
7460295f
MG
2903 if (this.isQuestionMark) {
2904 switch (ch) {
2905/*h*/ case 0x68: this.setMode(true); break;
2906/*l*/ case 0x6C: this.setMode(false); break;
2907/*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
2908 default: break;
2909 }
2910 this.isQuestionMark = false;
2911 break;
2912 }
2913 switch (ch) {
c27d0db9
MG
2914/*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
2915/*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
7460295f
MG
2916/*G*/ case 0x47:
2917/*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
2918/*A*/ case 0x41: this.gotoXY(this.cursorX,
2919 this.cursorY - (this.par[0] ? this.par[0] : 1));
2920 break;
2921/*B*/ case 0x42:
2922/*e*/ case 0x65: this.gotoXY(this.cursorX,
2923 this.cursorY + (this.par[0] ? this.par[0] : 1));
2924 break;
2925/*C*/ case 0x43:
2926/*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
2927 this.cursorY); break;
2928/*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
2929 this.cursorY); break;
2930/*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
2931 break;
2932/*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
2933 break;
2934/*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
2935/*H*/ case 0x48:
2936/*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
2937/*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
2938/*@*/ case 0x40: this.csiAt(this.par[0]); break;
2939/*J*/ case 0x4A: this.csiJ(this.par[0]); break;
2940/*K*/ case 0x4B: this.csiK(this.par[0]); break;
2941/*L*/ case 0x4C: this.csiL(this.par[0]); break;
2942/*M*/ case 0x4D: this.csiM(this.par[0]); break;
2943/*m*/ case 0x6D: this.csim(); break;
2944/*P*/ case 0x50: this.csiP(this.par[0]); break;
2945/*X*/ case 0x58: this.csiX(this.par[0]); break;
2946/*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
2947/*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
2948/*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
2949/*g*/ case 0x67: if (this.par[0] == 0) {
2950 this.userTabStop[this.cursorX] = false;
2951 } else if (this.par[0] == 2 || this.par[0] == 3) {
2952 this.userTabStop = [ ];
2953 for (var i = 0; i < this.terminalWidth; i++) {
2954 this.userTabStop[i] = false;
2955 }
2956 }
2957 break;
2958/*h*/ case 0x68: this.setMode(true); break;
2959/*l*/ case 0x6C: this.setMode(false); break;
2960/*n*/ case 0x6E: switch (this.par[0]) {
2961 case 5: this.statusReport(); break;
2962 case 6: this.cursorReport(); break;
2963 default: break;
2964 }
2965 break;
2966/*q*/ case 0x71: // LED control not implemented
2967 break;
2968/*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
2969 var b = this.par[1] ? this.par[1]
2970 : this.terminalHeight;
2971 if (t < b && b <= this.terminalHeight) {
2972 this.top = t - 1;
2973 this.bottom= b;
2974 this.gotoXaY(0, 0);
2975 }
2976 break;
2977/*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
2978 if (c > this.terminalWidth * this.terminalHeight) {
2979 c = this.terminalWidth * this.terminalHeight;
2980 }
2981 while (c-- > 0) {
2982 lineBuf += this.lastCharacter;
2983 }
2984 break;
2985/*s*/ case 0x73: this.saveCursor(); break;
2986/*u*/ case 0x75: this.restoreCursor(); break;
2987/*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
2988/*]*/ case 0x5D: this.settermCommand(); break;
2989 default: break;
2990 }
2991 break;
c27d0db9 2992 case 12 /* ESbang */:
7460295f
MG
2993 if (ch == 'p') {
2994 this.reset();
2995 }
c27d0db9 2996 this.isEsc = 0 /* ESnormal */;
7460295f 2997 break;
c27d0db9
MG
2998 case 13 /* ESpercent */:
2999 this.isEsc = 0 /* ESnormal */;
7460295f
MG
3000 switch (ch) {
3001/*@*/ case 0x40: this.utfEnabled = false; break;
3002/*G*/ case 0x47:
3003/*8*/ case 0x38: this.utfEnabled = true; break;
3004 default: break;
3005 }
3006 break;
c27d0db9
MG
3007 case 6 /* ESfunckey */:
3008 this.isEsc = 0 /* ESnormal */; break;
3009 case 7 /* EShash */:
3010 this.isEsc = 0 /* ESnormal */;
7460295f
MG
3011/*8*/ if (ch == 0x38) {
3012 // Screen alignment test not implemented
3013 }
3014 break;
c27d0db9
MG
3015 case 8 /* ESsetG0 */:
3016 case 9 /* ESsetG1 */:
3017 case 10 /* ESsetG2 */:
3018 case 11 /* ESsetG3 */:
3019 var g = this.isEsc - 8 /* ESsetG0 */;
3020 this.isEsc = 0 /* ESnormal */;
7460295f
MG
3021 switch (ch) {
3022/*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
3023/*A*/ case 0x42:
3024/*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
3025/*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
3026/*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
3027 default: break;
3028 }
3029 if (this.useGMap == g) {
3030 this.translate = this.GMap[g];
3031 }
3032 break;
c27d0db9 3033 case 17 /* ESstatus */:
7460295f
MG
3034 if (ch == 0x07) {
3035 if (this.statusString && this.statusString.charAt(0) == ';') {
3036 this.statusString = this.statusString.substr(1);
3037 }
3038 try {
3039 window.status = this.statusString;
3040 } catch (e) {
3041 }
c27d0db9 3042 this.isEsc = 0 /* ESnormal */;
7460295f
MG
3043 } else {
3044 this.statusString += String.fromCharCode(ch);
3045 }
3046 break;
c27d0db9
MG
3047 case 18 /* ESss2 */:
3048 case 19 /* ESss3 */:
7460295f 3049 if (ch < 256) {
c27d0db9 3050 ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
7460295f
MG
3051 [this.toggleMeta ? (ch | 0x80) : ch];
3052 if ((ch & 0xFF00) == 0xF000) {
3053 ch = ch & 0xFF;
3054 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
c27d0db9 3055 this.isEsc = 0 /* ESnormal */; break;
7460295f
MG
3056 }
3057 }
3058 this.lastCharacter = String.fromCharCode(ch);
3059 lineBuf += this.lastCharacter;
c27d0db9 3060 this.isEsc = 0 /* ESnormal */; break;
7460295f 3061 default:
c27d0db9 3062 this.isEsc = 0 /* ESnormal */; break;
7460295f
MG
3063 }
3064 break;
3065 }
3066 return lineBuf;
3067};
3068
3069VT100.prototype.renderString = function(s, showCursor) {
3070 // We try to minimize the number of DOM operations by coalescing individual
3071 // characters into strings. This is a significant performance improvement.
3072 var incX = s.length;
3073 if (incX > this.terminalWidth - this.cursorX) {
3074 incX = this.terminalWidth - this.cursorX;
3075 if (incX <= 0) {
3076 return;
3077 }
3078 s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
3079 }
3080 if (showCursor) {
3081 // Minimize the number of calls to putString(), by avoiding a direct
3082 // call to this.showCursor()
3083 this.cursor.style.visibility = '';
3084 }
3085 this.putString(this.cursorX, this.cursorY, s, this.style);
3086};
3087
3088VT100.prototype.vt100 = function(s) {
57e76178 3089 this.cursorNeedsShowing = this.hideCursor();
7460295f
MG
3090 this.respondString = '';
3091 var lineBuf = '';
3092 for (var i = 0; i < s.length; i++) {
3093 var ch = s.charCodeAt(i);
3094 if (this.utfEnabled) {
3095 // Decode UTF8 encoded character
3096 if (ch > 0x7F) {
3097 if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
3098 this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
3099 if (--this.utfCount <= 0) {
3100 if (this.utfChar > 0xFFFF || this.utfChar < 0) {
3101 ch = 0xFFFD;
3102 } else {
3103 ch = this.utfChar;
3104 }
3105 } else {
3106 continue;
3107 }
3108 } else {
3109 if ((ch & 0xE0) == 0xC0) {
3110 this.utfCount = 1;
3111 this.utfChar = ch & 0x1F;
3112 } else if ((ch & 0xF0) == 0xE0) {
3113 this.utfCount = 2;
3114 this.utfChar = ch & 0x0F;
3115 } else if ((ch & 0xF8) == 0xF0) {
3116 this.utfCount = 3;
3117 this.utfChar = ch & 0x07;
3118 } else if ((ch & 0xFC) == 0xF8) {
3119 this.utfCount = 4;
3120 this.utfChar = ch & 0x03;
3121 } else if ((ch & 0xFE) == 0xFC) {
3122 this.utfCount = 5;
3123 this.utfChar = ch & 0x01;
3124 } else {
3125 this.utfCount = 0;
3126 }
3127 continue;
3128 }
3129 } else {
3130 this.utfCount = 0;
3131 }
3132 }
3133 var isNormalCharacter =
3134 (ch >= 32 && ch <= 127 || ch >= 160 ||
3135 this.utfEnabled && ch >= 128 ||
3136 !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
3137 (ch != 0x7F || this.dispCtrl);
3138
c27d0db9 3139 if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
7460295f
MG
3140 if (ch < 256) {
3141 ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
3142 }
3143 if ((ch & 0xFF00) == 0xF000) {
3144 ch = ch & 0xFF;
3145 } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
3146 continue;
3147 }
3148 if (this.needWrap || this.insertMode) {
3149 if (lineBuf) {
3150 this.renderString(lineBuf);
3151 lineBuf = '';
3152 }
3153 }
3154 if (this.needWrap) {
3155 this.cr(); this.lf();
3156 }
3157 if (this.insertMode) {
3158 this.scrollRegion(this.cursorX, this.cursorY,
3159 this.terminalWidth - this.cursorX - 1, 1,
3160 1, 0, this.style);
3161 }
3162 this.lastCharacter = String.fromCharCode(ch);
3163 lineBuf += this.lastCharacter;
3164 if (this.cursorX + lineBuf.length >= this.terminalWidth) {
3165 this.needWrap = this.autoWrapMode;
3166 }
3167 } else {
3168 if (lineBuf) {
3169 this.renderString(lineBuf);
3170 lineBuf = '';
3171 }
3172 var expand = this.doControl(ch);
3173 if (expand.length) {
3174 var r = this.respondString;
3175 this.respondString= r + this.vt100(expand);
3176 }
3177 }
3178 }
3179 if (lineBuf) {
57e76178
MG
3180 this.renderString(lineBuf, this.cursorNeedsShowing);
3181 } else if (this.cursorNeedsShowing) {
7460295f
MG
3182 this.showCursor();
3183 }
3184 return this.respondString;
3185};
3186
3187VT100.prototype.Latin1Map = [
31880x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
31890x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
31900x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
31910x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
31920x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
31930x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
31940x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
31950x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
31960x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
31970x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
31980x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
31990x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
32000x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
32010x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
32020x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
32030x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
32040x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
32050x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
32060x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
32070x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
32080x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
32090x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
32100x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
32110x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
32120x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
32130x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
32140x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
32150x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
32160x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
32170x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
32180x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
32190x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3220];
3221
3222VT100.prototype.VT100GraphicsMap = [
32230x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
32240x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
32250x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
32260x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
32270x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
32280x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
32290x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
32300x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
32310x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
32320x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
32330x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
32340x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
32350x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
32360x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
32370xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
32380x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
32390x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
32400x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
32410x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
32420x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
32430x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
32440x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
32450x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
32460x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
32470x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
32480x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
32490x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
32500x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
32510x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
32520x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
32530x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
32540x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
3255];
3256
3257VT100.prototype.CodePage437Map = [
32580x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
32590x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
32600x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
32610x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
32620x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
32630x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
32640x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
32650x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
32660x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
32670x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
32680x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
32690x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
32700x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
32710x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
32720x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
32730x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
32740x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
32750x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
32760x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
32770x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
32780x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
32790x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
32800x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
32810x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
32820x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
32830x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
32840x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
32850x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
32860x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
32870x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
32880x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
32890x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
3290];
3291
3292VT100.prototype.DirectToFontMap = [
32930xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
32940xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
32950xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
32960xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
32970xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
32980xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
32990xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
33000xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
33010xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
33020xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
33030xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
33040xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
33050xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
33060xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
33070xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
33080xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
33090xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
33100xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
33110xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
33120xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
33130xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
33140xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
33150xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
33160xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
33170xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
33180xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
33190xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
33200xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
33210xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
33220xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
33230xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
33240xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
3325];
3326
3327VT100.prototype.ctrlAction = [
3328 true, false, false, false, false, false, false, true,
3329 true, true, true, true, true, true, true, true,
3330 false, false, false, false, false, false, false, false,
3331 true, false, true, true, false, false, false, false
3332];
3333
3334VT100.prototype.ctrlAlways = [
3335 true, false, false, false, false, false, false, false,
3336 true, false, true, false, true, true, true, true,
3337 false, false, false, false, false, false, false, false,
3338 false, false, false, true, false, false, false, false
3339];
3340
This page took 0.511413 seconds and 5 git commands to generate.