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