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