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