]> git.proxmox.com Git - mirror_novnc.git/blob - include/rfb.js
include/rfb.js: show Flash version in console.
[mirror_novnc.git] / include / rfb.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 "use strict";
10 /*jslint white: false, browser: true, bitwise: false, plusplus: false */
11 /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */
12
13
14 function RFB(conf) {
15
16 conf = conf || {}; // Configuration
17 var that = {}, // Public API interface
18
19 // Pre-declare private functions used before definitions (jslint)
20 init_vars, updateState, init_msg, normal_msg, recv_message,
21 framebufferUpdate, print_stats,
22
23 pixelFormat, clientEncodings, fbUpdateRequest,
24 keyEvent, pointerEvent, clientCutText,
25
26 extract_data_uri, scan_tight_imgQ,
27
28 send_array, checkEvents, // Overridable for testing
29
30
31 //
32 // Private RFB namespace variables
33 //
34 rfb_host = '',
35 rfb_port = 5900,
36 rfb_password = '',
37
38 rfb_state = 'disconnected',
39 rfb_version = 0,
40 rfb_max_version= 3.8,
41 rfb_auth_scheme= '',
42
43
44 // In preference order
45 encodings = [
46 ['COPYRECT', 0x01 ],
47 ['TIGHT_PNG', -260 ],
48 ['HEXTILE', 0x05 ],
49 ['RRE', 0x02 ],
50 ['RAW', 0x00 ],
51 ['DesktopSize', -223 ],
52 ['Cursor', -239 ],
53
54 // Psuedo-encoding settings
55 ['JPEG_quality_lo', -32 ],
56 //['JPEG_quality_hi', -23 ],
57 ['compress_lo', -255 ]
58 //['compress_hi', -247 ]
59 ],
60
61 encHandlers = {},
62 encNames = {},
63 encStats = {}, // [rectCnt, rectCntTot]
64
65 ws = null, // Web Socket object
66 canvas = null, // Canvas object
67 sendTimer = null, // Send Queue check timer
68 connTimer = null, // connection timer
69 disconnTimer = null, // disconnection timer
70 msgTimer = null, // queued handle_message timer
71
72 // Receive and send queues
73 rQ = [], // Receive Queue
74 rQi = 0, // Receive Queue Index
75 rQmax = 100000, // Max size before compacting
76 sQ = [], // Send Queue
77
78 // Frame buffer update state
79 FBU = {
80 rects : 0,
81 subrects : 0, // RRE
82 lines : 0, // RAW
83 tiles : 0, // HEXTILE
84 bytes : 0,
85 x : 0,
86 y : 0,
87 width : 0,
88 height : 0,
89 encoding : 0,
90 subencoding : -1,
91 background : null,
92 imgQ : [] // TIGHT_PNG image queue
93 },
94
95 fb_Bpp = 4,
96 fb_depth = 3,
97 fb_width = 0,
98 fb_height = 0,
99 fb_name = "",
100
101 scan_imgQ_rate = 100,
102 last_req_time = 0,
103 rre_chunk_sz = 100,
104
105 timing = {
106 last_fbu : 0,
107 fbu_total : 0,
108 fbu_total_cnt : 0,
109 full_fbu_total : 0,
110 full_fbu_cnt : 0,
111
112 fbu_rt_start : 0,
113 fbu_rt_total : 0,
114 fbu_rt_cnt : 0
115 },
116
117 test_mode = false,
118
119 /* Mouse state */
120 mouse_buttonMask = 0,
121 mouse_arr = [];
122
123
124 //
125 // Configuration settings
126 //
127 function cdef(v, type, defval, desc) {
128 Util.conf_default(conf, that, v, type, defval, desc); }
129
130 cdef('target', 'str', null, 'VNC viewport rendering Canvas');
131 cdef('focusContainer', 'dom', document, 'Area that traps keyboard input');
132
133 cdef('encrypt', 'bool', false, 'Use TLS/SSL/wss encryption');
134 cdef('true_color', 'bool', true, 'Request true color pixel data');
135 cdef('local_cursor', 'bool', false, 'Request locally rendered cursor');
136 cdef('shared', 'bool', true, 'Request shared mode');
137
138 cdef('connectTimeout', 'int', 2, 'Time (s) to wait for connection');
139 cdef('disconnectTimeout', 'int', 3, 'Time (s) to wait for disconnection');
140 cdef('check_rate', 'int', 217, 'Timing (ms) of send/receive check');
141 cdef('fbu_req_rate', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests');
142
143 cdef('updateState',
144 'func', function() { Util.Debug("updateState stub"); },
145 'callback: state update');
146 cdef('clipboardReceive',
147 'func', function() { Util.Debug("clipboardReceive stub"); },
148 'callback: clipboard contents received');
149
150
151 // Override/add some specific getters/setters
152 that.set_local_cursor = function(cursor) {
153 if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
154 conf.local_cursor = false;
155 } else {
156 if (canvas.get_cursor_uri()) {
157 conf.local_cursor = true;
158 } else {
159 Util.Warn("Browser does not support local cursor");
160 }
161 }
162 };
163
164 that.get_canvas = function() {
165 return canvas;
166 };
167
168
169
170
171 //
172 // Private functions
173 //
174
175 //
176 // Receive Queue functions
177 //
178 function rQlen() {
179 return rQ.length - rQi;
180 }
181
182 function rQshift16() {
183 return (rQ[rQi++] << 8) +
184 (rQ[rQi++] );
185 }
186 function rQshift32() {
187 return (rQ[rQi++] << 24) +
188 (rQ[rQi++] << 16) +
189 (rQ[rQi++] << 8) +
190 (rQ[rQi++] );
191 }
192 function rQshiftStr(len) {
193 var arr = rQ.slice(rQi, rQi + len);
194 rQi += len;
195 return arr.map(function (num) {
196 return String.fromCharCode(num); } ).join('');
197
198 }
199 function rQshiftBytes(len) {
200 rQi += len;
201 return rQ.slice(rQi-len, rQi);
202 }
203
204 // Check to see if we must wait for 'num' bytes (default to FBU.bytes)
205 // to be available in the receive queue. Return true if we need to
206 // wait (and possibly print a debug message), otherwise false.
207 function rQwait(msg, num, goback) {
208 if (typeof num !== 'number') { num = FBU.bytes; }
209 var rQlen = rQ.length - rQi; // Skip rQlen() function call
210 if (rQlen < num) {
211 if (goback) {
212 if (rQi < goback) {
213 throw("rQwait cannot backup " + goback + " bytes");
214 }
215 rQi -= goback;
216 }
217 //Util.Debug(" waiting for " + (num-rQlen) +
218 // " " + msg + " byte(s)");
219 return true; // true means need more data
220 }
221 return false;
222 }
223
224
225 //
226 // Setup routines
227 //
228
229 // Create the public API interface and initialize
230 function constructor() {
231 var i, rmode;
232 Util.Debug(">> RFB.constructor");
233
234 // Create lookup tables based encoding number
235 for (i=0; i < encodings.length; i+=1) {
236 encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
237 encNames[encodings[i][1]] = encodings[i][0];
238 encStats[encodings[i][1]] = [0, 0];
239 }
240 // Initialize canvas
241 try {
242 canvas = new Canvas({'target': conf.target,
243 'focusContainer': conf.focusContainer});
244 } catch (exc) {
245 Util.Error("Canvas exception: " + exc);
246 updateState('fatal', "No working Canvas");
247 }
248 rmode = canvas.get_render_mode();
249
250 init_vars();
251
252 /* Check web-socket-js if no builtin WebSocket support */
253 if (VNC_native_ws) {
254 Util.Info("Using native WebSockets");
255 updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
256 } else {
257 Util.Warn("Using web-socket-js bridge. Flash version: " +
258 Util.Flash.version);
259 if ((! Util.Flash) ||
260 (Util.Flash.version < 9)) {
261 updateState('fatal', "WebSockets or Adobe Flash is required");
262 } else if (document.location.href.substr(0, 7) === "file://") {
263 updateState('fatal',
264 "'file://' URL is incompatible with Adobe Flash");
265 } else {
266 updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
267 }
268 }
269
270 Util.Debug("<< RFB.constructor");
271 return that; // Return the public API interface
272 }
273
274 function init_ws() {
275 Util.Debug(">> RFB.init_ws");
276
277 var uri = "";
278 if (conf.encrypt) {
279 uri = "wss://";
280 } else {
281 uri = "ws://";
282 }
283 uri += rfb_host + ":" + rfb_port + "/";
284 Util.Info("connecting to " + uri);
285 ws = new WebSocket(uri);
286
287 ws.onmessage = recv_message;
288 ws.onopen = function(e) {
289 Util.Debug(">> WebSocket.onopen");
290 if (rfb_state === "connect") {
291 updateState('ProtocolVersion', "Starting VNC handshake");
292 } else {
293 fail("Got unexpected WebSockets connection");
294 }
295 Util.Debug("<< WebSocket.onopen");
296 };
297 ws.onclose = function(e) {
298 Util.Debug(">> WebSocket.onclose");
299 if (rfb_state === 'disconnect') {
300 updateState('disconnected', 'VNC disconnected');
301 } else if (rfb_state === 'ProtocolVersion') {
302 fail('Failed to connect to server');
303 } else if (rfb_state in {'failed':1, 'disconnected':1}) {
304 Util.Error("Received onclose while disconnected");
305 } else {
306 fail('Server disconnected');
307 }
308 Util.Debug("<< WebSocket.onclose");
309 };
310 ws.onerror = function(e) {
311 Util.Debug(">> WebSocket.onerror");
312 fail("WebSocket error");
313 Util.Debug("<< WebSocket.onerror");
314 };
315
316 Util.Debug("<< RFB.init_ws");
317 }
318
319 init_vars = function() {
320 /* Reset state */
321 rQ = [];
322 rQi = 0;
323 sQ = [];
324 FBU.rects = 0;
325 FBU.subrects = 0; // RRE and HEXTILE
326 FBU.lines = 0; // RAW
327 FBU.tiles = 0; // HEXTILE
328 FBU.imgQ = []; // TIGHT_PNG image queue
329 mouse_buttonMask = 0;
330 mouse_arr = [];
331
332 // Clear the per connection encoding stats
333 for (var i=0; i < encodings.length; i+=1) {
334 encStats[encodings[i][1]][0] = 0;
335 }
336 };
337
338 // Print statistics
339 print_stats = function() {
340 var i, encName, s;
341 Util.Info("Encoding stats for this connection:");
342 for (i=0; i < encodings.length; i+=1) {
343 s = encStats[encodings[i][1]];
344 if ((s[0] + s[1]) > 0) {
345 Util.Info(" " + encodings[i][0] + ": " +
346 s[0] + " rects");
347 }
348 }
349 Util.Info("Encoding stats since page load:");
350 for (i=0; i < encodings.length; i+=1) {
351 s = encStats[encodings[i][1]];
352 if ((s[0] + s[1]) > 0) {
353 Util.Info(" " + encodings[i][0] + ": "
354 + s[1] + " rects");
355 }
356 }
357 };
358
359 //
360 // Utility routines
361 //
362
363
364 /*
365 * Running states:
366 * disconnected - idle state
367 * normal - connected
368 *
369 * Page states:
370 * loaded - page load, equivalent to disconnected
371 * connect - starting initialization
372 * disconnect - starting disconnect
373 * failed - abnormal transition to disconnected
374 * fatal - failed to load page, or fatal error
375 *
376 * VNC initialization states:
377 * ProtocolVersion
378 * Security
379 * Authentication
380 * password - waiting for password, not part of RFB
381 * SecurityResult
382 * ClientInitialization - not triggered by server message
383 * ServerInitialization
384 */
385 updateState = function(state, statusMsg) {
386 var func, cmsg, oldstate = rfb_state;
387
388 if (state === oldstate) {
389 /* Already here, ignore */
390 Util.Debug("Already in state '" + state + "', ignoring.");
391 return;
392 }
393
394 /*
395 * These are disconnected states. A previous connect may
396 * asynchronously cause a connection so make sure we are closed.
397 */
398 if (state in {'disconnected':1, 'loaded':1, 'connect':1,
399 'disconnect':1, 'failed':1, 'fatal':1}) {
400 if (sendTimer) {
401 clearInterval(sendTimer);
402 sendTimer = null;
403 }
404
405 if (msgTimer) {
406 clearInterval(msgTimer);
407 msgTimer = null;
408 }
409
410 if (canvas && canvas.getContext()) {
411 canvas.stop();
412 if (Util.get_logging() !== 'debug') {
413 canvas.clear();
414 }
415 }
416
417 if (ws) {
418 if ((ws.readyState === WebSocket.OPEN) ||
419 (ws.readyState === WebSocket.CONNECTING)) {
420 Util.Info("Closing WebSocket connection");
421 ws.close();
422 }
423 ws.onmessage = function (e) { return; };
424 }
425 }
426
427 if (oldstate === 'fatal') {
428 Util.Error("Fatal error, cannot continue");
429 }
430
431 if ((state === 'failed') || (state === 'fatal')) {
432 func = Util.Error;
433 } else {
434 func = Util.Warn;
435 }
436
437 if ((oldstate === 'failed') && (state === 'disconnected')) {
438 // Do disconnect action, but stay in failed state.
439 rfb_state = 'failed';
440 } else {
441 rfb_state = state;
442 }
443
444 cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
445 func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
446
447 if (connTimer && (rfb_state !== 'connect')) {
448 Util.Debug("Clearing connect timer");
449 clearInterval(connTimer);
450 connTimer = null;
451 }
452
453 if (disconnTimer && (rfb_state !== 'disconnect')) {
454 Util.Debug("Clearing disconnect timer");
455 clearInterval(disconnTimer);
456 disconnTimer = null;
457 }
458
459 switch (state) {
460 case 'normal':
461 if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
462 Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
463 }
464
465 break;
466
467
468 case 'connect':
469
470 connTimer = setTimeout(function () {
471 fail("Connect timeout");
472 }, conf.connectTimeout * 1000);
473
474 init_vars();
475 init_ws();
476
477 // WebSocket.onopen transitions to 'ProtocolVersion'
478 break;
479
480
481 case 'disconnect':
482
483 if (! test_mode) {
484 disconnTimer = setTimeout(function () {
485 fail("Disconnect timeout");
486 }, conf.disconnectTimeout * 1000);
487 }
488
489 print_stats();
490
491 // WebSocket.onclose transitions to 'disconnected'
492 break;
493
494
495 case 'failed':
496 if (oldstate === 'disconnected') {
497 Util.Error("Invalid transition from 'disconnected' to 'failed'");
498 }
499 if (oldstate === 'normal') {
500 Util.Error("Error while connected.");
501 }
502 if (oldstate === 'init') {
503 Util.Error("Error while initializing.");
504 }
505
506 // Make sure we transition to disconnected
507 setTimeout(function() { updateState('disconnected'); }, 50);
508
509 break;
510
511
512 default:
513 // No state change action to take
514
515 }
516
517 if ((oldstate === 'failed') && (state === 'disconnected')) {
518 // Leave the failed message
519 conf.updateState(that, state, oldstate);
520 } else {
521 conf.updateState(that, state, oldstate, statusMsg);
522 }
523 };
524 function fail(msg) {
525 updateState('failed', msg);
526 return false;
527 }
528
529 function encode_message() {
530 /* base64 encode */
531 return Base64.encode(sQ);
532 }
533
534 function decode_message(data) {
535 //Util.Debug(">> decode_message: " + data);
536 /* base64 decode */
537 rQ = rQ.concat(Base64.decode(data, 0));
538 //Util.Debug(">> decode_message, rQ: " + rQ);
539 }
540
541 function handle_message() {
542 //Util.Debug("rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
543 if (rQlen() === 0) {
544 Util.Warn("handle_message called on empty receive queue");
545 return;
546 }
547 switch (rfb_state) {
548 case 'disconnected':
549 case 'failed':
550 Util.Error("Got data while disconnected");
551 break;
552 case 'normal':
553 if (normal_msg() && rQlen() > 0) {
554 // true means we can continue processing
555 // Give other events a chance to run
556 if (msgTimer === null) {
557 Util.Debug("More data to process, creating timer");
558 msgTimer = setTimeout(function () {
559 msgTimer = null;
560 handle_message();
561 }, 10);
562 } else {
563 Util.Debug("More data to process, existing timer");
564 }
565 }
566 // Compact the queue
567 if (rQ.length > rQmax) {
568 //Util.Debug("Compacting receive queue");
569 rQ = rQ.slice(rQi);
570 rQi = 0;
571 }
572 break;
573 default:
574 init_msg();
575 break;
576 }
577 }
578
579 recv_message = function(e) {
580 //Util.Debug(">> recv_message: " + e.data.length);
581
582 try {
583 decode_message(e.data);
584 if (rQlen() > 0) {
585 handle_message();
586 } else {
587 Util.Debug("Ignoring empty message");
588 }
589 } catch (exc) {
590 if (typeof exc.stack !== 'undefined') {
591 Util.Warn("recv_message, caught exception: " + exc.stack);
592 } else if (typeof exc.description !== 'undefined') {
593 Util.Warn("recv_message, caught exception: " + exc.description);
594 } else {
595 Util.Warn("recv_message, caught exception:" + exc);
596 }
597 if (typeof exc.name !== 'undefined') {
598 fail(exc.name + ": " + exc.message);
599 } else {
600 fail(exc);
601 }
602 }
603 //Util.Debug("<< recv_message");
604 };
605
606 // overridable for testing
607 send_array = function(arr) {
608 //Util.Debug(">> send_array: " + arr);
609 sQ = sQ.concat(arr);
610 if (ws.bufferedAmount === 0) {
611 //Util.Debug("arr: " + arr);
612 //Util.Debug("sQ: " + sQ);
613 ws.send(encode_message(sQ));
614 sQ = [];
615 } else {
616 Util.Debug("Delaying send");
617 }
618 };
619
620 function send_string(str) {
621 //Util.Debug(">> send_string: " + str);
622 send_array(str.split('').map(
623 function (chr) { return chr.charCodeAt(0); } ) );
624 }
625
626 function genDES(password, challenge) {
627 var i, passwd = [], des;
628 for (i=0; i < password.length; i += 1) {
629 passwd.push(password.charCodeAt(i));
630 }
631 return (new DES(passwd)).encrypt(challenge);
632 }
633
634 function flushClient() {
635 if (mouse_arr.length > 0) {
636 //send_array(mouse_arr.concat(fbUpdateRequest(1)));
637 send_array(mouse_arr);
638 setTimeout(function() {
639 send_array(fbUpdateRequest(1));
640 }, 50);
641
642 mouse_arr = [];
643 return true;
644 } else {
645 return false;
646 }
647 }
648
649 // overridable for testing
650 checkEvents = function() {
651 var now;
652 if (rfb_state === 'normal') {
653 if (! flushClient()) {
654 now = new Date().getTime();
655 if (now > last_req_time + conf.fbu_req_rate) {
656 last_req_time = now;
657 send_array(fbUpdateRequest(1));
658 }
659 }
660 }
661 setTimeout(checkEvents, conf.check_rate);
662 };
663
664 function keyPress(keysym, down) {
665 var arr;
666 arr = keyEvent(keysym, down);
667 arr = arr.concat(fbUpdateRequest(1));
668 send_array(arr);
669 }
670
671 function mouseButton(x, y, down, bmask) {
672 if (down) {
673 mouse_buttonMask |= bmask;
674 } else {
675 mouse_buttonMask ^= bmask;
676 }
677 mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
678 flushClient();
679 }
680
681 function mouseMove(x, y) {
682 //Util.Debug('>> mouseMove ' + x + "," + y);
683 mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
684 }
685
686
687 //
688 // Server message handlers
689 //
690
691 // RFB/VNC initialisation message handler
692 init_msg = function() {
693 //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
694
695 var strlen, reason, length, sversion, cversion,
696 i, types, num_types, challenge, response, bpp, depth,
697 big_endian, true_color, name_length;
698
699 //Util.Debug("rQ (" + rQlen() + ") " + rQ);
700 switch (rfb_state) {
701
702 case 'ProtocolVersion' :
703 if (rQlen() < 12) {
704 return fail("Incomplete protocol version");
705 }
706 sversion = rQshiftStr(12).substr(4,7);
707 Util.Info("Server ProtocolVersion: " + sversion);
708 switch (sversion) {
709 case "003.003": rfb_version = 3.3; break;
710 case "003.006": rfb_version = 3.3; break; // UltraVNC
711 case "003.007": rfb_version = 3.7; break;
712 case "003.008": rfb_version = 3.8; break;
713 default:
714 return fail("Invalid server version " + sversion);
715 }
716 if (rfb_version > rfb_max_version) {
717 rfb_version = rfb_max_version;
718 }
719
720 if (! test_mode) {
721 sendTimer = setInterval(function() {
722 // Send updates either at a rate of one update
723 // every 50ms, or whatever slower rate the network
724 // can handle.
725 if (ws.bufferedAmount === 0) {
726 if (sQ) {
727 ws.send(encode_message(sQ));
728 sQ = [];
729 }
730 } else {
731 Util.Debug("Delaying send");
732 }
733 }, 50);
734 }
735
736 cversion = "00" + parseInt(rfb_version,10) +
737 ".00" + ((rfb_version * 10) % 10);
738 send_string("RFB " + cversion + "\n");
739 updateState('Security', "Sent ProtocolVersion: " + cversion);
740 break;
741
742 case 'Security' :
743 if (rfb_version >= 3.7) {
744 // Server sends supported list, client decides
745 num_types = rQ[rQi++];
746 if (rQwait("security type", num_types, 1)) { return false; }
747 if (num_types === 0) {
748 strlen = rQshift32();
749 reason = rQshiftStr(strlen);
750 return fail("Security failure: " + reason);
751 }
752 rfb_auth_scheme = 0;
753 types = rQshiftBytes(num_types);
754 Util.Debug("Server security types: " + types);
755 for (i=0; i < types.length; i+=1) {
756 if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
757 rfb_auth_scheme = types[i];
758 }
759 }
760 if (rfb_auth_scheme === 0) {
761 return fail("Unsupported security types: " + types);
762 }
763
764 send_array([rfb_auth_scheme]);
765 } else {
766 // Server decides
767 if (rQwait("security scheme", 4)) { return false; }
768 rfb_auth_scheme = rQshift32();
769 }
770 updateState('Authentication',
771 "Authenticating using scheme: " + rfb_auth_scheme);
772 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
773 break;
774
775 // Triggered by fallthough, not by server message
776 case 'Authentication' :
777 //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
778 switch (rfb_auth_scheme) {
779 case 0: // connection failed
780 if (rQwait("auth reason", 4)) { return false; }
781 strlen = rQshift32();
782 reason = rQshiftStr(strlen);
783 return fail("Auth failure: " + reason);
784 case 1: // no authentication
785 if (rfb_version >= 3.8) {
786 updateState('SecurityResult');
787 return;
788 } else {
789 // Fall through to ClientInitialisation
790 }
791 break;
792 case 2: // VNC authentication
793 if (rfb_password.length === 0) {
794 updateState('password', "Password Required");
795 return;
796 }
797 if (rQwait("auth challenge", 16)) { return false; }
798 challenge = rQshiftBytes(16);
799 //Util.Debug("Password: " + rfb_password);
800 //Util.Debug("Challenge: " + challenge +
801 // " (" + challenge.length + ")");
802 response = genDES(rfb_password, challenge);
803 //Util.Debug("Response: " + response +
804 // " (" + response.length + ")");
805
806 //Util.Debug("Sending DES encrypted auth response");
807 send_array(response);
808 updateState('SecurityResult');
809 return;
810 default:
811 fail("Unsupported auth scheme: " + rfb_auth_scheme);
812 return;
813 }
814 updateState('ClientInitialisation', "No auth required");
815 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
816 break;
817
818 case 'SecurityResult' :
819 if (rQwait("VNC auth response ", 4)) { return false; }
820 switch (rQshift32()) {
821 case 0: // OK
822 // Fall through to ClientInitialisation
823 break;
824 case 1: // failed
825 if (rfb_version >= 3.8) {
826 length = rQshift32();
827 if (rQwait("SecurityResult reason", length, 8)) {
828 return false;
829 }
830 reason = rQshiftStr(length);
831 fail(reason);
832 } else {
833 fail("Authentication failed");
834 }
835 return;
836 case 2: // too-many
837 return fail("Too many auth attempts");
838 }
839 updateState('ClientInitialisation', "Authentication OK");
840 init_msg(); // Recursive fallthrough (workaround JSLint complaint)
841 break;
842
843 // Triggered by fallthough, not by server message
844 case 'ClientInitialisation' :
845 send_array([conf.shared ? 1 : 0]); // ClientInitialisation
846 updateState('ServerInitialisation', "Authentication OK");
847 break;
848
849 case 'ServerInitialisation' :
850 if (rQwait("server initialization", 24)) { return false; }
851
852 /* Screen size */
853 fb_width = rQshift16();
854 fb_height = rQshift16();
855
856 /* PIXEL_FORMAT */
857 bpp = rQ[rQi++];
858 depth = rQ[rQi++];
859 big_endian = rQ[rQi++];
860 true_color = rQ[rQi++];
861
862 Util.Info("Screen: " + fb_width + "x" + fb_height +
863 ", bpp: " + bpp + ", depth: " + depth +
864 ", big_endian: " + big_endian +
865 ", true_color: " + true_color);
866
867 /* Connection name/title */
868 rQshiftStr(12);
869 name_length = rQshift32();
870 fb_name = rQshiftStr(name_length);
871
872 canvas.resize(fb_width, fb_height, conf.true_color);
873 canvas.start(keyPress, mouseButton, mouseMove);
874
875 if (conf.true_color) {
876 fb_Bpp = 4;
877 fb_depth = 3;
878 } else {
879 fb_Bpp = 1;
880 fb_depth = 1;
881 }
882
883 response = pixelFormat();
884 response = response.concat(clientEncodings());
885 response = response.concat(fbUpdateRequest(0));
886 timing.fbu_rt_start = (new Date()).getTime();
887 send_array(response);
888
889 /* Start pushing/polling */
890 setTimeout(checkEvents, conf.check_rate);
891 setTimeout(scan_tight_imgQ, scan_imgQ_rate);
892
893 if (conf.encrypt) {
894 updateState('normal', "Connected (encrypted) to: " + fb_name);
895 } else {
896 updateState('normal', "Connected (unencrypted) to: " + fb_name);
897 }
898 break;
899 }
900 //Util.Debug("<< init_msg");
901 };
902
903
904 /* Normal RFB/VNC server message handler */
905 normal_msg = function() {
906 //Util.Debug(">> normal_msg");
907
908 var ret = true, msg_type, length,
909 c, first_colour, num_colours, red, green, blue;
910
911 if (FBU.rects > 0) {
912 msg_type = 0;
913 } else {
914 msg_type = rQ[rQi++];
915 }
916 switch (msg_type) {
917 case 0: // FramebufferUpdate
918 ret = framebufferUpdate(); // false means need more data
919 break;
920 case 1: // SetColourMapEntries
921 Util.Debug("SetColourMapEntries");
922 rQi++; // Padding
923 first_colour = rQshift16(); // First colour
924 num_colours = rQshift16();
925 for (c=0; c < num_colours; c+=1) {
926 red = rQshift16();
927 //Util.Debug("red before: " + red);
928 red = parseInt(red / 256, 10);
929 //Util.Debug("red after: " + red);
930 green = parseInt(rQshift16() / 256, 10);
931 blue = parseInt(rQshift16() / 256, 10);
932 canvas.set_colourMap([red, green, blue], first_colour + c);
933 }
934 Util.Info("Registered " + num_colours + " colourMap entries");
935 //Util.Debug("colourMap: " + canvas.get_colourMap());
936 break;
937 case 2: // Bell
938 Util.Warn("Bell (unsupported)");
939 break;
940 case 3: // ServerCutText
941 Util.Debug("ServerCutText");
942 if (rQwait("ServerCutText header", 7, 1)) { return false; }
943 rQshiftBytes(3); // Padding
944 length = rQshift32();
945 if (rQwait("ServerCutText", length, 8)) { return false; }
946
947 conf.clipboardReceive(that, rQshiftStr(length));
948 break;
949 default:
950 fail("Disconnected: illegal server message type " + msg_type);
951 Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
952 break;
953 }
954 //Util.Debug("<< normal_msg");
955 return ret;
956 };
957
958 framebufferUpdate = function() {
959 var now, hdr, fbu_rt_diff, ret = true;
960
961 if (FBU.rects === 0) {
962 //Util.Debug("New FBU: rQ.slice(0,20): " + rQ.slice(0,20));
963 if (rQwait("FBU header", 3)) {
964 if (rQi === 0) {
965 rQ.unshift(0); // FBU msg_type
966 } else {
967 rQi -= 1;
968 }
969 return false;
970 }
971 rQi++;
972 FBU.rects = rQshift16();
973 //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
974 FBU.bytes = 0;
975 timing.cur_fbu = 0;
976 if (timing.fbu_rt_start > 0) {
977 now = (new Date()).getTime();
978 Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
979 }
980 }
981
982 while (FBU.rects > 0) {
983 if (rfb_state !== "normal") {
984 return false;
985 }
986 if (rQwait("FBU")) { return false; }
987 if (FBU.bytes === 0) {
988 if (rQwait("rect header", 12)) { return false; }
989 /* New FramebufferUpdate */
990
991 hdr = rQshiftBytes(12);
992 FBU.x = (hdr[0] << 8) + hdr[1];
993 FBU.y = (hdr[2] << 8) + hdr[3];
994 FBU.width = (hdr[4] << 8) + hdr[5];
995 FBU.height = (hdr[6] << 8) + hdr[7];
996 FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
997 (hdr[10] << 8) + hdr[11], 10);
998
999 if (encNames[FBU.encoding]) {
1000 // Debug:
1001 /*
1002 var msg = "FramebufferUpdate rects:" + FBU.rects;
1003 msg += " x: " + FBU.x + " y: " + FBU.y;
1004 msg += " width: " + FBU.width + " height: " + FBU.height;
1005 msg += " encoding:" + FBU.encoding;
1006 msg += "(" + encNames[FBU.encoding] + ")";
1007 msg += ", rQlen(): " + rQlen();
1008 Util.Debug(msg);
1009 */
1010 } else {
1011 fail("Disconnected: unsupported encoding " +
1012 FBU.encoding);
1013 return false;
1014 }
1015 }
1016
1017 timing.last_fbu = (new Date()).getTime();
1018
1019 ret = encHandlers[FBU.encoding]();
1020
1021 now = (new Date()).getTime();
1022 timing.cur_fbu += (now - timing.last_fbu);
1023
1024 if (ret) {
1025 encStats[FBU.encoding][0] += 1;
1026 encStats[FBU.encoding][1] += 1;
1027 }
1028
1029 if (FBU.rects === 0) {
1030 if (((FBU.width === fb_width) &&
1031 (FBU.height === fb_height)) ||
1032 (timing.fbu_rt_start > 0)) {
1033 timing.full_fbu_total += timing.cur_fbu;
1034 timing.full_fbu_cnt += 1;
1035 Util.Info("Timing of full FBU, cur: " +
1036 timing.cur_fbu + ", total: " +
1037 timing.full_fbu_total + ", cnt: " +
1038 timing.full_fbu_cnt + ", avg: " +
1039 (timing.full_fbu_total /
1040 timing.full_fbu_cnt));
1041 }
1042 if (timing.fbu_rt_start > 0) {
1043 fbu_rt_diff = now - timing.fbu_rt_start;
1044 timing.fbu_rt_total += fbu_rt_diff;
1045 timing.fbu_rt_cnt += 1;
1046 Util.Info("full FBU round-trip, cur: " +
1047 fbu_rt_diff + ", total: " +
1048 timing.fbu_rt_total + ", cnt: " +
1049 timing.fbu_rt_cnt + ", avg: " +
1050 (timing.fbu_rt_total /
1051 timing.fbu_rt_cnt));
1052 timing.fbu_rt_start = 0;
1053 }
1054 }
1055 if (! ret) {
1056 return ret; // false ret means need more data
1057 }
1058 }
1059 return true; // We finished this FBU
1060 };
1061
1062 //
1063 // FramebufferUpdate encodings
1064 //
1065
1066 encHandlers.RAW = function display_raw() {
1067 //Util.Debug(">> display_raw (" + rQlen() + " bytes)");
1068
1069 var cur_y, cur_height;
1070
1071 if (FBU.lines === 0) {
1072 FBU.lines = FBU.height;
1073 }
1074 FBU.bytes = FBU.width * fb_Bpp; // At least a line
1075 if (rQwait("RAW")) { return false; }
1076 cur_y = FBU.y + (FBU.height - FBU.lines);
1077 cur_height = Math.min(FBU.lines,
1078 Math.floor(rQlen()/(FBU.width * fb_Bpp)));
1079 canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, rQ, rQi);
1080 rQshiftBytes(FBU.width * cur_height * fb_Bpp);
1081 FBU.lines -= cur_height;
1082
1083 if (FBU.lines > 0) {
1084 FBU.bytes = FBU.width * fb_Bpp; // At least another line
1085 } else {
1086 FBU.rects -= 1;
1087 FBU.bytes = 0;
1088 }
1089 //Util.Debug("<< display_raw (" + rQlen() + " bytes)");
1090 return true;
1091 };
1092
1093 encHandlers.COPYRECT = function display_copy_rect() {
1094 //Util.Debug(">> display_copy_rect");
1095
1096 var old_x, old_y;
1097
1098 if (rQwait("COPYRECT", 4)) { return false; }
1099 old_x = rQshift16();
1100 old_y = rQshift16();
1101 canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
1102 FBU.rects -= 1;
1103 FBU.bytes = 0;
1104 return true;
1105 };
1106
1107 encHandlers.RRE = function display_rre() {
1108 //Util.Debug(">> display_rre (" + rQlen() + " bytes)");
1109 var color, x, y, width, height, chunk;
1110
1111 if (FBU.subrects === 0) {
1112 if (rQwait("RRE", 4+fb_Bpp)) { return false; }
1113 FBU.subrects = rQshift32();
1114 color = rQshiftBytes(fb_Bpp); // Background
1115 canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
1116 }
1117 while ((FBU.subrects > 0) && (rQlen() >= (fb_Bpp + 8))) {
1118 color = rQshiftBytes(fb_Bpp);
1119 x = rQshift16();
1120 y = rQshift16();
1121 width = rQshift16();
1122 height = rQshift16();
1123 canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color);
1124 FBU.subrects -= 1;
1125 }
1126 //Util.Debug(" display_rre: rects: " + FBU.rects +
1127 // ", FBU.subrects: " + FBU.subrects);
1128
1129 if (FBU.subrects > 0) {
1130 chunk = Math.min(rre_chunk_sz, FBU.subrects);
1131 FBU.bytes = (fb_Bpp + 8) * chunk;
1132 } else {
1133 FBU.rects -= 1;
1134 FBU.bytes = 0;
1135 }
1136 //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
1137 return true;
1138 };
1139
1140 encHandlers.HEXTILE = function display_hextile() {
1141 //Util.Debug(">> display_hextile");
1142 var subencoding, subrects, tile, color, cur_tile,
1143 tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh;
1144
1145 if (FBU.tiles === 0) {
1146 FBU.tiles_x = Math.ceil(FBU.width/16);
1147 FBU.tiles_y = Math.ceil(FBU.height/16);
1148 FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
1149 FBU.tiles = FBU.total_tiles;
1150 }
1151
1152 /* FBU.bytes comes in as 1, rQlen() at least 1 */
1153 while (FBU.tiles > 0) {
1154 FBU.bytes = 1;
1155 if (rQwait("HEXTILE subencoding")) { return false; }
1156 //Util.Debug(" 2 rQ length: " + rQlen() + " rQ[rQi]: " + rQ[rQi] + " rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + ", FBU.rects: " + FBU.rects + ", FBU.tiles: " + FBU.tiles);
1157 subencoding = rQ[rQi]; // Peek
1158 if (subencoding > 30) { // Raw
1159 fail("Disconnected: illegal hextile subencoding " + subencoding);
1160 //Util.Debug("rQ.slice(0,30):" + rQ.slice(0,30));
1161 return false;
1162 }
1163 subrects = 0;
1164 cur_tile = FBU.total_tiles - FBU.tiles;
1165 tile_x = cur_tile % FBU.tiles_x;
1166 tile_y = Math.floor(cur_tile / FBU.tiles_x);
1167 x = FBU.x + tile_x * 16;
1168 y = FBU.y + tile_y * 16;
1169 w = Math.min(16, (FBU.x + FBU.width) - x);
1170 h = Math.min(16, (FBU.y + FBU.height) - y);
1171
1172 /* Figure out how much we are expecting */
1173 if (subencoding & 0x01) { // Raw
1174 //Util.Debug(" Raw subencoding");
1175 FBU.bytes += w * h * fb_Bpp;
1176 } else {
1177 if (subencoding & 0x02) { // Background
1178 FBU.bytes += fb_Bpp;
1179 }
1180 if (subencoding & 0x04) { // Foreground
1181 FBU.bytes += fb_Bpp;
1182 }
1183 if (subencoding & 0x08) { // AnySubrects
1184 FBU.bytes += 1; // Since we aren't shifting it off
1185 if (rQwait("hextile subrects header")) { return false; }
1186 subrects = rQ[rQi + FBU.bytes-1]; // Peek
1187 if (subencoding & 0x10) { // SubrectsColoured
1188 FBU.bytes += subrects * (fb_Bpp + 2);
1189 } else {
1190 FBU.bytes += subrects * 2;
1191 }
1192 }
1193 }
1194
1195 /*
1196 Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
1197 " (" + tile_x + "," + tile_y + ")" +
1198 " [" + x + "," + y + "]@" + w + "x" + h +
1199 ", subenc:" + subencoding +
1200 "(last: " + FBU.lastsubencoding + "), subrects:" +
1201 subrects +
1202 ", rQlen():" + rQlen() + ", FBU.bytes:" + FBU.bytes +
1203 " last:" + rQ.slice(FBU.bytes-10, FBU.bytes) +
1204 " next:" + rQ.slice(FBU.bytes-1, FBU.bytes+10));
1205 */
1206 if (rQwait("hextile")) { return false; }
1207
1208 /* We know the encoding and have a whole tile */
1209 FBU.subencoding = rQ[rQi];
1210 rQi += 1;
1211 if (FBU.subencoding === 0) {
1212 if (FBU.lastsubencoding & 0x01) {
1213 /* Weird: ignore blanks after RAW */
1214 Util.Debug(" Ignoring blank after RAW");
1215 } else {
1216 canvas.fillRect(x, y, w, h, FBU.background);
1217 }
1218 } else if (FBU.subencoding & 0x01) { // Raw
1219 canvas.blitImage(x, y, w, h, rQ, rQi);
1220 rQi += FBU.bytes - 1;
1221 } else {
1222 if (FBU.subencoding & 0x02) { // Background
1223 FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
1224 rQi += fb_Bpp;
1225 }
1226 if (FBU.subencoding & 0x04) { // Foreground
1227 FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
1228 rQi += fb_Bpp;
1229 }
1230
1231 tile = canvas.getTile(x, y, w, h, FBU.background);
1232 if (FBU.subencoding & 0x08) { // AnySubrects
1233 subrects = rQ[rQi];
1234 rQi += 1;
1235 for (s = 0; s < subrects; s += 1) {
1236 if (FBU.subencoding & 0x10) { // SubrectsColoured
1237 color = rQ.slice(rQi, rQi + fb_Bpp);
1238 rQi += fb_Bpp;
1239 } else {
1240 color = FBU.foreground;
1241 }
1242 xy = rQ[rQi];
1243 rQi += 1;
1244 sx = (xy >> 4);
1245 sy = (xy & 0x0f);
1246
1247 wh = rQ[rQi];
1248 rQi += 1;
1249 sw = (wh >> 4) + 1;
1250 sh = (wh & 0x0f) + 1;
1251
1252 canvas.setSubTile(tile, sx, sy, sw, sh, color);
1253 }
1254 }
1255 canvas.putTile(tile);
1256 }
1257 //rQshiftBytes(FBU.bytes);
1258 FBU.lastsubencoding = FBU.subencoding;
1259 FBU.bytes = 0;
1260 FBU.tiles -= 1;
1261 }
1262
1263 if (FBU.tiles === 0) {
1264 FBU.rects -= 1;
1265 }
1266
1267 //Util.Debug("<< display_hextile");
1268 return true;
1269 };
1270
1271
1272 encHandlers.TIGHT_PNG = function display_tight_png() {
1273 //Util.Debug(">> display_tight_png");
1274 var ctl, cmode, clength, getCLength, color, img;
1275 //Util.Debug(" FBU.rects: " + FBU.rects);
1276 //Util.Debug(" starting rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
1277
1278 FBU.bytes = 1; // compression-control byte
1279 if (rQwait("TIGHT compression-control")) { return false; }
1280
1281 // Get 'compact length' header and data size
1282 getCLength = function (arr, offset) {
1283 var header = 1, data = 0;
1284 data += arr[offset + 0] & 0x7f;
1285 if (arr[offset + 0] & 0x80) {
1286 header += 1;
1287 data += (arr[offset + 1] & 0x7f) << 7;
1288 if (arr[offset + 1] & 0x80) {
1289 header += 1;
1290 data += arr[offset + 2] << 14;
1291 }
1292 }
1293 return [header, data];
1294 };
1295
1296 ctl = rQ[rQi];
1297 switch (ctl >> 4) {
1298 case 0x08: cmode = "fill"; break;
1299 case 0x09: cmode = "jpeg"; break;
1300 case 0x0A: cmode = "png"; break;
1301 default: throw("Illegal basic compression received, ctl: " + ctl);
1302 }
1303 switch (cmode) {
1304 // fill uses fb_depth because TPIXELs drop the padding byte
1305 case "fill": FBU.bytes += fb_depth; break; // TPIXEL
1306 case "jpeg": FBU.bytes += 3; break; // max clength
1307 case "png": FBU.bytes += 3; break; // max clength
1308 }
1309
1310 if (rQwait("TIGHT " + cmode)) { return false; }
1311
1312 //Util.Debug(" rQ.slice(0,20): " + rQ.slice(0,20) + " (" + rQlen() + ")");
1313 //Util.Debug(" cmode: " + cmode);
1314
1315 // Determine FBU.bytes
1316 switch (cmode) {
1317 case "fill":
1318 rQi++; // shift off ctl
1319 color = rQshiftBytes(fb_depth);
1320 canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
1321 break;
1322 case "jpeg":
1323 case "png":
1324 clength = getCLength(rQ, rQi+1);
1325 FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
1326 if (rQwait("TIGHT " + cmode)) { return false; }
1327
1328 // We have everything, render it
1329 //Util.Debug(" png, rQlen(): " + rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
1330 rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
1331 img = new Image();
1332 img.onload = scan_tight_imgQ;
1333 FBU.imgQ.push([img, FBU.x, FBU.y]);
1334 img.src = "data:image/" + cmode +
1335 extract_data_uri(rQshiftBytes(clength[1]));
1336 img = null;
1337 break;
1338 }
1339 FBU.bytes = 0;
1340 FBU.rects -= 1;
1341 //Util.Debug(" ending rQ.slice(rQi,rQi+20): " + rQ.slice(rQi,rQi+20) + " (" + rQlen() + ")");
1342 //Util.Debug("<< display_tight_png");
1343 return true;
1344 };
1345
1346 extract_data_uri = function(arr) {
1347 //var i, stra = [];
1348 //for (i=0; i< arr.length; i += 1) {
1349 // stra.push(String.fromCharCode(arr[i]));
1350 //}
1351 //return "," + escape(stra.join(''));
1352 return ";base64," + Base64.encode(arr);
1353 };
1354
1355 scan_tight_imgQ = function() {
1356 var img, imgQ, ctx;
1357 ctx = canvas.getContext();
1358 if (rfb_state === 'normal') {
1359 imgQ = FBU.imgQ;
1360 while ((imgQ.length > 0) && (imgQ[0][0].complete)) {
1361 img = imgQ.shift();
1362 ctx.drawImage(img[0], img[1], img[2]);
1363 }
1364 setTimeout(scan_tight_imgQ, scan_imgQ_rate);
1365 }
1366 };
1367
1368 encHandlers.DesktopSize = function set_desktopsize() {
1369 Util.Debug(">> set_desktopsize");
1370 fb_width = FBU.width;
1371 fb_height = FBU.height;
1372 canvas.clear();
1373 canvas.resize(fb_width, fb_height);
1374 timing.fbu_rt_start = (new Date()).getTime();
1375 // Send a new non-incremental request
1376 send_array(fbUpdateRequest(0));
1377
1378 FBU.bytes = 0;
1379 FBU.rects -= 1;
1380
1381 Util.Debug("<< set_desktopsize");
1382 return true;
1383 };
1384
1385 encHandlers.Cursor = function set_cursor() {
1386 var x, y, w, h, pixelslength, masklength;
1387 //Util.Debug(">> set_cursor");
1388 x = FBU.x; // hotspot-x
1389 y = FBU.y; // hotspot-y
1390 w = FBU.width;
1391 h = FBU.height;
1392
1393 pixelslength = w * h * fb_Bpp;
1394 masklength = Math.floor((w + 7) / 8) * h;
1395
1396 FBU.bytes = pixelslength + masklength;
1397 if (rQwait("cursor encoding")) { return false; }
1398
1399 //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
1400
1401 canvas.changeCursor(rQshiftBytes(pixelslength),
1402 rQshiftBytes(masklength),
1403 x, y, w, h);
1404
1405 FBU.bytes = 0;
1406 FBU.rects -= 1;
1407
1408 //Util.Debug("<< set_cursor");
1409 return true;
1410 };
1411
1412 encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
1413 Util.Error("Server sent jpeg_quality pseudo-encoding");
1414 };
1415
1416 encHandlers.compress_lo = function set_compress_level() {
1417 Util.Error("Server sent compress level pseudo-encoding");
1418 };
1419
1420 /*
1421 * Client message routines
1422 */
1423
1424 pixelFormat = function() {
1425 //Util.Debug(">> pixelFormat");
1426 var arr;
1427 arr = [0]; // msg-type
1428 arr.push8(0); // padding
1429 arr.push8(0); // padding
1430 arr.push8(0); // padding
1431
1432 arr.push8(fb_Bpp * 8); // bits-per-pixel
1433 arr.push8(fb_depth * 8); // depth
1434 arr.push8(0); // little-endian
1435 arr.push8(conf.true_color ? 1 : 0); // true-color
1436
1437 arr.push16(255); // red-max
1438 arr.push16(255); // green-max
1439 arr.push16(255); // blue-max
1440 arr.push8(0); // red-shift
1441 arr.push8(8); // green-shift
1442 arr.push8(16); // blue-shift
1443
1444 arr.push8(0); // padding
1445 arr.push8(0); // padding
1446 arr.push8(0); // padding
1447 //Util.Debug("<< pixelFormat");
1448 return arr;
1449 };
1450
1451 clientEncodings = function() {
1452 //Util.Debug(">> clientEncodings");
1453 var arr, i, encList = [];
1454
1455 for (i=0; i<encodings.length; i += 1) {
1456 if ((encodings[i][0] === "Cursor") &&
1457 (! conf.local_cursor)) {
1458 Util.Debug("Skipping Cursor pseudo-encoding");
1459 } else {
1460 //Util.Debug("Adding encoding: " + encodings[i][0]);
1461 encList.push(encodings[i][1]);
1462 }
1463 }
1464
1465 arr = [2]; // msg-type
1466 arr.push8(0); // padding
1467
1468 arr.push16(encList.length); // encoding count
1469 for (i=0; i < encList.length; i += 1) {
1470 arr.push32(encList[i]);
1471 }
1472 //Util.Debug("<< clientEncodings: " + arr);
1473 return arr;
1474 };
1475
1476 fbUpdateRequest = function(incremental, x, y, xw, yw) {
1477 //Util.Debug(">> fbUpdateRequest");
1478 if (!x) { x = 0; }
1479 if (!y) { y = 0; }
1480 if (!xw) { xw = fb_width; }
1481 if (!yw) { yw = fb_height; }
1482 var arr;
1483 arr = [3]; // msg-type
1484 arr.push8(incremental);
1485 arr.push16(x);
1486 arr.push16(y);
1487 arr.push16(xw);
1488 arr.push16(yw);
1489 //Util.Debug("<< fbUpdateRequest");
1490 return arr;
1491 };
1492
1493 keyEvent = function(keysym, down) {
1494 //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
1495 var arr;
1496 arr = [4]; // msg-type
1497 arr.push8(down);
1498 arr.push16(0);
1499 arr.push32(keysym);
1500 //Util.Debug("<< keyEvent");
1501 return arr;
1502 };
1503
1504 pointerEvent = function(x, y) {
1505 //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
1506 // " , mask: " + mouse_buttonMask);
1507 var arr;
1508 arr = [5]; // msg-type
1509 arr.push8(mouse_buttonMask);
1510 arr.push16(x);
1511 arr.push16(y);
1512 //Util.Debug("<< pointerEvent");
1513 return arr;
1514 };
1515
1516 clientCutText = function(text) {
1517 //Util.Debug(">> clientCutText");
1518 var arr, i, n;
1519 arr = [6]; // msg-type
1520 arr.push8(0); // padding
1521 arr.push8(0); // padding
1522 arr.push8(0); // padding
1523 arr.push32(text.length);
1524 n = text.length;
1525 for (i=0; i < n; i+=1) {
1526 arr.push(text.charCodeAt(i));
1527 }
1528 //Util.Debug("<< clientCutText:" + arr);
1529 return arr;
1530 };
1531
1532
1533
1534 //
1535 // Public API interface functions
1536 //
1537
1538 that.connect = function(host, port, password) {
1539 //Util.Debug(">> connect");
1540
1541 rfb_host = host;
1542 rfb_port = port;
1543 rfb_password = (password !== undefined) ? password : "";
1544
1545 if ((!rfb_host) || (!rfb_port)) {
1546 return fail("Must set host and port");
1547 }
1548
1549 updateState('connect');
1550 //Util.Debug("<< connect");
1551
1552 };
1553
1554 that.disconnect = function() {
1555 //Util.Debug(">> disconnect");
1556 updateState('disconnect', 'Disconnecting');
1557 //Util.Debug("<< disconnect");
1558 };
1559
1560 that.sendPassword = function(passwd) {
1561 rfb_password = passwd;
1562 rfb_state = "Authentication";
1563 setTimeout(init_msg, 1);
1564 };
1565
1566 that.sendCtrlAltDel = function() {
1567 if (rfb_state !== "normal") { return false; }
1568 Util.Info("Sending Ctrl-Alt-Del");
1569 var arr = [];
1570 arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
1571 arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
1572 arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
1573 arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
1574 arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
1575 arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
1576 arr = arr.concat(fbUpdateRequest(1));
1577 send_array(arr);
1578 };
1579
1580 // Send a key press. If 'down' is not specified then send a down key
1581 // followed by an up key.
1582 that.sendKey = function(code, down) {
1583 if (rfb_state !== "normal") { return false; }
1584 var arr = [];
1585 if (typeof down !== 'undefined') {
1586 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
1587 arr = arr.concat(keyEvent(code, down ? 1 : 0));
1588 } else {
1589 Util.Info("Sending key code (down + up): " + code);
1590 arr = arr.concat(keyEvent(code, 1));
1591 arr = arr.concat(keyEvent(code, 0));
1592 }
1593 arr = arr.concat(fbUpdateRequest(1));
1594 send_array(arr);
1595 };
1596
1597 that.clipboardPasteFrom = function(text) {
1598 if (rfb_state !== "normal") { return; }
1599 //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
1600 send_array(clientCutText(text));
1601 //Util.Debug("<< clipboardPasteFrom");
1602 };
1603
1604 that.testMode = function(override_send_array) {
1605 // Overridable internal functions for testing
1606 test_mode = true;
1607 send_array = override_send_array;
1608 that.recv_message = recv_message; // Expose it
1609
1610 checkEvents = function () { /* Stub Out */ };
1611 that.connect = function(host, port, password) {
1612 rfb_host = host;
1613 rfb_port = port;
1614 rfb_password = password;
1615 updateState('ProtocolVersion', "Starting VNC handshake");
1616 };
1617 };
1618
1619
1620 return constructor(); // Return the public API interface
1621
1622 } // End of RFB()