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