Home | History | Annotate | Download | only in include
      1 /*
      2  * noVNC: HTML5 VNC client
      3  * Copyright (C) 2012 Joel Martin
      4  * Copyright (C) 2013 Samuel Mannehed for Cendio AB
      5  * Licensed under MPL 2.0 (see LICENSE.txt)
      6  *
      7  * See README.md for usage and integration instructions.
      8  *
      9  * TIGHT decoder portion:
     10  * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
     11  */
     12 
     13 /*jslint white: false, browser: true */
     14 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
     15 
     16 var RFB;
     17 
     18 (function () {
     19     "use strict";
     20     RFB = function (defaults) {
     21         if (!defaults) {
     22             defaults = {};
     23         }
     24 
     25         this._rfb_host = '';
     26         this._rfb_port = 5900;
     27         this._rfb_password = '';
     28         this._rfb_path = '';
     29 
     30         this._rfb_state = 'disconnected';
     31         this._rfb_version = 0;
     32         this._rfb_max_version = 3.8;
     33         this._rfb_auth_scheme = '';
     34 
     35         this._rfb_tightvnc = false;
     36         this._rfb_xvp_ver = 0;
     37 
     38         // In preference order
     39         this._encodings = [
     40             ['COPYRECT',         0x01 ],
     41             ['TIGHT',            0x07 ],
     42             ['TIGHT_PNG',        -260 ],
     43             ['HEXTILE',          0x05 ],
     44             ['RRE',              0x02 ],
     45             ['RAW',              0x00 ],
     46             ['DesktopSize',      -223 ],
     47             ['Cursor',           -239 ],
     48 
     49             // Psuedo-encoding settings
     50             //['JPEG_quality_lo',   -32 ],
     51             ['JPEG_quality_med',    -26 ],
     52             //['JPEG_quality_hi',   -23 ],
     53             //['compress_lo',      -255 ],
     54             ['compress_hi',        -247 ],
     55             ['last_rect',          -224 ],
     56             ['xvp',                -309 ]
     57         ];
     58 
     59         this._encHandlers = {};
     60         this._encNames = {};
     61         this._encStats = {};
     62 
     63         this._sock = null;              // Websock object
     64         this._display = null;           // Display object
     65         this._keyboard = null;          // Keyboard input handler object
     66         this._mouse = null;             // Mouse input handler object
     67         this._sendTimer = null;         // Send Queue check timer
     68         this._disconnTimer = null;      // disconnection timer
     69         this._msgTimer = null;          // queued handle_msg timer
     70 
     71         // Frame buffer update state
     72         this._FBU = {
     73             rects: 0,
     74             subrects: 0,            // RRE
     75             lines: 0,               // RAW
     76             tiles: 0,               // HEXTILE
     77             bytes: 0,
     78             x: 0,
     79             y: 0,
     80             width: 0,
     81             height: 0,
     82             encoding: 0,
     83             subencoding: -1,
     84             background: null,
     85             zlib: []                // TIGHT zlib streams
     86         };
     87 
     88         this._fb_Bpp = 4;
     89         this._fb_depth = 3;
     90         this._fb_width = 0;
     91         this._fb_height = 0;
     92         this._fb_name = "";
     93 
     94         this._rre_chunk_sz = 100;
     95 
     96         this._timing = {
     97             last_fbu: 0,
     98             fbu_total: 0,
     99             fbu_total_cnt: 0,
    100             full_fbu_total: 0,
    101             full_fbu_cnt: 0,
    102 
    103             fbu_rt_start: 0,
    104             fbu_rt_total: 0,
    105             fbu_rt_cnt: 0,
    106             pixels: 0
    107         };
    108 
    109         // Mouse state
    110         this._mouse_buttonMask = 0;
    111         this._mouse_arr = [];
    112         this._viewportDragging = false;
    113         this._viewportDragPos = {};
    114 
    115         // set the default value on user-facing properties
    116         Util.set_defaults(this, defaults, {
    117             'target': 'null',                       // VNC display rendering Canvas object
    118             'focusContainer': document,             // DOM element that captures keyboard input
    119             'encrypt': false,                       // Use TLS/SSL/wss encryption
    120             'true_color': true,                     // Request true color pixel data
    121             'local_cursor': false,                  // Request locally rendered cursor
    122             'shared': true,                         // Request shared mode
    123             'view_only': false,                     // Disable client mouse/keyboard
    124             'xvp_password_sep': '@',                // Separator for XVP password fields
    125             'disconnectTimeout': 3,                 // Time (s) to wait for disconnection
    126             'wsProtocols': ['binary', 'base64'],    // Protocols to use in the WebSocket connection
    127             'repeaterID': '',                       // [UltraVNC] RepeaterID to connect to
    128             'viewportDrag': false,                  // Move the viewport on mouse drags
    129 
    130             // Callback functions
    131             'onUpdateState': function () { },       // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
    132             'onPasswordRequired': function () { },  // onPasswordRequired(rfb): VNC password is required
    133             'onClipboard': function () { },         // onClipboard(rfb, text): RFB clipboard contents received
    134             'onBell': function () { },              // onBell(rfb): RFB Bell message received
    135             'onFBUReceive': function () { },        // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
    136             'onFBUComplete': function () { },       // onFBUComplete(rfb, fbu): RFB FBU received and processed
    137             'onFBResize': function () { },          // onFBResize(rfb, width, height): frame buffer resized
    138             'onDesktopName': function () { },       // onDesktopName(rfb, name): desktop name received
    139             'onXvpInit': function () { },           // onXvpInit(version): XVP extensions active for this connection
    140         });
    141 
    142         // main setup
    143         Util.Debug(">> RFB.constructor");
    144 
    145         // populate encHandlers with bound versions
    146         Object.keys(RFB.encodingHandlers).forEach(function (encName) {
    147             this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
    148         }.bind(this));
    149 
    150         // Create lookup tables based on encoding number
    151         for (var i = 0; i < this._encodings.length; i++) {
    152             this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
    153             this._encNames[this._encodings[i][1]] = this._encodings[i][0];
    154             this._encStats[this._encodings[i][1]] = [0, 0];
    155         }
    156 
    157         try {
    158             this._display = new Display({target: this._target});
    159         } catch (exc) {
    160             Util.Error("Display exception: " + exc);
    161             this._updateState('fatal', "No working Display");
    162         }
    163 
    164         this._keyboard = new Keyboard({target: this._focusContainer,
    165                                        onKeyPress: this._handleKeyPress.bind(this)});
    166 
    167         this._mouse = new Mouse({target: this._target,
    168                                  onMouseButton: this._handleMouseButton.bind(this),
    169                                  onMouseMove: this._handleMouseMove.bind(this),
    170                                  notify: this._keyboard.sync.bind(this._keyboard)});
    171 
    172         this._sock = new Websock();
    173         this._sock.on('message', this._handle_message.bind(this));
    174         this._sock.on('open', function () {
    175             if (this._rfb_state === 'connect') {
    176                 this._updateState('ProtocolVersion', "Starting VNC handshake");
    177             } else {
    178                 this._fail("Got unexpected WebSocket connection");
    179             }
    180         }.bind(this));
    181         this._sock.on('close', function (e) {
    182             Util.Warn("WebSocket on-close event");
    183             var msg = "";
    184             if (e.code) {
    185                 msg = " (code: " + e.code;
    186                 if (e.reason) {
    187                     msg += ", reason: " + e.reason;
    188                 }
    189                 msg += ")";
    190             }
    191             if (this._rfb_state === 'disconnect') {
    192                 this._updateState('disconnected', 'VNC disconnected' + msg);
    193             } else if (this._rfb_state === 'ProtocolVersion') {
    194                 this._fail('Failed to connect to server' + msg);
    195             } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
    196                 Util.Error("Received onclose while disconnected" + msg);
    197             } else {
    198                 this._fail("Server disconnected" + msg);
    199             }
    200         }.bind(this));
    201         this._sock.on('error', function (e) {
    202             Util.Warn("WebSocket on-error event");
    203         });
    204 
    205         this._init_vars();
    206 
    207         var rmode = this._display.get_render_mode();
    208         if (Websock_native) {
    209             Util.Info("Using native WebSockets");
    210             this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
    211         } else {
    212             Util.Warn("Using web-socket-js bridge.  Flash version: " + Util.Flash.version);
    213             if (!Util.Flash || Util.Flash.version < 9) {
    214                 this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
    215             } else if (document.location.href.substr(0, 7) === 'file://') {
    216                 this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash");
    217             } else {
    218                 this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
    219             }
    220         }
    221 
    222         Util.Debug("<< RFB.constructor");
    223     };
    224 
    225     RFB.prototype = {
    226         // Public methods
    227         connect: function (host, port, password, path) {
    228             this._rfb_host = host;
    229             this._rfb_port = port;
    230             this._rfb_password = (password !== undefined) ? password : "";
    231             this._rfb_path = (path !== undefined) ? path : "";
    232 
    233             if (!this._rfb_host || !this._rfb_port) {
    234                 return this._fail("Must set host and port");
    235             }
    236 
    237             this._updateState('connect');
    238         },
    239 
    240         disconnect: function () {
    241             this._updateState('disconnect', 'Disconnecting');
    242         },
    243 
    244         sendPassword: function (passwd) {
    245             this._rfb_password = passwd;
    246             this._rfb_state = 'Authentication';
    247             setTimeout(this._init_msg.bind(this), 1);
    248         },
    249 
    250         sendCtrlAltDel: function () {
    251             if (this._rfb_state !== 'normal' || this._view_only) { return false; }
    252             Util.Info("Sending Ctrl-Alt-Del");
    253 
    254             var arr = [];
    255             arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 1)); // Control
    256             arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 1)); // Alt
    257             arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 1)); // Delete
    258             arr = arr.concat(RFB.messages.keyEvent(0xFFFF, 0)); // Delete
    259             arr = arr.concat(RFB.messages.keyEvent(0xFFE9, 0)); // Alt
    260             arr = arr.concat(RFB.messages.keyEvent(0xFFE3, 0)); // Control
    261             this._sock.send(arr);
    262         },
    263 
    264         xvpOp: function (ver, op) {
    265             if (this._rfb_xvp_ver < ver) { return false; }
    266             Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
    267             this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
    268             return true;
    269         },
    270 
    271         xvpShutdown: function () {
    272             return this.xvpOp(1, 2);
    273         },
    274 
    275         xvpReboot: function () {
    276             return this.xvpOp(1, 3);
    277         },
    278 
    279         xvpReset: function () {
    280             return this.xvpOp(1, 4);
    281         },
    282 
    283         // Send a key press. If 'down' is not specified then send a down key
    284         // followed by an up key.
    285         sendKey: function (code, down) {
    286             if (this._rfb_state !== "normal" || this._view_only) { return false; }
    287             var arr = [];
    288             if (typeof down !== 'undefined') {
    289                 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
    290                 arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0));
    291             } else {
    292                 Util.Info("Sending key code (down + up): " + code);
    293                 arr = arr.concat(RFB.messages.keyEvent(code, 1));
    294                 arr = arr.concat(RFB.messages.keyEvent(code, 0));
    295             }
    296             this._sock.send(arr);
    297         },
    298 
    299         clipboardPasteFrom: function (text) {
    300             if (this._rfb_state !== 'normal') { return; }
    301             this._sock.send(RFB.messages.clientCutText(text));
    302         },
    303 
    304         // Private methods
    305 
    306         _connect: function () {
    307             Util.Debug(">> RFB.connect");
    308 
    309             var uri;
    310             if (typeof UsingSocketIO !== 'undefined') {
    311                 uri = 'http';
    312             } else {
    313                 uri = this._encrypt ? 'wss' : 'ws';
    314             }
    315 
    316             uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
    317             Util.Info("connecting to " + uri);
    318 
    319             this._sock.open(uri, this._wsProtocols);
    320 
    321             Util.Debug("<< RFB.connect");
    322         },
    323 
    324         _init_vars: function () {
    325             // reset state
    326             this._sock.init();
    327 
    328             this._FBU.rects        = 0;
    329             this._FBU.subrects     = 0;  // RRE and HEXTILE
    330             this._FBU.lines        = 0;  // RAW
    331             this._FBU.tiles        = 0;  // HEXTILE
    332             this._FBU.zlibs        = []; // TIGHT zlib encoders
    333             this._mouse_buttonMask = 0;
    334             this._mouse_arr        = [];
    335             this._rfb_tightvnc     = false;
    336 
    337             // Clear the per connection encoding stats
    338             var i;
    339             for (i = 0; i < this._encodings.length; i++) {
    340                 this._encStats[this._encodings[i][1]][0] = 0;
    341             }
    342 
    343             for (i = 0; i < 4; i++) {
    344                 this._FBU.zlibs[i] = new TINF();
    345                 this._FBU.zlibs[i].init();
    346             }
    347         },
    348 
    349         _print_stats: function () {
    350             Util.Info("Encoding stats for this connection:");
    351             var i, s;
    352             for (i = 0; i < this._encodings.length; i++) {
    353                 s = this._encStats[this._encodings[i][1]];
    354                 if (s[0] + s[1] > 0) {
    355                     Util.Info("    " + this._encodings[i][0] + ": " + s[0] + " rects");
    356                 }
    357             }
    358 
    359             Util.Info("Encoding stats since page load:");
    360             for (i = 0; i < this._encodings.length; i++) {
    361                 s = this._encStats[this._encodings[i][1]];
    362                 Util.Info("    " + this._encodings[i][0] + ": " + s[1] + " rects");
    363             }
    364         },
    365 
    366 
    367         /*
    368          * Page states:
    369          *   loaded       - page load, equivalent to disconnected
    370          *   disconnected - idle state
    371          *   connect      - starting to connect (to ProtocolVersion)
    372          *   normal       - connected
    373          *   disconnect   - starting to disconnect
    374          *   failed       - abnormal disconnect
    375          *   fatal        - failed to load page, or fatal error
    376          *
    377          * RFB protocol initialization states:
    378          *   ProtocolVersion
    379          *   Security
    380          *   Authentication
    381          *   password     - waiting for password, not part of RFB
    382          *   SecurityResult
    383          *   ClientInitialization - not triggered by server message
    384          *   ServerInitialization (to normal)
    385          */
    386         _updateState: function (state, statusMsg) {
    387             var oldstate = this._rfb_state;
    388 
    389             if (state === oldstate) {
    390                 // Already here, ignore
    391                 Util.Debug("Already in state '" + state + "', ignoring");
    392             }
    393 
    394             /*
    395              * These are disconnected states. A previous connect may
    396              * asynchronously cause a connection so make sure we are closed.
    397              */
    398             if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
    399                           'disconnect': 1, 'failed': 1, 'fatal': 1}) {
    400 
    401                 if (this._sendTimer) {
    402                     clearInterval(this._sendTimer);
    403                     this._sendTimer = null;
    404                 }
    405 
    406                 if (this._msgTimer) {
    407                     clearInterval(this._msgTimer);
    408                     this._msgTimer = null;
    409                 }
    410 
    411                 if (this._display && this._display.get_context()) {
    412                     this._keyboard.ungrab();
    413                     this._mouse.ungrab();
    414                     this._display.defaultCursor();
    415                     if (Util.get_logging() !== 'debug' || state === 'loaded') {
    416                         // Show noVNC logo on load and when disconnected, unless in
    417                         // debug mode
    418                         this._display.clear();
    419                     }
    420                 }
    421 
    422                 this._sock.close();
    423             }
    424 
    425             if (oldstate === 'fatal') {
    426                 Util.Error('Fatal error, cannot continue');
    427             }
    428 
    429             var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
    430             var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
    431             if (state === 'failed' || state === 'fatal') {
    432                 Util.Error(cmsg);
    433             } else {
    434                 Util.Warn(cmsg);
    435             }
    436 
    437             if (oldstate === 'failed' && state === 'disconnected') {
    438                 // do disconnect action, but stay in failed state
    439                 this._rfb_state = 'failed';
    440             } else {
    441                 this._rfb_state = state;
    442             }
    443 
    444             if (this._disconnTimer && this._rfb_state !== 'disconnect') {
    445                 Util.Debug("Clearing disconnect timer");
    446                 clearTimeout(this._disconnTimer);
    447                 this._disconnTimer = null;
    448             }
    449 
    450             switch (state) {
    451                 case 'normal':
    452                     if (oldstate === 'disconnected' || oldstate === 'failed') {
    453                         Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
    454                     }
    455                     break;
    456 
    457                 case 'connect':
    458                     this._init_vars();
    459                     this._connect();
    460                     // WebSocket.onopen transitions to 'ProtocolVersion'
    461                     break;
    462 
    463                 case 'disconnect':
    464                     this._disconnTimer = setTimeout(function () {
    465                         this._fail("Disconnect timeout");
    466                     }.bind(this), this._disconnectTimeout * 1000);
    467 
    468                     this._print_stats();
    469 
    470                     // WebSocket.onclose transitions to 'disconnected'
    471                     break;
    472 
    473                 case 'failed':
    474                     if (oldstate === 'disconnected') {
    475                         Util.Error("Invalid transition from 'disconnected' to 'failed'");
    476                     } else if (oldstate === 'normal') {
    477                         Util.Error("Error while connected.");
    478                     } else if (oldstate === 'init') {
    479                         Util.Error("Error while initializing.");
    480                     }
    481 
    482                     // Make sure we transition to disconnected
    483                     setTimeout(function () {
    484                         this._updateState('disconnected');
    485                     }.bind(this), 50);
    486 
    487                     break;
    488 
    489                 default:
    490                     // No state change action to take
    491             }
    492 
    493             if (oldstate === 'failed' && state === 'disconnected') {
    494                 this._onUpdateState(this, state, oldstate);
    495             } else {
    496                 this._onUpdateState(this, state, oldstate, statusMsg);
    497             }
    498         },
    499 
    500         _fail: function (msg) {
    501             this._updateState('failed', msg);
    502             return false;
    503         },
    504 
    505         _handle_message: function () {
    506             if (this._sock.rQlen() === 0) {
    507                 Util.Warn("handle_message called on an empty receive queue");
    508                 return;
    509             }
    510 
    511             switch (this._rfb_state) {
    512                 case 'disconnected':
    513                 case 'failed':
    514                     Util.Error("Got data while disconnected");
    515                     break;
    516                 case 'normal':
    517                     if (this._normal_msg() && this._sock.rQlen() > 0) {
    518                         // true means we can continue processing
    519                         // Give other events a chance to run
    520                         if (this._msgTimer === null) {
    521                             Util.Debug("More data to process, creating timer");
    522                             this._msgTimer = setTimeout(function () {
    523                                 this._msgTimer = null;
    524                                 this._handle_message();
    525                             }.bind(this), 10);
    526                         } else {
    527                             Util.Debug("More data to process, existing timer");
    528                         }
    529                     }
    530                     break;
    531                 default:
    532                     this._init_msg();
    533                     break;
    534             }
    535         },
    536 
    537         _checkEvents: function () {
    538             if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
    539                 this._sock.send(this._mouse_arr);
    540                 this._mouse_arr = [];
    541             }
    542         },
    543 
    544         _handleKeyPress: function (keysym, down) {
    545             if (this._view_only) { return; } // View only, skip keyboard, events
    546             this._sock.send(RFB.messages.keyEvent(keysym, down));
    547         },
    548 
    549         _handleMouseButton: function (x, y, down, bmask) {
    550             if (down) {
    551                 this._mouse_buttonMask |= bmask;
    552             } else {
    553                 this._mouse_buttonMask ^= bmask;
    554             }
    555 
    556             if (this._viewportDrag) {
    557                 if (down && !this._viewportDragging) {
    558                     this._viewportDragging = true;
    559                     this._viewportDragPos = {'x': x, 'y': y};
    560 
    561                     // Skip sending mouse events
    562                     return;
    563                 } else {
    564                     this._viewportDragging = false;
    565                 }
    566             }
    567 
    568             if (this._view_only) { return; } // View only, skip mouse events
    569 
    570             this._mouse_arr = this._mouse_arr.concat(
    571                     RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
    572             this._sock.send(this._mouse_arr);
    573             this._mouse_arr = [];
    574         },
    575 
    576         _handleMouseMove: function (x, y) {
    577             if (this._viewportDragging) {
    578                 var deltaX = this._viewportDragPos.x - x;
    579                 var deltaY = this._viewportDragPos.y - y;
    580                 this._viewportDragPos = {'x': x, 'y': y};
    581 
    582                 this._display.viewportChange(deltaX, deltaY);
    583 
    584                 // Skip sending mouse events
    585                 return;
    586             }
    587 
    588             if (this._view_only) { return; } // View only, skip mouse events
    589 
    590             this._mouse_arr = this._mouse_arr.concat(
    591                     RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
    592 
    593             this._checkEvents();
    594         },
    595 
    596         // Message Handlers
    597 
    598         _negotiate_protocol_version: function () {
    599             if (this._sock.rQlen() < 12) {
    600                 return this._fail("Incomplete protocol version");
    601             }
    602 
    603             var sversion = this._sock.rQshiftStr(12).substr(4, 7);
    604             Util.Info("Server ProtocolVersion: " + sversion);
    605             var is_repeater = 0;
    606             switch (sversion) {
    607                 case "000.000":  // UltraVNC repeater
    608                     is_repeater = 1;
    609                     break;
    610                 case "003.003":
    611                 case "003.006":  // UltraVNC
    612                 case "003.889":  // Apple Remote Desktop
    613                     this._rfb_version = 3.3;
    614                     break;
    615                 case "003.007":
    616                     this._rfb_version = 3.7;
    617                     break;
    618                 case "003.008":
    619                 case "004.000":  // Intel AMT KVM
    620                 case "004.001":  // RealVNC 4.6
    621                     this._rfb_version = 3.8;
    622                     break;
    623                 default:
    624                     return this._fail("Invalid server version " + sversion);
    625             }
    626 
    627             if (is_repeater) {
    628                 var repeaterID = this._repeaterID;
    629                 while (repeaterID.length < 250) {
    630                     repeaterID += "\0";
    631                 }
    632                 this._sock.send_string(repeaterID);
    633                 return true;
    634             }
    635 
    636             if (this._rfb_version > this._rfb_max_version) {
    637                 this._rfb_version = this._rfb_max_version;
    638             }
    639 
    640             // Send updates either at a rate of 1 update per 50ms, or
    641             // whatever slower rate the network can handle
    642             this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
    643 
    644             var cversion = "00" + parseInt(this._rfb_version, 10) +
    645                            ".00" + ((this._rfb_version * 10) % 10);
    646             this._sock.send_string("RFB " + cversion + "\n");
    647             this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
    648         },
    649 
    650         _negotiate_security: function () {
    651             if (this._rfb_version >= 3.7) {
    652                 // Server sends supported list, client decides
    653                 var num_types = this._sock.rQshift8();
    654                 if (this._sock.rQwait("security type", num_types, 1)) { return false; }
    655 
    656                 if (num_types === 0) {
    657                     var strlen = this._sock.rQshift32();
    658                     var reason = this._sock.rQshiftStr(strlen);
    659                     return this._fail("Security failure: " + reason);
    660                 }
    661 
    662                 this._rfb_auth_scheme = 0;
    663                 var types = this._sock.rQshiftBytes(num_types);
    664                 Util.Debug("Server security types: " + types);
    665                 for (var i = 0; i < types.length; i++) {
    666                     if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
    667                         this._rfb_auth_scheme = types[i];
    668                     }
    669                 }
    670 
    671                 if (this._rfb_auth_scheme === 0) {
    672                     return this._fail("Unsupported security types: " + types);
    673                 }
    674 
    675                 this._sock.send([this._rfb_auth_scheme]);
    676             } else {
    677                 // Server decides
    678                 if (this._sock.rQwait("security scheme", 4)) { return false; }
    679                 this._rfb_auth_scheme = this._sock.rQshift32();
    680             }
    681 
    682             this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
    683             return this._init_msg(); // jump to authentication
    684         },
    685 
    686         // authentication
    687         _negotiate_xvp_auth: function () {
    688             var xvp_sep = this._xvp_password_sep;
    689             var xvp_auth = this._rfb_password.split(xvp_sep);
    690             if (xvp_auth.length < 3) {
    691                 this._updateState('password', 'XVP credentials required (user' + xvp_sep +
    692                                   'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
    693                 this._onPasswordRequired(this);
    694                 return false;
    695             }
    696 
    697             var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
    698                                String.fromCharCode(xvp_auth[1].length) +
    699                                xvp_auth[0] +
    700                                xvp_auth[1];
    701             this._sock.send_string(xvp_auth_str);
    702             this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
    703             this._rfb_auth_scheme = 2;
    704             return this._negotiate_authentication();
    705         },
    706 
    707         _negotiate_std_vnc_auth: function () {
    708             if (this._rfb_password.length === 0) {
    709                 // Notify via both callbacks since it's kind of
    710                 // an RFB state change and a UI interface issue
    711                 this._updateState('password', "Password Required");
    712                 this._onPasswordRequired(this);
    713             }
    714 
    715             if (this._sock.rQwait("auth challenge", 16)) { return false; }
    716 
    717             var challenge = this._sock.rQshiftBytes(16);
    718             var response = RFB.genDES(this._rfb_password, challenge);
    719             this._sock.send(response);
    720             this._updateState("SecurityResult");
    721             return true;
    722         },
    723 
    724         _negotiate_tight_tunnels: function (numTunnels) {
    725             var clientSupportedTunnelTypes = {
    726                 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
    727             };
    728             var serverSupportedTunnelTypes = {};
    729             // receive tunnel capabilities
    730             for (var i = 0; i < numTunnels; i++) {
    731                 var cap_code = this._sock.rQshift32();
    732                 var cap_vendor = this._sock.rQshiftStr(4);
    733                 var cap_signature = this._sock.rQshiftStr(8);
    734                 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
    735             }
    736 
    737             // choose the notunnel type
    738             if (serverSupportedTunnelTypes[0]) {
    739                 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
    740                     serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
    741                     return this._fail("Client's tunnel type had the incorrect vendor or signature");
    742                 }
    743                 this._sock.send([0, 0, 0, 0]);  // use NOTUNNEL
    744                 return false; // wait until we receive the sub auth count to continue
    745             } else {
    746                 return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
    747             }
    748         },
    749 
    750         _negotiate_tight_auth: function () {
    751             if (!this._rfb_tightvnc) {  // first pass, do the tunnel negotiation
    752                 if (this._sock.rQwait("num tunnels", 4)) { return false; }
    753                 var numTunnels = this._sock.rQshift32();
    754                 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
    755 
    756                 this._rfb_tightvnc = true;
    757 
    758                 if (numTunnels > 0) {
    759                     this._negotiate_tight_tunnels(numTunnels);
    760                     return false;  // wait until we receive the sub auth to continue
    761                 }
    762             }
    763 
    764             // second pass, do the sub-auth negotiation
    765             if (this._sock.rQwait("sub auth count", 4)) { return false; }
    766             var subAuthCount = this._sock.rQshift32();
    767             if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
    768 
    769             var clientSupportedTypes = {
    770                 'STDVNOAUTH__': 1,
    771                 'STDVVNCAUTH_': 2
    772             };
    773 
    774             var serverSupportedTypes = [];
    775 
    776             for (var i = 0; i < subAuthCount; i++) {
    777                 var capNum = this._sock.rQshift32();
    778                 var capabilities = this._sock.rQshiftStr(12);
    779                 serverSupportedTypes.push(capabilities);
    780             }
    781 
    782             for (var authType in clientSupportedTypes) {
    783                 if (serverSupportedTypes.indexOf(authType) != -1) {
    784                     this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
    785 
    786                     switch (authType) {
    787                         case 'STDVNOAUTH__':  // no auth
    788                             this._updateState('SecurityResult');
    789                             return true;
    790                         case 'STDVVNCAUTH_': // VNC auth
    791                             this._rfb_auth_scheme = 2;
    792                             return this._init_msg();
    793                         default:
    794                             return this._fail("Unsupported tiny auth scheme: " + authType);
    795                     }
    796                 }
    797             }
    798 
    799             this._fail("No supported sub-auth types!");
    800         },
    801 
    802         _negotiate_authentication: function () {
    803             switch (this._rfb_auth_scheme) {
    804                 case 0:  // connection failed
    805                     if (this._sock.rQwait("auth reason", 4)) { return false; }
    806                     var strlen = this._sock.rQshift32();
    807                     var reason = this._sock.rQshiftStr(strlen);
    808                     return this._fail("Auth failure: " + reason);
    809 
    810                 case 1:  // no auth
    811                     if (this._rfb_version >= 3.8) {
    812                         this._updateState('SecurityResult');
    813                         return true;
    814                     }
    815                     this._updateState('ClientInitialisation', "No auth required");
    816                     return this._init_msg();
    817 
    818                 case 22:  // XVP auth
    819                     return this._negotiate_xvp_auth();
    820 
    821                 case 2:  // VNC authentication
    822                     return this._negotiate_std_vnc_auth();
    823 
    824                 case 16:  // TightVNC Security Type
    825                     return this._negotiate_tight_auth();
    826 
    827                 default:
    828                     return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
    829             }
    830         },
    831 
    832         _handle_security_result: function () {
    833             if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
    834             switch (this._sock.rQshift32()) {
    835                 case 0:  // OK
    836                     this._updateState('ClientInitialisation', 'Authentication OK');
    837                     return this._init_msg();
    838                 case 1:  // failed
    839                     if (this._rfb_version >= 3.8) {
    840                         var length = this._sock.rQshift32();
    841                         if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
    842                         var reason = this._sock.rQshiftStr(length);
    843                         return this._fail(reason);
    844                     } else {
    845                         return this._fail("Authentication failure");
    846                     }
    847                     return false;
    848                 case 2:
    849                     return this._fail("Too many auth attempts");
    850             }
    851         },
    852 
    853         _negotiate_server_init: function () {
    854             if (this._sock.rQwait("server initialization", 24)) { return false; }
    855 
    856             /* Screen size */
    857             this._fb_width  = this._sock.rQshift16();
    858             this._fb_height = this._sock.rQshift16();
    859 
    860             /* PIXEL_FORMAT */
    861             var bpp         = this._sock.rQshift8();
    862             var depth       = this._sock.rQshift8();
    863             var big_endian  = this._sock.rQshift8();
    864             var true_color  = this._sock.rQshift8();
    865 
    866             var red_max     = this._sock.rQshift16();
    867             var green_max   = this._sock.rQshift16();
    868             var blue_max    = this._sock.rQshift16();
    869             var red_shift   = this._sock.rQshift8();
    870             var green_shift = this._sock.rQshift8();
    871             var blue_shift  = this._sock.rQshift8();
    872             this._sock.rQskipBytes(3);  // padding
    873 
    874             // NB(directxman12): we don't want to call any callbacks or print messages until
    875             //                   *after* we're past the point where we could backtrack
    876 
    877             /* Connection name/title */
    878             var name_length = this._sock.rQshift32();
    879             if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
    880             this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
    881 
    882             if (this._rfb_tightvnc) {
    883                 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
    884                 // In TightVNC mode, ServerInit message is extended
    885                 var numServerMessages = this._sock.rQshift16();
    886                 var numClientMessages = this._sock.rQshift16();
    887                 var numEncodings = this._sock.rQshift16();
    888                 this._sock.rQskipBytes(2);  // padding
    889 
    890                 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
    891                 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
    892 
    893                 var i;
    894                 for (i = 0; i < numServerMessages; i++) {
    895                     var srvMsg = this._sock.rQshiftStr(16);
    896                 }
    897 
    898                 for (i = 0; i < numClientMessages; i++) {
    899                     var clientMsg = this._sock.rQshiftStr(16);
    900                 }
    901 
    902                 for (i = 0; i < numEncodings; i++) {
    903                     var encoding = this._sock.rQshiftStr(16);
    904                 }
    905             }
    906 
    907             // NB(directxman12): these are down here so that we don't run them multiple times
    908             //                   if we backtrack
    909             Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
    910                       ", bpp: " + bpp + ", depth: " + depth +
    911                       ", big_endian: " + big_endian +
    912                       ", true_color: " + true_color +
    913                       ", red_max: " + red_max +
    914                       ", green_max: " + green_max +
    915                       ", blue_max: " + blue_max +
    916                       ", red_shift: " + red_shift +
    917                       ", green_shift: " + green_shift +
    918                       ", blue_shift: " + blue_shift);
    919 
    920             if (big_endian !== 0) {
    921                 Util.Warn("Server native endian is not little endian");
    922             }
    923 
    924             if (red_shift !== 16) {
    925                 Util.Warn("Server native red-shift is not 16");
    926             }
    927 
    928             if (blue_shift !== 0) {
    929                 Util.Warn("Server native blue-shift is not 0");
    930             }
    931 
    932             // we're past the point where we could backtrack, so it's safe to call this
    933             this._onDesktopName(this, this._fb_name);
    934 
    935             if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
    936                 Util.Warn("Intel AMT KVM only supports 8/16 bit depths.  Disabling true color");
    937                 this._true_color = false;
    938             }
    939 
    940             this._display.set_true_color(this._true_color);
    941             this._onFBResize(this, this._fb_width, this._fb_height);
    942             this._display.resize(this._fb_width, this._fb_height);
    943             this._keyboard.grab();
    944             this._mouse.grab();
    945 
    946             if (this._true_color) {
    947                 this._fb_Bpp = 4;
    948                 this._fb_depth = 3;
    949             } else {
    950                 this._fb_Bpp = 1;
    951                 this._fb_depth = 1;
    952             }
    953 
    954             var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
    955             response = response.concat(
    956                             RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
    957             response = response.concat(
    958                             RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
    959                                                           this._fb_width, this._fb_height));
    960 
    961             this._timing.fbu_rt_start = (new Date()).getTime();
    962             this._timing.pixels = 0;
    963             this._sock.send(response);
    964 
    965             this._checkEvents();
    966 
    967             if (this._encrypt) {
    968                 this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
    969             } else {
    970                 this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
    971             }
    972         },
    973 
    974         _init_msg: function () {
    975             switch (this._rfb_state) {
    976                 case 'ProtocolVersion':
    977                     return this._negotiate_protocol_version();
    978 
    979                 case 'Security':
    980                     return this._negotiate_security();
    981 
    982                 case 'Authentication':
    983                     return this._negotiate_authentication();
    984 
    985                 case 'SecurityResult':
    986                     return this._handle_security_result();
    987 
    988                 case 'ClientInitialisation':
    989                     this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
    990                     this._updateState('ServerInitialisation', "Authentication OK");
    991                     return true;
    992 
    993                 case 'ServerInitialisation':
    994                     return this._negotiate_server_init();
    995             }
    996         },
    997 
    998         _handle_set_colour_map_msg: function () {
    999             Util.Debug("SetColorMapEntries");
   1000             this._sock.rQskip8();  // Padding
   1001 
   1002             var first_colour = this._sock.rQshift16();
   1003             var num_colours = this._sock.rQshift16();
   1004             if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
   1005 
   1006             for (var c = 0; c < num_colours; c++) {
   1007                 var red = parseInt(this._sock.rQshift16() / 256, 10);
   1008                 var green = parseInt(this._sock.rQshift16() / 256, 10);
   1009                 var blue = parseInt(this._sock.rQshift16() / 256, 10);
   1010                 this._display.set_colourMap([blue, green, red], first_colour + c);
   1011             }
   1012             Util.Debug("colourMap: " + this._display.get_colourMap());
   1013             Util.Info("Registered " + num_colours + " colourMap entries");
   1014 
   1015             return true;
   1016         },
   1017 
   1018         _handle_server_cut_text: function () {
   1019             Util.Debug("ServerCutText");
   1020             if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
   1021             this._sock.rQskipBytes(3);  // Padding
   1022             var length = this._sock.rQshift32();
   1023             if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
   1024 
   1025             var text = this._sock.rQshiftStr(length);
   1026             this._onClipboard(this, text);
   1027 
   1028             return true;
   1029         },
   1030 
   1031         _handle_xvp_msg: function () {
   1032             if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
   1033             this._sock.rQskip8();  // Padding
   1034             var xvp_ver = this._sock.rQshift8();
   1035             var xvp_msg = this._sock.rQshift8();
   1036 
   1037             switch (xvp_msg) {
   1038                 case 0:  // XVP_FAIL
   1039                     this._updateState(this._rfb_state, "Operation Failed");
   1040                     break;
   1041                 case 1:  // XVP_INIT
   1042                     this._rfb_xvp_ver = xvp_ver;
   1043                     Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
   1044                     this._onXvpInit(this._rfb_xvp_ver);
   1045                     break;
   1046                 default:
   1047                     this._fail("Disconnected: illegal server XVP message " + xvp_msg);
   1048                     break;
   1049             }
   1050 
   1051             return true;
   1052         },
   1053 
   1054         _normal_msg: function () {
   1055             var msg_type;
   1056 
   1057             if (this._FBU.rects > 0) {
   1058                 msg_type = 0;
   1059             } else {
   1060                 msg_type = this._sock.rQshift8();
   1061             }
   1062 
   1063             switch (msg_type) {
   1064                 case 0:  // FramebufferUpdate
   1065                     var ret = this._framebufferUpdate();
   1066                     if (ret) {
   1067                         this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
   1068                                                                       this._fb_width, this._fb_height));
   1069                     }
   1070                     return ret;
   1071 
   1072                 case 1:  // SetColorMapEntries
   1073                     return this._handle_set_colour_map_msg();
   1074 
   1075                 case 2:  // Bell
   1076                     Util.Debug("Bell");
   1077                     this._onBell(this);
   1078                     return true;
   1079 
   1080                 case 3:  // ServerCutText
   1081                     return this._handle_server_cut_text();
   1082 
   1083                 case 250:  // XVP
   1084                     return this._handle_xvp_msg();
   1085 
   1086                 default:
   1087                     this._fail("Disconnected: illegal server message type " + msg_type);
   1088                     Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
   1089                     return true;
   1090             }
   1091         },
   1092 
   1093         _framebufferUpdate: function () {
   1094             var ret = true;
   1095             var now;
   1096 
   1097             if (this._FBU.rects === 0) {
   1098                 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
   1099                 this._sock.rQskip8();  // Padding
   1100                 this._FBU.rects = this._sock.rQshift16();
   1101                 this._FBU.bytes = 0;
   1102                 this._timing.cur_fbu = 0;
   1103                 if (this._timing.fbu_rt_start > 0) {
   1104                     now = (new Date()).getTime();
   1105                     Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
   1106                 }
   1107             }
   1108 
   1109             while (this._FBU.rects > 0) {
   1110                 if (this._rfb_state !== "normal") { return false; }
   1111 
   1112                 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
   1113                 if (this._FBU.bytes === 0) {
   1114                     if (this._sock.rQwait("rect header", 12)) { return false; }
   1115                     /* New FramebufferUpdate */
   1116 
   1117                     var hdr = this._sock.rQshiftBytes(12);
   1118                     this._FBU.x        = (hdr[0] << 8) + hdr[1];
   1119                     this._FBU.y        = (hdr[2] << 8) + hdr[3];
   1120                     this._FBU.width    = (hdr[4] << 8) + hdr[5];
   1121                     this._FBU.height   = (hdr[6] << 8) + hdr[7];
   1122                     this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
   1123                                                   (hdr[10] << 8) + hdr[11], 10);
   1124 
   1125                     this._onFBUReceive(this,
   1126                         {'x': this._FBU.x, 'y': this._FBU.y,
   1127                          'width': this._FBU.width, 'height': this._FBU.height,
   1128                          'encoding': this._FBU.encoding,
   1129                          'encodingName': this._encNames[this._FBU.encoding]});
   1130 
   1131                     if (!this._encNames[this._FBU.encoding]) {
   1132                         this._fail("Disconnected: unsupported encoding " +
   1133                                    this._FBU.encoding);
   1134                         return false;
   1135                     }
   1136                 }
   1137 
   1138                 this._timing.last_fbu = (new Date()).getTime();
   1139 
   1140                 ret = this._encHandlers[this._FBU.encoding]();
   1141 
   1142                 now = (new Date()).getTime();
   1143                 this._timing.cur_fbu += (now - this._timing.last_fbu);
   1144 
   1145                 if (ret) {
   1146                     this._encStats[this._FBU.encoding][0]++;
   1147                     this._encStats[this._FBU.encoding][1]++;
   1148                     this._timing.pixels += this._FBU.width * this._FBU.height;
   1149                 }
   1150 
   1151                 if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
   1152                     if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
   1153                         this._timing.fbu_rt_start > 0) {
   1154                         this._timing.full_fbu_total += this._timing.cur_fbu;
   1155                         this._timing.full_fbu_cnt++;
   1156                         Util.Info("Timing of full FBU, curr: " +
   1157                                   this._timing.cur_fbu + ", total: " +
   1158                                   this._timing.full_fbu_total + ", cnt: " +
   1159                                   this._timing.full_fbu_cnt + ", avg: " +
   1160                                   (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
   1161                     }
   1162 
   1163                     if (this._timing.fbu_rt_start > 0) {
   1164                         var fbu_rt_diff = now - this._timing.fbu_rt_start;
   1165                         this._timing.fbu_rt_total += fbu_rt_diff;
   1166                         this._timing.fbu_rt_cnt++;
   1167                         Util.Info("full FBU round-trip, cur: " +
   1168                                   fbu_rt_diff + ", total: " +
   1169                                   this._timing.fbu_rt_total + ", cnt: " +
   1170                                   this._timing.fbu_rt_cnt + ", avg: " +
   1171                                   (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
   1172                         this._timing.fbu_rt_start = 0;
   1173                     }
   1174                 }
   1175 
   1176                 if (!ret) { return ret; }  // need more data
   1177             }
   1178 
   1179             this._onFBUComplete(this,
   1180                     {'x': this._FBU.x, 'y': this._FBU.y,
   1181                      'width': this._FBU.width, 'height': this._FBU.height,
   1182                      'encoding': this._FBU.encoding,
   1183                      'encodingName': this._encNames[this._FBU.encoding]});
   1184 
   1185             return true;  // We finished this FBU
   1186         },
   1187     };
   1188 
   1189     Util.make_properties(RFB, [
   1190         ['target', 'wo', 'dom'],                // VNC display rendering Canvas object
   1191         ['focusContainer', 'wo', 'dom'],        // DOM element that captures keyboard input
   1192         ['encrypt', 'rw', 'bool'],              // Use TLS/SSL/wss encryption
   1193         ['true_color', 'rw', 'bool'],           // Request true color pixel data
   1194         ['local_cursor', 'rw', 'bool'],         // Request locally rendered cursor
   1195         ['shared', 'rw', 'bool'],               // Request shared mode
   1196         ['view_only', 'rw', 'bool'],            // Disable client mouse/keyboard
   1197         ['xvp_password_sep', 'rw', 'str'],      // Separator for XVP password fields
   1198         ['disconnectTimeout', 'rw', 'int'],     // Time (s) to wait for disconnection
   1199         ['wsProtocols', 'rw', 'arr'],           // Protocols to use in the WebSocket connection
   1200         ['repeaterID', 'rw', 'str'],            // [UltraVNC] RepeaterID to connect to
   1201         ['viewportDrag', 'rw', 'bool'],         // Move the viewport on mouse drags
   1202 
   1203         // Callback functions
   1204         ['onUpdateState', 'rw', 'func'],        // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
   1205         ['onPasswordRequired', 'rw', 'func'],   // onPasswordRequired(rfb): VNC password is required
   1206         ['onClipboard', 'rw', 'func'],          // onClipboard(rfb, text): RFB clipboard contents received
   1207         ['onBell', 'rw', 'func'],               // onBell(rfb): RFB Bell message received
   1208         ['onFBUReceive', 'rw', 'func'],         // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
   1209         ['onFBUComplete', 'rw', 'func'],        // onFBUComplete(rfb, fbu): RFB FBU received and processed
   1210         ['onFBResize', 'rw', 'func'],           // onFBResize(rfb, width, height): frame buffer resized
   1211         ['onDesktopName', 'rw', 'func'],        // onDesktopName(rfb, name): desktop name received
   1212         ['onXvpInit', 'rw', 'func'],            // onXvpInit(version): XVP extensions active for this connection
   1213     ]);
   1214 
   1215     RFB.prototype.set_local_cursor = function (cursor) {
   1216         if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
   1217             this._local_cursor = false;
   1218         } else {
   1219             if (this._display.get_cursor_uri()) {
   1220                 this._local_cursor = true;
   1221             } else {
   1222                 Util.Warn("Browser does not support local cursor");
   1223             }
   1224         }
   1225     };
   1226 
   1227     RFB.prototype.get_display = function () { return this._display; };
   1228     RFB.prototype.get_keyboard = function () { return this._keyboard; };
   1229     RFB.prototype.get_mouse = function () { return this._mouse; };
   1230 
   1231     // Class Methods
   1232     RFB.messages = {
   1233         keyEvent: function (keysym, down) {
   1234             var arr = [4];
   1235             arr.push8(down);
   1236             arr.push16(0);
   1237             arr.push32(keysym);
   1238             return arr;
   1239         },
   1240 
   1241         pointerEvent: function (x, y, mask) {
   1242             var arr = [5];  // msg-type
   1243             arr.push8(mask);
   1244             arr.push16(x);
   1245             arr.push16(y);
   1246             return arr;
   1247         },
   1248 
   1249         // TODO(directxman12): make this unicode compatible?
   1250         clientCutText: function (text) {
   1251             var arr = [6];  // msg-type
   1252             arr.push8(0);   // padding
   1253             arr.push8(0);   // padding
   1254             arr.push8(0);   // padding
   1255             arr.push32(text.length);
   1256             var n = text.length;
   1257             for (var i = 0; i < n; i++) {
   1258                 arr.push(text.charCodeAt(i));
   1259             }
   1260 
   1261             return arr;
   1262         },
   1263 
   1264         pixelFormat: function (bpp, depth, true_color) {
   1265             var arr = [0]; // msg-type
   1266             arr.push8(0);  // padding
   1267             arr.push8(0);  // padding
   1268             arr.push8(0);  // padding
   1269 
   1270             arr.push8(bpp * 8); // bits-per-pixel
   1271             arr.push8(depth * 8); // depth
   1272             arr.push8(0);  // little-endian
   1273             arr.push8(true_color ? 1 : 0);  // true-color
   1274 
   1275             arr.push16(255);  // red-max
   1276             arr.push16(255);  // green-max
   1277             arr.push16(255);  // blue-max
   1278             arr.push8(16);    // red-shift
   1279             arr.push8(8);     // green-shift
   1280             arr.push8(0);     // blue-shift
   1281 
   1282             arr.push8(0);     // padding
   1283             arr.push8(0);     // padding
   1284             arr.push8(0);     // padding
   1285             return arr;
   1286         },
   1287 
   1288         clientEncodings: function (encodings, local_cursor, true_color) {
   1289             var i, encList = [];
   1290 
   1291             for (i = 0; i < encodings.length; i++) {
   1292                 if (encodings[i][0] === "Cursor" && !local_cursor) {
   1293                     Util.Debug("Skipping Cursor pseudo-encoding");
   1294                 } else if (encodings[i][0] === "TIGHT" && !true_color) {
   1295                     // TODO: remove this when we have tight+non-true-color
   1296                     Util.Warn("Skipping tight as it is only supported with true color");
   1297                 } else {
   1298                     encList.push(encodings[i][1]);
   1299                 }
   1300             }
   1301 
   1302             var arr = [2];  // msg-type
   1303             arr.push8(0);   // padding
   1304 
   1305             arr.push16(encList.length);  // encoding count
   1306             for (i = 0; i < encList.length; i++) {
   1307                 arr.push32(encList[i]);
   1308             }
   1309 
   1310             return arr;
   1311         },
   1312 
   1313         fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
   1314             var arr = [];
   1315 
   1316             var cb = cleanDirty.cleanBox;
   1317             var w, h;
   1318             if (cb.w > 0 && cb.h > 0) {
   1319                 w = typeof cb.w === "undefined" ? fb_width : cb.w;
   1320                 h = typeof cb.h === "undefined" ? fb_height : cb.h;
   1321                 // Request incremental for clean box
   1322                 arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h));
   1323             }
   1324 
   1325             for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
   1326                 var db = cleanDirty.dirtyBoxes[i];
   1327                 // Force all (non-incremental) for dirty box
   1328                 w = typeof db.w === "undefined" ? fb_width : db.w;
   1329                 h = typeof db.h === "undefined" ? fb_height : db.h;
   1330                 arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
   1331             }
   1332 
   1333             return arr;
   1334         },
   1335 
   1336         fbUpdateRequest: function (incremental, x, y, w, h) {
   1337             if (typeof(x) === "undefined") { x = 0; }
   1338             if (typeof(y) === "undefined") { y = 0; }
   1339 
   1340             var arr = [3];  // msg-type
   1341             arr.push8(incremental);
   1342             arr.push16(x);
   1343             arr.push16(y);
   1344             arr.push16(w);
   1345             arr.push16(h);
   1346 
   1347             return arr;
   1348         }
   1349     };
   1350 
   1351     RFB.genDES = function (password, challenge) {
   1352         var passwd = [];
   1353         for (var i = 0; i < password.length; i++) {
   1354             passwd.push(password.charCodeAt(i));
   1355         }
   1356         return (new DES(passwd)).encrypt(challenge);
   1357     };
   1358 
   1359     RFB.extract_data_uri = function (arr) {
   1360         return ";base64," + Base64.encode(arr);
   1361     };
   1362 
   1363     RFB.encodingHandlers = {
   1364         RAW: function () {
   1365             if (this._FBU.lines === 0) {
   1366                 this._FBU.lines = this._FBU.height;
   1367             }
   1368 
   1369             this._FBU.bytes = this._FBU.width * this._fb_Bpp;  // at least a line
   1370             if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
   1371             var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
   1372             var curr_height = Math.min(this._FBU.lines,
   1373                                        Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
   1374             this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
   1375                                     curr_height, this._sock.get_rQ(),
   1376                                     this._sock.get_rQi());
   1377             this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
   1378             this._FBU.lines -= curr_height;
   1379 
   1380             if (this._FBU.lines > 0) {
   1381                 this._FBU.bytes = this._FBU.width * this._fb_Bpp;  // At least another line
   1382             } else {
   1383                 this._FBU.rects--;
   1384                 this._FBU.bytes = 0;
   1385             }
   1386 
   1387             return true;
   1388         },
   1389 
   1390         COPYRECT: function () {
   1391             this._FBU.bytes = 4;
   1392             if (this._sock.rQwait("COPYRECT", 4)) { return false; }
   1393             this._display.renderQ_push({
   1394                 'type': 'copy',
   1395                 'old_x': this._sock.rQshift16(),
   1396                 'old_y': this._sock.rQshift16(),
   1397                 'x': this._FBU.x,
   1398                 'y': this._FBU.y,
   1399                 'width': this._FBU.width,
   1400                 'height': this._FBU.height
   1401             });
   1402             this._FBU.rects--;
   1403             this._FBU.bytes = 0;
   1404             return true;
   1405         },
   1406 
   1407         RRE: function () {
   1408             var color;
   1409             if (this._FBU.subrects === 0) {
   1410                 this._FBU.bytes = 4 + this._fb_Bpp;
   1411                 if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
   1412                 this._FBU.subrects = this._sock.rQshift32();
   1413                 color = this._sock.rQshiftBytes(this._fb_Bpp);  // Background
   1414                 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
   1415             }
   1416 
   1417             while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
   1418                 color = this._sock.rQshiftBytes(this._fb_Bpp);
   1419                 var x = this._sock.rQshift16();
   1420                 var y = this._sock.rQshift16();
   1421                 var width = this._sock.rQshift16();
   1422                 var height = this._sock.rQshift16();
   1423                 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
   1424                 this._FBU.subrects--;
   1425             }
   1426 
   1427             if (this._FBU.subrects > 0) {
   1428                 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
   1429                 this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
   1430             } else {
   1431                 this._FBU.rects--;
   1432                 this._FBU.bytes = 0;
   1433             }
   1434 
   1435             return true;
   1436         },
   1437 
   1438         HEXTILE: function () {
   1439             var rQ = this._sock.get_rQ();
   1440             var rQi = this._sock.get_rQi();
   1441 
   1442             if (this._FBU.tiles === 0) {
   1443                 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
   1444                 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
   1445                 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
   1446                 this._FBU.tiles = this._FBU.total_tiles;
   1447             }
   1448 
   1449             while (this._FBU.tiles > 0) {
   1450                 this._FBU.bytes = 1;
   1451                 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
   1452                 var subencoding = rQ[rQi];  // Peek
   1453                 if (subencoding > 30) {  // Raw
   1454                     this._fail("Disconnected: illegal hextile subencoding " + subencoding);
   1455                     return false;
   1456                 }
   1457 
   1458                 var subrects = 0;
   1459                 var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
   1460                 var tile_x = curr_tile % this._FBU.tiles_x;
   1461                 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
   1462                 var x = this._FBU.x + tile_x * 16;
   1463                 var y = this._FBU.y + tile_y * 16;
   1464                 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
   1465                 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
   1466 
   1467                 // Figure out how much we are expecting
   1468                 if (subencoding & 0x01) {  // Raw
   1469                     this._FBU.bytes += w * h * this._fb_Bpp;
   1470                 } else {
   1471                     if (subencoding & 0x02) {  // Background
   1472                         this._FBU.bytes += this._fb_Bpp;
   1473                     }
   1474                     if (subencoding & 0x04) {  // Foreground
   1475                         this._FBU.bytes += this._fb_Bpp;
   1476                     }
   1477                     if (subencoding & 0x08) {  // AnySubrects
   1478                         this._FBU.bytes++;  // Since we aren't shifting it off
   1479                         if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
   1480                         subrects = rQ[rQi + this._FBU.bytes - 1];  // Peek
   1481                         if (subencoding & 0x10) {  // SubrectsColoured
   1482                             this._FBU.bytes += subrects * (this._fb_Bpp + 2);
   1483                         } else {
   1484                             this._FBU.bytes += subrects * 2;
   1485                         }
   1486                     }
   1487                 }
   1488 
   1489                 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
   1490 
   1491                 // We know the encoding and have a whole tile
   1492                 this._FBU.subencoding = rQ[rQi];
   1493                 rQi++;
   1494                 if (this._FBU.subencoding === 0) {
   1495                     if (this._FBU.lastsubencoding & 0x01) {
   1496                         // Weird: ignore blanks are RAW
   1497                         Util.Debug("     Ignoring blank after RAW");
   1498                     } else {
   1499                         this._display.fillRect(x, y, w, h, rQ, rQi);
   1500                         rQi += this._FBU.bytes - 1;
   1501                     }
   1502                 } else if (this._FBU.subencoding & 0x01) {  // Raw
   1503                     this._display.blitImage(x, y, w, h, rQ, rQi);
   1504                     rQi += this._FBU.bytes - 1;
   1505                 } else {
   1506                     if (this._FBU.subencoding & 0x02) {  // Background
   1507                         this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
   1508                         rQi += this._fb_Bpp;
   1509                     }
   1510                     if (this._FBU.subencoding & 0x04) {  // Foreground
   1511                         this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
   1512                         rQi += this._fb_Bpp;
   1513                     }
   1514 
   1515                     this._display.startTile(x, y, w, h, this._FBU.background);
   1516                     if (this._FBU.subencoding & 0x08) {  // AnySubrects
   1517                         subrects = rQ[rQi];
   1518                         rQi++;
   1519 
   1520                         for (var s = 0; s < subrects; s++) {
   1521                             var color;
   1522                             if (this._FBU.subencoding & 0x10) {  // SubrectsColoured
   1523                                 color = rQ.slice(rQi, rQi + this._fb_Bpp);
   1524                                 rQi += this._fb_Bpp;
   1525                             } else {
   1526                                 color = this._FBU.foreground;
   1527                             }
   1528                             var xy = rQ[rQi];
   1529                             rQi++;
   1530                             var sx = (xy >> 4);
   1531                             var sy = (xy & 0x0f);
   1532 
   1533                             var wh = rQ[rQi];
   1534                             rQi++;
   1535                             var sw = (wh >> 4) + 1;
   1536                             var sh = (wh & 0x0f) + 1;
   1537 
   1538                             this._display.subTile(sx, sy, sw, sh, color);
   1539                         }
   1540                     }
   1541                     this._display.finishTile();
   1542                 }
   1543                 this._sock.set_rQi(rQi);
   1544                 this._FBU.lastsubencoding = this._FBU.subencoding;
   1545                 this._FBU.bytes = 0;
   1546                 this._FBU.tiles--;
   1547             }
   1548 
   1549             if (this._FBU.tiles === 0) {
   1550                 this._FBU.rects--;
   1551             }
   1552 
   1553             return true;
   1554         },
   1555 
   1556         getTightCLength: function (arr) {
   1557             var header = 1, data = 0;
   1558             data += arr[0] & 0x7f;
   1559             if (arr[0] & 0x80) {
   1560                 header++;
   1561                 data += (arr[1] & 0x7f) << 7;
   1562                 if (arr[1] & 0x80) {
   1563                     header++;
   1564                     data += arr[2] << 14;
   1565                 }
   1566             }
   1567             return [header, data];
   1568         },
   1569 
   1570         display_tight: function (isTightPNG) {
   1571             if (this._fb_depth === 1) {
   1572                 this._fail("Tight protocol handler only implements true color mode");
   1573             }
   1574 
   1575             this._FBU.bytes = 1;  // compression-control byte
   1576             if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
   1577 
   1578             var checksum = function (data) {
   1579                 var sum = 0;
   1580                 for (var i = 0; i < data.length; i++) {
   1581                     sum += data[i];
   1582                     if (sum > 65536) sum -= 65536;
   1583                 }
   1584                 return sum;
   1585             };
   1586 
   1587             var resetStreams = 0;
   1588             var streamId = -1;
   1589             var decompress = function (data) {
   1590                 for (var i = 0; i < 4; i++) {
   1591                     if ((resetStreams >> i) & 1) {
   1592                         this._FBU.zlibs[i].reset();
   1593                         Util.Info("Reset zlib stream " + i);
   1594                     }
   1595                 }
   1596 
   1597                 var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
   1598                 if (uncompressed.status !== 0) {
   1599                     Util.Error("Invalid data in zlib stream");
   1600                 }
   1601 
   1602                 return uncompressed.data;
   1603             }.bind(this);
   1604 
   1605             var indexedToRGB = function (data, numColors, palette, width, height) {
   1606                 // Convert indexed (palette based) image data to RGB
   1607                 // TODO: reduce number of calculations inside loop
   1608                 var dest = [];
   1609                 var x, y, dp, sp;
   1610                 if (numColors === 2) {
   1611                     var w = Math.floor((width + 7) / 8);
   1612                     var w1 = Math.floor(width / 8);
   1613 
   1614                     for (y = 0; y < height; y++) {
   1615                         var b;
   1616                         for (x = 0; x < w1; x++) {
   1617                             for (b = 7; b >= 0; b--) {
   1618                                 dp = (y * width + x * 8 + 7 - b) * 3;
   1619                                 sp = (data[y * w + x] >> b & 1) * 3;
   1620                                 dest[dp] = palette[sp];
   1621                                 dest[dp + 1] = palette[sp + 1];
   1622                                 dest[dp + 2] = palette[sp + 2];
   1623                             }
   1624                         }
   1625 
   1626                         for (b = 7; b >= 8 - width % 8; b--) {
   1627                             dp = (y * width + x * 8 + 7 - b) * 3;
   1628                             sp = (data[y * w + x] >> b & 1) * 3;
   1629                             dest[dp] = palette[sp];
   1630                             dest[dp + 1] = palette[sp + 1];
   1631                             dest[dp + 2] = palette[sp + 2];
   1632                         }
   1633                     }
   1634                 } else {
   1635                     for (y = 0; y < height; y++) {
   1636                         for (x = 0; x < width; x++) {
   1637                             dp = (y * width + x) * 3;
   1638                             sp = data[y * width + x] * 3;
   1639                             dest[dp] = palette[sp];
   1640                             dest[dp + 1] = palette[sp + 1];
   1641                             dest[dp + 2] = palette[sp + 2];
   1642                         }
   1643                     }
   1644                 }
   1645 
   1646                 return dest;
   1647             }.bind(this);
   1648 
   1649             var rQ = this._sock.get_rQ();
   1650             var rQi = this._sock.get_rQi();
   1651             var cmode, clength, data;
   1652 
   1653             var handlePalette = function () {
   1654                 var numColors = rQ[rQi + 2] + 1;
   1655                 var paletteSize = numColors * this._fb_depth;
   1656                 this._FBU.bytes += paletteSize;
   1657                 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
   1658 
   1659                 var bpp = (numColors <= 2) ? 1 : 8;
   1660                 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
   1661                 var raw = false;
   1662                 if (rowSize * this._FBU.height < 12) {
   1663                     raw = true;
   1664                     clength = [0, rowSize * this._FBU.height];
   1665                 } else {
   1666                     clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
   1667                                                                                       3 + paletteSize + 3));
   1668                 }
   1669 
   1670                 this._FBU.bytes += clength[0] + clength[1];
   1671                 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
   1672 
   1673                 // Shift ctl, filter id, num colors, palette entries, and clength off
   1674                 this._sock.rQskipBytes(3);
   1675                 var palette = this._sock.rQshiftBytes(paletteSize);
   1676                 this._sock.rQskipBytes(clength[0]);
   1677 
   1678                 if (raw) {
   1679                     data = this._sock.rQshiftBytes(clength[1]);
   1680                 } else {
   1681                     data = decompress(this._sock.rQshiftBytes(clength[1]));
   1682                 }
   1683 
   1684                 // Convert indexed (palette based) image data to RGB
   1685                 var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
   1686 
   1687                 this._display.renderQ_push({
   1688                     'type': 'blitRgb',
   1689                     'data': rgb,
   1690                     'x': this._FBU.x,
   1691                     'y': this._FBU.y,
   1692                     'width': this._FBU.width,
   1693                     'height': this._FBU.height
   1694                 });
   1695 
   1696                 return true;
   1697             }.bind(this);
   1698 
   1699             var handleCopy = function () {
   1700                 var raw = false;
   1701                 var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
   1702                 if (uncompressedSize < 12) {
   1703                     raw = true;
   1704                     clength = [0, uncompressedSize];
   1705                 } else {
   1706                     clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
   1707                 }
   1708                 this._FBU.bytes = 1 + clength[0] + clength[1];
   1709                 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
   1710 
   1711                 // Shift ctl, clength off
   1712                 this._sock.rQshiftBytes(1 + clength[0]);
   1713 
   1714                 if (raw) {
   1715                     data = this._sock.rQshiftBytes(clength[1]);
   1716                 } else {
   1717                     data = decompress(this._sock.rQshiftBytes(clength[1]));
   1718                 }
   1719 
   1720                 this._display.renderQ_push({
   1721                     'type': 'blitRgb',
   1722                     'data': data,
   1723                     'x': this._FBU.x,
   1724                     'y': this._FBU.y,
   1725                     'width': this._FBU.width,
   1726                     'height': this._FBU.height
   1727                 });
   1728 
   1729                 return true;
   1730             }.bind(this);
   1731 
   1732             var ctl = this._sock.rQpeek8();
   1733 
   1734             // Keep tight reset bits
   1735             resetStreams = ctl & 0xF;
   1736 
   1737             // Figure out filter
   1738             ctl = ctl >> 4;
   1739             streamId = ctl & 0x3;
   1740 
   1741             if (ctl === 0x08)       cmode = "fill";
   1742             else if (ctl === 0x09)  cmode = "jpeg";
   1743             else if (ctl === 0x0A)  cmode = "png";
   1744             else if (ctl & 0x04)    cmode = "filter";
   1745             else if (ctl < 0x04)    cmode = "copy";
   1746             else return this._fail("Illegal tight compression received, ctl: " + ctl);
   1747 
   1748             if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
   1749                 return this._fail("filter/copy received in tightPNG mode");
   1750             }
   1751 
   1752             switch (cmode) {
   1753                 // fill use fb_depth because TPIXELs drop the padding byte
   1754                 case "fill":  // TPIXEL
   1755                     this._FBU.bytes += this._fb_depth;
   1756                     break;
   1757                 case "jpeg":  // max clength
   1758                     this._FBU.bytes += 3;
   1759                     break;
   1760                 case "png":  // max clength
   1761                     this._FBU.bytes += 3;
   1762                     break;
   1763                 case "filter":  // filter id + num colors if palette
   1764                     this._FBU.bytes += 2;
   1765                     break;
   1766                 case "copy":
   1767                     break;
   1768             }
   1769 
   1770             if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
   1771 
   1772             // Determine FBU.bytes
   1773             switch (cmode) {
   1774                 case "fill":
   1775                     this._sock.rQskip8();  // shift off ctl
   1776                     var color = this._sock.rQshiftBytes(this._fb_depth);
   1777                     this._display.renderQ_push({
   1778                         'type': 'fill',
   1779                         'x': this._FBU.x,
   1780                         'y': this._FBU.y,
   1781                         'width': this._FBU.width,
   1782                         'height': this._FBU.height,
   1783                         'color': [color[2], color[1], color[0]]
   1784                     });
   1785                     break;
   1786                 case "png":
   1787                 case "jpeg":
   1788                     clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
   1789                     this._FBU.bytes = 1 + clength[0] + clength[1];  // ctl + clength size + jpeg-data
   1790                     if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
   1791 
   1792                     // We have everything, render it
   1793                     this._sock.rQskipBytes(1 + clength[0]);  // shift off clt + compact length
   1794                     var img = new Image();
   1795                     img.src = "data: image/" + cmode +
   1796                         RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
   1797                     this._display.renderQ_push({
   1798                         'type': 'img',
   1799                         'img': img,
   1800                         'x': this._FBU.x,
   1801                         'y': this._FBU.y
   1802                     });
   1803                     img = null;
   1804                     break;
   1805                 case "filter":
   1806                     var filterId = rQ[rQi + 1];
   1807                     if (filterId === 1) {
   1808                         if (!handlePalette()) { return false; }
   1809                     } else {
   1810                         // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
   1811                         // Filter 2, Gradient is valid but not use if jpeg is enabled
   1812                         // TODO(directxman12): why aren't we just calling '_fail' here
   1813                         throw new Error("Unsupported tight subencoding received, filter: " + filterId);
   1814                     }
   1815                     break;
   1816                 case "copy":
   1817                     if (!handleCopy()) { return false; }
   1818                     break;
   1819             }
   1820 
   1821 
   1822             this._FBU.bytes = 0;
   1823             this._FBU.rects--;
   1824 
   1825             return true;
   1826         },
   1827 
   1828         TIGHT: function () { return this._encHandlers.display_tight(false); },
   1829         TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
   1830 
   1831         last_rect: function () {
   1832             this._FBU.rects = 0;
   1833             return true;
   1834         },
   1835 
   1836         DesktopSize: function () {
   1837             Util.Debug(">> set_desktopsize");
   1838             this._fb_width = this._FBU.width;
   1839             this._fb_height = this._FBU.height;
   1840             this._onFBResize(this, this._fb_width, this._fb_height);
   1841             this._display.resize(this._fb_width, this._fb_height);
   1842             this._timing.fbu_rt_start = (new Date()).getTime();
   1843 
   1844             this._FBU.bytes = 0;
   1845             this._FBU.rects--;
   1846 
   1847             Util.Debug("<< set_desktopsize");
   1848             return true;
   1849         },
   1850 
   1851         Cursor: function () {
   1852             Util.Debug(">> set_cursor");
   1853             var x = this._FBU.x;  // hotspot-x
   1854             var y = this._FBU.y;  // hotspot-y
   1855             var w = this._FBU.width;
   1856             var h = this._FBU.height;
   1857 
   1858             var pixelslength = w * h * this._fb_Bpp;
   1859             var masklength = Math.floor((w + 7) / 8) * h;
   1860 
   1861             this._FBU.bytes = pixelslength + masklength;
   1862             if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
   1863 
   1864             this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
   1865                                        this._sock.rQshiftBytes(masklength),
   1866                                        x, y, w, h);
   1867 
   1868             this._FBU.bytes = 0;
   1869             this._FBU.rects--;
   1870 
   1871             Util.Debug("<< set_cursor");
   1872             return true;
   1873         },
   1874 
   1875         JPEG_quality_lo: function () {
   1876             Util.Error("Server sent jpeg_quality pseudo-encoding");
   1877         },
   1878 
   1879         compress_lo: function () {
   1880             Util.Error("Server sent compress level pseudo-encoding");
   1881         }
   1882     };
   1883 })();
   1884