]> git.proxmox.com Git - mirror_novnc.git/blame - core/rfb.js
Allow longer passwords in Plain authentication
[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 1440
18593154 1441 this._sock.send([
1442 (user.length >> 24) & 0xFF,
1443 (user.length >> 16) & 0xFF,
1444 (user.legnth >> 8) & 0xFF,
1445 user.length & 0xFF
1446 ]);
1447 this._sock.send([
1448 (pass.length >> 24) & 0xFF,
1449 (pass.length >> 16) & 0xFF,
1450 (pass.legnth >> 8) & 0xFF,
1451 pass.length & 0xFF
1452 ]);
ea858bfa
SM
1453 this._sock.sendString(user);
1454 this._sock.sendString(pass);
a1015d8d 1455
e7dec527 1456 this._rfbInitState = "SecurityResult";
a1015d8d
FS
1457 return true;
1458 }
1459 }
1460
00674385 1461 _negotiateStdVNCAuth() {
abfe5b7a
SM
1462 if (this._sock.rQwait("auth challenge", 16)) { return false; }
1463
e7dec527 1464 if (this._rfbCredentials.password === undefined) {
2b5f94fa
JD
1465 this.dispatchEvent(new CustomEvent(
1466 "credentialsrequired",
1467 { detail: { types: ["password"] } }));
6d6f0db0
SR
1468 return false;
1469 }
b1dee947 1470
6d6f0db0 1471 // TODO(directxman12): make genDES not require an Array
2b5f94fa 1472 const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
e7dec527 1473 const response = RFB.genDES(this._rfbCredentials.password, challenge);
6d6f0db0 1474 this._sock.send(response);
e7dec527 1475 this._rfbInitState = "SecurityResult";
6d6f0db0 1476 return true;
0e4808bf 1477 }
6d6f0db0 1478
00674385 1479 _negotiateTightUnixAuth() {
e7dec527
SM
1480 if (this._rfbCredentials.username === undefined ||
1481 this._rfbCredentials.password === undefined) {
1c982614
CKB
1482 this.dispatchEvent(new CustomEvent(
1483 "credentialsrequired",
1484 { detail: { types: ["username", "password"] } }));
1485 return false;
1486 }
1487
e7dec527
SM
1488 this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
1489 this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
ea858bfa
SM
1490 this._sock.sendString(this._rfbCredentials.username);
1491 this._sock.sendString(this._rfbCredentials.password);
e7dec527 1492 this._rfbInitState = "SecurityResult";
1c982614
CKB
1493 return true;
1494 }
1495
00674385 1496 _negotiateTightTunnels(numTunnels) {
2b5f94fa 1497 const clientSupportedTunnelTypes = {
6d6f0db0
SR
1498 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
1499 };
2b5f94fa 1500 const serverSupportedTunnelTypes = {};
6d6f0db0 1501 // receive tunnel capabilities
2b5f94fa 1502 for (let i = 0; i < numTunnels; i++) {
e7dec527
SM
1503 const capCode = this._sock.rQshift32();
1504 const capVendor = this._sock.rQshiftStr(4);
1505 const capSignature = this._sock.rQshiftStr(8);
1506 serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };
6d6f0db0
SR
1507 }
1508
e6bad200
PO
1509 Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
1510
8f47bd29
PO
1511 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1512 // but forgets to advertise it. Try to detect such servers by
1513 // looking for their custom tunnel type.
1514 if (serverSupportedTunnelTypes[1] &&
1515 (serverSupportedTunnelTypes[1].vendor === "SICR") &&
1516 (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
1517 Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1518 serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
1519 }
1520
6d6f0db0
SR
1521 // choose the notunnel type
1522 if (serverSupportedTunnelTypes[0]) {
1523 if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
1524 serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
d472f3f1 1525 return this._fail("Client's tunnel type had the incorrect " +
6d6f0db0 1526 "vendor or signature");
b1dee947 1527 }
e6bad200 1528 Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
6d6f0db0
SR
1529 this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
1530 return false; // wait until we receive the sub auth count to continue
1531 } else {
d472f3f1 1532 return this._fail("Server wanted tunnels, but doesn't support " +
6d6f0db0
SR
1533 "the notunnel type");
1534 }
0e4808bf 1535 }
b1dee947 1536
00674385 1537 _negotiateTightAuth() {
e7dec527 1538 if (!this._rfbTightVNC) { // first pass, do the tunnel negotiation
6d6f0db0 1539 if (this._sock.rQwait("num tunnels", 4)) { return false; }
2b5f94fa 1540 const numTunnels = this._sock.rQshift32();
6d6f0db0 1541 if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
c00ee156 1542
e7dec527 1543 this._rfbTightVNC = true;
b1dee947 1544
6d6f0db0 1545 if (numTunnels > 0) {
00674385 1546 this._negotiateTightTunnels(numTunnels);
6d6f0db0 1547 return false; // wait until we receive the sub auth to continue
b1dee947 1548 }
6d6f0db0 1549 }
b1dee947 1550
6d6f0db0
SR
1551 // second pass, do the sub-auth negotiation
1552 if (this._sock.rQwait("sub auth count", 4)) { return false; }
2b5f94fa 1553 const subAuthCount = this._sock.rQshift32();
6d6f0db0 1554 if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
e7dec527 1555 this._rfbInitState = 'SecurityResult';
6d6f0db0
SR
1556 return true;
1557 }
b1dee947 1558
6d6f0db0 1559 if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
b1dee947 1560
2b5f94fa 1561 const clientSupportedTypes = {
6d6f0db0 1562 'STDVNOAUTH__': 1,
1c982614
CKB
1563 'STDVVNCAUTH_': 2,
1564 'TGHTULGNAUTH': 129
6d6f0db0 1565 };
b1dee947 1566
2b5f94fa 1567 const serverSupportedTypes = [];
95eb681b 1568
2b5f94fa 1569 for (let i = 0; i < subAuthCount; i++) {
8727f598 1570 this._sock.rQshift32(); // capNum
2b5f94fa 1571 const capabilities = this._sock.rQshiftStr(12);
6d6f0db0
SR
1572 serverSupportedTypes.push(capabilities);
1573 }
d86cc2d9 1574
e6bad200
PO
1575 Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
1576
2b5f94fa 1577 for (let authType in clientSupportedTypes) {
6d6f0db0
SR
1578 if (serverSupportedTypes.indexOf(authType) != -1) {
1579 this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
e6bad200 1580 Log.Debug("Selected authentication type: " + authType);
d86cc2d9 1581
6d6f0db0
SR
1582 switch (authType) {
1583 case 'STDVNOAUTH__': // no auth
e7dec527 1584 this._rfbInitState = 'SecurityResult';
6d6f0db0
SR
1585 return true;
1586 case 'STDVVNCAUTH_': // VNC auth
e7dec527 1587 this._rfbAuthScheme = 2;
00674385 1588 return this._initMsg();
1c982614 1589 case 'TGHTULGNAUTH': // UNIX auth
e7dec527 1590 this._rfbAuthScheme = 129;
00674385 1591 return this._initMsg();
6d6f0db0 1592 default:
d472f3f1
SM
1593 return this._fail("Unsupported tiny auth scheme " +
1594 "(scheme: " + authType + ")");
b1dee947
SR
1595 }
1596 }
6d6f0db0 1597 }
b1dee947 1598
d472f3f1 1599 return this._fail("No supported sub-auth types!");
0e4808bf 1600 }
6d6f0db0 1601
00674385 1602 _negotiateAuthentication() {
e7dec527 1603 switch (this._rfbAuthScheme) {
6d6f0db0 1604 case 1: // no auth
e7dec527
SM
1605 if (this._rfbVersion >= 3.8) {
1606 this._rfbInitState = 'SecurityResult';
6d6f0db0
SR
1607 return true;
1608 }
e7dec527 1609 this._rfbInitState = 'ClientInitialisation';
00674385 1610 return this._initMsg();
95eb681b 1611
6d6f0db0 1612 case 22: // XVP auth
00674385 1613 return this._negotiateXvpAuth();
d86cc2d9 1614
6d6f0db0 1615 case 2: // VNC authentication
00674385 1616 return this._negotiateStdVNCAuth();
d86cc2d9 1617
6d6f0db0 1618 case 16: // TightVNC Security Type
00674385 1619 return this._negotiateTightAuth();
d86cc2d9 1620
a1015d8d 1621 case 19: // VeNCrypt Security Type
00674385 1622 return this._negotiateVeNCryptAuth();
a1015d8d 1623
1c982614 1624 case 129: // TightVNC UNIX Security Type
00674385 1625 return this._negotiateTightUnixAuth();
1c982614 1626
6d6f0db0 1627 default:
d472f3f1 1628 return this._fail("Unsupported auth scheme (scheme: " +
e7dec527 1629 this._rfbAuthScheme + ")");
6d6f0db0 1630 }
0e4808bf 1631 }
6d6f0db0 1632
00674385 1633 _handleSecurityResult() {
6d6f0db0 1634 if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
d472f3f1 1635
2b5f94fa 1636 const status = this._sock.rQshift32();
d472f3f1
SM
1637
1638 if (status === 0) { // OK
e7dec527 1639 this._rfbInitState = 'ClientInitialisation';
d472f3f1 1640 Log.Debug('Authentication OK');
00674385 1641 return this._initMsg();
d472f3f1 1642 } else {
e7dec527
SM
1643 if (this._rfbVersion >= 3.8) {
1644 this._rfbInitState = "SecurityReason";
1645 this._securityContext = "security result";
1646 this._securityStatus = status;
00674385 1647 return this._initMsg();
d472f3f1 1648 } else {
2b5f94fa
JD
1649 this.dispatchEvent(new CustomEvent(
1650 "securityfailure",
1651 { detail: { status: status } }));
d472f3f1
SM
1652
1653 return this._fail("Security handshake failed");
1654 }
6d6f0db0 1655 }
0e4808bf 1656 }
6d6f0db0 1657
00674385 1658 _negotiateServerInit() {
6d6f0db0
SR
1659 if (this._sock.rQwait("server initialization", 24)) { return false; }
1660
1661 /* Screen size */
2b5f94fa
JD
1662 const width = this._sock.rQshift16();
1663 const height = this._sock.rQshift16();
6d6f0db0
SR
1664
1665 /* PIXEL_FORMAT */
2b5f94fa
JD
1666 const bpp = this._sock.rQshift8();
1667 const depth = this._sock.rQshift8();
e7dec527
SM
1668 const bigEndian = this._sock.rQshift8();
1669 const trueColor = this._sock.rQshift8();
1670
1671 const redMax = this._sock.rQshift16();
1672 const greenMax = this._sock.rQshift16();
1673 const blueMax = this._sock.rQshift16();
1674 const redShift = this._sock.rQshift8();
1675 const greenShift = this._sock.rQshift8();
1676 const blueShift = this._sock.rQshift8();
6d6f0db0
SR
1677 this._sock.rQskipBytes(3); // padding
1678
1679 // NB(directxman12): we don't want to call any callbacks or print messages until
1680 // *after* we're past the point where we could backtrack
1681
1682 /* Connection name/title */
e7dec527
SM
1683 const nameLength = this._sock.rQshift32();
1684 if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
1685 let name = this._sock.rQshiftStr(nameLength);
2cf82a5c 1686 name = decodeUTF8(name, true);
6d6f0db0 1687
e7dec527
SM
1688 if (this._rfbTightVNC) {
1689 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
6d6f0db0 1690 // In TightVNC mode, ServerInit message is extended
2b5f94fa
JD
1691 const numServerMessages = this._sock.rQshift16();
1692 const numClientMessages = this._sock.rQshift16();
1693 const numEncodings = this._sock.rQshift16();
6d6f0db0
SR
1694 this._sock.rQskipBytes(2); // padding
1695
2b5f94fa 1696 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
e7dec527 1697 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
6d6f0db0
SR
1698
1699 // we don't actually do anything with the capability information that TIGHT sends,
1700 // so we just skip the all of this.
1701
1702 // TIGHT server message capabilities
1703 this._sock.rQskipBytes(16 * numServerMessages);
1704
1705 // TIGHT client message capabilities
1706 this._sock.rQskipBytes(16 * numClientMessages);
1707
1708 // TIGHT encoding capabilities
1709 this._sock.rQskipBytes(16 * numEncodings);
1710 }
d86cc2d9 1711
6d6f0db0
SR
1712 // NB(directxman12): these are down here so that we don't run them multiple times
1713 // if we backtrack
91d5c625 1714 Log.Info("Screen: " + width + "x" + height +
6d6f0db0 1715 ", bpp: " + bpp + ", depth: " + depth +
e7dec527
SM
1716 ", bigEndian: " + bigEndian +
1717 ", trueColor: " + trueColor +
1718 ", redMax: " + redMax +
1719 ", greenMax: " + greenMax +
1720 ", blueMax: " + blueMax +
1721 ", redShift: " + redShift +
1722 ", greenShift: " + greenShift +
1723 ", blueShift: " + blueShift);
6d6f0db0 1724
6d6f0db0 1725 // we're past the point where we could backtrack, so it's safe to call this
ce66b469 1726 this._setDesktopName(name);
91d5c625 1727 this._resize(width, height);
b1dee947 1728
747b4623 1729 if (!this._viewOnly) { this._keyboard.grab(); }
b1dee947 1730
e7dec527 1731 this._fbDepth = 24;
5a5f5ada 1732
e7dec527 1733 if (this._fbName === "Intel(r) AMT KVM") {
5a5f5ada 1734 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
e7dec527 1735 this._fbDepth = 8;
5a5f5ada
PO
1736 }
1737
e7dec527 1738 RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
49a81837 1739 this._sendEncodings();
e7dec527 1740 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
6d6f0db0 1741
6d6f0db0
SR
1742 this._updateConnectionState('connected');
1743 return true;
0e4808bf 1744 }
6d6f0db0 1745
0e4808bf 1746 _sendEncodings() {
2b5f94fa 1747 const encs = [];
49a81837
PO
1748
1749 // In preference order
1750 encs.push(encodings.encodingCopyRect);
5a5f5ada 1751 // Only supported with full depth support
e7dec527 1752 if (this._fbDepth == 24) {
5a5f5ada 1753 encs.push(encodings.encodingTight);
2c813a33 1754 encs.push(encodings.encodingTightPNG);
5a5f5ada
PO
1755 encs.push(encodings.encodingHextile);
1756 encs.push(encodings.encodingRRE);
1757 }
49a81837
PO
1758 encs.push(encodings.encodingRaw);
1759
1760 // Psuedo-encoding settings
efd1f8a4 1761 encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
479d8cef 1762 encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
49a81837
PO
1763
1764 encs.push(encodings.pseudoEncodingDesktopSize);
1765 encs.push(encodings.pseudoEncodingLastRect);
1766 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1767 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1768 encs.push(encodings.pseudoEncodingXvp);
1769 encs.push(encodings.pseudoEncodingFence);
1770 encs.push(encodings.pseudoEncodingContinuousUpdates);
ce66b469 1771 encs.push(encodings.pseudoEncodingDesktopName);
f73fdc3e 1772 encs.push(encodings.pseudoEncodingExtendedClipboard);
49a81837 1773
e7dec527 1774 if (this._fbDepth == 24) {
296ba51f 1775 encs.push(encodings.pseudoEncodingVMwareCursor);
49a81837
PO
1776 encs.push(encodings.pseudoEncodingCursor);
1777 }
1778
1779 RFB.messages.clientEncodings(this._sock, encs);
0e4808bf 1780 }
49a81837 1781
6d6f0db0
SR
1782 /* RFB protocol initialization states:
1783 * ProtocolVersion
1784 * Security
1785 * Authentication
1786 * SecurityResult
1787 * ClientInitialization - not triggered by server message
1788 * ServerInitialization
1789 */
00674385 1790 _initMsg() {
e7dec527 1791 switch (this._rfbInitState) {
6d6f0db0 1792 case 'ProtocolVersion':
00674385 1793 return this._negotiateProtocolVersion();
6d6f0db0
SR
1794
1795 case 'Security':
00674385 1796 return this._negotiateSecurity();
6d6f0db0
SR
1797
1798 case 'Authentication':
00674385 1799 return this._negotiateAuthentication();
6d6f0db0
SR
1800
1801 case 'SecurityResult':
00674385 1802 return this._handleSecurityResult();
6d6f0db0 1803
3bb15d4a 1804 case 'SecurityReason':
00674385 1805 return this._handleSecurityReason();
3bb15d4a 1806
6d6f0db0
SR
1807 case 'ClientInitialisation':
1808 this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
e7dec527 1809 this._rfbInitState = 'ServerInitialisation';
6d6f0db0 1810 return true;
b1dee947 1811
6d6f0db0 1812 case 'ServerInitialisation':
00674385 1813 return this._negotiateServerInit();
b1dee947 1814
6d6f0db0 1815 default:
d472f3f1 1816 return this._fail("Unknown init state (state: " +
e7dec527 1817 this._rfbInitState + ")");
6d6f0db0 1818 }
0e4808bf 1819 }
b1dee947 1820
00674385 1821 _handleSetColourMapMsg() {
6d6f0db0 1822 Log.Debug("SetColorMapEntries");
b1dee947 1823
d472f3f1 1824 return this._fail("Unexpected SetColorMapEntries message");
0e4808bf 1825 }
b1dee947 1826
00674385 1827 _handleServerCutText() {
6d6f0db0 1828 Log.Debug("ServerCutText");
b1dee947 1829
6d6f0db0 1830 if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
f73fdc3e 1831
6d6f0db0 1832 this._sock.rQskipBytes(3); // Padding
b1dee947 1833
f73fdc3e
NL
1834 let length = this._sock.rQshift32();
1835 length = toSigned32bit(length);
b4ff032e 1836
f73fdc3e 1837 if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
b4ff032e 1838
f73fdc3e
NL
1839 if (length >= 0) {
1840 //Standard msg
1841 const text = this._sock.rQshiftStr(length);
1842 if (this._viewOnly) {
1843 return true;
1844 }
1845
1846 this.dispatchEvent(new CustomEvent(
1847 "clipboard",
1848 { detail: { text: text } }));
1849
1850 } else {
1851 //Extended msg.
1852 length = Math.abs(length);
1853 const flags = this._sock.rQshift32();
1854 let formats = flags & 0x0000FFFF;
1855 let actions = flags & 0xFF000000;
1856
1857 let isCaps = (!!(actions & extendedClipboardActionCaps));
1858 if (isCaps) {
1859 this._clipboardServerCapabilitiesFormats = {};
1860 this._clipboardServerCapabilitiesActions = {};
1861
1862 // Update our server capabilities for Formats
1863 for (let i = 0; i <= 15; i++) {
1864 let index = 1 << i;
1865
1866 // Check if format flag is set.
1867 if ((formats & index)) {
1868 this._clipboardServerCapabilitiesFormats[index] = true;
1869 // We don't send unsolicited clipboard, so we
1870 // ignore the size
1871 this._sock.rQshift32();
1872 }
1873 }
1874
1875 // Update our server capabilities for Actions
1876 for (let i = 24; i <= 31; i++) {
1877 let index = 1 << i;
1878 this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
1879 }
1880
1881 /* Caps handling done, send caps with the clients
1882 capabilities set as a response */
1883 let clientActions = [
1884 extendedClipboardActionCaps,
1885 extendedClipboardActionRequest,
1886 extendedClipboardActionPeek,
1887 extendedClipboardActionNotify,
1888 extendedClipboardActionProvide
1889 ];
1890 RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
1891
1892 } else if (actions === extendedClipboardActionRequest) {
1893 if (this._viewOnly) {
1894 return true;
1895 }
1896
1897 // Check if server has told us it can handle Provide and there is clipboard data to send.
1898 if (this._clipboardText != null &&
1899 this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
1900
1901 if (formats & extendedClipboardFormatText) {
1902 RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
1903 }
1904 }
1905
1906 } else if (actions === extendedClipboardActionPeek) {
1907 if (this._viewOnly) {
1908 return true;
1909 }
1910
1911 if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
1912
1913 if (this._clipboardText != null) {
1914 RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
1915 } else {
1916 RFB.messages.extendedClipboardNotify(this._sock, []);
1917 }
1918 }
1919
1920 } else if (actions === extendedClipboardActionNotify) {
1921 if (this._viewOnly) {
1922 return true;
1923 }
1924
1925 if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
1926
1927 if (formats & extendedClipboardFormatText) {
1928 RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
1929 }
1930 }
1931
1932 } else if (actions === extendedClipboardActionProvide) {
1933 if (this._viewOnly) {
1934 return true;
1935 }
1936
1937 if (!(formats & extendedClipboardFormatText)) {
1938 return true;
1939 }
1940 // Ignore what we had in our clipboard client side.
1941 this._clipboardText = null;
1942
1943 // FIXME: Should probably verify that this data was actually requested
1944 let zlibStream = this._sock.rQshiftBytes(length - 4);
1945 let streamInflator = new Inflator();
1946 let textData = null;
b1dee947 1947
f73fdc3e
NL
1948 streamInflator.setInput(zlibStream);
1949 for (let i = 0; i <= 15; i++) {
1950 let format = 1 << i;
1951
1952 if (formats & format) {
1953
1954 let size = 0x00;
1955 let sizeArray = streamInflator.inflate(4);
1956
1957 size |= (sizeArray[0] << 24);
1958 size |= (sizeArray[1] << 16);
1959 size |= (sizeArray[2] << 8);
1960 size |= (sizeArray[3]);
1961 let chunk = streamInflator.inflate(size);
1962
1963 if (format === extendedClipboardFormatText) {
1964 textData = chunk;
1965 }
1966 }
1967 }
1968 streamInflator.setInput(null);
1969
1970 if (textData !== null) {
ceb8ef4e
AT
1971 let tmpText = "";
1972 for (let i = 0; i < textData.length; i++) {
1973 tmpText += String.fromCharCode(textData[i]);
1974 }
1975 textData = tmpText;
f73fdc3e
NL
1976
1977 textData = decodeUTF8(textData);
1978 if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
1979 textData = textData.slice(0, -1);
1980 }
1981
1982 textData = textData.replace("\r\n", "\n");
1983
1984 this.dispatchEvent(new CustomEvent(
1985 "clipboard",
1986 { detail: { text: textData } }));
1987 }
1988 } else {
1989 return this._fail("Unexpected action in extended clipboard message: " + actions);
1990 }
1991 }
6d6f0db0 1992 return true;
0e4808bf 1993 }
b1dee947 1994
00674385 1995 _handleServerFenceMsg() {
6d6f0db0
SR
1996 if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
1997 this._sock.rQskipBytes(3); // Padding
2b5f94fa
JD
1998 let flags = this._sock.rQshift32();
1999 let length = this._sock.rQshift8();
b1dee947 2000
6d6f0db0 2001 if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
b1dee947 2002
6d6f0db0
SR
2003 if (length > 64) {
2004 Log.Warn("Bad payload length (" + length + ") in fence response");
2005 length = 64;
2006 }
4e0c36dd 2007
2b5f94fa 2008 const payload = this._sock.rQshiftStr(length);
b1dee947 2009
6d6f0db0 2010 this._supportsFence = true;
b1dee947 2011
6d6f0db0
SR
2012 /*
2013 * Fence flags
2014 *
2015 * (1<<0) - BlockBefore
2016 * (1<<1) - BlockAfter
2017 * (1<<2) - SyncNext
2018 * (1<<31) - Request
2019 */
b1dee947 2020
6d6f0db0 2021 if (!(flags & (1<<31))) {
d472f3f1 2022 return this._fail("Unexpected fence response");
6d6f0db0 2023 }
b1dee947 2024
6d6f0db0
SR
2025 // Filter out unsupported flags
2026 // FIXME: support syncNext
2027 flags &= (1<<0) | (1<<1);
b1dee947 2028
6d6f0db0
SR
2029 // BlockBefore and BlockAfter are automatically handled by
2030 // the fact that we process each incoming message
2031 // synchronuosly.
2032 RFB.messages.clientFence(this._sock, flags, payload);
f78a652e 2033
6d6f0db0 2034 return true;
0e4808bf 2035 }
b1dee947 2036
00674385 2037 _handleXvpMsg() {
6d6f0db0 2038 if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
8a189a62 2039 this._sock.rQskipBytes(1); // Padding
e7dec527
SM
2040 const xvpVer = this._sock.rQshift8();
2041 const xvpMsg = this._sock.rQshift8();
b1dee947 2042
e7dec527 2043 switch (xvpMsg) {
6d6f0db0 2044 case 0: // XVP_FAIL
5b20d338 2045 Log.Error("XVP Operation Failed");
6d6f0db0
SR
2046 break;
2047 case 1: // XVP_INIT
e7dec527
SM
2048 this._rfbXvpVer = xvpVer;
2049 Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");
832be262 2050 this._setCapability("power", true);
6d6f0db0
SR
2051 break;
2052 default:
e7dec527 2053 this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");
6d6f0db0
SR
2054 break;
2055 }
b1dee947 2056
6d6f0db0 2057 return true;
0e4808bf 2058 }
3df13262 2059
00674385 2060 _normalMsg() {
e7dec527 2061 let msgType;
6d6f0db0 2062 if (this._FBU.rects > 0) {
e7dec527 2063 msgType = 0;
6d6f0db0 2064 } else {
e7dec527 2065 msgType = this._sock.rQshift8();
6d6f0db0 2066 }
76a86ff5 2067
2b5f94fa 2068 let first, ret;
e7dec527 2069 switch (msgType) {
6d6f0db0 2070 case 0: // FramebufferUpdate
2b5f94fa 2071 ret = this._framebufferUpdate();
6d6f0db0
SR
2072 if (ret && !this._enabledContinuousUpdates) {
2073 RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
e7dec527 2074 this._fbWidth, this._fbHeight);
6d6f0db0
SR
2075 }
2076 return ret;
3df13262 2077
6d6f0db0 2078 case 1: // SetColorMapEntries
00674385 2079 return this._handleSetColourMapMsg();
6d6f0db0
SR
2080
2081 case 2: // Bell
2082 Log.Debug("Bell");
2b5f94fa 2083 this.dispatchEvent(new CustomEvent(
4a16dc51 2084 "bell",
2b5f94fa 2085 { detail: {} }));
6d6f0db0
SR
2086 return true;
2087
2088 case 3: // ServerCutText
00674385 2089 return this._handleServerCutText();
6d6f0db0
SR
2090
2091 case 150: // EndOfContinuousUpdates
2b5f94fa 2092 first = !this._supportsContinuousUpdates;
6d6f0db0
SR
2093 this._supportsContinuousUpdates = true;
2094 this._enabledContinuousUpdates = false;
2095 if (first) {
2096 this._enabledContinuousUpdates = true;
2097 this._updateContinuousUpdates();
2098 Log.Info("Enabling continuous updates.");
2099 } else {
2100 // FIXME: We need to send a framebufferupdaterequest here
2101 // if we add support for turning off continuous updates
2102 }
2103 return true;
3df13262 2104
6d6f0db0 2105 case 248: // ServerFence
00674385 2106 return this._handleServerFenceMsg();
3df13262 2107
6d6f0db0 2108 case 250: // XVP
00674385 2109 return this._handleXvpMsg();
3df13262 2110
6d6f0db0 2111 default:
e7dec527 2112 this._fail("Unexpected server message (type " + msgType + ")");
6d6f0db0
SR
2113 Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
2114 return true;
2115 }
0e4808bf 2116 }
3df13262 2117
0e4808bf 2118 _onFlush() {
6d6f0db0
SR
2119 this._flushing = false;
2120 // Resume processing
8a189a62 2121 if (this._sock.rQlen > 0) {
00674385 2122 this._handleMessage();
6d6f0db0 2123 }
0e4808bf 2124 }
3df13262 2125
0e4808bf 2126 _framebufferUpdate() {
6d6f0db0
SR
2127 if (this._FBU.rects === 0) {
2128 if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
8a189a62 2129 this._sock.rQskipBytes(1); // Padding
6d6f0db0 2130 this._FBU.rects = this._sock.rQshift16();
b1dee947 2131
6d6f0db0
SR
2132 // Make sure the previous frame is fully rendered first
2133 // to avoid building up an excessive queue
2134 if (this._display.pending()) {
2135 this._flushing = true;
2136 this._display.flush();
2137 return false;
2138 }
2139 }
b1dee947 2140
6d6f0db0 2141 while (this._FBU.rects > 0) {
923cd220 2142 if (this._FBU.encoding === null) {
6d6f0db0
SR
2143 if (this._sock.rQwait("rect header", 12)) { return false; }
2144 /* New FramebufferUpdate */
b1dee947 2145
2b5f94fa 2146 const hdr = this._sock.rQshiftBytes(12);
6d6f0db0
SR
2147 this._FBU.x = (hdr[0] << 8) + hdr[1];
2148 this._FBU.y = (hdr[2] << 8) + hdr[3];
2149 this._FBU.width = (hdr[4] << 8) + hdr[5];
2150 this._FBU.height = (hdr[6] << 8) + hdr[7];
2151 this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
2152 (hdr[10] << 8) + hdr[11], 10);
6d6f0db0 2153 }
b1dee947 2154
11309f32
PO
2155 if (!this._handleRect()) {
2156 return false;
6d6f0db0 2157 }
b1dee947 2158
11309f32 2159 this._FBU.rects--;
923cd220 2160 this._FBU.encoding = null;
6d6f0db0 2161 }
76a86ff5 2162
6d6f0db0 2163 this._display.flip();
3df13262 2164
6d6f0db0 2165 return true; // We finished this FBU
0e4808bf 2166 }
b1dee947 2167
11309f32
PO
2168 _handleRect() {
2169 switch (this._FBU.encoding) {
2170 case encodings.pseudoEncodingLastRect:
2171 this._FBU.rects = 1; // Will be decreased when we return
2172 return true;
b1dee947 2173
296ba51f
NL
2174 case encodings.pseudoEncodingVMwareCursor:
2175 return this._handleVMwareCursor();
2176
11309f32
PO
2177 case encodings.pseudoEncodingCursor:
2178 return this._handleCursor();
d9ca5e5b 2179
11309f32 2180 case encodings.pseudoEncodingQEMUExtendedKeyEvent:
5b5b7474 2181 this._qemuExtKeyEventSupported = true;
11309f32
PO
2182 return true;
2183
ce66b469
NL
2184 case encodings.pseudoEncodingDesktopName:
2185 return this._handleDesktopName();
2186
11309f32
PO
2187 case encodings.pseudoEncodingDesktopSize:
2188 this._resize(this._FBU.width, this._FBU.height);
2189 return true;
2190
2191 case encodings.pseudoEncodingExtendedDesktopSize:
2192 return this._handleExtendedDesktopSize();
2193
2194 default:
2195 return this._handleDataRect();
2196 }
2197 }
2198
296ba51f
NL
2199 _handleVMwareCursor() {
2200 const hotx = this._FBU.x; // hotspot-x
2201 const hoty = this._FBU.y; // hotspot-y
2202 const w = this._FBU.width;
2203 const h = this._FBU.height;
2204 if (this._sock.rQwait("VMware cursor encoding", 1)) {
2205 return false;
2206 }
2207
e7dec527 2208 const cursorType = this._sock.rQshift8();
296ba51f
NL
2209
2210 this._sock.rQshift8(); //Padding
2211
2212 let rgba;
2213 const bytesPerPixel = 4;
2214
2215 //Classic cursor
e7dec527 2216 if (cursorType == 0) {
296ba51f
NL
2217 //Used to filter away unimportant bits.
2218 //OR is used for correct conversion in js.
2219 const PIXEL_MASK = 0xffffff00 | 0;
2220 rgba = new Array(w * h * bytesPerPixel);
2221
2222 if (this._sock.rQwait("VMware cursor classic encoding",
2223 (w * h * bytesPerPixel) * 2, 2)) {
2224 return false;
2225 }
2226
e7dec527 2227 let andMask = new Array(w * h);
296ba51f 2228 for (let pixel = 0; pixel < (w * h); pixel++) {
e7dec527 2229 andMask[pixel] = this._sock.rQshift32();
296ba51f
NL
2230 }
2231
e7dec527 2232 let xorMask = new Array(w * h);
296ba51f 2233 for (let pixel = 0; pixel < (w * h); pixel++) {
e7dec527 2234 xorMask[pixel] = this._sock.rQshift32();
296ba51f
NL
2235 }
2236
2237 for (let pixel = 0; pixel < (w * h); pixel++) {
e7dec527 2238 if (andMask[pixel] == 0) {
296ba51f 2239 //Fully opaque pixel
e7dec527 2240 let bgr = xorMask[pixel];
296ba51f
NL
2241 let r = bgr >> 8 & 0xff;
2242 let g = bgr >> 16 & 0xff;
2243 let b = bgr >> 24 & 0xff;
2244
2245 rgba[(pixel * bytesPerPixel) ] = r; //r
2246 rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
2247 rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
2248 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
2249
e7dec527 2250 } else if ((andMask[pixel] & PIXEL_MASK) ==
296ba51f
NL
2251 PIXEL_MASK) {
2252 //Only screen value matters, no mouse colouring
e7dec527 2253 if (xorMask[pixel] == 0) {
296ba51f
NL
2254 //Transparent pixel
2255 rgba[(pixel * bytesPerPixel) ] = 0x00;
2256 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2257 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2258 rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
2259
e7dec527 2260 } else if ((xorMask[pixel] & PIXEL_MASK) ==
296ba51f
NL
2261 PIXEL_MASK) {
2262 //Inverted pixel, not supported in browsers.
2263 //Fully opaque instead.
2264 rgba[(pixel * bytesPerPixel) ] = 0x00;
2265 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2266 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2267 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2268
2269 } else {
e7dec527 2270 //Unhandled xorMask
296ba51f
NL
2271 rgba[(pixel * bytesPerPixel) ] = 0x00;
2272 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2273 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2274 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2275 }
2276
2277 } else {
e7dec527 2278 //Unhandled andMask
296ba51f
NL
2279 rgba[(pixel * bytesPerPixel) ] = 0x00;
2280 rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
2281 rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
2282 rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
2283 }
2284 }
2285
2286 //Alpha cursor.
e7dec527 2287 } else if (cursorType == 1) {
296ba51f
NL
2288 if (this._sock.rQwait("VMware cursor alpha encoding",
2289 (w * h * 4), 2)) {
2290 return false;
2291 }
2292
2293 rgba = new Array(w * h * bytesPerPixel);
2294
2295 for (let pixel = 0; pixel < (w * h); pixel++) {
2296 let data = this._sock.rQshift32();
2297
71bb3fdf 2298 rgba[(pixel * 4) ] = data >> 24 & 0xff; //r
296ba51f 2299 rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
71bb3fdf 2300 rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff; //b
296ba51f
NL
2301 rgba[(pixel * 4) + 3 ] = data & 0xff; //a
2302 }
2303
2304 } else {
2305 Log.Warn("The given cursor type is not supported: "
e7dec527 2306 + cursorType + " given.");
296ba51f
NL
2307 return false;
2308 }
2309
2310 this._updateCursor(rgba, hotx, hoty, w, h);
2311
2312 return true;
2313 }
2314
11309f32 2315 _handleCursor() {
679535ec
PO
2316 const hotx = this._FBU.x; // hotspot-x
2317 const hoty = this._FBU.y; // hotspot-y
11309f32
PO
2318 const w = this._FBU.width;
2319 const h = this._FBU.height;
2320
2321 const pixelslength = w * h * 4;
679535ec 2322 const masklength = Math.ceil(w / 8) * h;
11309f32 2323
923cd220
PO
2324 let bytes = pixelslength + masklength;
2325 if (this._sock.rQwait("cursor encoding", bytes)) {
11309f32
PO
2326 return false;
2327 }
2328
679535ec
PO
2329 // Decode from BGRX pixels + bit mask to RGBA
2330 const pixels = this._sock.rQshiftBytes(pixelslength);
2331 const mask = this._sock.rQshiftBytes(masklength);
2332 let rgba = new Uint8Array(w * h * 4);
2333
e7dec527 2334 let pixIdx = 0;
679535ec
PO
2335 for (let y = 0; y < h; y++) {
2336 for (let x = 0; x < w; x++) {
e7dec527
SM
2337 let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);
2338 let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;
2339 rgba[pixIdx ] = pixels[pixIdx + 2];
2340 rgba[pixIdx + 1] = pixels[pixIdx + 1];
2341 rgba[pixIdx + 2] = pixels[pixIdx];
2342 rgba[pixIdx + 3] = alpha;
2343 pixIdx += 4;
679535ec
PO
2344 }
2345 }
2346
2347 this._updateCursor(rgba, hotx, hoty, w, h);
11309f32 2348
11309f32
PO
2349 return true;
2350 }
2351
ce66b469
NL
2352 _handleDesktopName() {
2353 if (this._sock.rQwait("DesktopName", 4)) {
2354 return false;
2355 }
2356
2357 let length = this._sock.rQshift32();
2358
2359 if (this._sock.rQwait("DesktopName", length, 4)) {
2360 return false;
2361 }
2362
2363 let name = this._sock.rQshiftStr(length);
2cf82a5c 2364 name = decodeUTF8(name, true);
ce66b469
NL
2365
2366 this._setDesktopName(name);
2367
2368 return true;
2369 }
2370
11309f32 2371 _handleExtendedDesktopSize() {
923cd220 2372 if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
11309f32
PO
2373 return false;
2374 }
2375
e7dec527 2376 const numberOfScreens = this._sock.rQpeek8();
11309f32 2377
e7dec527 2378 let bytes = 4 + (numberOfScreens * 16);
923cd220 2379 if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
11309f32
PO
2380 return false;
2381 }
2382
2383 const firstUpdate = !this._supportsSetDesktopSize;
2384 this._supportsSetDesktopSize = true;
2385
2386 // Normally we only apply the current resize mode after a
2387 // window resize event. However there is no such trigger on the
2388 // initial connect. And we don't know if the server supports
2389 // resizing until we've gotten here.
2390 if (firstUpdate) {
2391 this._requestRemoteResize();
2392 }
2393
2394 this._sock.rQskipBytes(1); // number-of-screens
2395 this._sock.rQskipBytes(3); // padding
2396
e7dec527 2397 for (let i = 0; i < numberOfScreens; i += 1) {
11309f32
PO
2398 // Save the id and flags of the first screen
2399 if (i === 0) {
e7dec527 2400 this._screenID = this._sock.rQshiftBytes(4); // id
11309f32
PO
2401 this._sock.rQskipBytes(2); // x-position
2402 this._sock.rQskipBytes(2); // y-position
2403 this._sock.rQskipBytes(2); // width
2404 this._sock.rQskipBytes(2); // height
e7dec527 2405 this._screenFlags = this._sock.rQshiftBytes(4); // flags
11309f32
PO
2406 } else {
2407 this._sock.rQskipBytes(16);
b1dee947 2408 }
11309f32 2409 }
b1dee947 2410
11309f32
PO
2411 /*
2412 * The x-position indicates the reason for the change:
2413 *
2414 * 0 - server resized on its own
2415 * 1 - this client requested the resize
2416 * 2 - another client requested the resize
2417 */
2418
2419 // We need to handle errors when we requested the resize.
2420 if (this._FBU.x === 1 && this._FBU.y !== 0) {
2421 let msg = "";
2422 // The y-position indicates the status code from the server
2423 switch (this._FBU.y) {
9881899e
PO
2424 case 1:
2425 msg = "Resize is administratively prohibited";
2426 break;
2427 case 2:
2428 msg = "Out of resources";
2429 break;
2430 case 3:
2431 msg = "Invalid screen layout";
2432 break;
2433 default:
2434 msg = "Unknown reason";
2435 break;
11309f32
PO
2436 }
2437 Log.Warn("Server did not accept the resize request: "
2438 + msg);
2439 } else {
2440 this._resize(this._FBU.width, this._FBU.height);
6d6f0db0 2441 }
b1dee947 2442
11309f32
PO
2443 return true;
2444 }
b1dee947 2445
11309f32 2446 _handleDataRect() {
923cd220
PO
2447 let decoder = this._decoders[this._FBU.encoding];
2448 if (!decoder) {
11309f32
PO
2449 this._fail("Unsupported encoding (encoding: " +
2450 this._FBU.encoding + ")");
2451 return false;
2452 }
2453
923cd220
PO
2454 try {
2455 return decoder.decodeRect(this._FBU.x, this._FBU.y,
2456 this._FBU.width, this._FBU.height,
2457 this._sock, this._display,
e7dec527 2458 this._fbDepth);
923cd220
PO
2459 } catch (err) {
2460 this._fail("Error decoding rect: " + err);
2461 return false;
2462 }
0e4808bf 2463 }
b1dee947 2464
0e4808bf 2465 _updateContinuousUpdates() {
6d6f0db0 2466 if (!this._enabledContinuousUpdates) { return; }
b1dee947 2467
6d6f0db0 2468 RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
e7dec527 2469 this._fbWidth, this._fbHeight);
0e4808bf 2470 }
91d5c625 2471
0e4808bf 2472 _resize(width, height) {
e7dec527
SM
2473 this._fbWidth = width;
2474 this._fbHeight = height;
91d5c625 2475
e7dec527 2476 this._display.resize(this._fbWidth, this._fbHeight);
e89eef94 2477
9b84f516
PO
2478 // Adjust the visible viewport based on the new dimensions
2479 this._updateClip();
2480 this._updateScale();
91d5c625 2481
91d5c625 2482 this._updateContinuousUpdates();
0e4808bf 2483 }
8db09746 2484
0e4808bf 2485 _xvpOp(ver, op) {
e7dec527 2486 if (this._rfbXvpVer < ver) { return; }
cd523e8f
PO
2487 Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
2488 RFB.messages.xvpOp(this._sock, ver, op);
0e4808bf 2489 }
76a86ff5 2490
d1314d4b
AP
2491 _updateCursor(rgba, hotx, hoty, w, h) {
2492 this._cursorImage = {
2493 rgbaPixels: rgba,
2494 hotx: hotx, hoty: hoty, w: w, h: h,
2495 };
2496 this._refreshCursor();
2497 }
2498
4c38179d
AP
2499 _shouldShowDotCursor() {
2500 // Called when this._cursorImage is updated
2501 if (!this._showDotCursor) {
2502 // User does not want to see the dot, so...
2503 return false;
2504 }
2505
2506 // The dot should not be shown if the cursor is already visible,
2507 // i.e. contains at least one not-fully-transparent pixel.
2508 // So iterate through all alpha bytes in rgba and stop at the
2509 // first non-zero.
2510 for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
2511 if (this._cursorImage.rgbaPixels[i]) {
2512 return false;
2513 }
2514 }
2515
2516 // At this point, we know that the cursor is fully transparent, and
2517 // the user wants to see the dot instead of this.
2518 return true;
2519 }
2520
d1314d4b 2521 _refreshCursor() {
e7dec527
SM
2522 if (this._rfbConnectionState !== "connecting" &&
2523 this._rfbConnectionState !== "connected") {
49db41ea
PO
2524 return;
2525 }
4c38179d
AP
2526 const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
2527 this._cursor.change(image.rgbaPixels,
2528 image.hotx, image.hoty,
2529 image.w, image.h
d1314d4b
AP
2530 );
2531 }
2532
0e4808bf 2533 static genDES(password, challenge) {
0505214c
JD
2534 const passwordChars = password.split('').map(c => c.charCodeAt(0));
2535 return (new DES(passwordChars)).encrypt(challenge);
0e4808bf
JD
2536 }
2537}
76a86ff5 2538
6d6f0db0
SR
2539// Class Methods
2540RFB.messages = {
0e4808bf 2541 keyEvent(sock, keysym, down) {
2b5f94fa
JD
2542 const buff = sock._sQ;
2543 const offset = sock._sQlen;
8db09746 2544
6d6f0db0
SR
2545 buff[offset] = 4; // msg-type
2546 buff[offset + 1] = down;
b1dee947 2547
6d6f0db0
SR
2548 buff[offset + 2] = 0;
2549 buff[offset + 3] = 0;
fb49f91b 2550
6d6f0db0
SR
2551 buff[offset + 4] = (keysym >> 24);
2552 buff[offset + 5] = (keysym >> 16);
2553 buff[offset + 6] = (keysym >> 8);
2554 buff[offset + 7] = keysym;
b1dee947 2555
6d6f0db0
SR
2556 sock._sQlen += 8;
2557 sock.flush();
2558 },
ef1e8bab 2559
0e4808bf 2560 QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
e7dec527 2561 function getRFBkeycode(xtScanCode) {
2b5f94fa
JD
2562 const upperByte = (keycode >> 8);
2563 const lowerByte = (keycode & 0x00ff);
6d6f0db0 2564 if (upperByte === 0xe0 && lowerByte < 0x7f) {
2b5f94fa 2565 return lowerByte | 0x80;
ef1e8bab 2566 }
e7dec527 2567 return xtScanCode;
ef1e8bab 2568 }
b1dee947 2569
2b5f94fa
JD
2570 const buff = sock._sQ;
2571 const offset = sock._sQlen;
8f06673a 2572
6d6f0db0
SR
2573 buff[offset] = 255; // msg-type
2574 buff[offset + 1] = 0; // sub msg-type
8f06673a 2575
6d6f0db0
SR
2576 buff[offset + 2] = (down >> 8);
2577 buff[offset + 3] = down;
8f06673a 2578
6d6f0db0
SR
2579 buff[offset + 4] = (keysym >> 24);
2580 buff[offset + 5] = (keysym >> 16);
2581 buff[offset + 6] = (keysym >> 8);
2582 buff[offset + 7] = keysym;
8f06673a 2583
2b5f94fa 2584 const RFBkeycode = getRFBkeycode(keycode);
8f06673a 2585
6d6f0db0
SR
2586 buff[offset + 8] = (RFBkeycode >> 24);
2587 buff[offset + 9] = (RFBkeycode >> 16);
2588 buff[offset + 10] = (RFBkeycode >> 8);
2589 buff[offset + 11] = RFBkeycode;
8f06673a 2590
6d6f0db0
SR
2591 sock._sQlen += 12;
2592 sock.flush();
2593 },
8f06673a 2594
0e4808bf 2595 pointerEvent(sock, x, y, mask) {
2b5f94fa
JD
2596 const buff = sock._sQ;
2597 const offset = sock._sQlen;
8f06673a 2598
6d6f0db0 2599 buff[offset] = 5; // msg-type
9ff86fb7 2600
6d6f0db0 2601 buff[offset + 1] = mask;
9ff86fb7 2602
6d6f0db0
SR
2603 buff[offset + 2] = x >> 8;
2604 buff[offset + 3] = x;
9ff86fb7 2605
6d6f0db0
SR
2606 buff[offset + 4] = y >> 8;
2607 buff[offset + 5] = y;
9ff86fb7 2608
6d6f0db0
SR
2609 sock._sQlen += 6;
2610 sock.flush();
2611 },
9ff86fb7 2612
f73fdc3e
NL
2613 // Used to build Notify and Request data.
2614 _buildExtendedClipboardFlags(actions, formats) {
2615 let data = new Uint8Array(4);
2616 let formatFlag = 0x00000000;
2617 let actionFlag = 0x00000000;
2618
2619 for (let i = 0; i < actions.length; i++) {
2620 actionFlag |= actions[i];
2621 }
2622
2623 for (let i = 0; i < formats.length; i++) {
2624 formatFlag |= formats[i];
2625 }
2626
2627 data[0] = actionFlag >> 24; // Actions
2628 data[1] = 0x00; // Reserved
2629 data[2] = 0x00; // Reserved
2630 data[3] = formatFlag; // Formats
2631
2632 return data;
2633 },
2634
2635 extendedClipboardProvide(sock, formats, inData) {
2636 // Deflate incomming data and their sizes
2637 let deflator = new Deflator();
2638 let dataToDeflate = [];
2639
2640 for (let i = 0; i < formats.length; i++) {
2641 // We only support the format Text at this time
2642 if (formats[i] != extendedClipboardFormatText) {
2643 throw new Error("Unsupported extended clipboard format for Provide message.");
2644 }
2645
2646 // Change lone \r or \n into \r\n as defined in rfbproto
2647 inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
2648
2649 // Check if it already has \0
2650 let text = encodeUTF8(inData[i] + "\0");
2651
2652 dataToDeflate.push( (text.length >> 24) & 0xFF,
2653 (text.length >> 16) & 0xFF,
2654 (text.length >> 8) & 0xFF,
2655 (text.length & 0xFF));
2656
2657 for (let j = 0; j < text.length; j++) {
2658 dataToDeflate.push(text.charCodeAt(j));
2659 }
2660 }
2661
2662 let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
2663
2664 // Build data to send
2665 let data = new Uint8Array(4 + deflatedData.length);
2666 data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
2667 formats));
2668 data.set(deflatedData, 4);
2669
2670 RFB.messages.clientCutText(sock, data, true);
2671 },
2672
2673 extendedClipboardNotify(sock, formats) {
2674 let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
2675 formats);
2676 RFB.messages.clientCutText(sock, flags, true);
2677 },
2678
2679 extendedClipboardRequest(sock, formats) {
2680 let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
2681 formats);
2682 RFB.messages.clientCutText(sock, flags, true);
2683 },
2684
2685 extendedClipboardCaps(sock, actions, formats) {
2686 let formatKeys = Object.keys(formats);
2687 let data = new Uint8Array(4 + (4 * formatKeys.length));
2688
2689 formatKeys.map(x => parseInt(x));
2690 formatKeys.sort((a, b) => a - b);
2691
2692 data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
2693
2694 let loopOffset = 4;
2695 for (let i = 0; i < formatKeys.length; i++) {
2696 data[loopOffset] = formats[formatKeys[i]] >> 24;
2697 data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
2698 data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
2699 data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
2700
2701 loopOffset += 4;
2702 data[3] |= (1 << formatKeys[i]); // Update our format flags
2703 }
2704
2705 RFB.messages.clientCutText(sock, data, true);
2706 },
2707
2708 clientCutText(sock, data, extended = false) {
2b5f94fa
JD
2709 const buff = sock._sQ;
2710 const offset = sock._sQlen;
b1dee947 2711
6d6f0db0 2712 buff[offset] = 6; // msg-type
9ff86fb7 2713
6d6f0db0
SR
2714 buff[offset + 1] = 0; // padding
2715 buff[offset + 2] = 0; // padding
2716 buff[offset + 3] = 0; // padding
9ff86fb7 2717
f73fdc3e
NL
2718 let length;
2719 if (extended) {
2720 length = toUnsigned32bit(-data.length);
2721 } else {
2722 length = data.length;
2723 }
9ff86fb7 2724
2bb8b28d
SM
2725 buff[offset + 4] = length >> 24;
2726 buff[offset + 5] = length >> 16;
2727 buff[offset + 6] = length >> 8;
2728 buff[offset + 7] = length;
9ff86fb7 2729
2bb8b28d 2730 sock._sQlen += 8;
9ff86fb7 2731
f73fdc3e 2732 // We have to keep track of from where in the data we begin creating the
2bb8b28d 2733 // buffer for the flush in the next iteration.
f73fdc3e 2734 let dataOffset = 0;
2bb8b28d 2735
f73fdc3e 2736 let remaining = data.length;
2bb8b28d
SM
2737 while (remaining > 0) {
2738
0e4808bf 2739 let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
2bb8b28d 2740 for (let i = 0; i < flushSize; i++) {
f73fdc3e 2741 buff[sock._sQlen + i] = data[dataOffset + i];
2bb8b28d
SM
2742 }
2743
2744 sock._sQlen += flushSize;
2745 sock.flush();
2746
2747 remaining -= flushSize;
f73fdc3e 2748 dataOffset += flushSize;
2bb8b28d 2749 }
f73fdc3e 2750
6d6f0db0
SR
2751 },
2752
0e4808bf 2753 setDesktopSize(sock, width, height, id, flags) {
2b5f94fa
JD
2754 const buff = sock._sQ;
2755 const offset = sock._sQlen;
6d6f0db0
SR
2756
2757 buff[offset] = 251; // msg-type
2758 buff[offset + 1] = 0; // padding
2759 buff[offset + 2] = width >> 8; // width
2760 buff[offset + 3] = width;
2761 buff[offset + 4] = height >> 8; // height
2762 buff[offset + 5] = height;
2763
2764 buff[offset + 6] = 1; // number-of-screens
2765 buff[offset + 7] = 0; // padding
2766
2767 // screen array
2768 buff[offset + 8] = id >> 24; // id
2769 buff[offset + 9] = id >> 16;
2770 buff[offset + 10] = id >> 8;
2771 buff[offset + 11] = id;
2772 buff[offset + 12] = 0; // x-position
2773 buff[offset + 13] = 0;
2774 buff[offset + 14] = 0; // y-position
2775 buff[offset + 15] = 0;
2776 buff[offset + 16] = width >> 8; // width
2777 buff[offset + 17] = width;
2778 buff[offset + 18] = height >> 8; // height
2779 buff[offset + 19] = height;
2780 buff[offset + 20] = flags >> 24; // flags
2781 buff[offset + 21] = flags >> 16;
2782 buff[offset + 22] = flags >> 8;
2783 buff[offset + 23] = flags;
2784
2785 sock._sQlen += 24;
2786 sock.flush();
2787 },
2788
0e4808bf 2789 clientFence(sock, flags, payload) {
2b5f94fa
JD
2790 const buff = sock._sQ;
2791 const offset = sock._sQlen;
6d6f0db0
SR
2792
2793 buff[offset] = 248; // msg-type
2794
2795 buff[offset + 1] = 0; // padding
2796 buff[offset + 2] = 0; // padding
2797 buff[offset + 3] = 0; // padding
2798
2799 buff[offset + 4] = flags >> 24; // flags
2800 buff[offset + 5] = flags >> 16;
2801 buff[offset + 6] = flags >> 8;
2802 buff[offset + 7] = flags;
2803
2b5f94fa 2804 const n = payload.length;
6d6f0db0
SR
2805
2806 buff[offset + 8] = n; // length
2807
2b5f94fa 2808 for (let i = 0; i < n; i++) {
6d6f0db0
SR
2809 buff[offset + 9 + i] = payload.charCodeAt(i);
2810 }
a7a89626 2811
6d6f0db0
SR
2812 sock._sQlen += 9 + n;
2813 sock.flush();
2814 },
3df13262 2815
0e4808bf 2816 enableContinuousUpdates(sock, enable, x, y, width, height) {
2b5f94fa
JD
2817 const buff = sock._sQ;
2818 const offset = sock._sQlen;
3df13262 2819
6d6f0db0
SR
2820 buff[offset] = 150; // msg-type
2821 buff[offset + 1] = enable; // enable-flag
76a86ff5 2822
6d6f0db0
SR
2823 buff[offset + 2] = x >> 8; // x
2824 buff[offset + 3] = x;
2825 buff[offset + 4] = y >> 8; // y
2826 buff[offset + 5] = y;
2827 buff[offset + 6] = width >> 8; // width
2828 buff[offset + 7] = width;
2829 buff[offset + 8] = height >> 8; // height
2830 buff[offset + 9] = height;
76a86ff5 2831
6d6f0db0
SR
2832 sock._sQlen += 10;
2833 sock.flush();
2834 },
76a86ff5 2835
e7dec527 2836 pixelFormat(sock, depth, trueColor) {
2b5f94fa
JD
2837 const buff = sock._sQ;
2838 const offset = sock._sQlen;
ae116051 2839
2b5f94fa 2840 let bpp;
5a5f5ada
PO
2841
2842 if (depth > 16) {
2843 bpp = 32;
2844 } else if (depth > 8) {
2845 bpp = 16;
2846 } else {
2847 bpp = 8;
2848 }
2849
2b5f94fa 2850 const bits = Math.floor(depth/3);
5a5f5ada 2851
6d6f0db0 2852 buff[offset] = 0; // msg-type
9ff86fb7 2853
6d6f0db0
SR
2854 buff[offset + 1] = 0; // padding
2855 buff[offset + 2] = 0; // padding
2856 buff[offset + 3] = 0; // padding
9ff86fb7 2857
5a5f5ada
PO
2858 buff[offset + 4] = bpp; // bits-per-pixel
2859 buff[offset + 5] = depth; // depth
6d6f0db0 2860 buff[offset + 6] = 0; // little-endian
e7dec527 2861 buff[offset + 7] = trueColor ? 1 : 0; // true-color
9ff86fb7 2862
6d6f0db0 2863 buff[offset + 8] = 0; // red-max
5a5f5ada 2864 buff[offset + 9] = (1 << bits) - 1; // red-max
9ff86fb7 2865
6d6f0db0 2866 buff[offset + 10] = 0; // green-max
5a5f5ada 2867 buff[offset + 11] = (1 << bits) - 1; // green-max
9ff86fb7 2868
6d6f0db0 2869 buff[offset + 12] = 0; // blue-max
5a5f5ada 2870 buff[offset + 13] = (1 << bits) - 1; // blue-max
9ff86fb7 2871
6a19390b 2872 buff[offset + 14] = bits * 0; // red-shift
5a5f5ada 2873 buff[offset + 15] = bits * 1; // green-shift
6a19390b 2874 buff[offset + 16] = bits * 2; // blue-shift
9ff86fb7 2875
6d6f0db0
SR
2876 buff[offset + 17] = 0; // padding
2877 buff[offset + 18] = 0; // padding
2878 buff[offset + 19] = 0; // padding
9ff86fb7 2879
6d6f0db0
SR
2880 sock._sQlen += 20;
2881 sock.flush();
2882 },
9ff86fb7 2883
0e4808bf 2884 clientEncodings(sock, encodings) {
2b5f94fa
JD
2885 const buff = sock._sQ;
2886 const offset = sock._sQlen;
b1dee947 2887
6d6f0db0
SR
2888 buff[offset] = 2; // msg-type
2889 buff[offset + 1] = 0; // padding
b1dee947 2890
49a81837
PO
2891 buff[offset + 2] = encodings.length >> 8;
2892 buff[offset + 3] = encodings.length;
9ff86fb7 2893
2b5f94fa
JD
2894 let j = offset + 4;
2895 for (let i = 0; i < encodings.length; i++) {
2896 const enc = encodings[i];
49a81837
PO
2897 buff[j] = enc >> 24;
2898 buff[j + 1] = enc >> 16;
2899 buff[j + 2] = enc >> 8;
2900 buff[j + 3] = enc;
a7a89626 2901
49a81837
PO
2902 j += 4;
2903 }
a7a89626 2904
6d6f0db0
SR
2905 sock._sQlen += j - offset;
2906 sock.flush();
2907 },
a679a97d 2908
0e4808bf 2909 fbUpdateRequest(sock, incremental, x, y, w, h) {
2b5f94fa
JD
2910 const buff = sock._sQ;
2911 const offset = sock._sQlen;
9ff86fb7 2912
6d6f0db0
SR
2913 if (typeof(x) === "undefined") { x = 0; }
2914 if (typeof(y) === "undefined") { y = 0; }
b1dee947 2915
6d6f0db0
SR
2916 buff[offset] = 3; // msg-type
2917 buff[offset + 1] = incremental ? 1 : 0;
9ff86fb7 2918
6d6f0db0
SR
2919 buff[offset + 2] = (x >> 8) & 0xFF;
2920 buff[offset + 3] = x & 0xFF;
9ff86fb7 2921
6d6f0db0
SR
2922 buff[offset + 4] = (y >> 8) & 0xFF;
2923 buff[offset + 5] = y & 0xFF;
9ff86fb7 2924
6d6f0db0
SR
2925 buff[offset + 6] = (w >> 8) & 0xFF;
2926 buff[offset + 7] = w & 0xFF;
9ff86fb7 2927
6d6f0db0
SR
2928 buff[offset + 8] = (h >> 8) & 0xFF;
2929 buff[offset + 9] = h & 0xFF;
b1dee947 2930
6d6f0db0
SR
2931 sock._sQlen += 10;
2932 sock.flush();
85b35fc0
PO
2933 },
2934
0e4808bf 2935 xvpOp(sock, ver, op) {
2b5f94fa
JD
2936 const buff = sock._sQ;
2937 const offset = sock._sQlen;
85b35fc0
PO
2938
2939 buff[offset] = 250; // msg-type
2940 buff[offset + 1] = 0; // padding
2941
2942 buff[offset + 2] = ver;
2943 buff[offset + 3] = op;
2944
2945 sock._sQlen += 4;
2946 sock.flush();
6d6f0db0 2947 }
6d6f0db0 2948};
b1dee947 2949
4c38179d
AP
2950RFB.cursors = {
2951 none: {
2952 rgbaPixels: new Uint8Array(),
2953 w: 0, h: 0,
2954 hotx: 0, hoty: 0,
2955 },
2956
2957 dot: {
9881899e 2958 /* eslint-disable indent */
4c38179d
AP
2959 rgbaPixels: new Uint8Array([
2960 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2961 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2962 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2963 ]),
9881899e 2964 /* eslint-enable indent */
4c38179d
AP
2965 w: 3, h: 3,
2966 hotx: 1, hoty: 1,
2967 }
2968};