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