2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2018 The noVNC Authors
4 * Licensed under MPL 2.0 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
10 import * as Log
from './util/logging.js';
11 import { decodeUTF8
} from './util/strings.js';
12 import { dragThreshold
} from './util/browser.js';
13 import EventTargetMixin
from './util/eventtarget.js';
14 import Display
from "./display.js";
15 import Keyboard
from "./input/keyboard.js";
16 import Mouse
from "./input/mouse.js";
17 import Cursor
from "./util/cursor.js";
18 import Websock
from "./websock.js";
19 import DES
from "./des.js";
20 import KeyTable
from "./input/keysym.js";
21 import XtScancode
from "./input/xtscancodes.js";
22 import { encodings
} from "./encodings.js";
23 import "./util/polyfill.js";
25 import RawDecoder
from "./decoders/raw.js";
26 import CopyRectDecoder
from "./decoders/copyrect.js";
27 import RREDecoder
from "./decoders/rre.js";
28 import HextileDecoder
from "./decoders/hextile.js";
29 import TightDecoder
from "./decoders/tight.js";
30 import TightPNGDecoder
from "./decoders/tightpng.js";
32 // How many seconds to wait for a disconnect to finish
33 const DISCONNECT_TIMEOUT
= 3;
34 const DEFAULT_BACKGROUND
= 'rgb(40, 40, 40)';
36 export default class RFB
extends EventTargetMixin
{
37 constructor(target
, url
, options
) {
39 throw new Error("Must specify target");
42 throw new Error("Must specify URL");
47 this._target
= target
;
51 options
= options
|| {};
52 this._rfb_credentials
= options
.credentials
|| {};
53 this._shared
= 'shared' in options
? !!options
.shared
: true;
54 this._repeaterID
= options
.repeaterID
|| '';
55 this._showDotCursor
= options
.showDotCursor
|| false;
58 this._rfb_connection_state
= '';
59 this._rfb_init_state
= '';
60 this._rfb_auth_scheme
= -1;
61 this._rfb_clean_disconnect
= true;
63 // Server capabilities
64 this._rfb_version
= 0;
65 this._rfb_max_version
= 3.8;
66 this._rfb_tightvnc
= false;
67 this._rfb_xvp_ver
= 0;
74 this._capabilities
= { power
: false };
76 this._supportsFence
= false;
78 this._supportsContinuousUpdates
= false;
79 this._enabledContinuousUpdates
= false;
81 this._supportsSetDesktopSize
= false;
83 this._screen_flags
= 0;
85 this._qemuExtKeyEventSupported
= false;
88 this._sock
= null; // Websock object
89 this._display
= null; // Display object
90 this._flushing
= false; // Display flushing state
91 this._keyboard
= null; // Keyboard input handler object
92 this._mouse
= null; // Mouse input handler object
95 this._disconnTimer
= null; // disconnection timer
96 this._resizeTimeout
= null; // resize rate limiting
111 this._mouse_buttonMask
= 0;
112 this._mouse_arr
= [];
113 this._viewportDragging
= false;
114 this._viewportDragPos
= {};
115 this._viewportHasMoved
= false;
117 // Bound event handlers
118 this._eventHandlers
= {
119 focusCanvas
: this._focusCanvas
.bind(this),
120 windowResize
: this._windowResize
.bind(this),
124 Log
.Debug(">> RFB.constructor");
126 // Create DOM elements
127 this._screen
= document
.createElement('div');
128 this._screen
.style
.display
= 'flex';
129 this._screen
.style
.width
= '100%';
130 this._screen
.style
.height
= '100%';
131 this._screen
.style
.overflow
= 'auto';
132 this._screen
.style
.background
= DEFAULT_BACKGROUND
;
133 this._canvas
= document
.createElement('canvas');
134 this._canvas
.style
.margin
= 'auto';
135 // Some browsers add an outline on focus
136 this._canvas
.style
.outline
= 'none';
137 // IE miscalculates width without this :(
138 this._canvas
.style
.flexShrink
= '0';
139 this._canvas
.width
= 0;
140 this._canvas
.height
= 0;
141 this._canvas
.tabIndex
= -1;
142 this._screen
.appendChild(this._canvas
);
145 this._cursor
= new Cursor();
147 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
148 // it. Result: no cursor at all until a window border or an edit field
149 // is hit blindly. But there are also VNC servers that draw the cursor
150 // in the framebuffer and don't send the empty local cursor. There is
151 // no way to satisfy both sides.
153 // The spec is unclear on this "initial cursor" issue. Many other
154 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
155 // initial cursor instead.
156 this._cursorImage
= RFB
.cursors
.none
;
158 // populate decoder array with objects
159 this._decoders
[encodings
.encodingRaw
] = new RawDecoder();
160 this._decoders
[encodings
.encodingCopyRect
] = new CopyRectDecoder();
161 this._decoders
[encodings
.encodingRRE
] = new RREDecoder();
162 this._decoders
[encodings
.encodingHextile
] = new HextileDecoder();
163 this._decoders
[encodings
.encodingTight
] = new TightDecoder();
164 this._decoders
[encodings
.encodingTightPNG
] = new TightPNGDecoder();
166 // NB: nothing that needs explicit teardown should be done
167 // before this point, since this can throw an exception
169 this._display
= new Display(this._canvas
);
171 Log
.Error("Display exception: " + exc
);
174 this._display
.onflush
= this._onFlush
.bind(this);
175 this._display
.clear();
177 this._keyboard
= new Keyboard(this._canvas
);
178 this._keyboard
.onkeyevent
= this._handleKeyEvent
.bind(this);
180 this._mouse
= new Mouse(this._canvas
);
181 this._mouse
.onmousebutton
= this._handleMouseButton
.bind(this);
182 this._mouse
.onmousemove
= this._handleMouseMove
.bind(this);
184 this._sock
= new Websock();
185 this._sock
.on('message', () => {
186 this._handle_message();
188 this._sock
.on('open', () => {
189 if ((this._rfb_connection_state
=== 'connecting') &&
190 (this._rfb_init_state
=== '')) {
191 this._rfb_init_state
= 'ProtocolVersion';
192 Log
.Debug("Starting VNC handshake");
194 this._fail("Unexpected server connection while " +
195 this._rfb_connection_state
);
198 this._sock
.on('close', (e
) => {
199 Log
.Debug("WebSocket on-close event");
202 msg
= "(code: " + e
.code
;
204 msg
+= ", reason: " + e
.reason
;
208 switch (this._rfb_connection_state
) {
210 this._fail("Connection closed " + msg
);
213 // Handle disconnects that were initiated server-side
214 this._updateConnectionState('disconnecting');
215 this._updateConnectionState('disconnected');
217 case 'disconnecting':
218 // Normal disconnection path
219 this._updateConnectionState('disconnected');
222 this._fail("Unexpected server disconnect " +
223 "when already disconnected " + msg
);
226 this._fail("Unexpected server disconnect before connecting " +
230 this._sock
.off('close');
232 this._sock
.on('error', e
=> Log
.Warn("WebSocket on-error event"));
234 // Slight delay of the actual connection so that the caller has
235 // time to set up callbacks
236 setTimeout(this._updateConnectionState
.bind(this, 'connecting'));
238 Log
.Debug("<< RFB.constructor");
240 // ===== PROPERTIES =====
242 this.dragViewport
= false;
243 this.focusOnClick
= true;
245 this._viewOnly
= false;
246 this._clipViewport
= false;
247 this._scaleViewport
= false;
248 this._resizeSession
= false;
251 // ===== PROPERTIES =====
253 get viewOnly() { return this._viewOnly
; }
254 set viewOnly(viewOnly
) {
255 this._viewOnly
= viewOnly
;
257 if (this._rfb_connection_state
=== "connecting" ||
258 this._rfb_connection_state
=== "connected") {
260 this._keyboard
.ungrab();
261 this._mouse
.ungrab();
263 this._keyboard
.grab();
269 get capabilities() { return this._capabilities
; }
271 get touchButton() { return this._mouse
.touchButton
; }
272 set touchButton(button
) { this._mouse
.touchButton
= button
; }
274 get clipViewport() { return this._clipViewport
; }
275 set clipViewport(viewport
) {
276 this._clipViewport
= viewport
;
280 get scaleViewport() { return this._scaleViewport
; }
281 set scaleViewport(scale
) {
282 this._scaleViewport
= scale
;
283 // Scaling trumps clipping, so we may need to adjust
284 // clipping when enabling or disabling scaling
285 if (scale
&& this._clipViewport
) {
289 if (!scale
&& this._clipViewport
) {
294 get resizeSession() { return this._resizeSession
; }
295 set resizeSession(resize
) {
296 this._resizeSession
= resize
;
298 this._requestRemoteResize();
302 get showDotCursor() { return this._showDotCursor
; }
303 set showDotCursor(show
) {
304 this._showDotCursor
= show
;
305 this._refreshCursor();
308 get background() { return this._screen
.style
.background
; }
309 set background(cssValue
) { this._screen
.style
.background
= cssValue
; }
311 // ===== PUBLIC METHODS =====
314 this._updateConnectionState('disconnecting');
315 this._sock
.off('error');
316 this._sock
.off('message');
317 this._sock
.off('open');
320 sendCredentials(creds
) {
321 this._rfb_credentials
= creds
;
322 setTimeout(this._init_msg
.bind(this), 0);
326 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
327 Log
.Info("Sending Ctrl-Alt-Del");
329 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
330 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
331 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
332 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
333 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
334 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
349 // Send a key press. If 'down' is not specified then send a down key
350 // followed by an up key.
351 sendKey(keysym
, code
, down
) {
352 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
354 if (down
=== undefined) {
355 this.sendKey(keysym
, code
, true);
356 this.sendKey(keysym
, code
, false);
360 const scancode
= XtScancode
[code
];
362 if (this._qemuExtKeyEventSupported
&& scancode
) {
364 keysym
= keysym
|| 0;
366 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
368 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
373 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
374 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
379 this._canvas
.focus();
386 clipboardPasteFrom(text
) {
387 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
388 RFB
.messages
.clientCutText(this._sock
, text
);
391 // ===== PRIVATE METHODS =====
394 Log
.Debug(">> RFB.connect");
396 Log
.Info("connecting to " + this._url
);
399 // WebSocket.onopen transitions to the RFB init states
400 this._sock
.open(this._url
, ['binary']);
402 if (e
.name
=== 'SyntaxError') {
403 this._fail("Invalid host or port (" + e
+ ")");
405 this._fail("Error when opening socket (" + e
+ ")");
409 // Make our elements part of the page
410 this._target
.appendChild(this._screen
);
412 this._cursor
.attach(this._canvas
);
413 this._refreshCursor();
415 // Monitor size changes of the screen
416 // FIXME: Use ResizeObserver, or hidden overflow
417 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
419 // Always grab focus on some kind of click event
420 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
421 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
423 Log
.Debug("<< RFB.connect");
427 Log
.Debug(">> RFB.disconnect");
428 this._cursor
.detach();
429 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
430 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
431 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
432 this._keyboard
.ungrab();
433 this._mouse
.ungrab();
436 this._target
.removeChild(this._screen
);
438 if (e
.name
=== 'NotFoundError') {
439 // Some cases where the initial connection fails
440 // can disconnect before the _screen is created
445 clearTimeout(this._resizeTimeout
);
446 Log
.Debug("<< RFB.disconnect");
449 _focusCanvas(event
) {
450 // Respect earlier handlers' request to not do side-effects
451 if (event
.defaultPrevented
) {
455 if (!this.focusOnClick
) {
462 _windowResize(event
) {
463 // If the window resized then our screen element might have
464 // as well. Update the viewport dimensions.
465 window
.requestAnimationFrame(() => {
470 if (this._resizeSession
) {
471 // Request changing the resolution of the remote display to
472 // the size of the local browser viewport.
474 // In order to not send multiple requests before the browser-resize
475 // is finished we wait 0.5 seconds before sending the request.
476 clearTimeout(this._resizeTimeout
);
477 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
481 // Update state of clipping in Display object, and make sure the
482 // configured viewport matches the current screen size
484 const cur_clip
= this._display
.clipViewport
;
485 let new_clip
= this._clipViewport
;
487 if (this._scaleViewport
) {
488 // Disable viewport clipping if we are scaling
492 if (cur_clip
!== new_clip
) {
493 this._display
.clipViewport
= new_clip
;
497 // When clipping is enabled, the screen is limited to
498 // the size of the container.
499 const size
= this._screenSize();
500 this._display
.viewportChangeSize(size
.w
, size
.h
);
501 this._fixScrollbars();
506 if (!this._scaleViewport
) {
507 this._display
.scale
= 1.0;
509 const size
= this._screenSize();
510 this._display
.autoscale(size
.w
, size
.h
);
512 this._fixScrollbars();
515 // Requests a change of remote desktop size. This message is an extension
516 // and may only be sent if we have received an ExtendedDesktopSize message
517 _requestRemoteResize() {
518 clearTimeout(this._resizeTimeout
);
519 this._resizeTimeout
= null;
521 if (!this._resizeSession
|| this._viewOnly
||
522 !this._supportsSetDesktopSize
) {
526 const size
= this._screenSize();
527 RFB
.messages
.setDesktopSize(this._sock
,
528 Math
.floor(size
.w
), Math
.floor(size
.h
),
529 this._screen_id
, this._screen_flags
);
531 Log
.Debug('Requested new desktop size: ' +
532 size
.w
+ 'x' + size
.h
);
535 // Gets the the size of the available screen
537 let r
= this._screen
.getBoundingClientRect();
538 return { w
: r
.width
, h
: r
.height
};
542 // This is a hack because Chrome screws up the calculation
543 // for when scrollbars are needed. So to fix it we temporarily
544 // toggle them off and on.
545 const orig
= this._screen
.style
.overflow
;
546 this._screen
.style
.overflow
= 'hidden';
547 // Force Chrome to recalculate the layout by asking for
548 // an element's dimensions
549 this._screen
.getBoundingClientRect();
550 this._screen
.style
.overflow
= orig
;
558 * disconnected - permanent state
560 _updateConnectionState(state
) {
561 const oldstate
= this._rfb_connection_state
;
563 if (state
=== oldstate
) {
564 Log
.Debug("Already in state '" + state
+ "', ignoring");
568 // The 'disconnected' state is permanent for each RFB object
569 if (oldstate
=== 'disconnected') {
570 Log
.Error("Tried changing state of a disconnected RFB object");
574 // Ensure proper transitions before doing anything
577 if (oldstate
!== 'connecting') {
578 Log
.Error("Bad transition to connected state, " +
579 "previous connection state: " + oldstate
);
585 if (oldstate
!== 'disconnecting') {
586 Log
.Error("Bad transition to disconnected state, " +
587 "previous connection state: " + oldstate
);
593 if (oldstate
!== '') {
594 Log
.Error("Bad transition to connecting state, " +
595 "previous connection state: " + oldstate
);
600 case 'disconnecting':
601 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
602 Log
.Error("Bad transition to disconnecting state, " +
603 "previous connection state: " + oldstate
);
609 Log
.Error("Unknown connection state: " + state
);
613 // State change actions
615 this._rfb_connection_state
= state
;
617 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
619 if (this._disconnTimer
&& state
!== 'disconnecting') {
620 Log
.Debug("Clearing disconnect timer");
621 clearTimeout(this._disconnTimer
);
622 this._disconnTimer
= null;
624 // make sure we don't get a double event
625 this._sock
.off('close');
634 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
637 case 'disconnecting':
640 this._disconnTimer
= setTimeout(() => {
641 Log
.Error("Disconnection timed out.");
642 this._updateConnectionState('disconnected');
643 }, DISCONNECT_TIMEOUT
* 1000);
647 this.dispatchEvent(new CustomEvent(
648 "disconnect", { detail
:
649 { clean
: this._rfb_clean_disconnect
} }));
654 /* Print errors and disconnect
656 * The parameter 'details' is used for information that
657 * should be logged but not sent to the user interface.
660 switch (this._rfb_connection_state
) {
661 case 'disconnecting':
662 Log
.Error("Failed when disconnecting: " + details
);
665 Log
.Error("Failed while connected: " + details
);
668 Log
.Error("Failed when connecting: " + details
);
671 Log
.Error("RFB failure: " + details
);
674 this._rfb_clean_disconnect
= false; //This is sent to the UI
676 // Transition to disconnected without waiting for socket to close
677 this._updateConnectionState('disconnecting');
678 this._updateConnectionState('disconnected');
683 _setCapability(cap
, val
) {
684 this._capabilities
[cap
] = val
;
685 this.dispatchEvent(new CustomEvent("capabilities",
686 { detail
: { capabilities
: this._capabilities
} }));
690 if (this._sock
.rQlen
=== 0) {
691 Log
.Warn("handle_message called on an empty receive queue");
695 switch (this._rfb_connection_state
) {
697 Log
.Error("Got data while disconnected");
701 if (this._flushing
) {
704 if (!this._normal_msg()) {
707 if (this._sock
.rQlen
=== 0) {
718 _handleKeyEvent(keysym
, code
, down
) {
719 this.sendKey(keysym
, code
, down
);
722 _handleMouseButton(x
, y
, down
, bmask
) {
724 this._mouse_buttonMask
|= bmask
;
726 this._mouse_buttonMask
&= ~bmask
;
729 if (this.dragViewport
) {
730 if (down
&& !this._viewportDragging
) {
731 this._viewportDragging
= true;
732 this._viewportDragPos
= {'x': x
, 'y': y
};
733 this._viewportHasMoved
= false;
735 // Skip sending mouse events
738 this._viewportDragging
= false;
740 // If we actually performed a drag then we are done
741 // here and should not send any mouse events
742 if (this._viewportHasMoved
) {
746 // Otherwise we treat this as a mouse click event.
747 // Send the button down event here, as the button up
748 // event is sent at the end of this function.
749 RFB
.messages
.pointerEvent(this._sock
,
750 this._display
.absX(x
),
751 this._display
.absY(y
),
756 if (this._viewOnly
) { return; } // View only, skip mouse events
758 if (this._rfb_connection_state
!== 'connected') { return; }
759 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
762 _handleMouseMove(x
, y
) {
763 if (this._viewportDragging
) {
764 const deltaX
= this._viewportDragPos
.x
- x
;
765 const deltaY
= this._viewportDragPos
.y
- y
;
767 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
768 Math
.abs(deltaY
) > dragThreshold
)) {
769 this._viewportHasMoved
= true;
771 this._viewportDragPos
= {'x': x
, 'y': y
};
772 this._display
.viewportChangePos(deltaX
, deltaY
);
775 // Skip sending mouse events
779 if (this._viewOnly
) { return; } // View only, skip mouse events
781 if (this._rfb_connection_state
!== 'connected') { return; }
782 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
787 _negotiate_protocol_version() {
788 if (this._sock
.rQwait("version", 12)) {
792 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
793 Log
.Info("Server ProtocolVersion: " + sversion
);
796 case "000.000": // UltraVNC repeater
800 case "003.006": // UltraVNC
801 case "003.889": // Apple Remote Desktop
802 this._rfb_version
= 3.3;
805 this._rfb_version
= 3.7;
808 case "004.000": // Intel AMT KVM
809 case "004.001": // RealVNC 4.6
810 case "005.000": // RealVNC 5.3
811 this._rfb_version
= 3.8;
814 return this._fail("Invalid server version " + sversion
);
818 let repeaterID
= "ID:" + this._repeaterID
;
819 while (repeaterID
.length
< 250) {
822 this._sock
.send_string(repeaterID
);
826 if (this._rfb_version
> this._rfb_max_version
) {
827 this._rfb_version
= this._rfb_max_version
;
830 const cversion
= "00" + parseInt(this._rfb_version
, 10) +
831 ".00" + ((this._rfb_version
* 10) % 10);
832 this._sock
.send_string("RFB " + cversion
+ "\n");
833 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
835 this._rfb_init_state
= 'Security';
838 _negotiate_security() {
839 // Polyfill since IE and PhantomJS doesn't have
840 // TypedArray.includes()
841 function includes(item
, array
) {
842 for (let i
= 0; i
< array
.length
; i
++) {
843 if (array
[i
] === item
) {
850 if (this._rfb_version
>= 3.7) {
851 // Server sends supported list, client decides
852 const num_types
= this._sock
.rQshift8();
853 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
855 if (num_types
=== 0) {
856 this._rfb_init_state
= "SecurityReason";
857 this._security_context
= "no security types";
858 this._security_status
= 1;
859 return this._init_msg();
862 const types
= this._sock
.rQshiftBytes(num_types
);
863 Log
.Debug("Server security types: " + types
);
865 // Look for each auth in preferred order
866 if (includes(1, types
)) {
867 this._rfb_auth_scheme
= 1; // None
868 } else if (includes(22, types
)) {
869 this._rfb_auth_scheme
= 22; // XVP
870 } else if (includes(16, types
)) {
871 this._rfb_auth_scheme
= 16; // Tight
872 } else if (includes(2, types
)) {
873 this._rfb_auth_scheme
= 2; // VNC Auth
875 return this._fail("Unsupported security types (types: " + types
+ ")");
878 this._sock
.send([this._rfb_auth_scheme
]);
881 if (this._sock
.rQwait("security scheme", 4)) { return false; }
882 this._rfb_auth_scheme
= this._sock
.rQshift32();
884 if (this._rfb_auth_scheme
== 0) {
885 this._rfb_init_state
= "SecurityReason";
886 this._security_context
= "authentication scheme";
887 this._security_status
= 1;
888 return this._init_msg();
892 this._rfb_init_state
= 'Authentication';
893 Log
.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme
);
895 return this._init_msg(); // jump to authentication
898 _handle_security_reason() {
899 if (this._sock
.rQwait("reason length", 4)) {
902 const strlen
= this._sock
.rQshift32();
906 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
907 reason
= this._sock
.rQshiftStr(strlen
);
911 this.dispatchEvent(new CustomEvent(
913 { detail
: { status
: this._security_status
,
914 reason
: reason
} }));
916 return this._fail("Security negotiation failed on " +
917 this._security_context
+
918 " (reason: " + reason
+ ")");
920 this.dispatchEvent(new CustomEvent(
922 { detail
: { status
: this._security_status
} }));
924 return this._fail("Security negotiation failed on " +
925 this._security_context
);
930 _negotiate_xvp_auth() {
931 if (!this._rfb_credentials
.username
||
932 !this._rfb_credentials
.password
||
933 !this._rfb_credentials
.target
) {
934 this.dispatchEvent(new CustomEvent(
935 "credentialsrequired",
936 { detail
: { types
: ["username", "password", "target"] } }));
940 const xvp_auth_str
= String
.fromCharCode(this._rfb_credentials
.username
.length
) +
941 String
.fromCharCode(this._rfb_credentials
.target
.length
) +
942 this._rfb_credentials
.username
+
943 this._rfb_credentials
.target
;
944 this._sock
.send_string(xvp_auth_str
);
945 this._rfb_auth_scheme
= 2;
946 return this._negotiate_authentication();
949 _negotiate_std_vnc_auth() {
950 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
952 if (!this._rfb_credentials
.password
) {
953 this.dispatchEvent(new CustomEvent(
954 "credentialsrequired",
955 { detail
: { types
: ["password"] } }));
959 // TODO(directxman12): make genDES not require an Array
960 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
961 const response
= RFB
.genDES(this._rfb_credentials
.password
, challenge
);
962 this._sock
.send(response
);
963 this._rfb_init_state
= "SecurityResult";
967 _negotiate_tight_tunnels(numTunnels
) {
968 const clientSupportedTunnelTypes
= {
969 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
971 const serverSupportedTunnelTypes
= {};
972 // receive tunnel capabilities
973 for (let i
= 0; i
< numTunnels
; i
++) {
974 const cap_code
= this._sock
.rQshift32();
975 const cap_vendor
= this._sock
.rQshiftStr(4);
976 const cap_signature
= this._sock
.rQshiftStr(8);
977 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
980 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
982 // Siemens touch panels have a VNC server that supports NOTUNNEL,
983 // but forgets to advertise it. Try to detect such servers by
984 // looking for their custom tunnel type.
985 if (serverSupportedTunnelTypes
[1] &&
986 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
987 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
988 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
989 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
992 // choose the notunnel type
993 if (serverSupportedTunnelTypes
[0]) {
994 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
995 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
996 return this._fail("Client's tunnel type had the incorrect " +
997 "vendor or signature");
999 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1000 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1001 return false; // wait until we receive the sub auth count to continue
1003 return this._fail("Server wanted tunnels, but doesn't support " +
1004 "the notunnel type");
1008 _negotiate_tight_auth() {
1009 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
1010 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1011 const numTunnels
= this._sock
.rQshift32();
1012 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1014 this._rfb_tightvnc
= true;
1016 if (numTunnels
> 0) {
1017 this._negotiate_tight_tunnels(numTunnels
);
1018 return false; // wait until we receive the sub auth to continue
1022 // second pass, do the sub-auth negotiation
1023 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1024 const subAuthCount
= this._sock
.rQshift32();
1025 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1026 this._rfb_init_state
= 'SecurityResult';
1030 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1032 const clientSupportedTypes
= {
1037 const serverSupportedTypes
= [];
1039 for (let i
= 0; i
< subAuthCount
; i
++) {
1040 this._sock
.rQshift32(); // capNum
1041 const capabilities
= this._sock
.rQshiftStr(12);
1042 serverSupportedTypes
.push(capabilities
);
1045 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1047 for (let authType
in clientSupportedTypes
) {
1048 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1049 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1050 Log
.Debug("Selected authentication type: " + authType
);
1053 case 'STDVNOAUTH__': // no auth
1054 this._rfb_init_state
= 'SecurityResult';
1056 case 'STDVVNCAUTH_': // VNC auth
1057 this._rfb_auth_scheme
= 2;
1058 return this._init_msg();
1060 return this._fail("Unsupported tiny auth scheme " +
1061 "(scheme: " + authType
+ ")");
1066 return this._fail("No supported sub-auth types!");
1069 _negotiate_authentication() {
1070 switch (this._rfb_auth_scheme
) {
1072 if (this._rfb_version
>= 3.8) {
1073 this._rfb_init_state
= 'SecurityResult';
1076 this._rfb_init_state
= 'ClientInitialisation';
1077 return this._init_msg();
1079 case 22: // XVP auth
1080 return this._negotiate_xvp_auth();
1082 case 2: // VNC authentication
1083 return this._negotiate_std_vnc_auth();
1085 case 16: // TightVNC Security Type
1086 return this._negotiate_tight_auth();
1089 return this._fail("Unsupported auth scheme (scheme: " +
1090 this._rfb_auth_scheme
+ ")");
1094 _handle_security_result() {
1095 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1097 const status
= this._sock
.rQshift32();
1099 if (status
=== 0) { // OK
1100 this._rfb_init_state
= 'ClientInitialisation';
1101 Log
.Debug('Authentication OK');
1102 return this._init_msg();
1104 if (this._rfb_version
>= 3.8) {
1105 this._rfb_init_state
= "SecurityReason";
1106 this._security_context
= "security result";
1107 this._security_status
= status
;
1108 return this._init_msg();
1110 this.dispatchEvent(new CustomEvent(
1112 { detail
: { status
: status
} }));
1114 return this._fail("Security handshake failed");
1119 _negotiate_server_init() {
1120 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1123 const width
= this._sock
.rQshift16();
1124 const height
= this._sock
.rQshift16();
1127 const bpp
= this._sock
.rQshift8();
1128 const depth
= this._sock
.rQshift8();
1129 const big_endian
= this._sock
.rQshift8();
1130 const true_color
= this._sock
.rQshift8();
1132 const red_max
= this._sock
.rQshift16();
1133 const green_max
= this._sock
.rQshift16();
1134 const blue_max
= this._sock
.rQshift16();
1135 const red_shift
= this._sock
.rQshift8();
1136 const green_shift
= this._sock
.rQshift8();
1137 const blue_shift
= this._sock
.rQshift8();
1138 this._sock
.rQskipBytes(3); // padding
1140 // NB(directxman12): we don't want to call any callbacks or print messages until
1141 // *after* we're past the point where we could backtrack
1143 /* Connection name/title */
1144 const name_length = this._sock.rQshift32();
1145 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1146 this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
1148 if (this._rfb_tightvnc) {
1149 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1150 // In TightVNC mode, ServerInit message is extended
1151 const numServerMessages = this._sock.rQshift16();
1152 const numClientMessages = this._sock.rQshift16();
1153 const numEncodings = this._sock.rQshift16();
1154 this._sock.rQskipBytes(2); // padding
1156 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1157 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1159 // we don't actually do anything with the capability information that TIGHT sends,
1160 // so we just skip the all of this.
1162 // TIGHT server message capabilities
1163 this._sock.rQskipBytes(16 * numServerMessages);
1165 // TIGHT client message capabilities
1166 this._sock.rQskipBytes(16 * numClientMessages);
1168 // TIGHT encoding capabilities
1169 this._sock.rQskipBytes(16 * numEncodings);
1172 // NB(directxman12): these are down here so that we don't run them multiple times
1174 Log.Info("Screen: " + width + "x" + height +
1175 ", bpp: " + bpp + ", depth: " + depth +
1176 ", big_endian: " + big_endian +
1177 ", true_color: " + true_color +
1178 ", red_max: " + red_max +
1179 ", green_max: " + green_max +
1180 ", blue_max: " + blue_max +
1181 ", red_shift: " + red_shift +
1182 ", green_shift: " + green_shift +
1183 ", blue_shift: " + blue_shift);
1185 if (big_endian !== 0) {
1186 Log.Warn("Server native endian is not little endian");
1189 if (red_shift !== 16) {
1190 Log.Warn("Server native red-shift is not 16");
1193 if (blue_shift !== 0) {
1194 Log.Warn("Server native blue-shift is not 0");
1197 // we're past the point where we could backtrack, so it's safe to call this
1198 this.dispatchEvent(new CustomEvent(
1200 { detail: { name: this._fb_name } }));
1202 this._resize(width, height);
1204 if (!this._viewOnly) { this._keyboard.grab(); }
1205 if (!this._viewOnly) { this._mouse.grab(); }
1207 this._fb_depth = 24;
1209 if (this._fb_name === "Intel(r) AMT KVM") {
1210 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1214 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1215 this._sendEncodings();
1216 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1218 this._updateConnectionState('connected');
1225 // In preference order
1226 encs.push(encodings.encodingCopyRect);
1227 // Only supported with full depth support
1228 if (this._fb_depth == 24) {
1229 encs.push(encodings.encodingTight);
1230 encs.push(encodings.encodingTightPNG);
1231 encs.push(encodings.encodingHextile);
1232 encs.push(encodings.encodingRRE);
1234 encs.push(encodings.encodingRaw);
1236 // Psuedo-encoding settings
1237 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1238 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1240 encs.push(encodings.pseudoEncodingDesktopSize);
1241 encs.push(encodings.pseudoEncodingLastRect);
1242 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1243 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1244 encs.push(encodings.pseudoEncodingXvp);
1245 encs.push(encodings.pseudoEncodingFence);
1246 encs.push(encodings.pseudoEncodingContinuousUpdates);
1248 if (this._fb_depth == 24) {
1249 encs.push(encodings.pseudoEncodingCursor);
1252 RFB.messages.clientEncodings(this._sock, encs);
1255 /* RFB protocol initialization states:
1260 * ClientInitialization - not triggered by server message
1261 * ServerInitialization
1264 switch (this._rfb_init_state
) {
1265 case 'ProtocolVersion':
1266 return this._negotiate_protocol_version();
1269 return this._negotiate_security();
1271 case 'Authentication':
1272 return this._negotiate_authentication();
1274 case 'SecurityResult':
1275 return this._handle_security_result();
1277 case 'SecurityReason':
1278 return this._handle_security_reason();
1280 case 'ClientInitialisation':
1281 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1282 this._rfb_init_state
= 'ServerInitialisation';
1285 case 'ServerInitialisation':
1286 return this._negotiate_server_init();
1289 return this._fail("Unknown init state (state: " +
1290 this._rfb_init_state
+ ")");
1294 _handle_set_colour_map_msg() {
1295 Log
.Debug("SetColorMapEntries");
1297 return this._fail("Unexpected SetColorMapEntries message");
1300 _handle_server_cut_text() {
1301 Log
.Debug("ServerCutText");
1303 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1304 this._sock
.rQskipBytes(3); // Padding
1305 const length
= this._sock
.rQshift32();
1306 if (this._sock
.rQwait("ServerCutText", length
, 8)) { return false; }
1308 const text
= this._sock
.rQshiftStr(length
);
1310 if (this._viewOnly
) { return true; }
1312 this.dispatchEvent(new CustomEvent(
1314 { detail
: { text
: text
} }));
1319 _handle_server_fence_msg() {
1320 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1321 this._sock
.rQskipBytes(3); // Padding
1322 let flags
= this._sock
.rQshift32();
1323 let length
= this._sock
.rQshift8();
1325 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1328 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1332 const payload
= this._sock
.rQshiftStr(length
);
1334 this._supportsFence
= true;
1339 * (1<<0) - BlockBefore
1340 * (1<<1) - BlockAfter
1345 if (!(flags
& (1<<31))) {
1346 return this._fail("Unexpected fence response");
1349 // Filter out unsupported flags
1350 // FIXME: support syncNext
1351 flags
&= (1<<0) | (1<<1);
1353 // BlockBefore and BlockAfter are automatically handled by
1354 // the fact that we process each incoming message
1356 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1362 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1363 this._sock
.rQskipBytes(1); // Padding
1364 const xvp_ver
= this._sock
.rQshift8();
1365 const xvp_msg
= this._sock
.rQshift8();
1369 Log
.Error("XVP Operation Failed");
1372 this._rfb_xvp_ver
= xvp_ver
;
1373 Log
.Info("XVP extensions enabled (version " + this._rfb_xvp_ver
+ ")");
1374 this._setCapability("power", true);
1377 this._fail("Illegal server XVP message (msg: " + xvp_msg
+ ")");
1386 if (this._FBU
.rects
> 0) {
1389 msg_type
= this._sock
.rQshift8();
1394 case 0: // FramebufferUpdate
1395 ret
= this._framebufferUpdate();
1396 if (ret
&& !this._enabledContinuousUpdates
) {
1397 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1398 this._fb_width
, this._fb_height
);
1402 case 1: // SetColorMapEntries
1403 return this._handle_set_colour_map_msg();
1407 this.dispatchEvent(new CustomEvent(
1412 case 3: // ServerCutText
1413 return this._handle_server_cut_text();
1415 case 150: // EndOfContinuousUpdates
1416 first
= !this._supportsContinuousUpdates
;
1417 this._supportsContinuousUpdates
= true;
1418 this._enabledContinuousUpdates
= false;
1420 this._enabledContinuousUpdates
= true;
1421 this._updateContinuousUpdates();
1422 Log
.Info("Enabling continuous updates.");
1424 // FIXME: We need to send a framebufferupdaterequest here
1425 // if we add support for turning off continuous updates
1429 case 248: // ServerFence
1430 return this._handle_server_fence_msg();
1433 return this._handle_xvp_msg();
1436 this._fail("Unexpected server message (type " + msg_type
+ ")");
1437 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1443 this._flushing
= false;
1444 // Resume processing
1445 if (this._sock
.rQlen
> 0) {
1446 this._handle_message();
1450 _framebufferUpdate() {
1451 if (this._FBU
.rects
=== 0) {
1452 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1453 this._sock
.rQskipBytes(1); // Padding
1454 this._FBU
.rects
= this._sock
.rQshift16();
1456 // Make sure the previous frame is fully rendered first
1457 // to avoid building up an excessive queue
1458 if (this._display
.pending()) {
1459 this._flushing
= true;
1460 this._display
.flush();
1465 while (this._FBU
.rects
> 0) {
1466 if (this._FBU
.encoding
=== null) {
1467 if (this._sock
.rQwait("rect header", 12)) { return false; }
1468 /* New FramebufferUpdate */
1470 const hdr
= this._sock
.rQshiftBytes(12);
1471 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1472 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1473 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1474 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1475 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1476 (hdr
[10] << 8) + hdr
[11], 10);
1479 if (!this._handleRect()) {
1484 this._FBU
.encoding
= null;
1487 this._display
.flip();
1489 return true; // We finished this FBU
1493 switch (this._FBU
.encoding
) {
1494 case encodings
.pseudoEncodingLastRect
:
1495 this._FBU
.rects
= 1; // Will be decreased when we return
1498 case encodings
.pseudoEncodingCursor
:
1499 return this._handleCursor();
1501 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1502 // Old Safari doesn't support creating keyboard events
1504 const keyboardEvent
= document
.createEvent("keyboardEvent");
1505 if (keyboardEvent
.code
!== undefined) {
1506 this._qemuExtKeyEventSupported
= true;
1513 case encodings
.pseudoEncodingDesktopSize
:
1514 this._resize(this._FBU
.width
, this._FBU
.height
);
1517 case encodings
.pseudoEncodingExtendedDesktopSize
:
1518 return this._handleExtendedDesktopSize();
1521 return this._handleDataRect();
1526 const hotx
= this._FBU
.x
; // hotspot-x
1527 const hoty
= this._FBU
.y
; // hotspot-y
1528 const w
= this._FBU
.width
;
1529 const h
= this._FBU
.height
;
1531 const pixelslength
= w
* h
* 4;
1532 const masklength
= Math
.ceil(w
/ 8) * h
;
1534 let bytes
= pixelslength
+ masklength
;
1535 if (this._sock
.rQwait("cursor encoding", bytes
)) {
1539 // Decode from BGRX pixels + bit mask to RGBA
1540 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
1541 const mask
= this._sock
.rQshiftBytes(masklength
);
1542 let rgba
= new Uint8Array(w
* h
* 4);
1545 for (let y
= 0; y
< h
; y
++) {
1546 for (let x
= 0; x
< w
; x
++) {
1547 let mask_idx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
1548 let alpha
= (mask
[mask_idx
] << (x
% 8)) & 0x80 ? 255 : 0;
1549 rgba
[pix_idx
] = pixels
[pix_idx
+ 2];
1550 rgba
[pix_idx
+ 1] = pixels
[pix_idx
+ 1];
1551 rgba
[pix_idx
+ 2] = pixels
[pix_idx
];
1552 rgba
[pix_idx
+ 3] = alpha
;
1557 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1562 _handleExtendedDesktopSize() {
1563 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
1567 const number_of_screens
= this._sock
.rQpeek8();
1569 let bytes
= 4 + (number_of_screens
* 16);
1570 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
1574 const firstUpdate
= !this._supportsSetDesktopSize
;
1575 this._supportsSetDesktopSize
= true;
1577 // Normally we only apply the current resize mode after a
1578 // window resize event. However there is no such trigger on the
1579 // initial connect. And we don't know if the server supports
1580 // resizing until we've gotten here.
1582 this._requestRemoteResize();
1585 this._sock
.rQskipBytes(1); // number-of-screens
1586 this._sock
.rQskipBytes(3); // padding
1588 for (let i
= 0; i
< number_of_screens
; i
+= 1) {
1589 // Save the id and flags of the first screen
1591 this._screen_id
= this._sock
.rQshiftBytes(4); // id
1592 this._sock
.rQskipBytes(2); // x-position
1593 this._sock
.rQskipBytes(2); // y-position
1594 this._sock
.rQskipBytes(2); // width
1595 this._sock
.rQskipBytes(2); // height
1596 this._screen_flags
= this._sock
.rQshiftBytes(4); // flags
1598 this._sock
.rQskipBytes(16);
1603 * The x-position indicates the reason for the change:
1605 * 0 - server resized on its own
1606 * 1 - this client requested the resize
1607 * 2 - another client requested the resize
1610 // We need to handle errors when we requested the resize.
1611 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
1613 // The y-position indicates the status code from the server
1614 switch (this._FBU
.y
) {
1616 msg
= "Resize is administratively prohibited";
1619 msg
= "Out of resources";
1622 msg
= "Invalid screen layout";
1625 msg
= "Unknown reason";
1628 Log
.Warn("Server did not accept the resize request: "
1631 this._resize(this._FBU
.width
, this._FBU
.height
);
1638 let decoder
= this._decoders
[this._FBU
.encoding
];
1640 this._fail("Unsupported encoding (encoding: " +
1641 this._FBU
.encoding
+ ")");
1646 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
1647 this._FBU
.width
, this._FBU
.height
,
1648 this._sock
, this._display
,
1651 this._fail("Error decoding rect: " + err
);
1656 _updateContinuousUpdates() {
1657 if (!this._enabledContinuousUpdates
) { return; }
1659 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
1660 this._fb_width
, this._fb_height
);
1663 _resize(width
, height
) {
1664 this._fb_width
= width
;
1665 this._fb_height
= height
;
1667 this._display
.resize(this._fb_width
, this._fb_height
);
1669 // Adjust the visible viewport based on the new dimensions
1671 this._updateScale();
1673 this._updateContinuousUpdates();
1677 if (this._rfb_xvp_ver
< ver
) { return; }
1678 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
1679 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
1682 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
1683 this._cursorImage
= {
1685 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
1687 this._refreshCursor();
1690 _shouldShowDotCursor() {
1691 // Called when this._cursorImage is updated
1692 if (!this._showDotCursor
) {
1693 // User does not want to see the dot, so...
1697 // The dot should not be shown if the cursor is already visible,
1698 // i.e. contains at least one not-fully-transparent pixel.
1699 // So iterate through all alpha bytes in rgba and stop at the
1701 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
1702 if (this._cursorImage
.rgbaPixels
[i
]) {
1707 // At this point, we know that the cursor is fully transparent, and
1708 // the user wants to see the dot instead of this.
1713 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
1714 this._cursor
.change(image
.rgbaPixels
,
1715 image
.hotx
, image
.hoty
,
1720 static genDES(password
, challenge
) {
1721 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
1722 return (new DES(passwordChars
)).encrypt(challenge
);
1728 keyEvent(sock
, keysym
, down
) {
1729 const buff
= sock
._sQ
;
1730 const offset
= sock
._sQlen
;
1732 buff
[offset
] = 4; // msg-type
1733 buff
[offset
+ 1] = down
;
1735 buff
[offset
+ 2] = 0;
1736 buff
[offset
+ 3] = 0;
1738 buff
[offset
+ 4] = (keysym
>> 24);
1739 buff
[offset
+ 5] = (keysym
>> 16);
1740 buff
[offset
+ 6] = (keysym
>> 8);
1741 buff
[offset
+ 7] = keysym
;
1747 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
1748 function getRFBkeycode(xt_scancode
) {
1749 const upperByte
= (keycode
>> 8);
1750 const lowerByte
= (keycode
& 0x00ff);
1751 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
1752 return lowerByte
| 0x80;
1757 const buff
= sock
._sQ
;
1758 const offset
= sock
._sQlen
;
1760 buff
[offset
] = 255; // msg-type
1761 buff
[offset
+ 1] = 0; // sub msg-type
1763 buff
[offset
+ 2] = (down
>> 8);
1764 buff
[offset
+ 3] = down
;
1766 buff
[offset
+ 4] = (keysym
>> 24);
1767 buff
[offset
+ 5] = (keysym
>> 16);
1768 buff
[offset
+ 6] = (keysym
>> 8);
1769 buff
[offset
+ 7] = keysym
;
1771 const RFBkeycode
= getRFBkeycode(keycode
);
1773 buff
[offset
+ 8] = (RFBkeycode
>> 24);
1774 buff
[offset
+ 9] = (RFBkeycode
>> 16);
1775 buff
[offset
+ 10] = (RFBkeycode
>> 8);
1776 buff
[offset
+ 11] = RFBkeycode
;
1782 pointerEvent(sock
, x
, y
, mask
) {
1783 const buff
= sock
._sQ
;
1784 const offset
= sock
._sQlen
;
1786 buff
[offset
] = 5; // msg-type
1788 buff
[offset
+ 1] = mask
;
1790 buff
[offset
+ 2] = x
>> 8;
1791 buff
[offset
+ 3] = x
;
1793 buff
[offset
+ 4] = y
>> 8;
1794 buff
[offset
+ 5] = y
;
1800 // TODO(directxman12): make this unicode compatible?
1801 clientCutText(sock
, text
) {
1802 const buff
= sock
._sQ
;
1803 const offset
= sock
._sQlen
;
1805 buff
[offset
] = 6; // msg-type
1807 buff
[offset
+ 1] = 0; // padding
1808 buff
[offset
+ 2] = 0; // padding
1809 buff
[offset
+ 3] = 0; // padding
1811 let length
= text
.length
;
1813 buff
[offset
+ 4] = length
>> 24;
1814 buff
[offset
+ 5] = length
>> 16;
1815 buff
[offset
+ 6] = length
>> 8;
1816 buff
[offset
+ 7] = length
;
1820 // We have to keep track of from where in the text we begin creating the
1821 // buffer for the flush in the next iteration.
1824 let remaining
= length
;
1825 while (remaining
> 0) {
1827 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
1828 for (let i
= 0; i
< flushSize
; i
++) {
1829 buff
[sock
._sQlen
+ i
] = text
.charCodeAt(textOffset
+ i
);
1832 sock
._sQlen
+= flushSize
;
1835 remaining
-= flushSize
;
1836 textOffset
+= flushSize
;
1840 setDesktopSize(sock
, width
, height
, id
, flags
) {
1841 const buff
= sock
._sQ
;
1842 const offset
= sock
._sQlen
;
1844 buff
[offset
] = 251; // msg-type
1845 buff
[offset
+ 1] = 0; // padding
1846 buff
[offset
+ 2] = width
>> 8; // width
1847 buff
[offset
+ 3] = width
;
1848 buff
[offset
+ 4] = height
>> 8; // height
1849 buff
[offset
+ 5] = height
;
1851 buff
[offset
+ 6] = 1; // number-of-screens
1852 buff
[offset
+ 7] = 0; // padding
1855 buff
[offset
+ 8] = id
>> 24; // id
1856 buff
[offset
+ 9] = id
>> 16;
1857 buff
[offset
+ 10] = id
>> 8;
1858 buff
[offset
+ 11] = id
;
1859 buff
[offset
+ 12] = 0; // x-position
1860 buff
[offset
+ 13] = 0;
1861 buff
[offset
+ 14] = 0; // y-position
1862 buff
[offset
+ 15] = 0;
1863 buff
[offset
+ 16] = width
>> 8; // width
1864 buff
[offset
+ 17] = width
;
1865 buff
[offset
+ 18] = height
>> 8; // height
1866 buff
[offset
+ 19] = height
;
1867 buff
[offset
+ 20] = flags
>> 24; // flags
1868 buff
[offset
+ 21] = flags
>> 16;
1869 buff
[offset
+ 22] = flags
>> 8;
1870 buff
[offset
+ 23] = flags
;
1876 clientFence(sock
, flags
, payload
) {
1877 const buff
= sock
._sQ
;
1878 const offset
= sock
._sQlen
;
1880 buff
[offset
] = 248; // msg-type
1882 buff
[offset
+ 1] = 0; // padding
1883 buff
[offset
+ 2] = 0; // padding
1884 buff
[offset
+ 3] = 0; // padding
1886 buff
[offset
+ 4] = flags
>> 24; // flags
1887 buff
[offset
+ 5] = flags
>> 16;
1888 buff
[offset
+ 6] = flags
>> 8;
1889 buff
[offset
+ 7] = flags
;
1891 const n
= payload
.length
;
1893 buff
[offset
+ 8] = n
; // length
1895 for (let i
= 0; i
< n
; i
++) {
1896 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
1899 sock
._sQlen
+= 9 + n
;
1903 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
1904 const buff
= sock
._sQ
;
1905 const offset
= sock
._sQlen
;
1907 buff
[offset
] = 150; // msg-type
1908 buff
[offset
+ 1] = enable
; // enable-flag
1910 buff
[offset
+ 2] = x
>> 8; // x
1911 buff
[offset
+ 3] = x
;
1912 buff
[offset
+ 4] = y
>> 8; // y
1913 buff
[offset
+ 5] = y
;
1914 buff
[offset
+ 6] = width
>> 8; // width
1915 buff
[offset
+ 7] = width
;
1916 buff
[offset
+ 8] = height
>> 8; // height
1917 buff
[offset
+ 9] = height
;
1923 pixelFormat(sock
, depth
, true_color
) {
1924 const buff
= sock
._sQ
;
1925 const offset
= sock
._sQlen
;
1931 } else if (depth
> 8) {
1937 const bits
= Math
.floor(depth
/3);
1939 buff
[offset
] = 0; // msg-type
1941 buff
[offset
+ 1] = 0; // padding
1942 buff
[offset
+ 2] = 0; // padding
1943 buff
[offset
+ 3] = 0; // padding
1945 buff
[offset
+ 4] = bpp
; // bits-per-pixel
1946 buff
[offset
+ 5] = depth
; // depth
1947 buff
[offset
+ 6] = 0; // little-endian
1948 buff
[offset
+ 7] = true_color
? 1 : 0; // true-color
1950 buff
[offset
+ 8] = 0; // red-max
1951 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
1953 buff
[offset
+ 10] = 0; // green-max
1954 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
1956 buff
[offset
+ 12] = 0; // blue-max
1957 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
1959 buff
[offset
+ 14] = bits
* 2; // red-shift
1960 buff
[offset
+ 15] = bits
* 1; // green-shift
1961 buff
[offset
+ 16] = bits
* 0; // blue-shift
1963 buff
[offset
+ 17] = 0; // padding
1964 buff
[offset
+ 18] = 0; // padding
1965 buff
[offset
+ 19] = 0; // padding
1971 clientEncodings(sock
, encodings
) {
1972 const buff
= sock
._sQ
;
1973 const offset
= sock
._sQlen
;
1975 buff
[offset
] = 2; // msg-type
1976 buff
[offset
+ 1] = 0; // padding
1978 buff
[offset
+ 2] = encodings
.length
>> 8;
1979 buff
[offset
+ 3] = encodings
.length
;
1982 for (let i
= 0; i
< encodings
.length
; i
++) {
1983 const enc
= encodings
[i
];
1984 buff
[j
] = enc
>> 24;
1985 buff
[j
+ 1] = enc
>> 16;
1986 buff
[j
+ 2] = enc
>> 8;
1992 sock
._sQlen
+= j
- offset
;
1996 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
1997 const buff
= sock
._sQ
;
1998 const offset
= sock
._sQlen
;
2000 if (typeof(x
) === "undefined") { x
= 0; }
2001 if (typeof(y
) === "undefined") { y
= 0; }
2003 buff
[offset
] = 3; // msg-type
2004 buff
[offset
+ 1] = incremental
? 1 : 0;
2006 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2007 buff
[offset
+ 3] = x
& 0xFF;
2009 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2010 buff
[offset
+ 5] = y
& 0xFF;
2012 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2013 buff
[offset
+ 7] = w
& 0xFF;
2015 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2016 buff
[offset
+ 9] = h
& 0xFF;
2022 xvpOp(sock
, ver
, op
) {
2023 const buff
= sock
._sQ
;
2024 const offset
= sock
._sQlen
;
2026 buff
[offset
] = 250; // msg-type
2027 buff
[offset
+ 1] = 0; // padding
2029 buff
[offset
+ 2] = ver
;
2030 buff
[offset
+ 3] = op
;
2039 rgbaPixels
: new Uint8Array(),
2045 /* eslint-disable indent */
2046 rgbaPixels
: new Uint8Array([
2047 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2048 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2049 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2051 /* eslint-enable indent */