]> git.proxmox.com Git - mirror_novnc.git/blame - include/rfb.js
Make sure Pako always has enough room
[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; }
f00193e0
SR
1486 this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
1487 this._FBU.x, this._FBU.y, this._FBU.width,
1488 this._FBU.height);
1489
b1dee947
SR
1490 this._FBU.rects--;
1491 this._FBU.bytes = 0;
1492 return true;
1493 },
1494
1495 RRE: function () {
1496 var color;
1497 if (this._FBU.subrects === 0) {
1498 this._FBU.bytes = 4 + this._fb_Bpp;
1499 if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
1500 this._FBU.subrects = this._sock.rQshift32();
1501 color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
1502 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1503 }
1504
1505 while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
1506 color = this._sock.rQshiftBytes(this._fb_Bpp);
1507 var x = this._sock.rQshift16();
1508 var y = this._sock.rQshift16();
1509 var width = this._sock.rQshift16();
1510 var height = this._sock.rQshift16();
1511 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1512 this._FBU.subrects--;
1513 }
1514
1515 if (this._FBU.subrects > 0) {
1516 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
1517 this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
1518 } else {
1519 this._FBU.rects--;
1520 this._FBU.bytes = 0;
1521 }
1522
1523 return true;
1524 },
1525
1526 HEXTILE: function () {
1527 var rQ = this._sock.get_rQ();
1528 var rQi = this._sock.get_rQi();
1529
1530 if (this._FBU.tiles === 0) {
1531 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
1532 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
1533 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
1534 this._FBU.tiles = this._FBU.total_tiles;
1535 }
1536
1537 while (this._FBU.tiles > 0) {
1538 this._FBU.bytes = 1;
1539 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
1540 var subencoding = rQ[rQi]; // Peek
1541 if (subencoding > 30) { // Raw
1542 this._fail("Disconnected: illegal hextile subencoding " + subencoding);
1543 return false;
1544 }
1545
1546 var subrects = 0;
1547 var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
1548 var tile_x = curr_tile % this._FBU.tiles_x;
1549 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
1550 var x = this._FBU.x + tile_x * 16;
1551 var y = this._FBU.y + tile_y * 16;
1552 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
1553 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
1554
1555 // Figure out how much we are expecting
1556 if (subencoding & 0x01) { // Raw
1557 this._FBU.bytes += w * h * this._fb_Bpp;
1558 } else {
1559 if (subencoding & 0x02) { // Background
1560 this._FBU.bytes += this._fb_Bpp;
1561 }
1562 if (subencoding & 0x04) { // Foreground
1563 this._FBU.bytes += this._fb_Bpp;
1564 }
1565 if (subencoding & 0x08) { // AnySubrects
1566 this._FBU.bytes++; // Since we aren't shifting it off
1567 if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
1568 subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
1569 if (subencoding & 0x10) { // SubrectsColoured
1570 this._FBU.bytes += subrects * (this._fb_Bpp + 2);
1571 } else {
1572 this._FBU.bytes += subrects * 2;
1573 }
1574 }
1575 }
1576
1577 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
1578
1579 // We know the encoding and have a whole tile
1580 this._FBU.subencoding = rQ[rQi];
1581 rQi++;
1582 if (this._FBU.subencoding === 0) {
1583 if (this._FBU.lastsubencoding & 0x01) {
1584 // Weird: ignore blanks are RAW
1585 Util.Debug(" Ignoring blank after RAW");
a7a89626 1586 } else {
df89129f 1587 this._display.fillRect(x, y, w, h, this._FBU.background);
b1dee947
SR
1588 }
1589 } else if (this._FBU.subencoding & 0x01) { // Raw
1590 this._display.blitImage(x, y, w, h, rQ, rQi);
1591 rQi += this._FBU.bytes - 1;
1592 } else {
1593 if (this._FBU.subencoding & 0x02) { // Background
38781d93
SR
1594 if (this._fb_Bpp == 1) {
1595 this._FBU.background = rQ[rQi];
1596 } else {
1597 // fb_Bpp is 4
1598 this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1599 }
b1dee947 1600 rQi += this._fb_Bpp;
a7a89626 1601 }
b1dee947 1602 if (this._FBU.subencoding & 0x04) { // Foreground
38781d93
SR
1603 if (this._fb_Bpp == 1) {
1604 this._FBU.foreground = rQ[rQi];
1605 } else {
1606 // this._fb_Bpp is 4
1607 this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1608 }
b1dee947
SR
1609 rQi += this._fb_Bpp;
1610 }
1611
1612 this._display.startTile(x, y, w, h, this._FBU.background);
1613 if (this._FBU.subencoding & 0x08) { // AnySubrects
1614 subrects = rQ[rQi];
1615 rQi++;
1616
1617 for (var s = 0; s < subrects; s++) {
1618 var color;
1619 if (this._FBU.subencoding & 0x10) { // SubrectsColoured
38781d93
SR
1620 if (this._fb_Bpp === 1) {
1621 color = rQ[rQi];
1622 } else {
1623 // _fb_Bpp is 4
1624 color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
1625 }
b1dee947
SR
1626 rQi += this._fb_Bpp;
1627 } else {
1628 color = this._FBU.foreground;
1629 }
1630 var xy = rQ[rQi];
1631 rQi++;
1632 var sx = (xy >> 4);
1633 var sy = (xy & 0x0f);
a7a89626 1634
b1dee947
SR
1635 var wh = rQ[rQi];
1636 rQi++;
1637 var sw = (wh >> 4) + 1;
1638 var sh = (wh & 0x0f) + 1;
a7a89626 1639
b1dee947
SR
1640 this._display.subTile(sx, sy, sw, sh, color);
1641 }
1642 }
1643 this._display.finishTile();
a7a89626 1644 }
b1dee947
SR
1645 this._sock.set_rQi(rQi);
1646 this._FBU.lastsubencoding = this._FBU.subencoding;
1647 this._FBU.bytes = 0;
1648 this._FBU.tiles--;
a7a89626 1649 }
d065cad9 1650
b1dee947
SR
1651 if (this._FBU.tiles === 0) {
1652 this._FBU.rects--;
1653 }
1654
1655 return true;
1656 },
1657
1658 getTightCLength: function (arr) {
1659 var header = 1, data = 0;
1660 data += arr[0] & 0x7f;
1661 if (arr[0] & 0x80) {
1662 header++;
1663 data += (arr[1] & 0x7f) << 7;
1664 if (arr[1] & 0x80) {
1665 header++;
1666 data += arr[2] << 14;
1667 }
1668 }
1669 return [header, data];
1670 },
1671
1672 display_tight: function (isTightPNG) {
1673 if (this._fb_depth === 1) {
1674 this._fail("Tight protocol handler only implements true color mode");
1675 }
9b75bcaa 1676
b1dee947
SR
1677 this._FBU.bytes = 1; // compression-control byte
1678 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
9b75bcaa 1679
b1dee947
SR
1680 var checksum = function (data) {
1681 var sum = 0;
1682 for (var i = 0; i < data.length; i++) {
1683 sum += data[i];
1684 if (sum > 65536) sum -= 65536;
1685 }
1686 return sum;
1687 };
1688
1689 var resetStreams = 0;
1690 var streamId = -1;
c802d931 1691 var decompress = function (data, expected) {
b1dee947
SR
1692 for (var i = 0; i < 4; i++) {
1693 if ((resetStreams >> i) & 1) {
1694 this._FBU.zlibs[i].reset();
c802d931 1695 console.debug('RESET!');
b1dee947
SR
1696 Util.Info("Reset zlib stream " + i);
1697 }
1698 }
de84e098 1699
6940936f 1700 //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
c802d931 1701 var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
6940936f 1702 /*if (uncompressed.status !== 0) {
b1dee947 1703 Util.Error("Invalid data in zlib stream");
6940936f 1704 }*/
de84e098 1705
6940936f
SR
1706 //return uncompressed.data;
1707 return uncompressed;
b1dee947
SR
1708 }.bind(this);
1709
d1800d09 1710 var indexedToRGBX2Color = function (data, palette, width, height) {
b1dee947
SR
1711 // Convert indexed (palette based) image data to RGB
1712 // TODO: reduce number of calculations inside loop
d1800d09
SR
1713 var dest = this._destBuff;
1714 var w = Math.floor((width + 7) / 8);
1715 var w1 = Math.floor(width / 8);
1716
1717 /*for (var y = 0; y < height; y++) {
1718 var b, x, dp, sp;
1719 var yoffset = y * width;
1720 var ybitoffset = y * w;
1721 var xoffset, targetbyte;
1722 for (x = 0; x < w1; x++) {
1723 xoffset = yoffset + x * 8;
1724 targetbyte = data[ybitoffset + x];
1725 for (b = 7; b >= 0; b--) {
1726 dp = (xoffset + 7 - b) * 3;
1727 sp = (targetbyte >> b & 1) * 3;
1728 dest[dp] = palette[sp];
1729 dest[dp + 1] = palette[sp + 1];
1730 dest[dp + 2] = palette[sp + 2];
b1dee947 1731 }
d1800d09 1732 }
d065cad9 1733
d1800d09
SR
1734 xoffset = yoffset + x * 8;
1735 targetbyte = data[ybitoffset + x];
1736 for (b = 7; b >= 8 - width % 8; b--) {
1737 dp = (xoffset + 7 - b) * 3;
1738 sp = (targetbyte >> b & 1) * 3;
1739 dest[dp] = palette[sp];
1740 dest[dp + 1] = palette[sp + 1];
1741 dest[dp + 2] = palette[sp + 2];
1742 }
1743 }*/
1744
1745 for (var y = 0; y < height; y++) {
1746 var b, x, dp, sp;
1747 for (x = 0; x < w1; x++) {
1748 for (b = 7; b >= 0; b--) {
1749 dp = (y * width + x * 8 + 7 - b) * 4;
b1dee947
SR
1750 sp = (data[y * w + x] >> b & 1) * 3;
1751 dest[dp] = palette[sp];
1752 dest[dp + 1] = palette[sp + 1];
1753 dest[dp + 2] = palette[sp + 2];
d1800d09 1754 dest[dp + 3] = 255;
b1dee947
SR
1755 }
1756 }
d1800d09
SR
1757
1758 for (b = 7; b >= 8 - width % 8; b--) {
1759 dp = (y * width + x * 8 + 7 - b) * 4;
1760 sp = (data[y * w + x] >> b & 1) * 3;
1761 dest[dp] = palette[sp];
1762 dest[dp + 1] = palette[sp + 1];
1763 dest[dp + 2] = palette[sp + 2];
1764 dest[dp + 3] = 255;
e16ad2fd
JM
1765 }
1766 }
b1dee947
SR
1767
1768 return dest;
1769 }.bind(this);
1770
d1800d09
SR
1771 var indexedToRGBX = function (data, palette, width, height) {
1772 // Convert indexed (palette based) image data to RGB
1773 var dest = this._destBuff;
1774 var total = width * height * 4;
1775 for (var i = 0, j = 0; i < total; i += 4, j++) {
1776 var sp = data[j] * 3;
1777 dest[i] = palette[sp];
1778 dest[i + 1] = palette[sp + 1];
1779 dest[i + 2] = palette[sp + 2];
1780 dest[i + 3] = 255;
1781 }
1782
1783 return dest;
1784 }.bind(this);
1785
b1dee947 1786 var rQi = this._sock.get_rQi();
89bdc8ce 1787 var rQ = this._sock.rQwhole();
d1800d09
SR
1788 var cmode, data;
1789 var cl_header, cl_data;
b1dee947
SR
1790
1791 var handlePalette = function () {
1792 var numColors = rQ[rQi + 2] + 1;
1793 var paletteSize = numColors * this._fb_depth;
1794 this._FBU.bytes += paletteSize;
1795 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
1796
1797 var bpp = (numColors <= 2) ? 1 : 8;
1798 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
1799 var raw = false;
1800 if (rowSize * this._FBU.height < 12) {
1801 raw = true;
d1800d09
SR
1802 cl_header = 0;
1803 cl_data = rowSize * this._FBU.height;
1804 //clength = [0, rowSize * this._FBU.height];
b1dee947 1805 } else {
d1800d09
SR
1806 // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
1807 var cl_offset = rQi + 3 + paletteSize;
1808 cl_header = 1;
1809 cl_data = 0;
1810 cl_data += rQ[cl_offset] & 0x7f;
1811 if (rQ[cl_offset] & 0x80) {
1812 cl_header++;
1813 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1814 if (rQ[cl_offset + 1] & 0x80) {
1815 cl_header++;
1816 cl_data += rQ[cl_offset + 2] << 14;
1817 }
1818 }
1819 // end inline getTightCLength
e16ad2fd 1820 }
b1dee947 1821
d1800d09 1822 this._FBU.bytes += cl_header + cl_data;
b1dee947
SR
1823 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1824
1825 // Shift ctl, filter id, num colors, palette entries, and clength off
1826 this._sock.rQskipBytes(3);
d1800d09
SR
1827 //var palette = this._sock.rQshiftBytes(paletteSize);
1828 this._sock.rQshiftTo(this._paletteBuff, paletteSize);
1829 this._sock.rQskipBytes(cl_header);
b1dee947
SR
1830
1831 if (raw) {
d1800d09 1832 data = this._sock.rQshiftBytes(cl_data);
b1dee947 1833 } else {
c802d931 1834 data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
b1dee947
SR
1835 }
1836
1837 // Convert indexed (palette based) image data to RGB
d1800d09
SR
1838 var rgbx;
1839 if (numColors == 2) {
1840 rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
f00193e0 1841 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
d1800d09
SR
1842 } else {
1843 rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
f00193e0 1844 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
d1800d09 1845 }
b1dee947 1846
b1dee947
SR
1847
1848 return true;
1849 }.bind(this);
1850
1851 var handleCopy = function () {
1852 var raw = false;
1853 var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
1854 if (uncompressedSize < 12) {
1855 raw = true;
d1800d09
SR
1856 cl_header = 0;
1857 cl_data = uncompressedSize;
b1dee947 1858 } else {
d1800d09
SR
1859 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
1860 var cl_offset = rQi + 1;
1861 cl_header = 1;
1862 cl_data = 0;
1863 cl_data += rQ[cl_offset] & 0x7f;
1864 if (rQ[cl_offset] & 0x80) {
1865 cl_header++;
1866 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1867 if (rQ[cl_offset + 1] & 0x80) {
1868 cl_header++;
1869 cl_data += rQ[cl_offset + 2] << 14;
1870 }
1871 }
1872 // end inline getTightCLength
b1dee947 1873 }
d1800d09 1874 this._FBU.bytes = 1 + cl_header + cl_data;
b1dee947
SR
1875 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1876
1877 // Shift ctl, clength off
d1800d09 1878 this._sock.rQshiftBytes(1 + cl_header);
b1dee947
SR
1879
1880 if (raw) {
d1800d09 1881 data = this._sock.rQshiftBytes(cl_data);
b1dee947 1882 } else {
c802d931 1883 data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
e16ad2fd 1884 }
b1dee947 1885
f00193e0 1886 this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
b1dee947
SR
1887
1888 return true;
1889 }.bind(this);
1890
1891 var ctl = this._sock.rQpeek8();
1892
1893 // Keep tight reset bits
1894 resetStreams = ctl & 0xF;
1895
1896 // Figure out filter
1897 ctl = ctl >> 4;
1898 streamId = ctl & 0x3;
1899
1900 if (ctl === 0x08) cmode = "fill";
1901 else if (ctl === 0x09) cmode = "jpeg";
1902 else if (ctl === 0x0A) cmode = "png";
1903 else if (ctl & 0x04) cmode = "filter";
1904 else if (ctl < 0x04) cmode = "copy";
1905 else return this._fail("Illegal tight compression received, ctl: " + ctl);
1906
1907 if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
1908 return this._fail("filter/copy received in tightPNG mode");
e16ad2fd 1909 }
de84e098 1910
b1dee947
SR
1911 switch (cmode) {
1912 // fill use fb_depth because TPIXELs drop the padding byte
1913 case "fill": // TPIXEL
1914 this._FBU.bytes += this._fb_depth;
1915 break;
1916 case "jpeg": // max clength
1917 this._FBU.bytes += 3;
1918 break;
1919 case "png": // max clength
1920 this._FBU.bytes += 3;
1921 break;
1922 case "filter": // filter id + num colors if palette
1923 this._FBU.bytes += 2;
1924 break;
1925 case "copy":
1926 break;
1927 }
d065cad9 1928
b1dee947
SR
1929 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1930
1931 // Determine FBU.bytes
1932 switch (cmode) {
1933 case "fill":
f00193e0
SR
1934 // skip ctl byte
1935 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, [rQ[rQi + 3], rQ[rQi + 2], rQ[rQi + 1]], false);
1936 this._sock.rQskipBytes(4);
b1dee947
SR
1937 break;
1938 case "png":
1939 case "jpeg":
d1800d09
SR
1940 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
1941 var cl_offset = rQi + 1;
1942 cl_header = 1;
1943 cl_data = 0;
1944 cl_data += rQ[cl_offset] & 0x7f;
1945 if (rQ[cl_offset] & 0x80) {
1946 cl_header++;
1947 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
1948 if (rQ[cl_offset + 1] & 0x80) {
1949 cl_header++;
1950 cl_data += rQ[cl_offset + 2] << 14;
1951 }
1952 }
1953 // end inline getTightCLength
1954 this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
b1dee947
SR
1955 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
1956
1957 // We have everything, render it
d1800d09 1958 this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
b1dee947
SR
1959 var img = new Image();
1960 img.src = "data: image/" + cmode +
d1800d09 1961 RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
b1dee947
SR
1962 this._display.renderQ_push({
1963 'type': 'img',
1964 'img': img,
1965 'x': this._FBU.x,
1966 'y': this._FBU.y
1967 });
1968 img = null;
1969 break;
1970 case "filter":
1971 var filterId = rQ[rQi + 1];
1972 if (filterId === 1) {
1973 if (!handlePalette()) { return false; }
1974 } else {
1975 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
1976 // Filter 2, Gradient is valid but not use if jpeg is enabled
1977 // TODO(directxman12): why aren't we just calling '_fail' here
1978 throw new Error("Unsupported tight subencoding received, filter: " + filterId);
1979 }
1980 break;
1981 case "copy":
1982 if (!handleCopy()) { return false; }
1983 break;
1984 }
5ca5e2d8 1985
de84e098 1986
b1dee947
SR
1987 this._FBU.bytes = 0;
1988 this._FBU.rects--;
c514fd5e 1989
b1dee947
SR
1990 return true;
1991 },
de84e098 1992
b1dee947
SR
1993 TIGHT: function () { return this._encHandlers.display_tight(false); },
1994 TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
a7a89626 1995
b1dee947
SR
1996 last_rect: function () {
1997 this._FBU.rects = 0;
1998 return true;
1999 },
a7a89626 2000
798340b9 2001 handle_FB_resize: function () {
2002 this._fb_width = this._FBU.width;
2003 this._fb_height = this._FBU.height;
d1800d09 2004 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
798340b9 2005 this._display.resize(this._fb_width, this._fb_height);
2006 this._onFBResize(this, this._fb_width, this._fb_height);
2007 this._timing.fbu_rt_start = (new Date()).getTime();
2008
2009 this._FBU.bytes = 0;
2010 this._FBU.rects -= 1;
2011 return true;
2012 },
2013
2014 ExtendedDesktopSize: function () {
b0ec6509 2015 this._FBU.bytes = 1;
798340b9 2016 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
b0ec6509 2017
2018 this._supportsSetDesktopSize = true;
2019 var number_of_screens = this._sock.rQpeek8();
2020
2021 this._FBU.bytes = 4 + (number_of_screens * 16);
798340b9 2022 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
b0ec6509 2023
2024 this._sock.rQskipBytes(1); // number-of-screens
2025 this._sock.rQskipBytes(3); // padding
2026
0442e153 2027 for (var i = 0; i < number_of_screens; i += 1) {
b0ec6509 2028 // Save the id and flags of the first screen
0442e153 2029 if (i === 0) {
b0ec6509 2030 this._screen_id = this._sock.rQshiftBytes(4); // id
2031 this._sock.rQskipBytes(2); // x-position
2032 this._sock.rQskipBytes(2); // y-position
2033 this._sock.rQskipBytes(2); // width
2034 this._sock.rQskipBytes(2); // height
2035 this._screen_flags = this._sock.rQshiftBytes(4); // flags
2036 } else {
2037 this._sock.rQskipBytes(16);
2038 }
2039 }
2040
798340b9 2041 /*
2042 * The x-position indicates the reason for the change:
2043 *
2044 * 0 - server resized on its own
2045 * 1 - this client requested the resize
2046 * 2 - another client requested the resize
2047 */
b0ec6509 2048
798340b9 2049 // We need to handle errors when we requested the resize.
0442e153 2050 if (this._FBU.x === 1 && this._FBU.y !== 0) {
798340b9 2051 var msg = "";
2052 // The y-position indicates the status code from the server
2053 switch (this._FBU.y) {
2054 case 1:
2055 msg = "Resize is administratively prohibited";
2056 break;
2057 case 2:
2058 msg = "Out of resources";
2059 break;
2060 case 3:
2061 msg = "Invalid screen layout";
2062 break;
2063 default:
2064 msg = "Unknown reason";
2065 break;
2066 }
2067 Util.Info("Server did not accept the resize request: " + msg);
2068 return true;
2069 }
b0ec6509 2070
798340b9 2071 this._encHandlers.handle_FB_resize();
b0ec6509 2072 return true;
2073 },
2074
b1dee947 2075 DesktopSize: function () {
798340b9 2076 this._encHandlers.handle_FB_resize();
b1dee947
SR
2077 return true;
2078 },
2079
2080 Cursor: function () {
2081 Util.Debug(">> set_cursor");
2082 var x = this._FBU.x; // hotspot-x
2083 var y = this._FBU.y; // hotspot-y
2084 var w = this._FBU.width;
2085 var h = this._FBU.height;
2086
2087 var pixelslength = w * h * this._fb_Bpp;
2088 var masklength = Math.floor((w + 7) / 8) * h;
8db09746 2089
b1dee947
SR
2090 this._FBU.bytes = pixelslength + masklength;
2091 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
8db09746 2092
b1dee947
SR
2093 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
2094 this._sock.rQshiftBytes(masklength),
2095 x, y, w, h);
a7a89626 2096
b1dee947
SR
2097 this._FBU.bytes = 0;
2098 this._FBU.rects--;
2099
2100 Util.Debug("<< set_cursor");
2101 return true;
2102 },
2103
2104 JPEG_quality_lo: function () {
2105 Util.Error("Server sent jpeg_quality pseudo-encoding");
2106 },
2107
2108 compress_lo: function () {
2109 Util.Error("Server sent compress level pseudo-encoding");
2110 }
2111 };
2112})();