]>
git.proxmox.com Git - mirror_novnc.git/blob - include/rfb.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
8 * TIGHT decoder portion:
9 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
12 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
13 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
16 function RFB(defaults
) {
19 var that
= {}, // Public API methods
20 conf
= {}, // Configuration attributes
22 // Pre-declare private functions used before definitions (jslint)
23 init_vars
, updateState
, fail
, handle_message
,
24 init_msg
, normal_msg
, framebufferUpdate
, print_stats
,
26 pixelFormat
, clientEncodings
, fbUpdateRequest
, fbUpdateRequests
,
27 keyEvent
, pointerEvent
, clientCutText
,
29 getTightCLength
, extract_data_uri
,
30 keyPress
, mouseButton
, mouseMove
,
32 checkEvents
, // Overridable for testing
36 // Private RFB namespace variables
43 rfb_state
= 'disconnected',
49 // In preference order
57 ['DesktopSize', -223 ],
60 // Psuedo-encoding settings
61 //['JPEG_quality_lo', -32 ],
62 ['JPEG_quality_med', -26 ],
63 //['JPEG_quality_hi', -23 ],
64 //['compress_lo', -255 ],
65 ['compress_hi', -247 ],
71 encStats
= {}, // [rectCnt, rectCntTot]
73 ws
= null, // Websock object
74 display
= null, // Display object
75 keyboard
= null, // Keyboard input handler object
76 mouse
= null, // Mouse input handler object
77 sendTimer
= null, // Send Queue check timer
78 connTimer
= null, // connection timer
79 disconnTimer
= null, // disconnection timer
80 msgTimer
= null, // queued handle_message timer
82 // Frame buffer update state
96 zlibs
: [] // TIGHT zlib streams
123 def_con_timeout
= Websock_native
? 2 : 5,
126 mouse_buttonMask
= 0,
128 viewportDragging
= false,
129 viewportDragPos
= {};
131 // Configuration attributes
132 Util
.conf_defaults(conf
, that
, defaults
, [
133 ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
134 ['focusContainer', 'wo', 'dom', document
, 'DOM element that captures keyboard input'],
136 ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
137 ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
138 ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
139 ['shared', 'rw', 'bool', true, 'Request shared mode'],
140 ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
142 ['connectTimeout', 'rw', 'int', def_con_timeout
, 'Time (s) to wait for connection'],
143 ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
145 // UltraVNC repeater ID to connect to
146 ['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'],
148 ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
150 ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
151 ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
153 // Callback functions
154 ['onUpdateState', 'rw', 'func', function() { },
155 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
156 ['onPasswordRequired', 'rw', 'func', function() { },
157 'onPasswordRequired(rfb): VNC password is required '],
158 ['onClipboard', 'rw', 'func', function() { },
159 'onClipboard(rfb, text): RFB clipboard contents received'],
160 ['onBell', 'rw', 'func', function() { },
161 'onBell(rfb): RFB Bell message received '],
162 ['onFBUReceive', 'rw', 'func', function() { },
163 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
164 ['onFBUComplete', 'rw', 'func', function() { },
165 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
166 ['onFBResize', 'rw', 'func', function() { },
167 'onFBResize(rfb, width, height): frame buffer resized'],
169 // These callback names are deprecated
170 ['updateState', 'rw', 'func', function() { },
171 'obsolete, use onUpdateState'],
172 ['clipboardReceive', 'rw', 'func', function() { },
173 'obsolete, use onClipboard']
177 // Override/add some specific configuration getters/setters
178 that
.set_local_cursor = function(cursor
) {
179 if ((!cursor
) || (cursor
in {'0':1, 'no':1, 'false':1})) {
180 conf
.local_cursor
= false;
182 if (display
.get_cursor_uri()) {
183 conf
.local_cursor
= true;
185 Util
.Warn("Browser does not support local cursor");
190 // These are fake configuration getters
191 that
.get_display = function() { return display
; };
193 that
.get_keyboard = function() { return keyboard
; };
195 that
.get_mouse = function() { return mouse
; };
203 // Create the public API interface and initialize values that stay
204 // constant across connect/disconnect
205 function constructor() {
207 Util
.Debug(">> RFB.constructor");
209 // Create lookup tables based encoding number
210 for (i
=0; i
< encodings
.length
; i
+=1) {
211 encHandlers
[encodings
[i
][1]] = encHandlers
[encodings
[i
][0]];
212 encNames
[encodings
[i
][1]] = encodings
[i
][0];
213 encStats
[encodings
[i
][1]] = [0, 0];
215 // Initialize display, mouse, keyboard, and websock
217 display
= new Display({'target': conf
.target
});
219 Util
.Error("Display exception: " + exc
);
220 updateState('fatal', "No working Display");
222 keyboard
= new Keyboard({'target': conf
.focusContainer
,
223 'onKeyPress': keyPress
});
224 mouse
= new Mouse({'target': conf
.target
,
225 'onMouseButton': mouseButton
,
226 'onMouseMove': mouseMove
});
228 rmode
= display
.get_render_mode();
231 ws
.on('message', handle_message
);
232 ws
.on('open', function() {
233 if (rfb_state
=== "connect") {
234 updateState('ProtocolVersion', "Starting VNC handshake");
236 fail("Got unexpected WebSockets connection");
239 ws
.on('close', function(e
) {
240 Util
.Warn("WebSocket on-close event");
243 msg
= " (code: " + e
.code
;
245 msg
+= ", reason: " + e
.reason
;
249 if (rfb_state
=== 'disconnect') {
250 updateState('disconnected', 'VNC disconnected' + msg
);
251 } else if (rfb_state
=== 'ProtocolVersion') {
252 fail('Failed to connect to server' + msg
);
253 } else if (rfb_state
in {'failed':1, 'disconnected':1}) {
254 Util
.Error("Received onclose while disconnected" + msg
);
256 fail('Server disconnected' + msg
);
259 ws
.on('error', function(e
) {
260 Util
.Warn("WebSocket on-error event");
261 //fail("WebSock reported an error");
267 /* Check web-socket-js if no builtin WebSocket support */
268 if (Websock_native
) {
269 Util
.Info("Using native WebSockets");
270 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
272 Util
.Warn("Using web-socket-js bridge. Flash version: " +
274 if ((! Util
.Flash
) ||
275 (Util
.Flash
.version
< 9)) {
276 updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
277 } else if (document
.location
.href
.substr(0, 7) === "file://") {
279 "'file://' URL is incompatible with Adobe Flash");
281 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
285 Util
.Debug("<< RFB.constructor");
286 return that
; // Return the public API interface
290 Util
.Debug(">> RFB.connect");
293 if (typeof UsingSocketIO
!== "undefined") {
294 uri
= "http://" + rfb_host
+ ":" + rfb_port
+ "/" + rfb_path
;
301 uri
+= rfb_host
+ ":" + rfb_port
+ "/" + rfb_path
;
303 Util
.Info("connecting to " + uri
);
304 // TODO: make protocols a configurable
305 ws
.open(uri
, ['binary', 'base64']);
307 Util
.Debug("<< RFB.connect");
310 // Initialize variables that are reset before each connection
311 init_vars = function() {
318 FBU
.subrects
= 0; // RRE and HEXTILE
319 FBU
.lines
= 0; // RAW
320 FBU
.tiles
= 0; // HEXTILE
321 FBU
.zlibs
= []; // TIGHT zlib encoders
322 mouse_buttonMask
= 0;
325 // Clear the per connection encoding stats
326 for (i
=0; i
< encodings
.length
; i
+=1) {
327 encStats
[encodings
[i
][1]][0] = 0;
330 for (i
=0; i
< 4; i
++) {
331 //FBU.zlibs[i] = new InflateStream();
332 FBU
.zlibs
[i
] = new TINF();
338 print_stats = function() {
340 Util
.Info("Encoding stats for this connection:");
341 for (i
=0; i
< encodings
.length
; i
+=1) {
342 s
= encStats
[encodings
[i
][1]];
343 if ((s
[0] + s
[1]) > 0) {
344 Util
.Info(" " + encodings
[i
][0] + ": " +
348 Util
.Info("Encoding stats since page load:");
349 for (i
=0; i
< encodings
.length
; i
+=1) {
350 s
= encStats
[encodings
[i
][1]];
351 if ((s
[0] + s
[1]) > 0) {
352 Util
.Info(" " + encodings
[i
][0] + ": " +
365 * loaded - page load, equivalent to disconnected
366 * disconnected - idle state
367 * connect - starting to connect (to ProtocolVersion)
369 * disconnect - starting to disconnect
370 * failed - abnormal disconnect
371 * fatal - failed to load page, or fatal error
373 * RFB protocol initialization states:
377 * password - waiting for password, not part of RFB
379 * ClientInitialization - not triggered by server message
380 * ServerInitialization (to normal)
382 updateState = function(state
, statusMsg
) {
383 var func
, cmsg
, oldstate
= rfb_state
;
385 if (state
=== oldstate
) {
386 /* Already here, ignore */
387 Util
.Debug("Already in state '" + state
+ "', ignoring.");
392 * These are disconnected states. A previous connect may
393 * asynchronously cause a connection so make sure we are closed.
395 if (state
in {'disconnected':1, 'loaded':1, 'connect':1,
396 'disconnect':1, 'failed':1, 'fatal':1}) {
398 clearInterval(sendTimer
);
403 clearInterval(msgTimer
);
407 if (display
&& display
.get_context()) {
410 display
.defaultCursor();
411 if ((Util
.get_logging() !== 'debug') ||
412 (state
=== 'loaded')) {
413 // Show noVNC logo on load and when disconnected if
422 if (oldstate
=== 'fatal') {
423 Util
.Error("Fatal error, cannot continue");
426 if ((state
=== 'failed') || (state
=== 'fatal')) {
432 cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
433 func("New state '" + state
+ "', was '" + oldstate
+ "'." + cmsg
);
435 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
436 // Do disconnect action, but stay in failed state
437 rfb_state
= 'failed';
442 if (connTimer
&& (rfb_state
!== 'connect')) {
443 Util
.Debug("Clearing connect timer");
444 clearInterval(connTimer
);
448 if (disconnTimer
&& (rfb_state
!== 'disconnect')) {
449 Util
.Debug("Clearing disconnect timer");
450 clearInterval(disconnTimer
);
456 if ((oldstate
=== 'disconnected') || (oldstate
=== 'failed')) {
457 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
465 connTimer
= setTimeout(function () {
466 fail("Connect timeout");
467 }, conf
.connectTimeout
* 1000);
472 // WebSocket.onopen transitions to 'ProtocolVersion'
479 disconnTimer
= setTimeout(function () {
480 fail("Disconnect timeout");
481 }, conf
.disconnectTimeout
* 1000);
486 // WebSocket.onclose transitions to 'disconnected'
491 if (oldstate
=== 'disconnected') {
492 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
494 if (oldstate
=== 'normal') {
495 Util
.Error("Error while connected.");
497 if (oldstate
=== 'init') {
498 Util
.Error("Error while initializing.");
501 // Make sure we transition to disconnected
502 setTimeout(function() { updateState('disconnected'); }, 50);
508 // No state change action to take
512 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
513 // Leave the failed message
514 conf
.updateState(that
, state
, oldstate
); // Obsolete
515 conf
.onUpdateState(that
, state
, oldstate
);
517 conf
.updateState(that
, state
, oldstate
, statusMsg
); // Obsolete
518 conf
.onUpdateState(that
, state
, oldstate
, statusMsg
);
522 fail = function(msg
) {
523 updateState('failed', msg
);
527 handle_message = function() {
528 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
529 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
530 if (ws
.rQlen() === 0) {
531 Util
.Warn("handle_message called on empty receive queue");
537 Util
.Error("Got data while disconnected");
540 if (normal_msg() && ws
.rQlen() > 0) {
541 // true means we can continue processing
542 // Give other events a chance to run
543 if (msgTimer
=== null) {
544 Util
.Debug("More data to process, creating timer");
545 msgTimer
= setTimeout(function () {
550 Util
.Debug("More data to process, existing timer");
561 function genDES(password
, challenge
) {
563 for (i
=0; i
< password
.length
; i
+= 1) {
564 passwd
.push(password
.charCodeAt(i
));
566 return (new DES(passwd
)).encrypt(challenge
);
569 function flushClient() {
570 if (mouse_arr
.length
> 0) {
571 //send(mouse_arr.concat(fbUpdateRequests()));
573 setTimeout(function() {
574 ws
.send(fbUpdateRequests());
584 // overridable for testing
585 checkEvents = function() {
587 if (rfb_state
=== 'normal' && !viewportDragging
) {
588 if (! flushClient()) {
589 now
= new Date().getTime();
590 if (now
> last_req_time
+ conf
.fbu_req_rate
) {
592 ws
.send(fbUpdateRequests());
596 setTimeout(checkEvents
, conf
.check_rate
);
599 keyPress = function(keysym
, down
) {
602 if (conf
.view_only
) { return; } // View only, skip keyboard events
604 arr
= keyEvent(keysym
, down
);
605 arr
= arr
.concat(fbUpdateRequests());
609 mouseButton = function(x
, y
, down
, bmask
) {
611 mouse_buttonMask
|= bmask
;
613 mouse_buttonMask
^= bmask
;
616 if (conf
.viewportDrag
) {
617 if (down
&& !viewportDragging
) {
618 viewportDragging
= true;
619 viewportDragPos
= {'x': x
, 'y': y
};
621 // Skip sending mouse events
624 viewportDragging
= false;
625 ws
.send(fbUpdateRequests()); // Force immediate redraw
629 if (conf
.view_only
) { return; } // View only, skip mouse events
631 mouse_arr
= mouse_arr
.concat(
632 pointerEvent(display
.absX(x
), display
.absY(y
)) );
636 mouseMove = function(x
, y
) {
637 //Util.Debug('>> mouseMove ' + x + "," + y);
640 if (viewportDragging
) {
641 //deltaX = x - viewportDragPos.x; // drag viewport
642 deltaX
= viewportDragPos
.x
- x
; // drag frame buffer
643 //deltaY = y - viewportDragPos.y; // drag viewport
644 deltaY
= viewportDragPos
.y
- y
; // drag frame buffer
645 viewportDragPos
= {'x': x
, 'y': y
};
647 display
.viewportChange(deltaX
, deltaY
);
649 // Skip sending mouse events
653 if (conf
.view_only
) { return; } // View only, skip mouse events
655 mouse_arr
= mouse_arr
.concat(
656 pointerEvent(display
.absX(x
), display
.absY(y
)) );
661 // Server message handlers
664 // RFB/VNC initialisation message handler
665 init_msg = function() {
666 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
668 var strlen
, reason
, length
, sversion
, cversion
, repeaterID
,
669 i
, types
, num_types
, challenge
, response
, bpp
, depth
,
670 big_endian
, red_max
, green_max
, blue_max
, red_shift
,
671 green_shift
, blue_shift
, true_color
, name_length
, is_repeater
;
673 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
676 case 'ProtocolVersion' :
677 if (ws
.rQlen() < 12) {
678 return fail("Incomplete protocol version");
680 sversion
= ws
.rQshiftStr(12).substr(4,7);
681 Util
.Info("Server ProtocolVersion: " + sversion
);
684 case "000.000": is_repeater
= 1; break; // UltraVNC repeater
685 case "003.003": rfb_version
= 3.3; break;
686 case "003.006": rfb_version
= 3.3; break; // UltraVNC
687 case "003.889": rfb_version
= 3.3; break; // Apple Remote Desktop
688 case "003.007": rfb_version
= 3.7; break;
689 case "003.008": rfb_version
= 3.8; break;
690 case "004.000": rfb_version
= 3.8; break; // Intel AMT KVM
691 case "004.001": rfb_version
= 3.8; break; // RealVNC 4.6
693 return fail("Invalid server version " + sversion
);
696 repeaterID
= conf
.repeaterID
;
697 while (repeaterID
.length
< 250) {
700 ws
.send_string(repeaterID
);
703 if (rfb_version
> rfb_max_version
) {
704 rfb_version
= rfb_max_version
;
708 sendTimer
= setInterval(function() {
709 // Send updates either at a rate of one update
710 // every 50ms, or whatever slower rate the network
716 cversion
= "00" + parseInt(rfb_version
,10) +
717 ".00" + ((rfb_version
* 10) % 10);
718 ws
.send_string("RFB " + cversion
+ "\n");
719 updateState('Security', "Sent ProtocolVersion: " + cversion
);
723 if (rfb_version
>= 3.7) {
724 // Server sends supported list, client decides
725 num_types
= ws
.rQshift8();
726 if (ws
.rQwait("security type", num_types
, 1)) { return false; }
727 if (num_types
=== 0) {
728 strlen
= ws
.rQshift32();
729 reason
= ws
.rQshiftStr(strlen
);
730 return fail("Security failure: " + reason
);
733 types
= ws
.rQshiftBytes(num_types
);
734 Util
.Debug("Server security types: " + types
);
735 for (i
=0; i
< types
.length
; i
+=1) {
736 if ((types
[i
] > rfb_auth_scheme
) && (types
[i
] < 3)) {
737 rfb_auth_scheme
= types
[i
];
740 if (rfb_auth_scheme
=== 0) {
741 return fail("Unsupported security types: " + types
);
744 ws
.send([rfb_auth_scheme
]);
747 if (ws
.rQwait("security scheme", 4)) { return false; }
748 rfb_auth_scheme
= ws
.rQshift32();
750 updateState('Authentication',
751 "Authenticating using scheme: " + rfb_auth_scheme
);
752 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
755 // Triggered by fallthough, not by server message
756 case 'Authentication' :
757 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
758 switch (rfb_auth_scheme
) {
759 case 0: // connection failed
760 if (ws
.rQwait("auth reason", 4)) { return false; }
761 strlen
= ws
.rQshift32();
762 reason
= ws
.rQshiftStr(strlen
);
763 return fail("Auth failure: " + reason
);
764 case 1: // no authentication
765 if (rfb_version
>= 3.8) {
766 updateState('SecurityResult');
769 // Fall through to ClientInitialisation
771 case 2: // VNC authentication
772 if (rfb_password
.length
=== 0) {
773 // Notify via both callbacks since it is kind of
774 // a RFB state change and a UI interface issue.
775 updateState('password', "Password Required");
776 conf
.onPasswordRequired(that
);
779 if (ws
.rQwait("auth challenge", 16)) { return false; }
780 challenge
= ws
.rQshiftBytes(16);
781 //Util.Debug("Password: " + rfb_password);
782 //Util.Debug("Challenge: " + challenge +
783 // " (" + challenge.length + ")");
784 response
= genDES(rfb_password
, challenge
);
785 //Util.Debug("Response: " + response +
786 // " (" + response.length + ")");
788 //Util.Debug("Sending DES encrypted auth response");
790 updateState('SecurityResult');
793 fail("Unsupported auth scheme: " + rfb_auth_scheme
);
796 updateState('ClientInitialisation', "No auth required");
797 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
800 case 'SecurityResult' :
801 if (ws
.rQwait("VNC auth response ", 4)) { return false; }
802 switch (ws
.rQshift32()) {
804 // Fall through to ClientInitialisation
807 if (rfb_version
>= 3.8) {
808 length
= ws
.rQshift32();
809 if (ws
.rQwait("SecurityResult reason", length
, 8)) {
812 reason
= ws
.rQshiftStr(length
);
815 fail("Authentication failed");
819 return fail("Too many auth attempts");
821 updateState('ClientInitialisation', "Authentication OK");
822 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
825 // Triggered by fallthough, not by server message
826 case 'ClientInitialisation' :
827 ws
.send([conf
.shared
? 1 : 0]); // ClientInitialisation
828 updateState('ServerInitialisation', "Authentication OK");
831 case 'ServerInitialisation' :
832 if (ws
.rQwait("server initialization", 24)) { return false; }
835 fb_width
= ws
.rQshift16();
836 fb_height
= ws
.rQshift16();
840 depth
= ws
.rQshift8();
841 big_endian
= ws
.rQshift8();
842 true_color
= ws
.rQshift8();
844 red_max
= ws
.rQshift16();
845 green_max
= ws
.rQshift16();
846 blue_max
= ws
.rQshift16();
847 red_shift
= ws
.rQshift8();
848 green_shift
= ws
.rQshift8();
849 blue_shift
= ws
.rQshift8();
850 ws
.rQshiftStr(3); // padding
852 Util
.Info("Screen: " + fb_width
+ "x" + fb_height
+
853 ", bpp: " + bpp
+ ", depth: " + depth
+
854 ", big_endian: " + big_endian
+
855 ", true_color: " + true_color
+
856 ", red_max: " + red_max
+
857 ", green_max: " + green_max
+
858 ", blue_max: " + blue_max
+
859 ", red_shift: " + red_shift
+
860 ", green_shift: " + green_shift
+
861 ", blue_shift: " + blue_shift
);
863 if (big_endian
!== 0) {
864 Util
.Warn("Server native endian is not little endian");
866 if (red_shift
!== 16) {
867 Util
.Warn("Server native red-shift is not 16");
869 if (blue_shift
!== 0) {
870 Util
.Warn("Server native blue-shift is not 0");
873 /* Connection name/title */
874 name_length = ws.rQshift32();
875 fb_name = ws.rQshiftStr(name_length);
877 if (conf.true_color && fb_name === "Intel(r) AMT KVM")
879 Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
880 conf.true_color = false;
883 display.set_true_color(conf.true_color);
884 conf.onFBResize(that, fb_width, fb_height);
885 display.resize(fb_width, fb_height);
889 if (conf.true_color) {
897 response = pixelFormat();
898 response = response.concat(clientEncodings());
899 response = response.concat(fbUpdateRequests());
900 timing.fbu_rt_start = (new Date()).getTime();
904 /* Start pushing/polling */
905 setTimeout(checkEvents, conf.check_rate);
908 updateState('normal', "Connected (encrypted) to: " + fb_name);
910 updateState('normal', "Connected (unencrypted) to: " + fb_name);
914 //Util.Debug("<< init_msg");
918 /* Normal RFB/VNC server message handler */
919 normal_msg = function() {
920 //Util.Debug(">> normal_msg");
922 var ret = true, msg_type, length, text,
923 c, first_colour, num_colours, red, green, blue;
928 msg_type = ws.rQshift8();
931 case 0: // FramebufferUpdate
932 ret = framebufferUpdate(); // false means need more data
934 case 1: // SetColourMapEntries
935 Util.Debug("SetColourMapEntries");
936 ws.rQshift8(); // Padding
937 first_colour = ws.rQshift16(); // First colour
938 num_colours = ws.rQshift16();
939 if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
941 for (c=0; c < num_colours; c+=1) {
942 red = ws.rQshift16();
943 //Util.Debug("red before: " + red);
944 red = parseInt(red / 256, 10);
945 //Util.Debug("red after: " + red);
946 green = parseInt(ws.rQshift16() / 256, 10);
947 blue = parseInt(ws.rQshift16() / 256, 10);
948 display.set_colourMap([blue, green, red], first_colour + c);
950 Util.Debug("colourMap: " + display.get_colourMap());
951 Util.Info("Registered " + num_colours + " colourMap entries");
952 //Util.Debug("colourMap: " + display.get_colourMap());
958 case 3: // ServerCutText
959 Util.Debug("ServerCutText");
960 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
961 ws.rQshiftBytes(3); // Padding
962 length = ws.rQshift32();
963 if (ws.rQwait("ServerCutText", length, 8)) { return false; }
965 text = ws.rQshiftStr(length);
966 conf.clipboardReceive(that, text); // Obsolete
967 conf.onClipboard(that, text);
970 fail("Disconnected: illegal server message type " + msg_type);
971 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
974 //Util.Debug("<< normal_msg");
978 framebufferUpdate = function() {
979 var now, hdr, fbu_rt_diff, ret = true;
981 if (FBU.rects === 0) {
982 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
983 if (ws.rQwait("FBU header", 3)) {
984 ws.rQunshift8(0); // FBU msg_type
987 ws.rQshift8(); // padding
988 FBU.rects = ws.rQshift16();
989 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
992 if (timing.fbu_rt_start > 0) {
993 now = (new Date()).getTime();
994 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
998 while (FBU.rects > 0) {
999 if (rfb_state !== "normal") {
1002 if (ws.rQwait("FBU", FBU.bytes)) { return false; }
1003 if (FBU.bytes === 0) {
1004 if (ws.rQwait("rect header", 12)) { return false; }
1005 /* New FramebufferUpdate */
1007 hdr
= ws
.rQshiftBytes(12);
1008 FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1009 FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1010 FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1011 FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1012 FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1013 (hdr
[10] << 8) + hdr
[11], 10);
1015 conf
.onFBUReceive(that
,
1016 {'x': FBU
.x
, 'y': FBU
.y
,
1017 'width': FBU
.width
, 'height': FBU
.height
,
1018 'encoding': FBU
.encoding
,
1019 'encodingName': encNames
[FBU
.encoding
]});
1021 if (encNames
[FBU
.encoding
]) {
1024 var msg = "FramebufferUpdate rects:" + FBU.rects;
1025 msg += " x: " + FBU.x + " y: " + FBU.y;
1026 msg += " width: " + FBU.width + " height: " + FBU.height;
1027 msg += " encoding:" + FBU.encoding;
1028 msg += "(" + encNames[FBU.encoding] + ")";
1029 msg += ", ws.rQlen(): " + ws.rQlen();
1033 fail("Disconnected: unsupported encoding " +
1039 timing
.last_fbu
= (new Date()).getTime();
1041 ret
= encHandlers
[FBU
.encoding
]();
1043 now
= (new Date()).getTime();
1044 timing
.cur_fbu
+= (now
- timing
.last_fbu
);
1047 encStats
[FBU
.encoding
][0] += 1;
1048 encStats
[FBU
.encoding
][1] += 1;
1049 timing
.pixels
+= FBU
.width
* FBU
.height
;
1052 if (timing
.pixels
>= (fb_width
* fb_height
)) {
1053 if (((FBU
.width
=== fb_width
) &&
1054 (FBU
.height
=== fb_height
)) ||
1055 (timing
.fbu_rt_start
> 0)) {
1056 timing
.full_fbu_total
+= timing
.cur_fbu
;
1057 timing
.full_fbu_cnt
+= 1;
1058 Util
.Info("Timing of full FBU, cur: " +
1059 timing
.cur_fbu
+ ", total: " +
1060 timing
.full_fbu_total
+ ", cnt: " +
1061 timing
.full_fbu_cnt
+ ", avg: " +
1062 (timing
.full_fbu_total
/
1063 timing
.full_fbu_cnt
));
1065 if (timing
.fbu_rt_start
> 0) {
1066 fbu_rt_diff
= now
- timing
.fbu_rt_start
;
1067 timing
.fbu_rt_total
+= fbu_rt_diff
;
1068 timing
.fbu_rt_cnt
+= 1;
1069 Util
.Info("full FBU round-trip, cur: " +
1070 fbu_rt_diff
+ ", total: " +
1071 timing
.fbu_rt_total
+ ", cnt: " +
1072 timing
.fbu_rt_cnt
+ ", avg: " +
1073 (timing
.fbu_rt_total
/
1074 timing
.fbu_rt_cnt
));
1075 timing
.fbu_rt_start
= 0;
1079 return ret
; // false ret means need more data
1083 conf
.onFBUComplete(that
,
1084 {'x': FBU
.x
, 'y': FBU
.y
,
1085 'width': FBU
.width
, 'height': FBU
.height
,
1086 'encoding': FBU
.encoding
,
1087 'encodingName': encNames
[FBU
.encoding
]});
1089 return true; // We finished this FBU
1093 // FramebufferUpdate encodings
1096 encHandlers
.RAW
= function display_raw() {
1097 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
1099 var cur_y
, cur_height
;
1101 if (FBU
.lines
=== 0) {
1102 FBU
.lines
= FBU
.height
;
1104 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least a line
1105 if (ws
.rQwait("RAW", FBU
.bytes
)) { return false; }
1106 cur_y
= FBU
.y
+ (FBU
.height
- FBU
.lines
);
1107 cur_height
= Math
.min(FBU
.lines
,
1108 Math
.floor(ws
.rQlen()/(FBU
.width
* fb_Bpp
)));
1109 display
.blitImage(FBU
.x
, cur_y
, FBU
.width
, cur_height
,
1110 ws
.get_rQ(), ws
.get_rQi());
1111 ws
.rQshiftBytes(FBU
.width
* cur_height
* fb_Bpp
);
1112 FBU
.lines
-= cur_height
;
1114 if (FBU
.lines
> 0) {
1115 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least another line
1120 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
1124 encHandlers
.COPYRECT
= function display_copy_rect() {
1125 //Util.Debug(">> display_copy_rect");
1129 if (ws
.rQwait("COPYRECT", 4)) { return false; }
1130 display
.renderQ_push({
1132 'old_x': ws
.rQshift16(),
1133 'old_y': ws
.rQshift16(),
1137 'height': FBU
.height
});
1143 encHandlers
.RRE
= function display_rre() {
1144 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
1145 var color
, x
, y
, width
, height
, chunk
;
1147 if (FBU
.subrects
=== 0) {
1148 if (ws
.rQwait("RRE", 4+fb_Bpp
)) { return false; }
1149 FBU
.subrects
= ws
.rQshift32();
1150 color
= ws
.rQshiftBytes(fb_Bpp
); // Background
1151 display
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1153 while ((FBU
.subrects
> 0) && (ws
.rQlen() >= (fb_Bpp
+ 8))) {
1154 color
= ws
.rQshiftBytes(fb_Bpp
);
1157 width
= ws
.rQshift16();
1158 height
= ws
.rQshift16();
1159 display
.fillRect(FBU
.x
+ x
, FBU
.y
+ y
, width
, height
, color
);
1162 //Util.Debug(" display_rre: rects: " + FBU.rects +
1163 // ", FBU.subrects: " + FBU.subrects);
1165 if (FBU
.subrects
> 0) {
1166 chunk
= Math
.min(rre_chunk_sz
, FBU
.subrects
);
1167 FBU
.bytes
= (fb_Bpp
+ 8) * chunk
;
1172 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1176 encHandlers
.HEXTILE
= function display_hextile() {
1177 //Util.Debug(">> display_hextile");
1178 var subencoding
, subrects
, color
, cur_tile
,
1179 tile_x
, x
, w
, tile_y
, y
, h
, xy
, s
, sx
, sy
, wh
, sw
, sh
,
1180 rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1182 if (FBU
.tiles
=== 0) {
1183 FBU
.tiles_x
= Math
.ceil(FBU
.width
/16);
1184 FBU
.tiles_y
= Math
.ceil(FBU
.height
/16);
1185 FBU
.total_tiles
= FBU
.tiles_x
* FBU
.tiles_y
;
1186 FBU
.tiles
= FBU
.total_tiles
;
1189 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
1190 while (FBU
.tiles
> 0) {
1192 if (ws
.rQwait("HEXTILE subencoding", FBU
.bytes
)) { return false; }
1193 subencoding
= rQ
[rQi
]; // Peek
1194 if (subencoding
> 30) { // Raw
1195 fail("Disconnected: illegal hextile subencoding " + subencoding
);
1196 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
1200 cur_tile
= FBU
.total_tiles
- FBU
.tiles
;
1201 tile_x
= cur_tile
% FBU
.tiles_x
;
1202 tile_y
= Math
.floor(cur_tile
/ FBU
.tiles_x
);
1203 x
= FBU
.x
+ tile_x
* 16;
1204 y
= FBU
.y
+ tile_y
* 16;
1205 w
= Math
.min(16, (FBU
.x
+ FBU
.width
) - x
);
1206 h
= Math
.min(16, (FBU
.y
+ FBU
.height
) - y
);
1208 /* Figure out how much we are expecting */
1209 if (subencoding
& 0x01) { // Raw
1210 //Util.Debug(" Raw subencoding");
1211 FBU
.bytes
+= w
* h
* fb_Bpp
;
1213 if (subencoding
& 0x02) { // Background
1214 FBU
.bytes
+= fb_Bpp
;
1216 if (subencoding
& 0x04) { // Foreground
1217 FBU
.bytes
+= fb_Bpp
;
1219 if (subencoding
& 0x08) { // AnySubrects
1220 FBU
.bytes
+= 1; // Since we aren't shifting it off
1221 if (ws
.rQwait("hextile subrects header", FBU
.bytes
)) { return false; }
1222 subrects
= rQ
[rQi
+ FBU
.bytes
-1]; // Peek
1223 if (subencoding
& 0x10) { // SubrectsColoured
1224 FBU
.bytes
+= subrects
* (fb_Bpp
+ 2);
1226 FBU
.bytes
+= subrects
* 2;
1232 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1233 " (" + tile_x + "," + tile_y + ")" +
1234 " [" + x + "," + y + "]@" + w + "x" + h +
1235 ", subenc:" + subencoding +
1236 "(last: " + FBU.lastsubencoding + "), subrects:" +
1238 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
1239 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
1240 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
1242 if (ws
.rQwait("hextile", FBU
.bytes
)) { return false; }
1244 /* We know the encoding and have a whole tile */
1245 FBU
.subencoding
= rQ
[rQi
];
1247 if (FBU
.subencoding
=== 0) {
1248 if (FBU
.lastsubencoding
& 0x01) {
1249 /* Weird: ignore blanks after RAW */
1250 Util
.Debug(" Ignoring blank after RAW");
1252 display
.fillRect(x
, y
, w
, h
, FBU
.background
);
1254 } else if (FBU
.subencoding
& 0x01) { // Raw
1255 display
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1256 rQi
+= FBU
.bytes
- 1;
1258 if (FBU
.subencoding
& 0x02) { // Background
1259 FBU
.background
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1262 if (FBU
.subencoding
& 0x04) { // Foreground
1263 FBU
.foreground
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1267 display
.startTile(x
, y
, w
, h
, FBU
.background
);
1268 if (FBU
.subencoding
& 0x08) { // AnySubrects
1271 for (s
= 0; s
< subrects
; s
+= 1) {
1272 if (FBU
.subencoding
& 0x10) { // SubrectsColoured
1273 color
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1276 color
= FBU
.foreground
;
1286 sh
= (wh
& 0x0f) + 1;
1288 display
.subTile(sx
, sy
, sw
, sh
, color
);
1291 display
.finishTile();
1294 FBU
.lastsubencoding
= FBU
.subencoding
;
1299 if (FBU
.tiles
=== 0) {
1303 //Util.Debug("<< display_hextile");
1308 // Get 'compact length' header and data size
1309 getTightCLength = function (arr
) {
1310 var header
= 1, data
= 0;
1311 data
+= arr
[0] & 0x7f;
1312 if (arr
[0] & 0x80) {
1314 data
+= (arr
[1] & 0x7f) << 7;
1315 if (arr
[1] & 0x80) {
1317 data
+= arr
[2] << 14;
1320 return [header
, data
];
1323 function display_tight(isTightPNG
) {
1324 //Util.Debug(">> display_tight");
1326 if (fb_depth
=== 1) {
1327 fail("Tight protocol handler only implements true color mode");
1330 var ctl
, cmode
, clength
, color
, img
, data
;
1331 var filterId
= -1, resetStreams
= 0, streamId
= -1;
1332 var rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1334 FBU
.bytes
= 1; // compression-control byte
1335 if (ws
.rQwait("TIGHT compression-control", FBU
.bytes
)) { return false; }
1337 var checksum = function(data
) {
1339 for (i
=0; i
<data
.length
;i
++) {
1341 if (sum
> 65536) sum
-= 65536;
1346 var decompress = function(data
) {
1347 for (var i
=0; i
<4; i
++) {
1348 if ((resetStreams
>> i
) & 1) {
1349 FBU
.zlibs
[i
].reset();
1350 Util
.Info("Reset zlib stream " + i
);
1353 var uncompressed
= FBU
.zlibs
[streamId
].uncompress(data
, 0);
1354 if (uncompressed
.status
!== 0) {
1355 Util
.Error("Invalid data in zlib stream");
1357 //Util.Warn("Decompressed " + data.length + " to " +
1358 // uncompressed.data.length + " checksums " +
1359 // checksum(data) + ":" + checksum(uncompressed.data));
1361 return uncompressed
.data
;
1364 var indexedToRGB = function (data
, numColors
, palette
, width
, height
) {
1365 // Convert indexed (palette based) image data to RGB
1366 // TODO: reduce number of calculations inside loop
1368 var x
, y
, b
, w
, w1
, dp
, sp
;
1369 if (numColors
=== 2) {
1370 w
= Math
.floor((width
+ 7) / 8);
1371 w1
= Math
.floor(width
/ 8);
1372 for (y
= 0; y
< height
; y
++) {
1373 for (x
= 0; x
< w1
; x
++) {
1374 for (b
= 7; b
>= 0; b
--) {
1375 dp
= (y
*width
+ x
*8 + 7-b
) * 3;
1376 sp
= (data
[y
*w
+ x
] >> b
& 1) * 3;
1377 dest
[dp
] = palette
[sp
];
1378 dest
[dp
+1] = palette
[sp
+1];
1379 dest
[dp
+2] = palette
[sp
+2];
1382 for (b
= 7; b
>= 8 - width
% 8; b
--) {
1383 dp
= (y
*width
+ x
*8 + 7-b
) * 3;
1384 sp
= (data
[y
*w
+ x
] >> b
& 1) * 3;
1385 dest
[dp
] = palette
[sp
];
1386 dest
[dp
+1] = palette
[sp
+1];
1387 dest
[dp
+2] = palette
[sp
+2];
1391 for (y
= 0; y
< height
; y
++) {
1392 for (x
= 0; x
< width
; x
++) {
1393 dp
= (y
*width
+ x
) * 3;
1394 sp
= data
[y
*width
+ x
] * 3;
1395 dest
[dp
] = palette
[sp
];
1396 dest
[dp
+1] = palette
[sp
+1];
1397 dest
[dp
+2] = palette
[sp
+2];
1403 var handlePalette = function() {
1404 var numColors
= rQ
[rQi
+ 2] + 1;
1405 var paletteSize
= numColors
* fb_depth
;
1406 FBU
.bytes
+= paletteSize
;
1407 if (ws
.rQwait("TIGHT palette " + cmode
, FBU
.bytes
)) { return false; }
1409 var bpp
= (numColors
<= 2) ? 1 : 8;
1410 var rowSize
= Math
.floor((FBU
.width
* bpp
+ 7) / 8);
1412 if (rowSize
* FBU
.height
< 12) {
1414 clength
= [0, rowSize
* FBU
.height
];
1416 clength
= getTightCLength(ws
.rQslice(3 + paletteSize
,
1417 3 + paletteSize
+ 3));
1419 FBU
.bytes
+= clength
[0] + clength
[1];
1420 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1422 // Shift ctl, filter id, num colors, palette entries, and clength off
1424 var palette
= ws
.rQshiftBytes(paletteSize
);
1425 ws
.rQshiftBytes(clength
[0]);
1428 data
= ws
.rQshiftBytes(clength
[1]);
1430 data
= decompress(ws
.rQshiftBytes(clength
[1]));
1433 // Convert indexed (palette based) image data to RGB
1434 var rgb
= indexedToRGB(data
, numColors
, palette
, FBU
.width
, FBU
.height
);
1436 // Add it to the render queue
1437 display
.renderQ_push({
1443 'height': FBU
.height
});
1447 var handleCopy = function() {
1449 var uncompressedSize
= FBU
.width
* FBU
.height
* fb_depth
;
1450 if (uncompressedSize
< 12) {
1452 clength
= [0, uncompressedSize
];
1454 clength
= getTightCLength(ws
.rQslice(1, 4));
1456 FBU
.bytes
= 1 + clength
[0] + clength
[1];
1457 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1459 // Shift ctl, clength off
1460 ws
.rQshiftBytes(1 + clength
[0]);
1463 data
= ws
.rQshiftBytes(clength
[1]);
1465 data
= decompress(ws
.rQshiftBytes(clength
[1]));
1468 display
.renderQ_push({
1474 'height': FBU
.height
});
1480 // Keep tight reset bits
1481 resetStreams
= ctl
& 0xF;
1483 // Figure out filter
1485 streamId
= ctl
& 0x3;
1487 if (ctl
=== 0x08) cmode
= "fill";
1488 else if (ctl
=== 0x09) cmode
= "jpeg";
1489 else if (ctl
=== 0x0A) cmode
= "png";
1490 else if (ctl
& 0x04) cmode
= "filter";
1491 else if (ctl
< 0x04) cmode
= "copy";
1492 else return fail("Illegal tight compression received, ctl: " + ctl
);
1494 if (isTightPNG
&& (cmode
=== "filter" || cmode
=== "copy")) {
1495 return fail("filter/copy received in tightPNG mode");
1499 // fill uses fb_depth because TPIXELs drop the padding byte
1500 case "fill": FBU
.bytes
+= fb_depth
; break; // TPIXEL
1501 case "jpeg": FBU
.bytes
+= 3; break; // max clength
1502 case "png": FBU
.bytes
+= 3; break; // max clength
1503 case "filter": FBU
.bytes
+= 2; break; // filter id + num colors if palette
1507 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1509 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1510 //Util.Debug(" cmode: " + cmode);
1512 // Determine FBU.bytes
1515 ws
.rQshift8(); // shift off ctl
1516 color
= ws
.rQshiftBytes(fb_depth
);
1517 display
.renderQ_push({
1522 'height': FBU
.height
,
1523 'color': [color
[2], color
[1], color
[0]] });
1527 clength
= getTightCLength(ws
.rQslice(1, 4));
1528 FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1529 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1531 // We have everything, render it
1532 //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
1533 // clength[0] + ", clength[1]: " + clength[1]);
1534 ws
.rQshiftBytes(1 + clength
[0]); // shift off ctl + compact length
1536 img
.src
= "data:image/" + cmode
+
1537 extract_data_uri(ws
.rQshiftBytes(clength
[1]));
1538 display
.renderQ_push({
1546 filterId
= rQ
[rQi
+ 1];
1547 if (filterId
=== 1) {
1548 if (!handlePalette()) { return false; }
1550 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1551 // Filter 2, Gradient is valid but not used if jpeg is enabled
1552 throw("Unsupported tight subencoding received, filter: " + filterId
);
1556 if (!handleCopy()) { return false; }
1562 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1563 //Util.Debug("<< display_tight_png");
1567 extract_data_uri = function(arr
) {
1569 //for (i=0; i< arr.length; i += 1) {
1570 // stra.push(String.fromCharCode(arr[i]));
1572 //return "," + escape(stra.join(''));
1573 return ";base64," + Base64
.encode(arr
);
1576 encHandlers
.TIGHT = function () { return display_tight(false); };
1577 encHandlers
.TIGHT_PNG = function () { return display_tight(true); };
1579 encHandlers
.last_rect
= function last_rect() {
1580 //Util.Debug(">> last_rect");
1582 //Util.Debug("<< last_rect");
1586 encHandlers
.DesktopSize
= function set_desktopsize() {
1587 Util
.Debug(">> set_desktopsize");
1588 fb_width
= FBU
.width
;
1589 fb_height
= FBU
.height
;
1590 conf
.onFBResize(that
, fb_width
, fb_height
);
1591 display
.resize(fb_width
, fb_height
);
1592 timing
.fbu_rt_start
= (new Date()).getTime();
1593 // Send a new non-incremental request
1594 ws
.send(fbUpdateRequests());
1599 Util
.Debug("<< set_desktopsize");
1603 encHandlers
.Cursor
= function set_cursor() {
1604 var x
, y
, w
, h
, pixelslength
, masklength
;
1605 Util
.Debug(">> set_cursor");
1606 x
= FBU
.x
; // hotspot-x
1607 y
= FBU
.y
; // hotspot-y
1611 pixelslength
= w
* h
* fb_Bpp
;
1612 masklength
= Math
.floor((w
+ 7) / 8) * h
;
1614 FBU
.bytes
= pixelslength
+ masklength
;
1615 if (ws
.rQwait("cursor encoding", FBU
.bytes
)) { return false; }
1617 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1619 display
.changeCursor(ws
.rQshiftBytes(pixelslength
),
1620 ws
.rQshiftBytes(masklength
),
1626 Util
.Debug("<< set_cursor");
1630 encHandlers
.JPEG_quality_lo
= function set_jpeg_quality() {
1631 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1634 encHandlers
.compress_lo
= function set_compress_level() {
1635 Util
.Error("Server sent compress level pseudo-encoding");
1639 * Client message routines
1642 pixelFormat = function() {
1643 //Util.Debug(">> pixelFormat");
1645 arr
= [0]; // msg-type
1646 arr
.push8(0); // padding
1647 arr
.push8(0); // padding
1648 arr
.push8(0); // padding
1650 arr
.push8(fb_Bpp
* 8); // bits-per-pixel
1651 arr
.push8(fb_depth
* 8); // depth
1652 arr
.push8(0); // little-endian
1653 arr
.push8(conf
.true_color
? 1 : 0); // true-color
1655 arr
.push16(255); // red-max
1656 arr
.push16(255); // green-max
1657 arr
.push16(255); // blue-max
1658 arr
.push8(16); // red-shift
1659 arr
.push8(8); // green-shift
1660 arr
.push8(0); // blue-shift
1662 arr
.push8(0); // padding
1663 arr
.push8(0); // padding
1664 arr
.push8(0); // padding
1665 //Util.Debug("<< pixelFormat");
1669 clientEncodings = function() {
1670 //Util.Debug(">> clientEncodings");
1671 var arr
, i
, encList
= [];
1673 for (i
=0; i
<encodings
.length
; i
+= 1) {
1674 if ((encodings
[i
][0] === "Cursor") &&
1675 (! conf
.local_cursor
)) {
1676 Util
.Debug("Skipping Cursor pseudo-encoding");
1678 //Util.Debug("Adding encoding: " + encodings[i][0]);
1679 encList
.push(encodings
[i
][1]);
1683 arr
= [2]; // msg-type
1684 arr
.push8(0); // padding
1686 arr
.push16(encList
.length
); // encoding count
1687 for (i
=0; i
< encList
.length
; i
+= 1) {
1688 arr
.push32(encList
[i
]);
1690 //Util.Debug("<< clientEncodings: " + arr);
1694 fbUpdateRequest = function(incremental
, x
, y
, xw
, yw
) {
1695 //Util.Debug(">> fbUpdateRequest");
1696 if (typeof(x
) === "undefined") { x
= 0; }
1697 if (typeof(y
) === "undefined") { y
= 0; }
1698 if (typeof(xw
) === "undefined") { xw
= fb_width
; }
1699 if (typeof(yw
) === "undefined") { yw
= fb_height
; }
1701 arr
= [3]; // msg-type
1702 arr
.push8(incremental
);
1707 //Util.Debug("<< fbUpdateRequest");
1711 // Based on clean/dirty areas, generate requests to send
1712 fbUpdateRequests = function() {
1713 var cleanDirty
= display
.getCleanDirtyReset(),
1714 arr
= [], i
, cb
, db
;
1716 cb
= cleanDirty
.cleanBox
;
1717 if (cb
.w
> 0 && cb
.h
> 0) {
1718 // Request incremental for clean box
1719 arr
= arr
.concat(fbUpdateRequest(1, cb
.x
, cb
.y
, cb
.w
, cb
.h
));
1721 for (i
= 0; i
< cleanDirty
.dirtyBoxes
.length
; i
++) {
1722 db
= cleanDirty
.dirtyBoxes
[i
];
1723 // Force all (non-incremental for dirty box
1724 arr
= arr
.concat(fbUpdateRequest(0, db
.x
, db
.y
, db
.w
, db
.h
));
1731 keyEvent = function(keysym
, down
) {
1732 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1734 arr
= [4]; // msg-type
1738 //Util.Debug("<< keyEvent");
1742 pointerEvent = function(x
, y
) {
1743 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1744 // " , mask: " + mouse_buttonMask);
1746 arr
= [5]; // msg-type
1747 arr
.push8(mouse_buttonMask
);
1750 //Util.Debug("<< pointerEvent");
1754 clientCutText = function(text
) {
1755 //Util.Debug(">> clientCutText");
1757 arr
= [6]; // msg-type
1758 arr
.push8(0); // padding
1759 arr
.push8(0); // padding
1760 arr
.push8(0); // padding
1761 arr
.push32(text
.length
);
1763 for (i
=0; i
< n
; i
+=1) {
1764 arr
.push(text
.charCodeAt(i
));
1766 //Util.Debug("<< clientCutText:" + arr);
1773 // Public API interface functions
1776 that
.connect = function(host
, port
, password
, path
) {
1777 //Util.Debug(">> connect");
1781 rfb_password
= (password
!== undefined) ? password
: "";
1782 rfb_path
= (path
!== undefined) ? path
: "";
1784 if ((!rfb_host
) || (!rfb_port
)) {
1785 return fail("Must set host and port");
1788 updateState('connect');
1789 //Util.Debug("<< connect");
1793 that
.disconnect = function() {
1794 //Util.Debug(">> disconnect");
1795 updateState('disconnect', 'Disconnecting');
1796 //Util.Debug("<< disconnect");
1799 that
.sendPassword = function(passwd
) {
1800 rfb_password
= passwd
;
1801 rfb_state
= "Authentication";
1802 setTimeout(init_msg
, 1);
1805 that
.sendCtrlAltDel = function() {
1806 if (rfb_state
!== "normal" || conf
.view_only
) { return false; }
1807 Util
.Info("Sending Ctrl-Alt-Del");
1809 arr
= arr
.concat(keyEvent(0xFFE3, 1)); // Control
1810 arr
= arr
.concat(keyEvent(0xFFE9, 1)); // Alt
1811 arr
= arr
.concat(keyEvent(0xFFFF, 1)); // Delete
1812 arr
= arr
.concat(keyEvent(0xFFFF, 0)); // Delete
1813 arr
= arr
.concat(keyEvent(0xFFE9, 0)); // Alt
1814 arr
= arr
.concat(keyEvent(0xFFE3, 0)); // Control
1815 arr
= arr
.concat(fbUpdateRequests());
1819 // Send a key press. If 'down' is not specified then send a down key
1820 // followed by an up key.
1821 that
.sendKey = function(code
, down
) {
1822 if (rfb_state
!== "normal" || conf
.view_only
) { return false; }
1824 if (typeof down
!== 'undefined') {
1825 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
1826 arr
= arr
.concat(keyEvent(code
, down
? 1 : 0));
1828 Util
.Info("Sending key code (down + up): " + code
);
1829 arr
= arr
.concat(keyEvent(code
, 1));
1830 arr
= arr
.concat(keyEvent(code
, 0));
1832 arr
= arr
.concat(fbUpdateRequests());
1836 that
.clipboardPasteFrom = function(text
) {
1837 if (rfb_state
!== "normal") { return; }
1838 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1839 ws
.send(clientCutText(text
));
1840 //Util.Debug("<< clipboardPasteFrom");
1843 // Override internal functions for testing
1844 that
.testMode = function(override_send
, data_mode
) {
1846 that
.recv_message
= ws
.testMode(override_send
, data_mode
);
1848 checkEvents = function () { /* Stub Out */ };
1849 that
.connect = function(host
, port
, password
) {
1852 rfb_password
= password
;
1854 updateState('ProtocolVersion', "Starting VNC handshake");
1859 return constructor(); // Return the public API interface