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