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