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