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