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_xvp_ver
= 0;
94 this._capabilities
= { power
: false };
96 this._supportsFence
= false;
98 this._supportsContinuousUpdates
= false;
99 this._enabledContinuousUpdates
= false;
101 this._supportsSetDesktopSize
= false;
103 this._screen_flags
= 0;
105 this._qemuExtKeyEventSupported
= false;
107 this._clipboardText
= null;
108 this._clipboardServerCapabilitiesActions
= {};
109 this._clipboardServerCapabilitiesFormats
= {};
112 this._sock
= null; // Websock object
113 this._display
= null; // Display object
114 this._flushing
= false; // Display flushing state
115 this._keyboard
= null; // Keyboard input handler object
116 this._mouse
= null; // Mouse input handler object
119 this._disconnTimer
= null; // disconnection timer
120 this._resizeTimeout
= null; // resize rate limiting
135 this._mouse_buttonMask
= 0;
136 this._mouse_arr
= [];
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._rfb_connection_state
=== 'connecting') &&
213 (this._rfb_init_state
=== '')) {
214 this._rfb_init_state
= 'ProtocolVersion';
215 Log
.Debug("Starting VNC handshake");
217 this._fail("Unexpected server connection while " +
218 this._rfb_connection_state
);
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._rfb_connection_state
) {
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
;
280 // ===== PROPERTIES =====
282 get viewOnly() { return this._viewOnly
; }
283 set viewOnly(viewOnly
) {
284 this._viewOnly
= viewOnly
;
286 if (this._rfb_connection_state
=== "connecting" ||
287 this._rfb_connection_state
=== "connected") {
289 this._keyboard
.ungrab();
290 this._mouse
.ungrab();
292 this._keyboard
.grab();
298 get capabilities() { return this._capabilities
; }
300 get touchButton() { return this._mouse
.touchButton
; }
301 set touchButton(button
) { this._mouse
.touchButton
= button
; }
303 get clipViewport() { return this._clipViewport
; }
304 set clipViewport(viewport
) {
305 this._clipViewport
= viewport
;
309 get scaleViewport() { return this._scaleViewport
; }
310 set scaleViewport(scale
) {
311 this._scaleViewport
= scale
;
312 // Scaling trumps clipping, so we may need to adjust
313 // clipping when enabling or disabling scaling
314 if (scale
&& this._clipViewport
) {
318 if (!scale
&& this._clipViewport
) {
323 get resizeSession() { return this._resizeSession
; }
324 set resizeSession(resize
) {
325 this._resizeSession
= resize
;
327 this._requestRemoteResize();
331 get showDotCursor() { return this._showDotCursor
; }
332 set showDotCursor(show
) {
333 this._showDotCursor
= show
;
334 this._refreshCursor();
337 get background() { return this._screen
.style
.background
; }
338 set background(cssValue
) { this._screen
.style
.background
= cssValue
; }
340 // ===== PUBLIC METHODS =====
343 this._updateConnectionState('disconnecting');
344 this._sock
.off('error');
345 this._sock
.off('message');
346 this._sock
.off('open');
349 sendCredentials(creds
) {
350 this._rfb_credentials
= creds
;
351 setTimeout(this._init_msg
.bind(this), 0);
355 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
356 Log
.Info("Sending Ctrl-Alt-Del");
358 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
359 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
360 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
361 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
362 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
363 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
378 // Send a key press. If 'down' is not specified then send a down key
379 // followed by an up key.
380 sendKey(keysym
, code
, down
) {
381 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
383 if (down
=== undefined) {
384 this.sendKey(keysym
, code
, true);
385 this.sendKey(keysym
, code
, false);
389 const scancode
= XtScancode
[code
];
391 if (this._qemuExtKeyEventSupported
&& scancode
) {
393 keysym
= keysym
|| 0;
395 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
397 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
402 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
403 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
408 this._canvas
.focus();
415 clipboardPasteFrom(text
) {
416 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
418 if (this._clipboardServerCapabilitiesFormats
[extendedClipboardFormatText
] &&
419 this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
421 this._clipboardText
= text
;
422 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
424 let data
= new Uint8Array(text
.length
);
425 for (let i
= 0; i
< text
.length
; i
++) {
426 // FIXME: text can have values outside of Latin1/Uint8
427 data
[i
] = text
.charCodeAt(i
);
430 RFB
.messages
.clientCutText(this._sock
, data
);
434 // ===== PRIVATE METHODS =====
437 Log
.Debug(">> RFB.connect");
439 Log
.Info("connecting to " + this._url
);
442 // WebSocket.onopen transitions to the RFB init states
443 this._sock
.open(this._url
, this._wsProtocols
);
445 if (e
.name
=== 'SyntaxError') {
446 this._fail("Invalid host or port (" + e
+ ")");
448 this._fail("Error when opening socket (" + e
+ ")");
452 // Make our elements part of the page
453 this._target
.appendChild(this._screen
);
455 this._cursor
.attach(this._canvas
);
456 this._refreshCursor();
458 // Monitor size changes of the screen
459 // FIXME: Use ResizeObserver, or hidden overflow
460 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
462 // Always grab focus on some kind of click event
463 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
464 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
466 Log
.Debug("<< RFB.connect");
470 Log
.Debug(">> RFB.disconnect");
471 this._cursor
.detach();
472 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
473 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
474 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
475 this._keyboard
.ungrab();
476 this._mouse
.ungrab();
479 this._target
.removeChild(this._screen
);
481 if (e
.name
=== 'NotFoundError') {
482 // Some cases where the initial connection fails
483 // can disconnect before the _screen is created
488 clearTimeout(this._resizeTimeout
);
489 Log
.Debug("<< RFB.disconnect");
492 _focusCanvas(event
) {
493 // Respect earlier handlers' request to not do side-effects
494 if (event
.defaultPrevented
) {
498 if (!this.focusOnClick
) {
505 _setDesktopName(name
) {
506 this._fb_name
= name
;
507 this.dispatchEvent(new CustomEvent(
509 { detail
: { name
: this._fb_name
} }));
512 _windowResize(event
) {
513 // If the window resized then our screen element might have
514 // as well. Update the viewport dimensions.
515 window
.requestAnimationFrame(() => {
520 if (this._resizeSession
) {
521 // Request changing the resolution of the remote display to
522 // the size of the local browser viewport.
524 // In order to not send multiple requests before the browser-resize
525 // is finished we wait 0.5 seconds before sending the request.
526 clearTimeout(this._resizeTimeout
);
527 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
531 // Update state of clipping in Display object, and make sure the
532 // configured viewport matches the current screen size
534 const cur_clip
= this._display
.clipViewport
;
535 let new_clip
= this._clipViewport
;
537 if (this._scaleViewport
) {
538 // Disable viewport clipping if we are scaling
542 if (cur_clip
!== new_clip
) {
543 this._display
.clipViewport
= new_clip
;
547 // When clipping is enabled, the screen is limited to
548 // the size of the container.
549 const size
= this._screenSize();
550 this._display
.viewportChangeSize(size
.w
, size
.h
);
551 this._fixScrollbars();
556 if (!this._scaleViewport
) {
557 this._display
.scale
= 1.0;
559 const size
= this._screenSize();
560 this._display
.autoscale(size
.w
, size
.h
);
562 this._fixScrollbars();
565 // Requests a change of remote desktop size. This message is an extension
566 // and may only be sent if we have received an ExtendedDesktopSize message
567 _requestRemoteResize() {
568 clearTimeout(this._resizeTimeout
);
569 this._resizeTimeout
= null;
571 if (!this._resizeSession
|| this._viewOnly
||
572 !this._supportsSetDesktopSize
) {
576 const size
= this._screenSize();
577 RFB
.messages
.setDesktopSize(this._sock
,
578 Math
.floor(size
.w
), Math
.floor(size
.h
),
579 this._screen_id
, this._screen_flags
);
581 Log
.Debug('Requested new desktop size: ' +
582 size
.w
+ 'x' + size
.h
);
585 // Gets the the size of the available screen
587 let r
= this._screen
.getBoundingClientRect();
588 return { w
: r
.width
, h
: r
.height
};
592 // This is a hack because Chrome screws up the calculation
593 // for when scrollbars are needed. So to fix it we temporarily
594 // toggle them off and on.
595 const orig
= this._screen
.style
.overflow
;
596 this._screen
.style
.overflow
= 'hidden';
597 // Force Chrome to recalculate the layout by asking for
598 // an element's dimensions
599 this._screen
.getBoundingClientRect();
600 this._screen
.style
.overflow
= orig
;
608 * disconnected - permanent state
610 _updateConnectionState(state
) {
611 const oldstate
= this._rfb_connection_state
;
613 if (state
=== oldstate
) {
614 Log
.Debug("Already in state '" + state
+ "', ignoring");
618 // The 'disconnected' state is permanent for each RFB object
619 if (oldstate
=== 'disconnected') {
620 Log
.Error("Tried changing state of a disconnected RFB object");
624 // Ensure proper transitions before doing anything
627 if (oldstate
!== 'connecting') {
628 Log
.Error("Bad transition to connected state, " +
629 "previous connection state: " + oldstate
);
635 if (oldstate
!== 'disconnecting') {
636 Log
.Error("Bad transition to disconnected state, " +
637 "previous connection state: " + oldstate
);
643 if (oldstate
!== '') {
644 Log
.Error("Bad transition to connecting state, " +
645 "previous connection state: " + oldstate
);
650 case 'disconnecting':
651 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
652 Log
.Error("Bad transition to disconnecting state, " +
653 "previous connection state: " + oldstate
);
659 Log
.Error("Unknown connection state: " + state
);
663 // State change actions
665 this._rfb_connection_state
= state
;
667 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
669 if (this._disconnTimer
&& state
!== 'disconnecting') {
670 Log
.Debug("Clearing disconnect timer");
671 clearTimeout(this._disconnTimer
);
672 this._disconnTimer
= null;
674 // make sure we don't get a double event
675 this._sock
.off('close');
684 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
687 case 'disconnecting':
690 this._disconnTimer
= setTimeout(() => {
691 Log
.Error("Disconnection timed out.");
692 this._updateConnectionState('disconnected');
693 }, DISCONNECT_TIMEOUT
* 1000);
697 this.dispatchEvent(new CustomEvent(
698 "disconnect", { detail
:
699 { clean
: this._rfb_clean_disconnect
} }));
704 /* Print errors and disconnect
706 * The parameter 'details' is used for information that
707 * should be logged but not sent to the user interface.
710 switch (this._rfb_connection_state
) {
711 case 'disconnecting':
712 Log
.Error("Failed when disconnecting: " + details
);
715 Log
.Error("Failed while connected: " + details
);
718 Log
.Error("Failed when connecting: " + details
);
721 Log
.Error("RFB failure: " + details
);
724 this._rfb_clean_disconnect
= false; //This is sent to the UI
726 // Transition to disconnected without waiting for socket to close
727 this._updateConnectionState('disconnecting');
728 this._updateConnectionState('disconnected');
733 _setCapability(cap
, val
) {
734 this._capabilities
[cap
] = val
;
735 this.dispatchEvent(new CustomEvent("capabilities",
736 { detail
: { capabilities
: this._capabilities
} }));
740 if (this._sock
.rQlen
=== 0) {
741 Log
.Warn("handle_message called on an empty receive queue");
745 switch (this._rfb_connection_state
) {
747 Log
.Error("Got data while disconnected");
751 if (this._flushing
) {
754 if (!this._normal_msg()) {
757 if (this._sock
.rQlen
=== 0) {
768 _handleKeyEvent(keysym
, code
, down
) {
769 this.sendKey(keysym
, code
, down
);
772 _handleMouseButton(x
, y
, down
, bmask
) {
774 this._mouse_buttonMask
|= bmask
;
776 this._mouse_buttonMask
&= ~bmask
;
779 if (this.dragViewport
) {
780 if (down
&& !this._viewportDragging
) {
781 this._viewportDragging
= true;
782 this._viewportDragPos
= {'x': x
, 'y': y
};
783 this._viewportHasMoved
= false;
785 // Skip sending mouse events
788 this._viewportDragging
= false;
790 // If we actually performed a drag then we are done
791 // here and should not send any mouse events
792 if (this._viewportHasMoved
) {
796 // Otherwise we treat this as a mouse click event.
797 // Send the button down event here, as the button up
798 // event is sent at the end of this function.
799 RFB
.messages
.pointerEvent(this._sock
,
800 this._display
.absX(x
),
801 this._display
.absY(y
),
806 if (this._viewOnly
) { return; } // View only, skip mouse events
808 if (this._rfb_connection_state
!== 'connected') { return; }
809 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
812 _handleMouseMove(x
, y
) {
813 if (this._viewportDragging
) {
814 const deltaX
= this._viewportDragPos
.x
- x
;
815 const deltaY
= this._viewportDragPos
.y
- y
;
817 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
818 Math
.abs(deltaY
) > dragThreshold
)) {
819 this._viewportHasMoved
= true;
821 this._viewportDragPos
= {'x': x
, 'y': y
};
822 this._display
.viewportChangePos(deltaX
, deltaY
);
825 // Skip sending mouse events
829 if (this._viewOnly
) { return; } // View only, skip mouse events
831 if (this._rfb_connection_state
!== 'connected') { return; }
832 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
837 _negotiate_protocol_version() {
838 if (this._sock
.rQwait("version", 12)) {
842 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
843 Log
.Info("Server ProtocolVersion: " + sversion
);
846 case "000.000": // UltraVNC repeater
850 case "003.006": // UltraVNC
851 case "003.889": // Apple Remote Desktop
852 this._rfb_version
= 3.3;
855 this._rfb_version
= 3.7;
858 case "004.000": // Intel AMT KVM
859 case "004.001": // RealVNC 4.6
860 case "005.000": // RealVNC 5.3
861 this._rfb_version
= 3.8;
864 return this._fail("Invalid server version " + sversion
);
868 let repeaterID
= "ID:" + this._repeaterID
;
869 while (repeaterID
.length
< 250) {
872 this._sock
.send_string(repeaterID
);
876 if (this._rfb_version
> this._rfb_max_version
) {
877 this._rfb_version
= this._rfb_max_version
;
880 const cversion
= "00" + parseInt(this._rfb_version
, 10) +
881 ".00" + ((this._rfb_version
* 10) % 10);
882 this._sock
.send_string("RFB " + cversion
+ "\n");
883 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
885 this._rfb_init_state
= 'Security';
888 _negotiate_security() {
889 // Polyfill since IE and PhantomJS doesn't have
890 // TypedArray.includes()
891 function includes(item
, array
) {
892 for (let i
= 0; i
< array
.length
; i
++) {
893 if (array
[i
] === item
) {
900 if (this._rfb_version
>= 3.7) {
901 // Server sends supported list, client decides
902 const num_types
= this._sock
.rQshift8();
903 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
905 if (num_types
=== 0) {
906 this._rfb_init_state
= "SecurityReason";
907 this._security_context
= "no security types";
908 this._security_status
= 1;
909 return this._init_msg();
912 const types
= this._sock
.rQshiftBytes(num_types
);
913 Log
.Debug("Server security types: " + types
);
915 // Look for each auth in preferred order
916 if (includes(1, types
)) {
917 this._rfb_auth_scheme
= 1; // None
918 } else if (includes(22, types
)) {
919 this._rfb_auth_scheme
= 22; // XVP
920 } else if (includes(16, types
)) {
921 this._rfb_auth_scheme
= 16; // Tight
922 } else if (includes(2, types
)) {
923 this._rfb_auth_scheme
= 2; // VNC Auth
925 return this._fail("Unsupported security types (types: " + types
+ ")");
928 this._sock
.send([this._rfb_auth_scheme
]);
931 if (this._sock
.rQwait("security scheme", 4)) { return false; }
932 this._rfb_auth_scheme
= this._sock
.rQshift32();
934 if (this._rfb_auth_scheme
== 0) {
935 this._rfb_init_state
= "SecurityReason";
936 this._security_context
= "authentication scheme";
937 this._security_status
= 1;
938 return this._init_msg();
942 this._rfb_init_state
= 'Authentication';
943 Log
.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme
);
945 return this._init_msg(); // jump to authentication
948 _handle_security_reason() {
949 if (this._sock
.rQwait("reason length", 4)) {
952 const strlen
= this._sock
.rQshift32();
956 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
957 reason
= this._sock
.rQshiftStr(strlen
);
961 this.dispatchEvent(new CustomEvent(
963 { detail
: { status
: this._security_status
,
964 reason
: reason
} }));
966 return this._fail("Security negotiation failed on " +
967 this._security_context
+
968 " (reason: " + reason
+ ")");
970 this.dispatchEvent(new CustomEvent(
972 { detail
: { status
: this._security_status
} }));
974 return this._fail("Security negotiation failed on " +
975 this._security_context
);
980 _negotiate_xvp_auth() {
981 if (this._rfb_credentials
.username
=== undefined ||
982 this._rfb_credentials
.password
=== undefined ||
983 this._rfb_credentials
.target
=== undefined) {
984 this.dispatchEvent(new CustomEvent(
985 "credentialsrequired",
986 { detail
: { types
: ["username", "password", "target"] } }));
990 const xvp_auth_str
= String
.fromCharCode(this._rfb_credentials
.username
.length
) +
991 String
.fromCharCode(this._rfb_credentials
.target
.length
) +
992 this._rfb_credentials
.username
+
993 this._rfb_credentials
.target
;
994 this._sock
.send_string(xvp_auth_str
);
995 this._rfb_auth_scheme
= 2;
996 return this._negotiate_authentication();
999 _negotiate_std_vnc_auth() {
1000 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
1002 if (this._rfb_credentials
.password
=== undefined) {
1003 this.dispatchEvent(new CustomEvent(
1004 "credentialsrequired",
1005 { detail
: { types
: ["password"] } }));
1009 // TODO(directxman12): make genDES not require an Array
1010 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
1011 const response
= RFB
.genDES(this._rfb_credentials
.password
, challenge
);
1012 this._sock
.send(response
);
1013 this._rfb_init_state
= "SecurityResult";
1017 _negotiate_tight_unix_auth() {
1018 if (this._rfb_credentials
.username
=== undefined ||
1019 this._rfb_credentials
.password
=== undefined) {
1020 this.dispatchEvent(new CustomEvent(
1021 "credentialsrequired",
1022 { detail
: { types
: ["username", "password"] } }));
1026 this._sock
.send([0, 0, 0, this._rfb_credentials
.username
.length
]);
1027 this._sock
.send([0, 0, 0, this._rfb_credentials
.password
.length
]);
1028 this._sock
.send_string(this._rfb_credentials
.username
);
1029 this._sock
.send_string(this._rfb_credentials
.password
);
1030 this._rfb_init_state
= "SecurityResult";
1034 _negotiate_tight_tunnels(numTunnels
) {
1035 const clientSupportedTunnelTypes
= {
1036 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
1038 const serverSupportedTunnelTypes
= {};
1039 // receive tunnel capabilities
1040 for (let i
= 0; i
< numTunnels
; i
++) {
1041 const cap_code
= this._sock
.rQshift32();
1042 const cap_vendor
= this._sock
.rQshiftStr(4);
1043 const cap_signature
= this._sock
.rQshiftStr(8);
1044 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
1047 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
1049 // Siemens touch panels have a VNC server that supports NOTUNNEL,
1050 // but forgets to advertise it. Try to detect such servers by
1051 // looking for their custom tunnel type.
1052 if (serverSupportedTunnelTypes
[1] &&
1053 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
1054 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
1055 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
1056 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
1059 // choose the notunnel type
1060 if (serverSupportedTunnelTypes
[0]) {
1061 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
1062 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
1063 return this._fail("Client's tunnel type had the incorrect " +
1064 "vendor or signature");
1066 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1067 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1068 return false; // wait until we receive the sub auth count to continue
1070 return this._fail("Server wanted tunnels, but doesn't support " +
1071 "the notunnel type");
1075 _negotiate_tight_auth() {
1076 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
1077 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1078 const numTunnels
= this._sock
.rQshift32();
1079 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1081 this._rfb_tightvnc
= true;
1083 if (numTunnels
> 0) {
1084 this._negotiate_tight_tunnels(numTunnels
);
1085 return false; // wait until we receive the sub auth to continue
1089 // second pass, do the sub-auth negotiation
1090 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1091 const subAuthCount
= this._sock
.rQshift32();
1092 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1093 this._rfb_init_state
= 'SecurityResult';
1097 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1099 const clientSupportedTypes
= {
1105 const serverSupportedTypes
= [];
1107 for (let i
= 0; i
< subAuthCount
; i
++) {
1108 this._sock
.rQshift32(); // capNum
1109 const capabilities
= this._sock
.rQshiftStr(12);
1110 serverSupportedTypes
.push(capabilities
);
1113 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1115 for (let authType
in clientSupportedTypes
) {
1116 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1117 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1118 Log
.Debug("Selected authentication type: " + authType
);
1121 case 'STDVNOAUTH__': // no auth
1122 this._rfb_init_state
= 'SecurityResult';
1124 case 'STDVVNCAUTH_': // VNC auth
1125 this._rfb_auth_scheme
= 2;
1126 return this._init_msg();
1127 case 'TGHTULGNAUTH': // UNIX auth
1128 this._rfb_auth_scheme
= 129;
1129 return this._init_msg();
1131 return this._fail("Unsupported tiny auth scheme " +
1132 "(scheme: " + authType
+ ")");
1137 return this._fail("No supported sub-auth types!");
1140 _negotiate_authentication() {
1141 switch (this._rfb_auth_scheme
) {
1143 if (this._rfb_version
>= 3.8) {
1144 this._rfb_init_state
= 'SecurityResult';
1147 this._rfb_init_state
= 'ClientInitialisation';
1148 return this._init_msg();
1150 case 22: // XVP auth
1151 return this._negotiate_xvp_auth();
1153 case 2: // VNC authentication
1154 return this._negotiate_std_vnc_auth();
1156 case 16: // TightVNC Security Type
1157 return this._negotiate_tight_auth();
1159 case 129: // TightVNC UNIX Security Type
1160 return this._negotiate_tight_unix_auth();
1163 return this._fail("Unsupported auth scheme (scheme: " +
1164 this._rfb_auth_scheme
+ ")");
1168 _handle_security_result() {
1169 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1171 const status
= this._sock
.rQshift32();
1173 if (status
=== 0) { // OK
1174 this._rfb_init_state
= 'ClientInitialisation';
1175 Log
.Debug('Authentication OK');
1176 return this._init_msg();
1178 if (this._rfb_version
>= 3.8) {
1179 this._rfb_init_state
= "SecurityReason";
1180 this._security_context
= "security result";
1181 this._security_status
= status
;
1182 return this._init_msg();
1184 this.dispatchEvent(new CustomEvent(
1186 { detail
: { status
: status
} }));
1188 return this._fail("Security handshake failed");
1193 _negotiate_server_init() {
1194 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1197 const width
= this._sock
.rQshift16();
1198 const height
= this._sock
.rQshift16();
1201 const bpp
= this._sock
.rQshift8();
1202 const depth
= this._sock
.rQshift8();
1203 const big_endian
= this._sock
.rQshift8();
1204 const true_color
= this._sock
.rQshift8();
1206 const red_max
= this._sock
.rQshift16();
1207 const green_max
= this._sock
.rQshift16();
1208 const blue_max
= this._sock
.rQshift16();
1209 const red_shift
= this._sock
.rQshift8();
1210 const green_shift
= this._sock
.rQshift8();
1211 const blue_shift
= this._sock
.rQshift8();
1212 this._sock
.rQskipBytes(3); // padding
1214 // NB(directxman12): we don't want to call any callbacks or print messages until
1215 // *after* we're past the point where we could backtrack
1217 /* Connection name/title */
1218 const name_length = this._sock.rQshift32();
1219 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1220 let name = this._sock.rQshiftStr(name_length);
1221 name = decodeUTF8(name, true);
1223 if (this._rfb_tightvnc) {
1224 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1225 // In TightVNC mode, ServerInit message is extended
1226 const numServerMessages = this._sock.rQshift16();
1227 const numClientMessages = this._sock.rQshift16();
1228 const numEncodings = this._sock.rQshift16();
1229 this._sock.rQskipBytes(2); // padding
1231 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1232 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1234 // we don't actually do anything with the capability information that TIGHT sends,
1235 // so we just skip the all of this.
1237 // TIGHT server message capabilities
1238 this._sock.rQskipBytes(16 * numServerMessages);
1240 // TIGHT client message capabilities
1241 this._sock.rQskipBytes(16 * numClientMessages);
1243 // TIGHT encoding capabilities
1244 this._sock.rQskipBytes(16 * numEncodings);
1247 // NB(directxman12): these are down here so that we don't run them multiple times
1249 Log.Info("Screen: " + width + "x" + height +
1250 ", bpp: " + bpp + ", depth: " + depth +
1251 ", big_endian: " + big_endian +
1252 ", true_color: " + true_color +
1253 ", red_max: " + red_max +
1254 ", green_max: " + green_max +
1255 ", blue_max: " + blue_max +
1256 ", red_shift: " + red_shift +
1257 ", green_shift: " + green_shift +
1258 ", blue_shift: " + blue_shift);
1260 // we're past the point where we could backtrack, so it's safe to call this
1261 this._setDesktopName(name);
1262 this._resize(width, height);
1264 if (!this._viewOnly) { this._keyboard.grab(); }
1265 if (!this._viewOnly) { this._mouse.grab(); }
1267 this._fb_depth = 24;
1269 if (this._fb_name === "Intel(r) AMT KVM") {
1270 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1274 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1275 this._sendEncodings();
1276 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1278 this._updateConnectionState('connected');
1285 // In preference order
1286 encs.push(encodings.encodingCopyRect);
1287 // Only supported with full depth support
1288 if (this._fb_depth == 24) {
1289 encs.push(encodings.encodingTight);
1290 encs.push(encodings.encodingTightPNG);
1291 encs.push(encodings.encodingHextile);
1292 encs.push(encodings.encodingRRE);
1294 encs.push(encodings.encodingRaw);
1296 // Psuedo-encoding settings
1297 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1298 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1300 encs.push(encodings.pseudoEncodingDesktopSize);
1301 encs.push(encodings.pseudoEncodingLastRect);
1302 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1303 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1304 encs.push(encodings.pseudoEncodingXvp);
1305 encs.push(encodings.pseudoEncodingFence);
1306 encs.push(encodings.pseudoEncodingContinuousUpdates);
1307 encs.push(encodings.pseudoEncodingDesktopName);
1308 encs.push(encodings.pseudoEncodingExtendedClipboard);
1310 if (this._fb_depth == 24) {
1311 encs.push(encodings.pseudoEncodingVMwareCursor);
1312 encs.push(encodings.pseudoEncodingCursor);
1315 RFB.messages.clientEncodings(this._sock, encs);
1318 /* RFB protocol initialization states:
1323 * ClientInitialization - not triggered by server message
1324 * ServerInitialization
1327 switch (this._rfb_init_state
) {
1328 case 'ProtocolVersion':
1329 return this._negotiate_protocol_version();
1332 return this._negotiate_security();
1334 case 'Authentication':
1335 return this._negotiate_authentication();
1337 case 'SecurityResult':
1338 return this._handle_security_result();
1340 case 'SecurityReason':
1341 return this._handle_security_reason();
1343 case 'ClientInitialisation':
1344 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1345 this._rfb_init_state
= 'ServerInitialisation';
1348 case 'ServerInitialisation':
1349 return this._negotiate_server_init();
1352 return this._fail("Unknown init state (state: " +
1353 this._rfb_init_state
+ ")");
1357 _handle_set_colour_map_msg() {
1358 Log
.Debug("SetColorMapEntries");
1360 return this._fail("Unexpected SetColorMapEntries message");
1363 _handle_server_cut_text() {
1364 Log
.Debug("ServerCutText");
1366 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1368 this._sock
.rQskipBytes(3); // Padding
1370 let length
= this._sock
.rQshift32();
1371 length
= toSigned32bit(length
);
1373 if (this._sock
.rQwait("ServerCutText content", Math
.abs(length
), 8)) { return false; }
1377 const text
= this._sock
.rQshiftStr(length
);
1378 if (this._viewOnly
) {
1382 this.dispatchEvent(new CustomEvent(
1384 { detail
: { text
: text
} }));
1388 length
= Math
.abs(length
);
1389 const flags
= this._sock
.rQshift32();
1390 let formats
= flags
& 0x0000FFFF;
1391 let actions
= flags
& 0xFF000000;
1393 let isCaps
= (!!(actions
& extendedClipboardActionCaps
));
1395 this._clipboardServerCapabilitiesFormats
= {};
1396 this._clipboardServerCapabilitiesActions
= {};
1398 // Update our server capabilities for Formats
1399 for (let i
= 0; i
<= 15; i
++) {
1402 // Check if format flag is set.
1403 if ((formats
& index
)) {
1404 this._clipboardServerCapabilitiesFormats
[index
] = true;
1405 // We don't send unsolicited clipboard, so we
1407 this._sock
.rQshift32();
1411 // Update our server capabilities for Actions
1412 for (let i
= 24; i
<= 31; i
++) {
1414 this._clipboardServerCapabilitiesActions
[index
] = !!(actions
& index
);
1417 /* Caps handling done, send caps with the clients
1418 capabilities set as a response */
1419 let clientActions
= [
1420 extendedClipboardActionCaps
,
1421 extendedClipboardActionRequest
,
1422 extendedClipboardActionPeek
,
1423 extendedClipboardActionNotify
,
1424 extendedClipboardActionProvide
1426 RFB
.messages
.extendedClipboardCaps(this._sock
, clientActions
, {extendedClipboardFormatText
: 0});
1428 } else if (actions
=== extendedClipboardActionRequest
) {
1429 if (this._viewOnly
) {
1433 // Check if server has told us it can handle Provide and there is clipboard data to send.
1434 if (this._clipboardText
!= null &&
1435 this._clipboardServerCapabilitiesActions
[extendedClipboardActionProvide
]) {
1437 if (formats
& extendedClipboardFormatText
) {
1438 RFB
.messages
.extendedClipboardProvide(this._sock
, [extendedClipboardFormatText
], [this._clipboardText
]);
1442 } else if (actions
=== extendedClipboardActionPeek
) {
1443 if (this._viewOnly
) {
1447 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionNotify
]) {
1449 if (this._clipboardText
!= null) {
1450 RFB
.messages
.extendedClipboardNotify(this._sock
, [extendedClipboardFormatText
]);
1452 RFB
.messages
.extendedClipboardNotify(this._sock
, []);
1456 } else if (actions
=== extendedClipboardActionNotify
) {
1457 if (this._viewOnly
) {
1461 if (this._clipboardServerCapabilitiesActions
[extendedClipboardActionRequest
]) {
1463 if (formats
& extendedClipboardFormatText
) {
1464 RFB
.messages
.extendedClipboardRequest(this._sock
, [extendedClipboardFormatText
]);
1468 } else if (actions
=== extendedClipboardActionProvide
) {
1469 if (this._viewOnly
) {
1473 if (!(formats
& extendedClipboardFormatText
)) {
1476 // Ignore what we had in our clipboard client side.
1477 this._clipboardText
= null;
1479 // FIXME: Should probably verify that this data was actually requested
1480 let zlibStream
= this._sock
.rQshiftBytes(length
- 4);
1481 let streamInflator
= new Inflator();
1482 let textData
= null;
1484 streamInflator
.setInput(zlibStream
);
1485 for (let i
= 0; i
<= 15; i
++) {
1486 let format
= 1 << i
;
1488 if (formats
& format
) {
1491 let sizeArray
= streamInflator
.inflate(4);
1493 size
|= (sizeArray
[0] << 24);
1494 size
|= (sizeArray
[1] << 16);
1495 size
|= (sizeArray
[2] << 8);
1496 size
|= (sizeArray
[3]);
1497 let chunk
= streamInflator
.inflate(size
);
1499 if (format
=== extendedClipboardFormatText
) {
1504 streamInflator
.setInput(null);
1506 if (textData
!== null) {
1508 for (let i
= 0; i
< textData
.length
; i
++) {
1509 tmpText
+= String
.fromCharCode(textData
[i
]);
1513 textData
= decodeUTF8(textData
);
1514 if ((textData
.length
> 0) && "\0" === textData
.charAt(textData
.length
- 1)) {
1515 textData
= textData
.slice(0, -1);
1518 textData
= textData
.replace("\r\n", "\n");
1520 this.dispatchEvent(new CustomEvent(
1522 { detail
: { text
: textData
} }));
1525 return this._fail("Unexpected action in extended clipboard message: " + actions
);
1531 _handle_server_fence_msg() {
1532 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1533 this._sock
.rQskipBytes(3); // Padding
1534 let flags
= this._sock
.rQshift32();
1535 let length
= this._sock
.rQshift8();
1537 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1540 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1544 const payload
= this._sock
.rQshiftStr(length
);
1546 this._supportsFence
= true;
1551 * (1<<0) - BlockBefore
1552 * (1<<1) - BlockAfter
1557 if (!(flags
& (1<<31))) {
1558 return this._fail("Unexpected fence response");
1561 // Filter out unsupported flags
1562 // FIXME: support syncNext
1563 flags
&= (1<<0) | (1<<1);
1565 // BlockBefore and BlockAfter are automatically handled by
1566 // the fact that we process each incoming message
1568 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1574 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1575 this._sock
.rQskipBytes(1); // Padding
1576 const xvp_ver
= this._sock
.rQshift8();
1577 const xvp_msg
= this._sock
.rQshift8();
1581 Log
.Error("XVP Operation Failed");
1584 this._rfb_xvp_ver
= xvp_ver
;
1585 Log
.Info("XVP extensions enabled (version " + this._rfb_xvp_ver
+ ")");
1586 this._setCapability("power", true);
1589 this._fail("Illegal server XVP message (msg: " + xvp_msg
+ ")");
1598 if (this._FBU
.rects
> 0) {
1601 msg_type
= this._sock
.rQshift8();
1606 case 0: // FramebufferUpdate
1607 ret
= this._framebufferUpdate();
1608 if (ret
&& !this._enabledContinuousUpdates
) {
1609 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1610 this._fb_width
, this._fb_height
);
1614 case 1: // SetColorMapEntries
1615 return this._handle_set_colour_map_msg();
1619 this.dispatchEvent(new CustomEvent(
1624 case 3: // ServerCutText
1625 return this._handle_server_cut_text();
1627 case 150: // EndOfContinuousUpdates
1628 first
= !this._supportsContinuousUpdates
;
1629 this._supportsContinuousUpdates
= true;
1630 this._enabledContinuousUpdates
= false;
1632 this._enabledContinuousUpdates
= true;
1633 this._updateContinuousUpdates();
1634 Log
.Info("Enabling continuous updates.");
1636 // FIXME: We need to send a framebufferupdaterequest here
1637 // if we add support for turning off continuous updates
1641 case 248: // ServerFence
1642 return this._handle_server_fence_msg();
1645 return this._handle_xvp_msg();
1648 this._fail("Unexpected server message (type " + msg_type
+ ")");
1649 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1655 this._flushing
= false;
1656 // Resume processing
1657 if (this._sock
.rQlen
> 0) {
1658 this._handle_message();
1662 _framebufferUpdate() {
1663 if (this._FBU
.rects
=== 0) {
1664 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1665 this._sock
.rQskipBytes(1); // Padding
1666 this._FBU
.rects
= this._sock
.rQshift16();
1668 // Make sure the previous frame is fully rendered first
1669 // to avoid building up an excessive queue
1670 if (this._display
.pending()) {
1671 this._flushing
= true;
1672 this._display
.flush();
1677 while (this._FBU
.rects
> 0) {
1678 if (this._FBU
.encoding
=== null) {
1679 if (this._sock
.rQwait("rect header", 12)) { return false; }
1680 /* New FramebufferUpdate */
1682 const hdr
= this._sock
.rQshiftBytes(12);
1683 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1684 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1685 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1686 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1687 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1688 (hdr
[10] << 8) + hdr
[11], 10);
1691 if (!this._handleRect()) {
1696 this._FBU
.encoding
= null;
1699 this._display
.flip();
1701 return true; // We finished this FBU
1705 switch (this._FBU
.encoding
) {
1706 case encodings
.pseudoEncodingLastRect
:
1707 this._FBU
.rects
= 1; // Will be decreased when we return
1710 case encodings
.pseudoEncodingVMwareCursor
:
1711 return this._handleVMwareCursor();
1713 case encodings
.pseudoEncodingCursor
:
1714 return this._handleCursor();
1716 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1717 // Old Safari doesn't support creating keyboard events
1719 const keyboardEvent
= document
.createEvent("keyboardEvent");
1720 if (keyboardEvent
.code
!== undefined) {
1721 this._qemuExtKeyEventSupported
= true;
1728 case encodings
.pseudoEncodingDesktopName
:
1729 return this._handleDesktopName();
1731 case encodings
.pseudoEncodingDesktopSize
:
1732 this._resize(this._FBU
.width
, this._FBU
.height
);
1735 case encodings
.pseudoEncodingExtendedDesktopSize
:
1736 return this._handleExtendedDesktopSize();
1739 return this._handleDataRect();
1743 _handleVMwareCursor() {
1744 const hotx
= this._FBU
.x
; // hotspot-x
1745 const hoty
= this._FBU
.y
; // hotspot-y
1746 const w
= this._FBU
.width
;
1747 const h
= this._FBU
.height
;
1748 if (this._sock
.rQwait("VMware cursor encoding", 1)) {
1752 const cursor_type
= this._sock
.rQshift8();
1754 this._sock
.rQshift8(); //Padding
1757 const bytesPerPixel
= 4;
1760 if (cursor_type
== 0) {
1761 //Used to filter away unimportant bits.
1762 //OR is used for correct conversion in js.
1763 const PIXEL_MASK
= 0xffffff00 | 0;
1764 rgba
= new Array(w
* h
* bytesPerPixel
);
1766 if (this._sock
.rQwait("VMware cursor classic encoding",
1767 (w
* h
* bytesPerPixel
) * 2, 2)) {
1771 let and_mask
= new Array(w
* h
);
1772 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1773 and_mask
[pixel
] = this._sock
.rQshift32();
1776 let xor_mask
= new Array(w
* h
);
1777 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1778 xor_mask
[pixel
] = this._sock
.rQshift32();
1781 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1782 if (and_mask
[pixel
] == 0) {
1783 //Fully opaque pixel
1784 let bgr
= xor_mask
[pixel
];
1785 let r
= bgr
>> 8 & 0xff;
1786 let g
= bgr
>> 16 & 0xff;
1787 let b
= bgr
>> 24 & 0xff;
1789 rgba
[(pixel
* bytesPerPixel
) ] = r
; //r
1790 rgba
[(pixel
* bytesPerPixel
) + 1 ] = g
; //g
1791 rgba
[(pixel
* bytesPerPixel
) + 2 ] = b
; //b
1792 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff; //a
1794 } else if ((and_mask
[pixel
] & PIXEL_MASK
) ==
1796 //Only screen value matters, no mouse colouring
1797 if (xor_mask
[pixel
] == 0) {
1799 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1800 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1801 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1802 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0x00;
1804 } else if ((xor_mask
[pixel
] & PIXEL_MASK
) ==
1806 //Inverted pixel, not supported in browsers.
1807 //Fully opaque instead.
1808 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1809 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1810 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1811 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1814 //Unhandled xor_mask
1815 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1816 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1817 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1818 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1822 //Unhandled and_mask
1823 rgba
[(pixel
* bytesPerPixel
) ] = 0x00;
1824 rgba
[(pixel
* bytesPerPixel
) + 1 ] = 0x00;
1825 rgba
[(pixel
* bytesPerPixel
) + 2 ] = 0x00;
1826 rgba
[(pixel
* bytesPerPixel
) + 3 ] = 0xff;
1831 } else if (cursor_type
== 1) {
1832 if (this._sock
.rQwait("VMware cursor alpha encoding",
1837 rgba
= new Array(w
* h
* bytesPerPixel
);
1839 for (let pixel
= 0; pixel
< (w
* h
); pixel
++) {
1840 let data
= this._sock
.rQshift32();
1842 rgba
[(pixel
* 4) ] = data
>> 24 & 0xff; //r
1843 rgba
[(pixel
* 4) + 1 ] = data
>> 16 & 0xff; //g
1844 rgba
[(pixel
* 4) + 2 ] = data
>> 8 & 0xff; //b
1845 rgba
[(pixel
* 4) + 3 ] = data
& 0xff; //a
1849 Log
.Warn("The given cursor type is not supported: "
1850 + cursor_type
+ " given.");
1854 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1860 const hotx
= this._FBU
.x
; // hotspot-x
1861 const hoty
= this._FBU
.y
; // hotspot-y
1862 const w
= this._FBU
.width
;
1863 const h
= this._FBU
.height
;
1865 const pixelslength
= w
* h
* 4;
1866 const masklength
= Math
.ceil(w
/ 8) * h
;
1868 let bytes
= pixelslength
+ masklength
;
1869 if (this._sock
.rQwait("cursor encoding", bytes
)) {
1873 // Decode from BGRX pixels + bit mask to RGBA
1874 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
1875 const mask
= this._sock
.rQshiftBytes(masklength
);
1876 let rgba
= new Uint8Array(w
* h
* 4);
1879 for (let y
= 0; y
< h
; y
++) {
1880 for (let x
= 0; x
< w
; x
++) {
1881 let mask_idx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
1882 let alpha
= (mask
[mask_idx
] << (x
% 8)) & 0x80 ? 255 : 0;
1883 rgba
[pix_idx
] = pixels
[pix_idx
+ 2];
1884 rgba
[pix_idx
+ 1] = pixels
[pix_idx
+ 1];
1885 rgba
[pix_idx
+ 2] = pixels
[pix_idx
];
1886 rgba
[pix_idx
+ 3] = alpha
;
1891 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1896 _handleDesktopName() {
1897 if (this._sock
.rQwait("DesktopName", 4)) {
1901 let length
= this._sock
.rQshift32();
1903 if (this._sock
.rQwait("DesktopName", length
, 4)) {
1907 let name
= this._sock
.rQshiftStr(length
);
1908 name
= decodeUTF8(name
, true);
1910 this._setDesktopName(name
);
1915 _handleExtendedDesktopSize() {
1916 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
1920 const number_of_screens
= this._sock
.rQpeek8();
1922 let bytes
= 4 + (number_of_screens
* 16);
1923 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
1927 const firstUpdate
= !this._supportsSetDesktopSize
;
1928 this._supportsSetDesktopSize
= true;
1930 // Normally we only apply the current resize mode after a
1931 // window resize event. However there is no such trigger on the
1932 // initial connect. And we don't know if the server supports
1933 // resizing until we've gotten here.
1935 this._requestRemoteResize();
1938 this._sock
.rQskipBytes(1); // number-of-screens
1939 this._sock
.rQskipBytes(3); // padding
1941 for (let i
= 0; i
< number_of_screens
; i
+= 1) {
1942 // Save the id and flags of the first screen
1944 this._screen_id
= this._sock
.rQshiftBytes(4); // id
1945 this._sock
.rQskipBytes(2); // x-position
1946 this._sock
.rQskipBytes(2); // y-position
1947 this._sock
.rQskipBytes(2); // width
1948 this._sock
.rQskipBytes(2); // height
1949 this._screen_flags
= this._sock
.rQshiftBytes(4); // flags
1951 this._sock
.rQskipBytes(16);
1956 * The x-position indicates the reason for the change:
1958 * 0 - server resized on its own
1959 * 1 - this client requested the resize
1960 * 2 - another client requested the resize
1963 // We need to handle errors when we requested the resize.
1964 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
1966 // The y-position indicates the status code from the server
1967 switch (this._FBU
.y
) {
1969 msg
= "Resize is administratively prohibited";
1972 msg
= "Out of resources";
1975 msg
= "Invalid screen layout";
1978 msg
= "Unknown reason";
1981 Log
.Warn("Server did not accept the resize request: "
1984 this._resize(this._FBU
.width
, this._FBU
.height
);
1991 let decoder
= this._decoders
[this._FBU
.encoding
];
1993 this._fail("Unsupported encoding (encoding: " +
1994 this._FBU
.encoding
+ ")");
1999 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
2000 this._FBU
.width
, this._FBU
.height
,
2001 this._sock
, this._display
,
2004 this._fail("Error decoding rect: " + err
);
2009 _updateContinuousUpdates() {
2010 if (!this._enabledContinuousUpdates
) { return; }
2012 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
2013 this._fb_width
, this._fb_height
);
2016 _resize(width
, height
) {
2017 this._fb_width
= width
;
2018 this._fb_height
= height
;
2020 this._display
.resize(this._fb_width
, this._fb_height
);
2022 // Adjust the visible viewport based on the new dimensions
2024 this._updateScale();
2026 this._updateContinuousUpdates();
2030 if (this._rfb_xvp_ver
< ver
) { return; }
2031 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
2032 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
2035 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
2036 this._cursorImage
= {
2038 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
2040 this._refreshCursor();
2043 _shouldShowDotCursor() {
2044 // Called when this._cursorImage is updated
2045 if (!this._showDotCursor
) {
2046 // User does not want to see the dot, so...
2050 // The dot should not be shown if the cursor is already visible,
2051 // i.e. contains at least one not-fully-transparent pixel.
2052 // So iterate through all alpha bytes in rgba and stop at the
2054 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
2055 if (this._cursorImage
.rgbaPixels
[i
]) {
2060 // At this point, we know that the cursor is fully transparent, and
2061 // the user wants to see the dot instead of this.
2066 if (this._rfb_connection_state
!== "connecting" &&
2067 this._rfb_connection_state
!== "connected") {
2070 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
2071 this._cursor
.change(image
.rgbaPixels
,
2072 image
.hotx
, image
.hoty
,
2077 static genDES(password
, challenge
) {
2078 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
2079 return (new DES(passwordChars
)).encrypt(challenge
);
2085 keyEvent(sock
, keysym
, down
) {
2086 const buff
= sock
._sQ
;
2087 const offset
= sock
._sQlen
;
2089 buff
[offset
] = 4; // msg-type
2090 buff
[offset
+ 1] = down
;
2092 buff
[offset
+ 2] = 0;
2093 buff
[offset
+ 3] = 0;
2095 buff
[offset
+ 4] = (keysym
>> 24);
2096 buff
[offset
+ 5] = (keysym
>> 16);
2097 buff
[offset
+ 6] = (keysym
>> 8);
2098 buff
[offset
+ 7] = keysym
;
2104 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
2105 function getRFBkeycode(xt_scancode
) {
2106 const upperByte
= (keycode
>> 8);
2107 const lowerByte
= (keycode
& 0x00ff);
2108 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
2109 return lowerByte
| 0x80;
2114 const buff
= sock
._sQ
;
2115 const offset
= sock
._sQlen
;
2117 buff
[offset
] = 255; // msg-type
2118 buff
[offset
+ 1] = 0; // sub msg-type
2120 buff
[offset
+ 2] = (down
>> 8);
2121 buff
[offset
+ 3] = down
;
2123 buff
[offset
+ 4] = (keysym
>> 24);
2124 buff
[offset
+ 5] = (keysym
>> 16);
2125 buff
[offset
+ 6] = (keysym
>> 8);
2126 buff
[offset
+ 7] = keysym
;
2128 const RFBkeycode
= getRFBkeycode(keycode
);
2130 buff
[offset
+ 8] = (RFBkeycode
>> 24);
2131 buff
[offset
+ 9] = (RFBkeycode
>> 16);
2132 buff
[offset
+ 10] = (RFBkeycode
>> 8);
2133 buff
[offset
+ 11] = RFBkeycode
;
2139 pointerEvent(sock
, x
, y
, mask
) {
2140 const buff
= sock
._sQ
;
2141 const offset
= sock
._sQlen
;
2143 buff
[offset
] = 5; // msg-type
2145 buff
[offset
+ 1] = mask
;
2147 buff
[offset
+ 2] = x
>> 8;
2148 buff
[offset
+ 3] = x
;
2150 buff
[offset
+ 4] = y
>> 8;
2151 buff
[offset
+ 5] = y
;
2157 // Used to build Notify and Request data.
2158 _buildExtendedClipboardFlags(actions
, formats
) {
2159 let data
= new Uint8Array(4);
2160 let formatFlag
= 0x00000000;
2161 let actionFlag
= 0x00000000;
2163 for (let i
= 0; i
< actions
.length
; i
++) {
2164 actionFlag
|= actions
[i
];
2167 for (let i
= 0; i
< formats
.length
; i
++) {
2168 formatFlag
|= formats
[i
];
2171 data
[0] = actionFlag
>> 24; // Actions
2172 data
[1] = 0x00; // Reserved
2173 data
[2] = 0x00; // Reserved
2174 data
[3] = formatFlag
; // Formats
2179 extendedClipboardProvide(sock
, formats
, inData
) {
2180 // Deflate incomming data and their sizes
2181 let deflator
= new Deflator();
2182 let dataToDeflate
= [];
2184 for (let i
= 0; i
< formats
.length
; i
++) {
2185 // We only support the format Text at this time
2186 if (formats
[i
] != extendedClipboardFormatText
) {
2187 throw new Error("Unsupported extended clipboard format for Provide message.");
2190 // Change lone \r or \n into \r\n as defined in rfbproto
2191 inData
[i
] = inData
[i
].replace(/\r\n|\r|\n/gm, "\r\n");
2193 // Check if it already has \0
2194 let text
= encodeUTF8(inData
[i
] + "\0");
2196 dataToDeflate
.push( (text
.length
>> 24) & 0xFF,
2197 (text
.length
>> 16) & 0xFF,
2198 (text
.length
>> 8) & 0xFF,
2199 (text
.length
& 0xFF));
2201 for (let j
= 0; j
< text
.length
; j
++) {
2202 dataToDeflate
.push(text
.charCodeAt(j
));
2206 let deflatedData
= deflator
.deflate(new Uint8Array(dataToDeflate
));
2208 // Build data to send
2209 let data
= new Uint8Array(4 + deflatedData
.length
);
2210 data
.set(RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionProvide
],
2212 data
.set(deflatedData
, 4);
2214 RFB
.messages
.clientCutText(sock
, data
, true);
2217 extendedClipboardNotify(sock
, formats
) {
2218 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionNotify
],
2220 RFB
.messages
.clientCutText(sock
, flags
, true);
2223 extendedClipboardRequest(sock
, formats
) {
2224 let flags
= RFB
.messages
._buildExtendedClipboardFlags([extendedClipboardActionRequest
],
2226 RFB
.messages
.clientCutText(sock
, flags
, true);
2229 extendedClipboardCaps(sock
, actions
, formats
) {
2230 let formatKeys
= Object
.keys(formats
);
2231 let data
= new Uint8Array(4 + (4 * formatKeys
.length
));
2233 formatKeys
.map(x
=> parseInt(x
));
2234 formatKeys
.sort((a
, b
) => a
- b
);
2236 data
.set(RFB
.messages
._buildExtendedClipboardFlags(actions
, []));
2239 for (let i
= 0; i
< formatKeys
.length
; i
++) {
2240 data
[loopOffset
] = formats
[formatKeys
[i
]] >> 24;
2241 data
[loopOffset
+ 1] = formats
[formatKeys
[i
]] >> 16;
2242 data
[loopOffset
+ 2] = formats
[formatKeys
[i
]] >> 8;
2243 data
[loopOffset
+ 3] = formats
[formatKeys
[i
]] >> 0;
2246 data
[3] |= (1 << formatKeys
[i
]); // Update our format flags
2249 RFB
.messages
.clientCutText(sock
, data
, true);
2252 clientCutText(sock
, data
, extended
= false) {
2253 const buff
= sock
._sQ
;
2254 const offset
= sock
._sQlen
;
2256 buff
[offset
] = 6; // msg-type
2258 buff
[offset
+ 1] = 0; // padding
2259 buff
[offset
+ 2] = 0; // padding
2260 buff
[offset
+ 3] = 0; // padding
2264 length
= toUnsigned32bit(-data
.length
);
2266 length
= data
.length
;
2269 buff
[offset
+ 4] = length
>> 24;
2270 buff
[offset
+ 5] = length
>> 16;
2271 buff
[offset
+ 6] = length
>> 8;
2272 buff
[offset
+ 7] = length
;
2276 // We have to keep track of from where in the data we begin creating the
2277 // buffer for the flush in the next iteration.
2280 let remaining
= data
.length
;
2281 while (remaining
> 0) {
2283 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
2284 for (let i
= 0; i
< flushSize
; i
++) {
2285 buff
[sock
._sQlen
+ i
] = data
[dataOffset
+ i
];
2288 sock
._sQlen
+= flushSize
;
2291 remaining
-= flushSize
;
2292 dataOffset
+= flushSize
;
2297 setDesktopSize(sock
, width
, height
, id
, flags
) {
2298 const buff
= sock
._sQ
;
2299 const offset
= sock
._sQlen
;
2301 buff
[offset
] = 251; // msg-type
2302 buff
[offset
+ 1] = 0; // padding
2303 buff
[offset
+ 2] = width
>> 8; // width
2304 buff
[offset
+ 3] = width
;
2305 buff
[offset
+ 4] = height
>> 8; // height
2306 buff
[offset
+ 5] = height
;
2308 buff
[offset
+ 6] = 1; // number-of-screens
2309 buff
[offset
+ 7] = 0; // padding
2312 buff
[offset
+ 8] = id
>> 24; // id
2313 buff
[offset
+ 9] = id
>> 16;
2314 buff
[offset
+ 10] = id
>> 8;
2315 buff
[offset
+ 11] = id
;
2316 buff
[offset
+ 12] = 0; // x-position
2317 buff
[offset
+ 13] = 0;
2318 buff
[offset
+ 14] = 0; // y-position
2319 buff
[offset
+ 15] = 0;
2320 buff
[offset
+ 16] = width
>> 8; // width
2321 buff
[offset
+ 17] = width
;
2322 buff
[offset
+ 18] = height
>> 8; // height
2323 buff
[offset
+ 19] = height
;
2324 buff
[offset
+ 20] = flags
>> 24; // flags
2325 buff
[offset
+ 21] = flags
>> 16;
2326 buff
[offset
+ 22] = flags
>> 8;
2327 buff
[offset
+ 23] = flags
;
2333 clientFence(sock
, flags
, payload
) {
2334 const buff
= sock
._sQ
;
2335 const offset
= sock
._sQlen
;
2337 buff
[offset
] = 248; // msg-type
2339 buff
[offset
+ 1] = 0; // padding
2340 buff
[offset
+ 2] = 0; // padding
2341 buff
[offset
+ 3] = 0; // padding
2343 buff
[offset
+ 4] = flags
>> 24; // flags
2344 buff
[offset
+ 5] = flags
>> 16;
2345 buff
[offset
+ 6] = flags
>> 8;
2346 buff
[offset
+ 7] = flags
;
2348 const n
= payload
.length
;
2350 buff
[offset
+ 8] = n
; // length
2352 for (let i
= 0; i
< n
; i
++) {
2353 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
2356 sock
._sQlen
+= 9 + n
;
2360 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
2361 const buff
= sock
._sQ
;
2362 const offset
= sock
._sQlen
;
2364 buff
[offset
] = 150; // msg-type
2365 buff
[offset
+ 1] = enable
; // enable-flag
2367 buff
[offset
+ 2] = x
>> 8; // x
2368 buff
[offset
+ 3] = x
;
2369 buff
[offset
+ 4] = y
>> 8; // y
2370 buff
[offset
+ 5] = y
;
2371 buff
[offset
+ 6] = width
>> 8; // width
2372 buff
[offset
+ 7] = width
;
2373 buff
[offset
+ 8] = height
>> 8; // height
2374 buff
[offset
+ 9] = height
;
2380 pixelFormat(sock
, depth
, true_color
) {
2381 const buff
= sock
._sQ
;
2382 const offset
= sock
._sQlen
;
2388 } else if (depth
> 8) {
2394 const bits
= Math
.floor(depth
/3);
2396 buff
[offset
] = 0; // msg-type
2398 buff
[offset
+ 1] = 0; // padding
2399 buff
[offset
+ 2] = 0; // padding
2400 buff
[offset
+ 3] = 0; // padding
2402 buff
[offset
+ 4] = bpp
; // bits-per-pixel
2403 buff
[offset
+ 5] = depth
; // depth
2404 buff
[offset
+ 6] = 0; // little-endian
2405 buff
[offset
+ 7] = true_color
? 1 : 0; // true-color
2407 buff
[offset
+ 8] = 0; // red-max
2408 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
2410 buff
[offset
+ 10] = 0; // green-max
2411 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
2413 buff
[offset
+ 12] = 0; // blue-max
2414 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
2416 buff
[offset
+ 14] = bits
* 2; // red-shift
2417 buff
[offset
+ 15] = bits
* 1; // green-shift
2418 buff
[offset
+ 16] = bits
* 0; // blue-shift
2420 buff
[offset
+ 17] = 0; // padding
2421 buff
[offset
+ 18] = 0; // padding
2422 buff
[offset
+ 19] = 0; // padding
2428 clientEncodings(sock
, encodings
) {
2429 const buff
= sock
._sQ
;
2430 const offset
= sock
._sQlen
;
2432 buff
[offset
] = 2; // msg-type
2433 buff
[offset
+ 1] = 0; // padding
2435 buff
[offset
+ 2] = encodings
.length
>> 8;
2436 buff
[offset
+ 3] = encodings
.length
;
2439 for (let i
= 0; i
< encodings
.length
; i
++) {
2440 const enc
= encodings
[i
];
2441 buff
[j
] = enc
>> 24;
2442 buff
[j
+ 1] = enc
>> 16;
2443 buff
[j
+ 2] = enc
>> 8;
2449 sock
._sQlen
+= j
- offset
;
2453 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
2454 const buff
= sock
._sQ
;
2455 const offset
= sock
._sQlen
;
2457 if (typeof(x
) === "undefined") { x
= 0; }
2458 if (typeof(y
) === "undefined") { y
= 0; }
2460 buff
[offset
] = 3; // msg-type
2461 buff
[offset
+ 1] = incremental
? 1 : 0;
2463 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2464 buff
[offset
+ 3] = x
& 0xFF;
2466 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2467 buff
[offset
+ 5] = y
& 0xFF;
2469 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2470 buff
[offset
+ 7] = w
& 0xFF;
2472 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2473 buff
[offset
+ 9] = h
& 0xFF;
2479 xvpOp(sock
, ver
, op
) {
2480 const buff
= sock
._sQ
;
2481 const offset
= sock
._sQlen
;
2483 buff
[offset
] = 250; // msg-type
2484 buff
[offset
+ 1] = 0; // padding
2486 buff
[offset
+ 2] = ver
;
2487 buff
[offset
+ 3] = op
;
2496 rgbaPixels
: new Uint8Array(),
2502 /* eslint-disable indent */
2503 rgbaPixels
: new Uint8Array([
2504 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2505 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2506 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2508 /* eslint-enable indent */