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