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