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