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