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