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