]>
git.proxmox.com Git - mirror_novnc.git/blob - include/rfb.js
c9fd92a9ca52f30c18c312119331fb3b74325106
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
5 * Licensed under MPL 2.0 (see LICENSE.txt)
7 * See README.md for usage and integration instructions.
9 * TIGHT decoder portion:
10 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
13 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
14 /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
17 function RFB(defaults
) {
20 var that
= {}, // Public API methods
21 conf
= {}, // Configuration attributes
23 // Pre-declare private functions used before definitions (jslint)
24 init_vars
, updateState
, fail
, handle_message
,
25 init_msg
, normal_msg
, framebufferUpdate
, print_stats
,
27 pixelFormat
, clientEncodings
, fbUpdateRequest
, fbUpdateRequests
,
28 keyEvent
, pointerEvent
, clientCutText
,
30 getTightCLength
, extract_data_uri
,
31 keyPress
, mouseButton
, mouseMove
,
33 checkEvents
, // Overridable for testing
37 // Private RFB namespace variables
44 rfb_state
= 'disconnected',
53 // In preference order
61 ['DesktopSize', -223 ],
64 // Psuedo-encoding settings
65 //['JPEG_quality_lo', -32 ],
66 ['JPEG_quality_med', -26 ],
67 //['JPEG_quality_hi', -23 ],
68 //['compress_lo', -255 ],
69 ['compress_hi', -247 ],
76 encStats
= {}, // [rectCnt, rectCntTot]
78 ws
= null, // Websock object
79 display
= null, // Display object
80 keyboard
= null, // Keyboard input handler object
81 mouse
= null, // Mouse input handler object
82 sendTimer
= null, // Send Queue check timer
83 disconnTimer
= null, // disconnection timer
84 msgTimer
= null, // queued handle_message timer
86 // Frame buffer update state
100 zlibs
: [] // TIGHT zlib streams
127 mouse_buttonMask
= 0,
129 viewportDragging
= false,
130 viewportDragPos
= {};
132 // Configuration attributes
133 Util
.conf_defaults(conf
, that
, defaults
, [
134 ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
135 ['focusContainer', 'wo', 'dom', document
, 'DOM element that captures keyboard input'],
137 ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
138 ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
139 ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
140 ['shared', 'rw', 'bool', true, 'Request shared mode'],
141 ['view_only', 'rw', 'bool', false, 'Disable client mouse/keyboard'],
142 ['xvp_password_sep', 'rw', 'str', '@', 'Separator for XVP password fields'],
143 ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
145 ['wsProtocols', 'rw', 'arr', ['binary', 'base64'],
146 'Protocols to use in the WebSocket connection'],
148 // UltraVNC repeater ID to connect to
149 ['repeaterID', 'rw', 'str', '', 'RepeaterID to connect to'],
151 ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
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'],
168 ['onDesktopName', 'rw', 'func', function() { },
169 'onDesktopName(rfb, name): desktop name received'],
170 ['onXvpInit', 'rw', 'func', function() { },
171 'onXvpInit(version): XVP extensions active for this connection'],
173 // These callback names are deprecated
174 ['updateState', 'rw', 'func', function() { },
175 'obsolete, use onUpdateState'],
176 ['clipboardReceive', 'rw', 'func', function() { },
177 'obsolete, use onClipboard']
181 // Override/add some specific configuration getters/setters
182 that
.set_local_cursor = function(cursor
) {
183 if ((!cursor
) || (cursor
in {'0':1, 'no':1, 'false':1})) {
184 conf
.local_cursor
= false;
186 if (display
.get_cursor_uri()) {
187 conf
.local_cursor
= true;
189 Util
.Warn("Browser does not support local cursor");
194 // These are fake configuration getters
195 that
.get_display = function() { return display
; };
197 that
.get_keyboard = function() { return keyboard
; };
199 that
.get_mouse = function() { return mouse
; };
207 // Create the public API interface and initialize values that stay
208 // constant across connect/disconnect
209 function constructor() {
211 Util
.Debug(">> RFB.constructor");
213 // Create lookup tables based encoding number
214 for (i
=0; i
< encodings
.length
; i
+=1) {
215 encHandlers
[encodings
[i
][1]] = encHandlers
[encodings
[i
][0]];
216 encNames
[encodings
[i
][1]] = encodings
[i
][0];
217 encStats
[encodings
[i
][1]] = [0, 0];
219 // Initialize display, mouse, keyboard, and websock
221 display
= new Display({'target': conf
.target
});
223 Util
.Error("Display exception: " + exc
);
224 updateState('fatal', "No working Display");
226 keyboard
= new Keyboard({'target': conf
.focusContainer
,
227 'onKeyPress': keyPress
});
228 mouse
= new Mouse({'target': conf
.target
,
229 'onMouseButton': mouseButton
,
230 'onMouseMove': mouseMove
,
231 'notify': keyboard
.sync
});
233 rmode
= display
.get_render_mode();
236 ws
.on('message', handle_message
);
237 ws
.on('open', function() {
238 if (rfb_state
=== "connect") {
239 updateState('ProtocolVersion', "Starting VNC handshake");
241 fail("Got unexpected WebSockets connection");
244 ws
.on('close', function(e
) {
245 Util
.Warn("WebSocket on-close event");
248 msg
= " (code: " + e
.code
;
250 msg
+= ", reason: " + e
.reason
;
254 if (rfb_state
=== 'disconnect') {
255 updateState('disconnected', 'VNC disconnected' + msg
);
256 } else if (rfb_state
=== 'ProtocolVersion') {
257 fail('Failed to connect to server' + msg
);
258 } else if (rfb_state
in {'failed':1, 'disconnected':1}) {
259 Util
.Error("Received onclose while disconnected" + msg
);
261 fail('Server disconnected' + msg
);
264 ws
.on('error', function(e
) {
265 Util
.Warn("WebSocket on-error event");
266 //fail("WebSock reported an error");
272 /* Check web-socket-js if no builtin WebSocket support */
273 if (Websock_native
) {
274 Util
.Info("Using native WebSockets");
275 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode
);
277 Util
.Warn("Using web-socket-js bridge. Flash version: " +
279 if ((! Util
.Flash
) ||
280 (Util
.Flash
.version
< 9)) {
281 updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
282 } else if (document
.location
.href
.substr(0, 7) === "file://") {
284 "'file://' URL is incompatible with Adobe Flash");
286 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode
);
290 Util
.Debug("<< RFB.constructor");
291 return that
; // Return the public API interface
295 Util
.Debug(">> RFB.connect");
298 if (typeof UsingSocketIO
!== "undefined") {
299 uri
= "http://" + rfb_host
+ ":" + rfb_port
+ "/" + rfb_path
;
306 uri
+= rfb_host
+ ":" + rfb_port
+ "/" + rfb_path
;
308 Util
.Info("connecting to " + uri
);
309 // TODO: make protocols a configurable
310 ws
.open(uri
, conf
.wsProtocols
);
312 Util
.Debug("<< RFB.connect");
315 // Initialize variables that are reset before each connection
316 init_vars = function() {
323 FBU
.subrects
= 0; // RRE and HEXTILE
324 FBU
.lines
= 0; // RAW
325 FBU
.tiles
= 0; // HEXTILE
326 FBU
.zlibs
= []; // TIGHT zlib encoders
327 mouse_buttonMask
= 0;
330 // Clear the per connection encoding stats
331 for (i
=0; i
< encodings
.length
; i
+=1) {
332 encStats
[encodings
[i
][1]][0] = 0;
335 for (i
=0; i
< 4; i
++) {
336 //FBU.zlibs[i] = new InflateStream();
337 FBU
.zlibs
[i
] = new TINF();
343 print_stats = function() {
345 Util
.Info("Encoding stats for this connection:");
346 for (i
=0; i
< encodings
.length
; i
+=1) {
347 s
= encStats
[encodings
[i
][1]];
348 if ((s
[0] + s
[1]) > 0) {
349 Util
.Info(" " + encodings
[i
][0] + ": " +
353 Util
.Info("Encoding stats since page load:");
354 for (i
=0; i
< encodings
.length
; i
+=1) {
355 s
= encStats
[encodings
[i
][1]];
356 if ((s
[0] + s
[1]) > 0) {
357 Util
.Info(" " + encodings
[i
][0] + ": " +
370 * loaded - page load, equivalent to disconnected
371 * disconnected - idle state
372 * connect - starting to connect (to ProtocolVersion)
374 * disconnect - starting to disconnect
375 * failed - abnormal disconnect
376 * fatal - failed to load page, or fatal error
378 * RFB protocol initialization states:
382 * password - waiting for password, not part of RFB
384 * ClientInitialization - not triggered by server message
385 * ServerInitialization (to normal)
387 updateState = function(state
, statusMsg
) {
388 var func
, cmsg
, oldstate
= rfb_state
;
390 if (state
=== oldstate
) {
391 /* Already here, ignore */
392 Util
.Debug("Already in state '" + state
+ "', ignoring.");
397 * These are disconnected states. A previous connect may
398 * asynchronously cause a connection so make sure we are closed.
400 if (state
in {'disconnected':1, 'loaded':1, 'connect':1,
401 'disconnect':1, 'failed':1, 'fatal':1}) {
403 clearInterval(sendTimer
);
408 clearTimeout(msgTimer
);
412 if (display
&& display
.get_context()) {
415 display
.defaultCursor();
416 if ((Util
.get_logging() !== 'debug') ||
417 (state
=== 'loaded')) {
418 // Show noVNC logo on load and when disconnected if
427 if (oldstate
=== 'fatal') {
428 Util
.Error("Fatal error, cannot continue");
431 if ((state
=== 'failed') || (state
=== 'fatal')) {
437 cmsg
= typeof(statusMsg
) !== 'undefined' ? (" Msg: " + statusMsg
) : "";
438 func("New state '" + state
+ "', was '" + oldstate
+ "'." + cmsg
);
440 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
441 // Do disconnect action, but stay in failed state
442 rfb_state
= 'failed';
447 if (disconnTimer
&& (rfb_state
!== 'disconnect')) {
448 Util
.Debug("Clearing disconnect timer");
449 clearTimeout(disconnTimer
);
455 if ((oldstate
=== 'disconnected') || (oldstate
=== 'failed')) {
456 Util
.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
467 // WebSocket.onopen transitions to 'ProtocolVersion'
474 disconnTimer
= setTimeout(function () {
475 fail("Disconnect timeout");
476 }, conf
.disconnectTimeout
* 1000);
481 // WebSocket.onclose transitions to 'disconnected'
486 if (oldstate
=== 'disconnected') {
487 Util
.Error("Invalid transition from 'disconnected' to 'failed'");
489 if (oldstate
=== 'normal') {
490 Util
.Error("Error while connected.");
492 if (oldstate
=== 'init') {
493 Util
.Error("Error while initializing.");
496 // Make sure we transition to disconnected
497 setTimeout(function() { updateState('disconnected'); }, 50);
503 // No state change action to take
507 if ((oldstate
=== 'failed') && (state
=== 'disconnected')) {
508 // Leave the failed message
509 conf
.updateState(that
, state
, oldstate
); // Obsolete
510 conf
.onUpdateState(that
, state
, oldstate
);
512 conf
.updateState(that
, state
, oldstate
, statusMsg
); // Obsolete
513 conf
.onUpdateState(that
, state
, oldstate
, statusMsg
);
517 fail = function(msg
) {
518 updateState('failed', msg
);
522 handle_message = function() {
523 //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
524 //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
525 if (ws
.rQlen() === 0) {
526 Util
.Warn("handle_message called on empty receive queue");
532 Util
.Error("Got data while disconnected");
535 if (normal_msg() && ws
.rQlen() > 0) {
536 // true means we can continue processing
537 // Give other events a chance to run
538 if (msgTimer
=== null) {
539 Util
.Debug("More data to process, creating timer");
540 msgTimer
= setTimeout(function () {
545 Util
.Debug("More data to process, existing timer");
556 function genDES(password
, challenge
) {
558 for (i
=0; i
< password
.length
; i
+= 1) {
559 passwd
.push(password
.charCodeAt(i
));
561 return (new DES(passwd
)).encrypt(challenge
);
564 // overridable for testing
565 checkEvents = function() {
566 if (rfb_state
=== 'normal' && !viewportDragging
&& mouse_arr
.length
> 0) {
572 keyPress = function(keysym
, down
) {
573 if (conf
.view_only
) { return; } // View only, skip keyboard events
575 ws
.send(keyEvent(keysym
, down
));
578 mouseButton = function(x
, y
, down
, bmask
) {
580 mouse_buttonMask
|= bmask
;
582 mouse_buttonMask
^= bmask
;
585 if (conf
.viewportDrag
) {
586 if (down
&& !viewportDragging
) {
587 viewportDragging
= true;
588 viewportDragPos
= {'x': x
, 'y': y
};
590 // Skip sending mouse events
593 viewportDragging
= false;
597 if (conf
.view_only
) { return; } // View only, skip mouse events
599 mouse_arr
= mouse_arr
.concat(
600 pointerEvent(display
.absX(x
), display
.absY(y
)) );
605 mouseMove = function(x
, y
) {
606 //Util.Debug('>> mouseMove ' + x + "," + y);
609 if (viewportDragging
) {
610 //deltaX = x - viewportDragPos.x; // drag viewport
611 deltaX
= viewportDragPos
.x
- x
; // drag frame buffer
612 //deltaY = y - viewportDragPos.y; // drag viewport
613 deltaY
= viewportDragPos
.y
- y
; // drag frame buffer
614 viewportDragPos
= {'x': x
, 'y': y
};
616 display
.viewportChange(deltaX
, deltaY
);
618 // Skip sending mouse events
622 if (conf
.view_only
) { return; } // View only, skip mouse events
624 mouse_arr
= mouse_arr
.concat(
625 pointerEvent(display
.absX(x
), display
.absY(y
)));
632 // Server message handlers
635 // RFB/VNC initialisation message handler
636 init_msg = function() {
637 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
639 var strlen
, reason
, length
, sversion
, cversion
, repeaterID
,
640 i
, types
, num_types
, challenge
, response
, bpp
, depth
,
641 big_endian
, red_max
, green_max
, blue_max
, red_shift
,
642 green_shift
, blue_shift
, true_color
, name_length
, is_repeater
,
643 xvp_sep
, xvp_auth
, xvp_auth_str
;
645 //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
648 case 'ProtocolVersion' :
649 if (ws
.rQlen() < 12) {
650 return fail("Incomplete protocol version");
652 sversion
= ws
.rQshiftStr(12).substr(4,7);
653 Util
.Info("Server ProtocolVersion: " + sversion
);
656 case "000.000": is_repeater
= 1; break; // UltraVNC repeater
657 case "003.003": rfb_version
= 3.3; break;
658 case "003.006": rfb_version
= 3.3; break; // UltraVNC
659 case "003.889": rfb_version
= 3.3; break; // Apple Remote Desktop
660 case "003.007": rfb_version
= 3.7; break;
661 case "003.008": rfb_version
= 3.8; break;
662 case "004.000": rfb_version
= 3.8; break; // Intel AMT KVM
663 case "004.001": rfb_version
= 3.8; break; // RealVNC 4.6
665 return fail("Invalid server version " + sversion
);
668 repeaterID
= conf
.repeaterID
;
669 while (repeaterID
.length
< 250) {
672 ws
.send_string(repeaterID
);
675 if (rfb_version
> rfb_max_version
) {
676 rfb_version
= rfb_max_version
;
680 sendTimer
= setInterval(function() {
681 // Send updates either at a rate of one update
682 // every 50ms, or whatever slower rate the network
688 cversion
= "00" + parseInt(rfb_version
,10) +
689 ".00" + ((rfb_version
* 10) % 10);
690 ws
.send_string("RFB " + cversion
+ "\n");
691 updateState('Security', "Sent ProtocolVersion: " + cversion
);
695 if (rfb_version
>= 3.7) {
696 // Server sends supported list, client decides
697 num_types
= ws
.rQshift8();
698 if (ws
.rQwait("security type", num_types
, 1)) { return false; }
699 if (num_types
=== 0) {
700 strlen
= ws
.rQshift32();
701 reason
= ws
.rQshiftStr(strlen
);
702 return fail("Security failure: " + reason
);
705 types
= ws
.rQshiftBytes(num_types
);
706 Util
.Debug("Server security types: " + types
);
707 for (i
=0; i
< types
.length
; i
+=1) {
708 if ((types
[i
] > rfb_auth_scheme
) && (types
[i
] <= 16 || types
[i
] == 22)) {
709 rfb_auth_scheme
= types
[i
];
712 if (rfb_auth_scheme
=== 0) {
713 return fail("Unsupported security types: " + types
);
716 ws
.send([rfb_auth_scheme
]);
719 if (ws
.rQwait("security scheme", 4)) { return false; }
720 rfb_auth_scheme
= ws
.rQshift32();
722 updateState('Authentication',
723 "Authenticating using scheme: " + rfb_auth_scheme
);
724 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
727 // Triggered by fallthough, not by server message
728 case 'Authentication' :
729 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
730 switch (rfb_auth_scheme
) {
731 case 0: // connection failed
732 if (ws
.rQwait("auth reason", 4)) { return false; }
733 strlen
= ws
.rQshift32();
734 reason
= ws
.rQshiftStr(strlen
);
735 return fail("Auth failure: " + reason
);
736 case 1: // no authentication
737 if (rfb_version
>= 3.8) {
738 updateState('SecurityResult');
741 // Fall through to ClientInitialisation
743 case 22: // XVP authentication
744 xvp_sep
= conf
.xvp_password_sep
;
745 xvp_auth
= rfb_password
.split(xvp_sep
);
746 if (xvp_auth
.length
< 3) {
747 updateState('password', "XVP credentials required (user" + xvp_sep
+
748 "target" + xvp_sep
+ "password) -- got only " + rfb_password
);
749 conf
.onPasswordRequired(that
);
752 xvp_auth_str
= String
.fromCharCode(xvp_auth
[0].length
) +
753 String
.fromCharCode(xvp_auth
[1].length
) +
756 ws
.send_string(xvp_auth_str
);
757 rfb_password
= xvp_auth
.slice(2).join(xvp_sep
);
759 // Fall through to standard VNC authentication with remaining part of password
760 case 2: // VNC authentication
761 if (rfb_password
.length
=== 0) {
762 // Notify via both callbacks since it is kind of
763 // a RFB state change and a UI interface issue.
764 updateState('password', "Password Required");
765 conf
.onPasswordRequired(that
);
768 if (ws
.rQwait("auth challenge", 16)) { return false; }
769 challenge
= ws
.rQshiftBytes(16);
770 //Util.Debug("Password: " + rfb_password);
771 //Util.Debug("Challenge: " + challenge +
772 // " (" + challenge.length + ")");
773 response
= genDES(rfb_password
, challenge
);
774 //Util.Debug("Response: " + response +
775 // " (" + response.length + ")");
777 //Util.Debug("Sending DES encrypted auth response");
779 updateState('SecurityResult');
781 case 16: // TightVNC Security Type
782 if (ws
.rQwait("num tunnels", 4)) { return false; }
783 var numTunnels
= ws
.rQshift32();
784 //console.log("Number of tunnels: "+numTunnels);
790 fail("Protocol requested tunnels, not currently supported. numTunnels: " + numTunnels
);
794 var clientSupportedTypes
= {
799 var serverSupportedTypes
= [];
801 if (ws
.rQwait("sub auth count", 4)) { return false; }
802 var subAuthCount
= ws
.rQshift32();
803 //console.log("Sub auth count: "+subAuthCount);
804 for (var i
=0;i
<subAuthCount
;i
++)
807 if (ws
.rQwait("sub auth capabilities "+i
, 16)) { return false; }
808 var capNum
= ws
.rQshift32();
809 var capabilities
= ws
.rQshiftStr(12);
810 //console.log("queue: "+ws.rQlen());
811 //console.log("auth type: "+capNum+": "+capabilities);
813 serverSupportedTypes
.push(capabilities
);
816 for (var authType
in clientSupportedTypes
)
818 if (serverSupportedTypes
.indexOf(authType
) != -1)
820 //console.log("selected authType "+authType);
821 ws
.send([0,0,0,clientSupportedTypes
[authType
]]);
827 updateState('SecurityResult');
830 // VNC Authentication. Reenter auth handler to complete auth
835 fail("Unsupported tiny auth scheme: " + authType
);
844 fail("Unsupported auth scheme: " + rfb_auth_scheme
);
847 updateState('ClientInitialisation', "No auth required");
848 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
851 case 'SecurityResult' :
852 if (ws
.rQwait("VNC auth response ", 4)) { return false; }
853 switch (ws
.rQshift32()) {
855 // Fall through to ClientInitialisation
858 if (rfb_version
>= 3.8) {
859 length
= ws
.rQshift32();
860 if (ws
.rQwait("SecurityResult reason", length
, 8)) {
863 reason
= ws
.rQshiftStr(length
);
866 fail("Authentication failed");
870 return fail("Too many auth attempts");
872 updateState('ClientInitialisation', "Authentication OK");
873 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
876 // Triggered by fallthough, not by server message
877 case 'ClientInitialisation' :
878 ws
.send([conf
.shared
? 1 : 0]); // ClientInitialisation
879 updateState('ServerInitialisation', "Authentication OK");
882 case 'ServerInitialisation' :
883 if (ws
.rQwait("server initialization", 24)) { return false; }
886 fb_width
= ws
.rQshift16();
887 fb_height
= ws
.rQshift16();
891 depth
= ws
.rQshift8();
892 big_endian
= ws
.rQshift8();
893 true_color
= ws
.rQshift8();
895 red_max
= ws
.rQshift16();
896 green_max
= ws
.rQshift16();
897 blue_max
= ws
.rQshift16();
898 red_shift
= ws
.rQshift8();
899 green_shift
= ws
.rQshift8();
900 blue_shift
= ws
.rQshift8();
901 ws
.rQshiftStr(3); // padding
903 Util
.Info("Screen: " + fb_width
+ "x" + fb_height
+
904 ", bpp: " + bpp
+ ", depth: " + depth
+
905 ", big_endian: " + big_endian
+
906 ", true_color: " + true_color
+
907 ", red_max: " + red_max
+
908 ", green_max: " + green_max
+
909 ", blue_max: " + blue_max
+
910 ", red_shift: " + red_shift
+
911 ", green_shift: " + green_shift
+
912 ", blue_shift: " + blue_shift
);
914 if (big_endian
!== 0) {
915 Util
.Warn("Server native endian is not little endian");
917 if (red_shift
!== 16) {
918 Util
.Warn("Server native red-shift is not 16");
920 if (blue_shift
!== 0) {
921 Util
.Warn("Server native blue-shift is not 0");
924 /* Connection name/title */
925 name_length = ws.rQshift32();
926 fb_name = ws.rQshiftStr(name_length);
927 conf.onDesktopName(that, fb_name);
929 if (conf.true_color && fb_name === "Intel(r) AMT KVM")
931 Util.Warn("Intel AMT KVM only support 8/16 bit depths. Disabling true color");
932 conf.true_color = false;
937 // In TightVNC mode, ServerInit message is extended
938 var numServerMessages = ws.rQshift16();
939 var numClientMessages = ws.rQshift16();
940 var numEncodings = ws.rQshift16();
941 ws.rQshift16(); // padding
942 //console.log("numServerMessages "+numServerMessages);
943 //console.log("numClientMessages "+numClientMessages);
944 //console.log("numEncodings "+numEncodings);
946 for (var i=0;i<numServerMessages;i++)
948 var srvMsg = ws.rQshiftStr(16);
949 //console.log("server message: "+srvMsg);
951 for (var i=0;i<numClientMessages;i++)
953 var clientMsg = ws.rQshiftStr(16);
954 //console.log("client message: "+clientMsg);
956 for (var i=0;i<numEncodings;i++)
958 var encoding = ws.rQshiftStr(16);
959 //console.log("encoding: "+encoding);
963 display.set_true_color(conf.true_color);
964 conf.onFBResize(that, fb_width, fb_height);
965 display.resize(fb_width, fb_height);
969 if (conf.true_color) {
977 response = pixelFormat();
978 response = response.concat(clientEncodings());
979 response = response.concat(fbUpdateRequests()); // initial fbu-request
980 timing.fbu_rt_start = (new Date()).getTime();
987 updateState('normal', "Connected (encrypted) to: " + fb_name);
989 updateState('normal', "Connected (unencrypted) to: " + fb_name);
993 //Util.Debug("<< init_msg");
997 /* Normal RFB/VNC server message handler */
998 normal_msg = function() {
999 //Util.Debug(">> normal_msg");
1001 var ret = true, msg_type, length, text,
1002 c, first_colour, num_colours, red, green, blue,
1005 if (FBU.rects > 0) {
1008 msg_type = ws.rQshift8();
1011 case 0: // FramebufferUpdate
1012 ret = framebufferUpdate(); // false means need more data
1014 // only allow one outstanding fbu-request at a time
1015 ws.send(fbUpdateRequests());
1018 case 1: // SetColourMapEntries
1019 Util.Debug("SetColourMapEntries");
1020 ws.rQshift8(); // Padding
1021 first_colour = ws.rQshift16(); // First colour
1022 num_colours = ws.rQshift16();
1023 if (ws.rQwait("SetColourMapEntries", num_colours*6, 6)) { return false; }
1025 for (c=0; c < num_colours; c+=1) {
1026 red = ws.rQshift16();
1027 //Util.Debug("red before: " + red);
1028 red = parseInt(red / 256, 10);
1029 //Util.Debug("red after: " + red);
1030 green = parseInt(ws.rQshift16() / 256, 10);
1031 blue = parseInt(ws.rQshift16() / 256, 10);
1032 display.set_colourMap([blue, green, red], first_colour + c);
1034 Util.Debug("colourMap: " + display.get_colourMap());
1035 Util.Info("Registered " + num_colours + " colourMap entries");
1036 //Util.Debug("colourMap: " + display.get_colourMap());
1042 case 3: // ServerCutText
1043 Util.Debug("ServerCutText");
1044 if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
1045 ws.rQshiftBytes(3); // Padding
1046 length = ws.rQshift32();
1047 if (ws.rQwait("ServerCutText", length, 8)) { return false; }
1049 text = ws.rQshiftStr(length);
1050 conf.clipboardReceive(that, text); // Obsolete
1051 conf.onClipboard(that, text);
1054 ws.rQshift8(); // Padding
1055 xvp_ver = ws.rQshift8();
1056 xvp_msg = ws.rQshift8();
1059 updateState(rfb_state, "Operation failed");
1062 rfb_xvp_ver = xvp_ver;
1063 Util.Info("XVP extensions enabled (version " + rfb_xvp_ver + ")");
1064 conf.onXvpInit(rfb_xvp_ver);
1067 fail("Disconnected: illegal server XVP message " + xvp_msg);
1072 fail("Disconnected: illegal server message type " + msg_type);
1073 Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
1076 //Util.Debug("<< normal_msg");
1080 framebufferUpdate = function() {
1081 var now, hdr, fbu_rt_diff, ret = true;
1083 if (FBU.rects === 0) {
1084 //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
1085 if (ws.rQwait("FBU header", 3)) {
1086 ws.rQunshift8(0); // FBU msg_type
1089 ws.rQshift8(); // padding
1090 FBU.rects = ws.rQshift16();
1091 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
1094 if (timing.fbu_rt_start > 0) {
1095 now = (new Date()).getTime();
1096 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
1100 while (FBU.rects > 0) {
1101 if (rfb_state !== "normal") {
1104 if (ws.rQwait("FBU", FBU.bytes)) { return false; }
1105 if (FBU.bytes === 0) {
1106 if (ws.rQwait("rect header", 12)) { return false; }
1107 /* New FramebufferUpdate */
1109 hdr
= ws
.rQshiftBytes(12);
1110 FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1111 FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1112 FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1113 FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1114 FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1115 (hdr
[10] << 8) + hdr
[11], 10);
1117 conf
.onFBUReceive(that
,
1118 {'x': FBU
.x
, 'y': FBU
.y
,
1119 'width': FBU
.width
, 'height': FBU
.height
,
1120 'encoding': FBU
.encoding
,
1121 'encodingName': encNames
[FBU
.encoding
]});
1123 if (encNames
[FBU
.encoding
]) {
1126 var msg = "FramebufferUpdate rects:" + FBU.rects;
1127 msg += " x: " + FBU.x + " y: " + FBU.y;
1128 msg += " width: " + FBU.width + " height: " + FBU.height;
1129 msg += " encoding:" + FBU.encoding;
1130 msg += "(" + encNames[FBU.encoding] + ")";
1131 msg += ", ws.rQlen(): " + ws.rQlen();
1135 fail("Disconnected: unsupported encoding " +
1141 timing
.last_fbu
= (new Date()).getTime();
1143 ret
= encHandlers
[FBU
.encoding
]();
1145 now
= (new Date()).getTime();
1146 timing
.cur_fbu
+= (now
- timing
.last_fbu
);
1149 encStats
[FBU
.encoding
][0] += 1;
1150 encStats
[FBU
.encoding
][1] += 1;
1151 timing
.pixels
+= FBU
.width
* FBU
.height
;
1154 if (timing
.pixels
>= (fb_width
* fb_height
)) {
1155 if (((FBU
.width
=== fb_width
) &&
1156 (FBU
.height
=== fb_height
)) ||
1157 (timing
.fbu_rt_start
> 0)) {
1158 timing
.full_fbu_total
+= timing
.cur_fbu
;
1159 timing
.full_fbu_cnt
+= 1;
1160 Util
.Info("Timing of full FBU, cur: " +
1161 timing
.cur_fbu
+ ", total: " +
1162 timing
.full_fbu_total
+ ", cnt: " +
1163 timing
.full_fbu_cnt
+ ", avg: " +
1164 (timing
.full_fbu_total
/
1165 timing
.full_fbu_cnt
));
1167 if (timing
.fbu_rt_start
> 0) {
1168 fbu_rt_diff
= now
- timing
.fbu_rt_start
;
1169 timing
.fbu_rt_total
+= fbu_rt_diff
;
1170 timing
.fbu_rt_cnt
+= 1;
1171 Util
.Info("full FBU round-trip, cur: " +
1172 fbu_rt_diff
+ ", total: " +
1173 timing
.fbu_rt_total
+ ", cnt: " +
1174 timing
.fbu_rt_cnt
+ ", avg: " +
1175 (timing
.fbu_rt_total
/
1176 timing
.fbu_rt_cnt
));
1177 timing
.fbu_rt_start
= 0;
1181 return ret
; // false ret means need more data
1185 conf
.onFBUComplete(that
,
1186 {'x': FBU
.x
, 'y': FBU
.y
,
1187 'width': FBU
.width
, 'height': FBU
.height
,
1188 'encoding': FBU
.encoding
,
1189 'encodingName': encNames
[FBU
.encoding
]});
1191 return true; // We finished this FBU
1195 // FramebufferUpdate encodings
1198 encHandlers
.RAW
= function display_raw() {
1199 //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
1201 var cur_y
, cur_height
;
1203 if (FBU
.lines
=== 0) {
1204 FBU
.lines
= FBU
.height
;
1206 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least a line
1207 if (ws
.rQwait("RAW", FBU
.bytes
)) { return false; }
1208 cur_y
= FBU
.y
+ (FBU
.height
- FBU
.lines
);
1209 cur_height
= Math
.min(FBU
.lines
,
1210 Math
.floor(ws
.rQlen()/(FBU
.width
* fb_Bpp
)));
1211 display
.blitImage(FBU
.x
, cur_y
, FBU
.width
, cur_height
,
1212 ws
.get_rQ(), ws
.get_rQi());
1213 ws
.rQshiftBytes(FBU
.width
* cur_height
* fb_Bpp
);
1214 FBU
.lines
-= cur_height
;
1216 if (FBU
.lines
> 0) {
1217 FBU
.bytes
= FBU
.width
* fb_Bpp
; // At least another line
1222 //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
1226 encHandlers
.COPYRECT
= function display_copy_rect() {
1227 //Util.Debug(">> display_copy_rect");
1232 if (ws
.rQwait("COPYRECT", 4)) { return false; }
1233 display
.renderQ_push({
1235 'old_x': ws
.rQshift16(),
1236 'old_y': ws
.rQshift16(),
1240 'height': FBU
.height
});
1246 encHandlers
.RRE
= function display_rre() {
1247 //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
1248 var color
, x
, y
, width
, height
, chunk
;
1250 if (FBU
.subrects
=== 0) {
1251 FBU
.bytes
= 4+fb_Bpp
;
1252 if (ws
.rQwait("RRE", 4+fb_Bpp
)) { return false; }
1253 FBU
.subrects
= ws
.rQshift32();
1254 color
= ws
.rQshiftBytes(fb_Bpp
); // Background
1255 display
.fillRect(FBU
.x
, FBU
.y
, FBU
.width
, FBU
.height
, color
);
1257 while ((FBU
.subrects
> 0) && (ws
.rQlen() >= (fb_Bpp
+ 8))) {
1258 color
= ws
.rQshiftBytes(fb_Bpp
);
1261 width
= ws
.rQshift16();
1262 height
= ws
.rQshift16();
1263 display
.fillRect(FBU
.x
+ x
, FBU
.y
+ y
, width
, height
, color
);
1266 //Util.Debug(" display_rre: rects: " + FBU.rects +
1267 // ", FBU.subrects: " + FBU.subrects);
1269 if (FBU
.subrects
> 0) {
1270 chunk
= Math
.min(rre_chunk_sz
, FBU
.subrects
);
1271 FBU
.bytes
= (fb_Bpp
+ 8) * chunk
;
1276 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1280 encHandlers
.HEXTILE
= function display_hextile() {
1281 //Util.Debug(">> display_hextile");
1282 var subencoding
, subrects
, color
, cur_tile
,
1283 tile_x
, x
, w
, tile_y
, y
, h
, xy
, s
, sx
, sy
, wh
, sw
, sh
,
1284 rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1286 if (FBU
.tiles
=== 0) {
1287 FBU
.tiles_x
= Math
.ceil(FBU
.width
/16);
1288 FBU
.tiles_y
= Math
.ceil(FBU
.height
/16);
1289 FBU
.total_tiles
= FBU
.tiles_x
* FBU
.tiles_y
;
1290 FBU
.tiles
= FBU
.total_tiles
;
1293 /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
1294 while (FBU
.tiles
> 0) {
1296 if (ws
.rQwait("HEXTILE subencoding", FBU
.bytes
)) { return false; }
1297 subencoding
= rQ
[rQi
]; // Peek
1298 if (subencoding
> 30) { // Raw
1299 fail("Disconnected: illegal hextile subencoding " + subencoding
);
1300 //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
1304 cur_tile
= FBU
.total_tiles
- FBU
.tiles
;
1305 tile_x
= cur_tile
% FBU
.tiles_x
;
1306 tile_y
= Math
.floor(cur_tile
/ FBU
.tiles_x
);
1307 x
= FBU
.x
+ tile_x
* 16;
1308 y
= FBU
.y
+ tile_y
* 16;
1309 w
= Math
.min(16, (FBU
.x
+ FBU
.width
) - x
);
1310 h
= Math
.min(16, (FBU
.y
+ FBU
.height
) - y
);
1312 /* Figure out how much we are expecting */
1313 if (subencoding
& 0x01) { // Raw
1314 //Util.Debug(" Raw subencoding");
1315 FBU
.bytes
+= w
* h
* fb_Bpp
;
1317 if (subencoding
& 0x02) { // Background
1318 FBU
.bytes
+= fb_Bpp
;
1320 if (subencoding
& 0x04) { // Foreground
1321 FBU
.bytes
+= fb_Bpp
;
1323 if (subencoding
& 0x08) { // AnySubrects
1324 FBU
.bytes
+= 1; // Since we aren't shifting it off
1325 if (ws
.rQwait("hextile subrects header", FBU
.bytes
)) { return false; }
1326 subrects
= rQ
[rQi
+ FBU
.bytes
-1]; // Peek
1327 if (subencoding
& 0x10) { // SubrectsColoured
1328 FBU
.bytes
+= subrects
* (fb_Bpp
+ 2);
1330 FBU
.bytes
+= subrects
* 2;
1336 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1337 " (" + tile_x + "," + tile_y + ")" +
1338 " [" + x + "," + y + "]@" + w + "x" + h +
1339 ", subenc:" + subencoding +
1340 "(last: " + FBU.lastsubencoding + "), subrects:" +
1342 ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
1343 " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
1344 " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
1346 if (ws
.rQwait("hextile", FBU
.bytes
)) { return false; }
1348 /* We know the encoding and have a whole tile */
1349 FBU
.subencoding
= rQ
[rQi
];
1351 if (FBU
.subencoding
=== 0) {
1352 if (FBU
.lastsubencoding
& 0x01) {
1353 /* Weird: ignore blanks after RAW */
1354 Util
.Debug(" Ignoring blank after RAW");
1356 display
.fillRect(x
, y
, w
, h
, FBU
.background
);
1358 } else if (FBU
.subencoding
& 0x01) { // Raw
1359 display
.blitImage(x
, y
, w
, h
, rQ
, rQi
);
1360 rQi
+= FBU
.bytes
- 1;
1362 if (FBU
.subencoding
& 0x02) { // Background
1363 FBU
.background
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1366 if (FBU
.subencoding
& 0x04) { // Foreground
1367 FBU
.foreground
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1371 display
.startTile(x
, y
, w
, h
, FBU
.background
);
1372 if (FBU
.subencoding
& 0x08) { // AnySubrects
1375 for (s
= 0; s
< subrects
; s
+= 1) {
1376 if (FBU
.subencoding
& 0x10) { // SubrectsColoured
1377 color
= rQ
.slice(rQi
, rQi
+ fb_Bpp
);
1380 color
= FBU
.foreground
;
1390 sh
= (wh
& 0x0f) + 1;
1392 display
.subTile(sx
, sy
, sw
, sh
, color
);
1395 display
.finishTile();
1398 FBU
.lastsubencoding
= FBU
.subencoding
;
1403 if (FBU
.tiles
=== 0) {
1407 //Util.Debug("<< display_hextile");
1412 // Get 'compact length' header and data size
1413 getTightCLength = function (arr
) {
1414 var header
= 1, data
= 0;
1415 data
+= arr
[0] & 0x7f;
1416 if (arr
[0] & 0x80) {
1418 data
+= (arr
[1] & 0x7f) << 7;
1419 if (arr
[1] & 0x80) {
1421 data
+= arr
[2] << 14;
1424 return [header
, data
];
1427 function display_tight(isTightPNG
) {
1428 //Util.Debug(">> display_tight");
1430 if (fb_depth
=== 1) {
1431 fail("Tight protocol handler only implements true color mode");
1434 var ctl
, cmode
, clength
, color
, img
, data
;
1435 var filterId
= -1, resetStreams
= 0, streamId
= -1;
1436 var rQ
= ws
.get_rQ(), rQi
= ws
.get_rQi();
1438 FBU
.bytes
= 1; // compression-control byte
1439 if (ws
.rQwait("TIGHT compression-control", FBU
.bytes
)) { return false; }
1441 var checksum = function(data
) {
1443 for (i
=0; i
<data
.length
;i
++) {
1445 if (sum
> 65536) sum
-= 65536;
1450 var decompress = function(data
) {
1451 for (var i
=0; i
<4; i
++) {
1452 if ((resetStreams
>> i
) & 1) {
1453 FBU
.zlibs
[i
].reset();
1454 Util
.Info("Reset zlib stream " + i
);
1457 var uncompressed
= FBU
.zlibs
[streamId
].uncompress(data
, 0);
1458 if (uncompressed
.status
!== 0) {
1459 Util
.Error("Invalid data in zlib stream");
1461 //Util.Warn("Decompressed " + data.length + " to " +
1462 // uncompressed.data.length + " checksums " +
1463 // checksum(data) + ":" + checksum(uncompressed.data));
1465 return uncompressed
.data
;
1468 var indexedToRGB = function (data
, numColors
, palette
, width
, height
) {
1469 // Convert indexed (palette based) image data to RGB
1470 // TODO: reduce number of calculations inside loop
1472 var x
, y
, b
, w
, w1
, dp
, sp
;
1473 if (numColors
=== 2) {
1474 w
= Math
.floor((width
+ 7) / 8);
1475 w1
= Math
.floor(width
/ 8);
1476 for (y
= 0; y
< height
; y
++) {
1477 for (x
= 0; x
< w1
; x
++) {
1478 for (b
= 7; b
>= 0; b
--) {
1479 dp
= (y
*width
+ x
*8 + 7-b
) * 3;
1480 sp
= (data
[y
*w
+ x
] >> b
& 1) * 3;
1481 dest
[dp
] = palette
[sp
];
1482 dest
[dp
+1] = palette
[sp
+1];
1483 dest
[dp
+2] = palette
[sp
+2];
1486 for (b
= 7; b
>= 8 - width
% 8; b
--) {
1487 dp
= (y
*width
+ x
*8 + 7-b
) * 3;
1488 sp
= (data
[y
*w
+ x
] >> b
& 1) * 3;
1489 dest
[dp
] = palette
[sp
];
1490 dest
[dp
+1] = palette
[sp
+1];
1491 dest
[dp
+2] = palette
[sp
+2];
1495 for (y
= 0; y
< height
; y
++) {
1496 for (x
= 0; x
< width
; x
++) {
1497 dp
= (y
*width
+ x
) * 3;
1498 sp
= data
[y
*width
+ x
] * 3;
1499 dest
[dp
] = palette
[sp
];
1500 dest
[dp
+1] = palette
[sp
+1];
1501 dest
[dp
+2] = palette
[sp
+2];
1507 var handlePalette = function() {
1508 var numColors
= rQ
[rQi
+ 2] + 1;
1509 var paletteSize
= numColors
* fb_depth
;
1510 FBU
.bytes
+= paletteSize
;
1511 if (ws
.rQwait("TIGHT palette " + cmode
, FBU
.bytes
)) { return false; }
1513 var bpp
= (numColors
<= 2) ? 1 : 8;
1514 var rowSize
= Math
.floor((FBU
.width
* bpp
+ 7) / 8);
1516 if (rowSize
* FBU
.height
< 12) {
1518 clength
= [0, rowSize
* FBU
.height
];
1520 clength
= getTightCLength(ws
.rQslice(3 + paletteSize
,
1521 3 + paletteSize
+ 3));
1523 FBU
.bytes
+= clength
[0] + clength
[1];
1524 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1526 // Shift ctl, filter id, num colors, palette entries, and clength off
1528 var palette
= ws
.rQshiftBytes(paletteSize
);
1529 ws
.rQshiftBytes(clength
[0]);
1532 data
= ws
.rQshiftBytes(clength
[1]);
1534 data
= decompress(ws
.rQshiftBytes(clength
[1]));
1537 // Convert indexed (palette based) image data to RGB
1538 var rgb
= indexedToRGB(data
, numColors
, palette
, FBU
.width
, FBU
.height
);
1540 // Add it to the render queue
1541 display
.renderQ_push({
1547 'height': FBU
.height
});
1551 var handleCopy = function() {
1553 var uncompressedSize
= FBU
.width
* FBU
.height
* fb_depth
;
1554 if (uncompressedSize
< 12) {
1556 clength
= [0, uncompressedSize
];
1558 clength
= getTightCLength(ws
.rQslice(1, 4));
1560 FBU
.bytes
= 1 + clength
[0] + clength
[1];
1561 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1563 // Shift ctl, clength off
1564 ws
.rQshiftBytes(1 + clength
[0]);
1567 data
= ws
.rQshiftBytes(clength
[1]);
1569 data
= decompress(ws
.rQshiftBytes(clength
[1]));
1572 display
.renderQ_push({
1578 'height': FBU
.height
});
1584 // Keep tight reset bits
1585 resetStreams
= ctl
& 0xF;
1587 // Figure out filter
1589 streamId
= ctl
& 0x3;
1591 if (ctl
=== 0x08) cmode
= "fill";
1592 else if (ctl
=== 0x09) cmode
= "jpeg";
1593 else if (ctl
=== 0x0A) cmode
= "png";
1594 else if (ctl
& 0x04) cmode
= "filter";
1595 else if (ctl
< 0x04) cmode
= "copy";
1596 else return fail("Illegal tight compression received, ctl: " + ctl
);
1598 if (isTightPNG
&& (cmode
=== "filter" || cmode
=== "copy")) {
1599 return fail("filter/copy received in tightPNG mode");
1603 // fill uses fb_depth because TPIXELs drop the padding byte
1604 case "fill": FBU
.bytes
+= fb_depth
; break; // TPIXEL
1605 case "jpeg": FBU
.bytes
+= 3; break; // max clength
1606 case "png": FBU
.bytes
+= 3; break; // max clength
1607 case "filter": FBU
.bytes
+= 2; break; // filter id + num colors if palette
1611 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1613 //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1614 //Util.Debug(" cmode: " + cmode);
1616 // Determine FBU.bytes
1619 ws
.rQshift8(); // shift off ctl
1620 color
= ws
.rQshiftBytes(fb_depth
);
1621 display
.renderQ_push({
1626 'height': FBU
.height
,
1627 'color': [color
[2], color
[1], color
[0]] });
1631 clength
= getTightCLength(ws
.rQslice(1, 4));
1632 FBU
.bytes
= 1 + clength
[0] + clength
[1]; // ctl + clength size + jpeg-data
1633 if (ws
.rQwait("TIGHT " + cmode
, FBU
.bytes
)) { return false; }
1635 // We have everything, render it
1636 //Util.Debug(" jpeg, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " +
1637 // clength[0] + ", clength[1]: " + clength[1]);
1638 ws
.rQshiftBytes(1 + clength
[0]); // shift off ctl + compact length
1640 img
.src
= "data:image/" + cmode
+
1641 extract_data_uri(ws
.rQshiftBytes(clength
[1]));
1642 display
.renderQ_push({
1650 filterId
= rQ
[rQi
+ 1];
1651 if (filterId
=== 1) {
1652 if (!handlePalette()) { return false; }
1654 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1655 // Filter 2, Gradient is valid but not used if jpeg is enabled
1656 throw("Unsupported tight subencoding received, filter: " + filterId
);
1660 if (!handleCopy()) { return false; }
1666 //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
1667 //Util.Debug("<< display_tight_png");
1671 extract_data_uri = function(arr
) {
1673 //for (i=0; i< arr.length; i += 1) {
1674 // stra.push(String.fromCharCode(arr[i]));
1676 //return "," + escape(stra.join(''));
1677 return ";base64," + Base64
.encode(arr
);
1680 encHandlers
.TIGHT = function () { return display_tight(false); };
1681 encHandlers
.TIGHT_PNG = function () { return display_tight(true); };
1683 encHandlers
.last_rect
= function last_rect() {
1684 //Util.Debug(">> last_rect");
1686 //Util.Debug("<< last_rect");
1690 encHandlers
.DesktopSize
= function set_desktopsize() {
1691 Util
.Debug(">> set_desktopsize");
1692 fb_width
= FBU
.width
;
1693 fb_height
= FBU
.height
;
1694 conf
.onFBResize(that
, fb_width
, fb_height
);
1695 display
.resize(fb_width
, fb_height
);
1696 timing
.fbu_rt_start
= (new Date()).getTime();
1701 Util
.Debug("<< set_desktopsize");
1705 encHandlers
.Cursor
= function set_cursor() {
1706 var x
, y
, w
, h
, pixelslength
, masklength
;
1707 Util
.Debug(">> set_cursor");
1708 x
= FBU
.x
; // hotspot-x
1709 y
= FBU
.y
; // hotspot-y
1713 pixelslength
= w
* h
* fb_Bpp
;
1714 masklength
= Math
.floor((w
+ 7) / 8) * h
;
1716 FBU
.bytes
= pixelslength
+ masklength
;
1717 if (ws
.rQwait("cursor encoding", FBU
.bytes
)) { return false; }
1719 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1721 display
.changeCursor(ws
.rQshiftBytes(pixelslength
),
1722 ws
.rQshiftBytes(masklength
),
1728 Util
.Debug("<< set_cursor");
1732 encHandlers
.JPEG_quality_lo
= function set_jpeg_quality() {
1733 Util
.Error("Server sent jpeg_quality pseudo-encoding");
1736 encHandlers
.compress_lo
= function set_compress_level() {
1737 Util
.Error("Server sent compress level pseudo-encoding");
1741 * Client message routines
1744 pixelFormat = function() {
1745 //Util.Debug(">> pixelFormat");
1747 arr
= [0]; // msg-type
1748 arr
.push8(0); // padding
1749 arr
.push8(0); // padding
1750 arr
.push8(0); // padding
1752 arr
.push8(fb_Bpp
* 8); // bits-per-pixel
1753 arr
.push8(fb_depth
* 8); // depth
1754 arr
.push8(0); // little-endian
1755 arr
.push8(conf
.true_color
? 1 : 0); // true-color
1757 arr
.push16(255); // red-max
1758 arr
.push16(255); // green-max
1759 arr
.push16(255); // blue-max
1760 arr
.push8(16); // red-shift
1761 arr
.push8(8); // green-shift
1762 arr
.push8(0); // blue-shift
1764 arr
.push8(0); // padding
1765 arr
.push8(0); // padding
1766 arr
.push8(0); // padding
1767 //Util.Debug("<< pixelFormat");
1771 clientEncodings = function() {
1772 //Util.Debug(">> clientEncodings");
1773 var arr
, i
, encList
= [];
1775 for (i
=0; i
<encodings
.length
; i
+= 1) {
1776 if ((encodings
[i
][0] === "Cursor") &&
1777 (! conf
.local_cursor
)) {
1778 Util
.Debug("Skipping Cursor pseudo-encoding");
1780 // TODO: remove this when we have tight+non-true-color
1781 } else if ((encodings
[i
][0] === "TIGHT") &&
1782 (! conf
.true_color
)) {
1783 Util
.Warn("Skipping tight, only support with true color");
1785 //Util.Debug("Adding encoding: " + encodings[i][0]);
1786 encList
.push(encodings
[i
][1]);
1790 arr
= [2]; // msg-type
1791 arr
.push8(0); // padding
1793 arr
.push16(encList
.length
); // encoding count
1794 for (i
=0; i
< encList
.length
; i
+= 1) {
1795 arr
.push32(encList
[i
]);
1797 //Util.Debug("<< clientEncodings: " + arr);
1801 fbUpdateRequest = function(incremental
, x
, y
, xw
, yw
) {
1802 //Util.Debug(">> fbUpdateRequest");
1803 if (typeof(x
) === "undefined") { x
= 0; }
1804 if (typeof(y
) === "undefined") { y
= 0; }
1805 if (typeof(xw
) === "undefined") { xw
= fb_width
; }
1806 if (typeof(yw
) === "undefined") { yw
= fb_height
; }
1808 arr
= [3]; // msg-type
1809 arr
.push8(incremental
);
1814 //Util.Debug("<< fbUpdateRequest");
1818 // Based on clean/dirty areas, generate requests to send
1819 fbUpdateRequests = function() {
1820 var cleanDirty
= display
.getCleanDirtyReset(),
1821 arr
= [], i
, cb
, db
;
1823 cb
= cleanDirty
.cleanBox
;
1824 if (cb
.w
> 0 && cb
.h
> 0) {
1825 // Request incremental for clean box
1826 arr
= arr
.concat(fbUpdateRequest(1, cb
.x
, cb
.y
, cb
.w
, cb
.h
));
1828 for (i
= 0; i
< cleanDirty
.dirtyBoxes
.length
; i
++) {
1829 db
= cleanDirty
.dirtyBoxes
[i
];
1830 // Force all (non-incremental for dirty box
1831 arr
= arr
.concat(fbUpdateRequest(0, db
.x
, db
.y
, db
.w
, db
.h
));
1838 keyEvent = function(keysym
, down
) {
1839 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1841 arr
= [4]; // msg-type
1845 //Util.Debug("<< keyEvent");
1849 pointerEvent = function(x
, y
) {
1850 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1851 // " , mask: " + mouse_buttonMask);
1853 arr
= [5]; // msg-type
1854 arr
.push8(mouse_buttonMask
);
1857 //Util.Debug("<< pointerEvent");
1861 clientCutText = function(text
) {
1862 //Util.Debug(">> clientCutText");
1864 arr
= [6]; // msg-type
1865 arr
.push8(0); // padding
1866 arr
.push8(0); // padding
1867 arr
.push8(0); // padding
1868 arr
.push32(text
.length
);
1870 for (i
=0; i
< n
; i
+=1) {
1871 arr
.push(text
.charCodeAt(i
));
1873 //Util.Debug("<< clientCutText:" + arr);
1880 // Public API interface functions
1883 that
.connect = function(host
, port
, password
, path
) {
1884 //Util.Debug(">> connect");
1888 rfb_password
= (password
!== undefined) ? password
: "";
1889 rfb_path
= (path
!== undefined) ? path
: "";
1891 if ((!rfb_host
) || (!rfb_port
)) {
1892 return fail("Must set host and port");
1895 updateState('connect');
1896 //Util.Debug("<< connect");
1900 that
.disconnect = function() {
1901 //Util.Debug(">> disconnect");
1902 updateState('disconnect', 'Disconnecting');
1903 //Util.Debug("<< disconnect");
1906 that
.sendPassword = function(passwd
) {
1907 rfb_password
= passwd
;
1908 rfb_state
= "Authentication";
1909 setTimeout(init_msg
, 1);
1912 that
.sendCtrlAltDel = function() {
1913 if (rfb_state
!== "normal" || conf
.view_only
) { return false; }
1914 Util
.Info("Sending Ctrl-Alt-Del");
1916 arr
= arr
.concat(keyEvent(0xFFE3, 1)); // Control
1917 arr
= arr
.concat(keyEvent(0xFFE9, 1)); // Alt
1918 arr
= arr
.concat(keyEvent(0xFFFF, 1)); // Delete
1919 arr
= arr
.concat(keyEvent(0xFFFF, 0)); // Delete
1920 arr
= arr
.concat(keyEvent(0xFFE9, 0)); // Alt
1921 arr
= arr
.concat(keyEvent(0xFFE3, 0)); // Control
1925 that
.xvpOp = function(ver
, op
) {
1926 if (rfb_xvp_ver
< ver
) { return false; }
1927 Util
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")")
1928 ws
.send_string("\xFA\x00" + String
.fromCharCode(ver
) + String
.fromCharCode(op
));
1932 that
.xvpShutdown = function() {
1933 return that
.xvpOp(1, 2);
1936 that
.xvpReboot = function() {
1937 return that
.xvpOp(1, 3);
1940 that
.xvpReset = function() {
1941 return that
.xvpOp(1, 4);
1944 // Send a key press. If 'down' is not specified then send a down key
1945 // followed by an up key.
1946 that
.sendKey = function(code
, down
) {
1947 if (rfb_state
!== "normal" || conf
.view_only
) { return false; }
1949 if (typeof down
!== 'undefined') {
1950 Util
.Info("Sending key code (" + (down
? "down" : "up") + "): " + code
);
1951 arr
= arr
.concat(keyEvent(code
, down
? 1 : 0));
1953 Util
.Info("Sending key code (down + up): " + code
);
1954 arr
= arr
.concat(keyEvent(code
, 1));
1955 arr
= arr
.concat(keyEvent(code
, 0));
1960 that
.clipboardPasteFrom = function(text
) {
1961 if (rfb_state
!== "normal") { return; }
1962 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1963 ws
.send(clientCutText(text
));
1964 //Util.Debug("<< clipboardPasteFrom");
1967 // Override internal functions for testing
1968 that
.testMode = function(override_send
, data_mode
) {
1970 that
.recv_message
= ws
.testMode(override_send
, data_mode
);
1972 checkEvents = function () { /* Stub Out */ };
1973 that
.connect = function(host
, port
, password
) {
1976 rfb_password
= password
;
1978 updateState('ProtocolVersion', "Starting VNC handshake");
1983 return constructor(); // Return the public API interface