]> git.proxmox.com Git - mirror_novnc.git/blame - core/rfb.js
Use undefined as the default value for password
[mirror_novnc.git] / core / rfb.js
CommitLineData
a7a89626
JM
1/*
2 * noVNC: HTML5 VNC client
412d9306 3 * Copyright (C) 2019 The noVNC Authors
1d728ace 4 * Licensed under MPL 2.0 (see LICENSE.txt)
a7a89626
JM
5 *
6 * See README.md for usage and integration instructions.
d38db74a 7 *
a7a89626
JM
8 */
9
6d6f0db0 10import * as Log from './util/logging.js';
6d6f0db0 11import { decodeUTF8 } from './util/strings.js';
3b7c4741 12import { dragThreshold } from './util/browser.js';
e89eef94 13import EventTargetMixin from './util/eventtarget.js';
3ae0bb09 14import Display from "./display.js";
c1e2785f
SM
15import Keyboard from "./input/keyboard.js";
16import Mouse from "./input/mouse.js";
b475eed5 17import Cursor from "./util/cursor.js";
3ae0bb09 18import Websock from "./websock.js";
3ae0bb09
SR
19import DES from "./des.js";
20import KeyTable from "./input/keysym.js";
21import XtScancode from "./input/xtscancodes.js";
e17cae8f 22import { encodings } from "./encodings.js";
e89eef94 23import "./util/polyfill.js";
3ae0bb09 24
923cd220
PO
25import RawDecoder from "./decoders/raw.js";
26import CopyRectDecoder from "./decoders/copyrect.js";
27import RREDecoder from "./decoders/rre.js";
28import HextileDecoder from "./decoders/hextile.js";
29import TightDecoder from "./decoders/tight.js";
30import TightPNGDecoder from "./decoders/tightpng.js";
31
68e09edc 32// How many seconds to wait for a disconnect to finish
2b5f94fa 33const DISCONNECT_TIMEOUT = 3;
d7791ebb 34const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
68e09edc 35
0e4808bf
JD
36export default class RFB extends EventTargetMixin {
37 constructor(target, url, options) {
38 if (!target) {
d3ed883a 39 throw new Error("Must specify target");
0e4808bf
JD
40 }
41 if (!url) {
d3ed883a 42 throw new Error("Must specify URL");
0e4808bf
JD
43 }
44
45 super();
46
47 this._target = target;
48 this._url = url;
49
50 // Connection details
51 options = options || {};
52 this._rfb_credentials = options.credentials || {};
53 this._shared = 'shared' in options ? !!options.shared : true;
54 this._repeaterID = options.repeaterID || '';
c9122303 55 this._wsProtocols = options.wsProtocols || [];
0e4808bf
JD
56
57 // Internal state
58 this._rfb_connection_state = '';
59 this._rfb_init_state = '';
c02b18f0 60 this._rfb_auth_scheme = -1;
0e4808bf
JD
61 this._rfb_clean_disconnect = true;
62
63 // Server capabilities
64 this._rfb_version = 0;
65 this._rfb_max_version = 3.8;
66 this._rfb_tightvnc = false;
67 this._rfb_xvp_ver = 0;
68
69 this._fb_width = 0;
70 this._fb_height = 0;
71
72 this._fb_name = "";
73
74 this._capabilities = { power: false };
75
76 this._supportsFence = false;
77
78 this._supportsContinuousUpdates = false;
79 this._enabledContinuousUpdates = false;
80
81 this._supportsSetDesktopSize = false;
82 this._screen_id = 0;
83 this._screen_flags = 0;
84
85 this._qemuExtKeyEventSupported = false;
86
87 // Internal objects
88 this._sock = null; // Websock object
89 this._display = null; // Display object
90 this._flushing = false; // Display flushing state
91 this._keyboard = null; // Keyboard input handler object
92 this._mouse = null; // Mouse input handler object
93
94 // Timers
95 this._disconnTimer = null; // disconnection timer
96 this._resizeTimeout = null; // resize rate limiting
97
e17cae8f 98 // Decoder states
923cd220 99 this._decoders = {};
0e4808bf
JD
100
101 this._FBU = {
102 rects: 0,
0e4808bf
JD
103 x: 0,
104 y: 0,
105 width: 0,
106 height: 0,
923cd220 107 encoding: null,
0e4808bf
JD
108 };
109
110 // Mouse state
111 this._mouse_buttonMask = 0;
112 this._mouse_arr = [];
113 this._viewportDragging = false;
114 this._viewportDragPos = {};
115 this._viewportHasMoved = false;
116
117 // Bound event handlers
118 this._eventHandlers = {
119 focusCanvas: this._focusCanvas.bind(this),
120 windowResize: this._windowResize.bind(this),
121 };
122
123 // main setup
124 Log.Debug(">> RFB.constructor");
125
126 // Create DOM elements
127 this._screen = document.createElement('div');
128 this._screen.style.display = 'flex';
129 this._screen.style.width = '100%';
130 this._screen.style.height = '100%';
131 this._screen.style.overflow = 'auto';
d7791ebb 132 this._screen.style.background = DEFAULT_BACKGROUND;
0e4808bf
JD
133 this._canvas = document.createElement('canvas');
134 this._canvas.style.margin = 'auto';
135 // Some browsers add an outline on focus
136 this._canvas.style.outline = 'none';
137 // IE miscalculates width without this :(
138 this._canvas.style.flexShrink = '0';
139 this._canvas.width = 0;
140 this._canvas.height = 0;
141 this._canvas.tabIndex = -1;
142 this._screen.appendChild(this._canvas);
45808617 143
d1314d4b
AP
144 // Cursor
145 this._cursor = new Cursor();
4c38179d
AP
146
147 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
148 // it. Result: no cursor at all until a window border or an edit field
149 // is hit blindly. But there are also VNC servers that draw the cursor
150 // in the framebuffer and don't send the empty local cursor. There is
151 // no way to satisfy both sides.
152 //
153 // The spec is unclear on this "initial cursor" issue. Many other
154 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
155 // initial cursor instead.
156 this._cursorImage = RFB.cursors.none;
b475eed5 157
923cd220
PO
158 // populate decoder array with objects
159 this._decoders[encodings.encodingRaw] = new RawDecoder();
160 this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
161 this._decoders[encodings.encodingRRE] = new RREDecoder();
162 this._decoders[encodings.encodingHextile] = new HextileDecoder();
163 this._decoders[encodings.encodingTight] = new TightDecoder();
164 this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
0e4808bf
JD
165
166 // NB: nothing that needs explicit teardown should be done
167 // before this point, since this can throw an exception
168 try {
169 this._display = new Display(this._canvas);
170 } catch (exc) {
171 Log.Error("Display exception: " + exc);
172 throw exc;
173 }
174 this._display.onflush = this._onFlush.bind(this);
0e4808bf
JD
175
176 this._keyboard = new Keyboard(this._canvas);
177 this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
178
179 this._mouse = new Mouse(this._canvas);
180 this._mouse.onmousebutton = this._handleMouseButton.bind(this);
181 this._mouse.onmousemove = this._handleMouseMove.bind(this);
182
183 this._sock = new Websock();
b8ff5d1b
PO
184 this._sock.on('message', () => {
185 this._handle_message();
186 });
651c23ec 187 this._sock.on('open', () => {
0e4808bf
JD
188 if ((this._rfb_connection_state === 'connecting') &&
189 (this._rfb_init_state === '')) {
190 this._rfb_init_state = 'ProtocolVersion';
191 Log.Debug("Starting VNC handshake");
192 } else {
193 this._fail("Unexpected server connection while " +
651c23ec 194 this._rfb_connection_state);
b1dee947 195 }
651c23ec
JD
196 });
197 this._sock.on('close', (e) => {
0e4808bf
JD
198 Log.Debug("WebSocket on-close event");
199 let msg = "";
200 if (e.code) {
201 msg = "(code: " + e.code;
202 if (e.reason) {
203 msg += ", reason: " + e.reason;
204 }
205 msg += ")";
206 }
207 switch (this._rfb_connection_state) {
208 case 'connecting':
209 this._fail("Connection closed " + msg);
210 break;
211 case 'connected':
212 // Handle disconnects that were initiated server-side
213 this._updateConnectionState('disconnecting');
214 this._updateConnectionState('disconnected');
215 break;
216 case 'disconnecting':
217 // Normal disconnection path
218 this._updateConnectionState('disconnected');
219 break;
220 case 'disconnected':
221 this._fail("Unexpected server disconnect " +
651c23ec 222 "when already disconnected " + msg);
0e4808bf
JD
223 break;
224 default:
225 this._fail("Unexpected server disconnect before connecting " +
651c23ec 226 msg);
0e4808bf
JD
227 break;
228 }
229 this._sock.off('close');
0e4808bf 230 });
651c23ec 231 this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
8db09746 232
0e4808bf
JD
233 // Slight delay of the actual connection so that the caller has
234 // time to set up callbacks
235 setTimeout(this._updateConnectionState.bind(this, 'connecting'));
ae510306 236
0e4808bf 237 Log.Debug("<< RFB.constructor");
a7a89626 238
0e4808bf 239 // ===== PROPERTIES =====
6d6f0db0 240
0e4808bf
JD
241 this.dragViewport = false;
242 this.focusOnClick = true;
a7a89626 243
0e4808bf
JD
244 this._viewOnly = false;
245 this._clipViewport = false;
246 this._scaleViewport = false;
247 this._resizeSession = false;
6aed0b4d
PO
248
249 this._showDotCursor = false;
250 if (options.showDotCursor !== undefined) {
251 Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
252 this._showDotCursor = options.showDotCursor;
253 }
0e4808bf
JD
254 }
255
256 // ===== PROPERTIES =====
257
258 get viewOnly() { return this._viewOnly; }
747b4623
PO
259 set viewOnly(viewOnly) {
260 this._viewOnly = viewOnly;
261
262 if (this._rfb_connection_state === "connecting" ||
263 this._rfb_connection_state === "connected") {
264 if (viewOnly) {
265 this._keyboard.ungrab();
266 this._mouse.ungrab();
267 } else {
268 this._keyboard.grab();
269 this._mouse.grab();
270 }
271 }
0e4808bf 272 }
6d6f0db0 273
0e4808bf 274 get capabilities() { return this._capabilities; }
747b4623 275
0e4808bf
JD
276 get touchButton() { return this._mouse.touchButton; }
277 set touchButton(button) { this._mouse.touchButton = button; }
747b4623 278
0e4808bf 279 get clipViewport() { return this._clipViewport; }
9b84f516
PO
280 set clipViewport(viewport) {
281 this._clipViewport = viewport;
282 this._updateClip();
0e4808bf 283 }
747b4623 284
0e4808bf 285 get scaleViewport() { return this._scaleViewport; }
9b84f516
PO
286 set scaleViewport(scale) {
287 this._scaleViewport = scale;
288 // Scaling trumps clipping, so we may need to adjust
289 // clipping when enabling or disabling scaling
290 if (scale && this._clipViewport) {
291 this._updateClip();
292 }
293 this._updateScale();
294 if (!scale && this._clipViewport) {
295 this._updateClip();
296 }
0e4808bf 297 }
747b4623 298
0e4808bf 299 get resizeSession() { return this._resizeSession; }
9b84f516
PO
300 set resizeSession(resize) {
301 this._resizeSession = resize;
302 if (resize) {
303 this._requestRemoteResize();
304 }
0e4808bf 305 }
a80aa416 306
4c38179d
AP
307 get showDotCursor() { return this._showDotCursor; }
308 set showDotCursor(show) {
309 this._showDotCursor = show;
310 this._refreshCursor();
311 }
312
d7791ebb
MM
313 get background() { return this._screen.style.background; }
314 set background(cssValue) { this._screen.style.background = cssValue; }
315
747b4623
PO
316 // ===== PUBLIC METHODS =====
317
0e4808bf 318 disconnect() {
6d6f0db0
SR
319 this._updateConnectionState('disconnecting');
320 this._sock.off('error');
321 this._sock.off('message');
322 this._sock.off('open');
0e4808bf 323 }
6d6f0db0 324
0e4808bf 325 sendCredentials(creds) {
430f00d6 326 this._rfb_credentials = creds;
6d6f0db0 327 setTimeout(this._init_msg.bind(this), 0);
0e4808bf 328 }
6d6f0db0 329
0e4808bf 330 sendCtrlAltDel() {
15a62695 331 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
6d6f0db0
SR
332 Log.Info("Sending Ctrl-Alt-Del");
333
94f5cf05
PO
334 this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
335 this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
336 this.sendKey(KeyTable.XK_Delete, "Delete", true);
337 this.sendKey(KeyTable.XK_Delete, "Delete", false);
338 this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
339 this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
0e4808bf 340 }
6d6f0db0 341
0e4808bf 342 machineShutdown() {
cd523e8f 343 this._xvpOp(1, 2);
0e4808bf 344 }
6d6f0db0 345
0e4808bf 346 machineReboot() {
cd523e8f 347 this._xvpOp(1, 3);
0e4808bf 348 }
6d6f0db0 349
0e4808bf 350 machineReset() {
cd523e8f 351 this._xvpOp(1, 4);
0e4808bf 352 }
6d6f0db0
SR
353
354 // Send a key press. If 'down' is not specified then send a down key
355 // followed by an up key.
0e4808bf 356 sendKey(keysym, code, down) {
15a62695 357 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
94f5cf05
PO
358
359 if (down === undefined) {
360 this.sendKey(keysym, code, true);
361 this.sendKey(keysym, code, false);
15a62695 362 return;
94f5cf05
PO
363 }
364
2b5f94fa 365 const scancode = XtScancode[code];
94f5cf05 366
be70fe0a 367 if (this._qemuExtKeyEventSupported && scancode) {
459ed008
PO
368 // 0 is NoSymbol
369 keysym = keysym || 0;
370
94f5cf05
PO
371 Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
372
373 RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
374 } else {
459ed008 375 if (!keysym) {
15a62695 376 return;
459ed008 377 }
6d6f0db0
SR
378 Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
379 RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
6d6f0db0 380 }
0e4808bf 381 }
6d6f0db0 382
0e4808bf 383 focus() {
9b84f516 384 this._canvas.focus();
0e4808bf 385 }
b9854a5c 386
0e4808bf 387 blur() {
9b84f516 388 this._canvas.blur();
0e4808bf 389 }
b9854a5c 390
0e4808bf 391 clipboardPasteFrom(text) {
9b84f516
PO
392 if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
393 RFB.messages.clientCutText(this._sock, text);
0e4808bf 394 }
b1dee947 395
747b4623 396 // ===== PRIVATE METHODS =====
b0ec6509 397
0e4808bf 398 _connect() {
6d6f0db0 399 Log.Debug(">> RFB.connect");
b0ec6509 400
5b4e5d01 401 Log.Info("connecting to " + this._url);
b1dee947 402
6d6f0db0
SR
403 try {
404 // WebSocket.onopen transitions to the RFB init states
25b3d49d 405 this._sock.open(this._url, this._wsProtocols);
6d6f0db0
SR
406 } catch (e) {
407 if (e.name === 'SyntaxError') {
d472f3f1 408 this._fail("Invalid host or port (" + e + ")");
b1dee947 409 } else {
d472f3f1 410 this._fail("Error when opening socket (" + e + ")");
159ad55d 411 }
6d6f0db0 412 }
e3efeb32 413
9b84f516
PO
414 // Make our elements part of the page
415 this._target.appendChild(this._screen);
416
b475eed5 417 this._cursor.attach(this._canvas);
4c38179d 418 this._refreshCursor();
b475eed5 419
9b84f516
PO
420 // Monitor size changes of the screen
421 // FIXME: Use ResizeObserver, or hidden overflow
422 window.addEventListener('resize', this._eventHandlers.windowResize);
423
2afda544 424 // Always grab focus on some kind of click event
9b84f516
PO
425 this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
426 this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
2afda544 427
6d6f0db0 428 Log.Debug("<< RFB.connect");
0e4808bf 429 }
6d6f0db0 430
0e4808bf 431 _disconnect() {
6d6f0db0 432 Log.Debug(">> RFB.disconnect");
b475eed5 433 this._cursor.detach();
9b84f516
PO
434 this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
435 this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
436 window.removeEventListener('resize', this._eventHandlers.windowResize);
437 this._keyboard.ungrab();
438 this._mouse.ungrab();
6d6f0db0 439 this._sock.close();
b245ec70
SM
440 try {
441 this._target.removeChild(this._screen);
442 } catch (e) {
443 if (e.name === 'NotFoundError') {
444 // Some cases where the initial connection fails
445 // can disconnect before the _screen is created
446 } else {
447 throw e;
448 }
449 }
9b84f516 450 clearTimeout(this._resizeTimeout);
6d6f0db0 451 Log.Debug("<< RFB.disconnect");
0e4808bf 452 }
6d6f0db0 453
0e4808bf 454 _focusCanvas(event) {
2afda544 455 // Respect earlier handlers' request to not do side-effects
1d6ff4a3
PO
456 if (event.defaultPrevented) {
457 return;
458 }
459
b8dfb983 460 if (!this.focusOnClick) {
1d6ff4a3
PO
461 return;
462 }
463
9b84f516 464 this.focus();
0e4808bf 465 }
9b84f516 466
ce66b469
NL
467 _setDesktopName(name) {
468 this._fb_name = name;
469 this.dispatchEvent(new CustomEvent(
470 "desktopname",
471 { detail: { name: this._fb_name } }));
472 }
473
0e4808bf 474 _windowResize(event) {
9b84f516
PO
475 // If the window resized then our screen element might have
476 // as well. Update the viewport dimensions.
651c23ec 477 window.requestAnimationFrame(() => {
9b84f516
PO
478 this._updateClip();
479 this._updateScale();
651c23ec 480 });
9b84f516
PO
481
482 if (this._resizeSession) {
483 // Request changing the resolution of the remote display to
484 // the size of the local browser viewport.
485
486 // In order to not send multiple requests before the browser-resize
487 // is finished we wait 0.5 seconds before sending the request.
488 clearTimeout(this._resizeTimeout);
489 this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
490 }
0e4808bf 491 }
9b84f516
PO
492
493 // Update state of clipping in Display object, and make sure the
494 // configured viewport matches the current screen size
0e4808bf 495 _updateClip() {
2b5f94fa
JD
496 const cur_clip = this._display.clipViewport;
497 let new_clip = this._clipViewport;
9b84f516
PO
498
499 if (this._scaleViewport) {
500 // Disable viewport clipping if we are scaling
501 new_clip = false;
502 }
503
504 if (cur_clip !== new_clip) {
505 this._display.clipViewport = new_clip;
506 }
507
508 if (new_clip) {
509 // When clipping is enabled, the screen is limited to
510 // the size of the container.
2b5f94fa 511 const size = this._screenSize();
9b84f516
PO
512 this._display.viewportChangeSize(size.w, size.h);
513 this._fixScrollbars();
514 }
0e4808bf 515 }
9b84f516 516
0e4808bf 517 _updateScale() {
9b84f516
PO
518 if (!this._scaleViewport) {
519 this._display.scale = 1.0;
520 } else {
2b5f94fa 521 const size = this._screenSize();
9b84f516
PO
522 this._display.autoscale(size.w, size.h);
523 }
524 this._fixScrollbars();
0e4808bf 525 }
9b84f516
PO
526
527 // Requests a change of remote desktop size. This message is an extension
528 // and may only be sent if we have received an ExtendedDesktopSize message
0e4808bf 529 _requestRemoteResize() {
9b84f516
PO
530 clearTimeout(this._resizeTimeout);
531 this._resizeTimeout = null;
532
533 if (!this._resizeSession || this._viewOnly ||
534 !this._supportsSetDesktopSize) {
535 return;
536 }
537
2b5f94fa 538 const size = this._screenSize();
ab1ace38
PO
539 RFB.messages.setDesktopSize(this._sock,
540 Math.floor(size.w), Math.floor(size.h),
9b84f516
PO
541 this._screen_id, this._screen_flags);
542
543 Log.Debug('Requested new desktop size: ' +
544 size.w + 'x' + size.h);
0e4808bf 545 }
9b84f516
PO
546
547 // Gets the the size of the available screen
0e4808bf 548 _screenSize() {
ab1ace38
PO
549 let r = this._screen.getBoundingClientRect();
550 return { w: r.width, h: r.height };
0e4808bf 551 }
9b84f516 552
0e4808bf 553 _fixScrollbars() {
9b84f516
PO
554 // This is a hack because Chrome screws up the calculation
555 // for when scrollbars are needed. So to fix it we temporarily
556 // toggle them off and on.
2b5f94fa 557 const orig = this._screen.style.overflow;
9b84f516
PO
558 this._screen.style.overflow = 'hidden';
559 // Force Chrome to recalculate the layout by asking for
560 // an element's dimensions
561 this._screen.getBoundingClientRect();
562 this._screen.style.overflow = orig;
0e4808bf 563 }
2afda544 564
6d6f0db0
SR
565 /*
566 * Connection states:
567 * connecting
568 * connected
569 * disconnecting
570 * disconnected - permanent state
571 */
0e4808bf 572 _updateConnectionState(state) {
2b5f94fa 573 const oldstate = this._rfb_connection_state;
6d6f0db0
SR
574
575 if (state === oldstate) {
576 Log.Debug("Already in state '" + state + "', ignoring");
577 return;
578 }
579
580 // The 'disconnected' state is permanent for each RFB object
581 if (oldstate === 'disconnected') {
582 Log.Error("Tried changing state of a disconnected RFB object");
583 return;
584 }
585
586 // Ensure proper transitions before doing anything
587 switch (state) {
588 case 'connected':
589 if (oldstate !== 'connecting') {
590 Log.Error("Bad transition to connected state, " +
591 "previous connection state: " + oldstate);
592 return;
b1dee947 593 }
6d6f0db0 594 break;
a7a89626 595
6d6f0db0
SR
596 case 'disconnected':
597 if (oldstate !== 'disconnecting') {
598 Log.Error("Bad transition to disconnected state, " +
599 "previous connection state: " + oldstate);
600 return;
601 }
602 break;
a7a89626 603
6d6f0db0
SR
604 case 'connecting':
605 if (oldstate !== '') {
606 Log.Error("Bad transition to connecting state, " +
607 "previous connection state: " + oldstate);
608 return;
609 }
610 break;
b1dee947 611
6d6f0db0
SR
612 case 'disconnecting':
613 if (oldstate !== 'connected' && oldstate !== 'connecting') {
614 Log.Error("Bad transition to disconnecting state, " +
615 "previous connection state: " + oldstate);
616 return;
617 }
618 break;
a679a97d 619
6d6f0db0
SR
620 default:
621 Log.Error("Unknown connection state: " + state);
3bb12056 622 return;
6d6f0db0 623 }
b1dee947 624
6d6f0db0 625 // State change actions
3bb12056 626
6d6f0db0 627 this._rfb_connection_state = state;
b2e961d4 628
651c23ec 629 Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
b2e961d4 630
6d6f0db0
SR
631 if (this._disconnTimer && state !== 'disconnecting') {
632 Log.Debug("Clearing disconnect timer");
633 clearTimeout(this._disconnTimer);
634 this._disconnTimer = null;
3bb12056 635
6d6f0db0
SR
636 // make sure we don't get a double event
637 this._sock.off('close');
638 }
b2e961d4 639
6d6f0db0 640 switch (state) {
6d6f0db0
SR
641 case 'connecting':
642 this._connect();
643 break;
b2e961d4 644
ee5cae9f 645 case 'connected':
2b5f94fa 646 this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
ee5cae9f
SM
647 break;
648
6d6f0db0
SR
649 case 'disconnecting':
650 this._disconnect();
b2e961d4 651
651c23ec 652 this._disconnTimer = setTimeout(() => {
d472f3f1 653 Log.Error("Disconnection timed out.");
6d6f0db0 654 this._updateConnectionState('disconnected');
651c23ec 655 }, DISCONNECT_TIMEOUT * 1000);
6d6f0db0 656 break;
ee5cae9f
SM
657
658 case 'disconnected':
2b5f94fa 659 this.dispatchEvent(new CustomEvent(
d472f3f1 660 "disconnect", { detail:
2b5f94fa 661 { clean: this._rfb_clean_disconnect } }));
ee5cae9f 662 break;
6d6f0db0 663 }
0e4808bf 664 }
6d6f0db0
SR
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 */
0e4808bf 671 _fail(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;
0e4808bf 693 }
6d6f0db0 694
0e4808bf 695 _setCapability(cap, val) {
832be262 696 this._capabilities[cap] = val;
2b5f94fa 697 this.dispatchEvent(new CustomEvent("capabilities",
0e4808bf
JD
698 { detail: { capabilities: this._capabilities } }));
699 }
b1dee947 700
0e4808bf 701 _handle_message() {
8a189a62 702 if (this._sock.rQlen === 0) {
6d6f0db0
SR
703 Log.Warn("handle_message called on an empty receive queue");
704 return;
705 }
b1dee947 706
6d6f0db0
SR
707 switch (this._rfb_connection_state) {
708 case 'disconnected':
709 Log.Error("Got data while disconnected");
710 break;
711 case 'connected':
712 while (true) {
713 if (this._flushing) {
714 break;
715 }
716 if (!this._normal_msg()) {
717 break;
718 }
8a189a62 719 if (this._sock.rQlen === 0) {
6d6f0db0
SR
720 break;
721 }
722 }
723 break;
724 default:
725 this._init_msg();
726 break;
727 }
0e4808bf 728 }
3bb12056 729
0e4808bf 730 _handleKeyEvent(keysym, code, down) {
d0703d1b 731 this.sendKey(keysym, code, down);
0e4808bf 732 }
06a9ef0c 733
0e4808bf 734 _handleMouseButton(x, y, down, bmask) {
6d6f0db0
SR
735 if (down) {
736 this._mouse_buttonMask |= bmask;
737 } else {
270bdbd7 738 this._mouse_buttonMask &= ~bmask;
6d6f0db0 739 }
a7127fee 740
0460e5fd 741 if (this.dragViewport) {
6d6f0db0
SR
742 if (down && !this._viewportDragging) {
743 this._viewportDragging = true;
744 this._viewportDragPos = {'x': x, 'y': y};
898cd32c 745 this._viewportHasMoved = false;
a7127fee 746
6d6f0db0 747 // Skip sending mouse events
b1dee947 748 return;
6d6f0db0
SR
749 } else {
750 this._viewportDragging = false;
8db09746 751
898cd32c
PO
752 // If we actually performed a drag then we are done
753 // here and should not send any mouse events
754 if (this._viewportHasMoved) {
755 return;
6d6f0db0 756 }
898cd32c
PO
757
758 // Otherwise we treat this as a mouse click event.
759 // Send the button down event here, as the button up
760 // event is sent at the end of this function.
761 RFB.messages.pointerEvent(this._sock,
762 this._display.absX(x),
763 this._display.absY(y),
764 bmask);
b1dee947 765 }
6d6f0db0 766 }
8db09746 767
747b4623 768 if (this._viewOnly) { return; } // View only, skip mouse events
8f06673a 769
6d6f0db0
SR
770 if (this._rfb_connection_state !== 'connected') { return; }
771 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
0e4808bf 772 }
8db09746 773
0e4808bf 774 _handleMouseMove(x, y) {
6d6f0db0 775 if (this._viewportDragging) {
2b5f94fa
JD
776 const deltaX = this._viewportDragPos.x - x;
777 const deltaY = this._viewportDragPos.y - y;
8db09746 778
6d6f0db0
SR
779 if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
780 Math.abs(deltaY) > dragThreshold)) {
781 this._viewportHasMoved = true;
27e77d46 782
6d6f0db0
SR
783 this._viewportDragPos = {'x': x, 'y': y};
784 this._display.viewportChangePos(deltaX, deltaY);
b50f3406 785 }
8db09746 786
6d6f0db0
SR
787 // Skip sending mouse events
788 return;
789 }
b1dee947 790
747b4623 791 if (this._viewOnly) { return; } // View only, skip mouse events
b1dee947 792
6d6f0db0
SR
793 if (this._rfb_connection_state !== 'connected') { return; }
794 RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
0e4808bf 795 }
b1dee947 796
6d6f0db0 797 // Message Handlers
27e77d46 798
0e4808bf 799 _negotiate_protocol_version() {
c13df5ae
PO
800 if (this._sock.rQwait("version", 12)) {
801 return false;
6d6f0db0 802 }
32df3fdb 803
2b5f94fa 804 const sversion = this._sock.rQshiftStr(12).substr(4, 7);
6d6f0db0 805 Log.Info("Server ProtocolVersion: " + sversion);
2b5f94fa 806 let is_repeater = 0;
6d6f0db0
SR
807 switch (sversion) {
808 case "000.000": // UltraVNC repeater
809 is_repeater = 1;
810 break;
811 case "003.003":
812 case "003.006": // UltraVNC
813 case "003.889": // Apple Remote Desktop
814 this._rfb_version = 3.3;
815 break;
816 case "003.007":
817 this._rfb_version = 3.7;
818 break;
819 case "003.008":
820 case "004.000": // Intel AMT KVM
821 case "004.001": // RealVNC 4.6
822 case "005.000": // RealVNC 5.3
823 this._rfb_version = 3.8;
824 break;
825 default:
d472f3f1 826 return this._fail("Invalid server version " + sversion);
6d6f0db0 827 }
8db09746 828
6d6f0db0 829 if (is_repeater) {
2b5f94fa 830 let repeaterID = "ID:" + this._repeaterID;
6d6f0db0
SR
831 while (repeaterID.length < 250) {
832 repeaterID += "\0";
b1dee947 833 }
6d6f0db0
SR
834 this._sock.send_string(repeaterID);
835 return true;
836 }
b1dee947 837
6d6f0db0
SR
838 if (this._rfb_version > this._rfb_max_version) {
839 this._rfb_version = this._rfb_max_version;
840 }
b1dee947 841
2b5f94fa 842 const cversion = "00" + parseInt(this._rfb_version, 10) +
6d6f0db0
SR
843 ".00" + ((this._rfb_version * 10) % 10);
844 this._sock.send_string("RFB " + cversion + "\n");
845 Log.Debug('Sent ProtocolVersion: ' + cversion);
b1dee947 846
6d6f0db0 847 this._rfb_init_state = 'Security';
0e4808bf 848 }
b1dee947 849
0e4808bf 850 _negotiate_security() {
6d6f0db0
SR
851 // Polyfill since IE and PhantomJS doesn't have
852 // TypedArray.includes()
853 function includes(item, array) {
2b5f94fa 854 for (let i = 0; i < array.length; i++) {
6d6f0db0
SR
855 if (array[i] === item) {
856 return true;
857 }
b1dee947 858 }
6d6f0db0
SR
859 return false;
860 }
b1dee947 861
6d6f0db0
SR
862 if (this._rfb_version >= 3.7) {
863 // Server sends supported list, client decides
2b5f94fa 864 const num_types = this._sock.rQshift8();
6d6f0db0 865 if (this._sock.rQwait("security type", num_types, 1)) { return false; }
b1dee947 866
6d6f0db0 867 if (num_types === 0) {
3bb15d4a
PO
868 this._rfb_init_state = "SecurityReason";
869 this._security_context = "no security types";
870 this._security_status = 1;
871 return this._init_msg();
6d6f0db0
SR
872 }
873
2b5f94fa 874 const types = this._sock.rQshiftBytes(num_types);
6d6f0db0
SR
875 Log.Debug("Server security types: " + types);
876
877 // Look for each auth in preferred order
6d6f0db0
SR
878 if (includes(1, types)) {
879 this._rfb_auth_scheme = 1; // None
880 } else if (includes(22, types)) {
881 this._rfb_auth_scheme = 22; // XVP
882 } else if (includes(16, types)) {
883 this._rfb_auth_scheme = 16; // Tight
884 } else if (includes(2, types)) {
885 this._rfb_auth_scheme = 2; // VNC Auth
886 } else {
d472f3f1 887 return this._fail("Unsupported security types (types: " + types + ")");
8db09746 888 }
b1dee947 889
6d6f0db0
SR
890 this._sock.send([this._rfb_auth_scheme]);
891 } else {
892 // Server decides
893 if (this._sock.rQwait("security scheme", 4)) { return false; }
894 this._rfb_auth_scheme = this._sock.rQshift32();
3bb15d4a
PO
895
896 if (this._rfb_auth_scheme == 0) {
897 this._rfb_init_state = "SecurityReason";
898 this._security_context = "authentication scheme";
899 this._security_status = 1;
900 return this._init_msg();
901 }
6d6f0db0 902 }
b1dee947 903
6d6f0db0
SR
904 this._rfb_init_state = 'Authentication';
905 Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
b1dee947 906
6d6f0db0 907 return this._init_msg(); // jump to authentication
0e4808bf 908 }
b1dee947 909
3bb15d4a 910 _handle_security_reason() {
d472f3f1
SM
911 if (this._sock.rQwait("reason length", 4)) {
912 return false;
913 }
2b5f94fa 914 const strlen = this._sock.rQshift32();
d472f3f1
SM
915 let reason = "";
916
917 if (strlen > 0) {
3bb15d4a 918 if (this._sock.rQwait("reason", strlen, 4)) { return false; }
d472f3f1
SM
919 reason = this._sock.rQshiftStr(strlen);
920 }
921
922 if (reason !== "") {
2b5f94fa 923 this.dispatchEvent(new CustomEvent(
d472f3f1 924 "securityfailure",
3bb15d4a
PO
925 { detail: { status: this._security_status,
926 reason: reason } }));
d472f3f1 927
3bb15d4a
PO
928 return this._fail("Security negotiation failed on " +
929 this._security_context +
d472f3f1
SM
930 " (reason: " + reason + ")");
931 } else {
2b5f94fa 932 this.dispatchEvent(new CustomEvent(
d472f3f1 933 "securityfailure",
3bb15d4a 934 { detail: { status: this._security_status } }));
d472f3f1 935
3bb15d4a
PO
936 return this._fail("Security negotiation failed on " +
937 this._security_context);
d472f3f1 938 }
0e4808bf 939 }
d472f3f1 940
6d6f0db0 941 // authentication
0e4808bf 942 _negotiate_xvp_auth() {
01d4514d
C
943 if (this._rfb_credentials.username === undefined ||
944 this._rfb_credentials.password === undefined ||
945 this._rfb_credentials.target === undefined) {
2b5f94fa
JD
946 this.dispatchEvent(new CustomEvent(
947 "credentialsrequired",
948 { detail: { types: ["username", "password", "target"] } }));
7b536961 949 return false;
6d6f0db0 950 }
0ee5ca6e 951
2b5f94fa 952 const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
430f00d6
PO
953 String.fromCharCode(this._rfb_credentials.target.length) +
954 this._rfb_credentials.username +
955 this._rfb_credentials.target;
6d6f0db0 956 this._sock.send_string(xvp_auth_str);
6d6f0db0
SR
957 this._rfb_auth_scheme = 2;
958 return this._negotiate_authentication();
0e4808bf 959 }
6d6f0db0 960
0e4808bf 961 _negotiate_std_vnc_auth() {
abfe5b7a
SM
962 if (this._sock.rQwait("auth challenge", 16)) { return false; }
963
01d4514d 964 if (this._rfb_credentials.password === undefined) {
2b5f94fa
JD
965 this.dispatchEvent(new CustomEvent(
966 "credentialsrequired",
967 { detail: { types: ["password"] } }));
6d6f0db0
SR
968 return false;
969 }
b1dee947 970
6d6f0db0 971 // TODO(directxman12): make genDES not require an Array
2b5f94fa
JD
972 const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
973 const response = RFB.genDES(this._rfb_credentials.password, challenge);
6d6f0db0
SR
974 this._sock.send(response);
975 this._rfb_init_state = "SecurityResult";
976 return true;
0e4808bf 977 }
6d6f0db0 978
1c982614
CKB
979 _negotiate_tight_unix_auth() {
980 if (this._rfb_credentials.username === undefined ||
981 this._rfb_credentials.password === undefined) {
982 this.dispatchEvent(new CustomEvent(
983 "credentialsrequired",
984 { detail: { types: ["username", "password"] } }));
985 return false;
986 }
987
988 this._sock.send([0, 0, 0, this._rfb_credentials.username.length]);
989 this._sock.send([0, 0, 0, this._rfb_credentials.password.length]);
990 this._sock.send_string(this._rfb_credentials.username);
991 this._sock.send_string(this._rfb_credentials.password);
992 this._rfb_init_state = "SecurityResult";
993 return true;
994 }
995
0e4808bf 996 _negotiate_tight_tunnels(numTunnels) {
2b5f94fa 997 const clientSupportedTunnelTypes = {
6d6f0db0
SR
998 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
999 };
2b5f94fa 1000 const serverSupportedTunnelTypes = {};
6d6f0db0 1001 // receive tunnel capabilities
2b5f94fa
JD
1002 for (let i = 0; i < numTunnels; i++) {
1003 const cap_code = this._sock.rQshift32();
1004 const cap_vendor = this._sock.rQshiftStr(4);
1005 const cap_signature = this._sock.rQshiftStr(8);
6d6f0db0
SR
1006 serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
1007 }
1008
e6bad200
PO
1009 Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
1010
8f47bd29
PO
1011 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1012 // but forgets to advertise it. Try to detect such servers by
1013 // looking for their custom tunnel type.
1014 if (serverSupportedTunnelTypes[1] &&
1015 (serverSupportedTunnelTypes[1].vendor === "SICR") &&
1016 (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
1017 Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1018 serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
1019 }
1020
6d6f0db0
SR
1021 // choose the notunnel type
1022 if (serverSupportedTunnelTypes[0]) {
1023 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
1024 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
d472f3f1 1025 return this._fail("Client's tunnel type had the incorrect " +
6d6f0db0 1026 "vendor or signature");
b1dee947 1027 }
e6bad200 1028 Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
6d6f0db0
SR
1029 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
1030 return false; // wait until we receive the sub auth count to continue
1031 } else {
d472f3f1 1032 return this._fail("Server wanted tunnels, but doesn't support " +
6d6f0db0
SR
1033 "the notunnel type");
1034 }
0e4808bf 1035 }
b1dee947 1036
0e4808bf 1037 _negotiate_tight_auth() {
6d6f0db0
SR
1038 if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
1039 if (this._sock.rQwait("num tunnels", 4)) { return false; }
2b5f94fa 1040 const numTunnels = this._sock.rQshift32();
6d6f0db0 1041 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
c00ee156 1042
6d6f0db0 1043 this._rfb_tightvnc = true;
b1dee947 1044
6d6f0db0
SR
1045 if (numTunnels > 0) {
1046 this._negotiate_tight_tunnels(numTunnels);
1047 return false; // wait until we receive the sub auth to continue
b1dee947 1048 }
6d6f0db0 1049 }
b1dee947 1050
6d6f0db0
SR
1051 // second pass, do the sub-auth negotiation
1052 if (this._sock.rQwait("sub auth count", 4)) { return false; }
2b5f94fa 1053 const subAuthCount = this._sock.rQshift32();
6d6f0db0
SR
1054 if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
1055 this._rfb_init_state = 'SecurityResult';
1056 return true;
1057 }
b1dee947 1058
6d6f0db0 1059 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
b1dee947 1060
2b5f94fa 1061 const clientSupportedTypes = {
6d6f0db0 1062 'STDVNOAUTH__': 1,
1c982614
CKB
1063 'STDVVNCAUTH_': 2,
1064 'TGHTULGNAUTH': 129
6d6f0db0 1065 };
b1dee947 1066
2b5f94fa 1067 const serverSupportedTypes = [];
95eb681b 1068
2b5f94fa 1069 for (let i = 0; i < subAuthCount; i++) {
8727f598 1070 this._sock.rQshift32(); // capNum
2b5f94fa 1071 const capabilities = this._sock.rQshiftStr(12);
6d6f0db0
SR
1072 serverSupportedTypes.push(capabilities);
1073 }
d86cc2d9 1074
e6bad200
PO
1075 Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
1076
2b5f94fa 1077 for (let authType in clientSupportedTypes) {
6d6f0db0
SR
1078 if (serverSupportedTypes.indexOf(authType) != -1) {
1079 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
e6bad200 1080 Log.Debug("Selected authentication type: " + authType);
d86cc2d9 1081
6d6f0db0
SR
1082 switch (authType) {
1083 case 'STDVNOAUTH__': // no auth
1084 this._rfb_init_state = 'SecurityResult';
1085 return true;
1086 case 'STDVVNCAUTH_': // VNC auth
1087 this._rfb_auth_scheme = 2;
1088 return this._init_msg();
1c982614
CKB
1089 case 'TGHTULGNAUTH': // UNIX auth
1090 this._rfb_auth_scheme = 129;
1091 return this._init_msg();
6d6f0db0 1092 default:
d472f3f1
SM
1093 return this._fail("Unsupported tiny auth scheme " +
1094 "(scheme: " + authType + ")");
b1dee947
SR
1095 }
1096 }
6d6f0db0 1097 }
b1dee947 1098
d472f3f1 1099 return this._fail("No supported sub-auth types!");
0e4808bf 1100 }
6d6f0db0 1101
0e4808bf 1102 _negotiate_authentication() {
6d6f0db0 1103 switch (this._rfb_auth_scheme) {
6d6f0db0
SR
1104 case 1: // no auth
1105 if (this._rfb_version >= 3.8) {
1106 this._rfb_init_state = 'SecurityResult';
1107 return true;
1108 }
1109 this._rfb_init_state = 'ClientInitialisation';
1110 return this._init_msg();
95eb681b 1111
6d6f0db0
SR
1112 case 22: // XVP auth
1113 return this._negotiate_xvp_auth();
d86cc2d9 1114
6d6f0db0
SR
1115 case 2: // VNC authentication
1116 return this._negotiate_std_vnc_auth();
d86cc2d9 1117
6d6f0db0
SR
1118 case 16: // TightVNC Security Type
1119 return this._negotiate_tight_auth();
d86cc2d9 1120
1c982614
CKB
1121 case 129: // TightVNC UNIX Security Type
1122 return this._negotiate_tight_unix_auth();
1123
6d6f0db0 1124 default:
d472f3f1
SM
1125 return this._fail("Unsupported auth scheme (scheme: " +
1126 this._rfb_auth_scheme + ")");
6d6f0db0 1127 }
0e4808bf 1128 }
6d6f0db0 1129
0e4808bf 1130 _handle_security_result() {
6d6f0db0 1131 if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
d472f3f1 1132
2b5f94fa 1133 const status = this._sock.rQshift32();
d472f3f1
SM
1134
1135 if (status === 0) { // OK
1136 this._rfb_init_state = 'ClientInitialisation';
1137 Log.Debug('Authentication OK');
1138 return this._init_msg();
1139 } else {
1140 if (this._rfb_version >= 3.8) {
3bb15d4a
PO
1141 this._rfb_init_state = "SecurityReason";
1142 this._security_context = "security result";
1143 this._security_status = status;
1144 return this._init_msg();
d472f3f1 1145 } else {
2b5f94fa
JD
1146 this.dispatchEvent(new CustomEvent(
1147 "securityfailure",
1148 { detail: { status: status } }));
d472f3f1
SM
1149
1150 return this._fail("Security handshake failed");
1151 }
6d6f0db0 1152 }
0e4808bf 1153 }
6d6f0db0 1154
0e4808bf 1155 _negotiate_server_init() {
6d6f0db0
SR
1156 if (this._sock.rQwait("server initialization", 24)) { return false; }
1157
1158 /* Screen size */
2b5f94fa
JD
1159 const width = this._sock.rQshift16();
1160 const height = this._sock.rQshift16();
6d6f0db0
SR
1161
1162 /* PIXEL_FORMAT */
2b5f94fa
JD
1163 const bpp = this._sock.rQshift8();
1164 const depth = this._sock.rQshift8();
1165 const big_endian = this._sock.rQshift8();
1166 const true_color = this._sock.rQshift8();
1167
1168 const red_max = this._sock.rQshift16();
1169 const green_max = this._sock.rQshift16();
1170 const blue_max = this._sock.rQshift16();
1171 const red_shift = this._sock.rQshift8();
1172 const green_shift = this._sock.rQshift8();
1173 const blue_shift = this._sock.rQshift8();
6d6f0db0
SR
1174 this._sock.rQskipBytes(3); // padding
1175
1176 // NB(directxman12): we don't want to call any callbacks or print messages until
1177 // *after* we're past the point where we could backtrack
1178
1179 /* Connection name/title */
2b5f94fa 1180 const name_length = this._sock.rQshift32();
6d6f0db0 1181 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
ff1b10ca
V
1182 let name = this._sock.rQshiftStr(name_length);
1183 try {
1184 name = decodeUTF8(name);
1185 } catch (e) {
1186 // bypass no-empty
1187 }
6d6f0db0
SR
1188
1189 if (this._rfb_tightvnc) {
1190 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1191 // In TightVNC mode, ServerInit message is extended
2b5f94fa
JD
1192 const numServerMessages = this._sock.rQshift16();
1193 const numClientMessages = this._sock.rQshift16();
1194 const numEncodings = this._sock.rQshift16();
6d6f0db0
SR
1195 this._sock.rQskipBytes(2); // padding
1196
2b5f94fa 1197 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
6d6f0db0
SR
1198 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1199
1200 // we don't actually do anything with the capability information that TIGHT sends,
1201 // so we just skip the all of this.
1202
1203 // TIGHT server message capabilities
1204 this._sock.rQskipBytes(16 * numServerMessages);
1205
1206 // TIGHT client message capabilities
1207 this._sock.rQskipBytes(16 * numClientMessages);
1208
1209 // TIGHT encoding capabilities
1210 this._sock.rQskipBytes(16 * numEncodings);
1211 }
d86cc2d9 1212
6d6f0db0
SR
1213 // NB(directxman12): these are down here so that we don't run them multiple times
1214 // if we backtrack
91d5c625 1215 Log.Info("Screen: " + width + "x" + height +
6d6f0db0
SR
1216 ", bpp: " + bpp + ", depth: " + depth +
1217 ", big_endian: " + big_endian +
1218 ", true_color: " + true_color +
1219 ", red_max: " + red_max +
1220 ", green_max: " + green_max +
1221 ", blue_max: " + blue_max +
1222 ", red_shift: " + red_shift +
1223 ", green_shift: " + green_shift +
1224 ", blue_shift: " + blue_shift);
1225
6d6f0db0 1226 // we're past the point where we could backtrack, so it's safe to call this
ce66b469 1227 this._setDesktopName(name);
91d5c625 1228 this._resize(width, height);
b1dee947 1229
747b4623
PO
1230 if (!this._viewOnly) { this._keyboard.grab(); }
1231 if (!this._viewOnly) { this._mouse.grab(); }
b1dee947 1232
5a5f5ada
PO
1233 this._fb_depth = 24;
1234
1235 if (this._fb_name === "Intel(r) AMT KVM") {
1236 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1237 this._fb_depth = 8;
1238 }
1239
1240 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
49a81837 1241 this._sendEncodings();
6d6f0db0
SR
1242 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1243
6d6f0db0
SR
1244 this._updateConnectionState('connected');
1245 return true;
0e4808bf 1246 }
6d6f0db0 1247
0e4808bf 1248 _sendEncodings() {
2b5f94fa 1249 const encs = [];
49a81837
PO
1250
1251 // In preference order
1252 encs.push(encodings.encodingCopyRect);
5a5f5ada
PO
1253 // Only supported with full depth support
1254 if (this._fb_depth == 24) {
1255 encs.push(encodings.encodingTight);
2c813a33 1256 encs.push(encodings.encodingTightPNG);
5a5f5ada
PO
1257 encs.push(encodings.encodingHextile);
1258 encs.push(encodings.encodingRRE);
1259 }
49a81837
PO
1260 encs.push(encodings.encodingRaw);
1261
1262 // Psuedo-encoding settings
49a81837
PO
1263 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1264 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1265
1266 encs.push(encodings.pseudoEncodingDesktopSize);
1267 encs.push(encodings.pseudoEncodingLastRect);
1268 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1269 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1270 encs.push(encodings.pseudoEncodingXvp);
1271 encs.push(encodings.pseudoEncodingFence);
1272 encs.push(encodings.pseudoEncodingContinuousUpdates);
ce66b469 1273 encs.push(encodings.pseudoEncodingDesktopName);
49a81837 1274
baa4f23e 1275 if (this._fb_depth == 24) {
296ba51f 1276 encs.push(encodings.pseudoEncodingVMwareCursor);
49a81837
PO
1277 encs.push(encodings.pseudoEncodingCursor);
1278 }
1279
1280 RFB.messages.clientEncodings(this._sock, encs);
0e4808bf 1281 }
49a81837 1282
6d6f0db0
SR
1283 /* RFB protocol initialization states:
1284 * ProtocolVersion
1285 * Security
1286 * Authentication
1287 * SecurityResult
1288 * ClientInitialization - not triggered by server message
1289 * ServerInitialization
1290 */
0e4808bf 1291 _init_msg() {
6d6f0db0
SR
1292 switch (this._rfb_init_state) {
1293 case 'ProtocolVersion':
1294 return this._negotiate_protocol_version();
1295
1296 case 'Security':
1297 return this._negotiate_security();
1298
1299 case 'Authentication':
1300 return this._negotiate_authentication();
1301
1302 case 'SecurityResult':
1303 return this._handle_security_result();
1304
3bb15d4a
PO
1305 case 'SecurityReason':
1306 return this._handle_security_reason();
1307
6d6f0db0
SR
1308 case 'ClientInitialisation':
1309 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
1310 this._rfb_init_state = 'ServerInitialisation';
1311 return true;
b1dee947 1312
6d6f0db0
SR
1313 case 'ServerInitialisation':
1314 return this._negotiate_server_init();
b1dee947 1315
6d6f0db0 1316 default:
d472f3f1
SM
1317 return this._fail("Unknown init state (state: " +
1318 this._rfb_init_state + ")");
6d6f0db0 1319 }
0e4808bf 1320 }
b1dee947 1321
0e4808bf 1322 _handle_set_colour_map_msg() {
6d6f0db0 1323 Log.Debug("SetColorMapEntries");
b1dee947 1324
d472f3f1 1325 return this._fail("Unexpected SetColorMapEntries message");
0e4808bf 1326 }
b1dee947 1327
0e4808bf 1328 _handle_server_cut_text() {
6d6f0db0 1329 Log.Debug("ServerCutText");
b1dee947 1330
6d6f0db0
SR
1331 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
1332 this._sock.rQskipBytes(3); // Padding
2b5f94fa 1333 const length = this._sock.rQshift32();
6d6f0db0 1334 if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
b1dee947 1335
2b5f94fa 1336 const text = this._sock.rQshiftStr(length);
b4ff032e 1337
747b4623 1338 if (this._viewOnly) { return true; }
b4ff032e 1339
2b5f94fa
JD
1340 this.dispatchEvent(new CustomEvent(
1341 "clipboard",
1342 { detail: { text: text } }));
b1dee947 1343
6d6f0db0 1344 return true;
0e4808bf 1345 }
b1dee947 1346
0e4808bf 1347 _handle_server_fence_msg() {
6d6f0db0
SR
1348 if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
1349 this._sock.rQskipBytes(3); // Padding
2b5f94fa
JD
1350 let flags = this._sock.rQshift32();
1351 let length = this._sock.rQshift8();
b1dee947 1352
6d6f0db0 1353 if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
b1dee947 1354
6d6f0db0
SR
1355 if (length > 64) {
1356 Log.Warn("Bad payload length (" + length + ") in fence response");
1357 length = 64;
1358 }
4e0c36dd 1359
2b5f94fa 1360 const payload = this._sock.rQshiftStr(length);
b1dee947 1361
6d6f0db0 1362 this._supportsFence = true;
b1dee947 1363
6d6f0db0
SR
1364 /*
1365 * Fence flags
1366 *
1367 * (1<<0) - BlockBefore
1368 * (1<<1) - BlockAfter
1369 * (1<<2) - SyncNext
1370 * (1<<31) - Request
1371 */
b1dee947 1372
6d6f0db0 1373 if (!(flags & (1<<31))) {
d472f3f1 1374 return this._fail("Unexpected fence response");
6d6f0db0 1375 }
b1dee947 1376
6d6f0db0
SR
1377 // Filter out unsupported flags
1378 // FIXME: support syncNext
1379 flags &= (1<<0) | (1<<1);
b1dee947 1380
6d6f0db0
SR
1381 // BlockBefore and BlockAfter are automatically handled by
1382 // the fact that we process each incoming message
1383 // synchronuosly.
1384 RFB.messages.clientFence(this._sock, flags, payload);
f78a652e 1385
6d6f0db0 1386 return true;
0e4808bf 1387 }
b1dee947 1388
0e4808bf 1389 _handle_xvp_msg() {
6d6f0db0 1390 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
8a189a62 1391 this._sock.rQskipBytes(1); // Padding
2b5f94fa
JD
1392 const xvp_ver = this._sock.rQshift8();
1393 const xvp_msg = this._sock.rQshift8();
b1dee947 1394
6d6f0db0
SR
1395 switch (xvp_msg) {
1396 case 0: // XVP_FAIL
5b20d338 1397 Log.Error("XVP Operation Failed");
6d6f0db0
SR
1398 break;
1399 case 1: // XVP_INIT
1400 this._rfb_xvp_ver = xvp_ver;
1401 Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
832be262 1402 this._setCapability("power", true);
6d6f0db0
SR
1403 break;
1404 default:
d472f3f1 1405 this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
6d6f0db0
SR
1406 break;
1407 }
b1dee947 1408
6d6f0db0 1409 return true;
0e4808bf 1410 }
3df13262 1411
0e4808bf 1412 _normal_msg() {
2b5f94fa 1413 let msg_type;
6d6f0db0
SR
1414 if (this._FBU.rects > 0) {
1415 msg_type = 0;
1416 } else {
1417 msg_type = this._sock.rQshift8();
1418 }
76a86ff5 1419
2b5f94fa 1420 let first, ret;
6d6f0db0
SR
1421 switch (msg_type) {
1422 case 0: // FramebufferUpdate
2b5f94fa 1423 ret = this._framebufferUpdate();
6d6f0db0
SR
1424 if (ret && !this._enabledContinuousUpdates) {
1425 RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
1426 this._fb_width, this._fb_height);
1427 }
1428 return ret;
3df13262 1429
6d6f0db0
SR
1430 case 1: // SetColorMapEntries
1431 return this._handle_set_colour_map_msg();
1432
1433 case 2: // Bell
1434 Log.Debug("Bell");
2b5f94fa 1435 this.dispatchEvent(new CustomEvent(
4a16dc51 1436 "bell",
2b5f94fa 1437 { detail: {} }));
6d6f0db0
SR
1438 return true;
1439
1440 case 3: // ServerCutText
1441 return this._handle_server_cut_text();
1442
1443 case 150: // EndOfContinuousUpdates
2b5f94fa 1444 first = !this._supportsContinuousUpdates;
6d6f0db0
SR
1445 this._supportsContinuousUpdates = true;
1446 this._enabledContinuousUpdates = false;
1447 if (first) {
1448 this._enabledContinuousUpdates = true;
1449 this._updateContinuousUpdates();
1450 Log.Info("Enabling continuous updates.");
1451 } else {
1452 // FIXME: We need to send a framebufferupdaterequest here
1453 // if we add support for turning off continuous updates
1454 }
1455 return true;
3df13262 1456
6d6f0db0
SR
1457 case 248: // ServerFence
1458 return this._handle_server_fence_msg();
3df13262 1459
6d6f0db0
SR
1460 case 250: // XVP
1461 return this._handle_xvp_msg();
3df13262 1462
6d6f0db0 1463 default:
d472f3f1 1464 this._fail("Unexpected server message (type " + msg_type + ")");
6d6f0db0
SR
1465 Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
1466 return true;
1467 }
0e4808bf 1468 }
3df13262 1469
0e4808bf 1470 _onFlush() {
6d6f0db0
SR
1471 this._flushing = false;
1472 // Resume processing
8a189a62 1473 if (this._sock.rQlen > 0) {
6d6f0db0
SR
1474 this._handle_message();
1475 }
0e4808bf 1476 }
3df13262 1477
0e4808bf 1478 _framebufferUpdate() {
6d6f0db0
SR
1479 if (this._FBU.rects === 0) {
1480 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
8a189a62 1481 this._sock.rQskipBytes(1); // Padding
6d6f0db0 1482 this._FBU.rects = this._sock.rQshift16();
b1dee947 1483
6d6f0db0
SR
1484 // Make sure the previous frame is fully rendered first
1485 // to avoid building up an excessive queue
1486 if (this._display.pending()) {
1487 this._flushing = true;
1488 this._display.flush();
1489 return false;
1490 }
1491 }
b1dee947 1492
6d6f0db0 1493 while (this._FBU.rects > 0) {
923cd220 1494 if (this._FBU.encoding === null) {
6d6f0db0
SR
1495 if (this._sock.rQwait("rect header", 12)) { return false; }
1496 /* New FramebufferUpdate */
b1dee947 1497
2b5f94fa 1498 const hdr = this._sock.rQshiftBytes(12);
6d6f0db0
SR
1499 this._FBU.x = (hdr[0] << 8) + hdr[1];
1500 this._FBU.y = (hdr[2] << 8) + hdr[3];
1501 this._FBU.width = (hdr[4] << 8) + hdr[5];
1502 this._FBU.height = (hdr[6] << 8) + hdr[7];
1503 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
1504 (hdr[10] << 8) + hdr[11], 10);
6d6f0db0 1505 }
b1dee947 1506
11309f32
PO
1507 if (!this._handleRect()) {
1508 return false;
6d6f0db0 1509 }
b1dee947 1510
11309f32 1511 this._FBU.rects--;
923cd220 1512 this._FBU.encoding = null;
6d6f0db0 1513 }
76a86ff5 1514
6d6f0db0 1515 this._display.flip();
3df13262 1516
6d6f0db0 1517 return true; // We finished this FBU
0e4808bf 1518 }
b1dee947 1519
11309f32
PO
1520 _handleRect() {
1521 switch (this._FBU.encoding) {
1522 case encodings.pseudoEncodingLastRect:
1523 this._FBU.rects = 1; // Will be decreased when we return
1524 return true;
b1dee947 1525
296ba51f
NL
1526 case encodings.pseudoEncodingVMwareCursor:
1527 return this._handleVMwareCursor();
1528
11309f32
PO
1529 case encodings.pseudoEncodingCursor:
1530 return this._handleCursor();
d9ca5e5b 1531
11309f32
PO
1532 case encodings.pseudoEncodingQEMUExtendedKeyEvent:
1533 // Old Safari doesn't support creating keyboard events
1534 try {
1535 const keyboardEvent = document.createEvent("keyboardEvent");
1536 if (keyboardEvent.code !== undefined) {
1537 this._qemuExtKeyEventSupported = true;
1538 }
1539 } catch (err) {
1540 // Do nothing
d9ca5e5b 1541 }
11309f32
PO
1542 return true;
1543
ce66b469
NL
1544 case encodings.pseudoEncodingDesktopName:
1545 return this._handleDesktopName();
1546
11309f32
PO
1547 case encodings.pseudoEncodingDesktopSize:
1548 this._resize(this._FBU.width, this._FBU.height);
1549 return true;
1550
1551 case encodings.pseudoEncodingExtendedDesktopSize:
1552 return this._handleExtendedDesktopSize();
1553
1554 default:
1555 return this._handleDataRect();
1556 }
1557 }
1558
296ba51f
NL
1559 _handleVMwareCursor() {
1560 const hotx = this._FBU.x; // hotspot-x
1561 const hoty = this._FBU.y; // hotspot-y
1562 const w = this._FBU.width;
1563 const h = this._FBU.height;
1564 if (this._sock.rQwait("VMware cursor encoding", 1)) {
1565 return false;
1566 }
1567
1568 const cursor_type = this._sock.rQshift8();
1569
1570 this._sock.rQshift8(); //Padding
1571
1572 let rgba;
1573 const bytesPerPixel = 4;
1574
1575 //Classic cursor
1576 if (cursor_type == 0) {
1577 //Used to filter away unimportant bits.
1578 //OR is used for correct conversion in js.
1579 const PIXEL_MASK = 0xffffff00 | 0;
1580 rgba = new Array(w * h * bytesPerPixel);
1581
1582 if (this._sock.rQwait("VMware cursor classic encoding",
1583 (w * h * bytesPerPixel) * 2, 2)) {
1584 return false;
1585 }
1586
1587 let and_mask = new Array(w * h);
1588 for (let pixel = 0; pixel < (w * h); pixel++) {
1589 and_mask[pixel] = this._sock.rQshift32();
1590 }
1591
1592 let xor_mask = new Array(w * h);
1593 for (let pixel = 0; pixel < (w * h); pixel++) {
1594 xor_mask[pixel] = this._sock.rQshift32();
1595 }
1596
1597 for (let pixel = 0; pixel < (w * h); pixel++) {
1598 if (and_mask[pixel] == 0) {
1599 //Fully opaque pixel
1600 let bgr = xor_mask[pixel];
1601 let r = bgr >> 8 & 0xff;
1602 let g = bgr >> 16 & 0xff;
1603 let b = bgr >> 24 & 0xff;
1604
1605 rgba[(pixel * bytesPerPixel) ] = r; //r
1606 rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
1607 rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
1608 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
1609
1610 } else if ((and_mask[pixel] & PIXEL_MASK) ==
1611 PIXEL_MASK) {
1612 //Only screen value matters, no mouse colouring
1613 if (xor_mask[pixel] == 0) {
1614 //Transparent pixel
1615 rgba[(pixel * bytesPerPixel) ] = 0x00;
1616 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1617 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1618 rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
1619
1620 } else if ((xor_mask[pixel] & PIXEL_MASK) ==
1621 PIXEL_MASK) {
1622 //Inverted pixel, not supported in browsers.
1623 //Fully opaque instead.
1624 rgba[(pixel * bytesPerPixel) ] = 0x00;
1625 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1626 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1627 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1628
1629 } else {
1630 //Unhandled xor_mask
1631 rgba[(pixel * bytesPerPixel) ] = 0x00;
1632 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1633 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1634 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1635 }
1636
1637 } else {
1638 //Unhandled and_mask
1639 rgba[(pixel * bytesPerPixel) ] = 0x00;
1640 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
1641 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
1642 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
1643 }
1644 }
1645
1646 //Alpha cursor.
1647 } else if (cursor_type == 1) {
1648 if (this._sock.rQwait("VMware cursor alpha encoding",
1649 (w * h * 4), 2)) {
1650 return false;
1651 }
1652
1653 rgba = new Array(w * h * bytesPerPixel);
1654
1655 for (let pixel = 0; pixel < (w * h); pixel++) {
1656 let data = this._sock.rQshift32();
1657
1658 rgba[(pixel * 4) ] = data >> 8 & 0xff; //r
1659 rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
1660 rgba[(pixel * 4) + 2 ] = data >> 24 & 0xff; //b
1661 rgba[(pixel * 4) + 3 ] = data & 0xff; //a
1662 }
1663
1664 } else {
1665 Log.Warn("The given cursor type is not supported: "
1666 + cursor_type + " given.");
1667 return false;
1668 }
1669
1670 this._updateCursor(rgba, hotx, hoty, w, h);
1671
1672 return true;
1673 }
1674
11309f32 1675 _handleCursor() {
679535ec
PO
1676 const hotx = this._FBU.x; // hotspot-x
1677 const hoty = this._FBU.y; // hotspot-y
11309f32
PO
1678 const w = this._FBU.width;
1679 const h = this._FBU.height;
1680
1681 const pixelslength = w * h * 4;
679535ec 1682 const masklength = Math.ceil(w / 8) * h;
11309f32 1683
923cd220
PO
1684 let bytes = pixelslength + masklength;
1685 if (this._sock.rQwait("cursor encoding", bytes)) {
11309f32
PO
1686 return false;
1687 }
1688
679535ec
PO
1689 // Decode from BGRX pixels + bit mask to RGBA
1690 const pixels = this._sock.rQshiftBytes(pixelslength);
1691 const mask = this._sock.rQshiftBytes(masklength);
1692 let rgba = new Uint8Array(w * h * 4);
1693
1694 let pix_idx = 0;
1695 for (let y = 0; y < h; y++) {
1696 for (let x = 0; x < w; x++) {
1697 let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
1698 let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0;
1699 rgba[pix_idx ] = pixels[pix_idx + 2];
1700 rgba[pix_idx + 1] = pixels[pix_idx + 1];
1701 rgba[pix_idx + 2] = pixels[pix_idx];
1702 rgba[pix_idx + 3] = alpha;
1703 pix_idx += 4;
1704 }
1705 }
1706
1707 this._updateCursor(rgba, hotx, hoty, w, h);
11309f32 1708
11309f32
PO
1709 return true;
1710 }
1711
ce66b469
NL
1712 _handleDesktopName() {
1713 if (this._sock.rQwait("DesktopName", 4)) {
1714 return false;
1715 }
1716
1717 let length = this._sock.rQshift32();
1718
1719 if (this._sock.rQwait("DesktopName", length, 4)) {
1720 return false;
1721 }
1722
1723 let name = this._sock.rQshiftStr(length);
ff1b10ca
V
1724 try {
1725 name = decodeUTF8(name);
1726 } catch (e) {
1727 // bypass no-empty
1728 }
ce66b469
NL
1729
1730 this._setDesktopName(name);
1731
1732 return true;
1733 }
1734
11309f32 1735 _handleExtendedDesktopSize() {
923cd220 1736 if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
11309f32
PO
1737 return false;
1738 }
1739
1740 const number_of_screens = this._sock.rQpeek8();
1741
923cd220
PO
1742 let bytes = 4 + (number_of_screens * 16);
1743 if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
11309f32
PO
1744 return false;
1745 }
1746
1747 const firstUpdate = !this._supportsSetDesktopSize;
1748 this._supportsSetDesktopSize = true;
1749
1750 // Normally we only apply the current resize mode after a
1751 // window resize event. However there is no such trigger on the
1752 // initial connect. And we don't know if the server supports
1753 // resizing until we've gotten here.
1754 if (firstUpdate) {
1755 this._requestRemoteResize();
1756 }
1757
1758 this._sock.rQskipBytes(1); // number-of-screens
1759 this._sock.rQskipBytes(3); // padding
1760
1761 for (let i = 0; i < number_of_screens; i += 1) {
1762 // Save the id and flags of the first screen
1763 if (i === 0) {
1764 this._screen_id = this._sock.rQshiftBytes(4); // id
1765 this._sock.rQskipBytes(2); // x-position
1766 this._sock.rQskipBytes(2); // y-position
1767 this._sock.rQskipBytes(2); // width
1768 this._sock.rQskipBytes(2); // height
1769 this._screen_flags = this._sock.rQshiftBytes(4); // flags
1770 } else {
1771 this._sock.rQskipBytes(16);
b1dee947 1772 }
11309f32 1773 }
b1dee947 1774
11309f32
PO
1775 /*
1776 * The x-position indicates the reason for the change:
1777 *
1778 * 0 - server resized on its own
1779 * 1 - this client requested the resize
1780 * 2 - another client requested the resize
1781 */
1782
1783 // We need to handle errors when we requested the resize.
1784 if (this._FBU.x === 1 && this._FBU.y !== 0) {
1785 let msg = "";
1786 // The y-position indicates the status code from the server
1787 switch (this._FBU.y) {
9881899e
PO
1788 case 1:
1789 msg = "Resize is administratively prohibited";
1790 break;
1791 case 2:
1792 msg = "Out of resources";
1793 break;
1794 case 3:
1795 msg = "Invalid screen layout";
1796 break;
1797 default:
1798 msg = "Unknown reason";
1799 break;
11309f32
PO
1800 }
1801 Log.Warn("Server did not accept the resize request: "
1802 + msg);
1803 } else {
1804 this._resize(this._FBU.width, this._FBU.height);
6d6f0db0 1805 }
b1dee947 1806
11309f32
PO
1807 return true;
1808 }
b1dee947 1809
11309f32 1810 _handleDataRect() {
923cd220
PO
1811 let decoder = this._decoders[this._FBU.encoding];
1812 if (!decoder) {
11309f32
PO
1813 this._fail("Unsupported encoding (encoding: " +
1814 this._FBU.encoding + ")");
1815 return false;
1816 }
1817
923cd220
PO
1818 try {
1819 return decoder.decodeRect(this._FBU.x, this._FBU.y,
1820 this._FBU.width, this._FBU.height,
1821 this._sock, this._display,
1822 this._fb_depth);
1823 } catch (err) {
1824 this._fail("Error decoding rect: " + err);
1825 return false;
1826 }
0e4808bf 1827 }
b1dee947 1828
0e4808bf 1829 _updateContinuousUpdates() {
6d6f0db0 1830 if (!this._enabledContinuousUpdates) { return; }
b1dee947 1831
6d6f0db0
SR
1832 RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
1833 this._fb_width, this._fb_height);
0e4808bf 1834 }
91d5c625 1835
0e4808bf 1836 _resize(width, height) {
91d5c625
PO
1837 this._fb_width = width;
1838 this._fb_height = height;
1839
91d5c625 1840 this._display.resize(this._fb_width, this._fb_height);
e89eef94 1841
9b84f516
PO
1842 // Adjust the visible viewport based on the new dimensions
1843 this._updateClip();
1844 this._updateScale();
91d5c625 1845
91d5c625 1846 this._updateContinuousUpdates();
0e4808bf 1847 }
8db09746 1848
0e4808bf 1849 _xvpOp(ver, op) {
cd523e8f
PO
1850 if (this._rfb_xvp_ver < ver) { return; }
1851 Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
1852 RFB.messages.xvpOp(this._sock, ver, op);
0e4808bf 1853 }
76a86ff5 1854
d1314d4b
AP
1855 _updateCursor(rgba, hotx, hoty, w, h) {
1856 this._cursorImage = {
1857 rgbaPixels: rgba,
1858 hotx: hotx, hoty: hoty, w: w, h: h,
1859 };
1860 this._refreshCursor();
1861 }
1862
4c38179d
AP
1863 _shouldShowDotCursor() {
1864 // Called when this._cursorImage is updated
1865 if (!this._showDotCursor) {
1866 // User does not want to see the dot, so...
1867 return false;
1868 }
1869
1870 // The dot should not be shown if the cursor is already visible,
1871 // i.e. contains at least one not-fully-transparent pixel.
1872 // So iterate through all alpha bytes in rgba and stop at the
1873 // first non-zero.
1874 for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
1875 if (this._cursorImage.rgbaPixels[i]) {
1876 return false;
1877 }
1878 }
1879
1880 // At this point, we know that the cursor is fully transparent, and
1881 // the user wants to see the dot instead of this.
1882 return true;
1883 }
1884
d1314d4b 1885 _refreshCursor() {
1f2bb528 1886 if (this._rfb_connection_state !== 'connected') { return; }
4c38179d
AP
1887 const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
1888 this._cursor.change(image.rgbaPixels,
1889 image.hotx, image.hoty,
1890 image.w, image.h
d1314d4b
AP
1891 );
1892 }
1893
0e4808bf 1894 static genDES(password, challenge) {
0505214c
JD
1895 const passwordChars = password.split('').map(c => c.charCodeAt(0));
1896 return (new DES(passwordChars)).encrypt(challenge);
0e4808bf
JD
1897 }
1898}
76a86ff5 1899
6d6f0db0
SR
1900// Class Methods
1901RFB.messages = {
0e4808bf 1902 keyEvent(sock, keysym, down) {
2b5f94fa
JD
1903 const buff = sock._sQ;
1904 const offset = sock._sQlen;
8db09746 1905
6d6f0db0
SR
1906 buff[offset] = 4; // msg-type
1907 buff[offset + 1] = down;
b1dee947 1908
6d6f0db0
SR
1909 buff[offset + 2] = 0;
1910 buff[offset + 3] = 0;
fb49f91b 1911
6d6f0db0
SR
1912 buff[offset + 4] = (keysym >> 24);
1913 buff[offset + 5] = (keysym >> 16);
1914 buff[offset + 6] = (keysym >> 8);
1915 buff[offset + 7] = keysym;
b1dee947 1916
6d6f0db0
SR
1917 sock._sQlen += 8;
1918 sock.flush();
1919 },
ef1e8bab 1920
0e4808bf 1921 QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
6d6f0db0 1922 function getRFBkeycode(xt_scancode) {
2b5f94fa
JD
1923 const upperByte = (keycode >> 8);
1924 const lowerByte = (keycode & 0x00ff);
6d6f0db0 1925 if (upperByte === 0xe0 && lowerByte < 0x7f) {
2b5f94fa 1926 return lowerByte | 0x80;
ef1e8bab 1927 }
6d6f0db0 1928 return xt_scancode;
ef1e8bab 1929 }
b1dee947 1930
2b5f94fa
JD
1931 const buff = sock._sQ;
1932 const offset = sock._sQlen;
8f06673a 1933
6d6f0db0
SR
1934 buff[offset] = 255; // msg-type
1935 buff[offset + 1] = 0; // sub msg-type
8f06673a 1936
6d6f0db0
SR
1937 buff[offset + 2] = (down >> 8);
1938 buff[offset + 3] = down;
8f06673a 1939
6d6f0db0
SR
1940 buff[offset + 4] = (keysym >> 24);
1941 buff[offset + 5] = (keysym >> 16);
1942 buff[offset + 6] = (keysym >> 8);
1943 buff[offset + 7] = keysym;
8f06673a 1944
2b5f94fa 1945 const RFBkeycode = getRFBkeycode(keycode);
8f06673a 1946
6d6f0db0
SR
1947 buff[offset + 8] = (RFBkeycode >> 24);
1948 buff[offset + 9] = (RFBkeycode >> 16);
1949 buff[offset + 10] = (RFBkeycode >> 8);
1950 buff[offset + 11] = RFBkeycode;
8f06673a 1951
6d6f0db0
SR
1952 sock._sQlen += 12;
1953 sock.flush();
1954 },
8f06673a 1955
0e4808bf 1956 pointerEvent(sock, x, y, mask) {
2b5f94fa
JD
1957 const buff = sock._sQ;
1958 const offset = sock._sQlen;
8f06673a 1959
6d6f0db0 1960 buff[offset] = 5; // msg-type
9ff86fb7 1961
6d6f0db0 1962 buff[offset + 1] = mask;
9ff86fb7 1963
6d6f0db0
SR
1964 buff[offset + 2] = x >> 8;
1965 buff[offset + 3] = x;
9ff86fb7 1966
6d6f0db0
SR
1967 buff[offset + 4] = y >> 8;
1968 buff[offset + 5] = y;
9ff86fb7 1969
6d6f0db0
SR
1970 sock._sQlen += 6;
1971 sock.flush();
1972 },
9ff86fb7 1973
6d6f0db0 1974 // TODO(directxman12): make this unicode compatible?
0e4808bf 1975 clientCutText(sock, text) {
2b5f94fa
JD
1976 const buff = sock._sQ;
1977 const offset = sock._sQlen;
b1dee947 1978
6d6f0db0 1979 buff[offset] = 6; // msg-type
9ff86fb7 1980
6d6f0db0
SR
1981 buff[offset + 1] = 0; // padding
1982 buff[offset + 2] = 0; // padding
1983 buff[offset + 3] = 0; // padding
9ff86fb7 1984
0e4808bf 1985 let length = text.length;
9ff86fb7 1986
2bb8b28d
SM
1987 buff[offset + 4] = length >> 24;
1988 buff[offset + 5] = length >> 16;
1989 buff[offset + 6] = length >> 8;
1990 buff[offset + 7] = length;
9ff86fb7 1991
2bb8b28d 1992 sock._sQlen += 8;
9ff86fb7 1993
2bb8b28d
SM
1994 // We have to keep track of from where in the text we begin creating the
1995 // buffer for the flush in the next iteration.
1996 let textOffset = 0;
1997
1998 let remaining = length;
1999 while (remaining > 0) {
2000
0e4808bf 2001 let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
2bb8b28d 2002 for (let i = 0; i < flushSize; i++) {
2b5f94fa 2003 buff[sock._sQlen + i] = text.charCodeAt(textOffset + i);
2bb8b28d
SM
2004 }
2005
2006 sock._sQlen += flushSize;
2007 sock.flush();
2008
2009 remaining -= flushSize;
2010 textOffset += flushSize;
2011 }
6d6f0db0
SR
2012 },
2013
0e4808bf 2014 setDesktopSize(sock, width, height, id, flags) {
2b5f94fa
JD
2015 const buff = sock._sQ;
2016 const offset = sock._sQlen;
6d6f0db0
SR
2017
2018 buff[offset] = 251; // msg-type
2019 buff[offset + 1] = 0; // padding
2020 buff[offset + 2] = width >> 8; // width
2021 buff[offset + 3] = width;
2022 buff[offset + 4] = height >> 8; // height
2023 buff[offset + 5] = height;
2024
2025 buff[offset + 6] = 1; // number-of-screens
2026 buff[offset + 7] = 0; // padding
2027
2028 // screen array
2029 buff[offset + 8] = id >> 24; // id
2030 buff[offset + 9] = id >> 16;
2031 buff[offset + 10] = id >> 8;
2032 buff[offset + 11] = id;
2033 buff[offset + 12] = 0; // x-position
2034 buff[offset + 13] = 0;
2035 buff[offset + 14] = 0; // y-position
2036 buff[offset + 15] = 0;
2037 buff[offset + 16] = width >> 8; // width
2038 buff[offset + 17] = width;
2039 buff[offset + 18] = height >> 8; // height
2040 buff[offset + 19] = height;
2041 buff[offset + 20] = flags >> 24; // flags
2042 buff[offset + 21] = flags >> 16;
2043 buff[offset + 22] = flags >> 8;
2044 buff[offset + 23] = flags;
2045
2046 sock._sQlen += 24;
2047 sock.flush();
2048 },
2049
0e4808bf 2050 clientFence(sock, flags, payload) {
2b5f94fa
JD
2051 const buff = sock._sQ;
2052 const offset = sock._sQlen;
6d6f0db0
SR
2053
2054 buff[offset] = 248; // msg-type
2055
2056 buff[offset + 1] = 0; // padding
2057 buff[offset + 2] = 0; // padding
2058 buff[offset + 3] = 0; // padding
2059
2060 buff[offset + 4] = flags >> 24; // flags
2061 buff[offset + 5] = flags >> 16;
2062 buff[offset + 6] = flags >> 8;
2063 buff[offset + 7] = flags;
2064
2b5f94fa 2065 const n = payload.length;
6d6f0db0
SR
2066
2067 buff[offset + 8] = n; // length
2068
2b5f94fa 2069 for (let i = 0; i < n; i++) {
6d6f0db0
SR
2070 buff[offset + 9 + i] = payload.charCodeAt(i);
2071 }
a7a89626 2072
6d6f0db0
SR
2073 sock._sQlen += 9 + n;
2074 sock.flush();
2075 },
3df13262 2076
0e4808bf 2077 enableContinuousUpdates(sock, enable, x, y, width, height) {
2b5f94fa
JD
2078 const buff = sock._sQ;
2079 const offset = sock._sQlen;
3df13262 2080
6d6f0db0
SR
2081 buff[offset] = 150; // msg-type
2082 buff[offset + 1] = enable; // enable-flag
76a86ff5 2083
6d6f0db0
SR
2084 buff[offset + 2] = x >> 8; // x
2085 buff[offset + 3] = x;
2086 buff[offset + 4] = y >> 8; // y
2087 buff[offset + 5] = y;
2088 buff[offset + 6] = width >> 8; // width
2089 buff[offset + 7] = width;
2090 buff[offset + 8] = height >> 8; // height
2091 buff[offset + 9] = height;
76a86ff5 2092
6d6f0db0
SR
2093 sock._sQlen += 10;
2094 sock.flush();
2095 },
76a86ff5 2096
0e4808bf 2097 pixelFormat(sock, depth, true_color) {
2b5f94fa
JD
2098 const buff = sock._sQ;
2099 const offset = sock._sQlen;
ae116051 2100
2b5f94fa 2101 let bpp;
5a5f5ada
PO
2102
2103 if (depth > 16) {
2104 bpp = 32;
2105 } else if (depth > 8) {
2106 bpp = 16;
2107 } else {
2108 bpp = 8;
2109 }
2110
2b5f94fa 2111 const bits = Math.floor(depth/3);
5a5f5ada 2112
6d6f0db0 2113 buff[offset] = 0; // msg-type
9ff86fb7 2114
6d6f0db0
SR
2115 buff[offset + 1] = 0; // padding
2116 buff[offset + 2] = 0; // padding
2117 buff[offset + 3] = 0; // padding
9ff86fb7 2118
5a5f5ada
PO
2119 buff[offset + 4] = bpp; // bits-per-pixel
2120 buff[offset + 5] = depth; // depth
6d6f0db0
SR
2121 buff[offset + 6] = 0; // little-endian
2122 buff[offset + 7] = true_color ? 1 : 0; // true-color
9ff86fb7 2123
6d6f0db0 2124 buff[offset + 8] = 0; // red-max
5a5f5ada 2125 buff[offset + 9] = (1 << bits) - 1; // red-max
9ff86fb7 2126
6d6f0db0 2127 buff[offset + 10] = 0; // green-max
5a5f5ada 2128 buff[offset + 11] = (1 << bits) - 1; // green-max
9ff86fb7 2129
6d6f0db0 2130 buff[offset + 12] = 0; // blue-max
5a5f5ada 2131 buff[offset + 13] = (1 << bits) - 1; // blue-max
9ff86fb7 2132
5a5f5ada
PO
2133 buff[offset + 14] = bits * 2; // red-shift
2134 buff[offset + 15] = bits * 1; // green-shift
2135 buff[offset + 16] = bits * 0; // blue-shift
9ff86fb7 2136
6d6f0db0
SR
2137 buff[offset + 17] = 0; // padding
2138 buff[offset + 18] = 0; // padding
2139 buff[offset + 19] = 0; // padding
9ff86fb7 2140
6d6f0db0
SR
2141 sock._sQlen += 20;
2142 sock.flush();
2143 },
9ff86fb7 2144
0e4808bf 2145 clientEncodings(sock, encodings) {
2b5f94fa
JD
2146 const buff = sock._sQ;
2147 const offset = sock._sQlen;
b1dee947 2148
6d6f0db0
SR
2149 buff[offset] = 2; // msg-type
2150 buff[offset + 1] = 0; // padding
b1dee947 2151
49a81837
PO
2152 buff[offset + 2] = encodings.length >> 8;
2153 buff[offset + 3] = encodings.length;
9ff86fb7 2154
2b5f94fa
JD
2155 let j = offset + 4;
2156 for (let i = 0; i < encodings.length; i++) {
2157 const enc = encodings[i];
49a81837
PO
2158 buff[j] = enc >> 24;
2159 buff[j + 1] = enc >> 16;
2160 buff[j + 2] = enc >> 8;
2161 buff[j + 3] = enc;
a7a89626 2162
49a81837
PO
2163 j += 4;
2164 }
a7a89626 2165
6d6f0db0
SR
2166 sock._sQlen += j - offset;
2167 sock.flush();
2168 },
a679a97d 2169
0e4808bf 2170 fbUpdateRequest(sock, incremental, x, y, w, h) {
2b5f94fa
JD
2171 const buff = sock._sQ;
2172 const offset = sock._sQlen;
9ff86fb7 2173
6d6f0db0
SR
2174 if (typeof(x) === "undefined") { x = 0; }
2175 if (typeof(y) === "undefined") { y = 0; }
b1dee947 2176
6d6f0db0
SR
2177 buff[offset] = 3; // msg-type
2178 buff[offset + 1] = incremental ? 1 : 0;
9ff86fb7 2179
6d6f0db0
SR
2180 buff[offset + 2] = (x >> 8) & 0xFF;
2181 buff[offset + 3] = x & 0xFF;
9ff86fb7 2182
6d6f0db0
SR
2183 buff[offset + 4] = (y >> 8) & 0xFF;
2184 buff[offset + 5] = y & 0xFF;
9ff86fb7 2185
6d6f0db0
SR
2186 buff[offset + 6] = (w >> 8) & 0xFF;
2187 buff[offset + 7] = w & 0xFF;
9ff86fb7 2188
6d6f0db0
SR
2189 buff[offset + 8] = (h >> 8) & 0xFF;
2190 buff[offset + 9] = h & 0xFF;
b1dee947 2191
6d6f0db0
SR
2192 sock._sQlen += 10;
2193 sock.flush();
85b35fc0
PO
2194 },
2195
0e4808bf 2196 xvpOp(sock, ver, op) {
2b5f94fa
JD
2197 const buff = sock._sQ;
2198 const offset = sock._sQlen;
85b35fc0
PO
2199
2200 buff[offset] = 250; // msg-type
2201 buff[offset + 1] = 0; // padding
2202
2203 buff[offset + 2] = ver;
2204 buff[offset + 3] = op;
2205
2206 sock._sQlen += 4;
2207 sock.flush();
6d6f0db0 2208 }
6d6f0db0 2209};
b1dee947 2210
4c38179d
AP
2211RFB.cursors = {
2212 none: {
2213 rgbaPixels: new Uint8Array(),
2214 w: 0, h: 0,
2215 hotx: 0, hoty: 0,
2216 },
2217
2218 dot: {
9881899e 2219 /* eslint-disable indent */
4c38179d
AP
2220 rgbaPixels: new Uint8Array([
2221 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2222 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2223 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2224 ]),
9881899e 2225 /* eslint-enable indent */
4c38179d
AP
2226 w: 3, h: 3,
2227 hotx: 1, hoty: 1,
2228 }
2229};