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