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