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