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