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