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