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