"Fossies" - the Fresh Open Source Software Archive

Member "shellinabox-2.20/shellinabox/vt100.jspp" (9 Nov 2016, 178301 Bytes) of package /linux/privat/shellinabox-2.20.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "vt100.jspp": 2.19_vs_2.20.

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