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