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._handle_message();
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._init_msg
.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("handle_message called on an empty receive queue");
788 switch (this._rfbConnectionState
) {
790 Log
.Error("Got data while disconnected");
794 if (this._flushing
) {
797 if (!this._normal_msg()) {
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 // Otherwise we treat this as a mouse click event.
840 // Send the button down event here, as the button up
841 // event is sent at the end of this function.
842 RFB
.messages
.pointerEvent(this._sock
,
843 this._display
.absX(x
),
844 this._display
.absY(y
),
849 if (this._viewOnly
) { return; } // View only, skip mouse events
851 if (this._rfbConnectionState
!== 'connected') { return; }
852 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouseButtonMask
);
855 _handleMouseMove(x
, y
) {
856 if (this._viewportDragging
) {
857 const deltaX
= this._viewportDragPos
.x
- x
;
858 const deltaY
= this._viewportDragPos
.y
- y
;
860 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
861 Math
.abs(deltaY
) > dragThreshold
)) {
862 this._viewportHasMoved
= true;
864 this._viewportDragPos
= {'x': x
, 'y': y
};
865 this._display
.viewportChangePos(deltaX
, deltaY
);
868 // Skip sending mouse events
872 if (this._viewOnly
) { return; } // View only, skip mouse events
874 if (this._rfbConnectionState
!== 'connected') { return; }
875 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouseButtonMask
);
880 _negotiate_protocol_version() {
881 if (this._sock
.rQwait("version", 12)) {
885 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
886 Log
.Info("Server ProtocolVersion: " + sversion
);
889 case "000.000": // UltraVNC repeater
893 case "003.006": // UltraVNC
894 case "003.889": // Apple Remote Desktop
895 this._rfbVersion
= 3.3;
898 this._rfbVersion
= 3.7;
901 case "004.000": // Intel AMT KVM
902 case "004.001": // RealVNC 4.6
903 case "005.000": // RealVNC 5.3
904 this._rfbVersion
= 3.8;
907 return this._fail("Invalid server version " + sversion
);
911 let repeaterID
= "ID:" + this._repeaterID
;
912 while (repeaterID
.length
< 250) {
915 this._sock
.send_string(repeaterID
);
919 if (this._rfbVersion
> this._rfbMaxVersion
) {
920 this._rfbVersion
= this._rfbMaxVersion
;
923 const cversion
= "00" + parseInt(this._rfbVersion
, 10) +
924 ".00" + ((this._rfbVersion
* 10) % 10);
925 this._sock
.send_string("RFB " + cversion
+ "\n");
926 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
928 this._rfbInitState
= 'Security';
931 _negotiate_security() {
932 // Polyfill since IE and PhantomJS doesn't have
933 // TypedArray.includes()
934 function includes(item
, array
) {
935 for (let i
= 0; i
< array
.length
; i
++) {
936 if (array
[i
] === item
) {
943 if (this._rfbVersion
>= 3.7) {
944 // Server sends supported list, client decides
945 const numTypes
= this._sock
.rQshift8();
946 if (this._sock
.rQwait("security type", numTypes
, 1)) { return false; }
948 if (numTypes
=== 0) {
949 this._rfbInitState
= "SecurityReason";
950 this._securityContext
= "no security types";
951 this._securityStatus
= 1;
952 return this._init_msg();
955 const types
= this._sock
.rQshiftBytes(numTypes
);
956 Log
.Debug("Server security types: " + types
);
958 // Look for each auth in preferred order
959 if (includes(1, types
)) {
960 this._rfbAuthScheme
= 1; // None
961 } else if (includes(22, types
)) {
962 this._rfbAuthScheme
= 22; // XVP
963 } else if (includes(16, types
)) {
964 this._rfbAuthScheme
= 16; // Tight
965 } else if (includes(2, types
)) {
966 this._rfbAuthScheme
= 2; // VNC Auth
967 } else if (includes(19, types
)) {
968 this._rfbAuthScheme
= 19; // VeNCrypt Auth
970 return this._fail("Unsupported security types (types: " + types
+ ")");
973 this._sock
.send([this._rfbAuthScheme
]);
976 if (this._sock
.rQwait("security scheme", 4)) { return false; }
977 this._rfbAuthScheme
= this._sock
.rQshift32();
979 if (this._rfbAuthScheme
== 0) {
980 this._rfbInitState
= "SecurityReason";
981 this._securityContext
= "authentication scheme";
982 this._securityStatus
= 1;
983 return this._init_msg();
987 this._rfbInitState
= 'Authentication';
988 Log
.Debug('Authenticating using scheme: ' + this._rfbAuthScheme
);
990 return this._init_msg(); // jump to authentication
993 _handle_security_reason() {
994 if (this._sock
.rQwait("reason length", 4)) {
997 const strlen
= this._sock
.rQshift32();
1001 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
1002 reason
= this._sock
.rQshiftStr(strlen
);
1005 if (reason
!== "") {
1006 this.dispatchEvent(new CustomEvent(
1008 { detail
: { status
: this._securityStatus
,
1009 reason
: reason
} }));
1011 return this._fail("Security negotiation failed on " +
1012 this._securityContext
+
1013 " (reason: " + reason
+ ")");
1015 this.dispatchEvent(new CustomEvent(
1017 { detail
: { status
: this._securityStatus
} }));
1019 return this._fail("Security negotiation failed on " +
1020 this._securityContext
);
1025 _negotiate_xvp_auth() {
1026 if (this._rfbCredentials
.username
=== undefined ||
1027 this._rfbCredentials
.password
=== undefined ||
1028 this._rfbCredentials
.target
=== undefined) {
1029 this.dispatchEvent(new CustomEvent(
1030 "credentialsrequired",
1031 { detail
: { types
: ["username", "password", "target"] } }));
1035 const xvpAuthStr
= String
.fromCharCode(this._rfbCredentials
.username
.length
) +
1036 String
.fromCharCode(this._rfbCredentials
.target
.length
) +
1037 this._rfbCredentials
.username
+
1038 this._rfbCredentials
.target
;
1039 this._sock
.send_string(xvpAuthStr
);
1040 this._rfbAuthScheme
= 2;
1041 return this._negotiate_authentication();
1044 // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
1045 _negotiate_vencrypt_auth() {
1047 // waiting for VeNCrypt version
1048 if (this._rfbVeNCryptState
== 0) {
1049 if (this._sock
.rQwait("vencrypt version", 2)) { return false; }
1051 const major
= this._sock
.rQshift8();
1052 const minor
= this._sock
.rQshift8();
1054 if (!(major
== 0 && minor
== 2)) {
1055 return this._fail("Unsupported VeNCrypt version " + major
+ "." + minor
);
1058 this._sock
.send([0, 2]);
1059 this._rfbVeNCryptState
= 1;
1063 if (this._rfbVeNCryptState
== 1) {
1064 if (this._sock
.rQwait("vencrypt ack", 1)) { return false; }
1066 const res
= this._sock
.rQshift8();
1069 return this._fail("VeNCrypt failure " + res
);
1072 this._rfbVeNCryptState
= 2;
1074 // must fall through here (i.e. no "else if"), beacause we may have already received
1075 // the subtypes length and won't be called again
1077 if (this._rfbVeNCryptState
== 2) { // waiting for subtypes length
1078 if (this._sock
.rQwait("vencrypt subtypes length", 1)) { return false; }
1080 const subtypesLength
= this._sock
.rQshift8();
1081 if (subtypesLength
< 1) {
1082 return this._fail("VeNCrypt subtypes empty");
1085 this._rfbVeNCryptSubtypesLength
= subtypesLength
;
1086 this._rfbVeNCryptState
= 3;
1089 // waiting for subtypes list
1090 if (this._rfbVeNCryptState
== 3) {
1091 if (this._sock
.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength
)) { return false; }
1093 const subtypes
= [];
1094 for (let i
= 0; i
< this._rfbVeNCryptSubtypesLength
; i
++) {
1095 subtypes
.push(this._sock
.rQshift32());
1098 // 256 = Plain subtype
1099 if (subtypes
.indexOf(256) != -1) {
1101 this._sock
.send([0, 0, 1, 0]);
1102 this._rfbVeNCryptState
= 4;
1104 return this._fail("VeNCrypt Plain subtype not offered by server");
1108 // negotiated Plain subtype, server waits for password
1109 if (this._rfbVeNCryptState
== 4) {
1110 if (!this._rfbCredentials
.username
||
1111 !this._rfbCredentials
.password
) {
1112 this.dispatchEvent(new CustomEvent(
1113 "credentialsrequired",
1114 { detail
: { types
: ["username", "password"] } }));
1118 const user
= encodeUTF8(this._rfbCredentials
.username
);
1119 const pass
= encodeUTF8(this._rfbCredentials
.password
);
1121 // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1122 this._sock
.send([0, 0, 0, user
.length
]);
1123 this._sock
.send([0, 0, 0, pass
.length
]);
1124 this._sock
.send_string(user
);
1125 this._sock
.send_string(pass
);
1127 this._rfbInitState
= "SecurityResult";
1132 _negotiate_std_vnc_auth() {
1133 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
1135 if (this._rfbCredentials
.password
=== undefined) {
1136 this.dispatchEvent(new CustomEvent(
1137 "credentialsrequired",
1138 { detail
: { types
: ["password"] } }));
1142 // TODO(directxman12): make genDES not require an Array
1143 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
1144 const response
= RFB
.genDES(this._rfbCredentials
.password
, challenge
);
1145 this._sock
.send(response
);
1146 this._rfbInitState
= "SecurityResult";
1150 _negotiate_tight_unix_auth() {
1151 if (this._rfbCredentials
.username
=== undefined ||
1152 this._rfbCredentials
.password
=== undefined) {
1153 this.dispatchEvent(new CustomEvent(
1154 "credentialsrequired",
1155 { detail
: { types
: ["username", "password"] } }));
1159 this._sock
.send([0, 0, 0, this._rfbCredentials
.username
.length
]);
1160 this._sock
.send([0, 0, 0, this._rfbCredentials
.password
.length
]);
1161 this._sock
.send_string(this._rfbCredentials
.username
);
1162 this._sock
.send_string(this._rfbCredentials
.password
);
1163 this._rfbInitState
= "SecurityResult";
1167 _negotiate_tight_tunnels(numTunnels
) {
1168 const clientSupportedTunnelTypes
= {
1169 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
1171 const serverSupportedTunnelTypes
= {};
1172 // receive tunnel capabilities
1173 for (let i
= 0; i
< numTunnels
; i
++) {
1174 const capCode
= this._sock
.rQshift32();
1175 const capVendor
= this._sock
.rQshiftStr(4);
1176 const capSignature
= this._sock
.rQshiftStr(8);
1177 serverSupportedTunnelTypes
[capCode
] = { vendor
: capVendor
, signature
: capSignature
};
1180 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
1182 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1183 // but forgets to advertise it. Try to detect such servers by
1184 // looking for their custom tunnel type.
1185 if (serverSupportedTunnelTypes
[1] &&
1186 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
1187 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
1188 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1189 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
1192 // choose the notunnel type
1193 if (serverSupportedTunnelTypes
[0]) {
1194 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
1195 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
1196 return this._fail("Client's tunnel type had the incorrect " +
1197 "vendor or signature");
1199 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1200 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1201 return false; // wait until we receive the sub auth count to continue
1203 return this._fail("Server wanted tunnels, but doesn't support " +
1204 "the notunnel type");
1208 _negotiate_tight_auth() {
1209 if (!this._rfbTightVNC
) { // first pass, do the tunnel negotiation
1210 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1211 const numTunnels
= this._sock
.rQshift32();
1212 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1214 this._rfbTightVNC
= true;
1216 if (numTunnels
> 0) {
1217 this._negotiate_tight_tunnels(numTunnels
);
1218 return false; // wait until we receive the sub auth to continue
1222 // second pass, do the sub-auth negotiation
1223 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1224 const subAuthCount
= this._sock
.rQshift32();
1225 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1226 this._rfbInitState
= 'SecurityResult';
1230 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1232 const clientSupportedTypes
= {
1238 const serverSupportedTypes
= [];
1240 for (let i
= 0; i
< subAuthCount
; i
++) {
1241 this._sock
.rQshift32(); // capNum
1242 const capabilities
= this._sock
.rQshiftStr(12);
1243 serverSupportedTypes
.push(capabilities
);
1246 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1248 for (let authType
in clientSupportedTypes
) {
1249 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1250 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1251 Log
.Debug("Selected authentication type: " + authType
);
1254 case 'STDVNOAUTH__': // no auth
1255 this._rfbInitState
= 'SecurityResult';
1257 case 'STDVVNCAUTH_': // VNC auth
1258 this._rfbAuthScheme
= 2;
1259 return this._init_msg();
1260 case 'TGHTULGNAUTH': // UNIX auth
1261 this._rfbAuthScheme
= 129;
1262 return this._init_msg();
1264 return this._fail("Unsupported tiny auth scheme " +
1265 "(scheme: " + authType
+ ")");
1270 return this._fail("No supported sub-auth types!");
1273 _negotiate_authentication() {
1274 switch (this._rfbAuthScheme
) {
1276 if (this._rfbVersion
>= 3.8) {
1277 this._rfbInitState
= 'SecurityResult';
1280 this._rfbInitState
= 'ClientInitialisation';
1281 return this._init_msg();
1283 case 22: // XVP auth
1284 return this._negotiate_xvp_auth();
1286 case 2: // VNC authentication
1287 return this._negotiate_std_vnc_auth();
1289 case 16: // TightVNC Security Type
1290 return this._negotiate_tight_auth();
1292 case 19: // VeNCrypt Security Type
1293 return this._negotiate_vencrypt_auth();
1295 case 129: // TightVNC UNIX Security Type
1296 return this._negotiate_tight_unix_auth();
1299 return this._fail("Unsupported auth scheme (scheme: " +
1300 this._rfbAuthScheme
+ ")");
1304 _handle_security_result() {
1305 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1307 const status
= this._sock
.rQshift32();
1309 if (status
=== 0) { // OK
1310 this._rfbInitState
= 'ClientInitialisation';
1311 Log
.Debug('Authentication OK');
1312 return this._init_msg();
1314 if (this._rfbVersion
>= 3.8) {
1315 this._rfbInitState
= "SecurityReason";
1316 this._securityContext
= "security result";
1317 this._securityStatus
= status
;
1318 return this._init_msg();
1320 this.dispatchEvent(new CustomEvent(
1322 { detail
: { status
: status
} }));
1324 return this._fail("Security handshake failed");
1329 _negotiate_server_init() {
1330 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1333 const width
= this._sock
.rQshift16();
1334 const height
= this._sock
.rQshift16();
1337 const bpp
= this._sock
.rQshift8();
1338 const depth
= this._sock
.rQshift8();
1339 const bigEndian
= this._sock
.rQshift8();
1340 const trueColor
= this._sock
.rQshift8();
1342 const redMax
= this._sock
.rQshift16();
1343 const greenMax
= this._sock
.rQshift16();
1344 const blueMax
= this._sock
.rQshift16();
1345 const redShift
= this._sock
.rQshift8();
1346 const greenShift
= this._sock
.rQshift8();
1347 const blueShift
= this._sock
.rQshift8();
1348 this._sock
.rQskipBytes(3); // padding
1350 // NB(directxman12): we don't want to call any callbacks or print messages until
1351 // *after* we're past the point where we could backtrack
1353 /* Connection name/title */
1354 const nameLength = this._sock.rQshift32();
1355 if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
1356 let name = this._sock.rQshiftStr(nameLength);
1357 name = decodeUTF8(name, true);
1359 if (this._rfbTightVNC) {
1360 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
1361 // In TightVNC mode, ServerInit message is extended
1362 const numServerMessages = this._sock.rQshift16();
1363 const numClientMessages = this._sock.rQshift16();
1364 const numEncodings = this._sock.rQshift16();
1365 this._sock.rQskipBytes(2); // padding
1367 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1368 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
1370 // we don't actually do anything with the capability information that TIGHT sends,
1371 // so we just skip the all of this.
1373 // TIGHT server message capabilities
1374 this._sock.rQskipBytes(16 * numServerMessages);
1376 // TIGHT client message capabilities
1377 this._sock.rQskipBytes(16 * numClientMessages);
1379 // TIGHT encoding capabilities
1380 this._sock.rQskipBytes(16 * numEncodings);
1383 // NB(directxman12): these are down here so that we don't run them multiple times
1385 Log.Info("Screen: " + width + "x" + height +
1386 ", bpp: " + bpp + ", depth: " + depth +
1387 ", bigEndian: " + bigEndian +
1388 ", trueColor: " + trueColor +
1389 ", redMax: " + redMax +
1390 ", greenMax: " + greenMax +
1391 ", blueMax: " + blueMax +
1392 ", redShift: " + redShift +
1393 ", greenShift: " + greenShift +
1394 ", blueShift: " + blueShift);
1396 // we're past the point where we could backtrack, so it's safe to call this
1397 this._setDesktopName(name);
1398 this._resize(width, height);
1400 if (!this._viewOnly) { this._keyboard.grab(); }
1401 if (!this._viewOnly) { this._mouse.grab(); }
1405 if (this._fbName === "Intel(r) AMT KVM") {
1406 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1410 RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
1411 this._sendEncodings();
1412 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
1414 this._updateConnectionState('connected');
1421 // In preference order
1422 encs.push(encodings.encodingCopyRect);
1423 // Only supported with full depth support
1424 if (this._fbDepth == 24) {
1425 encs.push(encodings.encodingTight);
1426 encs.push(encodings.encodingTightPNG);
1427 encs.push(encodings.encodingHextile);
1428 encs.push(encodings.encodingRRE);
1430 encs.push(encodings.encodingRaw);
1432 // Psuedo-encoding settings
1433 encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
1434 encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
1436 encs.push(encodings.pseudoEncodingDesktopSize);
1437 encs.push(encodings.pseudoEncodingLastRect);
1438 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1439 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1440 encs.push(encodings.pseudoEncodingXvp);
1441 encs.push(encodings.pseudoEncodingFence);
1442 encs.push(encodings.pseudoEncodingContinuousUpdates);
1443 encs.push(encodings.pseudoEncodingDesktopName);
1444 encs.push(encodings.pseudoEncodingExtendedClipboard);
1446 if (this._fbDepth == 24) {
1447 encs.push(encodings.pseudoEncodingVMwareCursor);
1448 encs.push(encodings.pseudoEncodingCursor);
1451 RFB.messages.clientEncodings(this._sock, encs);
1454 /* RFB protocol initialization states:
1459 * ClientInitialization - not triggered by server message
1460 * ServerInitialization
1463 switch (this._rfbInitState
) {
1464 case 'ProtocolVersion':
1465 return this._negotiate_protocol_version();
1468 return this._negotiate_security();
1470 case 'Authentication':
1471 return this._negotiate_authentication();
1473 case 'SecurityResult':
1474 return this._handle_security_result();
1476 case 'SecurityReason':
1477 return this._handle_security_reason();
1479 case 'ClientInitialisation':
1480 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1481 this._rfbInitState
= 'ServerInitialisation';
1484 case 'ServerInitialisation':
1485 return this._negotiate_server_init();
1488 return this._fail("Unknown init state (state: " +
1489 this._rfbInitState
+ ")");
1493 _handle_set_colour_map_msg() {
1494 Log
.Debug("SetColorMapEntries");
1496 return this._fail("Unexpected SetColorMapEntries message");
1499 _handle_server_cut_text() {
1500 Log
.Debug("ServerCutText");
1502 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1504 this._sock
.rQskipBytes(3); // Padding
1506 let length
= this._sock
.rQshift32();
1507 length
= toSigned32bit(length
);
1509 if (this._sock
.rQwait("ServerCutText content", Math
.abs(length
), 8)) { return false; }
1513 const text
= this._sock
.rQshiftStr(length
);
1514 if (this._viewOnly
) {
1518 this.dispatchEvent(new CustomEvent(
1520 { detail
: { text
: text
} }));
1524 length
= Math
.abs(length
);
1525 const flags
= this._sock
.rQshift32();
1526 let formats
= flags
& 0x0000FFFF;
1527 let actions
= flags
& 0xFF000000;
1529 let isCaps
= (!!(actions
& extendedClipboardActionCaps
));
1531 this._clipboardServerCapabilitiesFormats
= {};
1532 this._clipboardServerCapabilitiesActions
= {};
1534 // Update our server capabilities for Formats
1535 for (let i
= 0; i
<= 15; i
++) {
1538 // Check if format flag is set.
1539 if ((formats
& index
)) {
1540 this._clipboardServerCapabilitiesFormats
[index
] = true;
1541 // We don't send unsolicited clipboard, so we
1543 this._sock
.rQshift32();
1547 // Update our server capabilities for Actions
1548 for (let i
= 24; i
<= 31; i
++) {
1550 this._clipboardServerCapabilitiesActions
[index
] = !!(actions
& index
);
1553 /* Caps handling done, send caps with the clients
1554 capabilities set as a response */
1555 let clientActions
= [
1556 extendedClipboardActionCaps
,
1557 extendedClipboardActionRequest
,
1558 extendedClipboardActionPeek
,
1559 extendedClipboardActionNotify
,
1560 extendedClipboardActionProvide
1562 RFB
.messages
.extendedClipboardCaps(this._sock
, clientActions
, {extendedClipboardFormatText
: 0});
1564 } else if (actions
=== extendedClipboardActionRequest
) {
1565 if (this._viewOnly
) {
1569 // Check if server has told us it can handle Provide and there is clipboard data to send.
1570 if (this._clipboardText
!= null &&
1571 this._clipboardServerCapabilitiesActions
[extendedClipboardActionProvide
]) {
1573 if (formats
& extendedClipboardFormatText
) {
1574 RFB
.messages
.extendedClipboardProvide(this._sock
, [extendedClipboardFormatText
], [this._clipboardText
]);
1578 } else if (actions
=== extendedClipboardActionPeek
) {
1579 if (this._viewOnly
) {
1583 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
1585 if (this._clipboardText
!= null) {
1586 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
1588 RFB
.messages
.extendedClipboardNotify(this._sock
, []);
1592 } else if (actions
=== extendedClipboardActionNotify
) {
1593 if (this._viewOnly
) {
1597 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionRequest
]) {
1599 if (formats
& extendedClipboardFormatText
) {
1600 RFB
.messages
.extendedClipboardRequest(this._sock
, [extendedClipboardFormatText
]);
1604 } else if (actions
=== extendedClipboardActionProvide
) {
1605 if (this._viewOnly
) {
1609 if (!(formats
& extendedClipboardFormatText
)) {
1612 // Ignore what we had in our clipboard client side.
1613 this._clipboardText
= null;
1615 // FIXME: Should probably verify that this data was actually requested
1616 let zlibStream
= this._sock
.rQshiftBytes(length
- 4);
1617 let streamInflator
= new Inflator();
1618 let textData
= null;
1620 streamInflator
.setInput(zlibStream
);
1621 for (let i
= 0; i
<= 15; i
++) {
1622 let format
= 1 << i
;
1624 if (formats
& format
) {
1627 let sizeArray
= streamInflator
.inflate(4);
1629 size
|= (sizeArray
[0] << 24);
1630 size
|= (sizeArray
[1] << 16);
1631 size
|= (sizeArray
[2] << 8);
1632 size
|= (sizeArray
[3]);
1633 let chunk
= streamInflator
.inflate(size
);
1635 if (format
=== extendedClipboardFormatText
) {
1640 streamInflator
.setInput(null);
1642 if (textData
!== null) {
1644 for (let i
= 0; i
< textData
.length
; i
++) {
1645 tmpText
+= String
.fromCharCode(textData
[i
]);
1649 textData
= decodeUTF8(textData
);
1650 if ((textData
.length
> 0) && "\0" === textData
.charAt(textData
.length
- 1)) {
1651 textData
= textData
.slice(0, -1);
1654 textData
= textData
.replace("\r\n", "\n");
1656 this.dispatchEvent(new CustomEvent(
1658 { detail
: { text
: textData
} }));
1661 return this._fail("Unexpected action in extended clipboard message: " + actions
);
1667 _handle_server_fence_msg() {
1668 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1669 this._sock
.rQskipBytes(3); // Padding
1670 let flags
= this._sock
.rQshift32();
1671 let length
= this._sock
.rQshift8();
1673 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1676 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1680 const payload
= this._sock
.rQshiftStr(length
);
1682 this._supportsFence
= true;
1687 * (1<<0) - BlockBefore
1688 * (1<<1) - BlockAfter
1693 if (!(flags
& (1<<31))) {
1694 return this._fail("Unexpected fence response");
1697 // Filter out unsupported flags
1698 // FIXME: support syncNext
1699 flags
&= (1<<0) | (1<<1);
1701 // BlockBefore and BlockAfter are automatically handled by
1702 // the fact that we process each incoming message
1704 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1710 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1711 this._sock
.rQskipBytes(1); // Padding
1712 const xvpVer
= this._sock
.rQshift8();
1713 const xvpMsg
= this._sock
.rQshift8();
1717 Log
.Error("XVP Operation Failed");
1720 this._rfbXvpVer
= xvpVer
;
1721 Log
.Info("XVP extensions enabled (version " + this._rfbXvpVer
+ ")");
1722 this._setCapability("power", true);
1725 this._fail("Illegal server XVP message (msg: " + xvpMsg
+ ")");
1734 if (this._FBU
.rects
> 0) {
1737 msgType
= this._sock
.rQshift8();
1742 case 0: // FramebufferUpdate
1743 ret
= this._framebufferUpdate();
1744 if (ret
&& !this._enabledContinuousUpdates
) {
1745 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1746 this._fbWidth
, this._fbHeight
);
1750 case 1: // SetColorMapEntries
1751 return this._handle_set_colour_map_msg();
1755 this.dispatchEvent(new CustomEvent(
1760 case 3: // ServerCutText
1761 return this._handle_server_cut_text();
1763 case 150: // EndOfContinuousUpdates
1764 first
= !this._supportsContinuousUpdates
;
1765 this._supportsContinuousUpdates
= true;
1766 this._enabledContinuousUpdates
= false;
1768 this._enabledContinuousUpdates
= true;
1769 this._updateContinuousUpdates();
1770 Log
.Info("Enabling continuous updates.");
1772 // FIXME: We need to send a framebufferupdaterequest here
1773 // if we add support for turning off continuous updates
1777 case 248: // ServerFence
1778 return this._handle_server_fence_msg();
1781 return this._handle_xvp_msg();
1784 this._fail("Unexpected server message (type " + msgType
+ ")");
1785 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1791 this._flushing
= false;
1792 // Resume processing
1793 if (this._sock
.rQlen
> 0) {
1794 this._handle_message();
1798 _framebufferUpdate() {
1799 if (this._FBU
.rects
=== 0) {
1800 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1801 this._sock
.rQskipBytes(1); // Padding
1802 this._FBU
.rects
= this._sock
.rQshift16();
1804 // Make sure the previous frame is fully rendered first
1805 // to avoid building up an excessive queue
1806 if (this._display
.pending()) {
1807 this._flushing
= true;
1808 this._display
.flush();
1813 while (this._FBU
.rects
> 0) {
1814 if (this._FBU
.encoding
=== null) {
1815 if (this._sock
.rQwait("rect header", 12)) { return false; }
1816 /* New FramebufferUpdate */
1818 const hdr
= this._sock
.rQshiftBytes(12);
1819 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1820 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1821 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1822 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1823 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1824 (hdr
[10] << 8) + hdr
[11], 10);
1827 if (!this._handleRect()) {
1832 this._FBU
.encoding
= null;
1835 this._display
.flip();
1837 return true; // We finished this FBU
1841 switch (this._FBU
.encoding
) {
1842 case encodings
.pseudoEncodingLastRect
:
1843 this._FBU
.rects
= 1; // Will be decreased when we return
1846 case encodings
.pseudoEncodingVMwareCursor
:
1847 return this._handleVMwareCursor();
1849 case encodings
.pseudoEncodingCursor
:
1850 return this._handleCursor();
1852 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1853 // Old Safari doesn't support creating keyboard events
1855 const keyboardEvent
= document
.createEvent("keyboardEvent");
1856 if (keyboardEvent
.code
!== undefined) {
1857 this._qemuExtKeyEventSupported
= true;
1864 case encodings
.pseudoEncodingDesktopName
:
1865 return this._handleDesktopName();
1867 case encodings
.pseudoEncodingDesktopSize
:
1868 this._resize(this._FBU
.width
, this._FBU
.height
);
1871 case encodings
.pseudoEncodingExtendedDesktopSize
:
1872 return this._handleExtendedDesktopSize();
1875 return this._handleDataRect();
1879 _handleVMwareCursor() {
1880 const hotx
= this._FBU
.x
; // hotspot-x
1881 const hoty
= this._FBU
.y
; // hotspot-y
1882 const w
= this._FBU
.width
;
1883 const h
= this._FBU
.height
;
1884 if (this._sock
.rQwait("VMware cursor encoding", 1)) {
1888 const cursorType
= this._sock
.rQshift8();
1890 this._sock
.rQshift8(); //Padding
1893 const bytesPerPixel
= 4;
1896 if (cursorType
== 0) {
1897 //Used to filter away unimportant bits.
1898 //OR is used for correct conversion in js.
1899 const PIXEL_MASK
= 0xffffff00 | 0;
1900 rgba
= new Array(w
* h
* bytesPerPixel
);
1902 if (this._sock
.rQwait("VMware cursor classic encoding",
1903 (w
* h
* bytesPerPixel
) * 2, 2)) {
1907 let andMask
= new Array(w
* h
);
1908 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1909 andMask
[pixel
] = this._sock
.rQshift32();
1912 let xorMask
= new Array(w
* h
);
1913 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1914 xorMask
[pixel
] = this._sock
.rQshift32();
1917 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1918 if (andMask
[pixel
] == 0) {
1919 //Fully opaque pixel
1920 let bgr
= xorMask
[pixel
];
1921 let r
= bgr
>> 8 & 0xff;
1922 let g
= bgr
>> 16 & 0xff;
1923 let b
= bgr
>> 24 & 0xff;
1925 rgba
[(pixel
* bytesPerPixel
) ] = r
; //r
1926 rgba
[(pixel
* bytesPerPixel
) + 1 ] = g
; //g
1927 rgba
[(pixel
* bytesPerPixel
) + 2 ] = b
; //b
1928 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff; //a
1930 } else if ((andMask
[pixel
] & PIXEL_MASK
) ==
1932 //Only screen value matters, no mouse colouring
1933 if (xorMask
[pixel
] == 0) {
1935 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1936 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1937 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1938 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0x00;
1940 } else if ((xorMask
[pixel
] & PIXEL_MASK
) ==
1942 //Inverted pixel, not supported in browsers.
1943 //Fully opaque instead.
1944 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1945 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1946 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1947 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1951 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1952 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1953 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1954 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1959 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1960 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1961 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1962 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1967 } else if (cursorType
== 1) {
1968 if (this._sock
.rQwait("VMware cursor alpha encoding",
1973 rgba
= new Array(w
* h
* bytesPerPixel
);
1975 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1976 let data
= this._sock
.rQshift32();
1978 rgba
[(pixel
* 4) ] = data
>> 24 & 0xff; //r
1979 rgba
[(pixel
* 4) + 1 ] = data
>> 16 & 0xff; //g
1980 rgba
[(pixel
* 4) + 2 ] = data
>> 8 & 0xff; //b
1981 rgba
[(pixel
* 4) + 3 ] = data
& 0xff; //a
1985 Log
.Warn("The given cursor type is not supported: "
1986 + cursorType
+ " given.");
1990 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1996 const hotx
= this._FBU
.x
; // hotspot-x
1997 const hoty
= this._FBU
.y
; // hotspot-y
1998 const w
= this._FBU
.width
;
1999 const h
= this._FBU
.height
;
2001 const pixelslength
= w
* h
* 4;
2002 const masklength
= Math
.ceil(w
/ 8) * h
;
2004 let bytes
= pixelslength
+ masklength
;
2005 if (this._sock
.rQwait("cursor encoding", bytes
)) {
2009 // Decode from BGRX pixels + bit mask to RGBA
2010 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
2011 const mask
= this._sock
.rQshiftBytes(masklength
);
2012 let rgba
= new Uint8Array(w
* h
* 4);
2015 for (let y
= 0; y
< h
; y
++) {
2016 for (let x
= 0; x
< w
; x
++) {
2017 let maskIdx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
2018 let alpha
= (mask
[maskIdx
] << (x
% 8)) & 0x80 ? 255 : 0;
2019 rgba
[pixIdx
] = pixels
[pixIdx
+ 2];
2020 rgba
[pixIdx
+ 1] = pixels
[pixIdx
+ 1];
2021 rgba
[pixIdx
+ 2] = pixels
[pixIdx
];
2022 rgba
[pixIdx
+ 3] = alpha
;
2027 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
2032 _handleDesktopName() {
2033 if (this._sock
.rQwait("DesktopName", 4)) {
2037 let length
= this._sock
.rQshift32();
2039 if (this._sock
.rQwait("DesktopName", length
, 4)) {
2043 let name
= this._sock
.rQshiftStr(length
);
2044 name
= decodeUTF8(name
, true);
2046 this._setDesktopName(name
);
2051 _handleExtendedDesktopSize() {
2052 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
2056 const numberOfScreens
= this._sock
.rQpeek8();
2058 let bytes
= 4 + (numberOfScreens
* 16);
2059 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
2063 const firstUpdate
= !this._supportsSetDesktopSize
;
2064 this._supportsSetDesktopSize
= true;
2066 // Normally we only apply the current resize mode after a
2067 // window resize event. However there is no such trigger on the
2068 // initial connect. And we don't know if the server supports
2069 // resizing until we've gotten here.
2071 this._requestRemoteResize();
2074 this._sock
.rQskipBytes(1); // number-of-screens
2075 this._sock
.rQskipBytes(3); // padding
2077 for (let i
= 0; i
< numberOfScreens
; i
+= 1) {
2078 // Save the id and flags of the first screen
2080 this._screenID
= this._sock
.rQshiftBytes(4); // id
2081 this._sock
.rQskipBytes(2); // x-position
2082 this._sock
.rQskipBytes(2); // y-position
2083 this._sock
.rQskipBytes(2); // width
2084 this._sock
.rQskipBytes(2); // height
2085 this._screenFlags
= this._sock
.rQshiftBytes(4); // flags
2087 this._sock
.rQskipBytes(16);
2092 * The x-position indicates the reason for the change:
2094 * 0 - server resized on its own
2095 * 1 - this client requested the resize
2096 * 2 - another client requested the resize
2099 // We need to handle errors when we requested the resize.
2100 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
2102 // The y-position indicates the status code from the server
2103 switch (this._FBU
.y
) {
2105 msg
= "Resize is administratively prohibited";
2108 msg
= "Out of resources";
2111 msg
= "Invalid screen layout";
2114 msg
= "Unknown reason";
2117 Log
.Warn("Server did not accept the resize request: "
2120 this._resize(this._FBU
.width
, this._FBU
.height
);
2127 let decoder
= this._decoders
[this._FBU
.encoding
];
2129 this._fail("Unsupported encoding (encoding: " +
2130 this._FBU
.encoding
+ ")");
2135 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
2136 this._FBU
.width
, this._FBU
.height
,
2137 this._sock
, this._display
,
2140 this._fail("Error decoding rect: " + err
);
2145 _updateContinuousUpdates() {
2146 if (!this._enabledContinuousUpdates
) { return; }
2148 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
2149 this._fbWidth
, this._fbHeight
);
2152 _resize(width
, height
) {
2153 this._fbWidth
= width
;
2154 this._fbHeight
= height
;
2156 this._display
.resize(this._fbWidth
, this._fbHeight
);
2158 // Adjust the visible viewport based on the new dimensions
2160 this._updateScale();
2162 this._updateContinuousUpdates();
2166 if (this._rfbXvpVer
< ver
) { return; }
2167 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
2168 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
2171 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
2172 this._cursorImage
= {
2174 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
2176 this._refreshCursor();
2179 _shouldShowDotCursor() {
2180 // Called when this._cursorImage is updated
2181 if (!this._showDotCursor
) {
2182 // User does not want to see the dot, so...
2186 // The dot should not be shown if the cursor is already visible,
2187 // i.e. contains at least one not-fully-transparent pixel.
2188 // So iterate through all alpha bytes in rgba and stop at the
2190 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
2191 if (this._cursorImage
.rgbaPixels
[i
]) {
2196 // At this point, we know that the cursor is fully transparent, and
2197 // the user wants to see the dot instead of this.
2202 if (this._rfbConnectionState
!== "connecting" &&
2203 this._rfbConnectionState
!== "connected") {
2206 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
2207 this._cursor
.change(image
.rgbaPixels
,
2208 image
.hotx
, image
.hoty
,
2213 static genDES(password
, challenge
) {
2214 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
2215 return (new DES(passwordChars
)).encrypt(challenge
);
2221 keyEvent(sock
, keysym
, down
) {
2222 const buff
= sock
._sQ
;
2223 const offset
= sock
._sQlen
;
2225 buff
[offset
] = 4; // msg-type
2226 buff
[offset
+ 1] = down
;
2228 buff
[offset
+ 2] = 0;
2229 buff
[offset
+ 3] = 0;
2231 buff
[offset
+ 4] = (keysym
>> 24);
2232 buff
[offset
+ 5] = (keysym
>> 16);
2233 buff
[offset
+ 6] = (keysym
>> 8);
2234 buff
[offset
+ 7] = keysym
;
2240 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
2241 function getRFBkeycode(xtScanCode
) {
2242 const upperByte
= (keycode
>> 8);
2243 const lowerByte
= (keycode
& 0x00ff);
2244 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
2245 return lowerByte
| 0x80;
2250 const buff
= sock
._sQ
;
2251 const offset
= sock
._sQlen
;
2253 buff
[offset
] = 255; // msg-type
2254 buff
[offset
+ 1] = 0; // sub msg-type
2256 buff
[offset
+ 2] = (down
>> 8);
2257 buff
[offset
+ 3] = down
;
2259 buff
[offset
+ 4] = (keysym
>> 24);
2260 buff
[offset
+ 5] = (keysym
>> 16);
2261 buff
[offset
+ 6] = (keysym
>> 8);
2262 buff
[offset
+ 7] = keysym
;
2264 const RFBkeycode
= getRFBkeycode(keycode
);
2266 buff
[offset
+ 8] = (RFBkeycode
>> 24);
2267 buff
[offset
+ 9] = (RFBkeycode
>> 16);
2268 buff
[offset
+ 10] = (RFBkeycode
>> 8);
2269 buff
[offset
+ 11] = RFBkeycode
;
2275 pointerEvent(sock
, x
, y
, mask
) {
2276 const buff
= sock
._sQ
;
2277 const offset
= sock
._sQlen
;
2279 buff
[offset
] = 5; // msg-type
2281 buff
[offset
+ 1] = mask
;
2283 buff
[offset
+ 2] = x
>> 8;
2284 buff
[offset
+ 3] = x
;
2286 buff
[offset
+ 4] = y
>> 8;
2287 buff
[offset
+ 5] = y
;
2293 // Used to build Notify and Request data.
2294 _buildExtendedClipboardFlags(actions
, formats
) {
2295 let data
= new Uint8Array(4);
2296 let formatFlag
= 0x00000000;
2297 let actionFlag
= 0x00000000;
2299 for (let i
= 0; i
< actions
.length
; i
++) {
2300 actionFlag
|= actions
[i
];
2303 for (let i
= 0; i
< formats
.length
; i
++) {
2304 formatFlag
|= formats
[i
];
2307 data
[0] = actionFlag
>> 24; // Actions
2308 data
[1] = 0x00; // Reserved
2309 data
[2] = 0x00; // Reserved
2310 data
[3] = formatFlag
; // Formats
2315 extendedClipboardProvide(sock
, formats
, inData
) {
2316 // Deflate incomming data and their sizes
2317 let deflator
= new Deflator();
2318 let dataToDeflate
= [];
2320 for (let i
= 0; i
< formats
.length
; i
++) {
2321 // We only support the format Text at this time
2322 if (formats
[i
] != extendedClipboardFormatText
) {
2323 throw new Error("Unsupported extended clipboard format for Provide message.");
2326 // Change lone \r or \n into \r\n as defined in rfbproto
2327 inData
[i
] = inData
[i
].replace(/\r\n|\r|\n/gm, "\r\n");
2329 // Check if it already has \0
2330 let text
= encodeUTF8(inData
[i
] + "\0");
2332 dataToDeflate
.push( (text
.length
>> 24) & 0xFF,
2333 (text
.length
>> 16) & 0xFF,
2334 (text
.length
>> 8) & 0xFF,
2335 (text
.length
& 0xFF));
2337 for (let j
= 0; j
< text
.length
; j
++) {
2338 dataToDeflate
.push(text
.charCodeAt(j
));
2342 let deflatedData
= deflator
.deflate(new Uint8Array(dataToDeflate
));
2344 // Build data to send
2345 let data
= new Uint8Array(4 + deflatedData
.length
);
2346 data
.set(RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionProvide
],
2348 data
.set(deflatedData
, 4);
2350 RFB
.messages
.clientCutText(sock
, data
, true);
2353 extendedClipboardNotify(sock
, formats
) {
2354 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionNotify
],
2356 RFB
.messages
.clientCutText(sock
, flags
, true);
2359 extendedClipboardRequest(sock
, formats
) {
2360 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionRequest
],
2362 RFB
.messages
.clientCutText(sock
, flags
, true);
2365 extendedClipboardCaps(sock
, actions
, formats
) {
2366 let formatKeys
= Object
.keys(formats
);
2367 let data
= new Uint8Array(4 + (4 * formatKeys
.length
));
2369 formatKeys
.map(x
=> parseInt(x
));
2370 formatKeys
.sort((a
, b
) => a
- b
);
2372 data
.set(RFB
.messages
._buildExtendedClipboardFlags(actions
, []));
2375 for (let i
= 0; i
< formatKeys
.length
; i
++) {
2376 data
[loopOffset
] = formats
[formatKeys
[i
]] >> 24;
2377 data
[loopOffset
+ 1] = formats
[formatKeys
[i
]] >> 16;
2378 data
[loopOffset
+ 2] = formats
[formatKeys
[i
]] >> 8;
2379 data
[loopOffset
+ 3] = formats
[formatKeys
[i
]] >> 0;
2382 data
[3] |= (1 << formatKeys
[i
]); // Update our format flags
2385 RFB
.messages
.clientCutText(sock
, data
, true);
2388 clientCutText(sock
, data
, extended
= false) {
2389 const buff
= sock
._sQ
;
2390 const offset
= sock
._sQlen
;
2392 buff
[offset
] = 6; // msg-type
2394 buff
[offset
+ 1] = 0; // padding
2395 buff
[offset
+ 2] = 0; // padding
2396 buff
[offset
+ 3] = 0; // padding
2400 length
= toUnsigned32bit(-data
.length
);
2402 length
= data
.length
;
2405 buff
[offset
+ 4] = length
>> 24;
2406 buff
[offset
+ 5] = length
>> 16;
2407 buff
[offset
+ 6] = length
>> 8;
2408 buff
[offset
+ 7] = length
;
2412 // We have to keep track of from where in the data we begin creating the
2413 // buffer for the flush in the next iteration.
2416 let remaining
= data
.length
;
2417 while (remaining
> 0) {
2419 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
2420 for (let i
= 0; i
< flushSize
; i
++) {
2421 buff
[sock
._sQlen
+ i
] = data
[dataOffset
+ i
];
2424 sock
._sQlen
+= flushSize
;
2427 remaining
-= flushSize
;
2428 dataOffset
+= flushSize
;
2433 setDesktopSize(sock
, width
, height
, id
, flags
) {
2434 const buff
= sock
._sQ
;
2435 const offset
= sock
._sQlen
;
2437 buff
[offset
] = 251; // msg-type
2438 buff
[offset
+ 1] = 0; // padding
2439 buff
[offset
+ 2] = width
>> 8; // width
2440 buff
[offset
+ 3] = width
;
2441 buff
[offset
+ 4] = height
>> 8; // height
2442 buff
[offset
+ 5] = height
;
2444 buff
[offset
+ 6] = 1; // number-of-screens
2445 buff
[offset
+ 7] = 0; // padding
2448 buff
[offset
+ 8] = id
>> 24; // id
2449 buff
[offset
+ 9] = id
>> 16;
2450 buff
[offset
+ 10] = id
>> 8;
2451 buff
[offset
+ 11] = id
;
2452 buff
[offset
+ 12] = 0; // x-position
2453 buff
[offset
+ 13] = 0;
2454 buff
[offset
+ 14] = 0; // y-position
2455 buff
[offset
+ 15] = 0;
2456 buff
[offset
+ 16] = width
>> 8; // width
2457 buff
[offset
+ 17] = width
;
2458 buff
[offset
+ 18] = height
>> 8; // height
2459 buff
[offset
+ 19] = height
;
2460 buff
[offset
+ 20] = flags
>> 24; // flags
2461 buff
[offset
+ 21] = flags
>> 16;
2462 buff
[offset
+ 22] = flags
>> 8;
2463 buff
[offset
+ 23] = flags
;
2469 clientFence(sock
, flags
, payload
) {
2470 const buff
= sock
._sQ
;
2471 const offset
= sock
._sQlen
;
2473 buff
[offset
] = 248; // msg-type
2475 buff
[offset
+ 1] = 0; // padding
2476 buff
[offset
+ 2] = 0; // padding
2477 buff
[offset
+ 3] = 0; // padding
2479 buff
[offset
+ 4] = flags
>> 24; // flags
2480 buff
[offset
+ 5] = flags
>> 16;
2481 buff
[offset
+ 6] = flags
>> 8;
2482 buff
[offset
+ 7] = flags
;
2484 const n
= payload
.length
;
2486 buff
[offset
+ 8] = n
; // length
2488 for (let i
= 0; i
< n
; i
++) {
2489 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
2492 sock
._sQlen
+= 9 + n
;
2496 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
2497 const buff
= sock
._sQ
;
2498 const offset
= sock
._sQlen
;
2500 buff
[offset
] = 150; // msg-type
2501 buff
[offset
+ 1] = enable
; // enable-flag
2503 buff
[offset
+ 2] = x
>> 8; // x
2504 buff
[offset
+ 3] = x
;
2505 buff
[offset
+ 4] = y
>> 8; // y
2506 buff
[offset
+ 5] = y
;
2507 buff
[offset
+ 6] = width
>> 8; // width
2508 buff
[offset
+ 7] = width
;
2509 buff
[offset
+ 8] = height
>> 8; // height
2510 buff
[offset
+ 9] = height
;
2516 pixelFormat(sock
, depth
, trueColor
) {
2517 const buff
= sock
._sQ
;
2518 const offset
= sock
._sQlen
;
2524 } else if (depth
> 8) {
2530 const bits
= Math
.floor(depth
/3);
2532 buff
[offset
] = 0; // msg-type
2534 buff
[offset
+ 1] = 0; // padding
2535 buff
[offset
+ 2] = 0; // padding
2536 buff
[offset
+ 3] = 0; // padding
2538 buff
[offset
+ 4] = bpp
; // bits-per-pixel
2539 buff
[offset
+ 5] = depth
; // depth
2540 buff
[offset
+ 6] = 0; // little-endian
2541 buff
[offset
+ 7] = trueColor
? 1 : 0; // true-color
2543 buff
[offset
+ 8] = 0; // red-max
2544 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
2546 buff
[offset
+ 10] = 0; // green-max
2547 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
2549 buff
[offset
+ 12] = 0; // blue-max
2550 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
2552 buff
[offset
+ 14] = bits
* 2; // red-shift
2553 buff
[offset
+ 15] = bits
* 1; // green-shift
2554 buff
[offset
+ 16] = bits
* 0; // blue-shift
2556 buff
[offset
+ 17] = 0; // padding
2557 buff
[offset
+ 18] = 0; // padding
2558 buff
[offset
+ 19] = 0; // padding
2564 clientEncodings(sock
, encodings
) {
2565 const buff
= sock
._sQ
;
2566 const offset
= sock
._sQlen
;
2568 buff
[offset
] = 2; // msg-type
2569 buff
[offset
+ 1] = 0; // padding
2571 buff
[offset
+ 2] = encodings
.length
>> 8;
2572 buff
[offset
+ 3] = encodings
.length
;
2575 for (let i
= 0; i
< encodings
.length
; i
++) {
2576 const enc
= encodings
[i
];
2577 buff
[j
] = enc
>> 24;
2578 buff
[j
+ 1] = enc
>> 16;
2579 buff
[j
+ 2] = enc
>> 8;
2585 sock
._sQlen
+= j
- offset
;
2589 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
2590 const buff
= sock
._sQ
;
2591 const offset
= sock
._sQlen
;
2593 if (typeof(x
) === "undefined") { x
= 0; }
2594 if (typeof(y
) === "undefined") { y
= 0; }
2596 buff
[offset
] = 3; // msg-type
2597 buff
[offset
+ 1] = incremental
? 1 : 0;
2599 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2600 buff
[offset
+ 3] = x
& 0xFF;
2602 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2603 buff
[offset
+ 5] = y
& 0xFF;
2605 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2606 buff
[offset
+ 7] = w
& 0xFF;
2608 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2609 buff
[offset
+ 9] = h
& 0xFF;
2615 xvpOp(sock
, ver
, op
) {
2616 const buff
= sock
._sQ
;
2617 const offset
= sock
._sQlen
;
2619 buff
[offset
] = 250; // msg-type
2620 buff
[offset
+ 1] = 0; // padding
2622 buff
[offset
+ 2] = ver
;
2623 buff
[offset
+ 3] = op
;
2632 rgbaPixels
: new Uint8Array(),
2638 /* eslint-disable indent */
2639 rgbaPixels
: new Uint8Array([
2640 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2641 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2642 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2644 /* eslint-enable indent */