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