]>
git.proxmox.com Git - mirror_novnc.git/blob - include/rfb.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
10 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
11 /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */
16 conf
= conf
|| {}; // Configuration
17 var that
= {}, // Public API interface
19 // Pre-declare private functions used before definitions (jslint)
20 init_vars
, updateState
, init_msg
, normal_msg
, recv_message
,
21 framebufferUpdate
, print_stats
,
23 pixelFormat
, clientEncodings
, fbUpdateRequest
,
24 keyEvent
, pointerEvent
, clientCutText
,
26 extract_data_uri
, scan_tight_imgQ
,
28 send_array
, checkEvents
, // Overridable for testing
32 // Private RFB namespace variables
38 rfb_state
= 'disconnected',
44 // In preference order
51 ['DesktopSize', -223 ],
54 // Psuedo-encoding settings
55 ['JPEG_quality_lo', -32 ],
56 //['JPEG_quality_hi', -23 ],
57 ['compress_lo', -255 ]
58 //['compress_hi', -247 ]
63 encStats
= {}, // [rectCnt, rectCntTot]
65 ws
= null, // Web Socket object
66 canvas
= null, // Canvas object
67 sendTimer
= null, // Send Queue check timer
68 connTimer
= null, // connection timer
69 disconnTimer
= null, // disconnection timer
70 msgTimer
= null, // queued handle_message timer
72 // Receive and send queues
73 rQ
= [], // Receive Queue
74 rQi
= 0, // Receive Queue Index
75 rQmax
= 100000, // Max size before compacting
76 sQ
= [], // Send Queue
78 // Frame buffer update state
92 imgQ
: [] // TIGHT_PNG image queue
101 scan_imgQ_rate
= 100,
120 mouse_buttonMask
= 0,
125 // Configuration settings
127 function cdef(v
, type
, defval
, desc
) {
128 Util
.conf_default(conf
, that
, v
, type
, defval
, desc
); }
130 cdef('target', 'str', null, 'VNC viewport rendering Canvas');
131 cdef('focusContainer', 'dom', document
, 'Area that traps keyboard input');
133 cdef('encrypt', 'bool', false, 'Use TLS/SSL/wss encryption');
134 cdef('true_color', 'bool', true, 'Request true color pixel data');
135 cdef('local_cursor', 'bool', false, 'Request locally rendered cursor');
136 cdef('shared', 'bool', true, 'Request shared mode');
138 cdef('connectTimeout', 'int', 2, 'Time (s) to wait for connection');
139 cdef('disconnectTimeout', 'int', 3, 'Time (s) to wait for disconnection');
140 cdef('check_rate', 'int', 217, 'Timing (ms) of send/receive check');
141 cdef('fbu_req_rate', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests');
144 'func', function() { Util
.Debug("updateState stub"); },
145 'callback: state update');
146 cdef('clipboardReceive',
147 'func', function() { Util
.Debug("clipboardReceive stub"); },
148 'callback: clipboard contents received');
151 // Override/add some specific getters/setters
152 that
.set_local_cursor = function(cursor
) {
153 if ((!cursor
) || (cursor
in {'0':1, 'no':1, 'false':1})) {
154 conf
.local_cursor
= false;
156 if (canvas
.get_cursor_uri()) {
157 conf
.local_cursor
= true;
159 Util
.Warn("Browser does not support local cursor");
164 that
.get_canvas = function() {
176 // Receive Queue functions
179 return rQ
.length
- rQi
;
182 function rQshift16() {
183 return (rQ
[rQi
++] << 8) +
186 function rQshift32() {
187 return (rQ
[rQi
++] << 24) +
192 function rQshiftStr(len
) {
193 var arr
= rQ
.slice(rQi
, rQi
+ len
);
195 return arr
.map(function (num
) {
196 return String
.fromCharCode(num
); } ).join('');
199 function rQshiftBytes(len
) {
201 return rQ
.slice(rQi
-len
, rQi
);
204 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
205 // to be available in the receive queue. Return true if we need to
206 // wait (and possibly print a debug message), otherwise false.
207 function rQwait(msg
, num
, goback
) {
208 if (typeof num
!== 'number') { num
= FBU
.bytes
; }
209 var rQlen
= rQ
.length
- rQi
; // Skip rQlen() function call
213 throw("rQwait cannot backup " + goback
+ " bytes");
217 //Util.Debug(" waiting for " + (num-rQlen) +
218 // " " + msg + " byte(s)");
219 return true; // true means need more data
229 // Create the public API interface and initialize
230 function constructor() {
232 Util
.Debug(">> RFB.constructor");
234 // Create lookup tables based encoding number
235 for (i
=0; i
< encodings
.length
; i
+=1) {
236 encHandlers
[encodings
[i
][1]] = encHandlers
[encodings
[i
][0]];
237 encNames
[encodings
[i
][1]] = encodings
[i
][0];
238 encStats
[encodings
[i
][1]] = [0, 0];
242 canvas
= new Canvas({'target': conf
.target
,
243 'focusContainer': conf
.focusContainer
});
245 Util
.Error("Canvas exception: " + exc
);
246 updateState('fatal', "No working Canvas");
248 rmode
= canvas
.get_render_mode();
252 /* Check web-socket-js if no builtin WebSocket support */
254 Util
.Info("Using native WebSockets");
255 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
257 Util
.Warn("Using web-socket-js bridge. Flash version: " +
259 if ((! Util
.Flash
) ||
260 (Util
.Flash
.version
< 9)) {
261 updateState('fatal', "WebSockets or Adobe Flash is required");
262 } else if (document
.location
.href
.substr(0, 7) === "file://") {
264 "'file://' URL is incompatible with Adobe Flash");
266 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
270 Util
.Debug("<< RFB.constructor");
271 return that
; // Return the public API interface
275 Util
.Debug(">> RFB.init_ws");
283 uri
+= rfb_host
+ ":" + rfb_port
+ "/";
284 Util
.Info("connecting to " + uri
);
285 ws
= new WebSocket(uri
);
287 ws
.onmessage
= recv_message
;
288 ws
.onopen = function(e
) {
289 Util
.Debug(">> WebSocket.onopen");
290 if (rfb_state
=== "connect") {
291 updateState('ProtocolVersion', "Starting VNC handshake");
293 fail("Got unexpected WebSockets connection");
295 Util
.Debug("<< WebSocket.onopen");
297 ws
.onclose = function(e
) {
298 Util
.Debug(">> WebSocket.onclose");
299 if (rfb_state
=== 'disconnect') {
300 updateState('disconnected', 'VNC disconnected');
301 } else if (rfb_state
=== 'ProtocolVersion') {
302 fail('Failed to connect to server');
303 } else if (rfb_state
in {'failed':1, 'disconnected':1}) {
304 Util
.Error("Received onclose while disconnected");
306 fail('Server disconnected');
308 Util
.Debug("<< WebSocket.onclose");
310 ws
.onerror = function(e
) {
311 Util
.Debug(">> WebSocket.onerror");
312 fail("WebSocket error");
313 Util
.Debug("<< WebSocket.onerror");
316 Util
.Debug("<< RFB.init_ws");
319 init_vars = function() {
325 FBU
.subrects
= 0; // RRE and HEXTILE
326 FBU
.lines
= 0; // RAW
327 FBU
.tiles
= 0; // HEXTILE
328 FBU
.imgQ
= []; // TIGHT_PNG image queue
329 mouse_buttonMask
= 0;
332 // Clear the per connection encoding stats
333 for (var i
=0; i
< encodings
.length
; i
+=1) {
334 encStats
[encodings
[i
][1]][0] = 0;
339 print_stats = function() {
341 Util
.Info("Encoding stats for this connection:");
342 for (i
=0; i
< encodings
.length
; i
+=1) {
343 s
= encStats
[encodings
[i
][1]];
344 if ((s
[0] + s
[1]) > 0) {
345 Util
.Info(" " + encodings
[i
][0] + ": " +
349 Util
.Info("Encoding stats since page load:");
350 for (i
=0; i
< encodings
.length
; i
+=1) {
351 s
= encStats
[encodings
[i
][1]];
352 if ((s
[0] + s
[1]) > 0) {
353 Util
.Info(" " + encodings
[i
][0] + ": "
366 * disconnected - idle state
370 * loaded - page load, equivalent to disconnected
371 * connect - starting initialization
372 * disconnect - starting disconnect
373 * failed - abnormal transition to disconnected
374 * fatal - failed to load page, or fatal error
376 * VNC initialization states:
380 * password - waiting for password, not part of RFB
382 * ClientInitialization - not triggered by server message
383 * ServerInitialization
385 updateState = function(state
, statusMsg
) {
386 var func
, cmsg
, oldstate
= rfb_state
;
388 if (state
=== oldstate
) {
389 /* Already here, ignore */
390 Util
.Debug("Already in state '" + state
+ "', ignoring.");
395 * These are disconnected states. A previous connect may
396 * asynchronously cause a connection so make sure we are closed.
398 if (state
in {'disconnected':1, 'loaded':1, 'connect':1,
399 'disconnect':1, 'failed':1, 'fatal':1}) {
401 clearInterval(sendTimer
);
406 clearInterval(msgTimer
);
410 if (canvas
&& canvas
.getContext()) {
412 if (Util
.get_logging() !== 'debug') {
418 if ((ws
.readyState
=== WebSocket
.OPEN
) ||
419 (ws
.readyState
=== WebSocket
.CONNECTING
)) {
420 Util
.Info("Closing WebSocket connection");
423 ws
.onmessage = function (e
) { return; };
427 if (oldstate
=== 'fatal') {
428 Util
.Error("Fatal error, cannot continue");
431 if ((state
=== 'failed') || (state
=== 'fatal')) {
437 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
438 // Do disconnect action, but stay in failed state.
439 rfb_state
= 'failed';
444 cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
445 func("New state '" + rfb_state
+ "', was '" + oldstate
+ "'." + cmsg
);
447 if (connTimer
&& (rfb_state
!== 'connect')) {
448 Util
.Debug("Clearing connect timer");
449 clearInterval(connTimer
);
453 if (disconnTimer
&& (rfb_state
!== 'disconnect')) {
454 Util
.Debug("Clearing disconnect timer");
455 clearInterval(disconnTimer
);
461 if ((oldstate
=== 'disconnected') || (oldstate
=== 'failed')) {
462 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
470 connTimer
= setTimeout(function () {
471 fail("Connect timeout");
472 }, conf
.connectTimeout
* 1000);
477 // WebSocket.onopen transitions to 'ProtocolVersion'
484 disconnTimer
= setTimeout(function () {
485 fail("Disconnect timeout");
486 }, conf
.disconnectTimeout
* 1000);
491 // WebSocket.onclose transitions to 'disconnected'
496 if (oldstate
=== 'disconnected') {
497 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
499 if (oldstate
=== 'normal') {
500 Util
.Error("Error while connected.");
502 if (oldstate
=== 'init') {
503 Util
.Error("Error while initializing.");
506 // Make sure we transition to disconnected
507 setTimeout(function() { updateState('disconnected'); }, 50);
513 // No state change action to take
517 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
518 // Leave the failed message
519 conf
.updateState(that
, state
, oldstate
);
521 conf
.updateState(that
, state
, oldstate
, statusMsg
);
525 updateState('failed', msg
);
529 function encode_message() {
531 return Base64
.encode(sQ
);
534 function decode_message(data
) {
535 //Util.Debug(">> decode_message: " + data);
537 rQ
= rQ
.concat(Base64
.decode(data
, 0));
538 //Util.Debug(">> decode_message, rQ: " + rQ);
541 function handle_message() {
542 //Util.Debug("rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
544 Util
.Warn("handle_message called on empty receive queue");
550 Util
.Error("Got data while disconnected");
553 if (normal_msg() && rQlen() > 0) {
554 // true means we can continue processing
555 // Give other events a chance to run
556 if (msgTimer
=== null) {
557 Util
.Debug("More data to process, creating timer");
558 msgTimer
= setTimeout(function () {
563 Util
.Debug("More data to process, existing timer");
567 if (rQ
.length
> rQmax
) {
568 //Util.Debug("Compacting receive queue");
579 recv_message = function(e
) {
580 //Util.Debug(">> recv_message: " + e.data.length);
583 decode_message(e
.data
);
587 Util
.Debug("Ignoring empty message");
590 if (typeof exc
.stack
!== 'undefined') {
591 Util
.Warn("recv_message, caught exception: " + exc
.stack
);
592 } else if (typeof exc
.description
!== 'undefined') {
593 Util
.Warn("recv_message, caught exception: " + exc
.description
);
595 Util
.Warn("recv_message, caught exception:" + exc
);
597 if (typeof exc
.name
!== 'undefined') {
598 fail(exc
.name
+ ": " + exc
.message
);
603 //Util.Debug("<< recv_message");
606 // overridable for testing
607 send_array = function(arr
) {
608 //Util.Debug(">> send_array: " + arr);
610 if (ws
.bufferedAmount
=== 0) {
611 //Util.Debug("arr: " + arr);
612 //Util.Debug("sQ: " + sQ);
613 ws
.send(encode_message(sQ
));
616 Util
.Debug("Delaying send");
620 function send_string(str
) {
621 //Util.Debug(">> send_string: " + str);
622 send_array(str
.split('').map(
623 function (chr
) { return chr
.charCodeAt(0); } ) );
626 function genDES(password
, challenge
) {
627 var i
, passwd
= [], des
;
628 for (i
=0; i
< password
.length
; i
+= 1) {
629 passwd
.push(password
.charCodeAt(i
));
631 return (new DES(passwd
)).encrypt(challenge
);
634 function flushClient() {
635 if (mouse_arr
.length
> 0) {
636 //send_array(mouse_arr.concat(fbUpdateRequest(1)));
637 send_array(mouse_arr
);
638 setTimeout(function() {
639 send_array(fbUpdateRequest(1));
649 // overridable for testing
650 checkEvents = function() {
652 if (rfb_state
=== 'normal') {
653 if (! flushClient()) {
654 now
= new Date().getTime();
655 if (now
> last_req_time
+ conf
.fbu_req_rate
) {
657 send_array(fbUpdateRequest(1));
661 setTimeout(checkEvents
, conf
.check_rate
);
664 function keyPress(keysym
, down
) {
666 arr
= keyEvent(keysym
, down
);
667 arr
= arr
.concat(fbUpdateRequest(1));
671 function mouseButton(x
, y
, down
, bmask
) {
673 mouse_buttonMask
|= bmask
;
675 mouse_buttonMask
^= bmask
;
677 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
681 function mouseMove(x
, y
) {
682 //Util.Debug('>> mouseMove ' + x + "," + y);
683 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
688 // Server message handlers
691 // RFB/VNC initialisation message handler
692 init_msg = function() {
693 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
695 var strlen
, reason
, length
, sversion
, cversion
,
696 i
, types
, num_types
, challenge
, response
, bpp
, depth
,
697 big_endian
, true_color
, name_length
;
699 //Util.Debug("rQ (" + rQlen() + ") " + rQ);
702 case 'ProtocolVersion' :
704 return fail("Incomplete protocol version");
706 sversion
= rQshiftStr(12).substr(4,7);
707 Util
.Info("Server ProtocolVersion: " + sversion
);
709 case "003.003": rfb_version
= 3.3; break;
710 case "003.006": rfb_version
= 3.3; break; // UltraVNC
711 case "003.007": rfb_version
= 3.7; break;
712 case "003.008": rfb_version
= 3.8; break;
714 return fail("Invalid server version " + sversion
);
716 if (rfb_version
> rfb_max_version
) {
717 rfb_version
= rfb_max_version
;
721 sendTimer
= setInterval(function() {
722 // Send updates either at a rate of one update
723 // every 50ms, or whatever slower rate the network
725 if (ws
.bufferedAmount
=== 0) {
727 ws
.send(encode_message(sQ
));
731 Util
.Debug("Delaying send");
736 cversion
= "00" + parseInt(rfb_version
,10) +
737 ".00" + ((rfb_version
* 10) % 10);
738 send_string("RFB " + cversion
+ "\n");
739 updateState('Security', "Sent ProtocolVersion: " + cversion
);
743 if (rfb_version
>= 3.7) {
744 // Server sends supported list, client decides
745 num_types
= rQ
[rQi
++];
746 if (rQwait("security type", num_types
, 1)) { return false; }
747 if (num_types
=== 0) {
748 strlen
= rQshift32();
749 reason
= rQshiftStr(strlen
);
750 return fail("Security failure: " + reason
);
753 types
= rQshiftBytes(num_types
);
754 Util
.Debug("Server security types: " + types
);
755 for (i
=0; i
< types
.length
; i
+=1) {
756 if ((types
[i
] > rfb_auth_scheme
) && (types
[i
] < 3)) {
757 rfb_auth_scheme
= types
[i
];
760 if (rfb_auth_scheme
=== 0) {
761 return fail("Unsupported security types: " + types
);
764 send_array([rfb_auth_scheme
]);
767 if (rQwait("security scheme", 4)) { return false; }
768 rfb_auth_scheme
= rQshift32();
770 updateState('Authentication',
771 "Authenticating using scheme: " + rfb_auth_scheme
);
772 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
775 // Triggered by fallthough, not by server message
776 case 'Authentication' :
777 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
778 switch (rfb_auth_scheme
) {
779 case 0: // connection failed
780 if (rQwait("auth reason", 4)) { return false; }
781 strlen
= rQshift32();
782 reason
= rQshiftStr(strlen
);
783 return fail("Auth failure: " + reason
);
784 case 1: // no authentication
785 if (rfb_version
>= 3.8) {
786 updateState('SecurityResult');
789 // Fall through to ClientInitialisation
792 case 2: // VNC authentication
793 if (rfb_password
.length
=== 0) {
794 updateState('password', "Password Required");
797 if (rQwait("auth challenge", 16)) { return false; }
798 challenge
= rQshiftBytes(16);
799 //Util.Debug("Password: " + rfb_password);
800 //Util.Debug("Challenge: " + challenge +
801 // " (" + challenge.length + ")");
802 response
= genDES(rfb_password
, challenge
);
803 //Util.Debug("Response: " + response +
804 // " (" + response.length + ")");
806 //Util.Debug("Sending DES encrypted auth response");
807 send_array(response
);
808 updateState('SecurityResult');
811 fail("Unsupported auth scheme: " + rfb_auth_scheme
);
814 updateState('ClientInitialisation', "No auth required");
815 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
818 case 'SecurityResult' :
819 if (rQwait("VNC auth response ", 4)) { return false; }
820 switch (rQshift32()) {
822 // Fall through to ClientInitialisation
825 if (rfb_version
>= 3.8) {
826 length
= rQshift32();
827 if (rQwait("SecurityResult reason", length
, 8)) {
830 reason
= rQshiftStr(length
);
833 fail("Authentication failed");
837 return fail("Too many auth attempts");
839 updateState('ClientInitialisation', "Authentication OK");
840 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
843 // Triggered by fallthough, not by server message
844 case 'ClientInitialisation' :
845 send_array([conf
.shared
? 1 : 0]); // ClientInitialisation
846 updateState('ServerInitialisation', "Authentication OK");
849 case 'ServerInitialisation' :
850 if (rQwait("server initialization", 24)) { return false; }
853 fb_width
= rQshift16();
854 fb_height
= rQshift16();
859 big_endian
= rQ
[rQi
++];
860 true_color
= rQ
[rQi
++];
862 Util
.Info("Screen: " + fb_width
+ "x" + fb_height
+
863 ", bpp: " + bpp
+ ", depth: " + depth
+
864 ", big_endian: " + big_endian
+
865 ", true_color: " + true_color
);
867 /* Connection name/title */
869 name_length = rQshift32();
870 fb_name = rQshiftStr(name_length);
872 canvas.resize(fb_width, fb_height, conf.true_color);
873 canvas.start(keyPress, mouseButton, mouseMove);
875 if (conf.true_color) {
883 response = pixelFormat();
884 response = response.concat(clientEncodings());
885 response = response.concat(fbUpdateRequest(0));
886 timing.fbu_rt_start = (new Date()).getTime();
887 send_array(response);
889 /* Start pushing/polling */
890 setTimeout(checkEvents, conf.check_rate);
891 setTimeout(scan_tight_imgQ, scan_imgQ_rate);
894 updateState('normal', "Connected (encrypted) to: " + fb_name);
896 updateState('normal', "Connected (unencrypted) to: " + fb_name);
900 //Util.Debug("<< init_msg");
904 /* Normal RFB/VNC server message handler */
905 normal_msg = function() {
906 //Util.Debug(">> normal_msg");
908 var ret = true, msg_type, length,
909 c, first_colour, num_colours, red, green, blue;
914 msg_type = rQ[rQi++];
917 case 0: // FramebufferUpdate
918 ret = framebufferUpdate(); // false means need more data
920 case 1: // SetColourMapEntries
921 Util.Debug("SetColourMapEntries");
923 first_colour = rQshift16(); // First colour
924 num_colours = rQshift16();
925 for (c=0; c < num_colours; c+=1) {
927 //Util.Debug("red before: " + red);
928 red = parseInt(red / 256, 10);
929 //Util.Debug("red after: " + red);
930 green = parseInt(rQshift16() / 256, 10);
931 blue = parseInt(rQshift16() / 256, 10);
932 canvas.set_colourMap([red, green, blue], first_colour + c);
934 Util.Info("Registered " + num_colours + " colourMap entries");
935 //Util.Debug("colourMap: " + canvas.get_colourMap());
938 Util.Warn("Bell (unsupported)");
940 case 3: // ServerCutText
941 Util.Debug("ServerCutText");
942 if (rQwait("ServerCutText header", 7, 1)) { return false; }
943 rQshiftBytes(3); // Padding
944 length = rQshift32();
945 if (rQwait("ServerCutText", length, 8)) { return false; }
947 conf.clipboardReceive(that, rQshiftStr(length));
950 fail("Disconnected: illegal server message type " + msg_type);
951 Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
954 //Util.Debug("<< normal_msg");
958 framebufferUpdate = function() {
959 var now, hdr, fbu_rt_diff, ret = true;
961 if (FBU.rects === 0) {
962 //Util.Debug("New FBU: rQ.slice(0,20): " + rQ.slice(0,20));
963 if (rQwait("FBU header", 3)) {
965 rQ.unshift(0); // FBU msg_type
972 FBU.rects = rQshift16();
973 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
976 if (timing.fbu_rt_start > 0) {
977 now = (new Date()).getTime();
978 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
982 while (FBU.rects > 0) {
983 if (rfb_state !== "normal") {
986 if (rQwait("FBU")) { return false; }
987 if (FBU.bytes === 0) {
988 if (rQwait("rect header", 12)) { return false; }
989 /* New FramebufferUpdate */
991 hdr
= rQshiftBytes(12);
992 FBU
.x
= (hdr
[0] << 8) + hdr
[1];
993 FBU
.y
= (hdr
[2] << 8) + hdr
[3];
994 FBU
.width
= (hdr
[4] << 8) + hdr
[5];
995 FBU
.height
= (hdr
[6] << 8) + hdr
[7];
996 FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
997 (hdr
[10] << 8) + hdr
[11], 10);
999 if (encNames
[FBU
.encoding
]) {
1002 var msg = "FramebufferUpdate rects:" + FBU.rects;
1003 msg += " x: " + FBU.x + " y: " + FBU.y;
1004 msg += " width: " + FBU.width + " height: " + FBU.height;
1005 msg += " encoding:" + FBU.encoding;
1006 msg += "(" + encNames[FBU.encoding] + ")";
1007 msg += ", rQlen(): " + rQlen();
1011 fail("Disconnected: unsupported encoding " +
1017 timing
.last_fbu
= (new Date()).getTime();
1019 ret
= encHandlers
[FBU
.encoding
]();
1021 now
= (new Date()).getTime();
1022 timing
.cur_fbu
+= (now
- timing
.last_fbu
);
1025 encStats
[FBU
.encoding
][0] += 1;
1026 encStats
[FBU
.encoding
][1] += 1;
1029 if (FBU
.rects
=== 0) {
1030 if (((FBU
.width
=== fb_width
) &&
1031 (FBU
.height
=== fb_height
)) ||
1032 (timing
.fbu_rt_start
> 0)) {
1033 timing
.full_fbu_total
+= timing
.cur_fbu
;
1034 timing
.full_fbu_cnt
+= 1;
1035 Util
.Info("Timing of full FBU, cur: " +
1036 timing
.cur_fbu
+ ", total: " +
1037 timing
.full_fbu_total
+ ", cnt: " +
1038 timing
.full_fbu_cnt
+ ", avg: " +
1039 (timing
.full_fbu_total
/
1040 timing
.full_fbu_cnt
));
1042 if (timing
.fbu_rt_start
> 0) {
1043 fbu_rt_diff
= now
- timing
.fbu_rt_start
;
1044 timing
.fbu_rt_total
+= fbu_rt_diff
;
1045 timing
.fbu_rt_cnt
+= 1;
1046 Util
.Info("full FBU round-trip, cur: " +
1047 fbu_rt_diff
+ ", total: " +
1048 timing
.fbu_rt_total
+ ", cnt: " +
1049 timing
.fbu_rt_cnt
+ ", avg: " +
1050 (timing
.fbu_rt_total
/
1051 timing
.fbu_rt_cnt
));
1052 timing
.fbu_rt_start
= 0;
1056 return ret
; // false ret means need more data
1059 return true; // We finished this FBU
1063 // FramebufferUpdate encodings
1066 encHandlers
.RAW
= function display_raw() {
1067 //Util.Debug(">> display_raw (" + rQlen() + " bytes)");
1069 var cur_y
, cur_height
;
1071 if (FBU
.lines
=== 0) {
1072 FBU
.lines
= FBU
.height
;
1074 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least a line
1075 if (rQwait("RAW")) { return false; }
1076 cur_y
= FBU
.y
+ (FBU
.height
- FBU
.lines
);
1077 cur_height
= Math
.min(FBU
.lines
,
1078 Math
.floor(rQlen()/(FBU
.width
* fb_Bpp
)));
1079 canvas
.blitImage(FBU
.x
, cur_y
, FBU
.width
, cur_height
, rQ
, rQi
);
1080 rQshiftBytes(FBU
.width
* cur_height
* fb_Bpp
);
1081 FBU
.lines
-= cur_height
;
1083 if (FBU
.lines
> 0) {
1084 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least another line
1089 //Util.Debug("<< display_raw (" + rQlen() + " bytes)");
1093 encHandlers
.COPYRECT
= function display_copy_rect() {
1094 //Util.Debug(">> display_copy_rect");
1098 if (rQwait("COPYRECT", 4)) { return false; }
1099 old_x
= rQshift16();
1100 old_y
= rQshift16();
1101 canvas
.copyImage(old_x
, old_y
, FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
);
1107 encHandlers
.RRE
= function display_rre() {
1108 //Util.Debug(">> display_rre (" + rQlen() + " bytes)");
1109 var color
, x
, y
, width
, height
, chunk
;
1111 if (FBU
.subrects
=== 0) {
1112 if (rQwait("RRE", 4+fb_Bpp
)) { return false; }
1113 FBU
.subrects
= rQshift32();
1114 color
= rQshiftBytes(fb_Bpp
); // Background
1115 canvas
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1117 while ((FBU
.subrects
> 0) && (rQlen() >= (fb_Bpp
+ 8))) {
1118 color
= rQshiftBytes(fb_Bpp
);
1121 width
= rQshift16();
1122 height
= rQshift16();
1123 canvas
.fillRect(FBU
.x
+ x
, FBU
.y
+ y
, width
, height
, color
);
1126 //Util.Debug(" display_rre: rects: " + FBU.rects +
1127 // ", FBU.subrects: " + FBU.subrects);
1129 if (FBU
.subrects
> 0) {
1130 chunk
= Math
.min(rre_chunk_sz
, FBU
.subrects
);
1131 FBU
.bytes
= (fb_Bpp
+ 8) * chunk
;
1136 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1140 encHandlers
.HEXTILE
= function display_hextile() {
1141 //Util.Debug(">> display_hextile");
1142 var subencoding
, subrects
, tile
, color
, cur_tile
,
1143 tile_x
, x
, w
, tile_y
, y
, h
, xy
, s
, sx
, sy
, wh
, sw
, sh
;
1145 if (FBU
.tiles
=== 0) {
1146 FBU
.tiles_x
= Math
.ceil(FBU
.width
/16);
1147 FBU
.tiles_y
= Math
.ceil(FBU
.height
/16);
1148 FBU
.total_tiles
= FBU
.tiles_x
* FBU
.tiles_y
;
1149 FBU
.tiles
= FBU
.total_tiles
;
1152 /* FBU.bytes comes in as 1, rQlen() at least 1 */
1153 while (FBU
.tiles
> 0) {
1155 if (rQwait("HEXTILE subencoding")) { return false; }
1156 //Util.Debug(" 2 rQ length: " + rQlen() + " rQ[rQi]: " + rQ[rQi] + " rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + ", FBU.rects: " + FBU.rects + ", FBU.tiles: " + FBU.tiles);
1157 subencoding
= rQ
[rQi
]; // Peek
1158 if (subencoding
> 30) { // Raw
1159 fail("Disconnected: illegal hextile subencoding " + subencoding
);
1160 //Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
1164 cur_tile
= FBU
.total_tiles
- FBU
.tiles
;
1165 tile_x
= cur_tile
% FBU
.tiles_x
;
1166 tile_y
= Math
.floor(cur_tile
/ FBU
.tiles_x
);
1167 x
= FBU
.x
+ tile_x
* 16;
1168 y
= FBU
.y
+ tile_y
* 16;
1169 w
= Math
.min(16, (FBU
.x
+ FBU
.width
) - x
);
1170 h
= Math
.min(16, (FBU
.y
+ FBU
.height
) - y
);
1172 /* Figure out how much we are expecting */
1173 if (subencoding
& 0x01) { // Raw
1174 //Util.Debug(" Raw subencoding");
1175 FBU
.bytes
+= w
* h
* fb_Bpp
;
1177 if (subencoding
& 0x02) { // Background
1178 FBU
.bytes
+= fb_Bpp
;
1180 if (subencoding
& 0x04) { // Foreground
1181 FBU
.bytes
+= fb_Bpp
;
1183 if (subencoding
& 0x08) { // AnySubrects
1184 FBU
.bytes
+= 1; // Since we aren't shifting it off
1185 if (rQwait("hextile subrects header")) { return false; }
1186 subrects
= rQ
[rQi
+ FBU
.bytes
-1]; // Peek
1187 if (subencoding
& 0x10) { // SubrectsColoured
1188 FBU
.bytes
+= subrects
* (fb_Bpp
+ 2);
1190 FBU
.bytes
+= subrects
* 2;
1196 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1197 " (" + tile_x + "," + tile_y + ")" +
1198 " [" + x + "," + y + "]@" + w + "x" + h +
1199 ", subenc:" + subencoding +
1200 "(last: " + FBU.lastsubencoding + "), subrects:" +
1202 ", rQlen():" + rQlen() + ", FBU.bytes:" + FBU.bytes +
1203 " last:" + rQ.slice(FBU.bytes-10, FBU.bytes) +
1204 " next:" + rQ.slice(FBU.bytes-1, FBU.bytes+10));
1206 if (rQwait("hextile")) { return false; }
1208 /* We know the encoding and have a whole tile */
1209 FBU
.subencoding
= rQ
[rQi
];
1211 if (FBU
.subencoding
=== 0) {
1212 if (FBU
.lastsubencoding
& 0x01) {
1213 /* Weird: ignore blanks after RAW */
1214 Util
.Debug(" Ignoring blank after RAW");
1216 canvas
.fillRect(x
, y
, w
, h
, FBU
.background
);
1218 } else if (FBU
.subencoding
& 0x01) { // Raw
1219 canvas
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1220 rQi
+= FBU
.bytes
- 1;
1222 if (FBU
.subencoding
& 0x02) { // Background
1223 FBU
.background
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1226 if (FBU
.subencoding
& 0x04) { // Foreground
1227 FBU
.foreground
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1231 tile
= canvas
.getTile(x
, y
, w
, h
, FBU
.background
);
1232 if (FBU
.subencoding
& 0x08) { // AnySubrects
1235 for (s
= 0; s
< subrects
; s
+= 1) {
1236 if (FBU
.subencoding
& 0x10) { // SubrectsColoured
1237 color
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1240 color
= FBU
.foreground
;
1250 sh
= (wh
& 0x0f) + 1;
1252 canvas
.setSubTile(tile
, sx
, sy
, sw
, sh
, color
);
1255 canvas
.putTile(tile
);
1257 //rQshiftBytes(FBU.bytes);
1258 FBU
.lastsubencoding
= FBU
.subencoding
;
1263 if (FBU
.tiles
=== 0) {
1267 //Util.Debug("<< display_hextile");
1272 encHandlers
.TIGHT_PNG
= function display_tight_png() {
1273 //Util.Debug(">> display_tight_png");
1274 var ctl
, cmode
, clength
, getCLength
, color
, img
;
1275 //Util.Debug(" FBU.rects: " + FBU.rects);
1276 //Util.Debug(" starting rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
1278 FBU
.bytes
= 1; // compression-control byte
1279 if (rQwait("TIGHT compression-control")) { return false; }
1281 // Get 'compact length' header and data size
1282 getCLength = function (arr
, offset
) {
1283 var header
= 1, data
= 0;
1284 data
+= arr
[offset
+ 0] & 0x7f;
1285 if (arr
[offset
+ 0] & 0x80) {
1287 data
+= (arr
[offset
+ 1] & 0x7f) << 7;
1288 if (arr
[offset
+ 1] & 0x80) {
1290 data
+= arr
[offset
+ 2] << 14;
1293 return [header
, data
];
1298 case 0x08: cmode
= "fill"; break;
1299 case 0x09: cmode
= "jpeg"; break;
1300 case 0x0A: cmode
= "png"; break;
1301 default: throw("Illegal basic compression received, ctl: " + ctl
);
1304 // fill uses fb_depth because TPIXELs drop the padding byte
1305 case "fill": FBU
.bytes
+= fb_depth
; break; // TPIXEL
1306 case "jpeg": FBU
.bytes
+= 3; break; // max clength
1307 case "png": FBU
.bytes
+= 3; break; // max clength
1310 if (rQwait("TIGHT " + cmode
)) { return false; }
1312 //Util.Debug(" rQ.slice(0,20): " + rQ.slice(0,20) + " (" + rQlen() + ")");
1313 //Util.Debug(" cmode: " + cmode);
1315 // Determine FBU.bytes
1318 rQi
++; // shift off ctl
1319 color
= rQshiftBytes(fb_depth
);
1320 canvas
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1324 clength
= getCLength(rQ
, rQi
+1);
1325 FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1326 if (rQwait("TIGHT " + cmode
)) { return false; }
1328 // We have everything, render it
1329 //Util.Debug(" png, rQlen(): " + rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
1330 rQshiftBytes(1 + clength
[0]); // shift off ctl + compact length
1332 img
.onload
= scan_tight_imgQ
;
1333 FBU
.imgQ
.push([img
, FBU
.x
, FBU
.y
]);
1334 img
.src
= "data:image/" + cmode
+
1335 extract_data_uri(rQshiftBytes(clength
[1]));
1341 //Util.Debug(" ending rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
1342 //Util.Debug("<< display_tight_png");
1346 extract_data_uri = function(arr
) {
1348 //for (i=0; i< arr.length; i += 1) {
1349 // stra.push(String.fromCharCode(arr[i]));
1351 //return "," + escape(stra.join(''));
1352 return ";base64," + Base64
.encode(arr
);
1355 scan_tight_imgQ = function() {
1357 ctx
= canvas
.getContext();
1358 if (rfb_state
=== 'normal') {
1360 while ((imgQ
.length
> 0) && (imgQ
[0][0].complete
)) {
1362 ctx
.drawImage(img
[0], img
[1], img
[2]);
1364 setTimeout(scan_tight_imgQ
, scan_imgQ_rate
);
1368 encHandlers
.DesktopSize
= function set_desktopsize() {
1369 Util
.Debug(">> set_desktopsize");
1370 fb_width
= FBU
.width
;
1371 fb_height
= FBU
.height
;
1373 canvas
.resize(fb_width
, fb_height
);
1374 timing
.fbu_rt_start
= (new Date()).getTime();
1375 // Send a new non-incremental request
1376 send_array(fbUpdateRequest(0));
1381 Util
.Debug("<< set_desktopsize");
1385 encHandlers
.Cursor
= function set_cursor() {
1386 var x
, y
, w
, h
, pixelslength
, masklength
;
1387 //Util.Debug(">> set_cursor");
1388 x
= FBU
.x
; // hotspot-x
1389 y
= FBU
.y
; // hotspot-y
1393 pixelslength
= w
* h
* fb_Bpp
;
1394 masklength
= Math
.floor((w
+ 7) / 8) * h
;
1396 FBU
.bytes
= pixelslength
+ masklength
;
1397 if (rQwait("cursor encoding")) { return false; }
1399 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1401 canvas
.changeCursor(rQshiftBytes(pixelslength
),
1402 rQshiftBytes(masklength
),
1408 //Util.Debug("<< set_cursor");
1412 encHandlers
.JPEG_quality_lo
= function set_jpeg_quality() {
1413 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1416 encHandlers
.compress_lo
= function set_compress_level() {
1417 Util
.Error("Server sent compress level pseudo-encoding");
1421 * Client message routines
1424 pixelFormat = function() {
1425 //Util.Debug(">> pixelFormat");
1427 arr
= [0]; // msg-type
1428 arr
.push8(0); // padding
1429 arr
.push8(0); // padding
1430 arr
.push8(0); // padding
1432 arr
.push8(fb_Bpp
* 8); // bits-per-pixel
1433 arr
.push8(fb_depth
* 8); // depth
1434 arr
.push8(0); // little-endian
1435 arr
.push8(conf
.true_color
? 1 : 0); // true-color
1437 arr
.push16(255); // red-max
1438 arr
.push16(255); // green-max
1439 arr
.push16(255); // blue-max
1440 arr
.push8(0); // red-shift
1441 arr
.push8(8); // green-shift
1442 arr
.push8(16); // blue-shift
1444 arr
.push8(0); // padding
1445 arr
.push8(0); // padding
1446 arr
.push8(0); // padding
1447 //Util.Debug("<< pixelFormat");
1451 clientEncodings = function() {
1452 //Util.Debug(">> clientEncodings");
1453 var arr
, i
, encList
= [];
1455 for (i
=0; i
<encodings
.length
; i
+= 1) {
1456 if ((encodings
[i
][0] === "Cursor") &&
1457 (! conf
.local_cursor
)) {
1458 Util
.Debug("Skipping Cursor pseudo-encoding");
1460 //Util.Debug("Adding encoding: " + encodings[i][0]);
1461 encList
.push(encodings
[i
][1]);
1465 arr
= [2]; // msg-type
1466 arr
.push8(0); // padding
1468 arr
.push16(encList
.length
); // encoding count
1469 for (i
=0; i
< encList
.length
; i
+= 1) {
1470 arr
.push32(encList
[i
]);
1472 //Util.Debug("<< clientEncodings: " + arr);
1476 fbUpdateRequest = function(incremental
, x
, y
, xw
, yw
) {
1477 //Util.Debug(">> fbUpdateRequest");
1480 if (!xw
) { xw
= fb_width
; }
1481 if (!yw
) { yw
= fb_height
; }
1483 arr
= [3]; // msg-type
1484 arr
.push8(incremental
);
1489 //Util.Debug("<< fbUpdateRequest");
1493 keyEvent = function(keysym
, down
) {
1494 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1496 arr
= [4]; // msg-type
1500 //Util.Debug("<< keyEvent");
1504 pointerEvent = function(x
, y
) {
1505 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1506 // " , mask: " + mouse_buttonMask);
1508 arr
= [5]; // msg-type
1509 arr
.push8(mouse_buttonMask
);
1512 //Util.Debug("<< pointerEvent");
1516 clientCutText = function(text
) {
1517 //Util.Debug(">> clientCutText");
1519 arr
= [6]; // msg-type
1520 arr
.push8(0); // padding
1521 arr
.push8(0); // padding
1522 arr
.push8(0); // padding
1523 arr
.push32(text
.length
);
1525 for (i
=0; i
< n
; i
+=1) {
1526 arr
.push(text
.charCodeAt(i
));
1528 //Util.Debug("<< clientCutText:" + arr);
1535 // Public API interface functions
1538 that
.connect = function(host
, port
, password
) {
1539 //Util.Debug(">> connect");
1543 rfb_password
= (password
!== undefined) ? password
: "";
1545 if ((!rfb_host
) || (!rfb_port
)) {
1546 return fail("Must set host and port");
1549 updateState('connect');
1550 //Util.Debug("<< connect");
1554 that
.disconnect = function() {
1555 //Util.Debug(">> disconnect");
1556 updateState('disconnect', 'Disconnecting');
1557 //Util.Debug("<< disconnect");
1560 that
.sendPassword = function(passwd
) {
1561 rfb_password
= passwd
;
1562 rfb_state
= "Authentication";
1563 setTimeout(init_msg
, 1);
1566 that
.sendCtrlAltDel = function() {
1567 if (rfb_state
!== "normal") { return false; }
1568 Util
.Info("Sending Ctrl-Alt-Del");
1570 arr
= arr
.concat(keyEvent(0xFFE3, 1)); // Control
1571 arr
= arr
.concat(keyEvent(0xFFE9, 1)); // Alt
1572 arr
= arr
.concat(keyEvent(0xFFFF, 1)); // Delete
1573 arr
= arr
.concat(keyEvent(0xFFFF, 0)); // Delete
1574 arr
= arr
.concat(keyEvent(0xFFE9, 0)); // Alt
1575 arr
= arr
.concat(keyEvent(0xFFE3, 0)); // Control
1576 arr
= arr
.concat(fbUpdateRequest(1));
1580 // Send a key press. If 'down' is not specified then send a down key
1581 // followed by an up key.
1582 that
.sendKey = function(code
, down
) {
1583 if (rfb_state
!== "normal") { return false; }
1585 if (typeof down
!== 'undefined') {
1586 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
1587 arr
= arr
.concat(keyEvent(code
, down
? 1 : 0));
1589 Util
.Info("Sending key code (down + up): " + code
);
1590 arr
= arr
.concat(keyEvent(code
, 1));
1591 arr
= arr
.concat(keyEvent(code
, 0));
1593 arr
= arr
.concat(fbUpdateRequest(1));
1597 that
.clipboardPasteFrom = function(text
) {
1598 if (rfb_state
!== "normal") { return; }
1599 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1600 send_array(clientCutText(text
));
1601 //Util.Debug("<< clipboardPasteFrom");
1604 that
.testMode = function(override_send_array
) {
1605 // Overridable internal functions for testing
1607 send_array
= override_send_array
;
1608 that
.recv_message
= recv_message
; // Expose it
1610 checkEvents = function () { /* Stub Out */ };
1611 that
.connect = function(host
, port
, password
) {
1614 rfb_password
= password
;
1615 updateState('ProtocolVersion', "Starting VNC handshake");
1620 return constructor(); // Return the public API interface