]>
git.proxmox.com Git - mirror_novnc.git/blob - include/rfb.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2011 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
9 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
10 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */
13 function RFB(defaults
) {
16 var that
= {}, // Public API methods
17 conf
= {}, // Configuration attributes
19 // Pre-declare private functions used before definitions (jslint)
20 init_vars
, updateState
, fail
, handle_message
,
21 init_msg
, normal_msg
, framebufferUpdate
, print_stats
,
23 pixelFormat
, clientEncodings
, fbUpdateRequest
,
24 keyEvent
, pointerEvent
, clientCutText
,
26 extract_data_uri
, scan_tight_imgQ
,
27 keyPress
, mouseButton
, mouseMove
,
29 checkEvents
, // Overridable for testing
33 // Private RFB namespace variables
40 rfb_state
= 'disconnected',
46 // In preference order
53 ['DesktopSize', -223 ],
56 // Psuedo-encoding settings
57 ['JPEG_quality_lo', -32 ],
58 //['JPEG_quality_hi', -23 ],
59 ['compress_lo', -255 ]
60 //['compress_hi', -247 ]
65 encStats
= {}, // [rectCnt, rectCntTot]
67 ws
= null, // Websock object
68 display
= null, // Display object
69 keyboard
= null, // Keyboard input handler object
70 mouse
= null, // Mouse input handler object
71 sendTimer
= null, // Send Queue check timer
72 connTimer
= null, // connection timer
73 disconnTimer
= null, // disconnection timer
74 msgTimer
= null, // queued handle_message timer
76 // Frame buffer update state
90 imgQ
: [] // TIGHT_PNG image queue
99 scan_imgQ_rate
= 40, // 25 times per second or so
117 def_con_timeout
= Websock_native
? 2 : 5,
120 mouse_buttonMask
= 0,
123 // Configuration attributes
124 Util
.conf_defaults(conf
, that
, defaults
, [
125 ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
126 ['focusContainer', 'wo', 'dom', document
, 'DOM element that captures keyboard input'],
128 ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
129 ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
130 ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
131 ['shared', 'rw', 'bool', true, 'Request shared mode'],
133 ['connectTimeout', 'rw', 'int', def_con_timeout
, 'Time (s) to wait for connection'],
134 ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
136 ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
137 ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
139 // Callback functions
140 ['onUpdateState', 'rw', 'func', function() { },
141 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
142 ['onPasswordRequired', 'rw', 'func', function() { },
143 'onPasswordRequired(rfb): VNC password is required '],
144 ['onClipboard', 'rw', 'func', function() { },
145 'onClipboard(rfb, text): RFB clipboard contents received'],
146 ['onBell', 'rw', 'func', function() { },
147 'onBell(rfb): RFB Bell message received '],
148 ['onFBUReceive', 'rw', 'func', function() { },
149 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
150 ['onFBUComplete', 'rw', 'func', function() { },
151 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
153 // These callback names are deprecated
154 ['updateState', 'rw', 'func', function() { },
155 'obsolete, use onUpdateState'],
156 ['clipboardReceive', 'rw', 'func', function() { },
157 'obsolete, use onClipboard']
161 // Override/add some specific configuration getters/setters
162 that
.set_local_cursor = function(cursor
) {
163 if ((!cursor
) || (cursor
in {'0':1, 'no':1, 'false':1})) {
164 conf
.local_cursor
= false;
166 if (display
.get_cursor_uri()) {
167 conf
.local_cursor
= true;
169 Util
.Warn("Browser does not support local cursor");
174 // These are fake configuration getters
175 that
.get_display = function() { return display
; };
177 that
.get_keyboard = function() { return keyboard
; };
179 that
.get_mouse = function() { return mouse
; };
187 // Create the public API interface and initialize values that stay
188 // constant across connect/disconnect
189 function constructor() {
191 Util
.Debug(">> RFB.constructor");
193 // Create lookup tables based encoding number
194 for (i
=0; i
< encodings
.length
; i
+=1) {
195 encHandlers
[encodings
[i
][1]] = encHandlers
[encodings
[i
][0]];
196 encNames
[encodings
[i
][1]] = encodings
[i
][0];
197 encStats
[encodings
[i
][1]] = [0, 0];
199 // Initialize display, mouse, keyboard, and websock
201 display
= new Display({'target': conf
.target
});
203 Util
.Error("Display exception: " + exc
);
204 updateState('fatal', "No working Display");
206 keyboard
= new Keyboard({'target': conf
.focusContainer
,
207 'onKeyPress': keyPress
});
208 mouse
= new Mouse({'target': conf
.target
,
209 'onMouseButton': mouseButton
,
210 'onMouseMove': mouseMove
});
212 rmode
= display
.get_render_mode();
214 if (typeof noVNC_logo
!== 'undefined') {
215 display
.set_logo(noVNC_logo
);
219 ws
.on('message', handle_message
);
220 ws
.on('open', function() {
221 if (rfb_state
=== "connect") {
222 updateState('ProtocolVersion', "Starting VNC handshake");
224 fail("Got unexpected WebSockets connection");
227 ws
.on('close', function() {
228 if (rfb_state
=== 'disconnect') {
229 updateState('disconnected', 'VNC disconnected');
230 } else if (rfb_state
=== 'ProtocolVersion') {
231 fail('Failed to connect to server');
232 } else if (rfb_state
in {'failed':1, 'disconnected':1}) {
233 Util
.Error("Received onclose while disconnected");
235 fail('Server disconnected');
238 ws
.on('error', function(e
) {
239 fail("WebSock error: " + e
);
245 /* Check web-socket-js if no builtin WebSocket support */
246 if (Websock_native
) {
247 Util
.Info("Using native WebSockets");
248 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
250 Util
.Warn("Using web-socket-js bridge. Flash version: " +
252 if ((! Util
.Flash
) ||
253 (Util
.Flash
.version
< 9)) {
254 updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
255 } else if (document
.location
.href
.substr(0, 7) === "file://") {
257 "'file://' URL is incompatible with Adobe Flash");
259 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
263 Util
.Debug("<< RFB.constructor");
264 return that
; // Return the public API interface
268 Util
.Debug(">> RFB.connect");
276 uri
+= rfb_host
+ ":" + rfb_port
+ "/" + rfb_uri
;
277 Util
.Info("connecting to " + uri
);
280 Util
.Debug("<< RFB.connect");
283 // Initialize variables that are reset before each connection
284 init_vars = function() {
291 FBU
.subrects
= 0; // RRE and HEXTILE
292 FBU
.lines
= 0; // RAW
293 FBU
.tiles
= 0; // HEXTILE
294 FBU
.imgQ
= []; // TIGHT_PNG image queue
295 mouse_buttonMask
= 0;
298 // Clear the per connection encoding stats
299 for (i
=0; i
< encodings
.length
; i
+=1) {
300 encStats
[encodings
[i
][1]][0] = 0;
305 print_stats = function() {
307 Util
.Info("Encoding stats for this connection:");
308 for (i
=0; i
< encodings
.length
; i
+=1) {
309 s
= encStats
[encodings
[i
][1]];
310 if ((s
[0] + s
[1]) > 0) {
311 Util
.Info(" " + encodings
[i
][0] + ": " +
315 Util
.Info("Encoding stats since page load:");
316 for (i
=0; i
< encodings
.length
; i
+=1) {
317 s
= encStats
[encodings
[i
][1]];
318 if ((s
[0] + s
[1]) > 0) {
319 Util
.Info(" " + encodings
[i
][0] + ": " +
332 * loaded - page load, equivalent to disconnected
333 * disconnected - idle state
334 * connect - starting to connect (to ProtocolVersion)
336 * disconnect - starting to disconnect
337 * failed - abnormal disconnect
338 * fatal - failed to load page, or fatal error
340 * RFB protocol initialization states:
344 * password - waiting for password, not part of RFB
346 * ClientInitialization - not triggered by server message
347 * ServerInitialization (to normal)
349 updateState = function(state
, statusMsg
) {
350 var func
, cmsg
, oldstate
= rfb_state
;
352 if (state
=== oldstate
) {
353 /* Already here, ignore */
354 Util
.Debug("Already in state '" + state
+ "', ignoring.");
359 * These are disconnected states. A previous connect may
360 * asynchronously cause a connection so make sure we are closed.
362 if (state
in {'disconnected':1, 'loaded':1, 'connect':1,
363 'disconnect':1, 'failed':1, 'fatal':1}) {
365 clearInterval(sendTimer
);
370 clearInterval(msgTimer
);
374 if (display
&& display
.get_context()) {
377 display
.defaultCursor();
378 if ((Util
.get_logging() !== 'debug') ||
379 (state
=== 'loaded')) {
380 // Show noVNC logo on load and when disconnected if
389 if (oldstate
=== 'fatal') {
390 Util
.Error("Fatal error, cannot continue");
393 if ((state
=== 'failed') || (state
=== 'fatal')) {
399 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
400 // Do disconnect action, but stay in failed state.
401 rfb_state
= 'failed';
406 cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
407 func("New state '" + rfb_state
+ "', was '" + oldstate
+ "'." + cmsg
);
409 if (connTimer
&& (rfb_state
!== 'connect')) {
410 Util
.Debug("Clearing connect timer");
411 clearInterval(connTimer
);
415 if (disconnTimer
&& (rfb_state
!== 'disconnect')) {
416 Util
.Debug("Clearing disconnect timer");
417 clearInterval(disconnTimer
);
423 if ((oldstate
=== 'disconnected') || (oldstate
=== 'failed')) {
424 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
432 connTimer
= setTimeout(function () {
433 fail("Connect timeout");
434 }, conf
.connectTimeout
* 1000);
439 // WebSocket.onopen transitions to 'ProtocolVersion'
446 disconnTimer
= setTimeout(function () {
447 fail("Disconnect timeout");
448 }, conf
.disconnectTimeout
* 1000);
453 // WebSocket.onclose transitions to 'disconnected'
458 if (oldstate
=== 'disconnected') {
459 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
461 if (oldstate
=== 'normal') {
462 Util
.Error("Error while connected.");
464 if (oldstate
=== 'init') {
465 Util
.Error("Error while initializing.");
468 // Make sure we transition to disconnected
469 setTimeout(function() { updateState('disconnected'); }, 50);
475 // No state change action to take
479 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
480 // Leave the failed message
481 conf
.updateState(that
, state
, oldstate
); // Obsolete
482 conf
.onUpdateState(that
, state
, oldstate
);
484 conf
.updateState(that
, state
, oldstate
, statusMsg
); // Obsolete
485 conf
.onUpdateState(that
, state
, oldstate
, statusMsg
);
489 fail = function(msg
) {
490 updateState('failed', msg
);
494 handle_message = function() {
495 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
496 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
497 if (ws
.rQlen() === 0) {
498 Util
.Warn("handle_message called on empty receive queue");
504 Util
.Error("Got data while disconnected");
507 if (normal_msg() && ws
.rQlen() > 0) {
508 // true means we can continue processing
509 // Give other events a chance to run
510 if (msgTimer
=== null) {
511 Util
.Debug("More data to process, creating timer");
512 msgTimer
= setTimeout(function () {
517 Util
.Debug("More data to process, existing timer");
528 function genDES(password
, challenge
) {
530 for (i
=0; i
< password
.length
; i
+= 1) {
531 passwd
.push(password
.charCodeAt(i
));
533 return (new DES(passwd
)).encrypt(challenge
);
536 function flushClient() {
537 if (mouse_arr
.length
> 0) {
538 //send(mouse_arr.concat(fbUpdateRequest(1)));
540 setTimeout(function() {
541 ws
.send(fbUpdateRequest(1));
551 // overridable for testing
552 checkEvents = function() {
554 if (rfb_state
=== 'normal') {
555 if (! flushClient()) {
556 now
= new Date().getTime();
557 if (now
> last_req_time
+ conf
.fbu_req_rate
) {
559 ws
.send(fbUpdateRequest(1));
563 setTimeout(checkEvents
, conf
.check_rate
);
566 keyPress = function(keysym
, down
) {
568 arr
= keyEvent(keysym
, down
);
569 arr
= arr
.concat(fbUpdateRequest(1));
573 mouseButton = function(x
, y
, down
, bmask
) {
575 mouse_buttonMask
|= bmask
;
577 mouse_buttonMask
^= bmask
;
579 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
583 mouseMove = function(x
, y
) {
584 //Util.Debug('>> mouseMove ' + x + "," + y);
585 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
590 // Server message handlers
593 // RFB/VNC initialisation message handler
594 init_msg = function() {
595 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
597 var strlen
, reason
, length
, sversion
, cversion
,
598 i
, types
, num_types
, challenge
, response
, bpp
, depth
,
599 big_endian
, red_max
, green_max
, blue_max
, red_shift
,
600 green_shift
, blue_shift
, true_color
, name_length
;
602 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
605 case 'ProtocolVersion' :
606 if (ws
.rQlen() < 12) {
607 return fail("Incomplete protocol version");
609 sversion
= ws
.rQshiftStr(12).substr(4,7);
610 Util
.Info("Server ProtocolVersion: " + sversion
);
612 case "003.003": rfb_version
= 3.3; break;
613 case "003.006": rfb_version
= 3.3; break; // UltraVNC
614 case "003.007": rfb_version
= 3.7; break;
615 case "003.008": rfb_version
= 3.8; break;
617 return fail("Invalid server version " + sversion
);
619 if (rfb_version
> rfb_max_version
) {
620 rfb_version
= rfb_max_version
;
624 sendTimer
= setInterval(function() {
625 // Send updates either at a rate of one update
626 // every 50ms, or whatever slower rate the network
632 cversion
= "00" + parseInt(rfb_version
,10) +
633 ".00" + ((rfb_version
* 10) % 10);
634 ws
.send_string("RFB " + cversion
+ "\n");
635 updateState('Security', "Sent ProtocolVersion: " + cversion
);
639 if (rfb_version
>= 3.7) {
640 // Server sends supported list, client decides
641 num_types
= ws
.rQshift8();
642 if (ws
.rQwait("security type", num_types
, 1)) { return false; }
643 if (num_types
=== 0) {
644 strlen
= ws
.rQshift32();
645 reason
= ws
.rQshiftStr(strlen
);
646 return fail("Security failure: " + reason
);
649 types
= ws
.rQshiftBytes(num_types
);
650 Util
.Debug("Server security types: " + types
);
651 for (i
=0; i
< types
.length
; i
+=1) {
652 if ((types
[i
] > rfb_auth_scheme
) && (types
[i
] < 3)) {
653 rfb_auth_scheme
= types
[i
];
656 if (rfb_auth_scheme
=== 0) {
657 return fail("Unsupported security types: " + types
);
660 ws
.send([rfb_auth_scheme
]);
663 if (ws
.rQwait("security scheme", 4)) { return false; }
664 rfb_auth_scheme
= ws
.rQshift32();
666 updateState('Authentication',
667 "Authenticating using scheme: " + rfb_auth_scheme
);
668 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
671 // Triggered by fallthough, not by server message
672 case 'Authentication' :
673 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
674 switch (rfb_auth_scheme
) {
675 case 0: // connection failed
676 if (ws
.rQwait("auth reason", 4)) { return false; }
677 strlen
= ws
.rQshift32();
678 reason
= ws
.rQshiftStr(strlen
);
679 return fail("Auth failure: " + reason
);
680 case 1: // no authentication
681 if (rfb_version
>= 3.8) {
682 updateState('SecurityResult');
685 // Fall through to ClientInitialisation
687 case 2: // VNC authentication
688 if (rfb_password
.length
=== 0) {
689 // Notify via both callbacks since it is kind of
690 // a RFB state change and a UI interface issue.
691 updateState('password', "Password Required");
692 conf
.onPasswordRequired(that
);
695 if (ws
.rQwait("auth challenge", 16)) { return false; }
696 challenge
= ws
.rQshiftBytes(16);
697 //Util.Debug("Password: " + rfb_password);
698 //Util.Debug("Challenge: " + challenge +
699 // " (" + challenge.length + ")");
700 response
= genDES(rfb_password
, challenge
);
701 //Util.Debug("Response: " + response +
702 // " (" + response.length + ")");
704 //Util.Debug("Sending DES encrypted auth response");
706 updateState('SecurityResult');
709 fail("Unsupported auth scheme: " + rfb_auth_scheme
);
712 updateState('ClientInitialisation', "No auth required");
713 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
716 case 'SecurityResult' :
717 if (ws
.rQwait("VNC auth response ", 4)) { return false; }
718 switch (ws
.rQshift32()) {
720 // Fall through to ClientInitialisation
723 if (rfb_version
>= 3.8) {
724 length
= ws
.rQshift32();
725 if (ws
.rQwait("SecurityResult reason", length
, 8)) {
728 reason
= ws
.rQshiftStr(length
);
731 fail("Authentication failed");
735 return fail("Too many auth attempts");
737 updateState('ClientInitialisation', "Authentication OK");
738 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
741 // Triggered by fallthough, not by server message
742 case 'ClientInitialisation' :
743 ws
.send([conf
.shared
? 1 : 0]); // ClientInitialisation
744 updateState('ServerInitialisation', "Authentication OK");
747 case 'ServerInitialisation' :
748 if (ws
.rQwait("server initialization", 24)) { return false; }
751 fb_width
= ws
.rQshift16();
752 fb_height
= ws
.rQshift16();
756 depth
= ws
.rQshift8();
757 big_endian
= ws
.rQshift8();
758 true_color
= ws
.rQshift8();
760 red_max
= ws
.rQshift16();
761 green_max
= ws
.rQshift16();
762 blue_max
= ws
.rQshift16();
763 red_shift
= ws
.rQshift8();
764 green_shift
= ws
.rQshift8();
765 blue_shift
= ws
.rQshift8();
766 ws
.rQshiftStr(3); // padding
768 Util
.Info("Screen: " + fb_width
+ "x" + fb_height
+
769 ", bpp: " + bpp
+ ", depth: " + depth
+
770 ", big_endian: " + big_endian
+
771 ", true_color: " + true_color
+
772 ", red_max: " + red_max
+
773 ", green_max: " + green_max
+
774 ", blue_max: " + blue_max
+
775 ", red_shift: " + red_shift
+
776 ", green_shift: " + green_shift
+
777 ", blue_shift: " + blue_shift
);
779 /* Connection name/title */
780 name_length = ws.rQshift32();
781 fb_name = ws.rQshiftStr(name_length);
783 display.set_true_color(conf.true_color);
784 display.resize(fb_width, fb_height);
788 if (conf.true_color) {
796 response = pixelFormat();
797 response = response.concat(clientEncodings());
798 response = response.concat(fbUpdateRequest(0));
799 timing.fbu_rt_start = (new Date()).getTime();
802 /* Start pushing/polling */
803 setTimeout(checkEvents, conf.check_rate);
804 setTimeout(scan_tight_imgQ, scan_imgQ_rate);
807 updateState('normal', "Connected (encrypted) to: " + fb_name);
809 updateState('normal', "Connected (unencrypted) to: " + fb_name);
813 //Util.Debug("<< init_msg");
817 /* Normal RFB/VNC server message handler */
818 normal_msg = function() {
819 //Util.Debug(">> normal_msg");
821 var ret = true, msg_type, length, text,
822 c, first_colour, num_colours, red, green, blue;
827 msg_type = ws.rQshift8();
830 case 0: // FramebufferUpdate
831 ret = framebufferUpdate(); // false means need more data
833 case 1: // SetColourMapEntries
834 Util.Debug("SetColourMapEntries");
835 ws.rQshift8(); // Padding
836 first_colour = ws.rQshift16(); // First colour
837 num_colours = ws.rQshift16();
838 for (c=0; c < num_colours; c+=1) {
839 red = ws.rQshift16();
840 //Util.Debug("red before: " + red);
841 red = parseInt(red / 256, 10);
842 //Util.Debug("red after: " + red);
843 green = parseInt(ws.rQshift16() / 256, 10);
844 blue = parseInt(ws.rQshift16() / 256, 10);
845 display.set_colourMap([red, green, blue], first_colour + c);
847 Util.Debug("*** colourMap: " + display.get_colourMap());
848 Util.Info("Registered " + num_colours + " colourMap entries");
849 //Util.Debug("colourMap: " + display.get_colourMap());
855 case 3: // ServerCutText
856 Util.Debug("ServerCutText");
857 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
858 ws.rQshiftBytes(3); // Padding
859 length = ws.rQshift32();
860 if (ws.rQwait("ServerCutText", length, 8)) { return false; }
862 text = ws.rQshiftStr(length);
863 conf.clipboardReceive(that, text); // Obsolete
864 conf.onClipboard(that, text);
867 fail("Disconnected: illegal server message type " + msg_type);
868 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
871 //Util.Debug("<< normal_msg");
875 framebufferUpdate = function() {
876 var now, hdr, fbu_rt_diff, ret = true;
878 if (FBU.rects === 0) {
879 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
880 if (ws.rQwait("FBU header", 3)) {
881 ws.rQunshift8(0); // FBU msg_type
884 ws.rQshift8(); // padding
885 FBU.rects = ws.rQshift16();
886 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
889 if (timing.fbu_rt_start > 0) {
890 now = (new Date()).getTime();
891 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
895 while (FBU.rects > 0) {
896 if (rfb_state !== "normal") {
899 if (ws.rQwait("FBU", FBU.bytes)) { return false; }
900 if (FBU.bytes === 0) {
901 if (ws.rQwait("rect header", 12)) { return false; }
902 /* New FramebufferUpdate */
904 hdr
= ws
.rQshiftBytes(12);
905 FBU
.x
= (hdr
[0] << 8) + hdr
[1];
906 FBU
.y
= (hdr
[2] << 8) + hdr
[3];
907 FBU
.width
= (hdr
[4] << 8) + hdr
[5];
908 FBU
.height
= (hdr
[6] << 8) + hdr
[7];
909 FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
910 (hdr
[10] << 8) + hdr
[11], 10);
912 conf
.onFBUReceive(that
,
913 {'x': FBU
.x
, 'y': FBU
.y
,
914 'width': FBU
.width
, 'height': FBU
.height
,
915 'encoding': FBU
.encoding
,
916 'encodingName': encNames
[FBU
.encoding
]});
918 if (encNames
[FBU
.encoding
]) {
921 var msg = "FramebufferUpdate rects:" + FBU.rects;
922 msg += " x: " + FBU.x + " y: " + FBU.y;
923 msg += " width: " + FBU.width + " height: " + FBU.height;
924 msg += " encoding:" + FBU.encoding;
925 msg += "(" + encNames[FBU.encoding] + ")";
926 msg += ", ws.rQlen(): " + ws.rQlen();
930 fail("Disconnected: unsupported encoding " +
936 timing
.last_fbu
= (new Date()).getTime();
938 ret
= encHandlers
[FBU
.encoding
]();
940 now
= (new Date()).getTime();
941 timing
.cur_fbu
+= (now
- timing
.last_fbu
);
944 encStats
[FBU
.encoding
][0] += 1;
945 encStats
[FBU
.encoding
][1] += 1;
948 if (FBU
.rects
=== 0) {
949 if (((FBU
.width
=== fb_width
) &&
950 (FBU
.height
=== fb_height
)) ||
951 (timing
.fbu_rt_start
> 0)) {
952 timing
.full_fbu_total
+= timing
.cur_fbu
;
953 timing
.full_fbu_cnt
+= 1;
954 Util
.Info("Timing of full FBU, cur: " +
955 timing
.cur_fbu
+ ", total: " +
956 timing
.full_fbu_total
+ ", cnt: " +
957 timing
.full_fbu_cnt
+ ", avg: " +
958 (timing
.full_fbu_total
/
959 timing
.full_fbu_cnt
));
961 if (timing
.fbu_rt_start
> 0) {
962 fbu_rt_diff
= now
- timing
.fbu_rt_start
;
963 timing
.fbu_rt_total
+= fbu_rt_diff
;
964 timing
.fbu_rt_cnt
+= 1;
965 Util
.Info("full FBU round-trip, cur: " +
966 fbu_rt_diff
+ ", total: " +
967 timing
.fbu_rt_total
+ ", cnt: " +
968 timing
.fbu_rt_cnt
+ ", avg: " +
969 (timing
.fbu_rt_total
/
971 timing
.fbu_rt_start
= 0;
975 return ret
; // false ret means need more data
979 conf
.onFBUComplete(that
,
980 {'x': FBU
.x
, 'y': FBU
.y
,
981 'width': FBU
.width
, 'height': FBU
.height
,
982 'encoding': FBU
.encoding
,
983 'encodingName': encNames
[FBU
.encoding
]});
985 return true; // We finished this FBU
989 // FramebufferUpdate encodings
992 encHandlers
.RAW
= function display_raw() {
993 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
995 var cur_y
, cur_height
;
997 if (FBU
.lines
=== 0) {
998 FBU
.lines
= FBU
.height
;
1000 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least a line
1001 if (ws
.rQwait("RAW", FBU
.bytes
)) { return false; }
1002 cur_y
= FBU
.y
+ (FBU
.height
- FBU
.lines
);
1003 cur_height
= Math
.min(FBU
.lines
,
1004 Math
.floor(ws
.rQlen()/(FBU
.width
* fb_Bpp
)));
1005 display
.blitImage(FBU
.x
, cur_y
, FBU
.width
, cur_height
,
1006 ws
.get_rQ(), ws
.get_rQi());
1007 ws
.rQshiftBytes(FBU
.width
* cur_height
* fb_Bpp
);
1008 FBU
.lines
-= cur_height
;
1010 if (FBU
.lines
> 0) {
1011 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least another line
1016 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
1020 encHandlers
.COPYRECT
= function display_copy_rect() {
1021 //Util.Debug(">> display_copy_rect");
1025 if (ws
.rQwait("COPYRECT", 4)) { return false; }
1026 old_x
= ws
.rQshift16();
1027 old_y
= ws
.rQshift16();
1028 display
.copyImage(old_x
, old_y
, FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
);
1034 encHandlers
.RRE
= function display_rre() {
1035 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
1036 var color
, x
, y
, width
, height
, chunk
;
1038 if (FBU
.subrects
=== 0) {
1039 if (ws
.rQwait("RRE", 4+fb_Bpp
)) { return false; }
1040 FBU
.subrects
= ws
.rQshift32();
1041 color
= ws
.rQshiftBytes(fb_Bpp
); // Background
1042 display
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1044 while ((FBU
.subrects
> 0) && (ws
.rQlen() >= (fb_Bpp
+ 8))) {
1045 color
= ws
.rQshiftBytes(fb_Bpp
);
1048 width
= ws
.rQshift16();
1049 height
= ws
.rQshift16();
1050 display
.fillRect(FBU
.x
+ x
, FBU
.y
+ y
, width
, height
, color
);
1053 //Util.Debug(" display_rre: rects: " + FBU.rects +
1054 // ", FBU.subrects: " + FBU.subrects);
1056 if (FBU
.subrects
> 0) {
1057 chunk
= Math
.min(rre_chunk_sz
, FBU
.subrects
);
1058 FBU
.bytes
= (fb_Bpp
+ 8) * chunk
;
1063 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1067 encHandlers
.HEXTILE
= function display_hextile() {
1068 //Util.Debug(">> display_hextile");
1069 var subencoding
, subrects
, tile
, color
, cur_tile
,
1070 tile_x
, x
, w
, tile_y
, y
, h
, xy
, s
, sx
, sy
, wh
, sw
, sh
,
1071 rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1073 if (FBU
.tiles
=== 0) {
1074 FBU
.tiles_x
= Math
.ceil(FBU
.width
/16);
1075 FBU
.tiles_y
= Math
.ceil(FBU
.height
/16);
1076 FBU
.total_tiles
= FBU
.tiles_x
* FBU
.tiles_y
;
1077 FBU
.tiles
= FBU
.total_tiles
;
1080 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
1081 while (FBU
.tiles
> 0) {
1083 if (ws
.rQwait("HEXTILE subencoding", FBU
.bytes
)) { return false; }
1084 subencoding
= rQ
[rQi
]; // Peek
1085 if (subencoding
> 30) { // Raw
1086 fail("Disconnected: illegal hextile subencoding " + subencoding
);
1087 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
1091 cur_tile
= FBU
.total_tiles
- FBU
.tiles
;
1092 tile_x
= cur_tile
% FBU
.tiles_x
;
1093 tile_y
= Math
.floor(cur_tile
/ FBU
.tiles_x
);
1094 x
= FBU
.x
+ tile_x
* 16;
1095 y
= FBU
.y
+ tile_y
* 16;
1096 w
= Math
.min(16, (FBU
.x
+ FBU
.width
) - x
);
1097 h
= Math
.min(16, (FBU
.y
+ FBU
.height
) - y
);
1099 /* Figure out how much we are expecting */
1100 if (subencoding
& 0x01) { // Raw
1101 //Util.Debug(" Raw subencoding");
1102 FBU
.bytes
+= w
* h
* fb_Bpp
;
1104 if (subencoding
& 0x02) { // Background
1105 FBU
.bytes
+= fb_Bpp
;
1107 if (subencoding
& 0x04) { // Foreground
1108 FBU
.bytes
+= fb_Bpp
;
1110 if (subencoding
& 0x08) { // AnySubrects
1111 FBU
.bytes
+= 1; // Since we aren't shifting it off
1112 if (ws
.rQwait("hextile subrects header", FBU
.bytes
)) { return false; }
1113 subrects
= rQ
[rQi
+ FBU
.bytes
-1]; // Peek
1114 if (subencoding
& 0x10) { // SubrectsColoured
1115 FBU
.bytes
+= subrects
* (fb_Bpp
+ 2);
1117 FBU
.bytes
+= subrects
* 2;
1123 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1124 " (" + tile_x + "," + tile_y + ")" +
1125 " [" + x + "," + y + "]@" + w + "x" + h +
1126 ", subenc:" + subencoding +
1127 "(last: " + FBU.lastsubencoding + "), subrects:" +
1129 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
1130 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
1131 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
1133 if (ws
.rQwait("hextile", FBU
.bytes
)) { return false; }
1135 /* We know the encoding and have a whole tile */
1136 FBU
.subencoding
= rQ
[rQi
];
1138 if (FBU
.subencoding
=== 0) {
1139 if (FBU
.lastsubencoding
& 0x01) {
1140 /* Weird: ignore blanks after RAW */
1141 Util
.Debug(" Ignoring blank after RAW");
1143 display
.fillRect(x
, y
, w
, h
, FBU
.background
);
1145 } else if (FBU
.subencoding
& 0x01) { // Raw
1146 display
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1147 rQi
+= FBU
.bytes
- 1;
1149 if (FBU
.subencoding
& 0x02) { // Background
1150 FBU
.background
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1153 if (FBU
.subencoding
& 0x04) { // Foreground
1154 FBU
.foreground
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1158 tile
= display
.getTile(x
, y
, w
, h
, FBU
.background
);
1159 if (FBU
.subencoding
& 0x08) { // AnySubrects
1162 for (s
= 0; s
< subrects
; s
+= 1) {
1163 if (FBU
.subencoding
& 0x10) { // SubrectsColoured
1164 color
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1167 color
= FBU
.foreground
;
1177 sh
= (wh
& 0x0f) + 1;
1179 display
.setSubTile(tile
, sx
, sy
, sw
, sh
, color
);
1182 display
.putTile(tile
);
1185 FBU
.lastsubencoding
= FBU
.subencoding
;
1190 if (FBU
.tiles
=== 0) {
1194 //Util.Debug("<< display_hextile");
1199 encHandlers
.TIGHT_PNG
= function display_tight_png() {
1200 //Util.Debug(">> display_tight_png");
1201 var ctl
, cmode
, clength
, getCLength
, color
, img
;
1202 //Util.Debug(" FBU.rects: " + FBU.rects);
1203 //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1205 FBU
.bytes
= 1; // compression-control byte
1206 if (ws
.rQwait("TIGHT compression-control", FBU
.bytes
)) { return false; }
1208 // Get 'compact length' header and data size
1209 getCLength = function (arr
) {
1210 var header
= 1, data
= 0;
1211 data
+= arr
[0] & 0x7f;
1212 if (arr
[0] & 0x80) {
1214 data
+= (arr
[1] & 0x7f) << 7;
1215 if (arr
[1] & 0x80) {
1217 data
+= arr
[2] << 14;
1220 return [header
, data
];
1225 case 0x08: cmode
= "fill"; break;
1226 case 0x09: cmode
= "jpeg"; break;
1227 case 0x0A: cmode
= "png"; break;
1228 default: throw("Illegal basic compression received, ctl: " + ctl
);
1231 // fill uses fb_depth because TPIXELs drop the padding byte
1232 case "fill": FBU
.bytes
+= fb_depth
; break; // TPIXEL
1233 case "jpeg": FBU
.bytes
+= 3; break; // max clength
1234 case "png": FBU
.bytes
+= 3; break; // max clength
1237 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1239 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1240 //Util.Debug(" cmode: " + cmode);
1242 // Determine FBU.bytes
1245 ws
.rQshift8(); // shift off ctl
1246 color
= ws
.rQshiftBytes(fb_depth
);
1249 'img': {'complete': true},
1253 'height': FBU
.height
,
1258 clength
= getCLength(ws
.rQslice(1, 4));
1259 FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1260 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1262 // We have everything, render it
1263 //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
1264 ws
.rQshiftBytes(1 + clength
[0]); // shift off ctl + compact length
1266 //img.onload = scan_tight_imgQ;
1272 img
.src
= "data:image/" + cmode
+
1273 extract_data_uri(ws
.rQshiftBytes(clength
[1]));
1279 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1280 //Util.Debug("<< display_tight_png");
1284 extract_data_uri = function(arr
) {
1286 //for (i=0; i< arr.length; i += 1) {
1287 // stra.push(String.fromCharCode(arr[i]));
1289 //return "," + escape(stra.join(''));
1290 return ";base64," + Base64
.encode(arr
);
1293 scan_tight_imgQ = function() {
1294 var data
, imgQ
, ctx
;
1295 ctx
= display
.get_context();
1296 if (rfb_state
=== 'normal') {
1298 while ((imgQ
.length
> 0) && (imgQ
[0].img
.complete
)) {
1299 data
= imgQ
.shift();
1300 if (data
['type'] === 'fill') {
1301 display
.fillRect(data
.x
, data
.y
, data
.width
, data
.height
, data
.color
);
1303 ctx
.drawImage(data
.img
, data
.x
, data
.y
);
1306 setTimeout(scan_tight_imgQ
, scan_imgQ_rate
);
1310 encHandlers
.DesktopSize
= function set_desktopsize() {
1311 Util
.Debug(">> set_desktopsize");
1312 fb_width
= FBU
.width
;
1313 fb_height
= FBU
.height
;
1314 display
.resize(fb_width
, fb_height
);
1315 timing
.fbu_rt_start
= (new Date()).getTime();
1316 // Send a new non-incremental request
1317 ws
.send(fbUpdateRequest(0));
1322 Util
.Debug("<< set_desktopsize");
1326 encHandlers
.Cursor
= function set_cursor() {
1327 var x
, y
, w
, h
, pixelslength
, masklength
;
1328 //Util.Debug(">> set_cursor");
1329 x
= FBU
.x
; // hotspot-x
1330 y
= FBU
.y
; // hotspot-y
1334 pixelslength
= w
* h
* fb_Bpp
;
1335 masklength
= Math
.floor((w
+ 7) / 8) * h
;
1337 FBU
.bytes
= pixelslength
+ masklength
;
1338 if (ws
.rQwait("cursor encoding", FBU
.bytes
)) { return false; }
1340 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1342 display
.changeCursor(ws
.rQshiftBytes(pixelslength
),
1343 ws
.rQshiftBytes(masklength
),
1349 //Util.Debug("<< set_cursor");
1353 encHandlers
.JPEG_quality_lo
= function set_jpeg_quality() {
1354 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1357 encHandlers
.compress_lo
= function set_compress_level() {
1358 Util
.Error("Server sent compress level pseudo-encoding");
1362 * Client message routines
1365 pixelFormat = function() {
1366 //Util.Debug(">> pixelFormat");
1368 arr
= [0]; // msg-type
1369 arr
.push8(0); // padding
1370 arr
.push8(0); // padding
1371 arr
.push8(0); // padding
1373 arr
.push8(fb_Bpp
* 8); // bits-per-pixel
1374 arr
.push8(fb_depth
* 8); // depth
1375 arr
.push8(0); // little-endian
1376 arr
.push8(conf
.true_color
? 1 : 0); // true-color
1378 arr
.push16(255); // red-max
1379 arr
.push16(255); // green-max
1380 arr
.push16(255); // blue-max
1381 arr
.push8(0); // red-shift
1382 arr
.push8(8); // green-shift
1383 arr
.push8(16); // blue-shift
1385 arr
.push8(0); // padding
1386 arr
.push8(0); // padding
1387 arr
.push8(0); // padding
1388 //Util.Debug("<< pixelFormat");
1392 clientEncodings = function() {
1393 //Util.Debug(">> clientEncodings");
1394 var arr
, i
, encList
= [];
1396 for (i
=0; i
<encodings
.length
; i
+= 1) {
1397 if ((encodings
[i
][0] === "Cursor") &&
1398 (! conf
.local_cursor
)) {
1399 Util
.Debug("Skipping Cursor pseudo-encoding");
1401 //Util.Debug("Adding encoding: " + encodings[i][0]);
1402 encList
.push(encodings
[i
][1]);
1406 arr
= [2]; // msg-type
1407 arr
.push8(0); // padding
1409 arr
.push16(encList
.length
); // encoding count
1410 for (i
=0; i
< encList
.length
; i
+= 1) {
1411 arr
.push32(encList
[i
]);
1413 //Util.Debug("<< clientEncodings: " + arr);
1417 fbUpdateRequest = function(incremental
, x
, y
, xw
, yw
) {
1418 //Util.Debug(">> fbUpdateRequest");
1421 if (!xw
) { xw
= fb_width
; }
1422 if (!yw
) { yw
= fb_height
; }
1424 arr
= [3]; // msg-type
1425 arr
.push8(incremental
);
1430 //Util.Debug("<< fbUpdateRequest");
1434 keyEvent = function(keysym
, down
) {
1435 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1437 arr
= [4]; // msg-type
1441 //Util.Debug("<< keyEvent");
1445 pointerEvent = function(x
, y
) {
1446 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1447 // " , mask: " + mouse_buttonMask);
1449 arr
= [5]; // msg-type
1450 arr
.push8(mouse_buttonMask
);
1453 //Util.Debug("<< pointerEvent");
1457 clientCutText = function(text
) {
1458 //Util.Debug(">> clientCutText");
1460 arr
= [6]; // msg-type
1461 arr
.push8(0); // padding
1462 arr
.push8(0); // padding
1463 arr
.push8(0); // padding
1464 arr
.push32(text
.length
);
1466 for (i
=0; i
< n
; i
+=1) {
1467 arr
.push(text
.charCodeAt(i
));
1469 //Util.Debug("<< clientCutText:" + arr);
1476 // Public API interface functions
1479 that
.connect = function(host
, port
, password
, uri
) {
1480 //Util.Debug(">> connect");
1484 rfb_password
= (password
!== undefined) ? password
: "";
1485 rfb_uri
= (uri
!== undefined) ? uri
: "";
1487 if ((!rfb_host
) || (!rfb_port
)) {
1488 return fail("Must set host and port");
1491 updateState('connect');
1492 //Util.Debug("<< connect");
1496 that
.disconnect = function() {
1497 //Util.Debug(">> disconnect");
1498 updateState('disconnect', 'Disconnecting');
1499 //Util.Debug("<< disconnect");
1502 that
.sendPassword = function(passwd
) {
1503 rfb_password
= passwd
;
1504 rfb_state
= "Authentication";
1505 setTimeout(init_msg
, 1);
1508 that
.sendCtrlAltDel = function() {
1509 if (rfb_state
!== "normal") { return false; }
1510 Util
.Info("Sending Ctrl-Alt-Del");
1512 arr
= arr
.concat(keyEvent(0xFFE3, 1)); // Control
1513 arr
= arr
.concat(keyEvent(0xFFE9, 1)); // Alt
1514 arr
= arr
.concat(keyEvent(0xFFFF, 1)); // Delete
1515 arr
= arr
.concat(keyEvent(0xFFFF, 0)); // Delete
1516 arr
= arr
.concat(keyEvent(0xFFE9, 0)); // Alt
1517 arr
= arr
.concat(keyEvent(0xFFE3, 0)); // Control
1518 arr
= arr
.concat(fbUpdateRequest(1));
1522 // Send a key press. If 'down' is not specified then send a down key
1523 // followed by an up key.
1524 that
.sendKey = function(code
, down
) {
1525 if (rfb_state
!== "normal") { return false; }
1527 if (typeof down
!== 'undefined') {
1528 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
1529 arr
= arr
.concat(keyEvent(code
, down
? 1 : 0));
1531 Util
.Info("Sending key code (down + up): " + code
);
1532 arr
= arr
.concat(keyEvent(code
, 1));
1533 arr
= arr
.concat(keyEvent(code
, 0));
1535 arr
= arr
.concat(fbUpdateRequest(1));
1539 that
.clipboardPasteFrom = function(text
) {
1540 if (rfb_state
!== "normal") { return; }
1541 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1542 ws
.send(clientCutText(text
));
1543 //Util.Debug("<< clipboardPasteFrom");
1546 // Override internal functions for testing
1547 that
.testMode = function(override_send
) {
1549 that
.recv_message
= ws
.testMode(override_send
);
1551 checkEvents = function () { /* Stub Out */ };
1552 that
.connect = function(host
, port
, password
) {
1555 rfb_password
= password
;
1556 updateState('ProtocolVersion', "Starting VNC handshake");
1561 return constructor(); // Return the public API interface