]>
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 */
16 conf
= conf
|| {}; // Configuration
17 var that
= {}, // Public API interface
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
39 rfb_state
= 'disconnected',
45 // In preference order
52 ['DesktopSize', -223 ],
55 // Psuedo-encoding settings
56 ['JPEG_quality_lo', -32 ],
57 //['JPEG_quality_hi', -23 ],
58 ['compress_lo', -255 ]
59 //['compress_hi', -247 ]
64 encStats
= {}, // [rectCnt, rectCntTot]
66 ws
= null, // Websock object
67 display
= null, // Display object
68 keyboard
= null, // Keyboard input handler object
69 mouse
= null, // Mouse input handler object
70 sendTimer
= null, // Send Queue check timer
71 connTimer
= null, // connection timer
72 disconnTimer
= null, // disconnection timer
73 msgTimer
= null, // queued handle_message timer
75 // Frame buffer update state
89 imgQ
: [] // TIGHT_PNG image queue
117 mouse_buttonMask
= 0,
122 // Configuration settings
124 function cdef(v
, type
, defval
, desc
) {
125 Util
.conf_default(conf
, that
, v
, type
, defval
, desc
); }
127 cdef('target', 'dom', null, 'VNC display rendering Canvas object');
128 cdef('focusContainer', 'dom', document
, 'DOM element that captures keyboard input');
130 cdef('encrypt', 'bool', false, 'Use TLS/SSL/wss encryption');
131 cdef('true_color', 'bool', true, 'Request true color pixel data');
132 cdef('local_cursor', 'bool', false, 'Request locally rendered cursor');
133 cdef('shared', 'bool', true, 'Request shared mode');
135 if (Websock_native
) {
136 cdef('connectTimeout', 'int', 2, 'Time (s) to wait for connection');
138 cdef('connectTimeout', 'int', 5, 'Time (s) to wait for connection');
140 cdef('disconnectTimeout', 'int', 3, 'Time (s) to wait for disconnection');
141 cdef('check_rate', 'int', 217, 'Timing (ms) of send/receive check');
142 cdef('fbu_req_rate', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests');
144 // Callback functions
145 cdef('onUpdateState', 'func', function() { },
146 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change ');
147 cdef('onPasswordRequired', 'func', function() { },
148 'onPasswordRequired(rfb): VNC password is required ');
149 cdef('onClipboard', 'func', function() { },
150 'onClipboard(rfb, text): RFB clipboard contents received');
151 cdef('onBell', 'func', function() { },
152 'onBell(rfb): RFB Bell message received ');
153 cdef('onFBUReceive', 'func', function() { },
154 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed ');
155 cdef('onFBUComplete', 'func', function() { },
156 'onFBUComplete(rfb, fbu): RFB FBU received and processed ');
158 // These callback names are deprecated
159 cdef('updateState', 'func', function() { },
160 'obsolete, use onUpdateState');
161 cdef('clipboardReceive', 'func', function() { },
162 'obsolete, use onClipboard');
165 // Override/add some specific getters/setters
166 that
.set_local_cursor = function(cursor
) {
167 if ((!cursor
) || (cursor
in {'0':1, 'no':1, 'false':1})) {
168 conf
.local_cursor
= false;
170 if (display
.get_cursor_uri()) {
171 conf
.local_cursor
= true;
173 Util
.Warn("Browser does not support local cursor");
178 that
.get_display = function() {
181 that
.get_keyboard = function() {
184 that
.get_mouse = function() {
197 // Create the public API interface and initialize values that stay
198 // constant across connect/disconnect
199 function constructor() {
201 Util
.Debug(">> RFB.constructor");
203 // Create lookup tables based encoding number
204 for (i
=0; i
< encodings
.length
; i
+=1) {
205 encHandlers
[encodings
[i
][1]] = encHandlers
[encodings
[i
][0]];
206 encNames
[encodings
[i
][1]] = encodings
[i
][0];
207 encStats
[encodings
[i
][1]] = [0, 0];
209 // Initialize display, mouse, keyboard, and websock
211 display
= new Display({'target': conf
.target
});
213 Util
.Error("Display exception: " + exc
);
214 updateState('fatal', "No working Display");
216 keyboard
= new Keyboard({'target': conf
.focusContainer
,
217 'onKeyPress': keyPress
});
218 mouse
= new Mouse({'target': conf
.target
,
219 'onMouseButton': mouseButton
,
220 'onMouseMove': mouseMove
});
222 rmode
= display
.get_render_mode();
224 if (typeof noVNC_logo
!== 'undefined') {
225 display
.set_logo(noVNC_logo
);
229 ws
.on('message', handle_message
);
230 ws
.on('open', function() {
231 if (rfb_state
=== "connect") {
232 updateState('ProtocolVersion', "Starting VNC handshake");
234 fail("Got unexpected WebSockets connection");
237 ws
.on('close', function() {
238 if (rfb_state
=== 'disconnect') {
239 updateState('disconnected', 'VNC disconnected');
240 } else if (rfb_state
=== 'ProtocolVersion') {
241 fail('Failed to connect to server');
242 } else if (rfb_state
in {'failed':1, 'disconnected':1}) {
243 Util
.Error("Received onclose while disconnected");
245 fail('Server disconnected');
248 ws
.on('error', function(e
) {
249 fail("WebSock error: " + e
);
255 /* Check web-socket-js if no builtin WebSocket support */
256 if (Websock_native
) {
257 Util
.Info("Using native WebSockets");
258 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
260 Util
.Warn("Using web-socket-js bridge. Flash version: " +
262 if ((! Util
.Flash
) ||
263 (Util
.Flash
.version
< 9)) {
264 updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
265 } else if (document
.location
.href
.substr(0, 7) === "file://") {
267 "'file://' URL is incompatible with Adobe Flash");
269 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
273 Util
.Debug("<< RFB.constructor");
274 return that
; // Return the public API interface
278 Util
.Debug(">> RFB.connect");
286 uri
+= rfb_host
+ ":" + rfb_port
+ "/";
287 Util
.Info("connecting to " + uri
);
290 Util
.Debug("<< RFB.connect");
293 // Initialize variables that are reset before each connection
294 init_vars = function() {
301 FBU
.subrects
= 0; // RRE and HEXTILE
302 FBU
.lines
= 0; // RAW
303 FBU
.tiles
= 0; // HEXTILE
304 FBU
.imgQ
= []; // TIGHT_PNG image queue
305 mouse_buttonMask
= 0;
308 // Clear the per connection encoding stats
309 for (i
=0; i
< encodings
.length
; i
+=1) {
310 encStats
[encodings
[i
][1]][0] = 0;
315 print_stats = function() {
317 Util
.Info("Encoding stats for this connection:");
318 for (i
=0; i
< encodings
.length
; i
+=1) {
319 s
= encStats
[encodings
[i
][1]];
320 if ((s
[0] + s
[1]) > 0) {
321 Util
.Info(" " + encodings
[i
][0] + ": " +
325 Util
.Info("Encoding stats since page load:");
326 for (i
=0; i
< encodings
.length
; i
+=1) {
327 s
= encStats
[encodings
[i
][1]];
328 if ((s
[0] + s
[1]) > 0) {
329 Util
.Info(" " + encodings
[i
][0] + ": " +
342 * loaded - page load, equivalent to disconnected
343 * disconnected - idle state
344 * connect - starting to connect (to ProtocolVersion)
346 * disconnect - starting to disconnect
347 * failed - abnormal disconnect
348 * fatal - failed to load page, or fatal error
350 * RFB protocol initialization states:
354 * password - waiting for password, not part of RFB
356 * ClientInitialization - not triggered by server message
357 * ServerInitialization (to normal)
359 updateState = function(state
, statusMsg
) {
360 var func
, cmsg
, oldstate
= rfb_state
;
362 if (state
=== oldstate
) {
363 /* Already here, ignore */
364 Util
.Debug("Already in state '" + state
+ "', ignoring.");
369 * These are disconnected states. A previous connect may
370 * asynchronously cause a connection so make sure we are closed.
372 if (state
in {'disconnected':1, 'loaded':1, 'connect':1,
373 'disconnect':1, 'failed':1, 'fatal':1}) {
375 clearInterval(sendTimer
);
380 clearInterval(msgTimer
);
384 if (display
&& display
.get_context()) {
387 display
.defaultCursor();
388 if ((Util
.get_logging() !== 'debug') ||
389 (state
=== 'loaded')) {
390 // Show noVNC logo on load and when disconnected if
399 if (oldstate
=== 'fatal') {
400 Util
.Error("Fatal error, cannot continue");
403 if ((state
=== 'failed') || (state
=== 'fatal')) {
409 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
410 // Do disconnect action, but stay in failed state.
411 rfb_state
= 'failed';
416 cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
417 func("New state '" + rfb_state
+ "', was '" + oldstate
+ "'." + cmsg
);
419 if (connTimer
&& (rfb_state
!== 'connect')) {
420 Util
.Debug("Clearing connect timer");
421 clearInterval(connTimer
);
425 if (disconnTimer
&& (rfb_state
!== 'disconnect')) {
426 Util
.Debug("Clearing disconnect timer");
427 clearInterval(disconnTimer
);
433 if ((oldstate
=== 'disconnected') || (oldstate
=== 'failed')) {
434 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
442 connTimer
= setTimeout(function () {
443 fail("Connect timeout");
444 }, conf
.connectTimeout
* 1000);
449 // WebSocket.onopen transitions to 'ProtocolVersion'
456 disconnTimer
= setTimeout(function () {
457 fail("Disconnect timeout");
458 }, conf
.disconnectTimeout
* 1000);
463 // WebSocket.onclose transitions to 'disconnected'
468 if (oldstate
=== 'disconnected') {
469 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
471 if (oldstate
=== 'normal') {
472 Util
.Error("Error while connected.");
474 if (oldstate
=== 'init') {
475 Util
.Error("Error while initializing.");
478 // Make sure we transition to disconnected
479 setTimeout(function() { updateState('disconnected'); }, 50);
485 // No state change action to take
489 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
490 // Leave the failed message
491 conf
.updateState(that
, state
, oldstate
); // Obsolete
492 conf
.onUpdateState(that
, state
, oldstate
);
494 conf
.updateState(that
, state
, oldstate
, statusMsg
); // Obsolete
495 conf
.onUpdateState(that
, state
, oldstate
, statusMsg
);
499 fail = function(msg
) {
500 updateState('failed', msg
);
504 handle_message = function() {
505 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
506 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
507 if (ws
.rQlen() === 0) {
508 Util
.Warn("handle_message called on empty receive queue");
514 Util
.Error("Got data while disconnected");
517 if (normal_msg() && ws
.rQlen() > 0) {
518 // true means we can continue processing
519 // Give other events a chance to run
520 if (msgTimer
=== null) {
521 Util
.Debug("More data to process, creating timer");
522 msgTimer
= setTimeout(function () {
527 Util
.Debug("More data to process, existing timer");
538 function genDES(password
, challenge
) {
540 for (i
=0; i
< password
.length
; i
+= 1) {
541 passwd
.push(password
.charCodeAt(i
));
543 return (new DES(passwd
)).encrypt(challenge
);
546 function flushClient() {
547 if (mouse_arr
.length
> 0) {
548 //send(mouse_arr.concat(fbUpdateRequest(1)));
550 setTimeout(function() {
551 ws
.send(fbUpdateRequest(1));
561 // overridable for testing
562 checkEvents = function() {
564 if (rfb_state
=== 'normal') {
565 if (! flushClient()) {
566 now
= new Date().getTime();
567 if (now
> last_req_time
+ conf
.fbu_req_rate
) {
569 ws
.send(fbUpdateRequest(1));
573 setTimeout(checkEvents
, conf
.check_rate
);
576 keyPress = function(keysym
, down
) {
578 arr
= keyEvent(keysym
, down
);
579 arr
= arr
.concat(fbUpdateRequest(1));
583 mouseButton = function(x
, y
, down
, bmask
) {
585 mouse_buttonMask
|= bmask
;
587 mouse_buttonMask
^= bmask
;
589 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
593 mouseMove = function(x
, y
) {
594 //Util.Debug('>> mouseMove ' + x + "," + y);
595 mouse_arr
= mouse_arr
.concat( pointerEvent(x
, y
) );
600 // Server message handlers
603 // RFB/VNC initialisation message handler
604 init_msg = function() {
605 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
607 var strlen
, reason
, length
, sversion
, cversion
,
608 i
, types
, num_types
, challenge
, response
, bpp
, depth
,
609 big_endian
, true_color
, name_length
;
611 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
614 case 'ProtocolVersion' :
615 if (ws
.rQlen() < 12) {
616 return fail("Incomplete protocol version");
618 sversion
= ws
.rQshiftStr(12).substr(4,7);
619 Util
.Info("Server ProtocolVersion: " + sversion
);
621 case "003.003": rfb_version
= 3.3; break;
622 case "003.006": rfb_version
= 3.3; break; // UltraVNC
623 case "003.007": rfb_version
= 3.7; break;
624 case "003.008": rfb_version
= 3.8; break;
626 return fail("Invalid server version " + sversion
);
628 if (rfb_version
> rfb_max_version
) {
629 rfb_version
= rfb_max_version
;
633 sendTimer
= setInterval(function() {
634 // Send updates either at a rate of one update
635 // every 50ms, or whatever slower rate the network
641 cversion
= "00" + parseInt(rfb_version
,10) +
642 ".00" + ((rfb_version
* 10) % 10);
643 ws
.send_string("RFB " + cversion
+ "\n");
644 updateState('Security', "Sent ProtocolVersion: " + cversion
);
648 if (rfb_version
>= 3.7) {
649 // Server sends supported list, client decides
650 num_types
= ws
.rQshift8();
651 if (ws
.rQwait("security type", num_types
, 1)) { return false; }
652 if (num_types
=== 0) {
653 strlen
= ws
.rQshift32();
654 reason
= ws
.rQshiftStr(strlen
);
655 return fail("Security failure: " + reason
);
658 types
= ws
.rQshiftBytes(num_types
);
659 Util
.Debug("Server security types: " + types
);
660 for (i
=0; i
< types
.length
; i
+=1) {
661 if ((types
[i
] > rfb_auth_scheme
) && (types
[i
] < 3)) {
662 rfb_auth_scheme
= types
[i
];
665 if (rfb_auth_scheme
=== 0) {
666 return fail("Unsupported security types: " + types
);
669 ws
.send([rfb_auth_scheme
]);
672 if (ws
.rQwait("security scheme", 4)) { return false; }
673 rfb_auth_scheme
= ws
.rQshift32();
675 updateState('Authentication',
676 "Authenticating using scheme: " + rfb_auth_scheme
);
677 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
680 // Triggered by fallthough, not by server message
681 case 'Authentication' :
682 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
683 switch (rfb_auth_scheme
) {
684 case 0: // connection failed
685 if (ws
.rQwait("auth reason", 4)) { return false; }
686 strlen
= ws
.rQshift32();
687 reason
= ws
.rQshiftStr(strlen
);
688 return fail("Auth failure: " + reason
);
689 case 1: // no authentication
690 if (rfb_version
>= 3.8) {
691 updateState('SecurityResult');
694 // Fall through to ClientInitialisation
696 case 2: // VNC authentication
697 if (rfb_password
.length
=== 0) {
698 // Notify via both callbacks since it is kind of
699 // a RFB state change and a UI interface issue.
700 updateState('password', "Password Required");
701 conf
.onPasswordRequired(that
);
704 if (ws
.rQwait("auth challenge", 16)) { return false; }
705 challenge
= ws
.rQshiftBytes(16);
706 //Util.Debug("Password: " + rfb_password);
707 //Util.Debug("Challenge: " + challenge +
708 // " (" + challenge.length + ")");
709 response
= genDES(rfb_password
, challenge
);
710 //Util.Debug("Response: " + response +
711 // " (" + response.length + ")");
713 //Util.Debug("Sending DES encrypted auth response");
715 updateState('SecurityResult');
718 fail("Unsupported auth scheme: " + rfb_auth_scheme
);
721 updateState('ClientInitialisation', "No auth required");
722 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
725 case 'SecurityResult' :
726 if (ws
.rQwait("VNC auth response ", 4)) { return false; }
727 switch (ws
.rQshift32()) {
729 // Fall through to ClientInitialisation
732 if (rfb_version
>= 3.8) {
733 length
= ws
.rQshift32();
734 if (ws
.rQwait("SecurityResult reason", length
, 8)) {
737 reason
= ws
.rQshiftStr(length
);
740 fail("Authentication failed");
744 return fail("Too many auth attempts");
746 updateState('ClientInitialisation', "Authentication OK");
747 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
750 // Triggered by fallthough, not by server message
751 case 'ClientInitialisation' :
752 ws
.send([conf
.shared
? 1 : 0]); // ClientInitialisation
753 updateState('ServerInitialisation', "Authentication OK");
756 case 'ServerInitialisation' :
757 if (ws
.rQwait("server initialization", 24)) { return false; }
760 fb_width
= ws
.rQshift16();
761 fb_height
= ws
.rQshift16();
765 depth
= ws
.rQshift8();
766 big_endian
= ws
.rQshift8();
767 true_color
= ws
.rQshift8();
769 Util
.Info("Screen: " + fb_width
+ "x" + fb_height
+
770 ", bpp: " + bpp
+ ", depth: " + depth
+
771 ", big_endian: " + big_endian
+
772 ", true_color: " + true_color
);
774 /* Connection name/title */
776 name_length = ws.rQshift32();
777 fb_name = ws.rQshiftStr(name_length);
779 display.set_true_color(conf.true_color);
780 display.resize(fb_width, fb_height);
784 if (conf.true_color) {
792 response = pixelFormat();
793 response = response.concat(clientEncodings());
794 response = response.concat(fbUpdateRequest(0));
795 timing.fbu_rt_start = (new Date()).getTime();
798 /* Start pushing/polling */
799 setTimeout(checkEvents, conf.check_rate);
800 setTimeout(scan_tight_imgQ, scan_imgQ_rate);
803 updateState('normal', "Connected (encrypted) to: " + fb_name);
805 updateState('normal', "Connected (unencrypted) to: " + fb_name);
809 //Util.Debug("<< init_msg");
813 /* Normal RFB/VNC server message handler */
814 normal_msg = function() {
815 //Util.Debug(">> normal_msg");
817 var ret = true, msg_type, length, text,
818 c, first_colour, num_colours, red, green, blue;
823 msg_type = ws.rQshift8();
826 case 0: // FramebufferUpdate
827 ret = framebufferUpdate(); // false means need more data
829 case 1: // SetColourMapEntries
830 Util.Debug("SetColourMapEntries");
831 ws.rQshift8(); // Padding
832 first_colour = ws.rQshift16(); // First colour
833 num_colours = ws.rQshift16();
834 for (c=0; c < num_colours; c+=1) {
835 red = ws.rQshift16();
836 //Util.Debug("red before: " + red);
837 red = parseInt(red / 256, 10);
838 //Util.Debug("red after: " + red);
839 green = parseInt(ws.rQshift16() / 256, 10);
840 blue = parseInt(ws.rQshift16() / 256, 10);
841 Util.Debug("*** colourMap: " + display.get_colourMap());
842 display.set_colourMap([red, green, blue], first_colour + c);
844 Util.Info("Registered " + num_colours + " colourMap entries");
845 //Util.Debug("colourMap: " + display.get_colourMap());
851 case 3: // ServerCutText
852 Util.Debug("ServerCutText");
853 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
854 ws.rQshiftBytes(3); // Padding
855 length = ws.rQshift32();
856 if (ws.rQwait("ServerCutText", length, 8)) { return false; }
858 text = ws.rQshiftStr(length);
859 conf.clipboardReceive(that, text); // Obsolete
860 conf.onClipboard(that, text);
863 fail("Disconnected: illegal server message type " + msg_type);
864 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
867 //Util.Debug("<< normal_msg");
871 framebufferUpdate = function() {
872 var now, hdr, fbu_rt_diff, ret = true;
874 if (FBU.rects === 0) {
875 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
876 if (ws.rQwait("FBU header", 3)) {
877 ws.rQunshift8(0); // FBU msg_type
880 ws.rQshift8(); // padding
881 FBU.rects = ws.rQshift16();
882 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
885 if (timing.fbu_rt_start > 0) {
886 now = (new Date()).getTime();
887 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
891 while (FBU.rects > 0) {
892 if (rfb_state !== "normal") {
895 if (ws.rQwait("FBU", FBU.bytes)) { return false; }
896 if (FBU.bytes === 0) {
897 if (ws.rQwait("rect header", 12)) { return false; }
898 /* New FramebufferUpdate */
900 hdr
= ws
.rQshiftBytes(12);
901 FBU
.x
= (hdr
[0] << 8) + hdr
[1];
902 FBU
.y
= (hdr
[2] << 8) + hdr
[3];
903 FBU
.width
= (hdr
[4] << 8) + hdr
[5];
904 FBU
.height
= (hdr
[6] << 8) + hdr
[7];
905 FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
906 (hdr
[10] << 8) + hdr
[11], 10);
908 conf
.onFBUReceive(that
,
909 {'x': FBU
.x
, 'y': FBU
.y
,
910 'width': FBU
.width
, 'height': FBU
.height
,
911 'encoding': FBU
.encoding
,
912 'encodingName': encNames
[FBU
.encoding
]});
914 if (encNames
[FBU
.encoding
]) {
917 var msg = "FramebufferUpdate rects:" + FBU.rects;
918 msg += " x: " + FBU.x + " y: " + FBU.y;
919 msg += " width: " + FBU.width + " height: " + FBU.height;
920 msg += " encoding:" + FBU.encoding;
921 msg += "(" + encNames[FBU.encoding] + ")";
922 msg += ", ws.rQlen(): " + ws.rQlen();
926 fail("Disconnected: unsupported encoding " +
932 timing
.last_fbu
= (new Date()).getTime();
934 ret
= encHandlers
[FBU
.encoding
]();
936 now
= (new Date()).getTime();
937 timing
.cur_fbu
+= (now
- timing
.last_fbu
);
940 encStats
[FBU
.encoding
][0] += 1;
941 encStats
[FBU
.encoding
][1] += 1;
944 if (FBU
.rects
=== 0) {
945 if (((FBU
.width
=== fb_width
) &&
946 (FBU
.height
=== fb_height
)) ||
947 (timing
.fbu_rt_start
> 0)) {
948 timing
.full_fbu_total
+= timing
.cur_fbu
;
949 timing
.full_fbu_cnt
+= 1;
950 Util
.Info("Timing of full FBU, cur: " +
951 timing
.cur_fbu
+ ", total: " +
952 timing
.full_fbu_total
+ ", cnt: " +
953 timing
.full_fbu_cnt
+ ", avg: " +
954 (timing
.full_fbu_total
/
955 timing
.full_fbu_cnt
));
957 if (timing
.fbu_rt_start
> 0) {
958 fbu_rt_diff
= now
- timing
.fbu_rt_start
;
959 timing
.fbu_rt_total
+= fbu_rt_diff
;
960 timing
.fbu_rt_cnt
+= 1;
961 Util
.Info("full FBU round-trip, cur: " +
962 fbu_rt_diff
+ ", total: " +
963 timing
.fbu_rt_total
+ ", cnt: " +
964 timing
.fbu_rt_cnt
+ ", avg: " +
965 (timing
.fbu_rt_total
/
967 timing
.fbu_rt_start
= 0;
971 return ret
; // false ret means need more data
975 conf
.onFBUComplete(that
,
976 {'x': FBU
.x
, 'y': FBU
.y
,
977 'width': FBU
.width
, 'height': FBU
.height
,
978 'encoding': FBU
.encoding
,
979 'encodingName': encNames
[FBU
.encoding
]});
981 return true; // We finished this FBU
985 // FramebufferUpdate encodings
988 encHandlers
.RAW
= function display_raw() {
989 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
991 var cur_y
, cur_height
;
993 if (FBU
.lines
=== 0) {
994 FBU
.lines
= FBU
.height
;
996 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least a line
997 if (ws
.rQwait("RAW", FBU
.bytes
)) { return false; }
998 cur_y
= FBU
.y
+ (FBU
.height
- FBU
.lines
);
999 cur_height
= Math
.min(FBU
.lines
,
1000 Math
.floor(ws
.rQlen()/(FBU
.width
* fb_Bpp
)));
1001 display
.blitImage(FBU
.x
, cur_y
, FBU
.width
, cur_height
,
1002 ws
.get_rQ(), ws
.get_rQi());
1003 ws
.rQshiftBytes(FBU
.width
* cur_height
* fb_Bpp
);
1004 FBU
.lines
-= cur_height
;
1006 if (FBU
.lines
> 0) {
1007 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least another line
1012 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
1016 encHandlers
.COPYRECT
= function display_copy_rect() {
1017 //Util.Debug(">> display_copy_rect");
1021 if (ws
.rQwait("COPYRECT", 4)) { return false; }
1022 old_x
= ws
.rQshift16();
1023 old_y
= ws
.rQshift16();
1024 display
.copyImage(old_x
, old_y
, FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
);
1030 encHandlers
.RRE
= function display_rre() {
1031 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
1032 var color
, x
, y
, width
, height
, chunk
;
1034 if (FBU
.subrects
=== 0) {
1035 if (ws
.rQwait("RRE", 4+fb_Bpp
)) { return false; }
1036 FBU
.subrects
= ws
.rQshift32();
1037 color
= ws
.rQshiftBytes(fb_Bpp
); // Background
1038 display
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1040 while ((FBU
.subrects
> 0) && (ws
.rQlen() >= (fb_Bpp
+ 8))) {
1041 color
= ws
.rQshiftBytes(fb_Bpp
);
1044 width
= ws
.rQshift16();
1045 height
= ws
.rQshift16();
1046 display
.fillRect(FBU
.x
+ x
, FBU
.y
+ y
, width
, height
, color
);
1049 //Util.Debug(" display_rre: rects: " + FBU.rects +
1050 // ", FBU.subrects: " + FBU.subrects);
1052 if (FBU
.subrects
> 0) {
1053 chunk
= Math
.min(rre_chunk_sz
, FBU
.subrects
);
1054 FBU
.bytes
= (fb_Bpp
+ 8) * chunk
;
1059 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1063 encHandlers
.HEXTILE
= function display_hextile() {
1064 //Util.Debug(">> display_hextile");
1065 var subencoding
, subrects
, tile
, color
, cur_tile
,
1066 tile_x
, x
, w
, tile_y
, y
, h
, xy
, s
, sx
, sy
, wh
, sw
, sh
,
1067 rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1069 if (FBU
.tiles
=== 0) {
1070 FBU
.tiles_x
= Math
.ceil(FBU
.width
/16);
1071 FBU
.tiles_y
= Math
.ceil(FBU
.height
/16);
1072 FBU
.total_tiles
= FBU
.tiles_x
* FBU
.tiles_y
;
1073 FBU
.tiles
= FBU
.total_tiles
;
1076 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
1077 while (FBU
.tiles
> 0) {
1079 if (ws
.rQwait("HEXTILE subencoding", FBU
.bytes
)) { return false; }
1080 subencoding
= rQ
[rQi
]; // Peek
1081 if (subencoding
> 30) { // Raw
1082 fail("Disconnected: illegal hextile subencoding " + subencoding
);
1083 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
1087 cur_tile
= FBU
.total_tiles
- FBU
.tiles
;
1088 tile_x
= cur_tile
% FBU
.tiles_x
;
1089 tile_y
= Math
.floor(cur_tile
/ FBU
.tiles_x
);
1090 x
= FBU
.x
+ tile_x
* 16;
1091 y
= FBU
.y
+ tile_y
* 16;
1092 w
= Math
.min(16, (FBU
.x
+ FBU
.width
) - x
);
1093 h
= Math
.min(16, (FBU
.y
+ FBU
.height
) - y
);
1095 /* Figure out how much we are expecting */
1096 if (subencoding
& 0x01) { // Raw
1097 //Util.Debug(" Raw subencoding");
1098 FBU
.bytes
+= w
* h
* fb_Bpp
;
1100 if (subencoding
& 0x02) { // Background
1101 FBU
.bytes
+= fb_Bpp
;
1103 if (subencoding
& 0x04) { // Foreground
1104 FBU
.bytes
+= fb_Bpp
;
1106 if (subencoding
& 0x08) { // AnySubrects
1107 FBU
.bytes
+= 1; // Since we aren't shifting it off
1108 if (ws
.rQwait("hextile subrects header", FBU
.bytes
)) { return false; }
1109 subrects
= rQ
[rQi
+ FBU
.bytes
-1]; // Peek
1110 if (subencoding
& 0x10) { // SubrectsColoured
1111 FBU
.bytes
+= subrects
* (fb_Bpp
+ 2);
1113 FBU
.bytes
+= subrects
* 2;
1119 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1120 " (" + tile_x + "," + tile_y + ")" +
1121 " [" + x + "," + y + "]@" + w + "x" + h +
1122 ", subenc:" + subencoding +
1123 "(last: " + FBU.lastsubencoding + "), subrects:" +
1125 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
1126 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
1127 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
1129 if (ws
.rQwait("hextile", FBU
.bytes
)) { return false; }
1131 /* We know the encoding and have a whole tile */
1132 FBU
.subencoding
= rQ
[rQi
];
1134 if (FBU
.subencoding
=== 0) {
1135 if (FBU
.lastsubencoding
& 0x01) {
1136 /* Weird: ignore blanks after RAW */
1137 Util
.Debug(" Ignoring blank after RAW");
1139 display
.fillRect(x
, y
, w
, h
, FBU
.background
);
1141 } else if (FBU
.subencoding
& 0x01) { // Raw
1142 display
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1143 rQi
+= FBU
.bytes
- 1;
1145 if (FBU
.subencoding
& 0x02) { // Background
1146 FBU
.background
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1149 if (FBU
.subencoding
& 0x04) { // Foreground
1150 FBU
.foreground
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1154 tile
= display
.getTile(x
, y
, w
, h
, FBU
.background
);
1155 if (FBU
.subencoding
& 0x08) { // AnySubrects
1158 for (s
= 0; s
< subrects
; s
+= 1) {
1159 if (FBU
.subencoding
& 0x10) { // SubrectsColoured
1160 color
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1163 color
= FBU
.foreground
;
1173 sh
= (wh
& 0x0f) + 1;
1175 display
.setSubTile(tile
, sx
, sy
, sw
, sh
, color
);
1178 display
.putTile(tile
);
1181 FBU
.lastsubencoding
= FBU
.subencoding
;
1186 if (FBU
.tiles
=== 0) {
1190 //Util.Debug("<< display_hextile");
1195 encHandlers
.TIGHT_PNG
= function display_tight_png() {
1196 //Util.Debug(">> display_tight_png");
1197 var ctl
, cmode
, clength
, getCLength
, color
, img
;
1198 //Util.Debug(" FBU.rects: " + FBU.rects);
1199 //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1201 FBU
.bytes
= 1; // compression-control byte
1202 if (ws
.rQwait("TIGHT compression-control", FBU
.bytes
)) { return false; }
1204 // Get 'compact length' header and data size
1205 getCLength = function (arr
) {
1206 var header
= 1, data
= 0;
1207 data
+= arr
[0] & 0x7f;
1208 if (arr
[0] & 0x80) {
1210 data
+= (arr
[1] & 0x7f) << 7;
1211 if (arr
[1] & 0x80) {
1213 data
+= arr
[2] << 14;
1216 return [header
, data
];
1221 case 0x08: cmode
= "fill"; break;
1222 case 0x09: cmode
= "jpeg"; break;
1223 case 0x0A: cmode
= "png"; break;
1224 default: throw("Illegal basic compression received, ctl: " + ctl
);
1227 // fill uses fb_depth because TPIXELs drop the padding byte
1228 case "fill": FBU
.bytes
+= fb_depth
; break; // TPIXEL
1229 case "jpeg": FBU
.bytes
+= 3; break; // max clength
1230 case "png": FBU
.bytes
+= 3; break; // max clength
1233 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1235 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1236 //Util.Debug(" cmode: " + cmode);
1238 // Determine FBU.bytes
1241 ws
.rQshift8(); // shift off ctl
1242 color
= ws
.rQshiftBytes(fb_depth
);
1243 display
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1247 clength
= getCLength(ws
.rQslice(1, 4));
1248 FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1249 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1251 // We have everything, render it
1252 //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
1253 ws
.rQshiftBytes(1 + clength
[0]); // shift off ctl + compact length
1255 img
.onload
= scan_tight_imgQ
;
1256 FBU
.imgQ
.push([img
, FBU
.x
, FBU
.y
]);
1257 img
.src
= "data:image/" + cmode
+
1258 extract_data_uri(ws
.rQshiftBytes(clength
[1]));
1264 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1265 //Util.Debug("<< display_tight_png");
1269 extract_data_uri = function(arr
) {
1271 //for (i=0; i< arr.length; i += 1) {
1272 // stra.push(String.fromCharCode(arr[i]));
1274 //return "," + escape(stra.join(''));
1275 return ";base64," + Base64
.encode(arr
);
1278 scan_tight_imgQ = function() {
1280 ctx
= display
.get_context();
1281 if (rfb_state
=== 'normal') {
1283 while ((imgQ
.length
> 0) && (imgQ
[0][0].complete
)) {
1285 ctx
.drawImage(img
[0], img
[1], img
[2]);
1287 setTimeout(scan_tight_imgQ
, scan_imgQ_rate
);
1291 encHandlers
.DesktopSize
= function set_desktopsize() {
1292 Util
.Debug(">> set_desktopsize");
1293 fb_width
= FBU
.width
;
1294 fb_height
= FBU
.height
;
1295 display
.resize(fb_width
, fb_height
);
1296 timing
.fbu_rt_start
= (new Date()).getTime();
1297 // Send a new non-incremental request
1298 ws
.send(fbUpdateRequest(0));
1303 Util
.Debug("<< set_desktopsize");
1307 encHandlers
.Cursor
= function set_cursor() {
1308 var x
, y
, w
, h
, pixelslength
, masklength
;
1309 //Util.Debug(">> set_cursor");
1310 x
= FBU
.x
; // hotspot-x
1311 y
= FBU
.y
; // hotspot-y
1315 pixelslength
= w
* h
* fb_Bpp
;
1316 masklength
= Math
.floor((w
+ 7) / 8) * h
;
1318 FBU
.bytes
= pixelslength
+ masklength
;
1319 if (ws
.rQwait("cursor encoding", FBU
.bytes
)) { return false; }
1321 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1323 display
.changeCursor(ws
.rQshiftBytes(pixelslength
),
1324 ws
.rQshiftBytes(masklength
),
1330 //Util.Debug("<< set_cursor");
1334 encHandlers
.JPEG_quality_lo
= function set_jpeg_quality() {
1335 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1338 encHandlers
.compress_lo
= function set_compress_level() {
1339 Util
.Error("Server sent compress level pseudo-encoding");
1343 * Client message routines
1346 pixelFormat = function() {
1347 //Util.Debug(">> pixelFormat");
1349 arr
= [0]; // msg-type
1350 arr
.push8(0); // padding
1351 arr
.push8(0); // padding
1352 arr
.push8(0); // padding
1354 arr
.push8(fb_Bpp
* 8); // bits-per-pixel
1355 arr
.push8(fb_depth
* 8); // depth
1356 arr
.push8(0); // little-endian
1357 arr
.push8(conf
.true_color
? 1 : 0); // true-color
1359 arr
.push16(255); // red-max
1360 arr
.push16(255); // green-max
1361 arr
.push16(255); // blue-max
1362 arr
.push8(0); // red-shift
1363 arr
.push8(8); // green-shift
1364 arr
.push8(16); // blue-shift
1366 arr
.push8(0); // padding
1367 arr
.push8(0); // padding
1368 arr
.push8(0); // padding
1369 //Util.Debug("<< pixelFormat");
1373 clientEncodings = function() {
1374 //Util.Debug(">> clientEncodings");
1375 var arr
, i
, encList
= [];
1377 for (i
=0; i
<encodings
.length
; i
+= 1) {
1378 if ((encodings
[i
][0] === "Cursor") &&
1379 (! conf
.local_cursor
)) {
1380 Util
.Debug("Skipping Cursor pseudo-encoding");
1382 //Util.Debug("Adding encoding: " + encodings[i][0]);
1383 encList
.push(encodings
[i
][1]);
1387 arr
= [2]; // msg-type
1388 arr
.push8(0); // padding
1390 arr
.push16(encList
.length
); // encoding count
1391 for (i
=0; i
< encList
.length
; i
+= 1) {
1392 arr
.push32(encList
[i
]);
1394 //Util.Debug("<< clientEncodings: " + arr);
1398 fbUpdateRequest = function(incremental
, x
, y
, xw
, yw
) {
1399 //Util.Debug(">> fbUpdateRequest");
1402 if (!xw
) { xw
= fb_width
; }
1403 if (!yw
) { yw
= fb_height
; }
1405 arr
= [3]; // msg-type
1406 arr
.push8(incremental
);
1411 //Util.Debug("<< fbUpdateRequest");
1415 keyEvent = function(keysym
, down
) {
1416 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1418 arr
= [4]; // msg-type
1422 //Util.Debug("<< keyEvent");
1426 pointerEvent = function(x
, y
) {
1427 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1428 // " , mask: " + mouse_buttonMask);
1430 arr
= [5]; // msg-type
1431 arr
.push8(mouse_buttonMask
);
1434 //Util.Debug("<< pointerEvent");
1438 clientCutText = function(text
) {
1439 //Util.Debug(">> clientCutText");
1441 arr
= [6]; // msg-type
1442 arr
.push8(0); // padding
1443 arr
.push8(0); // padding
1444 arr
.push8(0); // padding
1445 arr
.push32(text
.length
);
1447 for (i
=0; i
< n
; i
+=1) {
1448 arr
.push(text
.charCodeAt(i
));
1450 //Util.Debug("<< clientCutText:" + arr);
1457 // Public API interface functions
1460 that
.connect = function(host
, port
, password
) {
1461 //Util.Debug(">> connect");
1465 rfb_password
= (password
!== undefined) ? password
: "";
1467 if ((!rfb_host
) || (!rfb_port
)) {
1468 return fail("Must set host and port");
1471 updateState('connect');
1472 //Util.Debug("<< connect");
1476 that
.disconnect = function() {
1477 //Util.Debug(">> disconnect");
1478 updateState('disconnect', 'Disconnecting');
1479 //Util.Debug("<< disconnect");
1482 that
.sendPassword = function(passwd
) {
1483 rfb_password
= passwd
;
1484 rfb_state
= "Authentication";
1485 setTimeout(init_msg
, 1);
1488 that
.sendCtrlAltDel = function() {
1489 if (rfb_state
!== "normal") { return false; }
1490 Util
.Info("Sending Ctrl-Alt-Del");
1492 arr
= arr
.concat(keyEvent(0xFFE3, 1)); // Control
1493 arr
= arr
.concat(keyEvent(0xFFE9, 1)); // Alt
1494 arr
= arr
.concat(keyEvent(0xFFFF, 1)); // Delete
1495 arr
= arr
.concat(keyEvent(0xFFFF, 0)); // Delete
1496 arr
= arr
.concat(keyEvent(0xFFE9, 0)); // Alt
1497 arr
= arr
.concat(keyEvent(0xFFE3, 0)); // Control
1498 arr
= arr
.concat(fbUpdateRequest(1));
1502 // Send a key press. If 'down' is not specified then send a down key
1503 // followed by an up key.
1504 that
.sendKey = function(code
, down
) {
1505 if (rfb_state
!== "normal") { return false; }
1507 if (typeof down
!== 'undefined') {
1508 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
1509 arr
= arr
.concat(keyEvent(code
, down
? 1 : 0));
1511 Util
.Info("Sending key code (down + up): " + code
);
1512 arr
= arr
.concat(keyEvent(code
, 1));
1513 arr
= arr
.concat(keyEvent(code
, 0));
1515 arr
= arr
.concat(fbUpdateRequest(1));
1519 that
.clipboardPasteFrom = function(text
) {
1520 if (rfb_state
!== "normal") { return; }
1521 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1522 ws
.send(clientCutText(text
));
1523 //Util.Debug("<< clipboardPasteFrom");
1526 that
.testMode = function(override_send
) {
1527 // Overridable internal functions for testing
1529 // TODO figure out what to do here
1530 ws
.send
= override_send
;
1531 that
.recv_message
= ws
.recv_message
; // Expose it
1533 checkEvents = function () { /* Stub Out */ };
1534 that
.connect = function(host
, port
, password
) {
1537 rfb_password
= password
;
1538 updateState('ProtocolVersion', "Starting VNC handshake");
1543 return constructor(); // Return the public API interface