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