]> git.proxmox.com Git - mirror_novnc.git/blame - include/rfb.js
Fix multi-line assertion messages in test runner
[mirror_novnc.git] / include / rfb.js
CommitLineData
a7a89626
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
406a8b4e 4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
1d728ace 5 * Licensed under MPL 2.0 (see LICENSE.txt)
a7a89626
JM
6 *
7 * See README.md for usage and integration instructions.
d38db74a
MT
8 *
9 * TIGHT decoder portion:
10 * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
a7a89626
JM
11 */
12
b1dee947 13/*jslint white: false, browser: true */
0911173a 14/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
a7a89626 15
b1dee947 16var RFB;
a7a89626 17
b1dee947
SR
18(function () {
19 "use strict";
20 RFB = function (defaults) {
21 if (!defaults) {
22 defaults = {};
23 }
5210330a 24
b1dee947
SR
25 this._rfb_host = '';
26 this._rfb_port = 5900;
27 this._rfb_password = '';
28 this._rfb_path = '';
29
30 this._rfb_state = 'disconnected';
31 this._rfb_version = 0;
32 this._rfb_max_version = 3.8;
33 this._rfb_auth_scheme = '';
34
35 this._rfb_tightvnc = false;
36 this._rfb_xvp_ver = 0;
37
38 // In preference order
39 this._encodings = [
798340b9 40 ['COPYRECT', 0x01 ],
41 ['TIGHT', 0x07 ],
42 ['TIGHT_PNG', -260 ],
43 ['HEXTILE', 0x05 ],
44 ['RRE', 0x02 ],
45 ['RAW', 0x00 ],
46 ['DesktopSize', -223 ],
47 ['Cursor', -239 ],
b1dee947
SR
48
49 // Psuedo-encoding settings
798340b9 50 //['JPEG_quality_lo', -32 ],
51 ['JPEG_quality_med', -26 ],
52 //['JPEG_quality_hi', -23 ],
53 //['compress_lo', -255 ],
54 ['compress_hi', -247 ],
55 ['last_rect', -224 ],
56 ['xvp', -309 ],
57 ['ExtendedDesktopSize', -308 ]
b1dee947
SR
58 ];
59
60 this._encHandlers = {};
61 this._encNames = {};
62 this._encStats = {};
63
64 this._sock = null; // Websock object
65 this._display = null; // Display object
66 this._keyboard = null; // Keyboard input handler object
67 this._mouse = null; // Mouse input handler object
68 this._sendTimer = null; // Send Queue check timer
69 this._disconnTimer = null; // disconnection timer
70 this._msgTimer = null; // queued handle_msg timer
71
72 // Frame buffer update state
73 this._FBU = {
74 rects: 0,
75 subrects: 0, // RRE
76 lines: 0, // RAW
77 tiles: 0, // HEXTILE
78 bytes: 0,
79 x: 0,
80 y: 0,
81 width: 0,
82 height: 0,
83 encoding: 0,
84 subencoding: -1,
85 background: null,
86 zlib: [] // TIGHT zlib streams
87 };
5210330a 88
b1dee947
SR
89 this._fb_Bpp = 4;
90 this._fb_depth = 3;
91 this._fb_width = 0;
92 this._fb_height = 0;
93 this._fb_name = "";
d1800d09
SR
94
95 this._destBuff = null;
96 this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
b1dee947
SR
97
98 this._rre_chunk_sz = 100;
99
100 this._timing = {
101 last_fbu: 0,
102 fbu_total: 0,
103 fbu_total_cnt: 0,
104 full_fbu_total: 0,
105 full_fbu_cnt: 0,
106
107 fbu_rt_start: 0,
108 fbu_rt_total: 0,
109 fbu_rt_cnt: 0,
110 pixels: 0
111 };
112
b0ec6509 113 this._supportsSetDesktopSize = false;
114 this._screen_id = 0;
115 this._screen_flags = 0;
116
b1dee947
SR
117 // Mouse state
118 this._mouse_buttonMask = 0;
119 this._mouse_arr = [];
120 this._viewportDragging = false;
121 this._viewportDragPos = {};
122
123 // set the default value on user-facing properties
124 Util.set_defaults(this, defaults, {
125 'target': 'null', // VNC display rendering Canvas object
126 'focusContainer': document, // DOM element that captures keyboard input
127 'encrypt': false, // Use TLS/SSL/wss encryption
128 'true_color': true, // Request true color pixel data
129 'local_cursor': false, // Request locally rendered cursor
130 'shared': true, // Request shared mode
131 'view_only': false, // Disable client mouse/keyboard
132 'xvp_password_sep': '@', // Separator for XVP password fields
133 'disconnectTimeout': 3, // Time (s) to wait for disconnection
38781d93 134 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
b1dee947
SR
135 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
136 'viewportDrag': false, // Move the viewport on mouse drags
137
138 // Callback functions
139 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
140 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
141 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
142 'onBell': function () { }, // onBell(rfb): RFB Bell message received
143 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
144 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
145 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
146 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
147 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
148 });
149
150 // main setup
151 Util.Debug(">> RFB.constructor");
152
153 // populate encHandlers with bound versions
154 Object.keys(RFB.encodingHandlers).forEach(function (encName) {
155 this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
156 }.bind(this));
157
158 // Create lookup tables based on encoding number
159 for (var i = 0; i < this._encodings.length; i++) {
160 this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
161 this._encNames[this._encodings[i][1]] = this._encodings[i][0];
162 this._encStats[this._encodings[i][1]] = [0, 0];
b688a909 163 }
b1dee947 164
d9fc1c7b
SR
165 // NB: nothing that needs explicit teardown should be done
166 // before this point, since this can throw an exception
b1dee947
SR
167 try {
168 this._display = new Display({target: this._target});
169 } catch (exc) {
170 Util.Error("Display exception: " + exc);
d9fc1c7b 171 throw exc;
d890e864 172 }
b1dee947
SR
173
174 this._keyboard = new Keyboard({target: this._focusContainer,
175 onKeyPress: this._handleKeyPress.bind(this)});
176
177 this._mouse = new Mouse({target: this._target,
178 onMouseButton: this._handleMouseButton.bind(this),
179 onMouseMove: this._handleMouseMove.bind(this),
180 notify: this._keyboard.sync.bind(this._keyboard)});
181
182 this._sock = new Websock();
183 this._sock.on('message', this._handle_message.bind(this));
184 this._sock.on('open', function () {
185 if (this._rfb_state === 'connect') {
186 this._updateState('ProtocolVersion', "Starting VNC handshake");
187 } else {
188 this._fail("Got unexpected WebSocket connection");
189 }
190 }.bind(this));
191 this._sock.on('close', function (e) {
192 Util.Warn("WebSocket on-close event");
193 var msg = "";
194 if (e.code) {
195 msg = " (code: " + e.code;
196 if (e.reason) {
197 msg += ", reason: " + e.reason;
198 }
199 msg += ")";
200 }
201 if (this._rfb_state === 'disconnect') {
202 this._updateState('disconnected', 'VNC disconnected' + msg);
203 } else if (this._rfb_state === 'ProtocolVersion') {
204 this._fail('Failed to connect to server' + msg);
205 } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
206 Util.Error("Received onclose while disconnected" + msg);
207 } else {
208 this._fail("Server disconnected" + msg);
209 }
155d78b3 210 this._sock.off('close');
b1dee947
SR
211 }.bind(this));
212 this._sock.on('error', function (e) {
213 Util.Warn("WebSocket on-error event");
214 });
215
216 this._init_vars();
217
218 var rmode = this._display.get_render_mode();
219 if (Websock_native) {
220 Util.Info("Using native WebSockets");
221 this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
0d1e1b72 222 } else {
38781d93
SR
223 this._cleanupSocket('fatal');
224 throw new Error("WebSocket support is required to use noVNC");
a679a97d 225 }
8db09746 226
b1dee947
SR
227 Util.Debug("<< RFB.constructor");
228 };
a7a89626 229
b1dee947
SR
230 RFB.prototype = {
231 // Public methods
232 connect: function (host, port, password, path) {
233 this._rfb_host = host;
234 this._rfb_port = port;
235 this._rfb_password = (password !== undefined) ? password : "";
236 this._rfb_path = (path !== undefined) ? path : "";
a7a89626 237
b1dee947
SR
238 if (!this._rfb_host || !this._rfb_port) {
239 return this._fail("Must set host and port");
240 }
e3efeb32 241
b1dee947
SR
242 this._updateState('connect');
243 },
244
245 disconnect: function () {
246 this._updateState('disconnect', 'Disconnecting');
155d78b3
JS
247 this._sock.off('error');
248 this._sock.off('message');
249 this._sock.off('open');
b1dee947
SR
250 },
251
252 sendPassword: function (passwd) {
253 this._rfb_password = passwd;
254 this._rfb_state = 'Authentication';
255 setTimeout(this._init_msg.bind(this), 1);
256 },
257
258 sendCtrlAltDel: function () {
259 if (this._rfb_state !== 'normal' || this._view_only) { return false; }
260 Util.Info("Sending Ctrl-Alt-Del");
261
9ff86fb7
SR
262 RFB.messages.keyEvent(this._sock, XK_Control_L, 1);
263 RFB.messages.keyEvent(this._sock, XK_Alt_L, 1);
264 RFB.messages.keyEvent(this._sock, XK_Delete, 1);
265 RFB.messages.keyEvent(this._sock, XK_Delete, 0);
266 RFB.messages.keyEvent(this._sock, XK_Alt_L, 0);
267 RFB.messages.keyEvent(this._sock, XK_Control_L, 0);
268
269 this._sock.flush();
b1dee947
SR
270 },
271
272 xvpOp: function (ver, op) {
273 if (this._rfb_xvp_ver < ver) { return false; }
274 Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
275 this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
276 return true;
277 },
278
279 xvpShutdown: function () {
280 return this.xvpOp(1, 2);
281 },
282
283 xvpReboot: function () {
284 return this.xvpOp(1, 3);
285 },
286
287 xvpReset: function () {
288 return this.xvpOp(1, 4);
289 },
290
291 // Send a key press. If 'down' is not specified then send a down key
292 // followed by an up key.
293 sendKey: function (code, down) {
294 if (this._rfb_state !== "normal" || this._view_only) { return false; }
b1dee947
SR
295 if (typeof down !== 'undefined') {
296 Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
9ff86fb7 297 RFB.messages.keyEvent(this._sock, code, down ? 1 : 0);
b1dee947
SR
298 } else {
299 Util.Info("Sending key code (down + up): " + code);
9ff86fb7
SR
300 RFB.messages.keyEvent(this._sock, code, 1);
301 RFB.messages.keyEvent(this._sock, code, 0);
b1dee947 302 }
9ff86fb7
SR
303
304 this._sock.flush();
b1dee947
SR
305 },
306
307 clipboardPasteFrom: function (text) {
308 if (this._rfb_state !== 'normal') { return; }
9ff86fb7
SR
309 RFB.messages.clientCutText(this._sock, text);
310 this._sock.flush();
b1dee947 311 },
e3efeb32 312
b0ec6509 313 setDesktopSize: function (width, height) {
314 if (this._rfb_state !== "normal") { return; }
315
316 if (this._supportsSetDesktopSize) {
317
318 var arr = [251]; // msg-type
319 arr.push8(0); // padding
320 arr.push16(width); // width
321 arr.push16(height); // height
322
323 arr.push8(1); // number-of-screens
324 arr.push8(0); // padding
325
326 // screen array
327 arr.push32(this._screen_id); // id
328 arr.push16(0); // x-position
329 arr.push16(0); // y-position
330 arr.push16(width); // width
331 arr.push16(height); // height
332 arr.push32(this._screen_flags); // flags
333
334 this._sock.send(arr);
335 }
336 },
337
338
b1dee947
SR
339 // Private methods
340
341 _connect: function () {
342 Util.Debug(">> RFB.connect");
343
344 var uri;
345 if (typeof UsingSocketIO !== 'undefined') {
346 uri = 'http';
347 } else {
348 uri = this._encrypt ? 'wss' : 'ws';
159ad55d 349 }
e3efeb32 350
b1dee947
SR
351 uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
352 Util.Info("connecting to " + uri);
a7a89626 353
58ca1978 354 this._sock.open(uri, this._wsProtocols);
a7a89626 355
b1dee947
SR
356 Util.Debug("<< RFB.connect");
357 },
a7a89626 358
b1dee947
SR
359 _init_vars: function () {
360 // reset state
b1dee947
SR
361 this._FBU.rects = 0;
362 this._FBU.subrects = 0; // RRE and HEXTILE
363 this._FBU.lines = 0; // RAW
364 this._FBU.tiles = 0; // HEXTILE
365 this._FBU.zlibs = []; // TIGHT zlib encoders
366 this._mouse_buttonMask = 0;
367 this._mouse_arr = [];
368 this._rfb_tightvnc = false;
8db09746 369
b1dee947
SR
370 // Clear the per connection encoding stats
371 var i;
372 for (i = 0; i < this._encodings.length; i++) {
373 this._encStats[this._encodings[i][1]][0] = 0;
374 }
a7a89626 375
b1dee947 376 for (i = 0; i < 4; i++) {
6940936f
SR
377 //this._FBU.zlibs[i] = new TINF();
378 //this._FBU.zlibs[i].init();
379 this._FBU.zlibs[i] = new inflator.Inflate();
b1dee947
SR
380 }
381 },
382
383 _print_stats: function () {
384 Util.Info("Encoding stats for this connection:");
385 var i, s;
386 for (i = 0; i < this._encodings.length; i++) {
387 s = this._encStats[this._encodings[i][1]];
388 if (s[0] + s[1] > 0) {
389 Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
390 }
391 }
a7a89626 392
b1dee947
SR
393 Util.Info("Encoding stats since page load:");
394 for (i = 0; i < this._encodings.length; i++) {
395 s = this._encStats[this._encodings[i][1]];
396 Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
397 }
398 },
a7a89626 399
d9fc1c7b
SR
400 _cleanupSocket: function (state) {
401 if (this._sendTimer) {
402 clearInterval(this._sendTimer);
403 this._sendTimer = null;
404 }
405
406 if (this._msgTimer) {
407 clearInterval(this._msgTimer);
408 this._msgTimer = null;
409 }
410
411 if (this._display && this._display.get_context()) {
412 this._keyboard.ungrab();
413 this._mouse.ungrab();
414 if (state !== 'connect' && state !== 'loaded') {
415 this._display.defaultCursor();
416 }
417 if (Util.get_logging() !== 'debug' || state === 'loaded') {
418 // Show noVNC logo on load and when disconnected, unless in
419 // debug mode
420 this._display.clear();
421 }
422 }
423
424 this._sock.close();
425 },
a7a89626 426
b1dee947
SR
427 /*
428 * Page states:
429 * loaded - page load, equivalent to disconnected
430 * disconnected - idle state
431 * connect - starting to connect (to ProtocolVersion)
432 * normal - connected
433 * disconnect - starting to disconnect
434 * failed - abnormal disconnect
435 * fatal - failed to load page, or fatal error
436 *
437 * RFB protocol initialization states:
438 * ProtocolVersion
439 * Security
440 * Authentication
441 * password - waiting for password, not part of RFB
442 * SecurityResult
443 * ClientInitialization - not triggered by server message
444 * ServerInitialization (to normal)
445 */
446 _updateState: function (state, statusMsg) {
447 var oldstate = this._rfb_state;
448
449 if (state === oldstate) {
450 // Already here, ignore
451 Util.Debug("Already in state '" + state + "', ignoring");
452 }
a679a97d 453
b1dee947
SR
454 /*
455 * These are disconnected states. A previous connect may
456 * asynchronously cause a connection so make sure we are closed.
457 */
458 if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
459 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
d9fc1c7b 460 this._cleanupSocket(state);
b1dee947 461 }
a7a89626 462
b1dee947
SR
463 if (oldstate === 'fatal') {
464 Util.Error('Fatal error, cannot continue');
465 }
466
467 var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
468 var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
469 if (state === 'failed' || state === 'fatal') {
470 Util.Error(cmsg);
4ed717ad 471 } else {
b1dee947 472 Util.Warn(cmsg);
4ed717ad 473 }
a5df24b4 474
b1dee947
SR
475 if (oldstate === 'failed' && state === 'disconnected') {
476 // do disconnect action, but stay in failed state
477 this._rfb_state = 'failed';
478 } else {
479 this._rfb_state = state;
480 }
481
482 if (this._disconnTimer && this._rfb_state !== 'disconnect') {
483 Util.Debug("Clearing disconnect timer");
484 clearTimeout(this._disconnTimer);
485 this._disconnTimer = null;
e543525f 486 this._sock.off('close'); // make sure we don't get a double event
b1dee947 487 }
06a9ef0c 488
b1dee947
SR
489 switch (state) {
490 case 'normal':
491 if (oldstate === 'disconnected' || oldstate === 'failed') {
492 Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
493 }
494 break;
495
496 case 'connect':
497 this._init_vars();
498 this._connect();
499 // WebSocket.onopen transitions to 'ProtocolVersion'
500 break;
501
502 case 'disconnect':
503 this._disconnTimer = setTimeout(function () {
504 this._fail("Disconnect timeout");
505 }.bind(this), this._disconnectTimeout * 1000);
506
507 this._print_stats();
508
509 // WebSocket.onclose transitions to 'disconnected'
510 break;
511
512 case 'failed':
513 if (oldstate === 'disconnected') {
514 Util.Error("Invalid transition from 'disconnected' to 'failed'");
515 } else if (oldstate === 'normal') {
516 Util.Error("Error while connected.");
517 } else if (oldstate === 'init') {
518 Util.Error("Error while initializing.");
519 }
8db09746 520
b1dee947
SR
521 // Make sure we transition to disconnected
522 setTimeout(function () {
523 this._updateState('disconnected');
524 }.bind(this), 50);
a5df24b4 525
b1dee947 526 break;
a5df24b4 527
b1dee947
SR
528 default:
529 // No state change action to take
530 }
a5df24b4 531
b1dee947
SR
532 if (oldstate === 'failed' && state === 'disconnected') {
533 this._onUpdateState(this, state, oldstate);
534 } else {
535 this._onUpdateState(this, state, oldstate, statusMsg);
536 }
537 },
a5df24b4 538
b1dee947
SR
539 _fail: function (msg) {
540 this._updateState('failed', msg);
541 return false;
542 },
06a9ef0c 543
b1dee947
SR
544 _handle_message: function () {
545 if (this._sock.rQlen() === 0) {
546 Util.Warn("handle_message called on an empty receive queue");
547 return;
548 }
8db09746 549
b1dee947
SR
550 switch (this._rfb_state) {
551 case 'disconnected':
552 case 'failed':
553 Util.Error("Got data while disconnected");
554 break;
555 case 'normal':
556 if (this._normal_msg() && this._sock.rQlen() > 0) {
557 // true means we can continue processing
558 // Give other events a chance to run
559 if (this._msgTimer === null) {
560 Util.Debug("More data to process, creating timer");
561 this._msgTimer = setTimeout(function () {
562 this._msgTimer = null;
563 this._handle_message();
564 }.bind(this), 10);
565 } else {
566 Util.Debug("More data to process, existing timer");
567 }
568 }
569 break;
570 default:
571 this._init_msg();
572 break;
573 }
574 },
8db09746 575
b1dee947
SR
576 _handleKeyPress: function (keysym, down) {
577 if (this._view_only) { return; } // View only, skip keyboard, events
9ff86fb7
SR
578 RFB.messages.keyEvent(this._sock, keysym, down);
579 this._sock.flush();
b1dee947 580 },
8db09746 581
b1dee947
SR
582 _handleMouseButton: function (x, y, down, bmask) {
583 if (down) {
584 this._mouse_buttonMask |= bmask;
585 } else {
d02a99f0 586 this._mouse_buttonMask ^= bmask;
b1dee947 587 }
8db09746 588
b1dee947
SR
589 if (this._viewportDrag) {
590 if (down && !this._viewportDragging) {
591 this._viewportDragging = true;
592 this._viewportDragPos = {'x': x, 'y': y};
8db09746 593
b1dee947
SR
594 // Skip sending mouse events
595 return;
596 } else {
597 this._viewportDragging = false;
598 }
b50f3406 599 }
8db09746 600
b1dee947
SR
601 if (this._view_only) { return; } // View only, skip mouse events
602
9ff86fb7
SR
603 if (this._rfb_state !== "normal") { return; }
604 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
b1dee947
SR
605 },
606
607 _handleMouseMove: function (x, y) {
608 if (this._viewportDragging) {
609 var deltaX = this._viewportDragPos.x - x;
610 var deltaY = this._viewportDragPos.y - y;
611 this._viewportDragPos = {'x': x, 'y': y};
612
b0ec6509 613 this._display.viewportChangePos(deltaX, deltaY);
8db09746 614
b1dee947
SR
615 // Skip sending mouse events
616 return;
617 }
618
619 if (this._view_only) { return; } // View only, skip mouse events
620
9ff86fb7
SR
621 if (this._rfb_state !== "normal") { return; }
622 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
b1dee947
SR
623 },
624
625 // Message Handlers
626
627 _negotiate_protocol_version: function () {
628 if (this._sock.rQlen() < 12) {
629 return this._fail("Incomplete protocol version");
630 }
631
632 var sversion = this._sock.rQshiftStr(12).substr(4, 7);
633 Util.Info("Server ProtocolVersion: " + sversion);
634 var is_repeater = 0;
635 switch (sversion) {
636 case "000.000": // UltraVNC repeater
637 is_repeater = 1;
638 break;
639 case "003.003":
640 case "003.006": // UltraVNC
641 case "003.889": // Apple Remote Desktop
642 this._rfb_version = 3.3;
643 break;
644 case "003.007":
645 this._rfb_version = 3.7;
646 break;
647 case "003.008":
648 case "004.000": // Intel AMT KVM
649 case "004.001": // RealVNC 4.6
650 this._rfb_version = 3.8;
651 break;
652 default:
653 return this._fail("Invalid server version " + sversion);
654 }
655
656 if (is_repeater) {
657 var repeaterID = this._repeaterID;
658 while (repeaterID.length < 250) {
659 repeaterID += "\0";
8db09746 660 }
b1dee947
SR
661 this._sock.send_string(repeaterID);
662 return true;
8db09746 663 }
b1dee947
SR
664
665 if (this._rfb_version > this._rfb_max_version) {
666 this._rfb_version = this._rfb_max_version;
8db09746 667 }
b1dee947
SR
668
669 // Send updates either at a rate of 1 update per 50ms, or
670 // whatever slower rate the network can handle
671 this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
672
673 var cversion = "00" + parseInt(this._rfb_version, 10) +
674 ".00" + ((this._rfb_version * 10) % 10);
675 this._sock.send_string("RFB " + cversion + "\n");
676 this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
677 },
678
679 _negotiate_security: function () {
680 if (this._rfb_version >= 3.7) {
681 // Server sends supported list, client decides
682 var num_types = this._sock.rQshift8();
683 if (this._sock.rQwait("security type", num_types, 1)) { return false; }
684
685 if (num_types === 0) {
686 var strlen = this._sock.rQshift32();
687 var reason = this._sock.rQshiftStr(strlen);
688 return this._fail("Security failure: " + reason);
1f758e87 689 }
b1dee947
SR
690
691 this._rfb_auth_scheme = 0;
692 var types = this._sock.rQshiftBytes(num_types);
693 Util.Debug("Server security types: " + types);
694 for (var i = 0; i < types.length; i++) {
695 if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
696 this._rfb_auth_scheme = types[i];
697 }
698 }
699
700 if (this._rfb_auth_scheme === 0) {
701 return this._fail("Unsupported security types: " + types);
03ab2515 702 }
b1dee947
SR
703
704 this._sock.send([this._rfb_auth_scheme]);
705 } else {
706 // Server decides
707 if (this._sock.rQwait("security scheme", 4)) { return false; }
708 this._rfb_auth_scheme = this._sock.rQshift32();
709 }
710
711 this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
712 return this._init_msg(); // jump to authentication
713 },
714
715 // authentication
716 _negotiate_xvp_auth: function () {
717 var xvp_sep = this._xvp_password_sep;
718 var xvp_auth = this._rfb_password.split(xvp_sep);
719 if (xvp_auth.length < 3) {
720 this._updateState('password', 'XVP credentials required (user' + xvp_sep +
721 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
722 this._onPasswordRequired(this);
723 return false;
724 }
725
726 var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
03ab2515
MS
727 String.fromCharCode(xvp_auth[1].length) +
728 xvp_auth[0] +
729 xvp_auth[1];
b1dee947
SR
730 this._sock.send_string(xvp_auth_str);
731 this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
732 this._rfb_auth_scheme = 2;
733 return this._negotiate_authentication();
734 },
735
736 _negotiate_std_vnc_auth: function () {
737 if (this._rfb_password.length === 0) {
738 // Notify via both callbacks since it's kind of
739 // an RFB state change and a UI interface issue
740 this._updateState('password', "Password Required");
741 this._onPasswordRequired(this);
742 }
743
744 if (this._sock.rQwait("auth challenge", 16)) { return false; }
745
38781d93
SR
746 // TODO(directxman12): make genDES not require an Array
747 var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
b1dee947
SR
748 var response = RFB.genDES(this._rfb_password, challenge);
749 this._sock.send(response);
750 this._updateState("SecurityResult");
751 return true;
752 },
753
754 _negotiate_tight_tunnels: function (numTunnels) {
755 var clientSupportedTunnelTypes = {
756 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
757 };
758 var serverSupportedTunnelTypes = {};
759 // receive tunnel capabilities
760 for (var i = 0; i < numTunnels; i++) {
761 var cap_code = this._sock.rQshift32();
762 var cap_vendor = this._sock.rQshiftStr(4);
763 var cap_signature = this._sock.rQshiftStr(8);
764 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
765 }
766
767 // choose the notunnel type
768 if (serverSupportedTunnelTypes[0]) {
769 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
770 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
771 return this._fail("Client's tunnel type had the incorrect vendor or signature");
8db09746 772 }
b1dee947
SR
773 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
774 return false; // wait until we receive the sub auth count to continue
775 } else {
776 return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
777 }
778 },
95eb681b 779
b1dee947
SR
780 _negotiate_tight_auth: function () {
781 if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
782 if (this._sock.rQwait("num tunnels", 4)) { return false; }
783 var numTunnels = this._sock.rQshift32();
784 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
d86cc2d9 785
b1dee947 786 this._rfb_tightvnc = true;
d86cc2d9 787
b1dee947
SR
788 if (numTunnels > 0) {
789 this._negotiate_tight_tunnels(numTunnels);
790 return false; // wait until we receive the sub auth to continue
791 }
792 }
793
794 // second pass, do the sub-auth negotiation
795 if (this._sock.rQwait("sub auth count", 4)) { return false; }
796 var subAuthCount = this._sock.rQshift32();
797 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
95eb681b 798
b1dee947
SR
799 var clientSupportedTypes = {
800 'STDVNOAUTH__': 1,
801 'STDVVNCAUTH_': 2
802 };
d86cc2d9 803
b1dee947 804 var serverSupportedTypes = [];
d86cc2d9 805
b1dee947
SR
806 for (var i = 0; i < subAuthCount; i++) {
807 var capNum = this._sock.rQshift32();
808 var capabilities = this._sock.rQshiftStr(12);
809 serverSupportedTypes.push(capabilities);
810 }
d86cc2d9 811
b1dee947
SR
812 for (var authType in clientSupportedTypes) {
813 if (serverSupportedTypes.indexOf(authType) != -1) {
814 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
815
816 switch (authType) {
817 case 'STDVNOAUTH__': // no auth
818 this._updateState('SecurityResult');
819 return true;
820 case 'STDVVNCAUTH_': // VNC auth
821 this._rfb_auth_scheme = 2;
822 return this._init_msg();
823 default:
824 return this._fail("Unsupported tiny auth scheme: " + authType);
825 }
d86cc2d9 826 }
b1dee947 827 }
d86cc2d9 828
b1dee947
SR
829 this._fail("No supported sub-auth types!");
830 },
831
832 _negotiate_authentication: function () {
833 switch (this._rfb_auth_scheme) {
834 case 0: // connection failed
835 if (this._sock.rQwait("auth reason", 4)) { return false; }
836 var strlen = this._sock.rQshift32();
837 var reason = this._sock.rQshiftStr(strlen);
838 return this._fail("Auth failure: " + reason);
839
840 case 1: // no auth
841 if (this._rfb_version >= 3.8) {
842 this._updateState('SecurityResult');
843 return true;
844 }
845 this._updateState('ClientInitialisation', "No auth required");
846 return this._init_msg();
847
848 case 22: // XVP auth
849 return this._negotiate_xvp_auth();
850
851 case 2: // VNC authentication
852 return this._negotiate_std_vnc_auth();
853
854 case 16: // TightVNC Security Type
855 return this._negotiate_tight_auth();
856
857 default:
858 return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
859 }
860 },
861
862 _handle_security_result: function () {
863 if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
864 switch (this._sock.rQshift32()) {
865 case 0: // OK
866 this._updateState('ClientInitialisation', 'Authentication OK');
867 return this._init_msg();
868 case 1: // failed
869 if (this._rfb_version >= 3.8) {
870 var length = this._sock.rQshift32();
871 if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
872 var reason = this._sock.rQshiftStr(length);
873 return this._fail(reason);
874 } else {
875 return this._fail("Authentication failure");
d86cc2d9 876 }
b1dee947
SR
877 return false;
878 case 2:
879 return this._fail("Too many auth attempts");
880 }
881 },
882
883 _negotiate_server_init: function () {
884 if (this._sock.rQwait("server initialization", 24)) { return false; }
885
886 /* Screen size */
887 this._fb_width = this._sock.rQshift16();
888 this._fb_height = this._sock.rQshift16();
9ff86fb7 889 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
b1dee947
SR
890
891 /* PIXEL_FORMAT */
892 var bpp = this._sock.rQshift8();
893 var depth = this._sock.rQshift8();
894 var big_endian = this._sock.rQshift8();
895 var true_color = this._sock.rQshift8();
896
897 var red_max = this._sock.rQshift16();
898 var green_max = this._sock.rQshift16();
899 var blue_max = this._sock.rQshift16();
900 var red_shift = this._sock.rQshift8();
901 var green_shift = this._sock.rQshift8();
902 var blue_shift = this._sock.rQshift8();
903 this._sock.rQskipBytes(3); // padding
904
905 // NB(directxman12): we don't want to call any callbacks or print messages until
906 // *after* we're past the point where we could backtrack
907
908 /* Connection name/title */
909 var name_length = this._sock.rQshift32();
910 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
911 this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
912
913 if (this._rfb_tightvnc) {
914 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
915 // In TightVNC mode, ServerInit message is extended
916 var numServerMessages = this._sock.rQshift16();
917 var numClientMessages = this._sock.rQshift16();
918 var numEncodings = this._sock.rQshift16();
919 this._sock.rQskipBytes(2); // padding
920
921 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
922 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
923
924 var i;
925 for (i = 0; i < numServerMessages; i++) {
926 var srvMsg = this._sock.rQshiftStr(16);
d86cc2d9
BR
927 }
928
b1dee947
SR
929 for (i = 0; i < numClientMessages; i++) {
930 var clientMsg = this._sock.rQshiftStr(16);
931 }
d86cc2d9 932
b1dee947
SR
933 for (i = 0; i < numEncodings; i++) {
934 var encoding = this._sock.rQshiftStr(16);
935 }
936 }
937
938 // NB(directxman12): these are down here so that we don't run them multiple times
939 // if we backtrack
940 Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
941 ", bpp: " + bpp + ", depth: " + depth +
942 ", big_endian: " + big_endian +
943 ", true_color: " + true_color +
944 ", red_max: " + red_max +
945 ", green_max: " + green_max +
946 ", blue_max: " + blue_max +
947 ", red_shift: " + red_shift +
948 ", green_shift: " + green_shift +
949 ", blue_shift: " + blue_shift);
950
951 if (big_endian !== 0) {
952 Util.Warn("Server native endian is not little endian");
953 }
954
955 if (red_shift !== 16) {
956 Util.Warn("Server native red-shift is not 16");
957 }
958
959 if (blue_shift !== 0) {
960 Util.Warn("Server native blue-shift is not 0");
961 }
962
963 // we're past the point where we could backtrack, so it's safe to call this
964 this._onDesktopName(this, this._fb_name);
965
966 if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
967 Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
968 this._true_color = false;
969 }
970
971 this._display.set_true_color(this._true_color);
b1dee947 972 this._display.resize(this._fb_width, this._fb_height);
b0ec6509 973 this._onFBResize(this, this._fb_width, this._fb_height);
b1dee947
SR
974 this._keyboard.grab();
975 this._mouse.grab();
976
977 if (this._true_color) {
978 this._fb_Bpp = 4;
979 this._fb_depth = 3;
980 } else {
981 this._fb_Bpp = 1;
982 this._fb_depth = 1;
983 }
984
9ff86fb7
SR
985 RFB.messages.pixelFormat(this._sock, this._fb_Bpp, this._fb_depth, this._true_color);
986 RFB.messages.clientEncodings(this._sock, this._encodings, this._local_cursor, this._true_color);
987 RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height);
b1dee947
SR
988
989 this._timing.fbu_rt_start = (new Date()).getTime();
990 this._timing.pixels = 0;
9ff86fb7 991 this._sock.flush();
b1dee947
SR
992
993 if (this._encrypt) {
994 this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
995 } else {
996 this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
997 }
998 },
999
1000 _init_msg: function () {
1001 switch (this._rfb_state) {
1002 case 'ProtocolVersion':
1003 return this._negotiate_protocol_version();
1004
1005 case 'Security':
1006 return this._negotiate_security();
1007
1008 case 'Authentication':
1009 return this._negotiate_authentication();
1010
1011 case 'SecurityResult':
1012 return this._handle_security_result();
1013
1014 case 'ClientInitialisation':
1015 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1016 this._updateState('ServerInitialisation', "Authentication OK");
1017 return true;
1018
1019 case 'ServerInitialisation':
1020 return this._negotiate_server_init();
1021 }
1022 },
1023
1024 _handle_set_colour_map_msg: function () {
1025 Util.Debug("SetColorMapEntries");
1026 this._sock.rQskip8(); // Padding
1027
1028 var first_colour = this._sock.rQshift16();
1029 var num_colours = this._sock.rQshift16();
1030 if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
1031
1032 for (var c = 0; c < num_colours; c++) {
1033 var red = parseInt(this._sock.rQshift16() / 256, 10);
1034 var green = parseInt(this._sock.rQshift16() / 256, 10);
1035 var blue = parseInt(this._sock.rQshift16() / 256, 10);
1036 this._display.set_colourMap([blue, green, red], first_colour + c);
1037 }
1038 Util.Debug("colourMap: " + this._display.get_colourMap());
1039 Util.Info("Registered " + num_colours + " colourMap entries");
1040
1041 return true;
1042 },
1043
1044 _handle_server_cut_text: function () {
1045 Util.Debug("ServerCutText");
1046 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1047 this._sock.rQskipBytes(3); // Padding
1048 var length = this._sock.rQshift32();
1049 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
1050
1051 var text = this._sock.rQshiftStr(length);
1052 this._onClipboard(this, text);
1053
1054 return true;
1055 },
1056
1057 _handle_xvp_msg: function () {
1058 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1059 this._sock.rQskip8(); // Padding
1060 var xvp_ver = this._sock.rQshift8();
1061 var xvp_msg = this._sock.rQshift8();
1062
1063 switch (xvp_msg) {
1064 case 0: // XVP_FAIL
1065 this._updateState(this._rfb_state, "Operation Failed");
1066 break;
1067 case 1: // XVP_INIT
1068 this._rfb_xvp_ver = xvp_ver;
1069 Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
1070 this._onXvpInit(this._rfb_xvp_ver);
1071 break;
1072 default:
1073 this._fail("Disconnected: illegal server XVP message " + xvp_msg);
1074 break;
1075 }
1076
1077 return true;
1078 },
1079
1080 _normal_msg: function () {
1081 var msg_type;
1082
1083 if (this._FBU.rects > 0) {
1084 msg_type = 0;
1085 } else {
1086 msg_type = this._sock.rQshift8();
1087 }
1088
1089 switch (msg_type) {
1090 case 0: // FramebufferUpdate
1091 var ret = this._framebufferUpdate();
1092 if (ret) {
9ff86fb7
SR
1093 RFB.messages.fbUpdateRequests(this._sock, this._display.getCleanDirtyReset(), this._fb_width, this._fb_height);
1094 this._sock.flush();
b1dee947
SR
1095 }
1096 return ret;
1097
1098 case 1: // SetColorMapEntries
1099 return this._handle_set_colour_map_msg();
1100
1101 case 2: // Bell
1102 Util.Debug("Bell");
1103 this._onBell(this);
1104 return true;
1105
1106 case 3: // ServerCutText
1107 return this._handle_server_cut_text();
1108
1109 case 250: // XVP
1110 return this._handle_xvp_msg();
1111
1112 default:
1113 this._fail("Disconnected: illegal server message type " + msg_type);
1114 Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1115 return true;
1116 }
1117 },
1118
1119 _framebufferUpdate: function () {
1120 var ret = true;
1121 var now;
1122
1123 if (this._FBU.rects === 0) {
1124 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
1125 this._sock.rQskip8(); // Padding
1126 this._FBU.rects = this._sock.rQshift16();
1127 this._FBU.bytes = 0;
1128 this._timing.cur_fbu = 0;
1129 if (this._timing.fbu_rt_start > 0) {
1130 now = (new Date()).getTime();
1131 Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
1132 }
1133 }
1134
1135 while (this._FBU.rects > 0) {
1136 if (this._rfb_state !== "normal") { return false; }
1137
1138 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1139 if (this._FBU.bytes === 0) {
1140 if (this._sock.rQwait("rect header", 12)) { return false; }
1141 /* New FramebufferUpdate */
1142
1143 var hdr = this._sock.rQshiftBytes(12);
1144 this._FBU.x = (hdr[0] << 8) + hdr[1];
1145 this._FBU.y = (hdr[2] << 8) + hdr[3];
1146 this._FBU.width = (hdr[4] << 8) + hdr[5];
1147 this._FBU.height = (hdr[6] << 8) + hdr[7];
1148 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1149 (hdr[10] << 8) + hdr[11], 10);
1150
1151 this._onFBUReceive(this,
1152 {'x': this._FBU.x, 'y': this._FBU.y,
1153 'width': this._FBU.width, 'height': this._FBU.height,
1154 'encoding': this._FBU.encoding,
1155 'encodingName': this._encNames[this._FBU.encoding]});
1156
1157 if (!this._encNames[this._FBU.encoding]) {
1158 this._fail("Disconnected: unsupported encoding " +
1159 this._FBU.encoding);
60440cee 1160 return false;
aa787069 1161 }
8db09746 1162 }
ac99a1f7 1163
b1dee947
SR
1164 this._timing.last_fbu = (new Date()).getTime();
1165
6940936f
SR
1166 var handler = this._encHandlers[this._FBU.encoding];
1167 try {
1168 //ret = this._encHandlers[this._FBU.encoding]();
1169 ret = handler();
1170 } catch (ex) {
1171 console.log("missed " + this._FBU.encoding + ": " + handler);
1172 ret = this._encHandlers[this._FBU.encoding]();
1173 }
b1dee947
SR
1174
1175 now = (new Date()).getTime();
1176 this._timing.cur_fbu += (now - this._timing.last_fbu);
1177
1178 if (ret) {
1179 this._encStats[this._FBU.encoding][0]++;
1180 this._encStats[this._FBU.encoding][1]++;
1181 this._timing.pixels += this._FBU.width * this._FBU.height;
1182 }
1183
1184 if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
1185 if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
1186 this._timing.fbu_rt_start > 0) {
1187 this._timing.full_fbu_total += this._timing.cur_fbu;
1188 this._timing.full_fbu_cnt++;
1189 Util.Info("Timing of full FBU, curr: " +
1190 this._timing.cur_fbu + ", total: " +
1191 this._timing.full_fbu_total + ", cnt: " +
1192 this._timing.full_fbu_cnt + ", avg: " +
1193 (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
1194 }
1195
1196 if (this._timing.fbu_rt_start > 0) {
1197 var fbu_rt_diff = now - this._timing.fbu_rt_start;
1198 this._timing.fbu_rt_total += fbu_rt_diff;
1199 this._timing.fbu_rt_cnt++;
1200 Util.Info("full FBU round-trip, cur: " +
1201 fbu_rt_diff + ", total: " +
1202 this._timing.fbu_rt_total + ", cnt: " +
1203 this._timing.fbu_rt_cnt + ", avg: " +
1204 (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
1205 this._timing.fbu_rt_start = 0;
1206 }
1207 }
8db09746 1208
b1dee947 1209 if (!ret) { return ret; } // need more data
d86cc2d9 1210 }
d86cc2d9 1211
b1dee947
SR
1212 this._onFBUComplete(this,
1213 {'x': this._FBU.x, 'y': this._FBU.y,
1214 'width': this._FBU.width, 'height': this._FBU.height,
1215 'encoding': this._FBU.encoding,
1216 'encodingName': this._encNames[this._FBU.encoding]});
8db09746 1217
b1dee947
SR
1218 return true; // We finished this FBU
1219 },
1220 };
8db09746 1221
b1dee947
SR
1222 Util.make_properties(RFB, [
1223 ['target', 'wo', 'dom'], // VNC display rendering Canvas object
1224 ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
1225 ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
1226 ['true_color', 'rw', 'bool'], // Request true color pixel data
1227 ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
1228 ['shared', 'rw', 'bool'], // Request shared mode
1229 ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
1230 ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
1231 ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
1232 ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
1233 ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
1234 ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
1235
1236 // Callback functions
1237 ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
1238 ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
1239 ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
1240 ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
1241 ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
1242 ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
1243 ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
1244 ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
1245 ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
1246 ]);
a7a89626 1247
b1dee947
SR
1248 RFB.prototype.set_local_cursor = function (cursor) {
1249 if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
1250 this._local_cursor = false;
b804b3e4 1251 this._display.disableLocalCursor(); //Only show server-side cursor
b1dee947
SR
1252 } else {
1253 if (this._display.get_cursor_uri()) {
1254 this._local_cursor = true;
a7a89626 1255 } else {
b1dee947 1256 Util.Warn("Browser does not support local cursor");
8ce27ddb 1257 this._display.disableLocalCursor();
a7a89626
JM
1258 }
1259 }
b1dee947
SR
1260 };
1261
1262 RFB.prototype.get_display = function () { return this._display; };
1263 RFB.prototype.get_keyboard = function () { return this._keyboard; };
1264 RFB.prototype.get_mouse = function () { return this._mouse; };
1265
1266 // Class Methods
1267 RFB.messages = {
9ff86fb7
SR
1268 keyEvent: function (sock, keysym, down) {
1269 var buff = sock._sQ;
1270 var offset = sock._sQlen;
1271
1272 buff[offset] = 4; // msg-type
1273 buff[offset + 1] = down;
1274
1275 buff[offset + 2] = 0;
1276 buff[offset + 3] = 0;
1277
1278 buff[offset + 4] = (keysym >> 24);
1279 buff[offset + 5] = (keysym >> 16);
1280 buff[offset + 6] = (keysym >> 8);
1281 buff[offset + 7] = keysym;
1282
1283 sock._sQlen += 8;
b1dee947
SR
1284 },
1285
9ff86fb7
SR
1286 pointerEvent: function (sock, x, y, mask) {
1287 var buff = sock._sQ;
1288 var offset = sock._sQlen;
1289
1290 buff[offset] = 5; // msg-type
1291
1292 buff[offset + 1] = mask;
1293
1294 buff[offset + 2] = x >> 8;
1295 buff[offset + 3] = x;
1296
1297 buff[offset + 4] = y >> 8;
1298 buff[offset + 5] = y;
1299
1300 sock._sQlen += 6;
b1dee947
SR
1301 },
1302
1303 // TODO(directxman12): make this unicode compatible?
9ff86fb7
SR
1304 clientCutText: function (sock, text) {
1305 var buff = sock._sQ;
1306 var offset = sock._sQlen;
1307
1308 buff[offset] = 6; // msg-type
1309
1310 buff[offset + 1] = 0; // padding
1311 buff[offset + 2] = 0; // padding
1312 buff[offset + 3] = 0; // padding
1313
b1dee947 1314 var n = text.length;
9ff86fb7
SR
1315
1316 buff[offset + 4] = n >> 24;
1317 buff[offset + 5] = n >> 16;
1318 buff[offset + 6] = n >> 8;
1319 buff[offset + 7] = n;
1320
b1dee947 1321 for (var i = 0; i < n; i++) {
9ff86fb7 1322 buff[offset + 8 + i] = text.charCodeAt(i);
b1dee947 1323 }
a7a89626 1324
9ff86fb7 1325 sock._sQlen += 8 + n;
b1dee947
SR
1326 },
1327
9ff86fb7
SR
1328 pixelFormat: function (sock, bpp, depth, true_color) {
1329 var buff = sock._sQ;
1330 var offset = sock._sQlen;
1331
1332 buff[offset] = 0; // msg-type
1333
1334 buff[offset + 1] = 0; // padding
1335 buff[offset + 2] = 0; // padding
1336 buff[offset + 3] = 0; // padding
1337
1338 buff[offset + 4] = bpp * 8; // bits-per-pixel
1339 buff[offset + 5] = depth * 8; // depth
1340 buff[offset + 6] = 0; // little-endian
1341 buff[offset + 7] = true_color ? 1 : 0; // true-color
1342
1343 buff[offset + 8] = 0; // red-max
1344 buff[offset + 9] = 255; // red-max
1345
1346 buff[offset + 10] = 0; // green-max
1347 buff[offset + 11] = 255; // green-max
1348
1349 buff[offset + 12] = 0; // blue-max
1350 buff[offset + 13] = 255; // blue-max
1351
1352 buff[offset + 14] = 16; // red-shift
1353 buff[offset + 15] = 8; // green-shift
1354 buff[offset + 16] = 0; // blue-shift
1355
1356 buff[offset + 17] = 0; // padding
1357 buff[offset + 18] = 0; // padding
1358 buff[offset + 19] = 0; // padding
1359
1360 sock._sQlen += 20;
b1dee947
SR
1361 },
1362
9ff86fb7
SR
1363 clientEncodings: function (sock, encodings, local_cursor, true_color) {
1364 var buff = sock._sQ;
1365 var offset = sock._sQlen;
b1dee947 1366
9ff86fb7
SR
1367 buff[offset] = 2; // msg-type
1368 buff[offset + 1] = 0; // padding
1369
1370 // offset + 2 and offset + 3 are encoding count
1371
1372 var i, j = offset + 4, cnt = 0;
b1dee947
SR
1373 for (i = 0; i < encodings.length; i++) {
1374 if (encodings[i][0] === "Cursor" && !local_cursor) {
1375 Util.Debug("Skipping Cursor pseudo-encoding");
1376 } else if (encodings[i][0] === "TIGHT" && !true_color) {
1377 // TODO: remove this when we have tight+non-true-color
1378 Util.Warn("Skipping tight as it is only supported with true color");
1379 } else {
9ff86fb7
SR
1380 var enc = encodings[i][1];
1381 buff[j] = enc >> 24;
1382 buff[j + 1] = enc >> 16;
1383 buff[j + 2] = enc >> 8;
1384 buff[j + 3] = enc;
1385
1386 j += 4;
1387 cnt++;
b1dee947
SR
1388 }
1389 }
a7a89626 1390
9ff86fb7
SR
1391 buff[offset + 2] = cnt >> 8;
1392 buff[offset + 3] = cnt;
a7a89626 1393
9ff86fb7 1394 sock._sQlen += j - offset;
b1dee947 1395 },
a679a97d 1396
9ff86fb7
SR
1397 fbUpdateRequests: function (sock, cleanDirty, fb_width, fb_height) {
1398 var offsetIncrement = 0;
b1dee947
SR
1399
1400 var cb = cleanDirty.cleanBox;
1401 var w, h;
1402 if (cb.w > 0 && cb.h > 0) {
1403 w = typeof cb.w === "undefined" ? fb_width : cb.w;
1404 h = typeof cb.h === "undefined" ? fb_height : cb.h;
1405 // Request incremental for clean box
9ff86fb7 1406 RFB.messages.fbUpdateRequest(sock, 1, cb.x, cb.y, w, h);
a7a89626 1407 }
b1dee947
SR
1408
1409 for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
1410 var db = cleanDirty.dirtyBoxes[i];
1411 // Force all (non-incremental) for dirty box
1412 w = typeof db.w === "undefined" ? fb_width : db.w;
1413 h = typeof db.h === "undefined" ? fb_height : db.h;
9ff86fb7 1414 RFB.messages.fbUpdateRequest(sock, 0, db.x, db.y, w, h);
b1dee947 1415 }
b1dee947
SR
1416 },
1417
9ff86fb7
SR
1418 fbUpdateRequest: function (sock, incremental, x, y, w, h) {
1419 var buff = sock._sQ;
1420 var offset = sock._sQlen;
1421
b1dee947
SR
1422 if (typeof(x) === "undefined") { x = 0; }
1423 if (typeof(y) === "undefined") { y = 0; }
1424
9ff86fb7
SR
1425 buff[offset] = 3; // msg-type
1426 buff[offset + 1] = incremental;
1427
1428 buff[offset + 2] = (x >> 8) & 0xFF;
1429 buff[offset + 3] = x & 0xFF;
1430
1431 buff[offset + 4] = (y >> 8) & 0xFF;
1432 buff[offset + 5] = y & 0xFF;
1433
1434 buff[offset + 6] = (w >> 8) & 0xFF;
1435 buff[offset + 7] = w & 0xFF;
1436
1437 buff[offset + 8] = (h >> 8) & 0xFF;
1438 buff[offset + 9] = h & 0xFF;
b1dee947 1439
9ff86fb7 1440 sock._sQlen += 10;
a7a89626 1441 }
b1dee947
SR
1442 };
1443
1444 RFB.genDES = function (password, challenge) {
1445 var passwd = [];
1446 for (var i = 0; i < password.length; i++) {
1447 passwd.push(password.charCodeAt(i));
a7a89626 1448 }
b1dee947
SR
1449 return (new DES(passwd)).encrypt(challenge);
1450 };
1451
1452 RFB.extract_data_uri = function (arr) {
1453 return ";base64," + Base64.encode(arr);
1454 };
1455
1456 RFB.encodingHandlers = {
1457 RAW: function () {
1458 if (this._FBU.lines === 0) {
1459 this._FBU.lines = this._FBU.height;
a7a89626 1460 }
a7a89626 1461
b1dee947
SR
1462 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
1463 if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
1464 var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
1465 var curr_height = Math.min(this._FBU.lines,
1466 Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
1467 this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
1468 curr_height, this._sock.get_rQ(),
1469 this._sock.get_rQi());
1470 this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
1471 this._FBU.lines -= curr_height;
1472
1473 if (this._FBU.lines > 0) {
1474 this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
a7a89626 1475 } else {
b1dee947
SR
1476 this._FBU.rects--;
1477 this._FBU.bytes = 0;
a7a89626 1478 }
b1dee947
SR
1479
1480 return true;
1481 },
1482
1483 COPYRECT: function () {
1484 this._FBU.bytes = 4;
1485 if (this._sock.rQwait("COPYRECT", 4)) { return false; }
1486 this._display.renderQ_push({
1487 'type': 'copy',
1488 'old_x': this._sock.rQshift16(),
1489 'old_y': this._sock.rQshift16(),
1490 'x': this._FBU.x,
1491 'y': this._FBU.y,
1492 'width': this._FBU.width,
1493 'height': this._FBU.height
1494 });
1495 this._FBU.rects--;
1496 this._FBU.bytes = 0;
1497 return true;
1498 },
1499
1500 RRE: function () {
1501 var color;
1502 if (this._FBU.subrects === 0) {
1503 this._FBU.bytes = 4 + this._fb_Bpp;
1504 if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
1505 this._FBU.subrects = this._sock.rQshift32();
1506 color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
1507 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1508 }
1509
1510 while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
1511 color = this._sock.rQshiftBytes(this._fb_Bpp);
1512 var x = this._sock.rQshift16();
1513 var y = this._sock.rQshift16();
1514 var width = this._sock.rQshift16();
1515 var height = this._sock.rQshift16();
1516 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1517 this._FBU.subrects--;
1518 }
1519
1520 if (this._FBU.subrects > 0) {
1521 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
1522 this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
1523 } else {
1524 this._FBU.rects--;
1525 this._FBU.bytes = 0;
1526 }
1527
1528 return true;
1529 },
1530
1531 HEXTILE: function () {
1532 var rQ = this._sock.get_rQ();
1533 var rQi = this._sock.get_rQi();
1534
1535 if (this._FBU.tiles === 0) {
1536 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
1537 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
1538 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
1539 this._FBU.tiles = this._FBU.total_tiles;
1540 }
1541
1542 while (this._FBU.tiles > 0) {
1543 this._FBU.bytes = 1;
1544 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
1545 var subencoding = rQ[rQi]; // Peek
1546 if (subencoding > 30) { // Raw
1547 this._fail("Disconnected: illegal hextile subencoding " + subencoding);
1548 return false;
1549 }
1550
1551 var subrects = 0;
1552 var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
1553 var tile_x = curr_tile % this._FBU.tiles_x;
1554 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
1555 var x = this._FBU.x + tile_x * 16;
1556 var y = this._FBU.y + tile_y * 16;
1557 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
1558 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
1559
1560 // Figure out how much we are expecting
1561 if (subencoding & 0x01) { // Raw
1562 this._FBU.bytes += w * h * this._fb_Bpp;
1563 } else {
1564 if (subencoding & 0x02) { // Background
1565 this._FBU.bytes += this._fb_Bpp;
1566 }
1567 if (subencoding & 0x04) { // Foreground
1568 this._FBU.bytes += this._fb_Bpp;
1569 }
1570 if (subencoding & 0x08) { // AnySubrects
1571 this._FBU.bytes++; // Since we aren't shifting it off
1572 if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
1573 subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
1574 if (subencoding & 0x10) { // SubrectsColoured
1575 this._FBU.bytes += subrects * (this._fb_Bpp + 2);
1576 } else {
1577 this._FBU.bytes += subrects * 2;
1578 }
1579 }
1580 }
1581
1582 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
1583
1584 // We know the encoding and have a whole tile
1585 this._FBU.subencoding = rQ[rQi];
1586 rQi++;
1587 if (this._FBU.subencoding === 0) {
1588 if (this._FBU.lastsubencoding & 0x01) {
1589 // Weird: ignore blanks are RAW
1590 Util.Debug(" Ignoring blank after RAW");
a7a89626 1591 } else {
df89129f 1592 this._display.fillRect(x, y, w, h, this._FBU.background);
b1dee947
SR
1593 }
1594 } else if (this._FBU.subencoding & 0x01) { // Raw
1595 this._display.blitImage(x, y, w, h, rQ, rQi);
1596 rQi += this._FBU.bytes - 1;
1597 } else {
1598 if (this._FBU.subencoding & 0x02) { // Background
38781d93
SR
1599 if (this._fb_Bpp == 1) {
1600 this._FBU.background = rQ[rQi];
1601 } else {
1602 // fb_Bpp is 4
1603 this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1604 }
b1dee947 1605 rQi += this._fb_Bpp;
a7a89626 1606 }
b1dee947 1607 if (this._FBU.subencoding & 0x04) { // Foreground
38781d93
SR
1608 if (this._fb_Bpp == 1) {
1609 this._FBU.foreground = rQ[rQi];
1610 } else {
1611 // this._fb_Bpp is 4
1612 this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1613 }
b1dee947
SR
1614 rQi += this._fb_Bpp;
1615 }
1616
1617 this._display.startTile(x, y, w, h, this._FBU.background);
1618 if (this._FBU.subencoding & 0x08) { // AnySubrects
1619 subrects = rQ[rQi];
1620 rQi++;
1621
1622 for (var s = 0; s < subrects; s++) {
1623 var color;
1624 if (this._FBU.subencoding & 0x10) { // SubrectsColoured
38781d93
SR
1625 if (this._fb_Bpp === 1) {
1626 color = rQ[rQi];
1627 } else {
1628 // _fb_Bpp is 4
1629 color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1630 }
b1dee947
SR
1631 rQi += this._fb_Bpp;
1632 } else {
1633 color = this._FBU.foreground;
1634 }
1635 var xy = rQ[rQi];
1636 rQi++;
1637 var sx = (xy >> 4);
1638 var sy = (xy & 0x0f);
a7a89626 1639
b1dee947
SR
1640 var wh = rQ[rQi];
1641 rQi++;
1642 var sw = (wh >> 4) + 1;
1643 var sh = (wh & 0x0f) + 1;
a7a89626 1644
b1dee947
SR
1645 this._display.subTile(sx, sy, sw, sh, color);
1646 }
1647 }
1648 this._display.finishTile();
a7a89626 1649 }
b1dee947
SR
1650 this._sock.set_rQi(rQi);
1651 this._FBU.lastsubencoding = this._FBU.subencoding;
1652 this._FBU.bytes = 0;
1653 this._FBU.tiles--;
a7a89626 1654 }
d065cad9 1655
b1dee947
SR
1656 if (this._FBU.tiles === 0) {
1657 this._FBU.rects--;
1658 }
1659
1660 return true;
1661 },
1662
1663 getTightCLength: function (arr) {
1664 var header = 1, data = 0;
1665 data += arr[0] & 0x7f;
1666 if (arr[0] & 0x80) {
1667 header++;
1668 data += (arr[1] & 0x7f) << 7;
1669 if (arr[1] & 0x80) {
1670 header++;
1671 data += arr[2] << 14;
1672 }
1673 }
1674 return [header, data];
1675 },
1676
1677 display_tight: function (isTightPNG) {
1678 if (this._fb_depth === 1) {
1679 this._fail("Tight protocol handler only implements true color mode");
1680 }
9b75bcaa 1681
b1dee947
SR
1682 this._FBU.bytes = 1; // compression-control byte
1683 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
9b75bcaa 1684
b1dee947
SR
1685 var checksum = function (data) {
1686 var sum = 0;
1687 for (var i = 0; i < data.length; i++) {
1688 sum += data[i];
1689 if (sum > 65536) sum -= 65536;
1690 }
1691 return sum;
1692 };
1693
1694 var resetStreams = 0;
1695 var streamId = -1;
1696 var decompress = function (data) {
1697 for (var i = 0; i < 4; i++) {
1698 if ((resetStreams >> i) & 1) {
1699 this._FBU.zlibs[i].reset();
1700 Util.Info("Reset zlib stream " + i);
1701 }
1702 }
de84e098 1703
6940936f
SR
1704 //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
1705 var uncompressed = this._FBU.zlibs[streamId].inflate(data, true);
1706 /*if (uncompressed.status !== 0) {
b1dee947 1707 Util.Error("Invalid data in zlib stream");
6940936f 1708 }*/
de84e098 1709
6940936f
SR
1710 //return uncompressed.data;
1711 return uncompressed;
b1dee947
SR
1712 }.bind(this);
1713
d1800d09 1714 var indexedToRGBX2Color = function (data, palette, width, height) {
b1dee947
SR
1715 // Convert indexed (palette based) image data to RGB
1716 // TODO: reduce number of calculations inside loop
d1800d09
SR
1717 var dest = this._destBuff;
1718 var w = Math.floor((width + 7) / 8);
1719 var w1 = Math.floor(width / 8);
1720
1721 /*for (var y = 0; y < height; y++) {
1722 var b, x, dp, sp;
1723 var yoffset = y * width;
1724 var ybitoffset = y * w;
1725 var xoffset, targetbyte;
1726 for (x = 0; x < w1; x++) {
1727 xoffset = yoffset + x * 8;
1728 targetbyte = data[ybitoffset + x];
1729 for (b = 7; b >= 0; b--) {
1730 dp = (xoffset + 7 - b) * 3;
1731 sp = (targetbyte >> b & 1) * 3;
1732 dest[dp] = palette[sp];
1733 dest[dp + 1] = palette[sp + 1];
1734 dest[dp + 2] = palette[sp + 2];
b1dee947 1735 }
d1800d09 1736 }
d065cad9 1737
d1800d09
SR
1738 xoffset = yoffset + x * 8;
1739 targetbyte = data[ybitoffset + x];
1740 for (b = 7; b >= 8 - width % 8; b--) {
1741 dp = (xoffset + 7 - b) * 3;
1742 sp = (targetbyte >> b & 1) * 3;
1743 dest[dp] = palette[sp];
1744 dest[dp + 1] = palette[sp + 1];
1745 dest[dp + 2] = palette[sp + 2];
1746 }
1747 }*/
1748
1749 for (var y = 0; y < height; y++) {
1750 var b, x, dp, sp;
1751 for (x = 0; x < w1; x++) {
1752 for (b = 7; b >= 0; b--) {
1753 dp = (y * width + x * 8 + 7 - b) * 4;
b1dee947
SR
1754 sp = (data[y * w + x] >> b & 1) * 3;
1755 dest[dp] = palette[sp];
1756 dest[dp + 1] = palette[sp + 1];
1757 dest[dp + 2] = palette[sp + 2];
d1800d09 1758 dest[dp + 3] = 255;
b1dee947
SR
1759 }
1760 }
d1800d09
SR
1761
1762 for (b = 7; b >= 8 - width % 8; b--) {
1763 dp = (y * width + x * 8 + 7 - b) * 4;
1764 sp = (data[y * w + x] >> b & 1) * 3;
1765 dest[dp] = palette[sp];
1766 dest[dp + 1] = palette[sp + 1];
1767 dest[dp + 2] = palette[sp + 2];
1768 dest[dp + 3] = 255;
e16ad2fd
JM
1769 }
1770 }
b1dee947
SR
1771
1772 return dest;
1773 }.bind(this);
1774
d1800d09
SR
1775 var indexedToRGBX = function (data, palette, width, height) {
1776 // Convert indexed (palette based) image data to RGB
1777 var dest = this._destBuff;
1778 var total = width * height * 4;
1779 for (var i = 0, j = 0; i < total; i += 4, j++) {
1780 var sp = data[j] * 3;
1781 dest[i] = palette[sp];
1782 dest[i + 1] = palette[sp + 1];
1783 dest[i + 2] = palette[sp + 2];
1784 dest[i + 3] = 255;
1785 }
1786
1787 return dest;
1788 }.bind(this);
1789
b1dee947
SR
1790 var rQ = this._sock.get_rQ();
1791 var rQi = this._sock.get_rQi();
d1800d09
SR
1792 var cmode, data;
1793 var cl_header, cl_data;
b1dee947
SR
1794
1795 var handlePalette = function () {
1796 var numColors = rQ[rQi + 2] + 1;
1797 var paletteSize = numColors * this._fb_depth;
1798 this._FBU.bytes += paletteSize;
1799 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
1800
1801 var bpp = (numColors <= 2) ? 1 : 8;
1802 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
1803 var raw = false;
1804 if (rowSize * this._FBU.height < 12) {
1805 raw = true;
d1800d09
SR
1806 cl_header = 0;
1807 cl_data = rowSize * this._FBU.height;
1808 //clength = [0, rowSize * this._FBU.height];
b1dee947 1809 } else {
d1800d09
SR
1810 // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
1811 var cl_offset = rQi + 3 + paletteSize;
1812 cl_header = 1;
1813 cl_data = 0;
1814 cl_data += rQ[cl_offset] & 0x7f;
1815 if (rQ[cl_offset] & 0x80) {
1816 cl_header++;
1817 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1818 if (rQ[cl_offset + 1] & 0x80) {
1819 cl_header++;
1820 cl_data += rQ[cl_offset + 2] << 14;
1821 }
1822 }
1823 // end inline getTightCLength
e16ad2fd 1824 }
b1dee947 1825
d1800d09 1826 this._FBU.bytes += cl_header + cl_data;
b1dee947
SR
1827 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1828
1829 // Shift ctl, filter id, num colors, palette entries, and clength off
1830 this._sock.rQskipBytes(3);
d1800d09
SR
1831 //var palette = this._sock.rQshiftBytes(paletteSize);
1832 this._sock.rQshiftTo(this._paletteBuff, paletteSize);
1833 this._sock.rQskipBytes(cl_header);
b1dee947
SR
1834
1835 if (raw) {
d1800d09 1836 data = this._sock.rQshiftBytes(cl_data);
b1dee947 1837 } else {
d1800d09 1838 data = decompress(this._sock.rQshiftBytes(cl_data));
b1dee947
SR
1839 }
1840
1841 // Convert indexed (palette based) image data to RGB
d1800d09
SR
1842 var rgbx;
1843 if (numColors == 2) {
1844 rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
1845
1846 /*this._display.renderQ_push({
1847 'type': 'blitRgbx',
1848 'data': rgbx,
1849 'x': this._FBU.x,
1850 'y': this._FBU.y,
1851 'width': this._FBU.width,
1852 'height': this._FBU.height
1853 });*/
1854 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0);
1855 } else {
1856 rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
1857
1858 /*this._display.renderQ_push({
1859 'type': 'blitRgbx',
1860 'data': rgbx,
1861 'x': this._FBU.x,
1862 'y': this._FBU.y,
1863 'width': this._FBU.width,
1864 'height': this._FBU.height
1865 });*/
1866 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0);
1867 }
b1dee947 1868
b1dee947
SR
1869
1870 return true;
1871 }.bind(this);
1872
1873 var handleCopy = function () {
1874 var raw = false;
1875 var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
1876 if (uncompressedSize < 12) {
1877 raw = true;
d1800d09
SR
1878 cl_header = 0;
1879 cl_data = uncompressedSize;
b1dee947 1880 } else {
d1800d09
SR
1881 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
1882 var cl_offset = rQi + 1;
1883 cl_header = 1;
1884 cl_data = 0;
1885 cl_data += rQ[cl_offset] & 0x7f;
1886 if (rQ[cl_offset] & 0x80) {
1887 cl_header++;
1888 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1889 if (rQ[cl_offset + 1] & 0x80) {
1890 cl_header++;
1891 cl_data += rQ[cl_offset + 2] << 14;
1892 }
1893 }
1894 // end inline getTightCLength
b1dee947 1895 }
d1800d09 1896 this._FBU.bytes = 1 + cl_header + cl_data;
b1dee947
SR
1897 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1898
1899 // Shift ctl, clength off
d1800d09 1900 this._sock.rQshiftBytes(1 + cl_header);
b1dee947
SR
1901
1902 if (raw) {
d1800d09 1903 data = this._sock.rQshiftBytes(cl_data);
b1dee947 1904 } else {
d1800d09 1905 data = decompress(this._sock.rQshiftBytes(cl_data));
e16ad2fd 1906 }
b1dee947
SR
1907
1908 this._display.renderQ_push({
1909 'type': 'blitRgb',
1910 'data': data,
1911 'x': this._FBU.x,
1912 'y': this._FBU.y,
1913 'width': this._FBU.width,
1914 'height': this._FBU.height
1915 });
1916
1917 return true;
1918 }.bind(this);
1919
1920 var ctl = this._sock.rQpeek8();
1921
1922 // Keep tight reset bits
1923 resetStreams = ctl & 0xF;
1924
1925 // Figure out filter
1926 ctl = ctl >> 4;
1927 streamId = ctl & 0x3;
1928
1929 if (ctl === 0x08) cmode = "fill";
1930 else if (ctl === 0x09) cmode = "jpeg";
1931 else if (ctl === 0x0A) cmode = "png";
1932 else if (ctl & 0x04) cmode = "filter";
1933 else if (ctl < 0x04) cmode = "copy";
1934 else return this._fail("Illegal tight compression received, ctl: " + ctl);
1935
1936 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
1937 return this._fail("filter/copy received in tightPNG mode");
e16ad2fd 1938 }
de84e098 1939
b1dee947
SR
1940 switch (cmode) {
1941 // fill use fb_depth because TPIXELs drop the padding byte
1942 case "fill": // TPIXEL
1943 this._FBU.bytes += this._fb_depth;
1944 break;
1945 case "jpeg": // max clength
1946 this._FBU.bytes += 3;
1947 break;
1948 case "png": // max clength
1949 this._FBU.bytes += 3;
1950 break;
1951 case "filter": // filter id + num colors if palette
1952 this._FBU.bytes += 2;
1953 break;
1954 case "copy":
1955 break;
1956 }
d065cad9 1957
b1dee947
SR
1958 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1959
1960 // Determine FBU.bytes
1961 switch (cmode) {
1962 case "fill":
1963 this._sock.rQskip8(); // shift off ctl
1964 var color = this._sock.rQshiftBytes(this._fb_depth);
1965 this._display.renderQ_push({
1966 'type': 'fill',
1967 'x': this._FBU.x,
1968 'y': this._FBU.y,
1969 'width': this._FBU.width,
1970 'height': this._FBU.height,
1971 'color': [color[2], color[1], color[0]]
1972 });
1973 break;
1974 case "png":
1975 case "jpeg":
d1800d09
SR
1976 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
1977 var cl_offset = rQi + 1;
1978 cl_header = 1;
1979 cl_data = 0;
1980 cl_data += rQ[cl_offset] & 0x7f;
1981 if (rQ[cl_offset] & 0x80) {
1982 cl_header++;
1983 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1984 if (rQ[cl_offset + 1] & 0x80) {
1985 cl_header++;
1986 cl_data += rQ[cl_offset + 2] << 14;
1987 }
1988 }
1989 // end inline getTightCLength
1990 this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
b1dee947
SR
1991 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1992
1993 // We have everything, render it
d1800d09 1994 this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
b1dee947
SR
1995 var img = new Image();
1996 img.src = "data: image/" + cmode +
d1800d09 1997 RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
b1dee947
SR
1998 this._display.renderQ_push({
1999 'type': 'img',
2000 'img': img,
2001 'x': this._FBU.x,
2002 'y': this._FBU.y
2003 });
2004 img = null;
2005 break;
2006 case "filter":
2007 var filterId = rQ[rQi + 1];
2008 if (filterId === 1) {
2009 if (!handlePalette()) { return false; }
2010 } else {
2011 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
2012 // Filter 2, Gradient is valid but not use if jpeg is enabled
2013 // TODO(directxman12): why aren't we just calling '_fail' here
2014 throw new Error("Unsupported tight subencoding received, filter: " + filterId);
2015 }
2016 break;
2017 case "copy":
2018 if (!handleCopy()) { return false; }
2019 break;
2020 }
5ca5e2d8 2021
de84e098 2022
b1dee947
SR
2023 this._FBU.bytes = 0;
2024 this._FBU.rects--;
c514fd5e 2025
b1dee947
SR
2026 return true;
2027 },
de84e098 2028
b1dee947
SR
2029 TIGHT: function () { return this._encHandlers.display_tight(false); },
2030 TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
a7a89626 2031
b1dee947
SR
2032 last_rect: function () {
2033 this._FBU.rects = 0;
2034 return true;
2035 },
a7a89626 2036
798340b9 2037 handle_FB_resize: function () {
2038 this._fb_width = this._FBU.width;
2039 this._fb_height = this._FBU.height;
d1800d09 2040 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
798340b9 2041 this._display.resize(this._fb_width, this._fb_height);
2042 this._onFBResize(this, this._fb_width, this._fb_height);
2043 this._timing.fbu_rt_start = (new Date()).getTime();
2044
2045 this._FBU.bytes = 0;
2046 this._FBU.rects -= 1;
2047 return true;
2048 },
2049
2050 ExtendedDesktopSize: function () {
b0ec6509 2051 this._FBU.bytes = 1;
798340b9 2052 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
b0ec6509 2053
2054 this._supportsSetDesktopSize = true;
2055 var number_of_screens = this._sock.rQpeek8();
2056
2057 this._FBU.bytes = 4 + (number_of_screens * 16);
798340b9 2058 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
b0ec6509 2059
2060 this._sock.rQskipBytes(1); // number-of-screens
2061 this._sock.rQskipBytes(3); // padding
2062
0442e153 2063 for (var i = 0; i < number_of_screens; i += 1) {
b0ec6509 2064 // Save the id and flags of the first screen
0442e153 2065 if (i === 0) {
b0ec6509 2066 this._screen_id = this._sock.rQshiftBytes(4); // id
2067 this._sock.rQskipBytes(2); // x-position
2068 this._sock.rQskipBytes(2); // y-position
2069 this._sock.rQskipBytes(2); // width
2070 this._sock.rQskipBytes(2); // height
2071 this._screen_flags = this._sock.rQshiftBytes(4); // flags
2072 } else {
2073 this._sock.rQskipBytes(16);
2074 }
2075 }
2076
798340b9 2077 /*
2078 * The x-position indicates the reason for the change:
2079 *
2080 * 0 - server resized on its own
2081 * 1 - this client requested the resize
2082 * 2 - another client requested the resize
2083 */
b0ec6509 2084
798340b9 2085 // We need to handle errors when we requested the resize.
0442e153 2086 if (this._FBU.x === 1 && this._FBU.y !== 0) {
798340b9 2087 var msg = "";
2088 // The y-position indicates the status code from the server
2089 switch (this._FBU.y) {
2090 case 1:
2091 msg = "Resize is administratively prohibited";
2092 break;
2093 case 2:
2094 msg = "Out of resources";
2095 break;
2096 case 3:
2097 msg = "Invalid screen layout";
2098 break;
2099 default:
2100 msg = "Unknown reason";
2101 break;
2102 }
2103 Util.Info("Server did not accept the resize request: " + msg);
2104 return true;
2105 }
b0ec6509 2106
798340b9 2107 this._encHandlers.handle_FB_resize();
b0ec6509 2108 return true;
2109 },
2110
b1dee947 2111 DesktopSize: function () {
798340b9 2112 this._encHandlers.handle_FB_resize();
b1dee947
SR
2113 return true;
2114 },
2115
2116 Cursor: function () {
2117 Util.Debug(">> set_cursor");
2118 var x = this._FBU.x; // hotspot-x
2119 var y = this._FBU.y; // hotspot-y
2120 var w = this._FBU.width;
2121 var h = this._FBU.height;
2122
2123 var pixelslength = w * h * this._fb_Bpp;
2124 var masklength = Math.floor((w + 7) / 8) * h;
8db09746 2125
b1dee947
SR
2126 this._FBU.bytes = pixelslength + masklength;
2127 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
8db09746 2128
b1dee947
SR
2129 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
2130 this._sock.rQshiftBytes(masklength),
2131 x, y, w, h);
a7a89626 2132
b1dee947
SR
2133 this._FBU.bytes = 0;
2134 this._FBU.rects--;
2135
2136 Util.Debug("<< set_cursor");
2137 return true;
2138 },
2139
2140 JPEG_quality_lo: function () {
2141 Util.Error("Server sent jpeg_quality pseudo-encoding");
2142 },
2143
2144 compress_lo: function () {
2145 Util.Error("Server sent compress level pseudo-encoding");
2146 }
2147 };
2148})();