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._rfb_credentials
= options
.credentials
|| {};
73 this._shared
= 'shared' in options
? !!options
.shared
: true;
74 this._repeaterID
= options
.repeaterID
|| '';
75 this._wsProtocols
= options
.wsProtocols
|| [];
78 this._rfb_connection_state
= '';
79 this._rfb_init_state
= '';
80 this._rfb_auth_scheme
= -1;
81 this._rfb_clean_disconnect
= true;
83 // Server capabilities
84 this._rfb_version
= 0;
85 this._rfb_max_version
= 3.8;
86 this._rfb_tightvnc
= false;
87 this._rfb_vencrypt_state
= 0;
88 this._rfb_xvp_ver
= 0;
95 this._capabilities
= { power
: false };
97 this._supportsFence
= false;
99 this._supportsContinuousUpdates
= false;
100 this._enabledContinuousUpdates
= false;
102 this._supportsSetDesktopSize
= false;
104 this._screen_flags
= 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._mouse_buttonMask
= 0;
137 this._mouse_arr
= [];
138 this._viewportDragging
= false;
139 this._viewportDragPos
= {};
140 this._viewportHasMoved
= false;
142 // Bound event handlers
143 this._eventHandlers
= {
144 focusCanvas
: this._focusCanvas
.bind(this),
145 windowResize
: this._windowResize
.bind(this),
149 Log
.Debug(">> RFB.constructor");
151 // Create DOM elements
152 this._screen
= document
.createElement('div');
153 this._screen
.style
.display
= 'flex';
154 this._screen
.style
.width
= '100%';
155 this._screen
.style
.height
= '100%';
156 this._screen
.style
.overflow
= 'auto';
157 this._screen
.style
.background
= DEFAULT_BACKGROUND
;
158 this._canvas
= document
.createElement('canvas');
159 this._canvas
.style
.margin
= 'auto';
160 // Some browsers add an outline on focus
161 this._canvas
.style
.outline
= 'none';
162 // IE miscalculates width without this :(
163 this._canvas
.style
.flexShrink
= '0';
164 this._canvas
.width
= 0;
165 this._canvas
.height
= 0;
166 this._canvas
.tabIndex
= -1;
167 this._screen
.appendChild(this._canvas
);
170 this._cursor
= new Cursor();
172 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
173 // it. Result: no cursor at all until a window border or an edit field
174 // is hit blindly. But there are also VNC servers that draw the cursor
175 // in the framebuffer and don't send the empty local cursor. There is
176 // no way to satisfy both sides.
178 // The spec is unclear on this "initial cursor" issue. Many other
179 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
180 // initial cursor instead.
181 this._cursorImage
= RFB
.cursors
.none
;
183 // populate decoder array with objects
184 this._decoders
[encodings
.encodingRaw
] = new RawDecoder();
185 this._decoders
[encodings
.encodingCopyRect
] = new CopyRectDecoder();
186 this._decoders
[encodings
.encodingRRE
] = new RREDecoder();
187 this._decoders
[encodings
.encodingHextile
] = new HextileDecoder();
188 this._decoders
[encodings
.encodingTight
] = new TightDecoder();
189 this._decoders
[encodings
.encodingTightPNG
] = new TightPNGDecoder();
191 // NB: nothing that needs explicit teardown should be done
192 // before this point, since this can throw an exception
194 this._display
= new Display(this._canvas
);
196 Log
.Error("Display exception: " + exc
);
199 this._display
.onflush
= this._onFlush
.bind(this);
201 this._keyboard
= new Keyboard(this._canvas
);
202 this._keyboard
.onkeyevent
= this._handleKeyEvent
.bind(this);
204 this._mouse
= new Mouse(this._canvas
);
205 this._mouse
.onmousebutton
= this._handleMouseButton
.bind(this);
206 this._mouse
.onmousemove
= this._handleMouseMove
.bind(this);
208 this._sock
= new Websock();
209 this._sock
.on('message', () => {
210 this._handle_message();
212 this._sock
.on('open', () => {
213 if ((this._rfb_connection_state
=== 'connecting') &&
214 (this._rfb_init_state
=== '')) {
215 this._rfb_init_state
= 'ProtocolVersion';
216 Log
.Debug("Starting VNC handshake");
218 this._fail("Unexpected server connection while " +
219 this._rfb_connection_state
);
222 this._sock
.on('close', (e
) => {
223 Log
.Debug("WebSocket on-close event");
226 msg
= "(code: " + e
.code
;
228 msg
+= ", reason: " + e
.reason
;
232 switch (this._rfb_connection_state
) {
234 this._fail("Connection closed " + msg
);
237 // Handle disconnects that were initiated server-side
238 this._updateConnectionState('disconnecting');
239 this._updateConnectionState('disconnected');
241 case 'disconnecting':
242 // Normal disconnection path
243 this._updateConnectionState('disconnected');
246 this._fail("Unexpected server disconnect " +
247 "when already disconnected " + msg
);
250 this._fail("Unexpected server disconnect before connecting " +
254 this._sock
.off('close');
256 this._sock
.on('error', e
=> Log
.Warn("WebSocket on-error event"));
258 // Slight delay of the actual connection so that the caller has
259 // time to set up callbacks
260 setTimeout(this._updateConnectionState
.bind(this, 'connecting'));
262 Log
.Debug("<< RFB.constructor");
264 // ===== PROPERTIES =====
266 this.dragViewport
= false;
267 this.focusOnClick
= true;
269 this._viewOnly
= false;
270 this._clipViewport
= false;
271 this._scaleViewport
= false;
272 this._resizeSession
= false;
274 this._showDotCursor
= false;
275 if (options
.showDotCursor
!== undefined) {
276 Log
.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
277 this._showDotCursor
= options
.showDotCursor
;
280 this._qualityLevel
= 6;
281 this._compressionLevel
= 2;
284 // ===== PROPERTIES =====
286 get viewOnly() { return this._viewOnly
; }
287 set viewOnly(viewOnly
) {
288 this._viewOnly
= viewOnly
;
290 if (this._rfb_connection_state
=== "connecting" ||
291 this._rfb_connection_state
=== "connected") {
293 this._keyboard
.ungrab();
294 this._mouse
.ungrab();
296 this._keyboard
.grab();
302 get capabilities() { return this._capabilities
; }
304 get touchButton() { return this._mouse
.touchButton
; }
305 set touchButton(button
) { this._mouse
.touchButton
= button
; }
307 get clipViewport() { return this._clipViewport
; }
308 set clipViewport(viewport
) {
309 this._clipViewport
= viewport
;
313 get scaleViewport() { return this._scaleViewport
; }
314 set scaleViewport(scale
) {
315 this._scaleViewport
= scale
;
316 // Scaling trumps clipping, so we may need to adjust
317 // clipping when enabling or disabling scaling
318 if (scale
&& this._clipViewport
) {
322 if (!scale
&& this._clipViewport
) {
327 get resizeSession() { return this._resizeSession
; }
328 set resizeSession(resize
) {
329 this._resizeSession
= resize
;
331 this._requestRemoteResize();
335 get showDotCursor() { return this._showDotCursor
; }
336 set showDotCursor(show
) {
337 this._showDotCursor
= show
;
338 this._refreshCursor();
341 get background() { return this._screen
.style
.background
; }
342 set background(cssValue
) { this._screen
.style
.background
= cssValue
; }
345 return this._qualityLevel
;
347 set qualityLevel(qualityLevel
) {
348 if (!Number
.isInteger(qualityLevel
) || qualityLevel
< 0 || qualityLevel
> 9) {
349 Log
.Error("qualityLevel must be an integer between 0 and 9");
353 if (this._qualityLevel
=== qualityLevel
) {
357 this._qualityLevel
= qualityLevel
;
359 if (this._rfb_connection_state
=== 'connected') {
360 this._sendEncodings();
364 get compressionLevel() {
365 return this._compressionLevel
;
367 set compressionLevel(compressionLevel
) {
368 if (!Number
.isInteger(compressionLevel
) || compressionLevel
< 0 || compressionLevel
> 9) {
369 Log
.Error("compressionLevel must be an integer between 0 and 9");
373 if (this._compressionLevel
=== compressionLevel
) {
377 this._compressionLevel
= compressionLevel
;
379 if (this._rfb_connection_state
=== 'connected') {
380 this._sendEncodings();
384 // ===== PUBLIC METHODS =====
387 this._updateConnectionState('disconnecting');
388 this._sock
.off('error');
389 this._sock
.off('message');
390 this._sock
.off('open');
393 sendCredentials(creds
) {
394 this._rfb_credentials
= creds
;
395 setTimeout(this._init_msg
.bind(this), 0);
399 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
400 Log
.Info("Sending Ctrl-Alt-Del");
402 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
403 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
404 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
405 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
406 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
407 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
422 // Send a key press. If 'down' is not specified then send a down key
423 // followed by an up key.
424 sendKey(keysym
, code
, down
) {
425 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
427 if (down
=== undefined) {
428 this.sendKey(keysym
, code
, true);
429 this.sendKey(keysym
, code
, false);
433 const scancode
= XtScancode
[code
];
435 if (this._qemuExtKeyEventSupported
&& scancode
) {
437 keysym
= keysym
|| 0;
439 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
441 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
446 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
447 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
452 this._canvas
.focus();
459 clipboardPasteFrom(text
) {
460 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
462 if (this._clipboardServerCapabilitiesFormats
[extendedClipboardFormatText
] &&
463 this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
465 this._clipboardText
= text
;
466 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
468 let data
= new Uint8Array(text
.length
);
469 for (let i
= 0; i
< text
.length
; i
++) {
470 // FIXME: text can have values outside of Latin1/Uint8
471 data
[i
] = text
.charCodeAt(i
);
474 RFB
.messages
.clientCutText(this._sock
, data
);
478 // ===== PRIVATE METHODS =====
481 Log
.Debug(">> RFB.connect");
483 Log
.Info("connecting to " + this._url
);
486 // WebSocket.onopen transitions to the RFB init states
487 this._sock
.open(this._url
, this._wsProtocols
);
489 if (e
.name
=== 'SyntaxError') {
490 this._fail("Invalid host or port (" + e
+ ")");
492 this._fail("Error when opening socket (" + e
+ ")");
496 // Make our elements part of the page
497 this._target
.appendChild(this._screen
);
499 this._cursor
.attach(this._canvas
);
500 this._refreshCursor();
502 // Monitor size changes of the screen
503 // FIXME: Use ResizeObserver, or hidden overflow
504 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
506 // Always grab focus on some kind of click event
507 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
508 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
510 Log
.Debug("<< RFB.connect");
514 Log
.Debug(">> RFB.disconnect");
515 this._cursor
.detach();
516 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
517 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
518 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
519 this._keyboard
.ungrab();
520 this._mouse
.ungrab();
523 this._target
.removeChild(this._screen
);
525 if (e
.name
=== 'NotFoundError') {
526 // Some cases where the initial connection fails
527 // can disconnect before the _screen is created
532 clearTimeout(this._resizeTimeout
);
533 Log
.Debug("<< RFB.disconnect");
536 _focusCanvas(event
) {
537 // Respect earlier handlers' request to not do side-effects
538 if (event
.defaultPrevented
) {
542 if (!this.focusOnClick
) {
549 _setDesktopName(name
) {
550 this._fb_name
= name
;
551 this.dispatchEvent(new CustomEvent(
553 { detail
: { name
: this._fb_name
} }));
556 _windowResize(event
) {
557 // If the window resized then our screen element might have
558 // as well. Update the viewport dimensions.
559 window
.requestAnimationFrame(() => {
564 if (this._resizeSession
) {
565 // Request changing the resolution of the remote display to
566 // the size of the local browser viewport.
568 // In order to not send multiple requests before the browser-resize
569 // is finished we wait 0.5 seconds before sending the request.
570 clearTimeout(this._resizeTimeout
);
571 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
575 // Update state of clipping in Display object, and make sure the
576 // configured viewport matches the current screen size
578 const cur_clip
= this._display
.clipViewport
;
579 let new_clip
= this._clipViewport
;
581 if (this._scaleViewport
) {
582 // Disable viewport clipping if we are scaling
586 if (cur_clip
!== new_clip
) {
587 this._display
.clipViewport
= new_clip
;
591 // When clipping is enabled, the screen is limited to
592 // the size of the container.
593 const size
= this._screenSize();
594 this._display
.viewportChangeSize(size
.w
, size
.h
);
595 this._fixScrollbars();
600 if (!this._scaleViewport
) {
601 this._display
.scale
= 1.0;
603 const size
= this._screenSize();
604 this._display
.autoscale(size
.w
, size
.h
);
606 this._fixScrollbars();
609 // Requests a change of remote desktop size. This message is an extension
610 // and may only be sent if we have received an ExtendedDesktopSize message
611 _requestRemoteResize() {
612 clearTimeout(this._resizeTimeout
);
613 this._resizeTimeout
= null;
615 if (!this._resizeSession
|| this._viewOnly
||
616 !this._supportsSetDesktopSize
) {
620 const size
= this._screenSize();
621 RFB
.messages
.setDesktopSize(this._sock
,
622 Math
.floor(size
.w
), Math
.floor(size
.h
),
623 this._screen_id
, this._screen_flags
);
625 Log
.Debug('Requested new desktop size: ' +
626 size
.w
+ 'x' + size
.h
);
629 // Gets the the size of the available screen
631 let r
= this._screen
.getBoundingClientRect();
632 return { w
: r
.width
, h
: r
.height
};
636 // This is a hack because Chrome screws up the calculation
637 // for when scrollbars are needed. So to fix it we temporarily
638 // toggle them off and on.
639 const orig
= this._screen
.style
.overflow
;
640 this._screen
.style
.overflow
= 'hidden';
641 // Force Chrome to recalculate the layout by asking for
642 // an element's dimensions
643 this._screen
.getBoundingClientRect();
644 this._screen
.style
.overflow
= orig
;
652 * disconnected - permanent state
654 _updateConnectionState(state
) {
655 const oldstate
= this._rfb_connection_state
;
657 if (state
=== oldstate
) {
658 Log
.Debug("Already in state '" + state
+ "', ignoring");
662 // The 'disconnected' state is permanent for each RFB object
663 if (oldstate
=== 'disconnected') {
664 Log
.Error("Tried changing state of a disconnected RFB object");
668 // Ensure proper transitions before doing anything
671 if (oldstate
!== 'connecting') {
672 Log
.Error("Bad transition to connected state, " +
673 "previous connection state: " + oldstate
);
679 if (oldstate
!== 'disconnecting') {
680 Log
.Error("Bad transition to disconnected state, " +
681 "previous connection state: " + oldstate
);
687 if (oldstate
!== '') {
688 Log
.Error("Bad transition to connecting state, " +
689 "previous connection state: " + oldstate
);
694 case 'disconnecting':
695 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
696 Log
.Error("Bad transition to disconnecting state, " +
697 "previous connection state: " + oldstate
);
703 Log
.Error("Unknown connection state: " + state
);
707 // State change actions
709 this._rfb_connection_state
= state
;
711 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
713 if (this._disconnTimer
&& state
!== 'disconnecting') {
714 Log
.Debug("Clearing disconnect timer");
715 clearTimeout(this._disconnTimer
);
716 this._disconnTimer
= null;
718 // make sure we don't get a double event
719 this._sock
.off('close');
728 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
731 case 'disconnecting':
734 this._disconnTimer
= setTimeout(() => {
735 Log
.Error("Disconnection timed out.");
736 this._updateConnectionState('disconnected');
737 }, DISCONNECT_TIMEOUT
* 1000);
741 this.dispatchEvent(new CustomEvent(
742 "disconnect", { detail
:
743 { clean
: this._rfb_clean_disconnect
} }));
748 /* Print errors and disconnect
750 * The parameter 'details' is used for information that
751 * should be logged but not sent to the user interface.
754 switch (this._rfb_connection_state
) {
755 case 'disconnecting':
756 Log
.Error("Failed when disconnecting: " + details
);
759 Log
.Error("Failed while connected: " + details
);
762 Log
.Error("Failed when connecting: " + details
);
765 Log
.Error("RFB failure: " + details
);
768 this._rfb_clean_disconnect
= false; //This is sent to the UI
770 // Transition to disconnected without waiting for socket to close
771 this._updateConnectionState('disconnecting');
772 this._updateConnectionState('disconnected');
777 _setCapability(cap
, val
) {
778 this._capabilities
[cap
] = val
;
779 this.dispatchEvent(new CustomEvent("capabilities",
780 { detail
: { capabilities
: this._capabilities
} }));
784 if (this._sock
.rQlen
=== 0) {
785 Log
.Warn("handle_message called on an empty receive queue");
789 switch (this._rfb_connection_state
) {
791 Log
.Error("Got data while disconnected");
795 if (this._flushing
) {
798 if (!this._normal_msg()) {
801 if (this._sock
.rQlen
=== 0) {
812 _handleKeyEvent(keysym
, code
, down
) {
813 this.sendKey(keysym
, code
, down
);
816 _handleMouseButton(x
, y
, down
, bmask
) {
818 this._mouse_buttonMask
|= bmask
;
820 this._mouse_buttonMask
&= ~bmask
;
823 if (this.dragViewport
) {
824 if (down
&& !this._viewportDragging
) {
825 this._viewportDragging
= true;
826 this._viewportDragPos
= {'x': x
, 'y': y
};
827 this._viewportHasMoved
= false;
829 // Skip sending mouse events
832 this._viewportDragging
= false;
834 // If we actually performed a drag then we are done
835 // here and should not send any mouse events
836 if (this._viewportHasMoved
) {
840 // Otherwise we treat this as a mouse click event.
841 // Send the button down event here, as the button up
842 // event is sent at the end of this function.
843 RFB
.messages
.pointerEvent(this._sock
,
844 this._display
.absX(x
),
845 this._display
.absY(y
),
850 if (this._viewOnly
) { return; } // View only, skip mouse events
852 if (this._rfb_connection_state
!== 'connected') { return; }
853 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
856 _handleMouseMove(x
, y
) {
857 if (this._viewportDragging
) {
858 const deltaX
= this._viewportDragPos
.x
- x
;
859 const deltaY
= this._viewportDragPos
.y
- y
;
861 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
862 Math
.abs(deltaY
) > dragThreshold
)) {
863 this._viewportHasMoved
= true;
865 this._viewportDragPos
= {'x': x
, 'y': y
};
866 this._display
.viewportChangePos(deltaX
, deltaY
);
869 // Skip sending mouse events
873 if (this._viewOnly
) { return; } // View only, skip mouse events
875 if (this._rfb_connection_state
!== 'connected') { return; }
876 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
881 _negotiate_protocol_version() {
882 if (this._sock
.rQwait("version", 12)) {
886 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
887 Log
.Info("Server ProtocolVersion: " + sversion
);
890 case "000.000": // UltraVNC repeater
894 case "003.006": // UltraVNC
895 case "003.889": // Apple Remote Desktop
896 this._rfb_version
= 3.3;
899 this._rfb_version
= 3.7;
902 case "004.000": // Intel AMT KVM
903 case "004.001": // RealVNC 4.6
904 case "005.000": // RealVNC 5.3
905 this._rfb_version
= 3.8;
908 return this._fail("Invalid server version " + sversion
);
912 let repeaterID
= "ID:" + this._repeaterID
;
913 while (repeaterID
.length
< 250) {
916 this._sock
.send_string(repeaterID
);
920 if (this._rfb_version
> this._rfb_max_version
) {
921 this._rfb_version
= this._rfb_max_version
;
924 const cversion
= "00" + parseInt(this._rfb_version
, 10) +
925 ".00" + ((this._rfb_version
* 10) % 10);
926 this._sock
.send_string("RFB " + cversion
+ "\n");
927 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
929 this._rfb_init_state
= 'Security';
932 _negotiate_security() {
933 // Polyfill since IE and PhantomJS doesn't have
934 // TypedArray.includes()
935 function includes(item
, array
) {
936 for (let i
= 0; i
< array
.length
; i
++) {
937 if (array
[i
] === item
) {
944 if (this._rfb_version
>= 3.7) {
945 // Server sends supported list, client decides
946 const num_types
= this._sock
.rQshift8();
947 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
949 if (num_types
=== 0) {
950 this._rfb_init_state
= "SecurityReason";
951 this._security_context
= "no security types";
952 this._security_status
= 1;
953 return this._init_msg();
956 const types
= this._sock
.rQshiftBytes(num_types
);
957 Log
.Debug("Server security types: " + types
);
959 // Look for each auth in preferred order
960 if (includes(1, types
)) {
961 this._rfb_auth_scheme
= 1; // None
962 } else if (includes(22, types
)) {
963 this._rfb_auth_scheme
= 22; // XVP
964 } else if (includes(16, types
)) {
965 this._rfb_auth_scheme
= 16; // Tight
966 } else if (includes(2, types
)) {
967 this._rfb_auth_scheme
= 2; // VNC Auth
968 } else if (includes(19, types
)) {
969 this._rfb_auth_scheme
= 19; // VeNCrypt Auth
971 return this._fail("Unsupported security types (types: " + types
+ ")");
974 this._sock
.send([this._rfb_auth_scheme
]);
977 if (this._sock
.rQwait("security scheme", 4)) { return false; }
978 this._rfb_auth_scheme
= this._sock
.rQshift32();
980 if (this._rfb_auth_scheme
== 0) {
981 this._rfb_init_state
= "SecurityReason";
982 this._security_context
= "authentication scheme";
983 this._security_status
= 1;
984 return this._init_msg();
988 this._rfb_init_state
= 'Authentication';
989 Log
.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme
);
991 return this._init_msg(); // jump to authentication
994 _handle_security_reason() {
995 if (this._sock
.rQwait("reason length", 4)) {
998 const strlen
= this._sock
.rQshift32();
1002 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
1003 reason
= this._sock
.rQshiftStr(strlen
);
1006 if (reason
!== "") {
1007 this.dispatchEvent(new CustomEvent(
1009 { detail
: { status
: this._security_status
,
1010 reason
: reason
} }));
1012 return this._fail("Security negotiation failed on " +
1013 this._security_context
+
1014 " (reason: " + reason
+ ")");
1016 this.dispatchEvent(new CustomEvent(
1018 { detail
: { status
: this._security_status
} }));
1020 return this._fail("Security negotiation failed on " +
1021 this._security_context
);
1026 _negotiate_xvp_auth() {
1027 if (this._rfb_credentials
.username
=== undefined ||
1028 this._rfb_credentials
.password
=== undefined ||
1029 this._rfb_credentials
.target
=== undefined) {
1030 this.dispatchEvent(new CustomEvent(
1031 "credentialsrequired",
1032 { detail
: { types
: ["username", "password", "target"] } }));
1036 const xvp_auth_str
= String
.fromCharCode(this._rfb_credentials
.username
.length
) +
1037 String
.fromCharCode(this._rfb_credentials
.target
.length
) +
1038 this._rfb_credentials
.username
+
1039 this._rfb_credentials
.target
;
1040 this._sock
.send_string(xvp_auth_str
);
1041 this._rfb_auth_scheme
= 2;
1042 return this._negotiate_authentication();
1045 // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
1046 _negotiate_vencrypt_auth() {
1048 // waiting for VeNCrypt version
1049 if (this._rfb_vencrypt_state
== 0) {
1050 if (this._sock
.rQwait("vencrypt version", 2)) { return false; }
1052 const major
= this._sock
.rQshift8();
1053 const minor
= this._sock
.rQshift8();
1055 if (!(major
== 0 && minor
== 2)) {
1056 return this._fail("Unsupported VeNCrypt version " + major
+ "." + minor
);
1059 this._sock
.send([0, 2]);
1060 this._rfb_vencrypt_state
= 1;
1064 if (this._rfb_vencrypt_state
== 1) {
1065 if (this._sock
.rQwait("vencrypt ack", 1)) { return false; }
1067 const res
= this._sock
.rQshift8();
1070 return this._fail("VeNCrypt failure " + res
);
1073 this._rfb_vencrypt_state
= 2;
1075 // must fall through here (i.e. no "else if"), beacause we may have already received
1076 // the subtypes length and won't be called again
1078 if (this._rfb_vencrypt_state
== 2) { // waiting for subtypes length
1079 if (this._sock
.rQwait("vencrypt subtypes length", 1)) { return false; }
1081 const subtypes_length
= this._sock
.rQshift8();
1082 if (subtypes_length
< 1) {
1083 return this._fail("VeNCrypt subtypes empty");
1086 this._rfb_vencrypt_subtypes_length
= subtypes_length
;
1087 this._rfb_vencrypt_state
= 3;
1090 // waiting for subtypes list
1091 if (this._rfb_vencrypt_state
== 3) {
1092 if (this._sock
.rQwait("vencrypt subtypes", 4 * this._rfb_vencrypt_subtypes_length
)) { return false; }
1094 const subtypes
= [];
1095 for (let i
= 0; i
< this._rfb_vencrypt_subtypes_length
; i
++) {
1096 subtypes
.push(this._sock
.rQshift32());
1099 // 256 = Plain subtype
1100 if (subtypes
.indexOf(256) != -1) {
1102 this._sock
.send([0, 0, 1, 0]);
1103 this._rfb_vencrypt_state
= 4;
1105 return this._fail("VeNCrypt Plain subtype not offered by server");
1109 // negotiated Plain subtype, server waits for password
1110 if (this._rfb_vencrypt_state
== 4) {
1111 if (!this._rfb_credentials
.username
||
1112 !this._rfb_credentials
.password
) {
1113 this.dispatchEvent(new CustomEvent(
1114 "credentialsrequired",
1115 { detail
: { types
: ["username", "password"] } }));
1119 const user
= encodeUTF8(this._rfb_credentials
.username
);
1120 const pass
= encodeUTF8(this._rfb_credentials
.password
);
1122 // XXX we assume lengths are <= 255 (should not be an issue in the real world)
1123 this._sock
.send([0, 0, 0, user
.length
]);
1124 this._sock
.send([0, 0, 0, pass
.length
]);
1125 this._sock
.send_string(user
);
1126 this._sock
.send_string(pass
);
1128 this._rfb_init_state
= "SecurityResult";
1133 _negotiate_std_vnc_auth() {
1134 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
1136 if (this._rfb_credentials
.password
=== undefined) {
1137 this.dispatchEvent(new CustomEvent(
1138 "credentialsrequired",
1139 { detail
: { types
: ["password"] } }));
1143 // TODO(directxman12): make genDES not require an Array
1144 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
1145 const response
= RFB
.genDES(this._rfb_credentials
.password
, challenge
);
1146 this._sock
.send(response
);
1147 this._rfb_init_state
= "SecurityResult";
1151 _negotiate_tight_unix_auth() {
1152 if (this._rfb_credentials
.username
=== undefined ||
1153 this._rfb_credentials
.password
=== undefined) {
1154 this.dispatchEvent(new CustomEvent(
1155 "credentialsrequired",
1156 { detail
: { types
: ["username", "password"] } }));
1160 this._sock
.send([0, 0, 0, this._rfb_credentials
.username
.length
]);
1161 this._sock
.send([0, 0, 0, this._rfb_credentials
.password
.length
]);
1162 this._sock
.send_string(this._rfb_credentials
.username
);
1163 this._sock
.send_string(this._rfb_credentials
.password
);
1164 this._rfb_init_state
= "SecurityResult";
1168 _negotiate_tight_tunnels(numTunnels
) {
1169 const clientSupportedTunnelTypes
= {
1170 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
1172 const serverSupportedTunnelTypes
= {};
1173 // receive tunnel capabilities
1174 for (let i
= 0; i
< numTunnels
; i
++) {
1175 const cap_code
= this._sock
.rQshift32();
1176 const cap_vendor
= this._sock
.rQshiftStr(4);
1177 const cap_signature
= this._sock
.rQshiftStr(8);
1178 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
1181 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
1183 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1184 // but forgets to advertise it. Try to detect such servers by
1185 // looking for their custom tunnel type.
1186 if (serverSupportedTunnelTypes
[1] &&
1187 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
1188 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
1189 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1190 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
1193 // choose the notunnel type
1194 if (serverSupportedTunnelTypes
[0]) {
1195 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
1196 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
1197 return this._fail("Client's tunnel type had the incorrect " +
1198 "vendor or signature");
1200 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1201 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1202 return false; // wait until we receive the sub auth count to continue
1204 return this._fail("Server wanted tunnels, but doesn't support " +
1205 "the notunnel type");
1209 _negotiate_tight_auth() {
1210 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
1211 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1212 const numTunnels
= this._sock
.rQshift32();
1213 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1215 this._rfb_tightvnc
= true;
1217 if (numTunnels
> 0) {
1218 this._negotiate_tight_tunnels(numTunnels
);
1219 return false; // wait until we receive the sub auth to continue
1223 // second pass, do the sub-auth negotiation
1224 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1225 const subAuthCount
= this._sock
.rQshift32();
1226 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1227 this._rfb_init_state
= 'SecurityResult';
1231 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1233 const clientSupportedTypes
= {
1239 const serverSupportedTypes
= [];
1241 for (let i
= 0; i
< subAuthCount
; i
++) {
1242 this._sock
.rQshift32(); // capNum
1243 const capabilities
= this._sock
.rQshiftStr(12);
1244 serverSupportedTypes
.push(capabilities
);
1247 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1249 for (let authType
in clientSupportedTypes
) {
1250 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1251 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1252 Log
.Debug("Selected authentication type: " + authType
);
1255 case 'STDVNOAUTH__': // no auth
1256 this._rfb_init_state
= 'SecurityResult';
1258 case 'STDVVNCAUTH_': // VNC auth
1259 this._rfb_auth_scheme
= 2;
1260 return this._init_msg();
1261 case 'TGHTULGNAUTH': // UNIX auth
1262 this._rfb_auth_scheme
= 129;
1263 return this._init_msg();
1265 return this._fail("Unsupported tiny auth scheme " +
1266 "(scheme: " + authType
+ ")");
1271 return this._fail("No supported sub-auth types!");
1274 _negotiate_authentication() {
1275 switch (this._rfb_auth_scheme
) {
1277 if (this._rfb_version
>= 3.8) {
1278 this._rfb_init_state
= 'SecurityResult';
1281 this._rfb_init_state
= 'ClientInitialisation';
1282 return this._init_msg();
1284 case 22: // XVP auth
1285 return this._negotiate_xvp_auth();
1287 case 2: // VNC authentication
1288 return this._negotiate_std_vnc_auth();
1290 case 16: // TightVNC Security Type
1291 return this._negotiate_tight_auth();
1293 case 19: // VeNCrypt Security Type
1294 return this._negotiate_vencrypt_auth();
1296 case 129: // TightVNC UNIX Security Type
1297 return this._negotiate_tight_unix_auth();
1300 return this._fail("Unsupported auth scheme (scheme: " +
1301 this._rfb_auth_scheme
+ ")");
1305 _handle_security_result() {
1306 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1308 const status
= this._sock
.rQshift32();
1310 if (status
=== 0) { // OK
1311 this._rfb_init_state
= 'ClientInitialisation';
1312 Log
.Debug('Authentication OK');
1313 return this._init_msg();
1315 if (this._rfb_version
>= 3.8) {
1316 this._rfb_init_state
= "SecurityReason";
1317 this._security_context
= "security result";
1318 this._security_status
= status
;
1319 return this._init_msg();
1321 this.dispatchEvent(new CustomEvent(
1323 { detail
: { status
: status
} }));
1325 return this._fail("Security handshake failed");
1330 _negotiate_server_init() {
1331 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1334 const width
= this._sock
.rQshift16();
1335 const height
= this._sock
.rQshift16();
1338 const bpp
= this._sock
.rQshift8();
1339 const depth
= this._sock
.rQshift8();
1340 const big_endian
= this._sock
.rQshift8();
1341 const true_color
= this._sock
.rQshift8();
1343 const red_max
= this._sock
.rQshift16();
1344 const green_max
= this._sock
.rQshift16();
1345 const blue_max
= this._sock
.rQshift16();
1346 const red_shift
= this._sock
.rQshift8();
1347 const green_shift
= this._sock
.rQshift8();
1348 const blue_shift
= this._sock
.rQshift8();
1349 this._sock
.rQskipBytes(3); // padding
1351 // NB(directxman12): we don't want to call any callbacks or print messages until
1352 // *after* we're past the point where we could backtrack
1354 /* Connection name/title */
1355 const name_length = this._sock.rQshift32();
1356 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1357 let name = this._sock.rQshiftStr(name_length);
1358 name = decodeUTF8(name, true);
1360 if (this._rfb_tightvnc) {
1361 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1362 // In TightVNC mode, ServerInit message is extended
1363 const numServerMessages = this._sock.rQshift16();
1364 const numClientMessages = this._sock.rQshift16();
1365 const numEncodings = this._sock.rQshift16();
1366 this._sock.rQskipBytes(2); // padding
1368 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1369 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1371 // we don't actually do anything with the capability information that TIGHT sends,
1372 // so we just skip the all of this.
1374 // TIGHT server message capabilities
1375 this._sock.rQskipBytes(16 * numServerMessages);
1377 // TIGHT client message capabilities
1378 this._sock.rQskipBytes(16 * numClientMessages);
1380 // TIGHT encoding capabilities
1381 this._sock.rQskipBytes(16 * numEncodings);
1384 // NB(directxman12): these are down here so that we don't run them multiple times
1386 Log.Info("Screen: " + width + "x" + height +
1387 ", bpp: " + bpp + ", depth: " + depth +
1388 ", big_endian: " + big_endian +
1389 ", true_color: " + true_color +
1390 ", red_max: " + red_max +
1391 ", green_max: " + green_max +
1392 ", blue_max: " + blue_max +
1393 ", red_shift: " + red_shift +
1394 ", green_shift: " + green_shift +
1395 ", blue_shift: " + blue_shift);
1397 // we're past the point where we could backtrack, so it's safe to call this
1398 this._setDesktopName(name);
1399 this._resize(width, height);
1401 if (!this._viewOnly) { this._keyboard.grab(); }
1402 if (!this._viewOnly) { this._mouse.grab(); }
1404 this._fb_depth = 24;
1406 if (this._fb_name === "Intel(r) AMT KVM") {
1407 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1411 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1412 this._sendEncodings();
1413 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1415 this._updateConnectionState('connected');
1422 // In preference order
1423 encs.push(encodings.encodingCopyRect);
1424 // Only supported with full depth support
1425 if (this._fb_depth == 24) {
1426 encs.push(encodings.encodingTight);
1427 encs.push(encodings.encodingTightPNG);
1428 encs.push(encodings.encodingHextile);
1429 encs.push(encodings.encodingRRE);
1431 encs.push(encodings.encodingRaw);
1433 // Psuedo-encoding settings
1434 encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
1435 encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
1437 encs.push(encodings.pseudoEncodingDesktopSize);
1438 encs.push(encodings.pseudoEncodingLastRect);
1439 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1440 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1441 encs.push(encodings.pseudoEncodingXvp);
1442 encs.push(encodings.pseudoEncodingFence);
1443 encs.push(encodings.pseudoEncodingContinuousUpdates);
1444 encs.push(encodings.pseudoEncodingDesktopName);
1445 encs.push(encodings.pseudoEncodingExtendedClipboard);
1447 if (this._fb_depth == 24) {
1448 encs.push(encodings.pseudoEncodingVMwareCursor);
1449 encs.push(encodings.pseudoEncodingCursor);
1452 RFB.messages.clientEncodings(this._sock, encs);
1455 /* RFB protocol initialization states:
1460 * ClientInitialization - not triggered by server message
1461 * ServerInitialization
1464 switch (this._rfb_init_state
) {
1465 case 'ProtocolVersion':
1466 return this._negotiate_protocol_version();
1469 return this._negotiate_security();
1471 case 'Authentication':
1472 return this._negotiate_authentication();
1474 case 'SecurityResult':
1475 return this._handle_security_result();
1477 case 'SecurityReason':
1478 return this._handle_security_reason();
1480 case 'ClientInitialisation':
1481 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1482 this._rfb_init_state
= 'ServerInitialisation';
1485 case 'ServerInitialisation':
1486 return this._negotiate_server_init();
1489 return this._fail("Unknown init state (state: " +
1490 this._rfb_init_state
+ ")");
1494 _handle_set_colour_map_msg() {
1495 Log
.Debug("SetColorMapEntries");
1497 return this._fail("Unexpected SetColorMapEntries message");
1500 _handle_server_cut_text() {
1501 Log
.Debug("ServerCutText");
1503 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1505 this._sock
.rQskipBytes(3); // Padding
1507 let length
= this._sock
.rQshift32();
1508 length
= toSigned32bit(length
);
1510 if (this._sock
.rQwait("ServerCutText content", Math
.abs(length
), 8)) { return false; }
1514 const text
= this._sock
.rQshiftStr(length
);
1515 if (this._viewOnly
) {
1519 this.dispatchEvent(new CustomEvent(
1521 { detail
: { text
: text
} }));
1525 length
= Math
.abs(length
);
1526 const flags
= this._sock
.rQshift32();
1527 let formats
= flags
& 0x0000FFFF;
1528 let actions
= flags
& 0xFF000000;
1530 let isCaps
= (!!(actions
& extendedClipboardActionCaps
));
1532 this._clipboardServerCapabilitiesFormats
= {};
1533 this._clipboardServerCapabilitiesActions
= {};
1535 // Update our server capabilities for Formats
1536 for (let i
= 0; i
<= 15; i
++) {
1539 // Check if format flag is set.
1540 if ((formats
& index
)) {
1541 this._clipboardServerCapabilitiesFormats
[index
] = true;
1542 // We don't send unsolicited clipboard, so we
1544 this._sock
.rQshift32();
1548 // Update our server capabilities for Actions
1549 for (let i
= 24; i
<= 31; i
++) {
1551 this._clipboardServerCapabilitiesActions
[index
] = !!(actions
& index
);
1554 /* Caps handling done, send caps with the clients
1555 capabilities set as a response */
1556 let clientActions
= [
1557 extendedClipboardActionCaps
,
1558 extendedClipboardActionRequest
,
1559 extendedClipboardActionPeek
,
1560 extendedClipboardActionNotify
,
1561 extendedClipboardActionProvide
1563 RFB
.messages
.extendedClipboardCaps(this._sock
, clientActions
, {extendedClipboardFormatText
: 0});
1565 } else if (actions
=== extendedClipboardActionRequest
) {
1566 if (this._viewOnly
) {
1570 // Check if server has told us it can handle Provide and there is clipboard data to send.
1571 if (this._clipboardText
!= null &&
1572 this._clipboardServerCapabilitiesActions
[extendedClipboardActionProvide
]) {
1574 if (formats
& extendedClipboardFormatText
) {
1575 RFB
.messages
.extendedClipboardProvide(this._sock
, [extendedClipboardFormatText
], [this._clipboardText
]);
1579 } else if (actions
=== extendedClipboardActionPeek
) {
1580 if (this._viewOnly
) {
1584 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
1586 if (this._clipboardText
!= null) {
1587 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
1589 RFB
.messages
.extendedClipboardNotify(this._sock
, []);
1593 } else if (actions
=== extendedClipboardActionNotify
) {
1594 if (this._viewOnly
) {
1598 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionRequest
]) {
1600 if (formats
& extendedClipboardFormatText
) {
1601 RFB
.messages
.extendedClipboardRequest(this._sock
, [extendedClipboardFormatText
]);
1605 } else if (actions
=== extendedClipboardActionProvide
) {
1606 if (this._viewOnly
) {
1610 if (!(formats
& extendedClipboardFormatText
)) {
1613 // Ignore what we had in our clipboard client side.
1614 this._clipboardText
= null;
1616 // FIXME: Should probably verify that this data was actually requested
1617 let zlibStream
= this._sock
.rQshiftBytes(length
- 4);
1618 let streamInflator
= new Inflator();
1619 let textData
= null;
1621 streamInflator
.setInput(zlibStream
);
1622 for (let i
= 0; i
<= 15; i
++) {
1623 let format
= 1 << i
;
1625 if (formats
& format
) {
1628 let sizeArray
= streamInflator
.inflate(4);
1630 size
|= (sizeArray
[0] << 24);
1631 size
|= (sizeArray
[1] << 16);
1632 size
|= (sizeArray
[2] << 8);
1633 size
|= (sizeArray
[3]);
1634 let chunk
= streamInflator
.inflate(size
);
1636 if (format
=== extendedClipboardFormatText
) {
1641 streamInflator
.setInput(null);
1643 if (textData
!== null) {
1645 for (let i
= 0; i
< textData
.length
; i
++) {
1646 tmpText
+= String
.fromCharCode(textData
[i
]);
1650 textData
= decodeUTF8(textData
);
1651 if ((textData
.length
> 0) && "\0" === textData
.charAt(textData
.length
- 1)) {
1652 textData
= textData
.slice(0, -1);
1655 textData
= textData
.replace("\r\n", "\n");
1657 this.dispatchEvent(new CustomEvent(
1659 { detail
: { text
: textData
} }));
1662 return this._fail("Unexpected action in extended clipboard message: " + actions
);
1668 _handle_server_fence_msg() {
1669 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1670 this._sock
.rQskipBytes(3); // Padding
1671 let flags
= this._sock
.rQshift32();
1672 let length
= this._sock
.rQshift8();
1674 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1677 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1681 const payload
= this._sock
.rQshiftStr(length
);
1683 this._supportsFence
= true;
1688 * (1<<0) - BlockBefore
1689 * (1<<1) - BlockAfter
1694 if (!(flags
& (1<<31))) {
1695 return this._fail("Unexpected fence response");
1698 // Filter out unsupported flags
1699 // FIXME: support syncNext
1700 flags
&= (1<<0) | (1<<1);
1702 // BlockBefore and BlockAfter are automatically handled by
1703 // the fact that we process each incoming message
1705 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1711 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1712 this._sock
.rQskipBytes(1); // Padding
1713 const xvp_ver
= this._sock
.rQshift8();
1714 const xvp_msg
= this._sock
.rQshift8();
1718 Log
.Error("XVP Operation Failed");
1721 this._rfb_xvp_ver
= xvp_ver
;
1722 Log
.Info("XVP extensions enabled (version " + this._rfb_xvp_ver
+ ")");
1723 this._setCapability("power", true);
1726 this._fail("Illegal server XVP message (msg: " + xvp_msg
+ ")");
1735 if (this._FBU
.rects
> 0) {
1738 msg_type
= this._sock
.rQshift8();
1743 case 0: // FramebufferUpdate
1744 ret
= this._framebufferUpdate();
1745 if (ret
&& !this._enabledContinuousUpdates
) {
1746 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1747 this._fb_width
, this._fb_height
);
1751 case 1: // SetColorMapEntries
1752 return this._handle_set_colour_map_msg();
1756 this.dispatchEvent(new CustomEvent(
1761 case 3: // ServerCutText
1762 return this._handle_server_cut_text();
1764 case 150: // EndOfContinuousUpdates
1765 first
= !this._supportsContinuousUpdates
;
1766 this._supportsContinuousUpdates
= true;
1767 this._enabledContinuousUpdates
= false;
1769 this._enabledContinuousUpdates
= true;
1770 this._updateContinuousUpdates();
1771 Log
.Info("Enabling continuous updates.");
1773 // FIXME: We need to send a framebufferupdaterequest here
1774 // if we add support for turning off continuous updates
1778 case 248: // ServerFence
1779 return this._handle_server_fence_msg();
1782 return this._handle_xvp_msg();
1785 this._fail("Unexpected server message (type " + msg_type
+ ")");
1786 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1792 this._flushing
= false;
1793 // Resume processing
1794 if (this._sock
.rQlen
> 0) {
1795 this._handle_message();
1799 _framebufferUpdate() {
1800 if (this._FBU
.rects
=== 0) {
1801 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1802 this._sock
.rQskipBytes(1); // Padding
1803 this._FBU
.rects
= this._sock
.rQshift16();
1805 // Make sure the previous frame is fully rendered first
1806 // to avoid building up an excessive queue
1807 if (this._display
.pending()) {
1808 this._flushing
= true;
1809 this._display
.flush();
1814 while (this._FBU
.rects
> 0) {
1815 if (this._FBU
.encoding
=== null) {
1816 if (this._sock
.rQwait("rect header", 12)) { return false; }
1817 /* New FramebufferUpdate */
1819 const hdr
= this._sock
.rQshiftBytes(12);
1820 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1821 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1822 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1823 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1824 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1825 (hdr
[10] << 8) + hdr
[11], 10);
1828 if (!this._handleRect()) {
1833 this._FBU
.encoding
= null;
1836 this._display
.flip();
1838 return true; // We finished this FBU
1842 switch (this._FBU
.encoding
) {
1843 case encodings
.pseudoEncodingLastRect
:
1844 this._FBU
.rects
= 1; // Will be decreased when we return
1847 case encodings
.pseudoEncodingVMwareCursor
:
1848 return this._handleVMwareCursor();
1850 case encodings
.pseudoEncodingCursor
:
1851 return this._handleCursor();
1853 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1854 // Old Safari doesn't support creating keyboard events
1856 const keyboardEvent
= document
.createEvent("keyboardEvent");
1857 if (keyboardEvent
.code
!== undefined) {
1858 this._qemuExtKeyEventSupported
= true;
1865 case encodings
.pseudoEncodingDesktopName
:
1866 return this._handleDesktopName();
1868 case encodings
.pseudoEncodingDesktopSize
:
1869 this._resize(this._FBU
.width
, this._FBU
.height
);
1872 case encodings
.pseudoEncodingExtendedDesktopSize
:
1873 return this._handleExtendedDesktopSize();
1876 return this._handleDataRect();
1880 _handleVMwareCursor() {
1881 const hotx
= this._FBU
.x
; // hotspot-x
1882 const hoty
= this._FBU
.y
; // hotspot-y
1883 const w
= this._FBU
.width
;
1884 const h
= this._FBU
.height
;
1885 if (this._sock
.rQwait("VMware cursor encoding", 1)) {
1889 const cursor_type
= this._sock
.rQshift8();
1891 this._sock
.rQshift8(); //Padding
1894 const bytesPerPixel
= 4;
1897 if (cursor_type
== 0) {
1898 //Used to filter away unimportant bits.
1899 //OR is used for correct conversion in js.
1900 const PIXEL_MASK
= 0xffffff00 | 0;
1901 rgba
= new Array(w
* h
* bytesPerPixel
);
1903 if (this._sock
.rQwait("VMware cursor classic encoding",
1904 (w
* h
* bytesPerPixel
) * 2, 2)) {
1908 let and_mask
= new Array(w
* h
);
1909 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1910 and_mask
[pixel
] = this._sock
.rQshift32();
1913 let xor_mask
= new Array(w
* h
);
1914 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1915 xor_mask
[pixel
] = this._sock
.rQshift32();
1918 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1919 if (and_mask
[pixel
] == 0) {
1920 //Fully opaque pixel
1921 let bgr
= xor_mask
[pixel
];
1922 let r
= bgr
>> 8 & 0xff;
1923 let g
= bgr
>> 16 & 0xff;
1924 let b
= bgr
>> 24 & 0xff;
1926 rgba
[(pixel
* bytesPerPixel
) ] = r
; //r
1927 rgba
[(pixel
* bytesPerPixel
) + 1 ] = g
; //g
1928 rgba
[(pixel
* bytesPerPixel
) + 2 ] = b
; //b
1929 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff; //a
1931 } else if ((and_mask
[pixel
] & PIXEL_MASK
) ==
1933 //Only screen value matters, no mouse colouring
1934 if (xor_mask
[pixel
] == 0) {
1936 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1937 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1938 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1939 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0x00;
1941 } else if ((xor_mask
[pixel
] & PIXEL_MASK
) ==
1943 //Inverted pixel, not supported in browsers.
1944 //Fully opaque instead.
1945 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1946 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1947 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1948 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1951 //Unhandled xor_mask
1952 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1953 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1954 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1955 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1959 //Unhandled and_mask
1960 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1961 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1962 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1963 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1968 } else if (cursor_type
== 1) {
1969 if (this._sock
.rQwait("VMware cursor alpha encoding",
1974 rgba
= new Array(w
* h
* bytesPerPixel
);
1976 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1977 let data
= this._sock
.rQshift32();
1979 rgba
[(pixel
* 4) ] = data
>> 24 & 0xff; //r
1980 rgba
[(pixel
* 4) + 1 ] = data
>> 16 & 0xff; //g
1981 rgba
[(pixel
* 4) + 2 ] = data
>> 8 & 0xff; //b
1982 rgba
[(pixel
* 4) + 3 ] = data
& 0xff; //a
1986 Log
.Warn("The given cursor type is not supported: "
1987 + cursor_type
+ " given.");
1991 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1997 const hotx
= this._FBU
.x
; // hotspot-x
1998 const hoty
= this._FBU
.y
; // hotspot-y
1999 const w
= this._FBU
.width
;
2000 const h
= this._FBU
.height
;
2002 const pixelslength
= w
* h
* 4;
2003 const masklength
= Math
.ceil(w
/ 8) * h
;
2005 let bytes
= pixelslength
+ masklength
;
2006 if (this._sock
.rQwait("cursor encoding", bytes
)) {
2010 // Decode from BGRX pixels + bit mask to RGBA
2011 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
2012 const mask
= this._sock
.rQshiftBytes(masklength
);
2013 let rgba
= new Uint8Array(w
* h
* 4);
2016 for (let y
= 0; y
< h
; y
++) {
2017 for (let x
= 0; x
< w
; x
++) {
2018 let mask_idx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
2019 let alpha
= (mask
[mask_idx
] << (x
% 8)) & 0x80 ? 255 : 0;
2020 rgba
[pix_idx
] = pixels
[pix_idx
+ 2];
2021 rgba
[pix_idx
+ 1] = pixels
[pix_idx
+ 1];
2022 rgba
[pix_idx
+ 2] = pixels
[pix_idx
];
2023 rgba
[pix_idx
+ 3] = alpha
;
2028 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
2033 _handleDesktopName() {
2034 if (this._sock
.rQwait("DesktopName", 4)) {
2038 let length
= this._sock
.rQshift32();
2040 if (this._sock
.rQwait("DesktopName", length
, 4)) {
2044 let name
= this._sock
.rQshiftStr(length
);
2045 name
= decodeUTF8(name
, true);
2047 this._setDesktopName(name
);
2052 _handleExtendedDesktopSize() {
2053 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
2057 const number_of_screens
= this._sock
.rQpeek8();
2059 let bytes
= 4 + (number_of_screens
* 16);
2060 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
2064 const firstUpdate
= !this._supportsSetDesktopSize
;
2065 this._supportsSetDesktopSize
= true;
2067 // Normally we only apply the current resize mode after a
2068 // window resize event. However there is no such trigger on the
2069 // initial connect. And we don't know if the server supports
2070 // resizing until we've gotten here.
2072 this._requestRemoteResize();
2075 this._sock
.rQskipBytes(1); // number-of-screens
2076 this._sock
.rQskipBytes(3); // padding
2078 for (let i
= 0; i
< number_of_screens
; i
+= 1) {
2079 // Save the id and flags of the first screen
2081 this._screen_id
= this._sock
.rQshiftBytes(4); // id
2082 this._sock
.rQskipBytes(2); // x-position
2083 this._sock
.rQskipBytes(2); // y-position
2084 this._sock
.rQskipBytes(2); // width
2085 this._sock
.rQskipBytes(2); // height
2086 this._screen_flags
= this._sock
.rQshiftBytes(4); // flags
2088 this._sock
.rQskipBytes(16);
2093 * The x-position indicates the reason for the change:
2095 * 0 - server resized on its own
2096 * 1 - this client requested the resize
2097 * 2 - another client requested the resize
2100 // We need to handle errors when we requested the resize.
2101 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
2103 // The y-position indicates the status code from the server
2104 switch (this._FBU
.y
) {
2106 msg
= "Resize is administratively prohibited";
2109 msg
= "Out of resources";
2112 msg
= "Invalid screen layout";
2115 msg
= "Unknown reason";
2118 Log
.Warn("Server did not accept the resize request: "
2121 this._resize(this._FBU
.width
, this._FBU
.height
);
2128 let decoder
= this._decoders
[this._FBU
.encoding
];
2130 this._fail("Unsupported encoding (encoding: " +
2131 this._FBU
.encoding
+ ")");
2136 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
2137 this._FBU
.width
, this._FBU
.height
,
2138 this._sock
, this._display
,
2141 this._fail("Error decoding rect: " + err
);
2146 _updateContinuousUpdates() {
2147 if (!this._enabledContinuousUpdates
) { return; }
2149 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
2150 this._fb_width
, this._fb_height
);
2153 _resize(width
, height
) {
2154 this._fb_width
= width
;
2155 this._fb_height
= height
;
2157 this._display
.resize(this._fb_width
, this._fb_height
);
2159 // Adjust the visible viewport based on the new dimensions
2161 this._updateScale();
2163 this._updateContinuousUpdates();
2167 if (this._rfb_xvp_ver
< ver
) { return; }
2168 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
2169 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
2172 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
2173 this._cursorImage
= {
2175 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
2177 this._refreshCursor();
2180 _shouldShowDotCursor() {
2181 // Called when this._cursorImage is updated
2182 if (!this._showDotCursor
) {
2183 // User does not want to see the dot, so...
2187 // The dot should not be shown if the cursor is already visible,
2188 // i.e. contains at least one not-fully-transparent pixel.
2189 // So iterate through all alpha bytes in rgba and stop at the
2191 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
2192 if (this._cursorImage
.rgbaPixels
[i
]) {
2197 // At this point, we know that the cursor is fully transparent, and
2198 // the user wants to see the dot instead of this.
2203 if (this._rfb_connection_state
!== "connecting" &&
2204 this._rfb_connection_state
!== "connected") {
2207 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
2208 this._cursor
.change(image
.rgbaPixels
,
2209 image
.hotx
, image
.hoty
,
2214 static genDES(password
, challenge
) {
2215 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
2216 return (new DES(passwordChars
)).encrypt(challenge
);
2222 keyEvent(sock
, keysym
, down
) {
2223 const buff
= sock
._sQ
;
2224 const offset
= sock
._sQlen
;
2226 buff
[offset
] = 4; // msg-type
2227 buff
[offset
+ 1] = down
;
2229 buff
[offset
+ 2] = 0;
2230 buff
[offset
+ 3] = 0;
2232 buff
[offset
+ 4] = (keysym
>> 24);
2233 buff
[offset
+ 5] = (keysym
>> 16);
2234 buff
[offset
+ 6] = (keysym
>> 8);
2235 buff
[offset
+ 7] = keysym
;
2241 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
2242 function getRFBkeycode(xt_scancode
) {
2243 const upperByte
= (keycode
>> 8);
2244 const lowerByte
= (keycode
& 0x00ff);
2245 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
2246 return lowerByte
| 0x80;
2251 const buff
= sock
._sQ
;
2252 const offset
= sock
._sQlen
;
2254 buff
[offset
] = 255; // msg-type
2255 buff
[offset
+ 1] = 0; // sub msg-type
2257 buff
[offset
+ 2] = (down
>> 8);
2258 buff
[offset
+ 3] = down
;
2260 buff
[offset
+ 4] = (keysym
>> 24);
2261 buff
[offset
+ 5] = (keysym
>> 16);
2262 buff
[offset
+ 6] = (keysym
>> 8);
2263 buff
[offset
+ 7] = keysym
;
2265 const RFBkeycode
= getRFBkeycode(keycode
);
2267 buff
[offset
+ 8] = (RFBkeycode
>> 24);
2268 buff
[offset
+ 9] = (RFBkeycode
>> 16);
2269 buff
[offset
+ 10] = (RFBkeycode
>> 8);
2270 buff
[offset
+ 11] = RFBkeycode
;
2276 pointerEvent(sock
, x
, y
, mask
) {
2277 const buff
= sock
._sQ
;
2278 const offset
= sock
._sQlen
;
2280 buff
[offset
] = 5; // msg-type
2282 buff
[offset
+ 1] = mask
;
2284 buff
[offset
+ 2] = x
>> 8;
2285 buff
[offset
+ 3] = x
;
2287 buff
[offset
+ 4] = y
>> 8;
2288 buff
[offset
+ 5] = y
;
2294 // Used to build Notify and Request data.
2295 _buildExtendedClipboardFlags(actions
, formats
) {
2296 let data
= new Uint8Array(4);
2297 let formatFlag
= 0x00000000;
2298 let actionFlag
= 0x00000000;
2300 for (let i
= 0; i
< actions
.length
; i
++) {
2301 actionFlag
|= actions
[i
];
2304 for (let i
= 0; i
< formats
.length
; i
++) {
2305 formatFlag
|= formats
[i
];
2308 data
[0] = actionFlag
>> 24; // Actions
2309 data
[1] = 0x00; // Reserved
2310 data
[2] = 0x00; // Reserved
2311 data
[3] = formatFlag
; // Formats
2316 extendedClipboardProvide(sock
, formats
, inData
) {
2317 // Deflate incomming data and their sizes
2318 let deflator
= new Deflator();
2319 let dataToDeflate
= [];
2321 for (let i
= 0; i
< formats
.length
; i
++) {
2322 // We only support the format Text at this time
2323 if (formats
[i
] != extendedClipboardFormatText
) {
2324 throw new Error("Unsupported extended clipboard format for Provide message.");
2327 // Change lone \r or \n into \r\n as defined in rfbproto
2328 inData
[i
] = inData
[i
].replace(/\r\n|\r|\n/gm, "\r\n");
2330 // Check if it already has \0
2331 let text
= encodeUTF8(inData
[i
] + "\0");
2333 dataToDeflate
.push( (text
.length
>> 24) & 0xFF,
2334 (text
.length
>> 16) & 0xFF,
2335 (text
.length
>> 8) & 0xFF,
2336 (text
.length
& 0xFF));
2338 for (let j
= 0; j
< text
.length
; j
++) {
2339 dataToDeflate
.push(text
.charCodeAt(j
));
2343 let deflatedData
= deflator
.deflate(new Uint8Array(dataToDeflate
));
2345 // Build data to send
2346 let data
= new Uint8Array(4 + deflatedData
.length
);
2347 data
.set(RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionProvide
],
2349 data
.set(deflatedData
, 4);
2351 RFB
.messages
.clientCutText(sock
, data
, true);
2354 extendedClipboardNotify(sock
, formats
) {
2355 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionNotify
],
2357 RFB
.messages
.clientCutText(sock
, flags
, true);
2360 extendedClipboardRequest(sock
, formats
) {
2361 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionRequest
],
2363 RFB
.messages
.clientCutText(sock
, flags
, true);
2366 extendedClipboardCaps(sock
, actions
, formats
) {
2367 let formatKeys
= Object
.keys(formats
);
2368 let data
= new Uint8Array(4 + (4 * formatKeys
.length
));
2370 formatKeys
.map(x
=> parseInt(x
));
2371 formatKeys
.sort((a
, b
) => a
- b
);
2373 data
.set(RFB
.messages
._buildExtendedClipboardFlags(actions
, []));
2376 for (let i
= 0; i
< formatKeys
.length
; i
++) {
2377 data
[loopOffset
] = formats
[formatKeys
[i
]] >> 24;
2378 data
[loopOffset
+ 1] = formats
[formatKeys
[i
]] >> 16;
2379 data
[loopOffset
+ 2] = formats
[formatKeys
[i
]] >> 8;
2380 data
[loopOffset
+ 3] = formats
[formatKeys
[i
]] >> 0;
2383 data
[3] |= (1 << formatKeys
[i
]); // Update our format flags
2386 RFB
.messages
.clientCutText(sock
, data
, true);
2389 clientCutText(sock
, data
, extended
= false) {
2390 const buff
= sock
._sQ
;
2391 const offset
= sock
._sQlen
;
2393 buff
[offset
] = 6; // msg-type
2395 buff
[offset
+ 1] = 0; // padding
2396 buff
[offset
+ 2] = 0; // padding
2397 buff
[offset
+ 3] = 0; // padding
2401 length
= toUnsigned32bit(-data
.length
);
2403 length
= data
.length
;
2406 buff
[offset
+ 4] = length
>> 24;
2407 buff
[offset
+ 5] = length
>> 16;
2408 buff
[offset
+ 6] = length
>> 8;
2409 buff
[offset
+ 7] = length
;
2413 // We have to keep track of from where in the data we begin creating the
2414 // buffer for the flush in the next iteration.
2417 let remaining
= data
.length
;
2418 while (remaining
> 0) {
2420 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
2421 for (let i
= 0; i
< flushSize
; i
++) {
2422 buff
[sock
._sQlen
+ i
] = data
[dataOffset
+ i
];
2425 sock
._sQlen
+= flushSize
;
2428 remaining
-= flushSize
;
2429 dataOffset
+= flushSize
;
2434 setDesktopSize(sock
, width
, height
, id
, flags
) {
2435 const buff
= sock
._sQ
;
2436 const offset
= sock
._sQlen
;
2438 buff
[offset
] = 251; // msg-type
2439 buff
[offset
+ 1] = 0; // padding
2440 buff
[offset
+ 2] = width
>> 8; // width
2441 buff
[offset
+ 3] = width
;
2442 buff
[offset
+ 4] = height
>> 8; // height
2443 buff
[offset
+ 5] = height
;
2445 buff
[offset
+ 6] = 1; // number-of-screens
2446 buff
[offset
+ 7] = 0; // padding
2449 buff
[offset
+ 8] = id
>> 24; // id
2450 buff
[offset
+ 9] = id
>> 16;
2451 buff
[offset
+ 10] = id
>> 8;
2452 buff
[offset
+ 11] = id
;
2453 buff
[offset
+ 12] = 0; // x-position
2454 buff
[offset
+ 13] = 0;
2455 buff
[offset
+ 14] = 0; // y-position
2456 buff
[offset
+ 15] = 0;
2457 buff
[offset
+ 16] = width
>> 8; // width
2458 buff
[offset
+ 17] = width
;
2459 buff
[offset
+ 18] = height
>> 8; // height
2460 buff
[offset
+ 19] = height
;
2461 buff
[offset
+ 20] = flags
>> 24; // flags
2462 buff
[offset
+ 21] = flags
>> 16;
2463 buff
[offset
+ 22] = flags
>> 8;
2464 buff
[offset
+ 23] = flags
;
2470 clientFence(sock
, flags
, payload
) {
2471 const buff
= sock
._sQ
;
2472 const offset
= sock
._sQlen
;
2474 buff
[offset
] = 248; // msg-type
2476 buff
[offset
+ 1] = 0; // padding
2477 buff
[offset
+ 2] = 0; // padding
2478 buff
[offset
+ 3] = 0; // padding
2480 buff
[offset
+ 4] = flags
>> 24; // flags
2481 buff
[offset
+ 5] = flags
>> 16;
2482 buff
[offset
+ 6] = flags
>> 8;
2483 buff
[offset
+ 7] = flags
;
2485 const n
= payload
.length
;
2487 buff
[offset
+ 8] = n
; // length
2489 for (let i
= 0; i
< n
; i
++) {
2490 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
2493 sock
._sQlen
+= 9 + n
;
2497 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
2498 const buff
= sock
._sQ
;
2499 const offset
= sock
._sQlen
;
2501 buff
[offset
] = 150; // msg-type
2502 buff
[offset
+ 1] = enable
; // enable-flag
2504 buff
[offset
+ 2] = x
>> 8; // x
2505 buff
[offset
+ 3] = x
;
2506 buff
[offset
+ 4] = y
>> 8; // y
2507 buff
[offset
+ 5] = y
;
2508 buff
[offset
+ 6] = width
>> 8; // width
2509 buff
[offset
+ 7] = width
;
2510 buff
[offset
+ 8] = height
>> 8; // height
2511 buff
[offset
+ 9] = height
;
2517 pixelFormat(sock
, depth
, true_color
) {
2518 const buff
= sock
._sQ
;
2519 const offset
= sock
._sQlen
;
2525 } else if (depth
> 8) {
2531 const bits
= Math
.floor(depth
/3);
2533 buff
[offset
] = 0; // msg-type
2535 buff
[offset
+ 1] = 0; // padding
2536 buff
[offset
+ 2] = 0; // padding
2537 buff
[offset
+ 3] = 0; // padding
2539 buff
[offset
+ 4] = bpp
; // bits-per-pixel
2540 buff
[offset
+ 5] = depth
; // depth
2541 buff
[offset
+ 6] = 0; // little-endian
2542 buff
[offset
+ 7] = true_color
? 1 : 0; // true-color
2544 buff
[offset
+ 8] = 0; // red-max
2545 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
2547 buff
[offset
+ 10] = 0; // green-max
2548 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
2550 buff
[offset
+ 12] = 0; // blue-max
2551 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
2553 buff
[offset
+ 14] = bits
* 2; // red-shift
2554 buff
[offset
+ 15] = bits
* 1; // green-shift
2555 buff
[offset
+ 16] = bits
* 0; // blue-shift
2557 buff
[offset
+ 17] = 0; // padding
2558 buff
[offset
+ 18] = 0; // padding
2559 buff
[offset
+ 19] = 0; // padding
2565 clientEncodings(sock
, encodings
) {
2566 const buff
= sock
._sQ
;
2567 const offset
= sock
._sQlen
;
2569 buff
[offset
] = 2; // msg-type
2570 buff
[offset
+ 1] = 0; // padding
2572 buff
[offset
+ 2] = encodings
.length
>> 8;
2573 buff
[offset
+ 3] = encodings
.length
;
2576 for (let i
= 0; i
< encodings
.length
; i
++) {
2577 const enc
= encodings
[i
];
2578 buff
[j
] = enc
>> 24;
2579 buff
[j
+ 1] = enc
>> 16;
2580 buff
[j
+ 2] = enc
>> 8;
2586 sock
._sQlen
+= j
- offset
;
2590 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
2591 const buff
= sock
._sQ
;
2592 const offset
= sock
._sQlen
;
2594 if (typeof(x
) === "undefined") { x
= 0; }
2595 if (typeof(y
) === "undefined") { y
= 0; }
2597 buff
[offset
] = 3; // msg-type
2598 buff
[offset
+ 1] = incremental
? 1 : 0;
2600 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2601 buff
[offset
+ 3] = x
& 0xFF;
2603 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2604 buff
[offset
+ 5] = y
& 0xFF;
2606 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2607 buff
[offset
+ 7] = w
& 0xFF;
2609 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2610 buff
[offset
+ 9] = h
& 0xFF;
2616 xvpOp(sock
, ver
, op
) {
2617 const buff
= sock
._sQ
;
2618 const offset
= sock
._sQlen
;
2620 buff
[offset
] = 250; // msg-type
2621 buff
[offset
+ 1] = 0; // padding
2623 buff
[offset
+ 2] = ver
;
2624 buff
[offset
+ 3] = op
;
2633 rgbaPixels
: new Uint8Array(),
2639 /* eslint-disable indent */
2640 rgbaPixels
: new Uint8Array([
2641 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2642 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2643 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2645 /* eslint-enable indent */