]> git.proxmox.com Git - mirror_novnc.git/blame - core/rfb.js
Move resize handling in to RFB object
[mirror_novnc.git] / core / rfb.js
CommitLineData
a7a89626
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
5b20d338 4 * Copyright (C) 2017 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
6d6f0db0 13import * as Log from './util/logging.js';
6d6f0db0 14import { decodeUTF8 } from './util/strings.js';
8d1f0a3d 15import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
e89eef94 16import EventTargetMixin from './util/eventtarget.js';
3ae0bb09 17import Display from "./display.js";
c1e2785f
SM
18import Keyboard from "./input/keyboard.js";
19import Mouse from "./input/mouse.js";
3ae0bb09
SR
20import Websock from "./websock.js";
21import Base64 from "./base64.js";
22import DES from "./des.js";
23import KeyTable from "./input/keysym.js";
24import XtScancode from "./input/xtscancodes.js";
fba220c6 25import Inflator from "./inflator.js";
f8ec2df2 26import { encodings, encodingName } from "./encodings.js";
e89eef94 27import "./util/polyfill.js";
3ae0bb09 28
b1dee947 29/*jslint white: false, browser: true */
ae510306 30/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
a7a89626 31
68e09edc
PO
32// How many seconds to wait for a disconnect to finish
33var DISCONNECT_TIMEOUT = 3;
34
2f4516f2
PO
35export default function RFB(target, url, options) {
36 if (!target) {
37 throw Error("Must specify target");
38 }
39 if (!url) {
40 throw Error("Must specify URL");
ae510306
SR
41 }
42
3d7bb020 43 this._target = target;
2f4516f2 44 this._url = url;
ae510306 45
c7d08d72 46 // Connection details
4e1c5435 47 options = options || {};
2f4516f2
PO
48 this._rfb_credentials = options.credentials || {};
49 this._shared = 'shared' in options ? !!options.shared : true;
50 this._repeaterID = options.repeaterID || '';
ae510306 51
c7d08d72 52 // Internal state
3bb12056 53 this._rfb_connection_state = '';
c00ee156 54 this._rfb_init_state = '';
ae510306 55 this._rfb_auth_scheme = '';
d472f3f1 56 this._rfb_clean_disconnect = true;
ae510306 57
c7d08d72
PO
58 // Server capabilities
59 this._rfb_version = 0;
60 this._rfb_max_version = 3.8;
ae510306
SR
61 this._rfb_tightvnc = false;
62 this._rfb_xvp_ver = 0;
63
c7d08d72
PO
64 this._fb_width = 0;
65 this._fb_height = 0;
66
67 this._fb_name = "";
ae510306 68
9b84f516 69 this._capabilities = { power: false };
cd523e8f 70
c7d08d72 71 this._supportsFence = false;
ae510306 72
c7d08d72
PO
73 this._supportsContinuousUpdates = false;
74 this._enabledContinuousUpdates = false;
75
76 this._supportsSetDesktopSize = false;
77 this._screen_id = 0;
78 this._screen_flags = 0;
79
80 this._qemuExtKeyEventSupported = false;
81
82 // Internal objects
ae510306
SR
83 this._sock = null; // Websock object
84 this._display = null; // Display object
d9ca5e5b 85 this._flushing = false; // Display flushing state
ae510306
SR
86 this._keyboard = null; // Keyboard input handler object
87 this._mouse = null; // Mouse input handler object
ae510306 88
c7d08d72
PO
89 // Timers
90 this._disconnTimer = null; // disconnection timer
9b84f516 91 this._resizeTimeout = null; // resize rate limiting
ae510306 92
c7d08d72
PO
93 // Decoder states and stats
94 this._encHandlers = {};
95 this._encStats = {};
ae510306 96
ae510306
SR
97 this._FBU = {
98 rects: 0,
c7d08d72 99 subrects: 0, // RRE and HEXTILE
ae510306
SR
100 lines: 0, // RAW
101 tiles: 0, // HEXTILE
102 bytes: 0,
103 x: 0,
104 y: 0,
105 width: 0,
106 height: 0,
107 encoding: 0,
108 subencoding: -1,
109 background: null,
c7d08d72 110 zlibs: [] // TIGHT zlib streams
ae510306 111 };
c7d08d72
PO
112 for (var i = 0; i < 4; i++) {
113 this._FBU.zlibs[i] = new Inflator();
114 }
b1dee947 115
ae510306
SR
116 this._destBuff = null;
117 this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
b1dee947 118
ae510306 119 this._rre_chunk_sz = 100;
b1dee947 120
ae510306
SR
121 this._timing = {
122 last_fbu: 0,
123 fbu_total: 0,
124 fbu_total_cnt: 0,
125 full_fbu_total: 0,
126 full_fbu_cnt: 0,
b1dee947 127
ae510306
SR
128 fbu_rt_start: 0,
129 fbu_rt_total: 0,
130 fbu_rt_cnt: 0,
131 pixels: 0
132 };
133
ae510306
SR
134 // Mouse state
135 this._mouse_buttonMask = 0;
136 this._mouse_arr = [];
137 this._viewportDragging = false;
138 this._viewportDragPos = {};
139 this._viewportHasMoved = false;
140
1d6ff4a3
PO
141 // Bound event handlers
142 this._eventHandlers = {
143 focusCanvas: this._focusCanvas.bind(this),
9b84f516 144 windowResize: this._windowResize.bind(this),
1d6ff4a3
PO
145 };
146
ae510306 147 // main setup
6d6f0db0 148 Log.Debug(">> RFB.constructor");
ae510306 149
9b84f516
PO
150 // Create DOM elements
151 this._screen = document.createElement('div');
152 this._screen.style.display = 'flex';
153 this._screen.style.width = '100%';
154 this._screen.style.height = '100%';
155 this._screen.style.overflow = 'auto';
156 this._screen.style.backgroundColor = 'rgb(40, 40, 40)';
157 this._canvas = document.createElement('canvas');
158 this._canvas.style.margin = 'auto';
159 // Some browsers add an outline on focus
160 this._canvas.style.outline = 'none';
161 // IE miscalculates width without this :(
162 this._canvas.style.flexShrink = '0';
163 this._canvas.width = 0;
164 this._canvas.height = 0;
165 this._canvas.tabIndex = -1;
166 this._screen.appendChild(this._canvas);
45808617 167
ae510306 168 // populate encHandlers with bound versions
cd230363
SM
169 this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
170 this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
171 this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
172 this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
173 this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this);
174
175 this._encHandlers[encodings.pseudoEncodingDesktopSize] = RFB.encodingHandlers.DesktopSize.bind(this);
176 this._encHandlers[encodings.pseudoEncodingLastRect] = RFB.encodingHandlers.last_rect.bind(this);
177 this._encHandlers[encodings.pseudoEncodingCursor] = RFB.encodingHandlers.Cursor.bind(this);
178 this._encHandlers[encodings.pseudoEncodingQEMUExtendedKeyEvent] = RFB.encodingHandlers.QEMUExtendedKeyEvent.bind(this);
179 this._encHandlers[encodings.pseudoEncodingExtendedDesktopSize] = RFB.encodingHandlers.ExtendedDesktopSize.bind(this);
ae510306
SR
180
181 // NB: nothing that needs explicit teardown should be done
182 // before this point, since this can throw an exception
183 try {
9b84f516 184 this._display = new Display(this._canvas);
ae510306 185 } catch (exc) {
6d6f0db0 186 Log.Error("Display exception: " + exc);
ae510306
SR
187 throw exc;
188 }
747b4623 189 this._display.onflush = this._onFlush.bind(this);
c7d08d72 190 this._display.clear();
ae510306 191
9b84f516 192 this._keyboard = new Keyboard(this._canvas);
747b4623 193 this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
ae510306 194
9b84f516 195 this._mouse = new Mouse(this._canvas);
747b4623
PO
196 this._mouse.onmousebutton = this._handleMouseButton.bind(this);
197 this._mouse.onmousemove = this._handleMouseMove.bind(this);
ae510306
SR
198
199 this._sock = new Websock();
200 this._sock.on('message', this._handle_message.bind(this));
201 this._sock.on('open', function () {
c2a4d3ef 202 if ((this._rfb_connection_state === 'connecting') &&
c00ee156
SM
203 (this._rfb_init_state === '')) {
204 this._rfb_init_state = 'ProtocolVersion';
6d6f0db0 205 Log.Debug("Starting VNC handshake");
ae510306 206 } else {
d472f3f1
SM
207 this._fail("Unexpected server connection while " +
208 this._rfb_connection_state);
ae510306
SR
209 }
210 }.bind(this));
211 this._sock.on('close', function (e) {
ddcb60c3 212 Log.Debug("WebSocket on-close event");
ae510306
SR
213 var msg = "";
214 if (e.code) {
d472f3f1 215 msg = "(code: " + e.code;
ae510306
SR
216 if (e.reason) {
217 msg += ", reason: " + e.reason;
b1dee947 218 }
ae510306
SR
219 msg += ")";
220 }
c00ee156 221 switch (this._rfb_connection_state) {
c2a4d3ef 222 case 'connecting':
d472f3f1 223 this._fail("Connection closed " + msg);
c00ee156 224 break;
b45905ab
SM
225 case 'connected':
226 // Handle disconnects that were initiated server-side
227 this._updateConnectionState('disconnecting');
228 this._updateConnectionState('disconnected');
229 break;
d472f3f1
SM
230 case 'disconnecting':
231 // Normal disconnection path
232 this._updateConnectionState('disconnected');
233 break;
c00ee156 234 case 'disconnected':
d472f3f1
SM
235 this._fail("Unexpected server disconnect " +
236 "when already disconnected " + msg);
c00ee156
SM
237 break;
238 default:
d472f3f1
SM
239 this._fail("Unexpected server disconnect before connecting " +
240 msg);
c00ee156 241 break;
a679a97d 242 }
ae510306
SR
243 this._sock.off('close');
244 }.bind(this));
245 this._sock.on('error', function (e) {
6d6f0db0 246 Log.Warn("WebSocket on-error event");
ae510306 247 });
8db09746 248
2f4516f2
PO
249 // Slight delay of the actual connection so that the caller has
250 // time to set up callbacks
251 setTimeout(this._updateConnectionState.bind(this, 'connecting'));
ae510306 252
6d6f0db0 253 Log.Debug("<< RFB.constructor");
ae510306 254};
a7a89626 255
6d6f0db0 256RFB.prototype = {
747b4623 257 // ===== PROPERTIES =====
6d6f0db0 258
0460e5fd 259 dragViewport: false,
a201bfc5 260 focusOnClick: true,
a7a89626 261
747b4623
PO
262 _viewOnly: false,
263 get viewOnly() { return this._viewOnly; },
264 set viewOnly(viewOnly) {
265 this._viewOnly = viewOnly;
266
267 if (this._rfb_connection_state === "connecting" ||
268 this._rfb_connection_state === "connected") {
269 if (viewOnly) {
270 this._keyboard.ungrab();
271 this._mouse.ungrab();
272 } else {
273 this._keyboard.grab();
274 this._mouse.grab();
275 }
276 }
6d6f0db0
SR
277 },
278
747b4623
PO
279 get capabilities() { return this._capabilities; },
280
281 get touchButton() { return this._mouse.touchButton; },
282 set touchButton(button) { this._mouse.touchButton = button; },
283
9b84f516
PO
284 _clipViewport: false,
285 get clipViewport() { return this._clipViewport; },
286 set clipViewport(viewport) {
287 this._clipViewport = viewport;
288 this._updateClip();
289 },
747b4623 290
9b84f516
PO
291 _scaleViewport: false,
292 get scaleViewport() { return this._scaleViewport; },
293 set scaleViewport(scale) {
294 this._scaleViewport = scale;
295 // Scaling trumps clipping, so we may need to adjust
296 // clipping when enabling or disabling scaling
297 if (scale && this._clipViewport) {
298 this._updateClip();
299 }
300 this._updateScale();
301 if (!scale && this._clipViewport) {
302 this._updateClip();
303 }
304 },
747b4623 305
9b84f516
PO
306 _resizeSession: false,
307 get resizeSession() { return this._resizeSession; },
308 set resizeSession(resize) {
309 this._resizeSession = resize;
310 if (resize) {
311 this._requestRemoteResize();
312 }
313 },
a80aa416 314
747b4623
PO
315 // ===== PUBLIC METHODS =====
316
6d6f0db0
SR
317 disconnect: function () {
318 this._updateConnectionState('disconnecting');
319 this._sock.off('error');
320 this._sock.off('message');
321 this._sock.off('open');
322 },
323
430f00d6
PO
324 sendCredentials: function (creds) {
325 this._rfb_credentials = creds;
6d6f0db0
SR
326 setTimeout(this._init_msg.bind(this), 0);
327 },
328
329 sendCtrlAltDel: function () {
15a62695 330 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
6d6f0db0
SR
331 Log.Info("Sending Ctrl-Alt-Del");
332
94f5cf05
PO
333 this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
334 this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
335 this.sendKey(KeyTable.XK_Delete, "Delete", true);
336 this.sendKey(KeyTable.XK_Delete, "Delete", false);
337 this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
338 this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
6d6f0db0
SR
339 },
340
cd523e8f
PO
341 machineShutdown: function () {
342 this._xvpOp(1, 2);
6d6f0db0
SR
343 },
344
cd523e8f
PO
345 machineReboot: function () {
346 this._xvpOp(1, 3);
6d6f0db0
SR
347 },
348
cd523e8f
PO
349 machineReset: function () {
350 this._xvpOp(1, 4);
6d6f0db0
SR
351 },
352
353 // Send a key press. If 'down' is not specified then send a down key
354 // followed by an up key.
94f5cf05 355 sendKey: function (keysym, code, down) {
15a62695 356 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
94f5cf05
PO
357
358 if (down === undefined) {
359 this.sendKey(keysym, code, true);
360 this.sendKey(keysym, code, false);
15a62695 361 return;
94f5cf05
PO
362 }
363
be70fe0a 364 var scancode = XtScancode[code];
94f5cf05 365
be70fe0a 366 if (this._qemuExtKeyEventSupported && scancode) {
459ed008
PO
367 // 0 is NoSymbol
368 keysym = keysym || 0;
369
94f5cf05
PO
370 Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
371
372 RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
373 } else {
459ed008 374 if (!keysym) {
15a62695 375 return;
459ed008 376 }
6d6f0db0
SR
377 Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
378 RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
6d6f0db0 379 }
6d6f0db0
SR
380 },
381
9b84f516
PO
382 focus: function () {
383 this._canvas.focus();
b9854a5c
PO
384 },
385
9b84f516
PO
386 blur: function () {
387 this._canvas.blur();
b9854a5c
PO
388 },
389
9b84f516
PO
390 clipboardPasteFrom: function (text) {
391 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
392 RFB.messages.clientCutText(this._sock, text);
6d6f0db0 393 },
b1dee947 394
747b4623 395 // ===== PRIVATE METHODS =====
b0ec6509 396
6d6f0db0
SR
397 _connect: function () {
398 Log.Debug(">> RFB.connect");
b0ec6509 399
5b4e5d01 400 Log.Info("connecting to " + this._url);
b1dee947 401
6d6f0db0
SR
402 try {
403 // WebSocket.onopen transitions to the RFB init states
f8318361 404 this._sock.open(this._url, ['binary']);
6d6f0db0
SR
405 } catch (e) {
406 if (e.name === 'SyntaxError') {
d472f3f1 407 this._fail("Invalid host or port (" + e + ")");
b1dee947 408 } else {
d472f3f1 409 this._fail("Error when opening socket (" + e + ")");
159ad55d 410 }
6d6f0db0 411 }
e3efeb32 412
9b84f516
PO
413 // Make our elements part of the page
414 this._target.appendChild(this._screen);
415
416 // Monitor size changes of the screen
417 // FIXME: Use ResizeObserver, or hidden overflow
418 window.addEventListener('resize', this._eventHandlers.windowResize);
419
2afda544 420 // Always grab focus on some kind of click event
9b84f516
PO
421 this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
422 this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
2afda544 423
6d6f0db0
SR
424 Log.Debug("<< RFB.connect");
425 },
426
427 _disconnect: function () {
428 Log.Debug(">> RFB.disconnect");
9b84f516
PO
429 this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
430 this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
431 window.removeEventListener('resize', this._eventHandlers.windowResize);
432 this._keyboard.ungrab();
433 this._mouse.ungrab();
6d6f0db0
SR
434 this._sock.close();
435 this._print_stats();
9b84f516
PO
436 this._target.removeChild(this._screen);
437 clearTimeout(this._resizeTimeout);
6d6f0db0
SR
438 Log.Debug("<< RFB.disconnect");
439 },
440
6d6f0db0 441 _print_stats: function () {
c3386227
PO
442 var stats = this._encStats;
443
6d6f0db0 444 Log.Info("Encoding stats for this connection:");
c3386227
PO
445 Object.keys(stats).forEach(function (key) {
446 var s = stats[key];
6d6f0db0 447 if (s[0] + s[1] > 0) {
c3386227 448 Log.Info(" " + encodingName(key) + ": " + s[0] + " rects");
b1dee947 449 }
c3386227 450 });
a7a89626 451
6d6f0db0 452 Log.Info("Encoding stats since page load:");
c3386227
PO
453 Object.keys(stats).forEach(function (key) {
454 var s = stats[key];
455 Log.Info(" " + encodingName(key) + ": " + s[1] + " rects");
456 });
6d6f0db0
SR
457 },
458
2afda544
PO
459 _focusCanvas: function(event) {
460 // Respect earlier handlers' request to not do side-effects
1d6ff4a3
PO
461 if (event.defaultPrevented) {
462 return;
463 }
464
b8dfb983 465 if (!this.focusOnClick) {
1d6ff4a3
PO
466 return;
467 }
468
9b84f516
PO
469 this.focus();
470 },
471
472 _windowResize: function (event) {
473 // If the window resized then our screen element might have
474 // as well. Update the viewport dimensions.
475 window.requestAnimationFrame(function () {
476 this._updateClip();
477 this._updateScale();
478 }.bind(this));
479
480 if (this._resizeSession) {
481 // Request changing the resolution of the remote display to
482 // the size of the local browser viewport.
483
484 // In order to not send multiple requests before the browser-resize
485 // is finished we wait 0.5 seconds before sending the request.
486 clearTimeout(this._resizeTimeout);
487 this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
488 }
489 },
490
491 // Update state of clipping in Display object, and make sure the
492 // configured viewport matches the current screen size
493 _updateClip: function () {
494 var cur_clip = this._display.clipViewport;
495 var new_clip = this._clipViewport;
496
497 if (this._scaleViewport) {
498 // Disable viewport clipping if we are scaling
499 new_clip = false;
500 }
501
502 if (cur_clip !== new_clip) {
503 this._display.clipViewport = new_clip;
504 }
505
506 if (new_clip) {
507 // When clipping is enabled, the screen is limited to
508 // the size of the container.
509 let size = this._screenSize();
510 this._display.viewportChangeSize(size.w, size.h);
511 this._fixScrollbars();
512 }
513 },
514
515 _updateScale: function () {
516 if (!this._scaleViewport) {
517 this._display.scale = 1.0;
518 } else {
519 let size = this._screenSize();
520 this._display.autoscale(size.w, size.h);
521 }
522 this._fixScrollbars();
523 },
524
525 // Requests a change of remote desktop size. This message is an extension
526 // and may only be sent if we have received an ExtendedDesktopSize message
527 _requestRemoteResize: function () {
528 clearTimeout(this._resizeTimeout);
529 this._resizeTimeout = null;
530
531 if (!this._resizeSession || this._viewOnly ||
532 !this._supportsSetDesktopSize) {
533 return;
534 }
535
536 let size = this._screenSize();
537 RFB.messages.setDesktopSize(this._sock, size.w, size.h,
538 this._screen_id, this._screen_flags);
539
540 Log.Debug('Requested new desktop size: ' +
541 size.w + 'x' + size.h);
542 },
543
544 // Gets the the size of the available screen
545 _screenSize: function () {
546 return { w: this._screen.offsetWidth,
547 h: this._screen.offsetHeight };
548 },
549
550 _fixScrollbars: function () {
551 // This is a hack because Chrome screws up the calculation
552 // for when scrollbars are needed. So to fix it we temporarily
553 // toggle them off and on.
554 var orig = this._screen.style.overflow;
555 this._screen.style.overflow = 'hidden';
556 // Force Chrome to recalculate the layout by asking for
557 // an element's dimensions
558 this._screen.getBoundingClientRect();
559 this._screen.style.overflow = orig;
2afda544
PO
560 },
561
6d6f0db0
SR
562 /*
563 * Connection states:
564 * connecting
565 * connected
566 * disconnecting
567 * disconnected - permanent state
568 */
569 _updateConnectionState: function (state) {
570 var oldstate = this._rfb_connection_state;
571
572 if (state === oldstate) {
573 Log.Debug("Already in state '" + state + "', ignoring");
574 return;
575 }
576
577 // The 'disconnected' state is permanent for each RFB object
578 if (oldstate === 'disconnected') {
579 Log.Error("Tried changing state of a disconnected RFB object");
580 return;
581 }
582
583 // Ensure proper transitions before doing anything
584 switch (state) {
585 case 'connected':
586 if (oldstate !== 'connecting') {
587 Log.Error("Bad transition to connected state, " +
588 "previous connection state: " + oldstate);
589 return;
b1dee947 590 }
6d6f0db0 591 break;
a7a89626 592
6d6f0db0
SR
593 case 'disconnected':
594 if (oldstate !== 'disconnecting') {
595 Log.Error("Bad transition to disconnected state, " +
596 "previous connection state: " + oldstate);
597 return;
598 }
599 break;
a7a89626 600
6d6f0db0
SR
601 case 'connecting':
602 if (oldstate !== '') {
603 Log.Error("Bad transition to connecting state, " +
604 "previous connection state: " + oldstate);
605 return;
606 }
607 break;
b1dee947 608
6d6f0db0
SR
609 case 'disconnecting':
610 if (oldstate !== 'connected' && oldstate !== 'connecting') {
611 Log.Error("Bad transition to disconnecting state, " +
612 "previous connection state: " + oldstate);
613 return;
614 }
615 break;
a679a97d 616
6d6f0db0
SR
617 default:
618 Log.Error("Unknown connection state: " + state);
3bb12056 619 return;
6d6f0db0 620 }
b1dee947 621
6d6f0db0 622 // State change actions
3bb12056 623
6d6f0db0 624 this._rfb_connection_state = state;
b2e961d4 625
6d6f0db0
SR
626 var smsg = "New state '" + state + "', was '" + oldstate + "'.";
627 Log.Debug(smsg);
b2e961d4 628
6d6f0db0
SR
629 if (this._disconnTimer && state !== 'disconnecting') {
630 Log.Debug("Clearing disconnect timer");
631 clearTimeout(this._disconnTimer);
632 this._disconnTimer = null;
3bb12056 633
6d6f0db0
SR
634 // make sure we don't get a double event
635 this._sock.off('close');
636 }
b2e961d4 637
6d6f0db0 638 switch (state) {
6d6f0db0
SR
639 case 'connecting':
640 this._connect();
641 break;
b2e961d4 642
ee5cae9f
SM
643 case 'connected':
644 var event = new CustomEvent("connect", { detail: {} });
645 this.dispatchEvent(event);
646 break;
647
6d6f0db0
SR
648 case 'disconnecting':
649 this._disconnect();
b2e961d4 650
6d6f0db0 651 this._disconnTimer = setTimeout(function () {
d472f3f1 652 Log.Error("Disconnection timed out.");
6d6f0db0 653 this._updateConnectionState('disconnected');
68e09edc 654 }.bind(this), DISCONNECT_TIMEOUT * 1000);
6d6f0db0 655 break;
ee5cae9f
SM
656
657 case 'disconnected':
d472f3f1
SM
658 event = new CustomEvent(
659 "disconnect", { detail:
660 { clean: this._rfb_clean_disconnect } });
ee5cae9f
SM
661 this.dispatchEvent(event);
662 break;
6d6f0db0
SR
663 }
664 },
665
666 /* Print errors and disconnect
667 *
d472f3f1 668 * The parameter 'details' is used for information that
6d6f0db0
SR
669 * should be logged but not sent to the user interface.
670 */
d472f3f1 671 _fail: function (details) {
6d6f0db0
SR
672 switch (this._rfb_connection_state) {
673 case 'disconnecting':
d472f3f1 674 Log.Error("Failed when disconnecting: " + details);
6d6f0db0
SR
675 break;
676 case 'connected':
d472f3f1 677 Log.Error("Failed while connected: " + details);
6d6f0db0
SR
678 break;
679 case 'connecting':
d472f3f1 680 Log.Error("Failed when connecting: " + details);
6d6f0db0
SR
681 break;
682 default:
d472f3f1 683 Log.Error("RFB failure: " + details);
6d6f0db0
SR
684 break;
685 }
d472f3f1 686 this._rfb_clean_disconnect = false; //This is sent to the UI
6d6f0db0
SR
687
688 // Transition to disconnected without waiting for socket to close
689 this._updateConnectionState('disconnecting');
690 this._updateConnectionState('disconnected');
691
692 return false;
693 },
694
832be262
PO
695 _setCapability: function (cap, val) {
696 this._capabilities[cap] = val;
e89eef94
PO
697 var event = new CustomEvent("capabilities",
698 { detail: { capabilities: this._capabilities } });
699 this.dispatchEvent(event);
6d6f0db0 700 },
b1dee947 701
6d6f0db0
SR
702 _handle_message: function () {
703 if (this._sock.rQlen() === 0) {
704 Log.Warn("handle_message called on an empty receive queue");
705 return;
706 }
b1dee947 707
6d6f0db0
SR
708 switch (this._rfb_connection_state) {
709 case 'disconnected':
710 Log.Error("Got data while disconnected");
711 break;
712 case 'connected':
713 while (true) {
714 if (this._flushing) {
715 break;
716 }
717 if (!this._normal_msg()) {
718 break;
719 }
720 if (this._sock.rQlen() === 0) {
721 break;
722 }
723 }
724 break;
725 default:
726 this._init_msg();
727 break;
728 }
729 },
3bb12056 730
d0703d1b
PO
731 _handleKeyEvent: function (keysym, code, down) {
732 this.sendKey(keysym, code, down);
6d6f0db0 733 },
06a9ef0c 734
6d6f0db0
SR
735 _handleMouseButton: function (x, y, down, bmask) {
736 if (down) {
737 this._mouse_buttonMask |= bmask;
738 } else {
270bdbd7 739 this._mouse_buttonMask &= ~bmask;
6d6f0db0 740 }
a7127fee 741
0460e5fd 742 if (this.dragViewport) {
6d6f0db0
SR
743 if (down && !this._viewportDragging) {
744 this._viewportDragging = true;
745 this._viewportDragPos = {'x': x, 'y': y};
898cd32c 746 this._viewportHasMoved = false;
a7127fee 747
6d6f0db0 748 // Skip sending mouse events
b1dee947 749 return;
6d6f0db0
SR
750 } else {
751 this._viewportDragging = false;
8db09746 752
898cd32c
PO
753 // If we actually performed a drag then we are done
754 // here and should not send any mouse events
755 if (this._viewportHasMoved) {
756 return;
6d6f0db0 757 }
898cd32c
PO
758
759 // Otherwise we treat this as a mouse click event.
760 // Send the button down event here, as the button up
761 // event is sent at the end of this function.
762 RFB.messages.pointerEvent(this._sock,
763 this._display.absX(x),
764 this._display.absY(y),
765 bmask);
b1dee947 766 }
6d6f0db0 767 }
8db09746 768
747b4623 769 if (this._viewOnly) { return; } // View only, skip mouse events
8f06673a 770
6d6f0db0
SR
771 if (this._rfb_connection_state !== 'connected') { return; }
772 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
773 },
8db09746 774
6d6f0db0
SR
775 _handleMouseMove: function (x, y) {
776 if (this._viewportDragging) {
777 var deltaX = this._viewportDragPos.x - x;
778 var deltaY = this._viewportDragPos.y - y;
8db09746 779
6d6f0db0
SR
780 // The goal is to trigger on a certain physical width, the
781 // devicePixelRatio brings us a bit closer but is not optimal.
782 var dragThreshold = 10 * (window.devicePixelRatio || 1);
8db09746 783
6d6f0db0
SR
784 if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
785 Math.abs(deltaY) > dragThreshold)) {
786 this._viewportHasMoved = true;
27e77d46 787
6d6f0db0
SR
788 this._viewportDragPos = {'x': x, 'y': y};
789 this._display.viewportChangePos(deltaX, deltaY);
b50f3406 790 }
8db09746 791
6d6f0db0
SR
792 // Skip sending mouse events
793 return;
794 }
b1dee947 795
747b4623 796 if (this._viewOnly) { return; } // View only, skip mouse events
b1dee947 797
6d6f0db0
SR
798 if (this._rfb_connection_state !== 'connected') { return; }
799 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
800 },
b1dee947 801
6d6f0db0 802 // Message Handlers
27e77d46 803
6d6f0db0
SR
804 _negotiate_protocol_version: function () {
805 if (this._sock.rQlen() < 12) {
d472f3f1 806 return this._fail("Received incomplete protocol version.");
6d6f0db0 807 }
32df3fdb 808
6d6f0db0
SR
809 var sversion = this._sock.rQshiftStr(12).substr(4, 7);
810 Log.Info("Server ProtocolVersion: " + sversion);
811 var is_repeater = 0;
812 switch (sversion) {
813 case "000.000": // UltraVNC repeater
814 is_repeater = 1;
815 break;
816 case "003.003":
817 case "003.006": // UltraVNC
818 case "003.889": // Apple Remote Desktop
819 this._rfb_version = 3.3;
820 break;
821 case "003.007":
822 this._rfb_version = 3.7;
823 break;
824 case "003.008":
825 case "004.000": // Intel AMT KVM
826 case "004.001": // RealVNC 4.6
827 case "005.000": // RealVNC 5.3
828 this._rfb_version = 3.8;
829 break;
830 default:
d472f3f1 831 return this._fail("Invalid server version " + sversion);
6d6f0db0 832 }
8db09746 833
6d6f0db0 834 if (is_repeater) {
ebf1c0f9 835 var repeaterID = "ID:" + this._repeaterID;
6d6f0db0
SR
836 while (repeaterID.length < 250) {
837 repeaterID += "\0";
b1dee947 838 }
6d6f0db0
SR
839 this._sock.send_string(repeaterID);
840 return true;
841 }
b1dee947 842
6d6f0db0
SR
843 if (this._rfb_version > this._rfb_max_version) {
844 this._rfb_version = this._rfb_max_version;
845 }
b1dee947 846
6d6f0db0
SR
847 var cversion = "00" + parseInt(this._rfb_version, 10) +
848 ".00" + ((this._rfb_version * 10) % 10);
849 this._sock.send_string("RFB " + cversion + "\n");
850 Log.Debug('Sent ProtocolVersion: ' + cversion);
b1dee947 851
6d6f0db0
SR
852 this._rfb_init_state = 'Security';
853 },
b1dee947 854
6d6f0db0
SR
855 _negotiate_security: function () {
856 // Polyfill since IE and PhantomJS doesn't have
857 // TypedArray.includes()
858 function includes(item, array) {
859 for (var i = 0; i < array.length; i++) {
860 if (array[i] === item) {
861 return true;
862 }
b1dee947 863 }
6d6f0db0
SR
864 return false;
865 }
b1dee947 866
6d6f0db0
SR
867 if (this._rfb_version >= 3.7) {
868 // Server sends supported list, client decides
869 var num_types = this._sock.rQshift8();
870 if (this._sock.rQwait("security type", num_types, 1)) { return false; }
b1dee947 871
6d6f0db0 872 if (num_types === 0) {
d472f3f1 873 return this._handle_security_failure("no security types");
6d6f0db0
SR
874 }
875
876 var types = this._sock.rQshiftBytes(num_types);
877 Log.Debug("Server security types: " + types);
878
879 // Look for each auth in preferred order
880 this._rfb_auth_scheme = 0;
881 if (includes(1, types)) {
882 this._rfb_auth_scheme = 1; // None
883 } else if (includes(22, types)) {
884 this._rfb_auth_scheme = 22; // XVP
885 } else if (includes(16, types)) {
886 this._rfb_auth_scheme = 16; // Tight
887 } else if (includes(2, types)) {
888 this._rfb_auth_scheme = 2; // VNC Auth
889 } else {
d472f3f1 890 return this._fail("Unsupported security types (types: " + types + ")");
8db09746 891 }
b1dee947 892
6d6f0db0
SR
893 this._sock.send([this._rfb_auth_scheme]);
894 } else {
895 // Server decides
896 if (this._sock.rQwait("security scheme", 4)) { return false; }
897 this._rfb_auth_scheme = this._sock.rQshift32();
898 }
b1dee947 899
6d6f0db0
SR
900 this._rfb_init_state = 'Authentication';
901 Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
b1dee947 902
6d6f0db0
SR
903 return this._init_msg(); // jump to authentication
904 },
b1dee947 905
d472f3f1
SM
906 /*
907 * Get the security failure reason if sent from the server and
908 * send the 'securityfailure' event.
909 *
910 * - The optional parameter context can be used to add some extra
911 * context to the log output.
912 *
913 * - The optional parameter security_result_status can be used to
914 * add a custom status code to the event.
915 */
916 _handle_security_failure: function (context, security_result_status) {
917
918 if (typeof context === 'undefined') {
919 context = "";
920 } else {
921 context = " on " + context;
922 }
923
924 if (typeof security_result_status === 'undefined') {
925 security_result_status = 1; // fail
926 }
927
928 if (this._sock.rQwait("reason length", 4)) {
929 return false;
930 }
931 let strlen = this._sock.rQshift32();
932 let reason = "";
933
934 if (strlen > 0) {
935 if (this._sock.rQwait("reason", strlen, 8)) { return false; }
936 reason = this._sock.rQshiftStr(strlen);
937 }
938
939 if (reason !== "") {
940
941 let event = new CustomEvent(
942 "securityfailure",
943 { detail: { status: security_result_status, reason: reason } });
944 this.dispatchEvent(event);
945
946 return this._fail("Security negotiation failed" + context +
947 " (reason: " + reason + ")");
948 } else {
949
950 let event = new CustomEvent(
951 "securityfailure",
952 { detail: { status: security_result_status } });
953 this.dispatchEvent(event);
954
955 return this._fail("Security negotiation failed" + context);
956 }
957 },
958
6d6f0db0
SR
959 // authentication
960 _negotiate_xvp_auth: function () {
430f00d6
PO
961 if (!this._rfb_credentials.username ||
962 !this._rfb_credentials.password ||
963 !this._rfb_credentials.target) {
e89eef94
PO
964 var event = new CustomEvent("credentialsrequired",
965 { detail: { types: ["username", "password", "target"] } });
966 this.dispatchEvent(event);
6d6f0db0
SR
967 return false;
968 }
0ee5ca6e 969
430f00d6
PO
970 var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
971 String.fromCharCode(this._rfb_credentials.target.length) +
972 this._rfb_credentials.username +
973 this._rfb_credentials.target;
6d6f0db0 974 this._sock.send_string(xvp_auth_str);
6d6f0db0
SR
975 this._rfb_auth_scheme = 2;
976 return this._negotiate_authentication();
977 },
978
979 _negotiate_std_vnc_auth: function () {
abfe5b7a
SM
980 if (this._sock.rQwait("auth challenge", 16)) { return false; }
981
430f00d6 982 if (!this._rfb_credentials.password) {
e89eef94
PO
983 var event = new CustomEvent("credentialsrequired",
984 { detail: { types: ["password"] } });
985 this.dispatchEvent(event);
6d6f0db0
SR
986 return false;
987 }
b1dee947 988
6d6f0db0
SR
989 // TODO(directxman12): make genDES not require an Array
990 var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
430f00d6 991 var response = RFB.genDES(this._rfb_credentials.password, challenge);
6d6f0db0
SR
992 this._sock.send(response);
993 this._rfb_init_state = "SecurityResult";
994 return true;
995 },
996
997 _negotiate_tight_tunnels: function (numTunnels) {
998 var clientSupportedTunnelTypes = {
999 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
1000 };
1001 var serverSupportedTunnelTypes = {};
1002 // receive tunnel capabilities
1003 for (var i = 0; i < numTunnels; i++) {
1004 var cap_code = this._sock.rQshift32();
1005 var cap_vendor = this._sock.rQshiftStr(4);
1006 var cap_signature = this._sock.rQshiftStr(8);
1007 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
1008 }
1009
1010 // choose the notunnel type
1011 if (serverSupportedTunnelTypes[0]) {
1012 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
1013 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
d472f3f1 1014 return this._fail("Client's tunnel type had the incorrect " +
6d6f0db0 1015 "vendor or signature");
b1dee947 1016 }
6d6f0db0
SR
1017 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
1018 return false; // wait until we receive the sub auth count to continue
1019 } else {
d472f3f1 1020 return this._fail("Server wanted tunnels, but doesn't support " +
6d6f0db0
SR
1021 "the notunnel type");
1022 }
1023 },
b1dee947 1024
6d6f0db0
SR
1025 _negotiate_tight_auth: function () {
1026 if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
1027 if (this._sock.rQwait("num tunnels", 4)) { return false; }
1028 var numTunnels = this._sock.rQshift32();
1029 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
c00ee156 1030
6d6f0db0 1031 this._rfb_tightvnc = true;
b1dee947 1032
6d6f0db0
SR
1033 if (numTunnels > 0) {
1034 this._negotiate_tight_tunnels(numTunnels);
1035 return false; // wait until we receive the sub auth to continue
b1dee947 1036 }
6d6f0db0 1037 }
b1dee947 1038
6d6f0db0
SR
1039 // second pass, do the sub-auth negotiation
1040 if (this._sock.rQwait("sub auth count", 4)) { return false; }
1041 var subAuthCount = this._sock.rQshift32();
1042 if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
1043 this._rfb_init_state = 'SecurityResult';
1044 return true;
1045 }
b1dee947 1046
6d6f0db0 1047 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
b1dee947 1048
6d6f0db0
SR
1049 var clientSupportedTypes = {
1050 'STDVNOAUTH__': 1,
1051 'STDVVNCAUTH_': 2
1052 };
b1dee947 1053
6d6f0db0 1054 var serverSupportedTypes = [];
95eb681b 1055
6d6f0db0
SR
1056 for (var i = 0; i < subAuthCount; i++) {
1057 var capNum = this._sock.rQshift32();
1058 var capabilities = this._sock.rQshiftStr(12);
1059 serverSupportedTypes.push(capabilities);
1060 }
d86cc2d9 1061
6d6f0db0
SR
1062 for (var authType in clientSupportedTypes) {
1063 if (serverSupportedTypes.indexOf(authType) != -1) {
1064 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
d86cc2d9 1065
6d6f0db0
SR
1066 switch (authType) {
1067 case 'STDVNOAUTH__': // no auth
1068 this._rfb_init_state = 'SecurityResult';
1069 return true;
1070 case 'STDVVNCAUTH_': // VNC auth
1071 this._rfb_auth_scheme = 2;
1072 return this._init_msg();
1073 default:
d472f3f1
SM
1074 return this._fail("Unsupported tiny auth scheme " +
1075 "(scheme: " + authType + ")");
b1dee947
SR
1076 }
1077 }
6d6f0db0 1078 }
b1dee947 1079
d472f3f1 1080 return this._fail("No supported sub-auth types!");
6d6f0db0
SR
1081 },
1082
1083 _negotiate_authentication: function () {
1084 switch (this._rfb_auth_scheme) {
1085 case 0: // connection failed
d472f3f1 1086 return this._handle_security_failure("authentication scheme");
6d6f0db0
SR
1087
1088 case 1: // no auth
1089 if (this._rfb_version >= 3.8) {
1090 this._rfb_init_state = 'SecurityResult';
1091 return true;
1092 }
1093 this._rfb_init_state = 'ClientInitialisation';
1094 return this._init_msg();
95eb681b 1095
6d6f0db0
SR
1096 case 22: // XVP auth
1097 return this._negotiate_xvp_auth();
d86cc2d9 1098
6d6f0db0
SR
1099 case 2: // VNC authentication
1100 return this._negotiate_std_vnc_auth();
d86cc2d9 1101
6d6f0db0
SR
1102 case 16: // TightVNC Security Type
1103 return this._negotiate_tight_auth();
d86cc2d9 1104
6d6f0db0 1105 default:
d472f3f1
SM
1106 return this._fail("Unsupported auth scheme (scheme: " +
1107 this._rfb_auth_scheme + ")");
6d6f0db0
SR
1108 }
1109 },
1110
1111 _handle_security_result: function () {
1112 if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
d472f3f1
SM
1113
1114 let status = this._sock.rQshift32();
1115
1116 if (status === 0) { // OK
1117 this._rfb_init_state = 'ClientInitialisation';
1118 Log.Debug('Authentication OK');
1119 return this._init_msg();
1120 } else {
1121 if (this._rfb_version >= 3.8) {
1122 return this._handle_security_failure("security result", status);
1123 } else {
1124 let event = new CustomEvent("securityfailure",
1125 { detail: { status: status } });
1126 this.dispatchEvent(event);
1127
1128 return this._fail("Security handshake failed");
1129 }
6d6f0db0
SR
1130 }
1131 },
1132
1133 _negotiate_server_init: function () {
1134 if (this._sock.rQwait("server initialization", 24)) { return false; }
1135
1136 /* Screen size */
91d5c625
PO
1137 var width = this._sock.rQshift16();
1138 var height = this._sock.rQshift16();
6d6f0db0
SR
1139
1140 /* PIXEL_FORMAT */
1141 var bpp = this._sock.rQshift8();
1142 var depth = this._sock.rQshift8();
1143 var big_endian = this._sock.rQshift8();
1144 var true_color = this._sock.rQshift8();
1145
1146 var red_max = this._sock.rQshift16();
1147 var green_max = this._sock.rQshift16();
1148 var blue_max = this._sock.rQshift16();
1149 var red_shift = this._sock.rQshift8();
1150 var green_shift = this._sock.rQshift8();
1151 var blue_shift = this._sock.rQshift8();
1152 this._sock.rQskipBytes(3); // padding
1153
1154 // NB(directxman12): we don't want to call any callbacks or print messages until
1155 // *after* we're past the point where we could backtrack
1156
1157 /* Connection name/title */
1158 var name_length = this._sock.rQshift32();
1159 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1160 this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
1161
1162 if (this._rfb_tightvnc) {
1163 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1164 // In TightVNC mode, ServerInit message is extended
1165 var numServerMessages = this._sock.rQshift16();
1166 var numClientMessages = this._sock.rQshift16();
1167 var numEncodings = this._sock.rQshift16();
1168 this._sock.rQskipBytes(2); // padding
1169
1170 var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1171 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1172
1173 // we don't actually do anything with the capability information that TIGHT sends,
1174 // so we just skip the all of this.
1175
1176 // TIGHT server message capabilities
1177 this._sock.rQskipBytes(16 * numServerMessages);
1178
1179 // TIGHT client message capabilities
1180 this._sock.rQskipBytes(16 * numClientMessages);
1181
1182 // TIGHT encoding capabilities
1183 this._sock.rQskipBytes(16 * numEncodings);
1184 }
d86cc2d9 1185
6d6f0db0
SR
1186 // NB(directxman12): these are down here so that we don't run them multiple times
1187 // if we backtrack
91d5c625 1188 Log.Info("Screen: " + width + "x" + height +
6d6f0db0
SR
1189 ", bpp: " + bpp + ", depth: " + depth +
1190 ", big_endian: " + big_endian +
1191 ", true_color: " + true_color +
1192 ", red_max: " + red_max +
1193 ", green_max: " + green_max +
1194 ", blue_max: " + blue_max +
1195 ", red_shift: " + red_shift +
1196 ", green_shift: " + green_shift +
1197 ", blue_shift: " + blue_shift);
1198
1199 if (big_endian !== 0) {
1200 Log.Warn("Server native endian is not little endian");
1201 }
b1dee947 1202
6d6f0db0
SR
1203 if (red_shift !== 16) {
1204 Log.Warn("Server native red-shift is not 16");
1205 }
b1dee947 1206
6d6f0db0
SR
1207 if (blue_shift !== 0) {
1208 Log.Warn("Server native blue-shift is not 0");
1209 }
b1dee947 1210
6d6f0db0 1211 // we're past the point where we could backtrack, so it's safe to call this
e89eef94
PO
1212 var event = new CustomEvent("desktopname",
1213 { detail: { name: this._fb_name } });
1214 this.dispatchEvent(event);
b1dee947 1215
91d5c625 1216 this._resize(width, height);
b1dee947 1217
747b4623
PO
1218 if (!this._viewOnly) { this._keyboard.grab(); }
1219 if (!this._viewOnly) { this._mouse.grab(); }
b1dee947 1220
5a5f5ada
PO
1221 this._fb_depth = 24;
1222
1223 if (this._fb_name === "Intel(r) AMT KVM") {
1224 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1225 this._fb_depth = 8;
1226 }
1227
1228 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
49a81837 1229 this._sendEncodings();
6d6f0db0
SR
1230 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1231
1232 this._timing.fbu_rt_start = (new Date()).getTime();
1233 this._timing.pixels = 0;
1234
cd74793b
PO
1235 // Cursor will be server side until the server decides to honor
1236 // our request and send over the cursor image
1237 this._display.disableLocalCursor();
1238
6d6f0db0
SR
1239 this._updateConnectionState('connected');
1240 return true;
1241 },
1242
49a81837
PO
1243 _sendEncodings: function () {
1244 var encs = [];
1245
1246 // In preference order
1247 encs.push(encodings.encodingCopyRect);
5a5f5ada
PO
1248 // Only supported with full depth support
1249 if (this._fb_depth == 24) {
1250 encs.push(encodings.encodingTight);
1251 encs.push(encodings.encodingHextile);
1252 encs.push(encodings.encodingRRE);
1253 }
49a81837
PO
1254 encs.push(encodings.encodingRaw);
1255
1256 // Psuedo-encoding settings
1257 encs.push(encodings.pseudoEncodingTightPNG);
1258 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1259 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1260
1261 encs.push(encodings.pseudoEncodingDesktopSize);
1262 encs.push(encodings.pseudoEncodingLastRect);
1263 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1264 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1265 encs.push(encodings.pseudoEncodingXvp);
1266 encs.push(encodings.pseudoEncodingFence);
1267 encs.push(encodings.pseudoEncodingContinuousUpdates);
1268
8d1f0a3d
PO
1269 if (browserSupportsCursorURIs() &&
1270 !isTouchDevice && this._fb_depth == 24) {
49a81837
PO
1271 encs.push(encodings.pseudoEncodingCursor);
1272 }
1273
1274 RFB.messages.clientEncodings(this._sock, encs);
1275 },
1276
6d6f0db0
SR
1277 /* RFB protocol initialization states:
1278 * ProtocolVersion
1279 * Security
1280 * Authentication
1281 * SecurityResult
1282 * ClientInitialization - not triggered by server message
1283 * ServerInitialization
1284 */
1285 _init_msg: function () {
1286 switch (this._rfb_init_state) {
1287 case 'ProtocolVersion':
1288 return this._negotiate_protocol_version();
1289
1290 case 'Security':
1291 return this._negotiate_security();
1292
1293 case 'Authentication':
1294 return this._negotiate_authentication();
1295
1296 case 'SecurityResult':
1297 return this._handle_security_result();
1298
1299 case 'ClientInitialisation':
1300 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1301 this._rfb_init_state = 'ServerInitialisation';
1302 return true;
b1dee947 1303
6d6f0db0
SR
1304 case 'ServerInitialisation':
1305 return this._negotiate_server_init();
b1dee947 1306
6d6f0db0 1307 default:
d472f3f1
SM
1308 return this._fail("Unknown init state (state: " +
1309 this._rfb_init_state + ")");
6d6f0db0
SR
1310 }
1311 },
b1dee947 1312
6d6f0db0
SR
1313 _handle_set_colour_map_msg: function () {
1314 Log.Debug("SetColorMapEntries");
b1dee947 1315
d472f3f1 1316 return this._fail("Unexpected SetColorMapEntries message");
6d6f0db0 1317 },
b1dee947 1318
6d6f0db0
SR
1319 _handle_server_cut_text: function () {
1320 Log.Debug("ServerCutText");
b1dee947 1321
6d6f0db0
SR
1322 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1323 this._sock.rQskipBytes(3); // Padding
1324 var length = this._sock.rQshift32();
1325 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
b1dee947 1326
6d6f0db0 1327 var text = this._sock.rQshiftStr(length);
b4ff032e 1328
747b4623 1329 if (this._viewOnly) { return true; }
b4ff032e 1330
e89eef94
PO
1331 var event = new CustomEvent("clipboard",
1332 { detail: { text: text } });
1333 this.dispatchEvent(event);
b1dee947 1334
6d6f0db0
SR
1335 return true;
1336 },
b1dee947 1337
6d6f0db0
SR
1338 _handle_server_fence_msg: function() {
1339 if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
1340 this._sock.rQskipBytes(3); // Padding
1341 var flags = this._sock.rQshift32();
1342 var length = this._sock.rQshift8();
b1dee947 1343
6d6f0db0 1344 if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
b1dee947 1345
6d6f0db0
SR
1346 if (length > 64) {
1347 Log.Warn("Bad payload length (" + length + ") in fence response");
1348 length = 64;
1349 }
4e0c36dd 1350
6d6f0db0 1351 var payload = this._sock.rQshiftStr(length);
b1dee947 1352
6d6f0db0 1353 this._supportsFence = true;
b1dee947 1354
6d6f0db0
SR
1355 /*
1356 * Fence flags
1357 *
1358 * (1<<0) - BlockBefore
1359 * (1<<1) - BlockAfter
1360 * (1<<2) - SyncNext
1361 * (1<<31) - Request
1362 */
b1dee947 1363
6d6f0db0 1364 if (!(flags & (1<<31))) {
d472f3f1 1365 return this._fail("Unexpected fence response");
6d6f0db0 1366 }
b1dee947 1367
6d6f0db0
SR
1368 // Filter out unsupported flags
1369 // FIXME: support syncNext
1370 flags &= (1<<0) | (1<<1);
b1dee947 1371
6d6f0db0
SR
1372 // BlockBefore and BlockAfter are automatically handled by
1373 // the fact that we process each incoming message
1374 // synchronuosly.
1375 RFB.messages.clientFence(this._sock, flags, payload);
f78a652e 1376
6d6f0db0
SR
1377 return true;
1378 },
b1dee947 1379
6d6f0db0
SR
1380 _handle_xvp_msg: function () {
1381 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
1382 this._sock.rQskip8(); // Padding
1383 var xvp_ver = this._sock.rQshift8();
1384 var xvp_msg = this._sock.rQshift8();
b1dee947 1385
6d6f0db0
SR
1386 switch (xvp_msg) {
1387 case 0: // XVP_FAIL
5b20d338 1388 Log.Error("XVP Operation Failed");
6d6f0db0
SR
1389 break;
1390 case 1: // XVP_INIT
1391 this._rfb_xvp_ver = xvp_ver;
1392 Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
832be262 1393 this._setCapability("power", true);
6d6f0db0
SR
1394 break;
1395 default:
d472f3f1 1396 this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
6d6f0db0
SR
1397 break;
1398 }
b1dee947 1399
6d6f0db0
SR
1400 return true;
1401 },
3df13262 1402
6d6f0db0
SR
1403 _normal_msg: function () {
1404 var msg_type;
76a86ff5 1405
6d6f0db0
SR
1406 if (this._FBU.rects > 0) {
1407 msg_type = 0;
1408 } else {
1409 msg_type = this._sock.rQshift8();
1410 }
76a86ff5 1411
6d6f0db0
SR
1412 switch (msg_type) {
1413 case 0: // FramebufferUpdate
1414 var ret = this._framebufferUpdate();
1415 if (ret && !this._enabledContinuousUpdates) {
1416 RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
1417 this._fb_width, this._fb_height);
1418 }
1419 return ret;
3df13262 1420
6d6f0db0
SR
1421 case 1: // SetColorMapEntries
1422 return this._handle_set_colour_map_msg();
1423
1424 case 2: // Bell
1425 Log.Debug("Bell");
e89eef94
PO
1426 var event = new CustomEvent("bell", { detail: {} });
1427 this.dispatchEvent(event);
6d6f0db0
SR
1428 return true;
1429
1430 case 3: // ServerCutText
1431 return this._handle_server_cut_text();
1432
1433 case 150: // EndOfContinuousUpdates
1434 var first = !(this._supportsContinuousUpdates);
1435 this._supportsContinuousUpdates = true;
1436 this._enabledContinuousUpdates = false;
1437 if (first) {
1438 this._enabledContinuousUpdates = true;
1439 this._updateContinuousUpdates();
1440 Log.Info("Enabling continuous updates.");
1441 } else {
1442 // FIXME: We need to send a framebufferupdaterequest here
1443 // if we add support for turning off continuous updates
1444 }
1445 return true;
3df13262 1446
6d6f0db0
SR
1447 case 248: // ServerFence
1448 return this._handle_server_fence_msg();
3df13262 1449
6d6f0db0
SR
1450 case 250: // XVP
1451 return this._handle_xvp_msg();
3df13262 1452
6d6f0db0 1453 default:
d472f3f1 1454 this._fail("Unexpected server message (type " + msg_type + ")");
6d6f0db0
SR
1455 Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1456 return true;
1457 }
1458 },
3df13262 1459
6d6f0db0
SR
1460 _onFlush: function() {
1461 this._flushing = false;
1462 // Resume processing
1463 if (this._sock.rQlen() > 0) {
1464 this._handle_message();
1465 }
1466 },
3df13262 1467
6d6f0db0
SR
1468 _framebufferUpdate: function () {
1469 var ret = true;
1470 var now;
3df13262 1471
6d6f0db0
SR
1472 if (this._FBU.rects === 0) {
1473 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
b1dee947 1474 this._sock.rQskip8(); // Padding
6d6f0db0
SR
1475 this._FBU.rects = this._sock.rQshift16();
1476 this._FBU.bytes = 0;
1477 this._timing.cur_fbu = 0;
1478 if (this._timing.fbu_rt_start > 0) {
1479 now = (new Date()).getTime();
1480 Log.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
b1dee947
SR
1481 }
1482
6d6f0db0
SR
1483 // Make sure the previous frame is fully rendered first
1484 // to avoid building up an excessive queue
1485 if (this._display.pending()) {
1486 this._flushing = true;
1487 this._display.flush();
1488 return false;
1489 }
1490 }
b1dee947 1491
6d6f0db0
SR
1492 while (this._FBU.rects > 0) {
1493 if (this._rfb_connection_state !== 'connected') { return false; }
b1dee947 1494
6d6f0db0
SR
1495 if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
1496 if (this._FBU.bytes === 0) {
1497 if (this._sock.rQwait("rect header", 12)) { return false; }
1498 /* New FramebufferUpdate */
b1dee947 1499
6d6f0db0
SR
1500 var hdr = this._sock.rQshiftBytes(12);
1501 this._FBU.x = (hdr[0] << 8) + hdr[1];
1502 this._FBU.y = (hdr[2] << 8) + hdr[3];
1503 this._FBU.width = (hdr[4] << 8) + hdr[5];
1504 this._FBU.height = (hdr[6] << 8) + hdr[7];
1505 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1506 (hdr[10] << 8) + hdr[11], 10);
b1dee947 1507
49a81837 1508 if (!this._encHandlers[this._FBU.encoding]) {
d472f3f1
SM
1509 this._fail("Unsupported encoding (encoding: " +
1510 this._FBU.encoding + ")");
6d6f0db0
SR
1511 return false;
1512 }
1513 }
b1dee947 1514
6d6f0db0 1515 this._timing.last_fbu = (new Date()).getTime();
76a86ff5 1516
6d6f0db0 1517 ret = this._encHandlers[this._FBU.encoding]();
3df13262 1518
6d6f0db0
SR
1519 now = (new Date()).getTime();
1520 this._timing.cur_fbu += (now - this._timing.last_fbu);
b1dee947 1521
6d6f0db0 1522 if (ret) {
c3386227
PO
1523 if (!(this._FBU.encoding in this._encStats)) {
1524 this._encStats[this._FBU.encoding] = [0, 0];
1525 }
6d6f0db0
SR
1526 this._encStats[this._FBU.encoding][0]++;
1527 this._encStats[this._FBU.encoding][1]++;
1528 this._timing.pixels += this._FBU.width * this._FBU.height;
b1dee947 1529 }
b1dee947 1530
6d6f0db0
SR
1531 if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
1532 if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
1533 this._timing.fbu_rt_start > 0) {
1534 this._timing.full_fbu_total += this._timing.cur_fbu;
1535 this._timing.full_fbu_cnt++;
1536 Log.Info("Timing of full FBU, curr: " +
1537 this._timing.cur_fbu + ", total: " +
1538 this._timing.full_fbu_total + ", cnt: " +
1539 this._timing.full_fbu_cnt + ", avg: " +
1540 (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
b1dee947 1541 }
d9ca5e5b 1542
6d6f0db0
SR
1543 if (this._timing.fbu_rt_start > 0) {
1544 var fbu_rt_diff = now - this._timing.fbu_rt_start;
1545 this._timing.fbu_rt_total += fbu_rt_diff;
1546 this._timing.fbu_rt_cnt++;
1547 Log.Info("full FBU round-trip, cur: " +
1548 fbu_rt_diff + ", total: " +
1549 this._timing.fbu_rt_total + ", cnt: " +
1550 this._timing.fbu_rt_cnt + ", avg: " +
1551 (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
1552 this._timing.fbu_rt_start = 0;
d9ca5e5b 1553 }
b1dee947
SR
1554 }
1555
6d6f0db0
SR
1556 if (!ret) { return ret; } // need more data
1557 }
b1dee947 1558
6d6f0db0 1559 this._display.flip();
b1dee947 1560
6d6f0db0
SR
1561 return true; // We finished this FBU
1562 },
b1dee947 1563
6d6f0db0
SR
1564 _updateContinuousUpdates: function() {
1565 if (!this._enabledContinuousUpdates) { return; }
b1dee947 1566
6d6f0db0
SR
1567 RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
1568 this._fb_width, this._fb_height);
91d5c625
PO
1569 },
1570
1571 _resize: function(width, height) {
1572 this._fb_width = width;
1573 this._fb_height = height;
1574
1575 this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
1576
1577 this._display.resize(this._fb_width, this._fb_height);
e89eef94 1578
9b84f516
PO
1579 // Adjust the visible viewport based on the new dimensions
1580 this._updateClip();
1581 this._updateScale();
91d5c625
PO
1582
1583 this._timing.fbu_rt_start = (new Date()).getTime();
1584 this._updateContinuousUpdates();
cd523e8f 1585 },
8db09746 1586
cd523e8f
PO
1587 _xvpOp: function (ver, op) {
1588 if (this._rfb_xvp_ver < ver) { return; }
1589 Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
1590 RFB.messages.xvpOp(this._sock, ver, op);
1591 },
6d6f0db0 1592};
76a86ff5 1593
e89eef94 1594Object.assign(RFB.prototype, EventTargetMixin);
76a86ff5 1595
6d6f0db0
SR
1596// Class Methods
1597RFB.messages = {
1598 keyEvent: function (sock, keysym, down) {
1599 var buff = sock._sQ;
1600 var offset = sock._sQlen;
8db09746 1601
6d6f0db0
SR
1602 buff[offset] = 4; // msg-type
1603 buff[offset + 1] = down;
b1dee947 1604
6d6f0db0
SR
1605 buff[offset + 2] = 0;
1606 buff[offset + 3] = 0;
fb49f91b 1607
6d6f0db0
SR
1608 buff[offset + 4] = (keysym >> 24);
1609 buff[offset + 5] = (keysym >> 16);
1610 buff[offset + 6] = (keysym >> 8);
1611 buff[offset + 7] = keysym;
b1dee947 1612
6d6f0db0
SR
1613 sock._sQlen += 8;
1614 sock.flush();
1615 },
ef1e8bab 1616
6d6f0db0
SR
1617 QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
1618 function getRFBkeycode(xt_scancode) {
1619 var upperByte = (keycode >> 8);
1620 var lowerByte = (keycode & 0x00ff);
1621 if (upperByte === 0xe0 && lowerByte < 0x7f) {
1622 lowerByte = lowerByte | 0x80;
1623 return lowerByte;
ef1e8bab 1624 }
6d6f0db0 1625 return xt_scancode;
ef1e8bab 1626 }
b1dee947 1627
6d6f0db0
SR
1628 var buff = sock._sQ;
1629 var offset = sock._sQlen;
8f06673a 1630
6d6f0db0
SR
1631 buff[offset] = 255; // msg-type
1632 buff[offset + 1] = 0; // sub msg-type
8f06673a 1633
6d6f0db0
SR
1634 buff[offset + 2] = (down >> 8);
1635 buff[offset + 3] = down;
8f06673a 1636
6d6f0db0
SR
1637 buff[offset + 4] = (keysym >> 24);
1638 buff[offset + 5] = (keysym >> 16);
1639 buff[offset + 6] = (keysym >> 8);
1640 buff[offset + 7] = keysym;
8f06673a 1641
6d6f0db0 1642 var RFBkeycode = getRFBkeycode(keycode);
8f06673a 1643
6d6f0db0
SR
1644 buff[offset + 8] = (RFBkeycode >> 24);
1645 buff[offset + 9] = (RFBkeycode >> 16);
1646 buff[offset + 10] = (RFBkeycode >> 8);
1647 buff[offset + 11] = RFBkeycode;
8f06673a 1648
6d6f0db0
SR
1649 sock._sQlen += 12;
1650 sock.flush();
1651 },
8f06673a 1652
6d6f0db0
SR
1653 pointerEvent: function (sock, x, y, mask) {
1654 var buff = sock._sQ;
1655 var offset = sock._sQlen;
8f06673a 1656
6d6f0db0 1657 buff[offset] = 5; // msg-type
9ff86fb7 1658
6d6f0db0 1659 buff[offset + 1] = mask;
9ff86fb7 1660
6d6f0db0
SR
1661 buff[offset + 2] = x >> 8;
1662 buff[offset + 3] = x;
9ff86fb7 1663
6d6f0db0
SR
1664 buff[offset + 4] = y >> 8;
1665 buff[offset + 5] = y;
9ff86fb7 1666
6d6f0db0
SR
1667 sock._sQlen += 6;
1668 sock.flush();
1669 },
9ff86fb7 1670
6d6f0db0
SR
1671 // TODO(directxman12): make this unicode compatible?
1672 clientCutText: function (sock, text) {
1673 var buff = sock._sQ;
1674 var offset = sock._sQlen;
b1dee947 1675
6d6f0db0 1676 buff[offset] = 6; // msg-type
9ff86fb7 1677
6d6f0db0
SR
1678 buff[offset + 1] = 0; // padding
1679 buff[offset + 2] = 0; // padding
1680 buff[offset + 3] = 0; // padding
9ff86fb7 1681
6d6f0db0 1682 var n = text.length;
9ff86fb7 1683
6d6f0db0
SR
1684 buff[offset + 4] = n >> 24;
1685 buff[offset + 5] = n >> 16;
1686 buff[offset + 6] = n >> 8;
1687 buff[offset + 7] = n;
9ff86fb7 1688
6d6f0db0
SR
1689 for (var i = 0; i < n; i++) {
1690 buff[offset + 8 + i] = text.charCodeAt(i);
1691 }
9ff86fb7 1692
6d6f0db0
SR
1693 sock._sQlen += 8 + n;
1694 sock.flush();
1695 },
1696
1697 setDesktopSize: function (sock, width, height, id, flags) {
1698 var buff = sock._sQ;
1699 var offset = sock._sQlen;
1700
1701 buff[offset] = 251; // msg-type
1702 buff[offset + 1] = 0; // padding
1703 buff[offset + 2] = width >> 8; // width
1704 buff[offset + 3] = width;
1705 buff[offset + 4] = height >> 8; // height
1706 buff[offset + 5] = height;
1707
1708 buff[offset + 6] = 1; // number-of-screens
1709 buff[offset + 7] = 0; // padding
1710
1711 // screen array
1712 buff[offset + 8] = id >> 24; // id
1713 buff[offset + 9] = id >> 16;
1714 buff[offset + 10] = id >> 8;
1715 buff[offset + 11] = id;
1716 buff[offset + 12] = 0; // x-position
1717 buff[offset + 13] = 0;
1718 buff[offset + 14] = 0; // y-position
1719 buff[offset + 15] = 0;
1720 buff[offset + 16] = width >> 8; // width
1721 buff[offset + 17] = width;
1722 buff[offset + 18] = height >> 8; // height
1723 buff[offset + 19] = height;
1724 buff[offset + 20] = flags >> 24; // flags
1725 buff[offset + 21] = flags >> 16;
1726 buff[offset + 22] = flags >> 8;
1727 buff[offset + 23] = flags;
1728
1729 sock._sQlen += 24;
1730 sock.flush();
1731 },
1732
1733 clientFence: function (sock, flags, payload) {
1734 var buff = sock._sQ;
1735 var offset = sock._sQlen;
1736
1737 buff[offset] = 248; // msg-type
1738
1739 buff[offset + 1] = 0; // padding
1740 buff[offset + 2] = 0; // padding
1741 buff[offset + 3] = 0; // padding
1742
1743 buff[offset + 4] = flags >> 24; // flags
1744 buff[offset + 5] = flags >> 16;
1745 buff[offset + 6] = flags >> 8;
1746 buff[offset + 7] = flags;
1747
1748 var n = payload.length;
1749
1750 buff[offset + 8] = n; // length
1751
1752 for (var i = 0; i < n; i++) {
1753 buff[offset + 9 + i] = payload.charCodeAt(i);
1754 }
a7a89626 1755
6d6f0db0
SR
1756 sock._sQlen += 9 + n;
1757 sock.flush();
1758 },
3df13262 1759
6d6f0db0
SR
1760 enableContinuousUpdates: function (sock, enable, x, y, width, height) {
1761 var buff = sock._sQ;
1762 var offset = sock._sQlen;
3df13262 1763
6d6f0db0
SR
1764 buff[offset] = 150; // msg-type
1765 buff[offset + 1] = enable; // enable-flag
76a86ff5 1766
6d6f0db0
SR
1767 buff[offset + 2] = x >> 8; // x
1768 buff[offset + 3] = x;
1769 buff[offset + 4] = y >> 8; // y
1770 buff[offset + 5] = y;
1771 buff[offset + 6] = width >> 8; // width
1772 buff[offset + 7] = width;
1773 buff[offset + 8] = height >> 8; // height
1774 buff[offset + 9] = height;
76a86ff5 1775
6d6f0db0
SR
1776 sock._sQlen += 10;
1777 sock.flush();
1778 },
76a86ff5 1779
5a5f5ada 1780 pixelFormat: function (sock, depth, true_color) {
6d6f0db0
SR
1781 var buff = sock._sQ;
1782 var offset = sock._sQlen;
ae116051 1783
5a5f5ada
PO
1784 var bpp, bits;
1785
1786 if (depth > 16) {
1787 bpp = 32;
1788 } else if (depth > 8) {
1789 bpp = 16;
1790 } else {
1791 bpp = 8;
1792 }
1793
1794 bits = Math.floor(depth/3);
1795
6d6f0db0 1796 buff[offset] = 0; // msg-type
9ff86fb7 1797
6d6f0db0
SR
1798 buff[offset + 1] = 0; // padding
1799 buff[offset + 2] = 0; // padding
1800 buff[offset + 3] = 0; // padding
9ff86fb7 1801
5a5f5ada
PO
1802 buff[offset + 4] = bpp; // bits-per-pixel
1803 buff[offset + 5] = depth; // depth
6d6f0db0
SR
1804 buff[offset + 6] = 0; // little-endian
1805 buff[offset + 7] = true_color ? 1 : 0; // true-color
9ff86fb7 1806
6d6f0db0 1807 buff[offset + 8] = 0; // red-max
5a5f5ada 1808 buff[offset + 9] = (1 << bits) - 1; // red-max
9ff86fb7 1809
6d6f0db0 1810 buff[offset + 10] = 0; // green-max
5a5f5ada 1811 buff[offset + 11] = (1 << bits) - 1; // green-max
9ff86fb7 1812
6d6f0db0 1813 buff[offset + 12] = 0; // blue-max
5a5f5ada 1814 buff[offset + 13] = (1 << bits) - 1; // blue-max
9ff86fb7 1815
5a5f5ada
PO
1816 buff[offset + 14] = bits * 2; // red-shift
1817 buff[offset + 15] = bits * 1; // green-shift
1818 buff[offset + 16] = bits * 0; // blue-shift
9ff86fb7 1819
6d6f0db0
SR
1820 buff[offset + 17] = 0; // padding
1821 buff[offset + 18] = 0; // padding
1822 buff[offset + 19] = 0; // padding
9ff86fb7 1823
6d6f0db0
SR
1824 sock._sQlen += 20;
1825 sock.flush();
1826 },
9ff86fb7 1827
49a81837 1828 clientEncodings: function (sock, encodings) {
6d6f0db0
SR
1829 var buff = sock._sQ;
1830 var offset = sock._sQlen;
b1dee947 1831
6d6f0db0
SR
1832 buff[offset] = 2; // msg-type
1833 buff[offset + 1] = 0; // padding
b1dee947 1834
49a81837
PO
1835 buff[offset + 2] = encodings.length >> 8;
1836 buff[offset + 3] = encodings.length;
9ff86fb7 1837
49a81837 1838 var i, j = offset + 4;
6d6f0db0 1839 for (i = 0; i < encodings.length; i++) {
49a81837
PO
1840 var enc = encodings[i];
1841 buff[j] = enc >> 24;
1842 buff[j + 1] = enc >> 16;
1843 buff[j + 2] = enc >> 8;
1844 buff[j + 3] = enc;
a7a89626 1845
49a81837
PO
1846 j += 4;
1847 }
a7a89626 1848
6d6f0db0
SR
1849 sock._sQlen += j - offset;
1850 sock.flush();
1851 },
a679a97d 1852
6d6f0db0
SR
1853 fbUpdateRequest: function (sock, incremental, x, y, w, h) {
1854 var buff = sock._sQ;
1855 var offset = sock._sQlen;
9ff86fb7 1856
6d6f0db0
SR
1857 if (typeof(x) === "undefined") { x = 0; }
1858 if (typeof(y) === "undefined") { y = 0; }
b1dee947 1859
6d6f0db0
SR
1860 buff[offset] = 3; // msg-type
1861 buff[offset + 1] = incremental ? 1 : 0;
9ff86fb7 1862
6d6f0db0
SR
1863 buff[offset + 2] = (x >> 8) & 0xFF;
1864 buff[offset + 3] = x & 0xFF;
9ff86fb7 1865
6d6f0db0
SR
1866 buff[offset + 4] = (y >> 8) & 0xFF;
1867 buff[offset + 5] = y & 0xFF;
9ff86fb7 1868
6d6f0db0
SR
1869 buff[offset + 6] = (w >> 8) & 0xFF;
1870 buff[offset + 7] = w & 0xFF;
9ff86fb7 1871
6d6f0db0
SR
1872 buff[offset + 8] = (h >> 8) & 0xFF;
1873 buff[offset + 9] = h & 0xFF;
b1dee947 1874
6d6f0db0
SR
1875 sock._sQlen += 10;
1876 sock.flush();
85b35fc0
PO
1877 },
1878
1879 xvpOp: function (sock, ver, op) {
1880 var buff = sock._sQ;
1881 var offset = sock._sQlen;
1882
1883 buff[offset] = 250; // msg-type
1884 buff[offset + 1] = 0; // padding
1885
1886 buff[offset + 2] = ver;
1887 buff[offset + 3] = op;
1888
1889 sock._sQlen += 4;
1890 sock.flush();
1891 },
6d6f0db0 1892};
b1dee947 1893
6d6f0db0
SR
1894RFB.genDES = function (password, challenge) {
1895 var passwd = [];
1896 for (var i = 0; i < password.length; i++) {
1897 passwd.push(password.charCodeAt(i));
1898 }
1899 return (new DES(passwd)).encrypt(challenge);
1900};
b1dee947 1901
6d6f0db0
SR
1902RFB.encodingHandlers = {
1903 RAW: function () {
1904 if (this._FBU.lines === 0) {
1905 this._FBU.lines = this._FBU.height;
1906 }
a7a89626 1907
5a5f5ada
PO
1908 var pixelSize = this._fb_depth == 8 ? 1 : 4;
1909 this._FBU.bytes = this._FBU.width * pixelSize; // at least a line
6d6f0db0
SR
1910 if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
1911 var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
1912 var curr_height = Math.min(this._FBU.lines,
5a5f5ada
PO
1913 Math.floor(this._sock.rQlen() / (this._FBU.width * pixelSize)));
1914 var data = this._sock.get_rQ();
1915 var index = this._sock.get_rQi();
1916 if (this._fb_depth == 8) {
1917 var pixels = this._FBU.width * curr_height
1918 var newdata = new Uint8Array(pixels * 4);
1919 var i;
1920 for (i = 0;i < pixels;i++) {
1921 newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
1922 newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
1923 newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
1924 newdata[i * 4 + 4] = 0;
1925 }
1926 data = newdata;
1927 index = 0;
1928 }
6d6f0db0 1929 this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
5a5f5ada
PO
1930 curr_height, data, index);
1931 this._sock.rQskipBytes(this._FBU.width * curr_height * pixelSize);
6d6f0db0
SR
1932 this._FBU.lines -= curr_height;
1933
1934 if (this._FBU.lines > 0) {
5a5f5ada 1935 this._FBU.bytes = this._FBU.width * pixelSize; // At least another line
6d6f0db0
SR
1936 } else {
1937 this._FBU.rects--;
1938 this._FBU.bytes = 0;
1939 }
b1dee947 1940
6d6f0db0
SR
1941 return true;
1942 },
1943
1944 COPYRECT: function () {
1945 this._FBU.bytes = 4;
1946 if (this._sock.rQwait("COPYRECT", 4)) { return false; }
1947 this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
1948 this._FBU.x, this._FBU.y, this._FBU.width,
1949 this._FBU.height);
1950
1951 this._FBU.rects--;
1952 this._FBU.bytes = 0;
1953 return true;
1954 },
1955
1956 RRE: function () {
1957 var color;
1958 if (this._FBU.subrects === 0) {
26586b9d
PO
1959 this._FBU.bytes = 4 + 4;
1960 if (this._sock.rQwait("RRE", 4 + 4)) { return false; }
6d6f0db0 1961 this._FBU.subrects = this._sock.rQshift32();
26586b9d 1962 color = this._sock.rQshiftBytes(4); // Background
6d6f0db0
SR
1963 this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
1964 }
b1dee947 1965
26586b9d
PO
1966 while (this._FBU.subrects > 0 && this._sock.rQlen() >= (4 + 8)) {
1967 color = this._sock.rQshiftBytes(4);
6d6f0db0
SR
1968 var x = this._sock.rQshift16();
1969 var y = this._sock.rQshift16();
1970 var width = this._sock.rQshift16();
1971 var height = this._sock.rQshift16();
1972 this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
1973 this._FBU.subrects--;
1974 }
f00193e0 1975
6d6f0db0
SR
1976 if (this._FBU.subrects > 0) {
1977 var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
26586b9d 1978 this._FBU.bytes = (4 + 8) * chunk;
6d6f0db0 1979 } else {
b1dee947
SR
1980 this._FBU.rects--;
1981 this._FBU.bytes = 0;
6d6f0db0 1982 }
b1dee947 1983
6d6f0db0
SR
1984 return true;
1985 },
b1dee947 1986
6d6f0db0
SR
1987 HEXTILE: function () {
1988 var rQ = this._sock.get_rQ();
1989 var rQi = this._sock.get_rQi();
b1dee947 1990
6d6f0db0
SR
1991 if (this._FBU.tiles === 0) {
1992 this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
1993 this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
1994 this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
1995 this._FBU.tiles = this._FBU.total_tiles;
1996 }
b1dee947 1997
6d6f0db0
SR
1998 while (this._FBU.tiles > 0) {
1999 this._FBU.bytes = 1;
2000 if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
2001 var subencoding = rQ[rQi]; // Peek
2002 if (subencoding > 30) { // Raw
d472f3f1
SM
2003 this._fail("Illegal hextile subencoding (subencoding: " +
2004 subencoding + ")");
6d6f0db0 2005 return false;
b1dee947
SR
2006 }
2007
6d6f0db0
SR
2008 var subrects = 0;
2009 var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
2010 var tile_x = curr_tile % this._FBU.tiles_x;
2011 var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
2012 var x = this._FBU.x + tile_x * 16;
2013 var y = this._FBU.y + tile_y * 16;
2014 var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
2015 var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
b1dee947 2016
6d6f0db0
SR
2017 // Figure out how much we are expecting
2018 if (subencoding & 0x01) { // Raw
26586b9d 2019 this._FBU.bytes += w * h * 4;
6d6f0db0
SR
2020 } else {
2021 if (subencoding & 0x02) { // Background
26586b9d 2022 this._FBU.bytes += 4;
6d6f0db0
SR
2023 }
2024 if (subencoding & 0x04) { // Foreground
26586b9d 2025 this._FBU.bytes += 4;
6d6f0db0
SR
2026 }
2027 if (subencoding & 0x08) { // AnySubrects
2028 this._FBU.bytes++; // Since we aren't shifting it off
2029 if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
2030 subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
2031 if (subencoding & 0x10) { // SubrectsColoured
26586b9d 2032 this._FBU.bytes += subrects * (4 + 2);
6d6f0db0
SR
2033 } else {
2034 this._FBU.bytes += subrects * 2;
b1dee947
SR
2035 }
2036 }
6d6f0db0 2037 }
b1dee947 2038
6d6f0db0 2039 if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
b1dee947 2040
6d6f0db0
SR
2041 // We know the encoding and have a whole tile
2042 this._FBU.subencoding = rQ[rQi];
2043 rQi++;
2044 if (this._FBU.subencoding === 0) {
2045 if (this._FBU.lastsubencoding & 0x01) {
2046 // Weird: ignore blanks are RAW
2047 Log.Debug(" Ignoring blank after RAW");
b1dee947 2048 } else {
6d6f0db0
SR
2049 this._display.fillRect(x, y, w, h, this._FBU.background);
2050 }
2051 } else if (this._FBU.subencoding & 0x01) { // Raw
2052 this._display.blitImage(x, y, w, h, rQ, rQi);
2053 rQi += this._FBU.bytes - 1;
2054 } else {
2055 if (this._FBU.subencoding & 0x02) { // Background
26586b9d
PO
2056 this._FBU.background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2057 rQi += 4;
6d6f0db0
SR
2058 }
2059 if (this._FBU.subencoding & 0x04) { // Foreground
26586b9d
PO
2060 this._FBU.foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2061 rQi += 4;
6d6f0db0 2062 }
b1dee947 2063
6d6f0db0
SR
2064 this._display.startTile(x, y, w, h, this._FBU.background);
2065 if (this._FBU.subencoding & 0x08) { // AnySubrects
2066 subrects = rQ[rQi];
2067 rQi++;
b1dee947 2068
6d6f0db0
SR
2069 for (var s = 0; s < subrects; s++) {
2070 var color;
2071 if (this._FBU.subencoding & 0x10) { // SubrectsColoured
26586b9d
PO
2072 color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
2073 rQi += 4;
6d6f0db0
SR
2074 } else {
2075 color = this._FBU.foreground;
2076 }
2077 var xy = rQ[rQi];
2078 rQi++;
2079 var sx = (xy >> 4);
2080 var sy = (xy & 0x0f);
a7a89626 2081
6d6f0db0
SR
2082 var wh = rQ[rQi];
2083 rQi++;
2084 var sw = (wh >> 4) + 1;
2085 var sh = (wh & 0x0f) + 1;
a7a89626 2086
6d6f0db0 2087 this._display.subTile(sx, sy, sw, sh, color);
b1dee947 2088 }
a7a89626 2089 }
6d6f0db0 2090 this._display.finishTile();
a7a89626 2091 }
6d6f0db0
SR
2092 this._sock.set_rQi(rQi);
2093 this._FBU.lastsubencoding = this._FBU.subencoding;
2094 this._FBU.bytes = 0;
2095 this._FBU.tiles--;
2096 }
d065cad9 2097
6d6f0db0
SR
2098 if (this._FBU.tiles === 0) {
2099 this._FBU.rects--;
2100 }
b1dee947 2101
6d6f0db0
SR
2102 return true;
2103 },
b1dee947 2104
3e8b26ab 2105 TIGHT: function () {
6d6f0db0
SR
2106 this._FBU.bytes = 1; // compression-control byte
2107 if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
2108
2109 var checksum = function (data) {
2110 var sum = 0;
2111 for (var i = 0; i < data.length; i++) {
2112 sum += data[i];
2113 if (sum > 65536) sum -= 65536;
2114 }
2115 return sum;
2116 };
2117
2118 var resetStreams = 0;
2119 var streamId = -1;
2120 var decompress = function (data, expected) {
2121 for (var i = 0; i < 4; i++) {
2122 if ((resetStreams >> i) & 1) {
2123 this._FBU.zlibs[i].reset();
2124 Log.Info("Reset zlib stream " + i);
b1dee947 2125 }
6d6f0db0 2126 }
de84e098 2127
6d6f0db0
SR
2128 //var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
2129 var uncompressed = this._FBU.zlibs[streamId].inflate(data, true, expected);
2130 /*if (uncompressed.status !== 0) {
2131 Log.Error("Invalid data in zlib stream");
2132 }*/
2133
2134 //return uncompressed.data;
2135 return uncompressed;
2136 }.bind(this);
2137
2138 var indexedToRGBX2Color = function (data, palette, width, height) {
2139 // Convert indexed (palette based) image data to RGB
2140 // TODO: reduce number of calculations inside loop
2141 var dest = this._destBuff;
2142 var w = Math.floor((width + 7) / 8);
2143 var w1 = Math.floor(width / 8);
2144
2145 /*for (var y = 0; y < height; y++) {
2146 var b, x, dp, sp;
2147 var yoffset = y * width;
2148 var ybitoffset = y * w;
2149 var xoffset, targetbyte;
2150 for (x = 0; x < w1; x++) {
d1800d09
SR
2151 xoffset = yoffset + x * 8;
2152 targetbyte = data[ybitoffset + x];
6d6f0db0 2153 for (b = 7; b >= 0; b--) {
d1800d09
SR
2154 dp = (xoffset + 7 - b) * 3;
2155 sp = (targetbyte >> b & 1) * 3;
2156 dest[dp] = palette[sp];
2157 dest[dp + 1] = palette[sp + 1];
2158 dest[dp + 2] = palette[sp + 2];
2159 }
6d6f0db0
SR
2160 }
2161
2162 xoffset = yoffset + x * 8;
2163 targetbyte = data[ybitoffset + x];
2164 for (b = 7; b >= 8 - width % 8; b--) {
2165 dp = (xoffset + 7 - b) * 3;
2166 sp = (targetbyte >> b & 1) * 3;
2167 dest[dp] = palette[sp];
2168 dest[dp + 1] = palette[sp + 1];
2169 dest[dp + 2] = palette[sp + 2];
2170 }
2171 }*/
d1800d09 2172
6d6f0db0
SR
2173 for (var y = 0; y < height; y++) {
2174 var b, x, dp, sp;
2175 for (x = 0; x < w1; x++) {
2176 for (b = 7; b >= 0; b--) {
d1800d09
SR
2177 dp = (y * width + x * 8 + 7 - b) * 4;
2178 sp = (data[y * w + x] >> b & 1) * 3;
2179 dest[dp] = palette[sp];
2180 dest[dp + 1] = palette[sp + 1];
2181 dest[dp + 2] = palette[sp + 2];
2182 dest[dp + 3] = 255;
e16ad2fd
JM
2183 }
2184 }
b1dee947 2185
6d6f0db0
SR
2186 for (b = 7; b >= 8 - width % 8; b--) {
2187 dp = (y * width + x * 8 + 7 - b) * 4;
2188 sp = (data[y * w + x] >> b & 1) * 3;
2189 dest[dp] = palette[sp];
2190 dest[dp + 1] = palette[sp + 1];
2191 dest[dp + 2] = palette[sp + 2];
2192 dest[dp + 3] = 255;
d1800d09 2193 }
6d6f0db0 2194 }
d1800d09 2195
6d6f0db0
SR
2196 return dest;
2197 }.bind(this);
2198
2199 var indexedToRGBX = function (data, palette, width, height) {
2200 // Convert indexed (palette based) image data to RGB
2201 var dest = this._destBuff;
2202 var total = width * height * 4;
2203 for (var i = 0, j = 0; i < total; i += 4, j++) {
2204 var sp = data[j] * 3;
2205 dest[i] = palette[sp];
2206 dest[i + 1] = palette[sp + 1];
2207 dest[i + 2] = palette[sp + 2];
2208 dest[i + 3] = 255;
2209 }
2210
2211 return dest;
2212 }.bind(this);
2213
2214 var rQi = this._sock.get_rQi();
2215 var rQ = this._sock.rQwhole();
2216 var cmode, data;
2217 var cl_header, cl_data;
2218
2219 var handlePalette = function () {
2220 var numColors = rQ[rQi + 2] + 1;
26586b9d 2221 var paletteSize = numColors * 3;
6d6f0db0
SR
2222 this._FBU.bytes += paletteSize;
2223 if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
2224
2225 var bpp = (numColors <= 2) ? 1 : 8;
2226 var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
2227 var raw = false;
2228 if (rowSize * this._FBU.height < 12) {
2229 raw = true;
2230 cl_header = 0;
2231 cl_data = rowSize * this._FBU.height;
2232 //clength = [0, rowSize * this._FBU.height];
2233 } else {
2234 // begin inline getTightCLength (returning two-item arrays is bad for performance with GC)
2235 var cl_offset = rQi + 3 + paletteSize;
2236 cl_header = 1;
2237 cl_data = 0;
2238 cl_data += rQ[cl_offset] & 0x7f;
2239 if (rQ[cl_offset] & 0x80) {
2240 cl_header++;
2241 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2242 if (rQ[cl_offset + 1] & 0x80) {
d1800d09 2243 cl_header++;
6d6f0db0 2244 cl_data += rQ[cl_offset + 2] << 14;
d1800d09 2245 }
e16ad2fd 2246 }
6d6f0db0
SR
2247 // end inline getTightCLength
2248 }
b1dee947 2249
6d6f0db0
SR
2250 this._FBU.bytes += cl_header + cl_data;
2251 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
b1dee947 2252
6d6f0db0
SR
2253 // Shift ctl, filter id, num colors, palette entries, and clength off
2254 this._sock.rQskipBytes(3);
2255 //var palette = this._sock.rQshiftBytes(paletteSize);
2256 this._sock.rQshiftTo(this._paletteBuff, paletteSize);
2257 this._sock.rQskipBytes(cl_header);
b1dee947 2258
6d6f0db0
SR
2259 if (raw) {
2260 data = this._sock.rQshiftBytes(cl_data);
2261 } else {
2262 data = decompress(this._sock.rQshiftBytes(cl_data), rowSize * this._FBU.height);
2263 }
b1dee947 2264
6d6f0db0
SR
2265 // Convert indexed (palette based) image data to RGB
2266 var rgbx;
2267 if (numColors == 2) {
2268 rgbx = indexedToRGBX2Color(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2269 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2270 } else {
2271 rgbx = indexedToRGBX(data, this._paletteBuff, this._FBU.width, this._FBU.height);
2272 this._display.blitRgbxImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, rgbx, 0, false);
2273 }
b1dee947 2274
b1dee947 2275
6d6f0db0
SR
2276 return true;
2277 }.bind(this);
2278
2279 var handleCopy = function () {
2280 var raw = false;
26586b9d 2281 var uncompressedSize = this._FBU.width * this._FBU.height * 3;
6d6f0db0
SR
2282 if (uncompressedSize < 12) {
2283 raw = true;
2284 cl_header = 0;
2285 cl_data = uncompressedSize;
2286 } else {
2287 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2288 var cl_offset = rQi + 1;
2289 cl_header = 1;
2290 cl_data = 0;
2291 cl_data += rQ[cl_offset] & 0x7f;
2292 if (rQ[cl_offset] & 0x80) {
2293 cl_header++;
2294 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2295 if (rQ[cl_offset + 1] & 0x80) {
d1800d09 2296 cl_header++;
6d6f0db0 2297 cl_data += rQ[cl_offset + 2] << 14;
d1800d09 2298 }
b1dee947 2299 }
6d6f0db0
SR
2300 // end inline getTightCLength
2301 }
2302 this._FBU.bytes = 1 + cl_header + cl_data;
2303 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
b1dee947 2304
6d6f0db0
SR
2305 // Shift ctl, clength off
2306 this._sock.rQshiftBytes(1 + cl_header);
b1dee947 2307
6d6f0db0
SR
2308 if (raw) {
2309 data = this._sock.rQshiftBytes(cl_data);
2310 } else {
2311 data = decompress(this._sock.rQshiftBytes(cl_data), uncompressedSize);
2312 }
b1dee947 2313
6d6f0db0 2314 this._display.blitRgbImage(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, data, 0, false);
b1dee947 2315
6d6f0db0
SR
2316 return true;
2317 }.bind(this);
b1dee947 2318
6d6f0db0 2319 var ctl = this._sock.rQpeek8();
b1dee947 2320
6d6f0db0
SR
2321 // Keep tight reset bits
2322 resetStreams = ctl & 0xF;
b1dee947 2323
6d6f0db0
SR
2324 // Figure out filter
2325 ctl = ctl >> 4;
2326 streamId = ctl & 0x3;
b1dee947 2327
6d6f0db0
SR
2328 if (ctl === 0x08) cmode = "fill";
2329 else if (ctl === 0x09) cmode = "jpeg";
2330 else if (ctl === 0x0A) cmode = "png";
2331 else if (ctl & 0x04) cmode = "filter";
2332 else if (ctl < 0x04) cmode = "copy";
d472f3f1
SM
2333 else return this._fail("Illegal tight compression received (ctl: " +
2334 ctl + ")");
b1dee947 2335
6d6f0db0 2336 switch (cmode) {
26586b9d 2337 // fill use depth because TPIXELs drop the padding byte
6d6f0db0 2338 case "fill": // TPIXEL
26586b9d 2339 this._FBU.bytes += 3;
6d6f0db0
SR
2340 break;
2341 case "jpeg": // max clength
2342 this._FBU.bytes += 3;
2343 break;
2344 case "png": // max clength
2345 this._FBU.bytes += 3;
2346 break;
2347 case "filter": // filter id + num colors if palette
2348 this._FBU.bytes += 2;
2349 break;
2350 case "copy":
2351 break;
2352 }
d065cad9 2353
6d6f0db0 2354 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
b1dee947 2355
6d6f0db0
SR
2356 // Determine FBU.bytes
2357 switch (cmode) {
2358 case "fill":
2359 // skip ctl byte
2360 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);
2361 this._sock.rQskipBytes(4);
2362 break;
2363 case "png":
2364 case "jpeg":
2365 // begin inline getTightCLength (returning two-item arrays is for peformance with GC)
2366 var cl_offset = rQi + 1;
2367 cl_header = 1;
2368 cl_data = 0;
2369 cl_data += rQ[cl_offset] & 0x7f;
2370 if (rQ[cl_offset] & 0x80) {
2371 cl_header++;
2372 cl_data += (rQ[cl_offset + 1] & 0x7f) << 7;
2373 if (rQ[cl_offset + 1] & 0x80) {
d1800d09 2374 cl_header++;
6d6f0db0 2375 cl_data += rQ[cl_offset + 2] << 14;
b1dee947 2376 }
6d6f0db0
SR
2377 }
2378 // end inline getTightCLength
2379 this._FBU.bytes = 1 + cl_header + cl_data; // ctl + clength size + jpeg-data
2380 if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
798340b9 2381
6d6f0db0
SR
2382 // We have everything, render it
2383 this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
2384 data = this._sock.rQshiftBytes(cl_data);
2385 this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
2386 break;
2387 case "filter":
2388 var filterId = rQ[rQi + 1];
2389 if (filterId === 1) {
2390 if (!handlePalette()) { return false; }
b0ec6509 2391 } else {
6d6f0db0
SR
2392 // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
2393 // Filter 2, Gradient is valid but not use if jpeg is enabled
d472f3f1
SM
2394 this._fail("Unsupported tight subencoding received " +
2395 "(filter: " + filterId + ")");
b0ec6509 2396 }
6d6f0db0
SR
2397 break;
2398 case "copy":
2399 if (!handleCopy()) { return false; }
2400 break;
2401 }
b0ec6509 2402
6d6f0db0
SR
2403
2404 this._FBU.bytes = 0;
2405 this._FBU.rects--;
2406
2407 return true;
2408 },
2409
6d6f0db0
SR
2410 last_rect: function () {
2411 this._FBU.rects = 0;
2412 return true;
2413 },
2414
6d6f0db0
SR
2415 ExtendedDesktopSize: function () {
2416 this._FBU.bytes = 1;
2417 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2418
9b84f516 2419 var firstUpdate = !this._supportsSetDesktopSize;
6d6f0db0 2420 this._supportsSetDesktopSize = true;
9b84f516
PO
2421
2422 // Normally we only apply the current resize mode after a
2423 // window resize event. However there is no such trigger on the
2424 // initial connect. And we don't know if the server supports
2425 // resizing until we've gotten here.
2426 if (firstUpdate) {
2427 this._requestRemoteResize();
2428 }
832be262 2429
6d6f0db0
SR
2430 var number_of_screens = this._sock.rQpeek8();
2431
2432 this._FBU.bytes = 4 + (number_of_screens * 16);
2433 if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
2434
2435 this._sock.rQskipBytes(1); // number-of-screens
2436 this._sock.rQskipBytes(3); // padding
2437
2438 for (var i = 0; i < number_of_screens; i += 1) {
2439 // Save the id and flags of the first screen
2440 if (i === 0) {
2441 this._screen_id = this._sock.rQshiftBytes(4); // id
2442 this._sock.rQskipBytes(2); // x-position
2443 this._sock.rQskipBytes(2); // y-position
2444 this._sock.rQskipBytes(2); // width
2445 this._sock.rQskipBytes(2); // height
2446 this._screen_flags = this._sock.rQshiftBytes(4); // flags
2447 } else {
2448 this._sock.rQskipBytes(16);
798340b9 2449 }
6d6f0db0 2450 }
b0ec6509 2451
6d6f0db0
SR
2452 /*
2453 * The x-position indicates the reason for the change:
2454 *
2455 * 0 - server resized on its own
2456 * 1 - this client requested the resize
2457 * 2 - another client requested the resize
2458 */
b0ec6509 2459
6d6f0db0
SR
2460 // We need to handle errors when we requested the resize.
2461 if (this._FBU.x === 1 && this._FBU.y !== 0) {
2462 var msg = "";
2463 // The y-position indicates the status code from the server
2464 switch (this._FBU.y) {
2465 case 1:
2466 msg = "Resize is administratively prohibited";
2467 break;
2468 case 2:
2469 msg = "Out of resources";
2470 break;
2471 case 3:
2472 msg = "Invalid screen layout";
2473 break;
2474 default:
2475 msg = "Unknown reason";
2476 break;
2477 }
5b20d338
SM
2478 Log.Warn("Server did not accept the resize request: "
2479 + msg);
910fd3af
PO
2480 } else {
2481 this._resize(this._FBU.width, this._FBU.height);
6d6f0db0 2482 }
b1dee947 2483
91d5c625
PO
2484 this._FBU.bytes = 0;
2485 this._FBU.rects -= 1;
6d6f0db0
SR
2486 return true;
2487 },
b1dee947 2488
6d6f0db0 2489 DesktopSize: function () {
91d5c625
PO
2490 this._resize(this._FBU.width, this._FBU.height);
2491 this._FBU.bytes = 0;
2492 this._FBU.rects -= 1;
6d6f0db0
SR
2493 return true;
2494 },
8db09746 2495
6d6f0db0
SR
2496 Cursor: function () {
2497 Log.Debug(">> set_cursor");
2498 var x = this._FBU.x; // hotspot-x
2499 var y = this._FBU.y; // hotspot-y
2500 var w = this._FBU.width;
2501 var h = this._FBU.height;
8db09746 2502
26586b9d 2503 var pixelslength = w * h * 4;
6d6f0db0 2504 var masklength = Math.floor((w + 7) / 8) * h;
a7a89626 2505
6d6f0db0
SR
2506 this._FBU.bytes = pixelslength + masklength;
2507 if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
b1dee947 2508
6d6f0db0
SR
2509 this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
2510 this._sock.rQshiftBytes(masklength),
2511 x, y, w, h);
b1dee947 2512
6d6f0db0
SR
2513 this._FBU.bytes = 0;
2514 this._FBU.rects--;
8f06673a 2515
6d6f0db0
SR
2516 Log.Debug("<< set_cursor");
2517 return true;
2518 },
25e4928f 2519
6d6f0db0
SR
2520 QEMUExtendedKeyEvent: function () {
2521 this._FBU.rects--;
25e4928f 2522
2bf4cf5a
PO
2523 // Old Safari doesn't support creating keyboard events
2524 try {
2525 var keyboardEvent = document.createEvent("keyboardEvent");
2526 if (keyboardEvent.code !== undefined) {
2527 this._qemuExtKeyEventSupported = true;
2528 }
2529 } catch (err) {
058be785 2530 }
6d6f0db0 2531 },
6d6f0db0 2532};