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