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