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