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