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)
7 * See README.md for usage and integration instructions.
9 * TIGHT decoder portion:
10 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
13 /*jslint white: false, browser: true */
14 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
20 RFB = function (defaults
) {
26 this._rfb_port
= 5900;
27 this._rfb_password
= '';
30 this._rfb_state
= 'disconnected';
31 this._rfb_version
= 0;
32 this._rfb_max_version
= 3.8;
33 this._rfb_auth_scheme
= '';
35 this._rfb_tightvnc
= false;
36 this._rfb_xvp_ver
= 0;
38 // In preference order
46 ['DesktopSize', -223 ],
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 ],
57 ['ExtendedDesktopSize', -308 ]
60 this._encHandlers
= {};
64 this._sock
= null; // Websock object
65 this._display
= null; // Display object
66 this._keyboard
= null; // Keyboard input handler object
67 this._mouse
= null; // Mouse input handler object
68 this._sendTimer
= null; // Send Queue check timer
69 this._disconnTimer
= null; // disconnection timer
70 this._msgTimer
= null; // queued handle_msg timer
72 // Frame buffer update state
86 zlib
: [] // TIGHT zlib streams
95 this._rre_chunk_sz
= 100;
110 this._supportsSetDesktopSize
= false;
112 this._screen_flags
= 0;
115 this._mouse_buttonMask
= 0;
116 this._mouse_arr
= [];
117 this._viewportDragging
= false;
118 this._viewportDragPos
= {};
120 // set the default value on user-facing properties
121 Util
.set_defaults(this, defaults
, {
122 'target': 'null', // VNC display rendering Canvas object
123 'focusContainer': document
, // DOM element that captures keyboard input
124 'encrypt': false, // Use TLS/SSL/wss encryption
125 'true_color': true, // Request true color pixel data
126 'local_cursor': false, // Request locally rendered cursor
127 'shared': true, // Request shared mode
128 'view_only': false, // Disable client mouse/keyboard
129 'xvp_password_sep': '@', // Separator for XVP password fields
130 'disconnectTimeout': 3, // Time (s) to wait for disconnection
131 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection
132 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
133 'viewportDrag': false, // Move the viewport on mouse drags
135 // Callback functions
136 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
137 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
138 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
139 'onBell': function () { }, // onBell(rfb): RFB Bell message received
140 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
141 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
142 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
143 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
144 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
148 Util
.Debug(">> RFB.constructor");
150 // populate encHandlers with bound versions
151 Object
.keys(RFB
.encodingHandlers
).forEach(function (encName
) {
152 this._encHandlers
[encName
] = RFB
.encodingHandlers
[encName
].bind(this);
155 // Create lookup tables based on encoding number
156 for (var i
= 0; i
< this._encodings
.length
; i
++) {
157 this._encHandlers
[this._encodings
[i
][1]] = this._encHandlers
[this._encodings
[i
][0]];
158 this._encNames
[this._encodings
[i
][1]] = this._encodings
[i
][0];
159 this._encStats
[this._encodings
[i
][1]] = [0, 0];
162 // NB: nothing that needs explicit teardown should be done
163 // before this point, since this can throw an exception
165 this._display
= new Display({target
: this._target
});
167 Util
.Error("Display exception: " + exc
);
171 this._keyboard
= new Keyboard({target
: this._focusContainer
,
172 onKeyPress
: this._handleKeyPress
.bind(this)});
174 this._mouse
= new Mouse({target
: this._target
,
175 onMouseButton
: this._handleMouseButton
.bind(this),
176 onMouseMove
: this._handleMouseMove
.bind(this),
177 notify
: this._keyboard
.sync
.bind(this._keyboard
)});
179 this._sock
= new Websock();
180 this._sock
.on('message', this._handle_message
.bind(this));
181 this._sock
.on('open', function () {
182 if (this._rfb_state
=== 'connect') {
183 this._updateState('ProtocolVersion', "Starting VNC handshake");
185 this._fail("Got unexpected WebSocket connection");
188 this._sock
.on('close', function (e
) {
189 Util
.Warn("WebSocket on-close event");
192 msg
= " (code: " + e
.code
;
194 msg
+= ", reason: " + e
.reason
;
198 if (this._rfb_state
=== 'disconnect') {
199 this._updateState('disconnected', 'VNC disconnected' + msg
);
200 } else if (this._rfb_state
=== 'ProtocolVersion') {
201 this._fail('Failed to connect to server' + msg
);
202 } else if (this._rfb_state
in {'failed': 1, 'disconnected': 1}) {
203 Util
.Error("Received onclose while disconnected" + msg
);
205 this._fail("Server disconnected" + msg
);
207 this._sock
.off('close');
209 this._sock
.on('error', function (e
) {
210 Util
.Warn("WebSocket on-error event");
215 var rmode
= this._display
.get_render_mode();
216 if (Websock_native
) {
217 Util
.Info("Using native WebSockets");
218 this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
220 Util
.Warn("Using web-socket-js bridge. Flash version: " + Util
.Flash
.version
);
221 if (!Util
.Flash
|| Util
.Flash
.version
< 9) {
222 this._cleanupSocket('fatal');
223 throw new Exception("WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
224 } else if (document
.location
.href
.substr(0, 7) === 'file://') {
225 this._cleanupSocket('fatal');
226 throw new Exception("'file://' URL is incompatible with Adobe Flash");
228 this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
232 Util
.Debug("<< RFB.constructor");
237 connect: function (host
, port
, password
, path
) {
238 this._rfb_host
= host
;
239 this._rfb_port
= port
;
240 this._rfb_password
= (password
!== undefined) ? password
: "";
241 this._rfb_path
= (path
!== undefined) ? path
: "";
243 if (!this._rfb_host
|| !this._rfb_port
) {
244 return this._fail("Must set host and port");
247 this._updateState('connect');
250 disconnect: function () {
251 this._updateState('disconnect', 'Disconnecting');
252 this._sock
.off('error');
253 this._sock
.off('message');
254 this._sock
.off('open');
257 sendPassword: function (passwd
) {
258 this._rfb_password
= passwd
;
259 this._rfb_state
= 'Authentication';
260 setTimeout(this._init_msg
.bind(this), 1);
263 sendCtrlAltDel: function () {
264 if (this._rfb_state
!== 'normal' || this._view_only
) { return false; }
265 Util
.Info("Sending Ctrl-Alt-Del");
268 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Control_L
, 1));
269 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Alt_L
, 1));
270 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Delete
, 1));
271 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Delete
, 0));
272 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Alt_L
, 0));
273 arr
= arr
.concat(RFB
.messages
.keyEvent(XK_Control_L
, 0));
274 this._sock
.send(arr
);
277 xvpOp: function (ver
, op
) {
278 if (this._rfb_xvp_ver
< ver
) { return false; }
279 Util
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
280 this._sock
.send_string("\xFA\x00" + String
.fromCharCode(ver
) + String
.fromCharCode(op
));
284 xvpShutdown: function () {
285 return this.xvpOp(1, 2);
288 xvpReboot: function () {
289 return this.xvpOp(1, 3);
292 xvpReset: function () {
293 return this.xvpOp(1, 4);
296 // Send a key press. If 'down' is not specified then send a down key
297 // followed by an up key.
298 sendKey: function (code
, down
) {
299 if (this._rfb_state
!== "normal" || this._view_only
) { return false; }
301 if (typeof down
!== 'undefined') {
302 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
303 arr
= arr
.concat(RFB
.messages
.keyEvent(code
, down
? 1 : 0));
305 Util
.Info("Sending key code (down + up): " + code
);
306 arr
= arr
.concat(RFB
.messages
.keyEvent(code
, 1));
307 arr
= arr
.concat(RFB
.messages
.keyEvent(code
, 0));
309 this._sock
.send(arr
);
312 clipboardPasteFrom: function (text
) {
313 if (this._rfb_state
!== 'normal') { return; }
314 this._sock
.send(RFB
.messages
.clientCutText(text
));
317 setDesktopSize: function (width
, height
) {
318 if (this._rfb_state
!== "normal") { return; }
320 if (this._supportsSetDesktopSize
) {
322 var arr
= [251]; // msg-type
323 arr
.push8(0); // padding
324 arr
.push16(width
); // width
325 arr
.push16(height
); // height
327 arr
.push8(1); // number-of-screens
328 arr
.push8(0); // padding
331 arr
.push32(this._screen_id
); // id
332 arr
.push16(0); // x-position
333 arr
.push16(0); // y-position
334 arr
.push16(width
); // width
335 arr
.push16(height
); // height
336 arr
.push32(this._screen_flags
); // flags
338 this._sock
.send(arr
);
345 _connect: function () {
346 Util
.Debug(">> RFB.connect");
349 if (typeof UsingSocketIO
!== 'undefined') {
352 uri
= this._encrypt
? 'wss' : 'ws';
355 uri
+= '://' + this._rfb_host
+ ':' + this._rfb_port
+ '/' + this._rfb_path
;
356 Util
.Info("connecting to " + uri
);
358 this._sock
.open(uri
, this._wsProtocols
);
360 Util
.Debug("<< RFB.connect");
363 _init_vars: function () {
368 this._FBU
.subrects
= 0; // RRE and HEXTILE
369 this._FBU
.lines
= 0; // RAW
370 this._FBU
.tiles
= 0; // HEXTILE
371 this._FBU
.zlibs
= []; // TIGHT zlib encoders
372 this._mouse_buttonMask
= 0;
373 this._mouse_arr
= [];
374 this._rfb_tightvnc
= false;
376 // Clear the per connection encoding stats
378 for (i
= 0; i
< this._encodings
.length
; i
++) {
379 this._encStats
[this._encodings
[i
][1]][0] = 0;
382 for (i
= 0; i
< 4; i
++) {
383 this._FBU
.zlibs
[i
] = new TINF();
384 this._FBU
.zlibs
[i
].init();
388 _print_stats: function () {
389 Util
.Info("Encoding stats for this connection:");
391 for (i
= 0; i
< this._encodings
.length
; i
++) {
392 s
= this._encStats
[this._encodings
[i
][1]];
393 if (s
[0] + s
[1] > 0) {
394 Util
.Info(" " + this._encodings
[i
][0] + ": " + s
[0] + " rects");
398 Util
.Info("Encoding stats since page load:");
399 for (i
= 0; i
< this._encodings
.length
; i
++) {
400 s
= this._encStats
[this._encodings
[i
][1]];
401 Util
.Info(" " + this._encodings
[i
][0] + ": " + s
[1] + " rects");
405 _cleanupSocket: function (state
) {
406 if (this._sendTimer
) {
407 clearInterval(this._sendTimer
);
408 this._sendTimer
= null;
411 if (this._msgTimer
) {
412 clearInterval(this._msgTimer
);
413 this._msgTimer
= null;
416 if (this._display
&& this._display
.get_context()) {
417 this._keyboard
.ungrab();
418 this._mouse
.ungrab();
419 if (state
!== 'connect' && state
!== 'loaded') {
420 this._display
.defaultCursor();
422 if (Util
.get_logging() !== 'debug' || state
=== 'loaded') {
423 // Show noVNC logo on load and when disconnected, unless in
425 this._display
.clear();
434 * loaded - page load, equivalent to disconnected
435 * disconnected - idle state
436 * connect - starting to connect (to ProtocolVersion)
438 * disconnect - starting to disconnect
439 * failed - abnormal disconnect
440 * fatal - failed to load page, or fatal error
442 * RFB protocol initialization states:
446 * password - waiting for password, not part of RFB
448 * ClientInitialization - not triggered by server message
449 * ServerInitialization (to normal)
451 _updateState: function (state
, statusMsg
) {
452 var oldstate
= this._rfb_state
;
454 if (state
=== oldstate
) {
455 // Already here, ignore
456 Util
.Debug("Already in state '" + state
+ "', ignoring");
460 * These are disconnected states. A previous connect may
461 * asynchronously cause a connection so make sure we are closed.
463 if (state
in {'disconnected': 1, 'loaded': 1, 'connect': 1,
464 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
465 this._cleanupSocket(state
);
468 if (oldstate
=== 'fatal') {
469 Util
.Error('Fatal error, cannot continue');
472 var cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
473 var fullmsg
= "New state '" + state
+ "', was '" + oldstate
+ "'." + cmsg
;
474 if (state
=== 'failed' || state
=== 'fatal') {
480 if (oldstate
=== 'failed' && state
=== 'disconnected') {
481 // do disconnect action, but stay in failed state
482 this._rfb_state
= 'failed';
484 this._rfb_state
= state
;
487 if (this._disconnTimer
&& this._rfb_state
!== 'disconnect') {
488 Util
.Debug("Clearing disconnect timer");
489 clearTimeout(this._disconnTimer
);
490 this._disconnTimer
= null;
491 this._sock
.off('close'); // make sure we don't get a double event
496 if (oldstate
=== 'disconnected' || oldstate
=== 'failed') {
497 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
504 // WebSocket.onopen transitions to 'ProtocolVersion'
508 this._disconnTimer
= setTimeout(function () {
509 this._fail("Disconnect timeout");
510 }.bind(this), this._disconnectTimeout
* 1000);
514 // WebSocket.onclose transitions to 'disconnected'
518 if (oldstate
=== 'disconnected') {
519 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
520 } else if (oldstate
=== 'normal') {
521 Util
.Error("Error while connected.");
522 } else if (oldstate
=== 'init') {
523 Util
.Error("Error while initializing.");
526 // Make sure we transition to disconnected
527 setTimeout(function () {
528 this._updateState('disconnected');
534 // No state change action to take
537 if (oldstate
=== 'failed' && state
=== 'disconnected') {
538 this._onUpdateState(this, state
, oldstate
);
540 this._onUpdateState(this, state
, oldstate
, statusMsg
);
544 _fail: function (msg
) {
545 this._updateState('failed', msg
);
549 _handle_message: function () {
550 if (this._sock
.rQlen() === 0) {
551 Util
.Warn("handle_message called on an empty receive queue");
555 switch (this._rfb_state
) {
558 Util
.Error("Got data while disconnected");
561 if (this._normal_msg() && this._sock
.rQlen() > 0) {
562 // true means we can continue processing
563 // Give other events a chance to run
564 if (this._msgTimer
=== null) {
565 Util
.Debug("More data to process, creating timer");
566 this._msgTimer
= setTimeout(function () {
567 this._msgTimer
= null;
568 this._handle_message();
571 Util
.Debug("More data to process, existing timer");
581 _checkEvents: function () {
582 if (this._rfb_state
=== 'normal' && !this._viewportDragging
&& this._mouse_arr
.length
> 0) {
583 this._sock
.send(this._mouse_arr
);
584 this._mouse_arr
= [];
588 _handleKeyPress: function (keysym
, down
) {
589 if (this._view_only
) { return; } // View only, skip keyboard, events
590 this._sock
.send(RFB
.messages
.keyEvent(keysym
, down
));
593 _handleMouseButton: function (x
, y
, down
, bmask
) {
595 this._mouse_buttonMask
|= bmask
;
597 this._mouse_buttonMask
^= bmask
;
600 if (this._viewportDrag
) {
601 if (down
&& !this._viewportDragging
) {
602 this._viewportDragging
= true;
603 this._viewportDragPos
= {'x': x
, 'y': y
};
605 // Skip sending mouse events
608 this._viewportDragging
= false;
612 if (this._view_only
) { return; } // View only, skip mouse events
614 this._mouse_arr
= this._mouse_arr
.concat(
615 RFB
.messages
.pointerEvent(this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
));
616 this._sock
.send(this._mouse_arr
);
617 this._mouse_arr
= [];
620 _handleMouseMove: function (x
, y
) {
621 if (this._viewportDragging
) {
622 var deltaX
= this._viewportDragPos
.x
- x
;
623 var deltaY
= this._viewportDragPos
.y
- y
;
624 this._viewportDragPos
= {'x': x
, 'y': y
};
626 this._display
.viewportChangePos(deltaX
, deltaY
);
628 // Skip sending mouse events
632 if (this._view_only
) { return; } // View only, skip mouse events
634 this._mouse_arr
= this._mouse_arr
.concat(
635 RFB
.messages
.pointerEvent(this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
));
642 _negotiate_protocol_version: function () {
643 if (this._sock
.rQlen() < 12) {
644 return this._fail("Incomplete protocol version");
647 var sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
648 Util
.Info("Server ProtocolVersion: " + sversion
);
651 case "000.000": // UltraVNC repeater
655 case "003.006": // UltraVNC
656 case "003.889": // Apple Remote Desktop
657 this._rfb_version
= 3.3;
660 this._rfb_version
= 3.7;
663 case "004.000": // Intel AMT KVM
664 case "004.001": // RealVNC 4.6
665 this._rfb_version
= 3.8;
668 return this._fail("Invalid server version " + sversion
);
672 var repeaterID
= this._repeaterID
;
673 while (repeaterID
.length
< 250) {
676 this._sock
.send_string(repeaterID
);
680 if (this._rfb_version
> this._rfb_max_version
) {
681 this._rfb_version
= this._rfb_max_version
;
684 // Send updates either at a rate of 1 update per 50ms, or
685 // whatever slower rate the network can handle
686 this._sendTimer
= setInterval(this._sock
.flush
.bind(this._sock
), 50);
688 var cversion
= "00" + parseInt(this._rfb_version
, 10) +
689 ".00" + ((this._rfb_version
* 10) % 10);
690 this._sock
.send_string("RFB " + cversion
+ "\n");
691 this._updateState('Security', 'Sent ProtocolVersion: ' + cversion
);
694 _negotiate_security: function () {
695 if (this._rfb_version
>= 3.7) {
696 // Server sends supported list, client decides
697 var num_types
= this._sock
.rQshift8();
698 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
700 if (num_types
=== 0) {
701 var strlen
= this._sock
.rQshift32();
702 var reason
= this._sock
.rQshiftStr(strlen
);
703 return this._fail("Security failure: " + reason
);
706 this._rfb_auth_scheme
= 0;
707 var types
= this._sock
.rQshiftBytes(num_types
);
708 Util
.Debug("Server security types: " + types
);
709 for (var i
= 0; i
< types
.length
; i
++) {
710 if (types
[i
] > this._rfb_auth_scheme
&& (types
[i
] <= 16 || types
[i
] == 22)) {
711 this._rfb_auth_scheme
= types
[i
];
715 if (this._rfb_auth_scheme
=== 0) {
716 return this._fail("Unsupported security types: " + types
);
719 this._sock
.send([this._rfb_auth_scheme
]);
722 if (this._sock
.rQwait("security scheme", 4)) { return false; }
723 this._rfb_auth_scheme
= this._sock
.rQshift32();
726 this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme
);
727 return this._init_msg(); // jump to authentication
731 _negotiate_xvp_auth: function () {
732 var xvp_sep
= this._xvp_password_sep
;
733 var xvp_auth
= this._rfb_password
.split(xvp_sep
);
734 if (xvp_auth
.length
< 3) {
735 this._updateState('password', 'XVP credentials required (user' + xvp_sep
+
736 'target' + xvp_sep
+ 'password) -- got only ' + this._rfb_password
);
737 this._onPasswordRequired(this);
741 var xvp_auth_str
= String
.fromCharCode(xvp_auth
[0].length
) +
742 String
.fromCharCode(xvp_auth
[1].length
) +
745 this._sock
.send_string(xvp_auth_str
);
746 this._rfb_password
= xvp_auth
.slice(2).join(xvp_sep
);
747 this._rfb_auth_scheme
= 2;
748 return this._negotiate_authentication();
751 _negotiate_std_vnc_auth: function () {
752 if (this._rfb_password
.length
=== 0) {
753 // Notify via both callbacks since it's kind of
754 // an RFB state change and a UI interface issue
755 this._updateState('password', "Password Required");
756 this._onPasswordRequired(this);
759 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
761 var challenge
= this._sock
.rQshiftBytes(16);
762 var response
= RFB
.genDES(this._rfb_password
, challenge
);
763 this._sock
.send(response
);
764 this._updateState("SecurityResult");
768 _negotiate_tight_tunnels: function (numTunnels
) {
769 var clientSupportedTunnelTypes
= {
770 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
772 var serverSupportedTunnelTypes
= {};
773 // receive tunnel capabilities
774 for (var i
= 0; i
< numTunnels
; i
++) {
775 var cap_code
= this._sock
.rQshift32();
776 var cap_vendor
= this._sock
.rQshiftStr(4);
777 var cap_signature
= this._sock
.rQshiftStr(8);
778 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
781 // choose the notunnel type
782 if (serverSupportedTunnelTypes
[0]) {
783 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
784 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
785 return this._fail("Client's tunnel type had the incorrect vendor or signature");
787 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
788 return false; // wait until we receive the sub auth count to continue
790 return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
794 _negotiate_tight_auth: function () {
795 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
796 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
797 var numTunnels
= this._sock
.rQshift32();
798 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
800 this._rfb_tightvnc
= true;
802 if (numTunnels
> 0) {
803 this._negotiate_tight_tunnels(numTunnels
);
804 return false; // wait until we receive the sub auth to continue
808 // second pass, do the sub-auth negotiation
809 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
810 var subAuthCount
= this._sock
.rQshift32();
811 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
813 var clientSupportedTypes
= {
818 var serverSupportedTypes
= [];
820 for (var i
= 0; i
< subAuthCount
; i
++) {
821 var capNum
= this._sock
.rQshift32();
822 var capabilities
= this._sock
.rQshiftStr(12);
823 serverSupportedTypes
.push(capabilities
);
826 for (var authType
in clientSupportedTypes
) {
827 if (serverSupportedTypes
.indexOf(authType
) != -1) {
828 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
831 case 'STDVNOAUTH__': // no auth
832 this._updateState('SecurityResult');
834 case 'STDVVNCAUTH_': // VNC auth
835 this._rfb_auth_scheme
= 2;
836 return this._init_msg();
838 return this._fail("Unsupported tiny auth scheme: " + authType
);
843 this._fail("No supported sub-auth types!");
846 _negotiate_authentication: function () {
847 switch (this._rfb_auth_scheme
) {
848 case 0: // connection failed
849 if (this._sock
.rQwait("auth reason", 4)) { return false; }
850 var strlen
= this._sock
.rQshift32();
851 var reason
= this._sock
.rQshiftStr(strlen
);
852 return this._fail("Auth failure: " + reason
);
855 if (this._rfb_version
>= 3.8) {
856 this._updateState('SecurityResult');
859 this._updateState('ClientInitialisation', "No auth required");
860 return this._init_msg();
863 return this._negotiate_xvp_auth();
865 case 2: // VNC authentication
866 return this._negotiate_std_vnc_auth();
868 case 16: // TightVNC Security Type
869 return this._negotiate_tight_auth();
872 return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme
);
876 _handle_security_result: function () {
877 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
878 switch (this._sock
.rQshift32()) {
880 this._updateState('ClientInitialisation', 'Authentication OK');
881 return this._init_msg();
883 if (this._rfb_version
>= 3.8) {
884 var length
= this._sock
.rQshift32();
885 if (this._sock
.rQwait("SecurityResult reason", length
, 8)) { return false; }
886 var reason
= this._sock
.rQshiftStr(length
);
887 return this._fail(reason
);
889 return this._fail("Authentication failure");
893 return this._fail("Too many auth attempts");
897 _negotiate_server_init: function () {
898 if (this._sock
.rQwait("server initialization", 24)) { return false; }
901 this._fb_width
= this._sock
.rQshift16();
902 this._fb_height
= this._sock
.rQshift16();
905 var bpp
= this._sock
.rQshift8();
906 var depth
= this._sock
.rQshift8();
907 var big_endian
= this._sock
.rQshift8();
908 var true_color
= this._sock
.rQshift8();
910 var red_max
= this._sock
.rQshift16();
911 var green_max
= this._sock
.rQshift16();
912 var blue_max
= this._sock
.rQshift16();
913 var red_shift
= this._sock
.rQshift8();
914 var green_shift
= this._sock
.rQshift8();
915 var blue_shift
= this._sock
.rQshift8();
916 this._sock
.rQskipBytes(3); // padding
918 // NB(directxman12): we don't want to call any callbacks or print messages until
919 // *after* we're past the point where we could backtrack
921 /* Connection name/title */
922 var name_length = this._sock.rQshift32();
923 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
924 this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
926 if (this._rfb_tightvnc) {
927 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
928 // In TightVNC mode, ServerInit message is extended
929 var numServerMessages = this._sock.rQshift16();
930 var numClientMessages = this._sock.rQshift16();
931 var numEncodings = this._sock.rQshift16();
932 this._sock.rQskipBytes(2); // padding
934 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
935 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
938 for (i = 0; i < numServerMessages; i++) {
939 var srvMsg = this._sock.rQshiftStr(16);
942 for (i = 0; i < numClientMessages; i++) {
943 var clientMsg = this._sock.rQshiftStr(16);
946 for (i = 0; i < numEncodings; i++) {
947 var encoding = this._sock.rQshiftStr(16);
951 // NB(directxman12): these are down here so that we don't run them multiple times
953 Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
954 ", bpp: " + bpp + ", depth: " + depth +
955 ", big_endian: " + big_endian +
956 ", true_color: " + true_color +
957 ", red_max: " + red_max +
958 ", green_max: " + green_max +
959 ", blue_max: " + blue_max +
960 ", red_shift: " + red_shift +
961 ", green_shift: " + green_shift +
962 ", blue_shift: " + blue_shift);
964 if (big_endian !== 0) {
965 Util.Warn("Server native endian is not little endian");
968 if (red_shift !== 16) {
969 Util.Warn("Server native red-shift is not 16");
972 if (blue_shift !== 0) {
973 Util.Warn("Server native blue-shift is not 0");
976 // we're past the point where we could backtrack, so it's safe to call this
977 this._onDesktopName(this, this._fb_name);
979 if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
980 Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
981 this._true_color = false;
984 this._display.set_true_color(this._true_color);
985 this._display.resize(this._fb_width, this._fb_height);
986 this._onFBResize(this, this._fb_width, this._fb_height);
987 this._keyboard.grab();
990 if (this._true_color) {
998 var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
999 response = response.concat(
1000 RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
1001 response = response.concat(
1002 RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
1003 this._fb_width, this._fb_height));
1005 this._timing.fbu_rt_start = (new Date()).getTime();
1006 this._timing.pixels = 0;
1007 this._sock.send(response);
1009 this._checkEvents();
1011 if (this._encrypt) {
1012 this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
1014 this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
1018 _init_msg: function () {
1019 switch (this._rfb_state) {
1020 case 'ProtocolVersion':
1021 return this._negotiate_protocol_version();
1024 return this._negotiate_security();
1026 case 'Authentication':
1027 return this._negotiate_authentication();
1029 case 'SecurityResult':
1030 return this._handle_security_result();
1032 case 'ClientInitialisation':
1033 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1034 this._updateState('ServerInitialisation', "Authentication OK");
1037 case 'ServerInitialisation':
1038 return this._negotiate_server_init();
1042 _handle_set_colour_map_msg: function () {
1043 Util.Debug("SetColorMapEntries");
1044 this._sock.rQskip8(); // Padding
1046 var first_colour = this._sock.rQshift16();
1047 var num_colours = this._sock.rQshift16();
1048 if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
1050 for (var c = 0; c < num_colours; c++) {
1051 var red = parseInt(this._sock.rQshift16() / 256, 10);
1052 var green = parseInt(this._sock.rQshift16() / 256, 10);
1053 var blue = parseInt(this._sock.rQshift16() / 256, 10);
1054 this._display.set_colourMap([blue, green, red], first_colour + c);
1056 Util.Debug("colourMap: " + this._display.get_colourMap());
1057 Util.Info("Registered " + num_colours + " colourMap entries");
1062 _handle_server_cut_text: function () {
1063 Util.Debug("ServerCutText");
1064 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1065 this._sock.rQskipBytes(3); // Padding
1066 var length = this._sock.rQshift32();
1067 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
1069 var text = this._sock.rQshiftStr(length);
1070 this._onClipboard(this, text);
1075 _handle_xvp_msg: function () {
1076 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1077 this._sock.rQskip8(); // Padding
1078 var xvp_ver = this._sock.rQshift8();
1079 var xvp_msg = this._sock.rQshift8();
1083 this._updateState(this._rfb_state, "Operation Failed");
1086 this._rfb_xvp_ver = xvp_ver;
1087 Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
1088 this._onXvpInit(this._rfb_xvp_ver);
1091 this._fail("Disconnected: illegal server XVP message " + xvp_msg);
1098 _normal_msg: function () {
1101 if (this._FBU.rects > 0) {
1104 msg_type = this._sock.rQshift8();
1108 case 0: // FramebufferUpdate
1109 var ret = this._framebufferUpdate();
1111 this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
1112 this._fb_width, this._fb_height));
1116 case 1: // SetColorMapEntries
1117 return this._handle_set_colour_map_msg();
1124 case 3: // ServerCutText
1125 return this._handle_server_cut_text();
1128 return this._handle_xvp_msg();
1131 this._fail("Disconnected: illegal server message type " + msg_type);
1132 Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1137 _framebufferUpdate: function () {
1141 if (this._FBU.rects === 0) {
1142 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
1143 this._sock.rQskip8(); // Padding
1144 this._FBU.rects = this._sock.rQshift16();
1145 this._FBU.bytes = 0;
1146 this._timing.cur_fbu = 0;
1147 if (this._timing.fbu_rt_start > 0) {
1148 now = (new Date()).getTime();
1149 Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
1153 while (this._FBU.rects > 0) {
1154 if (this._rfb_state !== "normal") { return false; }
1156 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1157 if (this._FBU.bytes === 0) {
1158 if (this._sock.rQwait("rect header", 12)) { return false; }
1159 /* New FramebufferUpdate */
1161 var hdr
= this._sock
.rQshiftBytes(12);
1162 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1163 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1164 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1165 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1166 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1167 (hdr
[10] << 8) + hdr
[11], 10);
1169 this._onFBUReceive(this,
1170 {'x': this._FBU
.x
, 'y': this._FBU
.y
,
1171 'width': this._FBU
.width
, 'height': this._FBU
.height
,
1172 'encoding': this._FBU
.encoding
,
1173 'encodingName': this._encNames
[this._FBU
.encoding
]});
1175 if (!this._encNames
[this._FBU
.encoding
]) {
1176 this._fail("Disconnected: unsupported encoding " +
1177 this._FBU
.encoding
);
1182 this._timing
.last_fbu
= (new Date()).getTime();
1184 ret
= this._encHandlers
[this._FBU
.encoding
]();
1186 now
= (new Date()).getTime();
1187 this._timing
.cur_fbu
+= (now
- this._timing
.last_fbu
);
1190 this._encStats
[this._FBU
.encoding
][0]++;
1191 this._encStats
[this._FBU
.encoding
][1]++;
1192 this._timing
.pixels
+= this._FBU
.width
* this._FBU
.height
;
1195 if (this._timing
.pixels
>= (this._fb_width
* this._fb_height
)) {
1196 if ((this._FBU
.width
=== this._fb_width
&& this._FBU
.height
=== this._fb_height
) ||
1197 this._timing
.fbu_rt_start
> 0) {
1198 this._timing
.full_fbu_total
+= this._timing
.cur_fbu
;
1199 this._timing
.full_fbu_cnt
++;
1200 Util
.Info("Timing of full FBU, curr: " +
1201 this._timing
.cur_fbu
+ ", total: " +
1202 this._timing
.full_fbu_total
+ ", cnt: " +
1203 this._timing
.full_fbu_cnt
+ ", avg: " +
1204 (this._timing
.full_fbu_total
/ this._timing
.full_fbu_cnt
));
1207 if (this._timing
.fbu_rt_start
> 0) {
1208 var fbu_rt_diff
= now
- this._timing
.fbu_rt_start
;
1209 this._timing
.fbu_rt_total
+= fbu_rt_diff
;
1210 this._timing
.fbu_rt_cnt
++;
1211 Util
.Info("full FBU round-trip, cur: " +
1212 fbu_rt_diff
+ ", total: " +
1213 this._timing
.fbu_rt_total
+ ", cnt: " +
1214 this._timing
.fbu_rt_cnt
+ ", avg: " +
1215 (this._timing
.fbu_rt_total
/ this._timing
.fbu_rt_cnt
));
1216 this._timing
.fbu_rt_start
= 0;
1220 if (!ret
) { return ret
; } // need more data
1223 this._onFBUComplete(this,
1224 {'x': this._FBU
.x
, 'y': this._FBU
.y
,
1225 'width': this._FBU
.width
, 'height': this._FBU
.height
,
1226 'encoding': this._FBU
.encoding
,
1227 'encodingName': this._encNames
[this._FBU
.encoding
]});
1229 return true; // We finished this FBU
1233 Util
.make_properties(RFB
, [
1234 ['target', 'wo', 'dom'], // VNC display rendering Canvas object
1235 ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
1236 ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
1237 ['true_color', 'rw', 'bool'], // Request true color pixel data
1238 ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
1239 ['shared', 'rw', 'bool'], // Request shared mode
1240 ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
1241 ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
1242 ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
1243 ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
1244 ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
1245 ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
1247 // Callback functions
1248 ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
1249 ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
1250 ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
1251 ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
1252 ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
1253 ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
1254 ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
1255 ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
1256 ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
1259 RFB
.prototype.set_local_cursor = function (cursor
) {
1260 if (!cursor
|| (cursor
in {'0': 1, 'no': 1, 'false': 1})) {
1261 this._local_cursor
= false;
1262 this._display
.disableLocalCursor(); //Only show server-side cursor
1264 if (this._display
.get_cursor_uri()) {
1265 this._local_cursor
= true;
1267 Util
.Warn("Browser does not support local cursor");
1272 RFB
.prototype.get_display = function () { return this._display
; };
1273 RFB
.prototype.get_keyboard = function () { return this._keyboard
; };
1274 RFB
.prototype.get_mouse = function () { return this._mouse
; };
1278 keyEvent: function (keysym
, down
) {
1286 pointerEvent: function (x
, y
, mask
) {
1287 var arr
= [5]; // msg-type
1294 // TODO(directxman12): make this unicode compatible?
1295 clientCutText: function (text
) {
1296 var arr
= [6]; // msg-type
1297 arr
.push8(0); // padding
1298 arr
.push8(0); // padding
1299 arr
.push8(0); // padding
1300 arr
.push32(text
.length
);
1301 var n
= text
.length
;
1302 for (var i
= 0; i
< n
; i
++) {
1303 arr
.push(text
.charCodeAt(i
));
1309 pixelFormat: function (bpp
, depth
, true_color
) {
1310 var arr
= [0]; // msg-type
1311 arr
.push8(0); // padding
1312 arr
.push8(0); // padding
1313 arr
.push8(0); // padding
1315 arr
.push8(bpp
* 8); // bits-per-pixel
1316 arr
.push8(depth
* 8); // depth
1317 arr
.push8(0); // little-endian
1318 arr
.push8(true_color
? 1 : 0); // true-color
1320 arr
.push16(255); // red-max
1321 arr
.push16(255); // green-max
1322 arr
.push16(255); // blue-max
1323 arr
.push8(16); // red-shift
1324 arr
.push8(8); // green-shift
1325 arr
.push8(0); // blue-shift
1327 arr
.push8(0); // padding
1328 arr
.push8(0); // padding
1329 arr
.push8(0); // padding
1333 clientEncodings: function (encodings
, local_cursor
, true_color
) {
1334 var i
, encList
= [];
1336 for (i
= 0; i
< encodings
.length
; i
++) {
1337 if (encodings
[i
][0] === "Cursor" && !local_cursor
) {
1338 Util
.Debug("Skipping Cursor pseudo-encoding");
1339 } else if (encodings
[i
][0] === "TIGHT" && !true_color
) {
1340 // TODO: remove this when we have tight+non-true-color
1341 Util
.Warn("Skipping tight as it is only supported with true color");
1343 encList
.push(encodings
[i
][1]);
1347 var arr
= [2]; // msg-type
1348 arr
.push8(0); // padding
1350 arr
.push16(encList
.length
); // encoding count
1351 for (i
= 0; i
< encList
.length
; i
++) {
1352 arr
.push32(encList
[i
]);
1358 fbUpdateRequests: function (cleanDirty
, fb_width
, fb_height
) {
1361 var cb
= cleanDirty
.cleanBox
;
1363 if (cb
.w
> 0 && cb
.h
> 0) {
1364 w
= typeof cb
.w
=== "undefined" ? fb_width
: cb
.w
;
1365 h
= typeof cb
.h
=== "undefined" ? fb_height
: cb
.h
;
1366 // Request incremental for clean box
1367 arr
= arr
.concat(RFB
.messages
.fbUpdateRequest(1, cb
.x
, cb
.y
, w
, h
));
1370 for (var i
= 0; i
< cleanDirty
.dirtyBoxes
.length
; i
++) {
1371 var db
= cleanDirty
.dirtyBoxes
[i
];
1372 // Force all (non-incremental) for dirty box
1373 w
= typeof db
.w
=== "undefined" ? fb_width
: db
.w
;
1374 h
= typeof db
.h
=== "undefined" ? fb_height
: db
.h
;
1375 arr
= arr
.concat(RFB
.messages
.fbUpdateRequest(0, db
.x
, db
.y
, w
, h
));
1381 fbUpdateRequest: function (incremental
, x
, y
, w
, h
) {
1382 if (typeof(x
) === "undefined") { x
= 0; }
1383 if (typeof(y
) === "undefined") { y
= 0; }
1385 var arr
= [3]; // msg-type
1386 arr
.push8(incremental
);
1396 RFB
.genDES = function (password
, challenge
) {
1398 for (var i
= 0; i
< password
.length
; i
++) {
1399 passwd
.push(password
.charCodeAt(i
));
1401 return (new DES(passwd
)).encrypt(challenge
);
1404 RFB
.extract_data_uri = function (arr
) {
1405 return ";base64," + Base64
.encode(arr
);
1408 RFB
.encodingHandlers
= {
1410 if (this._FBU
.lines
=== 0) {
1411 this._FBU
.lines
= this._FBU
.height
;
1414 this._FBU
.bytes
= this._FBU
.width
* this._fb_Bpp
; // at least a line
1415 if (this._sock
.rQwait("RAW", this._FBU
.bytes
)) { return false; }
1416 var cur_y
= this._FBU
.y
+ (this._FBU
.height
- this._FBU
.lines
);
1417 var curr_height
= Math
.min(this._FBU
.lines
,
1418 Math
.floor(this._sock
.rQlen() / (this._FBU
.width
* this._fb_Bpp
)));
1419 this._display
.blitImage(this._FBU
.x
, cur_y
, this._FBU
.width
,
1420 curr_height
, this._sock
.get_rQ(),
1421 this._sock
.get_rQi());
1422 this._sock
.rQskipBytes(this._FBU
.width
* curr_height
* this._fb_Bpp
);
1423 this._FBU
.lines
-= curr_height
;
1425 if (this._FBU
.lines
> 0) {
1426 this._FBU
.bytes
= this._FBU
.width
* this._fb_Bpp
; // At least another line
1429 this._FBU
.bytes
= 0;
1435 COPYRECT: function () {
1436 this._FBU
.bytes
= 4;
1437 if (this._sock
.rQwait("COPYRECT", 4)) { return false; }
1438 this._display
.renderQ_push({
1440 'old_x': this._sock
.rQshift16(),
1441 'old_y': this._sock
.rQshift16(),
1444 'width': this._FBU
.width
,
1445 'height': this._FBU
.height
1448 this._FBU
.bytes
= 0;
1454 if (this._FBU
.subrects
=== 0) {
1455 this._FBU
.bytes
= 4 + this._fb_Bpp
;
1456 if (this._sock
.rQwait("RRE", 4 + this._fb_Bpp
)) { return false; }
1457 this._FBU
.subrects
= this._sock
.rQshift32();
1458 color
= this._sock
.rQshiftBytes(this._fb_Bpp
); // Background
1459 this._display
.fillRect(this._FBU
.x
, this._FBU
.y
, this._FBU
.width
, this._FBU
.height
, color
);
1462 while (this._FBU
.subrects
> 0 && this._sock
.rQlen() >= (this._fb_Bpp
+ 8)) {
1463 color
= this._sock
.rQshiftBytes(this._fb_Bpp
);
1464 var x
= this._sock
.rQshift16();
1465 var y
= this._sock
.rQshift16();
1466 var width
= this._sock
.rQshift16();
1467 var height
= this._sock
.rQshift16();
1468 this._display
.fillRect(this._FBU
.x
+ x
, this._FBU
.y
+ y
, width
, height
, color
);
1469 this._FBU
.subrects
--;
1472 if (this._FBU
.subrects
> 0) {
1473 var chunk
= Math
.min(this._rre_chunk_sz
, this._FBU
.subrects
);
1474 this._FBU
.bytes
= (this._fb_Bpp
+ 8) * chunk
;
1477 this._FBU
.bytes
= 0;
1483 HEXTILE: function () {
1484 var rQ
= this._sock
.get_rQ();
1485 var rQi
= this._sock
.get_rQi();
1487 if (this._FBU
.tiles
=== 0) {
1488 this._FBU
.tiles_x
= Math
.ceil(this._FBU
.width
/ 16);
1489 this._FBU
.tiles_y
= Math
.ceil(this._FBU
.height
/ 16);
1490 this._FBU
.total_tiles
= this._FBU
.tiles_x
* this._FBU
.tiles_y
;
1491 this._FBU
.tiles
= this._FBU
.total_tiles
;
1494 while (this._FBU
.tiles
> 0) {
1495 this._FBU
.bytes
= 1;
1496 if (this._sock
.rQwait("HEXTILE subencoding", this._FBU
.bytes
)) { return false; }
1497 var subencoding
= rQ
[rQi
]; // Peek
1498 if (subencoding
> 30) { // Raw
1499 this._fail("Disconnected: illegal hextile subencoding " + subencoding
);
1504 var curr_tile
= this._FBU
.total_tiles
- this._FBU
.tiles
;
1505 var tile_x
= curr_tile
% this._FBU
.tiles_x
;
1506 var tile_y
= Math
.floor(curr_tile
/ this._FBU
.tiles_x
);
1507 var x
= this._FBU
.x
+ tile_x
* 16;
1508 var y
= this._FBU
.y
+ tile_y
* 16;
1509 var w
= Math
.min(16, (this._FBU
.x
+ this._FBU
.width
) - x
);
1510 var h
= Math
.min(16, (this._FBU
.y
+ this._FBU
.height
) - y
);
1512 // Figure out how much we are expecting
1513 if (subencoding
& 0x01) { // Raw
1514 this._FBU
.bytes
+= w
* h
* this._fb_Bpp
;
1516 if (subencoding
& 0x02) { // Background
1517 this._FBU
.bytes
+= this._fb_Bpp
;
1519 if (subencoding
& 0x04) { // Foreground
1520 this._FBU
.bytes
+= this._fb_Bpp
;
1522 if (subencoding
& 0x08) { // AnySubrects
1523 this._FBU
.bytes
++; // Since we aren't shifting it off
1524 if (this._sock
.rQwait("hextile subrects header", this._FBU
.bytes
)) { return false; }
1525 subrects
= rQ
[rQi
+ this._FBU
.bytes
- 1]; // Peek
1526 if (subencoding
& 0x10) { // SubrectsColoured
1527 this._FBU
.bytes
+= subrects
* (this._fb_Bpp
+ 2);
1529 this._FBU
.bytes
+= subrects
* 2;
1534 if (this._sock
.rQwait("hextile", this._FBU
.bytes
)) { return false; }
1536 // We know the encoding and have a whole tile
1537 this._FBU
.subencoding
= rQ
[rQi
];
1539 if (this._FBU
.subencoding
=== 0) {
1540 if (this._FBU
.lastsubencoding
& 0x01) {
1541 // Weird: ignore blanks are RAW
1542 Util
.Debug(" Ignoring blank after RAW");
1544 this._display
.fillRect(x
, y
, w
, h
, this._FBU
.background
);
1546 } else if (this._FBU
.subencoding
& 0x01) { // Raw
1547 this._display
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1548 rQi
+= this._FBU
.bytes
- 1;
1550 if (this._FBU
.subencoding
& 0x02) { // Background
1551 this._FBU
.background
= rQ
.slice(rQi
, rQi
+ this._fb_Bpp
);
1552 rQi
+= this._fb_Bpp
;
1554 if (this._FBU
.subencoding
& 0x04) { // Foreground
1555 this._FBU
.foreground
= rQ
.slice(rQi
, rQi
+ this._fb_Bpp
);
1556 rQi
+= this._fb_Bpp
;
1559 this._display
.startTile(x
, y
, w
, h
, this._FBU
.background
);
1560 if (this._FBU
.subencoding
& 0x08) { // AnySubrects
1564 for (var s
= 0; s
< subrects
; s
++) {
1566 if (this._FBU
.subencoding
& 0x10) { // SubrectsColoured
1567 color
= rQ
.slice(rQi
, rQi
+ this._fb_Bpp
);
1568 rQi
+= this._fb_Bpp
;
1570 color
= this._FBU
.foreground
;
1575 var sy
= (xy
& 0x0f);
1579 var sw
= (wh
>> 4) + 1;
1580 var sh
= (wh
& 0x0f) + 1;
1582 this._display
.subTile(sx
, sy
, sw
, sh
, color
);
1585 this._display
.finishTile();
1587 this._sock
.set_rQi(rQi
);
1588 this._FBU
.lastsubencoding
= this._FBU
.subencoding
;
1589 this._FBU
.bytes
= 0;
1593 if (this._FBU
.tiles
=== 0) {
1600 getTightCLength: function (arr
) {
1601 var header
= 1, data
= 0;
1602 data
+= arr
[0] & 0x7f;
1603 if (arr
[0] & 0x80) {
1605 data
+= (arr
[1] & 0x7f) << 7;
1606 if (arr
[1] & 0x80) {
1608 data
+= arr
[2] << 14;
1611 return [header
, data
];
1614 display_tight: function (isTightPNG
) {
1615 if (this._fb_depth
=== 1) {
1616 this._fail("Tight protocol handler only implements true color mode");
1619 this._FBU
.bytes
= 1; // compression-control byte
1620 if (this._sock
.rQwait("TIGHT compression-control", this._FBU
.bytes
)) { return false; }
1622 var checksum = function (data
) {
1624 for (var i
= 0; i
< data
.length
; i
++) {
1626 if (sum
> 65536) sum
-= 65536;
1631 var resetStreams
= 0;
1633 var decompress = function (data
) {
1634 for (var i
= 0; i
< 4; i
++) {
1635 if ((resetStreams
>> i
) & 1) {
1636 this._FBU
.zlibs
[i
].reset();
1637 Util
.Info("Reset zlib stream " + i
);
1641 var uncompressed
= this._FBU
.zlibs
[streamId
].uncompress(data
, 0);
1642 if (uncompressed
.status
!== 0) {
1643 Util
.Error("Invalid data in zlib stream");
1646 return uncompressed
.data
;
1649 var indexedToRGB = function (data
, numColors
, palette
, width
, height
) {
1650 // Convert indexed (palette based) image data to RGB
1651 // TODO: reduce number of calculations inside loop
1654 if (numColors
=== 2) {
1655 var w
= Math
.floor((width
+ 7) / 8);
1656 var w1
= Math
.floor(width
/ 8);
1658 for (y
= 0; y
< height
; y
++) {
1660 for (x
= 0; x
< w1
; x
++) {
1661 for (b
= 7; b
>= 0; b
--) {
1662 dp
= (y
* width
+ x
* 8 + 7 - b
) * 3;
1663 sp
= (data
[y
* w
+ x
] >> b
& 1) * 3;
1664 dest
[dp
] = palette
[sp
];
1665 dest
[dp
+ 1] = palette
[sp
+ 1];
1666 dest
[dp
+ 2] = palette
[sp
+ 2];
1670 for (b
= 7; b
>= 8 - width
% 8; b
--) {
1671 dp
= (y
* width
+ x
* 8 + 7 - b
) * 3;
1672 sp
= (data
[y
* w
+ x
] >> b
& 1) * 3;
1673 dest
[dp
] = palette
[sp
];
1674 dest
[dp
+ 1] = palette
[sp
+ 1];
1675 dest
[dp
+ 2] = palette
[sp
+ 2];
1679 for (y
= 0; y
< height
; y
++) {
1680 for (x
= 0; x
< width
; x
++) {
1681 dp
= (y
* width
+ x
) * 3;
1682 sp
= data
[y
* width
+ x
] * 3;
1683 dest
[dp
] = palette
[sp
];
1684 dest
[dp
+ 1] = palette
[sp
+ 1];
1685 dest
[dp
+ 2] = palette
[sp
+ 2];
1693 var rQ
= this._sock
.get_rQ();
1694 var rQi
= this._sock
.get_rQi();
1695 var cmode
, clength
, data
;
1697 var handlePalette = function () {
1698 var numColors
= rQ
[rQi
+ 2] + 1;
1699 var paletteSize
= numColors
* this._fb_depth
;
1700 this._FBU
.bytes
+= paletteSize
;
1701 if (this._sock
.rQwait("TIGHT palette " + cmode
, this._FBU
.bytes
)) { return false; }
1703 var bpp
= (numColors
<= 2) ? 1 : 8;
1704 var rowSize
= Math
.floor((this._FBU
.width
* bpp
+ 7) / 8);
1706 if (rowSize
* this._FBU
.height
< 12) {
1708 clength
= [0, rowSize
* this._FBU
.height
];
1710 clength
= RFB
.encodingHandlers
.getTightCLength(this._sock
.rQslice(3 + paletteSize
,
1711 3 + paletteSize
+ 3));
1714 this._FBU
.bytes
+= clength
[0] + clength
[1];
1715 if (this._sock
.rQwait("TIGHT " + cmode
, this._FBU
.bytes
)) { return false; }
1717 // Shift ctl, filter id, num colors, palette entries, and clength off
1718 this._sock
.rQskipBytes(3);
1719 var palette
= this._sock
.rQshiftBytes(paletteSize
);
1720 this._sock
.rQskipBytes(clength
[0]);
1723 data
= this._sock
.rQshiftBytes(clength
[1]);
1725 data
= decompress(this._sock
.rQshiftBytes(clength
[1]));
1728 // Convert indexed (palette based) image data to RGB
1729 var rgb
= indexedToRGB(data
, numColors
, palette
, this._FBU
.width
, this._FBU
.height
);
1731 this._display
.renderQ_push({
1736 'width': this._FBU
.width
,
1737 'height': this._FBU
.height
1743 var handleCopy = function () {
1745 var uncompressedSize
= this._FBU
.width
* this._FBU
.height
* this._fb_depth
;
1746 if (uncompressedSize
< 12) {
1748 clength
= [0, uncompressedSize
];
1750 clength
= RFB
.encodingHandlers
.getTightCLength(this._sock
.rQslice(1, 4));
1752 this._FBU
.bytes
= 1 + clength
[0] + clength
[1];
1753 if (this._sock
.rQwait("TIGHT " + cmode
, this._FBU
.bytes
)) { return false; }
1755 // Shift ctl, clength off
1756 this._sock
.rQshiftBytes(1 + clength
[0]);
1759 data
= this._sock
.rQshiftBytes(clength
[1]);
1761 data
= decompress(this._sock
.rQshiftBytes(clength
[1]));
1764 this._display
.renderQ_push({
1769 'width': this._FBU
.width
,
1770 'height': this._FBU
.height
1776 var ctl
= this._sock
.rQpeek8();
1778 // Keep tight reset bits
1779 resetStreams
= ctl
& 0xF;
1781 // Figure out filter
1783 streamId
= ctl
& 0x3;
1785 if (ctl
=== 0x08) cmode
= "fill";
1786 else if (ctl
=== 0x09) cmode
= "jpeg";
1787 else if (ctl
=== 0x0A) cmode
= "png";
1788 else if (ctl
& 0x04) cmode
= "filter";
1789 else if (ctl
< 0x04) cmode
= "copy";
1790 else return this._fail("Illegal tight compression received, ctl: " + ctl
);
1792 if (isTightPNG
&& (cmode
=== "filter" || cmode
=== "copy")) {
1793 return this._fail("filter/copy received in tightPNG mode");
1797 // fill use fb_depth because TPIXELs drop the padding byte
1798 case "fill": // TPIXEL
1799 this._FBU
.bytes
+= this._fb_depth
;
1801 case "jpeg": // max clength
1802 this._FBU
.bytes
+= 3;
1804 case "png": // max clength
1805 this._FBU
.bytes
+= 3;
1807 case "filter": // filter id + num colors if palette
1808 this._FBU
.bytes
+= 2;
1814 if (this._sock
.rQwait("TIGHT " + cmode
, this._FBU
.bytes
)) { return false; }
1816 // Determine FBU.bytes
1819 this._sock
.rQskip8(); // shift off ctl
1820 var color
= this._sock
.rQshiftBytes(this._fb_depth
);
1821 this._display
.renderQ_push({
1825 'width': this._FBU
.width
,
1826 'height': this._FBU
.height
,
1827 'color': [color
[2], color
[1], color
[0]]
1832 clength
= RFB
.encodingHandlers
.getTightCLength(this._sock
.rQslice(1, 4));
1833 this._FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1834 if (this._sock
.rQwait("TIGHT " + cmode
, this._FBU
.bytes
)) { return false; }
1836 // We have everything, render it
1837 this._sock
.rQskipBytes(1 + clength
[0]); // shift off clt + compact length
1838 var img
= new Image();
1839 img
.src
= "data: image/" + cmode
+
1840 RFB
.extract_data_uri(this._sock
.rQshiftBytes(clength
[1]));
1841 this._display
.renderQ_push({
1850 var filterId
= rQ
[rQi
+ 1];
1851 if (filterId
=== 1) {
1852 if (!handlePalette()) { return false; }
1854 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1855 // Filter 2, Gradient is valid but not use if jpeg is enabled
1856 // TODO(directxman12): why aren't we just calling '_fail' here
1857 throw new Error("Unsupported tight subencoding received, filter: " + filterId
);
1861 if (!handleCopy()) { return false; }
1866 this._FBU
.bytes
= 0;
1872 TIGHT: function () { return this._encHandlers
.display_tight(false); },
1873 TIGHT_PNG: function () { return this._encHandlers
.display_tight(true); },
1875 last_rect: function () {
1876 this._FBU
.rects
= 0;
1880 handle_FB_resize: function () {
1881 this._fb_width
= this._FBU
.width
;
1882 this._fb_height
= this._FBU
.height
;
1883 this._display
.resize(this._fb_width
, this._fb_height
);
1884 this._onFBResize(this, this._fb_width
, this._fb_height
);
1885 this._timing
.fbu_rt_start
= (new Date()).getTime();
1887 this._FBU
.bytes
= 0;
1888 this._FBU
.rects
-= 1;
1892 ExtendedDesktopSize: function () {
1893 this._FBU
.bytes
= 1;
1894 if (this._sock
.rQwait("ExtendedDesktopSize", this._FBU
.bytes
)) { return false; }
1896 this._supportsSetDesktopSize
= true;
1897 var number_of_screens
= this._sock
.rQpeek8();
1899 this._FBU
.bytes
= 4 + (number_of_screens
* 16);
1900 if (this._sock
.rQwait("ExtendedDesktopSize", this._FBU
.bytes
)) { return false; }
1902 this._sock
.rQskipBytes(1); // number-of-screens
1903 this._sock
.rQskipBytes(3); // padding
1905 for (var i
=0; i
<number_of_screens
; i
+= 1) {
1906 // Save the id and flags of the first screen
1908 this._screen_id
= this._sock
.rQshiftBytes(4); // id
1909 this._sock
.rQskipBytes(2); // x-position
1910 this._sock
.rQskipBytes(2); // y-position
1911 this._sock
.rQskipBytes(2); // width
1912 this._sock
.rQskipBytes(2); // height
1913 this._screen_flags
= this._sock
.rQshiftBytes(4); // flags
1915 this._sock
.rQskipBytes(16);
1920 * The x-position indicates the reason for the change:
1922 * 0 - server resized on its own
1923 * 1 - this client requested the resize
1924 * 2 - another client requested the resize
1927 // We need to handle errors when we requested the resize.
1928 if (this._FBU
.x
== 1 && this._FBU
.y
!= 0) {
1930 // The y-position indicates the status code from the server
1931 switch (this._FBU
.y
) {
1933 msg
= "Resize is administratively prohibited";
1936 msg
= "Out of resources";
1939 msg
= "Invalid screen layout";
1942 msg
= "Unknown reason";
1945 Util
.Info("Server did not accept the resize request: " + msg
);
1949 this._encHandlers
.handle_FB_resize();
1953 DesktopSize: function () {
1954 this._encHandlers
.handle_FB_resize();
1958 Cursor: function () {
1959 Util
.Debug(">> set_cursor");
1960 var x
= this._FBU
.x
; // hotspot-x
1961 var y
= this._FBU
.y
; // hotspot-y
1962 var w
= this._FBU
.width
;
1963 var h
= this._FBU
.height
;
1965 var pixelslength
= w
* h
* this._fb_Bpp
;
1966 var masklength
= Math
.floor((w
+ 7) / 8) * h
;
1968 this._FBU
.bytes
= pixelslength
+ masklength
;
1969 if (this._sock
.rQwait("cursor encoding", this._FBU
.bytes
)) { return false; }
1971 this._display
.changeCursor(this._sock
.rQshiftBytes(pixelslength
),
1972 this._sock
.rQshiftBytes(masklength
),
1975 this._FBU
.bytes
= 0;
1978 Util
.Debug("<< set_cursor");
1982 JPEG_quality_lo: function () {
1983 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1986 compress_lo: function () {
1987 Util
.Error("Server sent compress level pseudo-encoding");