2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2020 The noVNC Authors
4 * Licensed under MPL 2.0 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
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 EventTargetMixin
from './util/eventtarget.js';
15 import Display
from "./display.js";
16 import Inflator
from "./inflator.js";
17 import Deflator
from "./deflator.js";
18 import Keyboard
from "./input/keyboard.js";
19 import Mouse
from "./input/mouse.js";
20 import Cursor
from "./util/cursor.js";
21 import Websock
from "./websock.js";
22 import DES
from "./des.js";
23 import KeyTable
from "./input/keysym.js";
24 import XtScancode
from "./input/xtscancodes.js";
25 import { encodings
} from "./encodings.js";
26 import "./util/polyfill.js";
28 import RawDecoder
from "./decoders/raw.js";
29 import CopyRectDecoder
from "./decoders/copyrect.js";
30 import RREDecoder
from "./decoders/rre.js";
31 import HextileDecoder
from "./decoders/hextile.js";
32 import TightDecoder
from "./decoders/tight.js";
33 import TightPNGDecoder
from "./decoders/tightpng.js";
35 // How many seconds to wait for a disconnect to finish
36 const DISCONNECT_TIMEOUT
= 3;
37 const DEFAULT_BACKGROUND
= 'rgb(40, 40, 40)';
39 // Extended clipboard pseudo-encoding formats
40 const extendedClipboardFormatText
= 1;
41 /*eslint-disable no-unused-vars */
42 const extendedClipboardFormatRtf
= 1 << 1;
43 const extendedClipboardFormatHtml
= 1 << 2;
44 const extendedClipboardFormatDib
= 1 << 3;
45 const extendedClipboardFormatFiles
= 1 << 4;
48 // Extended clipboard pseudo-encoding actions
49 const extendedClipboardActionCaps
= 1 << 24;
50 const extendedClipboardActionRequest
= 1 << 25;
51 const extendedClipboardActionPeek
= 1 << 26;
52 const extendedClipboardActionNotify
= 1 << 27;
53 const extendedClipboardActionProvide
= 1 << 28;
56 export default class RFB
extends EventTargetMixin
{
57 constructor(target
, url
, options
) {
59 throw new Error("Must specify target");
62 throw new Error("Must specify URL");
67 this._target
= target
;
71 options
= options
|| {};
72 this._rfbCredentials
= options
.credentials
|| {};
73 this._shared
= 'shared' in options
? !!options
.shared
: true;
74 this._repeaterID
= options
.repeaterID
|| '';
75 this._wsProtocols
= options
.wsProtocols
|| [];
78 this._rfbConnectionState
= '';
79 this._rfbInitState
= '';
80 this._rfbAuthScheme
= -1;
81 this._rfbCleanDisconnect
= true;
83 // Server capabilities
85 this._rfbMaxVersion
= 3.8;
86 this._rfbTightVNC
= false;
87 this._rfbVeNCryptState
= 0;
95 this._capabilities
= { power
: false };
97 this._supportsFence
= false;
99 this._supportsContinuousUpdates
= false;
100 this._enabledContinuousUpdates
= false;
102 this._supportsSetDesktopSize
= false;
104 this._screenFlags
= 0;
106 this._qemuExtKeyEventSupported
= false;
108 this._clipboardText
= null;
109 this._clipboardServerCapabilitiesActions
= {};
110 this._clipboardServerCapabilitiesFormats
= {};
113 this._sock
= null; // Websock object
114 this._display
= null; // Display object
115 this._flushing
= false; // Display flushing state
116 this._keyboard
= null; // Keyboard input handler object
117 this._mouse
= null; // Mouse input handler object
120 this._disconnTimer
= null; // disconnection timer
121 this._resizeTimeout
= null; // resize rate limiting
136 this._mouseButtonMask
= 0;
137 this._viewportDragging
= false;
138 this._viewportDragPos
= {};
139 this._viewportHasMoved
= false;
141 // Bound event handlers
142 this._eventHandlers
= {
143 focusCanvas
: this._focusCanvas
.bind(this),
144 windowResize
: this._windowResize
.bind(this),
148 Log
.Debug(">> RFB.constructor");
150 // Create DOM elements
151 this._screen
= document
.createElement('div');
152 this._screen
.style
.display
= 'flex';
153 this._screen
.style
.width
= '100%';
154 this._screen
.style
.height
= '100%';
155 this._screen
.style
.overflow
= 'auto';
156 this._screen
.style
.background
= DEFAULT_BACKGROUND
;
157 this._canvas
= document
.createElement('canvas');
158 this._canvas
.style
.margin
= 'auto';
159 // Some browsers add an outline on focus
160 this._canvas
.style
.outline
= 'none';
161 // IE miscalculates width without this :(
162 this._canvas
.style
.flexShrink
= '0';
163 this._canvas
.width
= 0;
164 this._canvas
.height
= 0;
165 this._canvas
.tabIndex
= -1;
166 this._screen
.appendChild(this._canvas
);
169 this._cursor
= new Cursor();
171 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
172 // it. Result: no cursor at all until a window border or an edit field
173 // is hit blindly. But there are also VNC servers that draw the cursor
174 // in the framebuffer and don't send the empty local cursor. There is
175 // no way to satisfy both sides.
177 // The spec is unclear on this "initial cursor" issue. Many other
178 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
179 // initial cursor instead.
180 this._cursorImage
= RFB
.cursors
.none
;
182 // populate decoder array with objects
183 this._decoders
[encodings
.encodingRaw
] = new RawDecoder();
184 this._decoders
[encodings
.encodingCopyRect
] = new CopyRectDecoder();
185 this._decoders
[encodings
.encodingRRE
] = new RREDecoder();
186 this._decoders
[encodings
.encodingHextile
] = new HextileDecoder();
187 this._decoders
[encodings
.encodingTight
] = new TightDecoder();
188 this._decoders
[encodings
.encodingTightPNG
] = new TightPNGDecoder();
190 // NB: nothing that needs explicit teardown should be done
191 // before this point, since this can throw an exception
193 this._display
= new Display(this._canvas
);
195 Log
.Error("Display exception: " + exc
);
198 this._display
.onflush
= this._onFlush
.bind(this);
200 this._keyboard
= new Keyboard(this._canvas
);
201 this._keyboard
.onkeyevent
= this._handleKeyEvent
.bind(this);
203 this._mouse
= new Mouse(this._canvas
);
204 this._mouse
.onmousebutton
= this._handleMouseButton
.bind(this);
205 this._mouse
.onmousemove
= this._handleMouseMove
.bind(this);
207 this._sock
= new Websock();
208 this._sock
.on('message', () => {
209 this._handleMessage();
211 this._sock
.on('open', () => {
212 if ((this._rfbConnectionState
=== 'connecting') &&
213 (this._rfbInitState
=== '')) {
214 this._rfbInitState
= 'ProtocolVersion';
215 Log
.Debug("Starting VNC handshake");
217 this._fail("Unexpected server connection while " +
218 this._rfbConnectionState
);
221 this._sock
.on('close', (e
) => {
222 Log
.Debug("WebSocket on-close event");
225 msg
= "(code: " + e
.code
;
227 msg
+= ", reason: " + e
.reason
;
231 switch (this._rfbConnectionState
) {
233 this._fail("Connection closed " + msg
);
236 // Handle disconnects that were initiated server-side
237 this._updateConnectionState('disconnecting');
238 this._updateConnectionState('disconnected');
240 case 'disconnecting':
241 // Normal disconnection path
242 this._updateConnectionState('disconnected');
245 this._fail("Unexpected server disconnect " +
246 "when already disconnected " + msg
);
249 this._fail("Unexpected server disconnect before connecting " +
253 this._sock
.off('close');
255 this._sock
.on('error', e
=> Log
.Warn("WebSocket on-error event"));
257 // Slight delay of the actual connection so that the caller has
258 // time to set up callbacks
259 setTimeout(this._updateConnectionState
.bind(this, 'connecting'));
261 Log
.Debug("<< RFB.constructor");
263 // ===== PROPERTIES =====
265 this.dragViewport
= false;
266 this.focusOnClick
= true;
268 this._viewOnly
= false;
269 this._clipViewport
= false;
270 this._scaleViewport
= false;
271 this._resizeSession
= false;
273 this._showDotCursor
= false;
274 if (options
.showDotCursor
!== undefined) {
275 Log
.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
276 this._showDotCursor
= options
.showDotCursor
;
279 this._qualityLevel
= 6;
280 this._compressionLevel
= 2;
283 // ===== PROPERTIES =====
285 get viewOnly() { return this._viewOnly
; }
286 set viewOnly(viewOnly
) {
287 this._viewOnly
= viewOnly
;
289 if (this._rfbConnectionState
=== "connecting" ||
290 this._rfbConnectionState
=== "connected") {
292 this._keyboard
.ungrab();
293 this._mouse
.ungrab();
295 this._keyboard
.grab();
301 get capabilities() { return this._capabilities
; }
303 get touchButton() { return this._mouse
.touchButton
; }
304 set touchButton(button
) { this._mouse
.touchButton
= button
; }
306 get clipViewport() { return this._clipViewport
; }
307 set clipViewport(viewport
) {
308 this._clipViewport
= viewport
;
312 get scaleViewport() { return this._scaleViewport
; }
313 set scaleViewport(scale
) {
314 this._scaleViewport
= scale
;
315 // Scaling trumps clipping, so we may need to adjust
316 // clipping when enabling or disabling scaling
317 if (scale
&& this._clipViewport
) {
321 if (!scale
&& this._clipViewport
) {
326 get resizeSession() { return this._resizeSession
; }
327 set resizeSession(resize
) {
328 this._resizeSession
= resize
;
330 this._requestRemoteResize();
334 get showDotCursor() { return this._showDotCursor
; }
335 set showDotCursor(show
) {
336 this._showDotCursor
= show
;
337 this._refreshCursor();
340 get background() { return this._screen
.style
.background
; }
341 set background(cssValue
) { this._screen
.style
.background
= cssValue
; }
344 return this._qualityLevel
;
346 set qualityLevel(qualityLevel
) {
347 if (!Number
.isInteger(qualityLevel
) || qualityLevel
< 0 || qualityLevel
> 9) {
348 Log
.Error("qualityLevel must be an integer between 0 and 9");
352 if (this._qualityLevel
=== qualityLevel
) {
356 this._qualityLevel
= qualityLevel
;
358 if (this._rfbConnectionState
=== 'connected') {
359 this._sendEncodings();
363 get compressionLevel() {
364 return this._compressionLevel
;
366 set compressionLevel(compressionLevel
) {
367 if (!Number
.isInteger(compressionLevel
) || compressionLevel
< 0 || compressionLevel
> 9) {
368 Log
.Error("compressionLevel must be an integer between 0 and 9");
372 if (this._compressionLevel
=== compressionLevel
) {
376 this._compressionLevel
= compressionLevel
;
378 if (this._rfbConnectionState
=== 'connected') {
379 this._sendEncodings();
383 // ===== PUBLIC METHODS =====
386 this._updateConnectionState('disconnecting');
387 this._sock
.off('error');
388 this._sock
.off('message');
389 this._sock
.off('open');
392 sendCredentials(creds
) {
393 this._rfbCredentials
= creds
;
394 setTimeout(this._initMsg
.bind(this), 0);
398 if (this._rfbConnectionState
!== 'connected' || this._viewOnly
) { return; }
399 Log
.Info("Sending Ctrl-Alt-Del");
401 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
402 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
403 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
404 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
405 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
406 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
421 // Send a key press. If 'down' is not specified then send a down key
422 // followed by an up key.
423 sendKey(keysym
, code
, down
) {
424 if (this._rfbConnectionState
!== 'connected' || this._viewOnly
) { return; }
426 if (down
=== undefined) {
427 this.sendKey(keysym
, code
, true);
428 this.sendKey(keysym
, code
, false);
432 const scancode
= XtScancode
[code
];
434 if (this._qemuExtKeyEventSupported
&& scancode
) {
436 keysym
= keysym
|| 0;
438 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
440 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
445 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
446 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
451 this._canvas
.focus();
458 clipboardPasteFrom(text
) {
459 if (this._rfbConnectionState
!== 'connected' || this._viewOnly
) { return; }
461 if (this._clipboardServerCapabilitiesFormats
[extendedClipboardFormatText
] &&
462 this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
464 this._clipboardText
= text
;
465 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
467 let data
= new Uint8Array(text
.length
);
468 for (let i
= 0; i
< text
.length
; i
++) {
469 // FIXME: text can have values outside of Latin1/Uint8
470 data
[i
] = text
.charCodeAt(i
);
473 RFB
.messages
.clientCutText(this._sock
, data
);
477 // ===== PRIVATE METHODS =====
480 Log
.Debug(">> RFB.connect");
482 Log
.Info("connecting to " + this._url
);
485 // WebSocket.onopen transitions to the RFB init states
486 this._sock
.open(this._url
, this._wsProtocols
);
488 if (e
.name
=== 'SyntaxError') {
489 this._fail("Invalid host or port (" + e
+ ")");
491 this._fail("Error when opening socket (" + e
+ ")");
495 // Make our elements part of the page
496 this._target
.appendChild(this._screen
);
498 this._cursor
.attach(this._canvas
);
499 this._refreshCursor();
501 // Monitor size changes of the screen
502 // FIXME: Use ResizeObserver, or hidden overflow
503 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
505 // Always grab focus on some kind of click event
506 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
507 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
509 Log
.Debug("<< RFB.connect");
513 Log
.Debug(">> RFB.disconnect");
514 this._cursor
.detach();
515 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
516 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
517 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
518 this._keyboard
.ungrab();
519 this._mouse
.ungrab();
522 this._target
.removeChild(this._screen
);
524 if (e
.name
=== 'NotFoundError') {
525 // Some cases where the initial connection fails
526 // can disconnect before the _screen is created
531 clearTimeout(this._resizeTimeout
);
532 Log
.Debug("<< RFB.disconnect");
535 _focusCanvas(event
) {
536 // Respect earlier handlers' request to not do side-effects
537 if (event
.defaultPrevented
) {
541 if (!this.focusOnClick
) {
548 _setDesktopName(name
) {
550 this.dispatchEvent(new CustomEvent(
552 { detail
: { name
: this._fbName
} }));
555 _windowResize(event
) {
556 // If the window resized then our screen element might have
557 // as well. Update the viewport dimensions.
558 window
.requestAnimationFrame(() => {
563 if (this._resizeSession
) {
564 // Request changing the resolution of the remote display to
565 // the size of the local browser viewport.
567 // In order to not send multiple requests before the browser-resize
568 // is finished we wait 0.5 seconds before sending the request.
569 clearTimeout(this._resizeTimeout
);
570 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
574 // Update state of clipping in Display object, and make sure the
575 // configured viewport matches the current screen size
577 const curClip
= this._display
.clipViewport
;
578 let newClip
= this._clipViewport
;
580 if (this._scaleViewport
) {
581 // Disable viewport clipping if we are scaling
585 if (curClip
!== newClip
) {
586 this._display
.clipViewport
= newClip
;
590 // When clipping is enabled, the screen is limited to
591 // the size of the container.
592 const size
= this._screenSize();
593 this._display
.viewportChangeSize(size
.w
, size
.h
);
594 this._fixScrollbars();
599 if (!this._scaleViewport
) {
600 this._display
.scale
= 1.0;
602 const size
= this._screenSize();
603 this._display
.autoscale(size
.w
, size
.h
);
605 this._fixScrollbars();
608 // Requests a change of remote desktop size. This message is an extension
609 // and may only be sent if we have received an ExtendedDesktopSize message
610 _requestRemoteResize() {
611 clearTimeout(this._resizeTimeout
);
612 this._resizeTimeout
= null;
614 if (!this._resizeSession
|| this._viewOnly
||
615 !this._supportsSetDesktopSize
) {
619 const size
= this._screenSize();
620 RFB
.messages
.setDesktopSize(this._sock
,
621 Math
.floor(size
.w
), Math
.floor(size
.h
),
622 this._screenID
, this._screenFlags
);
624 Log
.Debug('Requested new desktop size: ' +
625 size
.w
+ 'x' + size
.h
);
628 // Gets the the size of the available screen
630 let r
= this._screen
.getBoundingClientRect();
631 return { w
: r
.width
, h
: r
.height
};
635 // This is a hack because Chrome screws up the calculation
636 // for when scrollbars are needed. So to fix it we temporarily
637 // toggle them off and on.
638 const orig
= this._screen
.style
.overflow
;
639 this._screen
.style
.overflow
= 'hidden';
640 // Force Chrome to recalculate the layout by asking for
641 // an element's dimensions
642 this._screen
.getBoundingClientRect();
643 this._screen
.style
.overflow
= orig
;
651 * disconnected - permanent state
653 _updateConnectionState(state
) {
654 const oldstate
= this._rfbConnectionState
;
656 if (state
=== oldstate
) {
657 Log
.Debug("Already in state '" + state
+ "', ignoring");
661 // The 'disconnected' state is permanent for each RFB object
662 if (oldstate
=== 'disconnected') {
663 Log
.Error("Tried changing state of a disconnected RFB object");
667 // Ensure proper transitions before doing anything
670 if (oldstate
!== 'connecting') {
671 Log
.Error("Bad transition to connected state, " +
672 "previous connection state: " + oldstate
);
678 if (oldstate
!== 'disconnecting') {
679 Log
.Error("Bad transition to disconnected state, " +
680 "previous connection state: " + oldstate
);
686 if (oldstate
!== '') {
687 Log
.Error("Bad transition to connecting state, " +
688 "previous connection state: " + oldstate
);
693 case 'disconnecting':
694 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
695 Log
.Error("Bad transition to disconnecting state, " +
696 "previous connection state: " + oldstate
);
702 Log
.Error("Unknown connection state: " + state
);
706 // State change actions
708 this._rfbConnectionState
= state
;
710 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
712 if (this._disconnTimer
&& state
!== 'disconnecting') {
713 Log
.Debug("Clearing disconnect timer");
714 clearTimeout(this._disconnTimer
);
715 this._disconnTimer
= null;
717 // make sure we don't get a double event
718 this._sock
.off('close');
727 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
730 case 'disconnecting':
733 this._disconnTimer
= setTimeout(() => {
734 Log
.Error("Disconnection timed out.");
735 this._updateConnectionState('disconnected');
736 }, DISCONNECT_TIMEOUT
* 1000);
740 this.dispatchEvent(new CustomEvent(
741 "disconnect", { detail
:
742 { clean
: this._rfbCleanDisconnect
} }));
747 /* Print errors and disconnect
749 * The parameter 'details' is used for information that
750 * should be logged but not sent to the user interface.
753 switch (this._rfbConnectionState
) {
754 case 'disconnecting':
755 Log
.Error("Failed when disconnecting: " + details
);
758 Log
.Error("Failed while connected: " + details
);
761 Log
.Error("Failed when connecting: " + details
);
764 Log
.Error("RFB failure: " + details
);
767 this._rfbCleanDisconnect
= false; //This is sent to the UI
769 // Transition to disconnected without waiting for socket to close
770 this._updateConnectionState('disconnecting');
771 this._updateConnectionState('disconnected');
776 _setCapability(cap
, val
) {
777 this._capabilities
[cap
] = val
;
778 this.dispatchEvent(new CustomEvent("capabilities",
779 { detail
: { capabilities
: this._capabilities
} }));
783 if (this._sock
.rQlen
=== 0) {
784 Log
.Warn("handleMessage called on an empty receive queue");
788 switch (this._rfbConnectionState
) {
790 Log
.Error("Got data while disconnected");
794 if (this._flushing
) {
797 if (!this._normalMsg()) {
800 if (this._sock
.rQlen
=== 0) {
811 _handleKeyEvent(keysym
, code
, down
) {
812 this.sendKey(keysym
, code
, down
);
815 _handleMouseButton(x
, y
, down
, bmask
) {
817 this._mouseButtonMask
|= bmask
;
819 this._mouseButtonMask
&= ~bmask
;
822 if (this.dragViewport
) {
823 if (down
&& !this._viewportDragging
) {
824 this._viewportDragging
= true;
825 this._viewportDragPos
= {'x': x
, 'y': y
};
826 this._viewportHasMoved
= false;
828 // Skip sending mouse events
831 this._viewportDragging
= false;
833 // If we actually performed a drag then we are done
834 // here and should not send any mouse events
835 if (this._viewportHasMoved
) {
839 if (this._viewOnly
) { return; }
841 // Otherwise we treat this as a mouse click event.
842 // Send the button down event here, as the button up
843 // event is sent at the end of this function.
844 RFB
.messages
.pointerEvent(this._sock
,
845 this._display
.absX(x
),
846 this._display
.absY(y
),
851 if (this._viewOnly
) { return; } // View only, skip mouse events
853 if (this._rfbConnectionState
!== 'connected') { return; }
854 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouseButtonMask
);
857 _handleMouseMove(x
, y
) {
858 if (this._viewportDragging
) {
859 const deltaX
= this._viewportDragPos
.x
- x
;
860 const deltaY
= this._viewportDragPos
.y
- y
;
862 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
863 Math
.abs(deltaY
) > dragThreshold
)) {
864 this._viewportHasMoved
= true;
866 this._viewportDragPos
= {'x': x
, 'y': y
};
867 this._display
.viewportChangePos(deltaX
, deltaY
);
870 // Skip sending mouse events
874 if (this._viewOnly
) { return; } // View only, skip mouse events
876 if (this._rfbConnectionState
!== 'connected') { return; }
877 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouseButtonMask
);
882 _negotiateProtocolVersion() {
883 if (this._sock
.rQwait("version", 12)) {
887 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
888 Log
.Info("Server ProtocolVersion: " + sversion
);
891 case "000.000": // UltraVNC repeater
895 case "003.006": // UltraVNC
896 case "003.889": // Apple Remote Desktop
897 this._rfbVersion
= 3.3;
900 this._rfbVersion
= 3.7;
903 case "004.000": // Intel AMT KVM
904 case "004.001": // RealVNC 4.6
905 case "005.000": // RealVNC 5.3
906 this._rfbVersion
= 3.8;
909 return this._fail("Invalid server version " + sversion
);
913 let repeaterID
= "ID:" + this._repeaterID
;
914 while (repeaterID
.length
< 250) {
917 this._sock
.send_string(repeaterID
);
921 if (this._rfbVersion
> this._rfbMaxVersion
) {
922 this._rfbVersion
= this._rfbMaxVersion
;
925 const cversion
= "00" + parseInt(this._rfbVersion
, 10) +
926 ".00" + ((this._rfbVersion
* 10) % 10);
927 this._sock
.send_string("RFB " + cversion
+ "\n");
928 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
930 this._rfbInitState
= 'Security';
933 _negotiateSecurity() {
934 // Polyfill since IE and PhantomJS doesn't have
935 // TypedArray.includes()
936 function includes(item
, array
) {
937 for (let i
= 0; i
< array
.length
; i
++) {
938 if (array
[i
] === item
) {
945 if (this._rfbVersion
>= 3.7) {
946 // Server sends supported list, client decides
947 const numTypes
= this._sock
.rQshift8();
948 if (this._sock
.rQwait("security type", numTypes
, 1)) { return false; }
950 if (numTypes
=== 0) {
951 this._rfbInitState
= "SecurityReason";
952 this._securityContext
= "no security types";
953 this._securityStatus
= 1;
954 return this._initMsg();
957 const types
= this._sock
.rQshiftBytes(numTypes
);
958 Log
.Debug("Server security types: " + types
);
960 // Look for each auth in preferred order
961 if (includes(1, types
)) {
962 this._rfbAuthScheme
= 1; // None
963 } else if (includes(22, types
)) {
964 this._rfbAuthScheme
= 22; // XVP
965 } else if (includes(16, types
)) {
966 this._rfbAuthScheme
= 16; // Tight
967 } else if (includes(2, types
)) {
968 this._rfbAuthScheme
= 2; // VNC Auth
969 } else if (includes(19, types
)) {
970 this._rfbAuthScheme
= 19; // VeNCrypt Auth
972 return this._fail("Unsupported security types (types: " + types
+ ")");
975 this._sock
.send([this._rfbAuthScheme
]);
978 if (this._sock
.rQwait("security scheme", 4)) { return false; }
979 this._rfbAuthScheme
= this._sock
.rQshift32();
981 if (this._rfbAuthScheme
== 0) {
982 this._rfbInitState
= "SecurityReason";
983 this._securityContext
= "authentication scheme";
984 this._securityStatus
= 1;
985 return this._initMsg();
989 this._rfbInitState
= 'Authentication';
990 Log
.Debug('Authenticating using scheme: ' + this._rfbAuthScheme
);
992 return this._initMsg(); // jump to authentication
995 _handleSecurityReason() {
996 if (this._sock
.rQwait("reason length", 4)) {
999 const strlen
= this._sock
.rQshift32();
1003 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
1004 reason
= this._sock
.rQshiftStr(strlen
);
1007 if (reason
!== "") {
1008 this.dispatchEvent(new CustomEvent(
1010 { detail
: { status
: this._securityStatus
,
1011 reason
: reason
} }));
1013 return this._fail("Security negotiation failed on " +
1014 this._securityContext
+
1015 " (reason: " + reason
+ ")");
1017 this.dispatchEvent(new CustomEvent(
1019 { detail
: { status
: this._securityStatus
} }));
1021 return this._fail("Security negotiation failed on " +
1022 this._securityContext
);
1027 _negotiateXvpAuth() {
1028 if (this._rfbCredentials
.username
=== undefined ||
1029 this._rfbCredentials
.password
=== undefined ||
1030 this._rfbCredentials
.target
=== undefined) {
1031 this.dispatchEvent(new CustomEvent(
1032 "credentialsrequired",
1033 { detail
: { types
: ["username", "password", "target"] } }));
1037 const xvpAuthStr
= String
.fromCharCode(this._rfbCredentials
.username
.length
) +
1038 String
.fromCharCode(this._rfbCredentials
.target
.length
) +
1039 this._rfbCredentials
.username
+
1040 this._rfbCredentials
.target
;
1041 this._sock
.send_string(xvpAuthStr
);
1042 this._rfbAuthScheme
= 2;
1043 return this._negotiateAuthentication();
1046 // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
1047 _negotiateVeNCryptAuth() {
1049 // waiting for VeNCrypt version
1050 if (this._rfbVeNCryptState
== 0) {
1051 if (this._sock
.rQwait("vencrypt version", 2)) { return false; }
1053 const major
= this._sock
.rQshift8();
1054 const minor
= this._sock
.rQshift8();
1056 if (!(major
== 0 && minor
== 2)) {
1057 return this._fail("Unsupported VeNCrypt version " + major
+ "." + minor
);
1060 this._sock
.send([0, 2]);
1061 this._rfbVeNCryptState
= 1;
1065 if (this._rfbVeNCryptState
== 1) {
1066 if (this._sock
.rQwait("vencrypt ack", 1)) { return false; }
1068 const res
= this._sock
.rQshift8();
1071 return this._fail("VeNCrypt failure " + res
);
1074 this._rfbVeNCryptState
= 2;
1076 // must fall through here (i.e. no "else if"), beacause we may have already received
1077 // the subtypes length and won't be called again
1079 if (this._rfbVeNCryptState
== 2) { // waiting for subtypes length
1080 if (this._sock
.rQwait("vencrypt subtypes length", 1)) { return false; }
1082 const subtypesLength
= this._sock
.rQshift8();
1083 if (subtypesLength
< 1) {
1084 return this._fail("VeNCrypt subtypes empty");
1087 this._rfbVeNCryptSubtypesLength
= subtypesLength
;
1088 this._rfbVeNCryptState
= 3;
1091 // waiting for subtypes list
1092 if (this._rfbVeNCryptState
== 3) {
1093 if (this._sock
.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength
)) { return false; }
1095 const subtypes
= [];
1096 for (let i
= 0; i
< this._rfbVeNCryptSubtypesLength
; i
++) {
1097 subtypes
.push(this._sock
.rQshift32());
1100 // 256 = Plain subtype
1101 if (subtypes
.indexOf(256) != -1) {
1103 this._sock
.send([0, 0, 1, 0]);
1104 this._rfbVeNCryptState
= 4;
1106 return this._fail("VeNCrypt Plain subtype not offered by server");
1110 // negotiated Plain subtype, server waits for password
1111 if (this._rfbVeNCryptState
== 4) {
1112 if (!this._rfbCredentials
.username
||
1113 !this._rfbCredentials
.password
) {
1114 this.dispatchEvent(new CustomEvent(
1115 "credentialsrequired",
1116 { detail
: { types
: ["username", "password"] } }));
1120 const user
= encodeUTF8(this._rfbCredentials
.username
);
1121 const pass
= encodeUTF8(this._rfbCredentials
.password
);
1123 // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1124 this._sock
.send([0, 0, 0, user
.length
]);
1125 this._sock
.send([0, 0, 0, pass
.length
]);
1126 this._sock
.send_string(user
);
1127 this._sock
.send_string(pass
);
1129 this._rfbInitState
= "SecurityResult";
1134 _negotiateStdVNCAuth() {
1135 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
1137 if (this._rfbCredentials
.password
=== undefined) {
1138 this.dispatchEvent(new CustomEvent(
1139 "credentialsrequired",
1140 { detail
: { types
: ["password"] } }));
1144 // TODO(directxman12): make genDES not require an Array
1145 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
1146 const response
= RFB
.genDES(this._rfbCredentials
.password
, challenge
);
1147 this._sock
.send(response
);
1148 this._rfbInitState
= "SecurityResult";
1152 _negotiateTightUnixAuth() {
1153 if (this._rfbCredentials
.username
=== undefined ||
1154 this._rfbCredentials
.password
=== undefined) {
1155 this.dispatchEvent(new CustomEvent(
1156 "credentialsrequired",
1157 { detail
: { types
: ["username", "password"] } }));
1161 this._sock
.send([0, 0, 0, this._rfbCredentials
.username
.length
]);
1162 this._sock
.send([0, 0, 0, this._rfbCredentials
.password
.length
]);
1163 this._sock
.send_string(this._rfbCredentials
.username
);
1164 this._sock
.send_string(this._rfbCredentials
.password
);
1165 this._rfbInitState
= "SecurityResult";
1169 _negotiateTightTunnels(numTunnels
) {
1170 const clientSupportedTunnelTypes
= {
1171 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
1173 const serverSupportedTunnelTypes
= {};
1174 // receive tunnel capabilities
1175 for (let i
= 0; i
< numTunnels
; i
++) {
1176 const capCode
= this._sock
.rQshift32();
1177 const capVendor
= this._sock
.rQshiftStr(4);
1178 const capSignature
= this._sock
.rQshiftStr(8);
1179 serverSupportedTunnelTypes
[capCode
] = { vendor
: capVendor
, signature
: capSignature
};
1182 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
1184 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1185 // but forgets to advertise it. Try to detect such servers by
1186 // looking for their custom tunnel type.
1187 if (serverSupportedTunnelTypes
[1] &&
1188 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
1189 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
1190 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1191 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
1194 // choose the notunnel type
1195 if (serverSupportedTunnelTypes
[0]) {
1196 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
1197 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
1198 return this._fail("Client's tunnel type had the incorrect " +
1199 "vendor or signature");
1201 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1202 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1203 return false; // wait until we receive the sub auth count to continue
1205 return this._fail("Server wanted tunnels, but doesn't support " +
1206 "the notunnel type");
1210 _negotiateTightAuth() {
1211 if (!this._rfbTightVNC
) { // first pass, do the tunnel negotiation
1212 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1213 const numTunnels
= this._sock
.rQshift32();
1214 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1216 this._rfbTightVNC
= true;
1218 if (numTunnels
> 0) {
1219 this._negotiateTightTunnels(numTunnels
);
1220 return false; // wait until we receive the sub auth to continue
1224 // second pass, do the sub-auth negotiation
1225 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1226 const subAuthCount
= this._sock
.rQshift32();
1227 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1228 this._rfbInitState
= 'SecurityResult';
1232 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1234 const clientSupportedTypes
= {
1240 const serverSupportedTypes
= [];
1242 for (let i
= 0; i
< subAuthCount
; i
++) {
1243 this._sock
.rQshift32(); // capNum
1244 const capabilities
= this._sock
.rQshiftStr(12);
1245 serverSupportedTypes
.push(capabilities
);
1248 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1250 for (let authType
in clientSupportedTypes
) {
1251 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1252 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1253 Log
.Debug("Selected authentication type: " + authType
);
1256 case 'STDVNOAUTH__': // no auth
1257 this._rfbInitState
= 'SecurityResult';
1259 case 'STDVVNCAUTH_': // VNC auth
1260 this._rfbAuthScheme
= 2;
1261 return this._initMsg();
1262 case 'TGHTULGNAUTH': // UNIX auth
1263 this._rfbAuthScheme
= 129;
1264 return this._initMsg();
1266 return this._fail("Unsupported tiny auth scheme " +
1267 "(scheme: " + authType
+ ")");
1272 return this._fail("No supported sub-auth types!");
1275 _negotiateAuthentication() {
1276 switch (this._rfbAuthScheme
) {
1278 if (this._rfbVersion
>= 3.8) {
1279 this._rfbInitState
= 'SecurityResult';
1282 this._rfbInitState
= 'ClientInitialisation';
1283 return this._initMsg();
1285 case 22: // XVP auth
1286 return this._negotiateXvpAuth();
1288 case 2: // VNC authentication
1289 return this._negotiateStdVNCAuth();
1291 case 16: // TightVNC Security Type
1292 return this._negotiateTightAuth();
1294 case 19: // VeNCrypt Security Type
1295 return this._negotiateVeNCryptAuth();
1297 case 129: // TightVNC UNIX Security Type
1298 return this._negotiateTightUnixAuth();
1301 return this._fail("Unsupported auth scheme (scheme: " +
1302 this._rfbAuthScheme
+ ")");
1306 _handleSecurityResult() {
1307 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1309 const status
= this._sock
.rQshift32();
1311 if (status
=== 0) { // OK
1312 this._rfbInitState
= 'ClientInitialisation';
1313 Log
.Debug('Authentication OK');
1314 return this._initMsg();
1316 if (this._rfbVersion
>= 3.8) {
1317 this._rfbInitState
= "SecurityReason";
1318 this._securityContext
= "security result";
1319 this._securityStatus
= status
;
1320 return this._initMsg();
1322 this.dispatchEvent(new CustomEvent(
1324 { detail
: { status
: status
} }));
1326 return this._fail("Security handshake failed");
1331 _negotiateServerInit() {
1332 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1335 const width
= this._sock
.rQshift16();
1336 const height
= this._sock
.rQshift16();
1339 const bpp
= this._sock
.rQshift8();
1340 const depth
= this._sock
.rQshift8();
1341 const bigEndian
= this._sock
.rQshift8();
1342 const trueColor
= this._sock
.rQshift8();
1344 const redMax
= this._sock
.rQshift16();
1345 const greenMax
= this._sock
.rQshift16();
1346 const blueMax
= this._sock
.rQshift16();
1347 const redShift
= this._sock
.rQshift8();
1348 const greenShift
= this._sock
.rQshift8();
1349 const blueShift
= this._sock
.rQshift8();
1350 this._sock
.rQskipBytes(3); // padding
1352 // NB(directxman12): we don't want to call any callbacks or print messages until
1353 // *after* we're past the point where we could backtrack
1355 /* Connection name/title */
1356 const nameLength = this._sock.rQshift32();
1357 if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
1358 let name = this._sock.rQshiftStr(nameLength);
1359 name = decodeUTF8(name, true);
1361 if (this._rfbTightVNC) {
1362 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
1363 // In TightVNC mode, ServerInit message is extended
1364 const numServerMessages = this._sock.rQshift16();
1365 const numClientMessages = this._sock.rQshift16();
1366 const numEncodings = this._sock.rQshift16();
1367 this._sock.rQskipBytes(2); // padding
1369 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1370 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
1372 // we don't actually do anything with the capability information that TIGHT sends,
1373 // so we just skip the all of this.
1375 // TIGHT server message capabilities
1376 this._sock.rQskipBytes(16 * numServerMessages);
1378 // TIGHT client message capabilities
1379 this._sock.rQskipBytes(16 * numClientMessages);
1381 // TIGHT encoding capabilities
1382 this._sock.rQskipBytes(16 * numEncodings);
1385 // NB(directxman12): these are down here so that we don't run them multiple times
1387 Log.Info("Screen: " + width + "x" + height +
1388 ", bpp: " + bpp + ", depth: " + depth +
1389 ", bigEndian: " + bigEndian +
1390 ", trueColor: " + trueColor +
1391 ", redMax: " + redMax +
1392 ", greenMax: " + greenMax +
1393 ", blueMax: " + blueMax +
1394 ", redShift: " + redShift +
1395 ", greenShift: " + greenShift +
1396 ", blueShift: " + blueShift);
1398 // we're past the point where we could backtrack, so it's safe to call this
1399 this._setDesktopName(name);
1400 this._resize(width, height);
1402 if (!this._viewOnly) { this._keyboard.grab(); }
1403 if (!this._viewOnly) { this._mouse.grab(); }
1407 if (this._fbName === "Intel(r) AMT KVM") {
1408 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1412 RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
1413 this._sendEncodings();
1414 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
1416 this._updateConnectionState('connected');
1423 // In preference order
1424 encs.push(encodings.encodingCopyRect);
1425 // Only supported with full depth support
1426 if (this._fbDepth == 24) {
1427 encs.push(encodings.encodingTight);
1428 encs.push(encodings.encodingTightPNG);
1429 encs.push(encodings.encodingHextile);
1430 encs.push(encodings.encodingRRE);
1432 encs.push(encodings.encodingRaw);
1434 // Psuedo-encoding settings
1435 encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
1436 encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
1438 encs.push(encodings.pseudoEncodingDesktopSize);
1439 encs.push(encodings.pseudoEncodingLastRect);
1440 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1441 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1442 encs.push(encodings.pseudoEncodingXvp);
1443 encs.push(encodings.pseudoEncodingFence);
1444 encs.push(encodings.pseudoEncodingContinuousUpdates);
1445 encs.push(encodings.pseudoEncodingDesktopName);
1446 encs.push(encodings.pseudoEncodingExtendedClipboard);
1448 if (this._fbDepth == 24) {
1449 encs.push(encodings.pseudoEncodingVMwareCursor);
1450 encs.push(encodings.pseudoEncodingCursor);
1453 RFB.messages.clientEncodings(this._sock, encs);
1456 /* RFB protocol initialization states:
1461 * ClientInitialization - not triggered by server message
1462 * ServerInitialization
1465 switch (this._rfbInitState
) {
1466 case 'ProtocolVersion':
1467 return this._negotiateProtocolVersion();
1470 return this._negotiateSecurity();
1472 case 'Authentication':
1473 return this._negotiateAuthentication();
1475 case 'SecurityResult':
1476 return this._handleSecurityResult();
1478 case 'SecurityReason':
1479 return this._handleSecurityReason();
1481 case 'ClientInitialisation':
1482 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1483 this._rfbInitState
= 'ServerInitialisation';
1486 case 'ServerInitialisation':
1487 return this._negotiateServerInit();
1490 return this._fail("Unknown init state (state: " +
1491 this._rfbInitState
+ ")");
1495 _handleSetColourMapMsg() {
1496 Log
.Debug("SetColorMapEntries");
1498 return this._fail("Unexpected SetColorMapEntries message");
1501 _handleServerCutText() {
1502 Log
.Debug("ServerCutText");
1504 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1506 this._sock
.rQskipBytes(3); // Padding
1508 let length
= this._sock
.rQshift32();
1509 length
= toSigned32bit(length
);
1511 if (this._sock
.rQwait("ServerCutText content", Math
.abs(length
), 8)) { return false; }
1515 const text
= this._sock
.rQshiftStr(length
);
1516 if (this._viewOnly
) {
1520 this.dispatchEvent(new CustomEvent(
1522 { detail
: { text
: text
} }));
1526 length
= Math
.abs(length
);
1527 const flags
= this._sock
.rQshift32();
1528 let formats
= flags
& 0x0000FFFF;
1529 let actions
= flags
& 0xFF000000;
1531 let isCaps
= (!!(actions
& extendedClipboardActionCaps
));
1533 this._clipboardServerCapabilitiesFormats
= {};
1534 this._clipboardServerCapabilitiesActions
= {};
1536 // Update our server capabilities for Formats
1537 for (let i
= 0; i
<= 15; i
++) {
1540 // Check if format flag is set.
1541 if ((formats
& index
)) {
1542 this._clipboardServerCapabilitiesFormats
[index
] = true;
1543 // We don't send unsolicited clipboard, so we
1545 this._sock
.rQshift32();
1549 // Update our server capabilities for Actions
1550 for (let i
= 24; i
<= 31; i
++) {
1552 this._clipboardServerCapabilitiesActions
[index
] = !!(actions
& index
);
1555 /* Caps handling done, send caps with the clients
1556 capabilities set as a response */
1557 let clientActions
= [
1558 extendedClipboardActionCaps
,
1559 extendedClipboardActionRequest
,
1560 extendedClipboardActionPeek
,
1561 extendedClipboardActionNotify
,
1562 extendedClipboardActionProvide
1564 RFB
.messages
.extendedClipboardCaps(this._sock
, clientActions
, {extendedClipboardFormatText
: 0});
1566 } else if (actions
=== extendedClipboardActionRequest
) {
1567 if (this._viewOnly
) {
1571 // Check if server has told us it can handle Provide and there is clipboard data to send.
1572 if (this._clipboardText
!= null &&
1573 this._clipboardServerCapabilitiesActions
[extendedClipboardActionProvide
]) {
1575 if (formats
& extendedClipboardFormatText
) {
1576 RFB
.messages
.extendedClipboardProvide(this._sock
, [extendedClipboardFormatText
], [this._clipboardText
]);
1580 } else if (actions
=== extendedClipboardActionPeek
) {
1581 if (this._viewOnly
) {
1585 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
1587 if (this._clipboardText
!= null) {
1588 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
1590 RFB
.messages
.extendedClipboardNotify(this._sock
, []);
1594 } else if (actions
=== extendedClipboardActionNotify
) {
1595 if (this._viewOnly
) {
1599 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionRequest
]) {
1601 if (formats
& extendedClipboardFormatText
) {
1602 RFB
.messages
.extendedClipboardRequest(this._sock
, [extendedClipboardFormatText
]);
1606 } else if (actions
=== extendedClipboardActionProvide
) {
1607 if (this._viewOnly
) {
1611 if (!(formats
& extendedClipboardFormatText
)) {
1614 // Ignore what we had in our clipboard client side.
1615 this._clipboardText
= null;
1617 // FIXME: Should probably verify that this data was actually requested
1618 let zlibStream
= this._sock
.rQshiftBytes(length
- 4);
1619 let streamInflator
= new Inflator();
1620 let textData
= null;
1622 streamInflator
.setInput(zlibStream
);
1623 for (let i
= 0; i
<= 15; i
++) {
1624 let format
= 1 << i
;
1626 if (formats
& format
) {
1629 let sizeArray
= streamInflator
.inflate(4);
1631 size
|= (sizeArray
[0] << 24);
1632 size
|= (sizeArray
[1] << 16);
1633 size
|= (sizeArray
[2] << 8);
1634 size
|= (sizeArray
[3]);
1635 let chunk
= streamInflator
.inflate(size
);
1637 if (format
=== extendedClipboardFormatText
) {
1642 streamInflator
.setInput(null);
1644 if (textData
!== null) {
1646 for (let i
= 0; i
< textData
.length
; i
++) {
1647 tmpText
+= String
.fromCharCode(textData
[i
]);
1651 textData
= decodeUTF8(textData
);
1652 if ((textData
.length
> 0) && "\0" === textData
.charAt(textData
.length
- 1)) {
1653 textData
= textData
.slice(0, -1);
1656 textData
= textData
.replace("\r\n", "\n");
1658 this.dispatchEvent(new CustomEvent(
1660 { detail
: { text
: textData
} }));
1663 return this._fail("Unexpected action in extended clipboard message: " + actions
);
1669 _handleServerFenceMsg() {
1670 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1671 this._sock
.rQskipBytes(3); // Padding
1672 let flags
= this._sock
.rQshift32();
1673 let length
= this._sock
.rQshift8();
1675 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1678 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1682 const payload
= this._sock
.rQshiftStr(length
);
1684 this._supportsFence
= true;
1689 * (1<<0) - BlockBefore
1690 * (1<<1) - BlockAfter
1695 if (!(flags
& (1<<31))) {
1696 return this._fail("Unexpected fence response");
1699 // Filter out unsupported flags
1700 // FIXME: support syncNext
1701 flags
&= (1<<0) | (1<<1);
1703 // BlockBefore and BlockAfter are automatically handled by
1704 // the fact that we process each incoming message
1706 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1712 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1713 this._sock
.rQskipBytes(1); // Padding
1714 const xvpVer
= this._sock
.rQshift8();
1715 const xvpMsg
= this._sock
.rQshift8();
1719 Log
.Error("XVP Operation Failed");
1722 this._rfbXvpVer
= xvpVer
;
1723 Log
.Info("XVP extensions enabled (version " + this._rfbXvpVer
+ ")");
1724 this._setCapability("power", true);
1727 this._fail("Illegal server XVP message (msg: " + xvpMsg
+ ")");
1736 if (this._FBU
.rects
> 0) {
1739 msgType
= this._sock
.rQshift8();
1744 case 0: // FramebufferUpdate
1745 ret
= this._framebufferUpdate();
1746 if (ret
&& !this._enabledContinuousUpdates
) {
1747 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1748 this._fbWidth
, this._fbHeight
);
1752 case 1: // SetColorMapEntries
1753 return this._handleSetColourMapMsg();
1757 this.dispatchEvent(new CustomEvent(
1762 case 3: // ServerCutText
1763 return this._handleServerCutText();
1765 case 150: // EndOfContinuousUpdates
1766 first
= !this._supportsContinuousUpdates
;
1767 this._supportsContinuousUpdates
= true;
1768 this._enabledContinuousUpdates
= false;
1770 this._enabledContinuousUpdates
= true;
1771 this._updateContinuousUpdates();
1772 Log
.Info("Enabling continuous updates.");
1774 // FIXME: We need to send a framebufferupdaterequest here
1775 // if we add support for turning off continuous updates
1779 case 248: // ServerFence
1780 return this._handleServerFenceMsg();
1783 return this._handleXvpMsg();
1786 this._fail("Unexpected server message (type " + msgType
+ ")");
1787 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1793 this._flushing
= false;
1794 // Resume processing
1795 if (this._sock
.rQlen
> 0) {
1796 this._handleMessage();
1800 _framebufferUpdate() {
1801 if (this._FBU
.rects
=== 0) {
1802 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1803 this._sock
.rQskipBytes(1); // Padding
1804 this._FBU
.rects
= this._sock
.rQshift16();
1806 // Make sure the previous frame is fully rendered first
1807 // to avoid building up an excessive queue
1808 if (this._display
.pending()) {
1809 this._flushing
= true;
1810 this._display
.flush();
1815 while (this._FBU
.rects
> 0) {
1816 if (this._FBU
.encoding
=== null) {
1817 if (this._sock
.rQwait("rect header", 12)) { return false; }
1818 /* New FramebufferUpdate */
1820 const hdr
= this._sock
.rQshiftBytes(12);
1821 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1822 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1823 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1824 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1825 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1826 (hdr
[10] << 8) + hdr
[11], 10);
1829 if (!this._handleRect()) {
1834 this._FBU
.encoding
= null;
1837 this._display
.flip();
1839 return true; // We finished this FBU
1843 switch (this._FBU
.encoding
) {
1844 case encodings
.pseudoEncodingLastRect
:
1845 this._FBU
.rects
= 1; // Will be decreased when we return
1848 case encodings
.pseudoEncodingVMwareCursor
:
1849 return this._handleVMwareCursor();
1851 case encodings
.pseudoEncodingCursor
:
1852 return this._handleCursor();
1854 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1855 // Old Safari doesn't support creating keyboard events
1857 const keyboardEvent
= document
.createEvent("keyboardEvent");
1858 if (keyboardEvent
.code
!== undefined) {
1859 this._qemuExtKeyEventSupported
= true;
1866 case encodings
.pseudoEncodingDesktopName
:
1867 return this._handleDesktopName();
1869 case encodings
.pseudoEncodingDesktopSize
:
1870 this._resize(this._FBU
.width
, this._FBU
.height
);
1873 case encodings
.pseudoEncodingExtendedDesktopSize
:
1874 return this._handleExtendedDesktopSize();
1877 return this._handleDataRect();
1881 _handleVMwareCursor() {
1882 const hotx
= this._FBU
.x
; // hotspot-x
1883 const hoty
= this._FBU
.y
; // hotspot-y
1884 const w
= this._FBU
.width
;
1885 const h
= this._FBU
.height
;
1886 if (this._sock
.rQwait("VMware cursor encoding", 1)) {
1890 const cursorType
= this._sock
.rQshift8();
1892 this._sock
.rQshift8(); //Padding
1895 const bytesPerPixel
= 4;
1898 if (cursorType
== 0) {
1899 //Used to filter away unimportant bits.
1900 //OR is used for correct conversion in js.
1901 const PIXEL_MASK
= 0xffffff00 | 0;
1902 rgba
= new Array(w
* h
* bytesPerPixel
);
1904 if (this._sock
.rQwait("VMware cursor classic encoding",
1905 (w
* h
* bytesPerPixel
) * 2, 2)) {
1909 let andMask
= new Array(w
* h
);
1910 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1911 andMask
[pixel
] = this._sock
.rQshift32();
1914 let xorMask
= new Array(w
* h
);
1915 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1916 xorMask
[pixel
] = this._sock
.rQshift32();
1919 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1920 if (andMask
[pixel
] == 0) {
1921 //Fully opaque pixel
1922 let bgr
= xorMask
[pixel
];
1923 let r
= bgr
>> 8 & 0xff;
1924 let g
= bgr
>> 16 & 0xff;
1925 let b
= bgr
>> 24 & 0xff;
1927 rgba
[(pixel
* bytesPerPixel
) ] = r
; //r
1928 rgba
[(pixel
* bytesPerPixel
) + 1 ] = g
; //g
1929 rgba
[(pixel
* bytesPerPixel
) + 2 ] = b
; //b
1930 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff; //a
1932 } else if ((andMask
[pixel
] & PIXEL_MASK
) ==
1934 //Only screen value matters, no mouse colouring
1935 if (xorMask
[pixel
] == 0) {
1937 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1938 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1939 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1940 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0x00;
1942 } else if ((xorMask
[pixel
] & PIXEL_MASK
) ==
1944 //Inverted pixel, not supported in browsers.
1945 //Fully opaque instead.
1946 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1947 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1948 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1949 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1953 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1954 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1955 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1956 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1961 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1962 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1963 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1964 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1969 } else if (cursorType
== 1) {
1970 if (this._sock
.rQwait("VMware cursor alpha encoding",
1975 rgba
= new Array(w
* h
* bytesPerPixel
);
1977 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1978 let data
= this._sock
.rQshift32();
1980 rgba
[(pixel
* 4) ] = data
>> 24 & 0xff; //r
1981 rgba
[(pixel
* 4) + 1 ] = data
>> 16 & 0xff; //g
1982 rgba
[(pixel
* 4) + 2 ] = data
>> 8 & 0xff; //b
1983 rgba
[(pixel
* 4) + 3 ] = data
& 0xff; //a
1987 Log
.Warn("The given cursor type is not supported: "
1988 + cursorType
+ " given.");
1992 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1998 const hotx
= this._FBU
.x
; // hotspot-x
1999 const hoty
= this._FBU
.y
; // hotspot-y
2000 const w
= this._FBU
.width
;
2001 const h
= this._FBU
.height
;
2003 const pixelslength
= w
* h
* 4;
2004 const masklength
= Math
.ceil(w
/ 8) * h
;
2006 let bytes
= pixelslength
+ masklength
;
2007 if (this._sock
.rQwait("cursor encoding", bytes
)) {
2011 // Decode from BGRX pixels + bit mask to RGBA
2012 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
2013 const mask
= this._sock
.rQshiftBytes(masklength
);
2014 let rgba
= new Uint8Array(w
* h
* 4);
2017 for (let y
= 0; y
< h
; y
++) {
2018 for (let x
= 0; x
< w
; x
++) {
2019 let maskIdx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
2020 let alpha
= (mask
[maskIdx
] << (x
% 8)) & 0x80 ? 255 : 0;
2021 rgba
[pixIdx
] = pixels
[pixIdx
+ 2];
2022 rgba
[pixIdx
+ 1] = pixels
[pixIdx
+ 1];
2023 rgba
[pixIdx
+ 2] = pixels
[pixIdx
];
2024 rgba
[pixIdx
+ 3] = alpha
;
2029 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
2034 _handleDesktopName() {
2035 if (this._sock
.rQwait("DesktopName", 4)) {
2039 let length
= this._sock
.rQshift32();
2041 if (this._sock
.rQwait("DesktopName", length
, 4)) {
2045 let name
= this._sock
.rQshiftStr(length
);
2046 name
= decodeUTF8(name
, true);
2048 this._setDesktopName(name
);
2053 _handleExtendedDesktopSize() {
2054 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
2058 const numberOfScreens
= this._sock
.rQpeek8();
2060 let bytes
= 4 + (numberOfScreens
* 16);
2061 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
2065 const firstUpdate
= !this._supportsSetDesktopSize
;
2066 this._supportsSetDesktopSize
= true;
2068 // Normally we only apply the current resize mode after a
2069 // window resize event. However there is no such trigger on the
2070 // initial connect. And we don't know if the server supports
2071 // resizing until we've gotten here.
2073 this._requestRemoteResize();
2076 this._sock
.rQskipBytes(1); // number-of-screens
2077 this._sock
.rQskipBytes(3); // padding
2079 for (let i
= 0; i
< numberOfScreens
; i
+= 1) {
2080 // Save the id and flags of the first screen
2082 this._screenID
= this._sock
.rQshiftBytes(4); // id
2083 this._sock
.rQskipBytes(2); // x-position
2084 this._sock
.rQskipBytes(2); // y-position
2085 this._sock
.rQskipBytes(2); // width
2086 this._sock
.rQskipBytes(2); // height
2087 this._screenFlags
= this._sock
.rQshiftBytes(4); // flags
2089 this._sock
.rQskipBytes(16);
2094 * The x-position indicates the reason for the change:
2096 * 0 - server resized on its own
2097 * 1 - this client requested the resize
2098 * 2 - another client requested the resize
2101 // We need to handle errors when we requested the resize.
2102 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
2104 // The y-position indicates the status code from the server
2105 switch (this._FBU
.y
) {
2107 msg
= "Resize is administratively prohibited";
2110 msg
= "Out of resources";
2113 msg
= "Invalid screen layout";
2116 msg
= "Unknown reason";
2119 Log
.Warn("Server did not accept the resize request: "
2122 this._resize(this._FBU
.width
, this._FBU
.height
);
2129 let decoder
= this._decoders
[this._FBU
.encoding
];
2131 this._fail("Unsupported encoding (encoding: " +
2132 this._FBU
.encoding
+ ")");
2137 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
2138 this._FBU
.width
, this._FBU
.height
,
2139 this._sock
, this._display
,
2142 this._fail("Error decoding rect: " + err
);
2147 _updateContinuousUpdates() {
2148 if (!this._enabledContinuousUpdates
) { return; }
2150 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
2151 this._fbWidth
, this._fbHeight
);
2154 _resize(width
, height
) {
2155 this._fbWidth
= width
;
2156 this._fbHeight
= height
;
2158 this._display
.resize(this._fbWidth
, this._fbHeight
);
2160 // Adjust the visible viewport based on the new dimensions
2162 this._updateScale();
2164 this._updateContinuousUpdates();
2168 if (this._rfbXvpVer
< ver
) { return; }
2169 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
2170 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
2173 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
2174 this._cursorImage
= {
2176 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
2178 this._refreshCursor();
2181 _shouldShowDotCursor() {
2182 // Called when this._cursorImage is updated
2183 if (!this._showDotCursor
) {
2184 // User does not want to see the dot, so...
2188 // The dot should not be shown if the cursor is already visible,
2189 // i.e. contains at least one not-fully-transparent pixel.
2190 // So iterate through all alpha bytes in rgba and stop at the
2192 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
2193 if (this._cursorImage
.rgbaPixels
[i
]) {
2198 // At this point, we know that the cursor is fully transparent, and
2199 // the user wants to see the dot instead of this.
2204 if (this._rfbConnectionState
!== "connecting" &&
2205 this._rfbConnectionState
!== "connected") {
2208 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
2209 this._cursor
.change(image
.rgbaPixels
,
2210 image
.hotx
, image
.hoty
,
2215 static genDES(password
, challenge
) {
2216 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
2217 return (new DES(passwordChars
)).encrypt(challenge
);
2223 keyEvent(sock
, keysym
, down
) {
2224 const buff
= sock
._sQ
;
2225 const offset
= sock
._sQlen
;
2227 buff
[offset
] = 4; // msg-type
2228 buff
[offset
+ 1] = down
;
2230 buff
[offset
+ 2] = 0;
2231 buff
[offset
+ 3] = 0;
2233 buff
[offset
+ 4] = (keysym
>> 24);
2234 buff
[offset
+ 5] = (keysym
>> 16);
2235 buff
[offset
+ 6] = (keysym
>> 8);
2236 buff
[offset
+ 7] = keysym
;
2242 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
2243 function getRFBkeycode(xtScanCode
) {
2244 const upperByte
= (keycode
>> 8);
2245 const lowerByte
= (keycode
& 0x00ff);
2246 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
2247 return lowerByte
| 0x80;
2252 const buff
= sock
._sQ
;
2253 const offset
= sock
._sQlen
;
2255 buff
[offset
] = 255; // msg-type
2256 buff
[offset
+ 1] = 0; // sub msg-type
2258 buff
[offset
+ 2] = (down
>> 8);
2259 buff
[offset
+ 3] = down
;
2261 buff
[offset
+ 4] = (keysym
>> 24);
2262 buff
[offset
+ 5] = (keysym
>> 16);
2263 buff
[offset
+ 6] = (keysym
>> 8);
2264 buff
[offset
+ 7] = keysym
;
2266 const RFBkeycode
= getRFBkeycode(keycode
);
2268 buff
[offset
+ 8] = (RFBkeycode
>> 24);
2269 buff
[offset
+ 9] = (RFBkeycode
>> 16);
2270 buff
[offset
+ 10] = (RFBkeycode
>> 8);
2271 buff
[offset
+ 11] = RFBkeycode
;
2277 pointerEvent(sock
, x
, y
, mask
) {
2278 const buff
= sock
._sQ
;
2279 const offset
= sock
._sQlen
;
2281 buff
[offset
] = 5; // msg-type
2283 buff
[offset
+ 1] = mask
;
2285 buff
[offset
+ 2] = x
>> 8;
2286 buff
[offset
+ 3] = x
;
2288 buff
[offset
+ 4] = y
>> 8;
2289 buff
[offset
+ 5] = y
;
2295 // Used to build Notify and Request data.
2296 _buildExtendedClipboardFlags(actions
, formats
) {
2297 let data
= new Uint8Array(4);
2298 let formatFlag
= 0x00000000;
2299 let actionFlag
= 0x00000000;
2301 for (let i
= 0; i
< actions
.length
; i
++) {
2302 actionFlag
|= actions
[i
];
2305 for (let i
= 0; i
< formats
.length
; i
++) {
2306 formatFlag
|= formats
[i
];
2309 data
[0] = actionFlag
>> 24; // Actions
2310 data
[1] = 0x00; // Reserved
2311 data
[2] = 0x00; // Reserved
2312 data
[3] = formatFlag
; // Formats
2317 extendedClipboardProvide(sock
, formats
, inData
) {
2318 // Deflate incomming data and their sizes
2319 let deflator
= new Deflator();
2320 let dataToDeflate
= [];
2322 for (let i
= 0; i
< formats
.length
; i
++) {
2323 // We only support the format Text at this time
2324 if (formats
[i
] != extendedClipboardFormatText
) {
2325 throw new Error("Unsupported extended clipboard format for Provide message.");
2328 // Change lone \r or \n into \r\n as defined in rfbproto
2329 inData
[i
] = inData
[i
].replace(/\r\n|\r|\n/gm, "\r\n");
2331 // Check if it already has \0
2332 let text
= encodeUTF8(inData
[i
] + "\0");
2334 dataToDeflate
.push( (text
.length
>> 24) & 0xFF,
2335 (text
.length
>> 16) & 0xFF,
2336 (text
.length
>> 8) & 0xFF,
2337 (text
.length
& 0xFF));
2339 for (let j
= 0; j
< text
.length
; j
++) {
2340 dataToDeflate
.push(text
.charCodeAt(j
));
2344 let deflatedData
= deflator
.deflate(new Uint8Array(dataToDeflate
));
2346 // Build data to send
2347 let data
= new Uint8Array(4 + deflatedData
.length
);
2348 data
.set(RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionProvide
],
2350 data
.set(deflatedData
, 4);
2352 RFB
.messages
.clientCutText(sock
, data
, true);
2355 extendedClipboardNotify(sock
, formats
) {
2356 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionNotify
],
2358 RFB
.messages
.clientCutText(sock
, flags
, true);
2361 extendedClipboardRequest(sock
, formats
) {
2362 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionRequest
],
2364 RFB
.messages
.clientCutText(sock
, flags
, true);
2367 extendedClipboardCaps(sock
, actions
, formats
) {
2368 let formatKeys
= Object
.keys(formats
);
2369 let data
= new Uint8Array(4 + (4 * formatKeys
.length
));
2371 formatKeys
.map(x
=> parseInt(x
));
2372 formatKeys
.sort((a
, b
) => a
- b
);
2374 data
.set(RFB
.messages
._buildExtendedClipboardFlags(actions
, []));
2377 for (let i
= 0; i
< formatKeys
.length
; i
++) {
2378 data
[loopOffset
] = formats
[formatKeys
[i
]] >> 24;
2379 data
[loopOffset
+ 1] = formats
[formatKeys
[i
]] >> 16;
2380 data
[loopOffset
+ 2] = formats
[formatKeys
[i
]] >> 8;
2381 data
[loopOffset
+ 3] = formats
[formatKeys
[i
]] >> 0;
2384 data
[3] |= (1 << formatKeys
[i
]); // Update our format flags
2387 RFB
.messages
.clientCutText(sock
, data
, true);
2390 clientCutText(sock
, data
, extended
= false) {
2391 const buff
= sock
._sQ
;
2392 const offset
= sock
._sQlen
;
2394 buff
[offset
] = 6; // msg-type
2396 buff
[offset
+ 1] = 0; // padding
2397 buff
[offset
+ 2] = 0; // padding
2398 buff
[offset
+ 3] = 0; // padding
2402 length
= toUnsigned32bit(-data
.length
);
2404 length
= data
.length
;
2407 buff
[offset
+ 4] = length
>> 24;
2408 buff
[offset
+ 5] = length
>> 16;
2409 buff
[offset
+ 6] = length
>> 8;
2410 buff
[offset
+ 7] = length
;
2414 // We have to keep track of from where in the data we begin creating the
2415 // buffer for the flush in the next iteration.
2418 let remaining
= data
.length
;
2419 while (remaining
> 0) {
2421 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
2422 for (let i
= 0; i
< flushSize
; i
++) {
2423 buff
[sock
._sQlen
+ i
] = data
[dataOffset
+ i
];
2426 sock
._sQlen
+= flushSize
;
2429 remaining
-= flushSize
;
2430 dataOffset
+= flushSize
;
2435 setDesktopSize(sock
, width
, height
, id
, flags
) {
2436 const buff
= sock
._sQ
;
2437 const offset
= sock
._sQlen
;
2439 buff
[offset
] = 251; // msg-type
2440 buff
[offset
+ 1] = 0; // padding
2441 buff
[offset
+ 2] = width
>> 8; // width
2442 buff
[offset
+ 3] = width
;
2443 buff
[offset
+ 4] = height
>> 8; // height
2444 buff
[offset
+ 5] = height
;
2446 buff
[offset
+ 6] = 1; // number-of-screens
2447 buff
[offset
+ 7] = 0; // padding
2450 buff
[offset
+ 8] = id
>> 24; // id
2451 buff
[offset
+ 9] = id
>> 16;
2452 buff
[offset
+ 10] = id
>> 8;
2453 buff
[offset
+ 11] = id
;
2454 buff
[offset
+ 12] = 0; // x-position
2455 buff
[offset
+ 13] = 0;
2456 buff
[offset
+ 14] = 0; // y-position
2457 buff
[offset
+ 15] = 0;
2458 buff
[offset
+ 16] = width
>> 8; // width
2459 buff
[offset
+ 17] = width
;
2460 buff
[offset
+ 18] = height
>> 8; // height
2461 buff
[offset
+ 19] = height
;
2462 buff
[offset
+ 20] = flags
>> 24; // flags
2463 buff
[offset
+ 21] = flags
>> 16;
2464 buff
[offset
+ 22] = flags
>> 8;
2465 buff
[offset
+ 23] = flags
;
2471 clientFence(sock
, flags
, payload
) {
2472 const buff
= sock
._sQ
;
2473 const offset
= sock
._sQlen
;
2475 buff
[offset
] = 248; // msg-type
2477 buff
[offset
+ 1] = 0; // padding
2478 buff
[offset
+ 2] = 0; // padding
2479 buff
[offset
+ 3] = 0; // padding
2481 buff
[offset
+ 4] = flags
>> 24; // flags
2482 buff
[offset
+ 5] = flags
>> 16;
2483 buff
[offset
+ 6] = flags
>> 8;
2484 buff
[offset
+ 7] = flags
;
2486 const n
= payload
.length
;
2488 buff
[offset
+ 8] = n
; // length
2490 for (let i
= 0; i
< n
; i
++) {
2491 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
2494 sock
._sQlen
+= 9 + n
;
2498 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
2499 const buff
= sock
._sQ
;
2500 const offset
= sock
._sQlen
;
2502 buff
[offset
] = 150; // msg-type
2503 buff
[offset
+ 1] = enable
; // enable-flag
2505 buff
[offset
+ 2] = x
>> 8; // x
2506 buff
[offset
+ 3] = x
;
2507 buff
[offset
+ 4] = y
>> 8; // y
2508 buff
[offset
+ 5] = y
;
2509 buff
[offset
+ 6] = width
>> 8; // width
2510 buff
[offset
+ 7] = width
;
2511 buff
[offset
+ 8] = height
>> 8; // height
2512 buff
[offset
+ 9] = height
;
2518 pixelFormat(sock
, depth
, trueColor
) {
2519 const buff
= sock
._sQ
;
2520 const offset
= sock
._sQlen
;
2526 } else if (depth
> 8) {
2532 const bits
= Math
.floor(depth
/3);
2534 buff
[offset
] = 0; // msg-type
2536 buff
[offset
+ 1] = 0; // padding
2537 buff
[offset
+ 2] = 0; // padding
2538 buff
[offset
+ 3] = 0; // padding
2540 buff
[offset
+ 4] = bpp
; // bits-per-pixel
2541 buff
[offset
+ 5] = depth
; // depth
2542 buff
[offset
+ 6] = 0; // little-endian
2543 buff
[offset
+ 7] = trueColor
? 1 : 0; // true-color
2545 buff
[offset
+ 8] = 0; // red-max
2546 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
2548 buff
[offset
+ 10] = 0; // green-max
2549 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
2551 buff
[offset
+ 12] = 0; // blue-max
2552 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
2554 buff
[offset
+ 14] = bits
* 2; // red-shift
2555 buff
[offset
+ 15] = bits
* 1; // green-shift
2556 buff
[offset
+ 16] = bits
* 0; // blue-shift
2558 buff
[offset
+ 17] = 0; // padding
2559 buff
[offset
+ 18] = 0; // padding
2560 buff
[offset
+ 19] = 0; // padding
2566 clientEncodings(sock
, encodings
) {
2567 const buff
= sock
._sQ
;
2568 const offset
= sock
._sQlen
;
2570 buff
[offset
] = 2; // msg-type
2571 buff
[offset
+ 1] = 0; // padding
2573 buff
[offset
+ 2] = encodings
.length
>> 8;
2574 buff
[offset
+ 3] = encodings
.length
;
2577 for (let i
= 0; i
< encodings
.length
; i
++) {
2578 const enc
= encodings
[i
];
2579 buff
[j
] = enc
>> 24;
2580 buff
[j
+ 1] = enc
>> 16;
2581 buff
[j
+ 2] = enc
>> 8;
2587 sock
._sQlen
+= j
- offset
;
2591 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
2592 const buff
= sock
._sQ
;
2593 const offset
= sock
._sQlen
;
2595 if (typeof(x
) === "undefined") { x
= 0; }
2596 if (typeof(y
) === "undefined") { y
= 0; }
2598 buff
[offset
] = 3; // msg-type
2599 buff
[offset
+ 1] = incremental
? 1 : 0;
2601 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2602 buff
[offset
+ 3] = x
& 0xFF;
2604 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2605 buff
[offset
+ 5] = y
& 0xFF;
2607 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2608 buff
[offset
+ 7] = w
& 0xFF;
2610 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2611 buff
[offset
+ 9] = h
& 0xFF;
2617 xvpOp(sock
, ver
, op
) {
2618 const buff
= sock
._sQ
;
2619 const offset
= sock
._sQlen
;
2621 buff
[offset
] = 250; // msg-type
2622 buff
[offset
+ 1] = 0; // padding
2624 buff
[offset
+ 2] = ver
;
2625 buff
[offset
+ 3] = op
;
2634 rgbaPixels
: new Uint8Array(),
2640 /* eslint-disable indent */
2641 rgbaPixels
: new Uint8Array([
2642 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2643 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2644 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2646 /* eslint-enable indent */