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;
56 this._wsProtocols
= options
.wsProtocols
|| ['binary'];
59 this._rfb_connection_state
= '';
60 this._rfb_init_state
= '';
61 this._rfb_auth_scheme
= -1;
62 this._rfb_clean_disconnect
= true;
64 // Server capabilities
65 this._rfb_version
= 0;
66 this._rfb_max_version
= 3.8;
67 this._rfb_tightvnc
= false;
68 this._rfb_xvp_ver
= 0;
75 this._capabilities
= { power
: false };
77 this._supportsFence
= false;
79 this._supportsContinuousUpdates
= false;
80 this._enabledContinuousUpdates
= false;
82 this._supportsSetDesktopSize
= false;
84 this._screen_flags
= 0;
86 this._qemuExtKeyEventSupported
= false;
89 this._sock
= null; // Websock object
90 this._display
= null; // Display object
91 this._flushing
= false; // Display flushing state
92 this._keyboard
= null; // Keyboard input handler object
93 this._mouse
= null; // Mouse input handler object
96 this._disconnTimer
= null; // disconnection timer
97 this._resizeTimeout
= null; // resize rate limiting
112 this._mouse_buttonMask
= 0;
113 this._mouse_arr
= [];
114 this._viewportDragging
= false;
115 this._viewportDragPos
= {};
116 this._viewportHasMoved
= false;
118 // Bound event handlers
119 this._eventHandlers
= {
120 focusCanvas
: this._focusCanvas
.bind(this),
121 windowResize
: this._windowResize
.bind(this),
125 Log
.Debug(">> RFB.constructor");
127 // Create DOM elements
128 this._screen
= document
.createElement('div');
129 this._screen
.style
.display
= 'flex';
130 this._screen
.style
.width
= '100%';
131 this._screen
.style
.height
= '100%';
132 this._screen
.style
.overflow
= 'auto';
133 this._screen
.style
.background
= DEFAULT_BACKGROUND
;
134 this._canvas
= document
.createElement('canvas');
135 this._canvas
.style
.margin
= 'auto';
136 // Some browsers add an outline on focus
137 this._canvas
.style
.outline
= 'none';
138 // IE miscalculates width without this :(
139 this._canvas
.style
.flexShrink
= '0';
140 this._canvas
.width
= 0;
141 this._canvas
.height
= 0;
142 this._canvas
.tabIndex
= -1;
143 this._screen
.appendChild(this._canvas
);
146 this._cursor
= new Cursor();
148 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
149 // it. Result: no cursor at all until a window border or an edit field
150 // is hit blindly. But there are also VNC servers that draw the cursor
151 // in the framebuffer and don't send the empty local cursor. There is
152 // no way to satisfy both sides.
154 // The spec is unclear on this "initial cursor" issue. Many other
155 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
156 // initial cursor instead.
157 this._cursorImage
= RFB
.cursors
.none
;
159 // populate decoder array with objects
160 this._decoders
[encodings
.encodingRaw
] = new RawDecoder();
161 this._decoders
[encodings
.encodingCopyRect
] = new CopyRectDecoder();
162 this._decoders
[encodings
.encodingRRE
] = new RREDecoder();
163 this._decoders
[encodings
.encodingHextile
] = new HextileDecoder();
164 this._decoders
[encodings
.encodingTight
] = new TightDecoder();
165 this._decoders
[encodings
.encodingTightPNG
] = new TightPNGDecoder();
167 // NB: nothing that needs explicit teardown should be done
168 // before this point, since this can throw an exception
170 this._display
= new Display(this._canvas
);
172 Log
.Error("Display exception: " + exc
);
175 this._display
.onflush
= this._onFlush
.bind(this);
176 this._display
.clear();
178 this._keyboard
= new Keyboard(this._canvas
);
179 this._keyboard
.onkeyevent
= this._handleKeyEvent
.bind(this);
181 this._mouse
= new Mouse(this._canvas
);
182 this._mouse
.onmousebutton
= this._handleMouseButton
.bind(this);
183 this._mouse
.onmousemove
= this._handleMouseMove
.bind(this);
185 this._sock
= new Websock();
186 this._sock
.on('message', () => {
187 this._handle_message();
189 this._sock
.on('open', () => {
190 if ((this._rfb_connection_state
=== 'connecting') &&
191 (this._rfb_init_state
=== '')) {
192 this._rfb_init_state
= 'ProtocolVersion';
193 Log
.Debug("Starting VNC handshake");
195 this._fail("Unexpected server connection while " +
196 this._rfb_connection_state
);
199 this._sock
.on('close', (e
) => {
200 Log
.Debug("WebSocket on-close event");
203 msg
= "(code: " + e
.code
;
205 msg
+= ", reason: " + e
.reason
;
209 switch (this._rfb_connection_state
) {
211 this._fail("Connection closed " + msg
);
214 // Handle disconnects that were initiated server-side
215 this._updateConnectionState('disconnecting');
216 this._updateConnectionState('disconnected');
218 case 'disconnecting':
219 // Normal disconnection path
220 this._updateConnectionState('disconnected');
223 this._fail("Unexpected server disconnect " +
224 "when already disconnected " + msg
);
227 this._fail("Unexpected server disconnect before connecting " +
231 this._sock
.off('close');
233 this._sock
.on('error', e
=> Log
.Warn("WebSocket on-error event"));
235 // Slight delay of the actual connection so that the caller has
236 // time to set up callbacks
237 setTimeout(this._updateConnectionState
.bind(this, 'connecting'));
239 Log
.Debug("<< RFB.constructor");
241 // ===== PROPERTIES =====
243 this.dragViewport
= false;
244 this.focusOnClick
= true;
246 this._viewOnly
= false;
247 this._clipViewport
= false;
248 this._scaleViewport
= false;
249 this._resizeSession
= false;
252 // ===== PROPERTIES =====
254 get viewOnly() { return this._viewOnly
; }
255 set viewOnly(viewOnly
) {
256 this._viewOnly
= viewOnly
;
258 if (this._rfb_connection_state
=== "connecting" ||
259 this._rfb_connection_state
=== "connected") {
261 this._keyboard
.ungrab();
262 this._mouse
.ungrab();
264 this._keyboard
.grab();
270 get capabilities() { return this._capabilities
; }
272 get touchButton() { return this._mouse
.touchButton
; }
273 set touchButton(button
) { this._mouse
.touchButton
= button
; }
275 get clipViewport() { return this._clipViewport
; }
276 set clipViewport(viewport
) {
277 this._clipViewport
= viewport
;
281 get scaleViewport() { return this._scaleViewport
; }
282 set scaleViewport(scale
) {
283 this._scaleViewport
= scale
;
284 // Scaling trumps clipping, so we may need to adjust
285 // clipping when enabling or disabling scaling
286 if (scale
&& this._clipViewport
) {
290 if (!scale
&& this._clipViewport
) {
295 get resizeSession() { return this._resizeSession
; }
296 set resizeSession(resize
) {
297 this._resizeSession
= resize
;
299 this._requestRemoteResize();
303 get showDotCursor() { return this._showDotCursor
; }
304 set showDotCursor(show
) {
305 this._showDotCursor
= show
;
306 this._refreshCursor();
309 get background() { return this._screen
.style
.background
; }
310 set background(cssValue
) { this._screen
.style
.background
= cssValue
; }
312 // ===== PUBLIC METHODS =====
315 this._updateConnectionState('disconnecting');
316 this._sock
.off('error');
317 this._sock
.off('message');
318 this._sock
.off('open');
321 sendCredentials(creds
) {
322 this._rfb_credentials
= creds
;
323 setTimeout(this._init_msg
.bind(this), 0);
327 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
328 Log
.Info("Sending Ctrl-Alt-Del");
330 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
331 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
332 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
333 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
334 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
335 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
350 // Send a key press. If 'down' is not specified then send a down key
351 // followed by an up key.
352 sendKey(keysym
, code
, down
) {
353 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
355 if (down
=== undefined) {
356 this.sendKey(keysym
, code
, true);
357 this.sendKey(keysym
, code
, false);
361 const scancode
= XtScancode
[code
];
363 if (this._qemuExtKeyEventSupported
&& scancode
) {
365 keysym
= keysym
|| 0;
367 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
369 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
374 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
375 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
380 this._canvas
.focus();
387 clipboardPasteFrom(text
) {
388 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
389 RFB
.messages
.clientCutText(this._sock
, text
);
392 // ===== PRIVATE METHODS =====
395 Log
.Debug(">> RFB.connect");
397 Log
.Info("connecting to " + this._url
);
400 // WebSocket.onopen transitions to the RFB init states
401 this._sock
.open(this._url
, this._wsProtocols
);
403 if (e
.name
=== 'SyntaxError') {
404 this._fail("Invalid host or port (" + e
+ ")");
406 this._fail("Error when opening socket (" + e
+ ")");
410 // Make our elements part of the page
411 this._target
.appendChild(this._screen
);
413 this._cursor
.attach(this._canvas
);
414 this._refreshCursor();
416 // Monitor size changes of the screen
417 // FIXME: Use ResizeObserver, or hidden overflow
418 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
420 // Always grab focus on some kind of click event
421 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
422 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
424 Log
.Debug("<< RFB.connect");
428 Log
.Debug(">> RFB.disconnect");
429 this._cursor
.detach();
430 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
431 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
432 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
433 this._keyboard
.ungrab();
434 this._mouse
.ungrab();
437 this._target
.removeChild(this._screen
);
439 if (e
.name
=== 'NotFoundError') {
440 // Some cases where the initial connection fails
441 // can disconnect before the _screen is created
446 clearTimeout(this._resizeTimeout
);
447 Log
.Debug("<< RFB.disconnect");
450 _focusCanvas(event
) {
451 // Respect earlier handlers' request to not do side-effects
452 if (event
.defaultPrevented
) {
456 if (!this.focusOnClick
) {
463 _windowResize(event
) {
464 // If the window resized then our screen element might have
465 // as well. Update the viewport dimensions.
466 window
.requestAnimationFrame(() => {
471 if (this._resizeSession
) {
472 // Request changing the resolution of the remote display to
473 // the size of the local browser viewport.
475 // In order to not send multiple requests before the browser-resize
476 // is finished we wait 0.5 seconds before sending the request.
477 clearTimeout(this._resizeTimeout
);
478 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
482 // Update state of clipping in Display object, and make sure the
483 // configured viewport matches the current screen size
485 const cur_clip
= this._display
.clipViewport
;
486 let new_clip
= this._clipViewport
;
488 if (this._scaleViewport
) {
489 // Disable viewport clipping if we are scaling
493 if (cur_clip
!== new_clip
) {
494 this._display
.clipViewport
= new_clip
;
498 // When clipping is enabled, the screen is limited to
499 // the size of the container.
500 const size
= this._screenSize();
501 this._display
.viewportChangeSize(size
.w
, size
.h
);
502 this._fixScrollbars();
507 if (!this._scaleViewport
) {
508 this._display
.scale
= 1.0;
510 const size
= this._screenSize();
511 this._display
.autoscale(size
.w
, size
.h
);
513 this._fixScrollbars();
516 // Requests a change of remote desktop size. This message is an extension
517 // and may only be sent if we have received an ExtendedDesktopSize message
518 _requestRemoteResize() {
519 clearTimeout(this._resizeTimeout
);
520 this._resizeTimeout
= null;
522 if (!this._resizeSession
|| this._viewOnly
||
523 !this._supportsSetDesktopSize
) {
527 const size
= this._screenSize();
528 RFB
.messages
.setDesktopSize(this._sock
,
529 Math
.floor(size
.w
), Math
.floor(size
.h
),
530 this._screen_id
, this._screen_flags
);
532 Log
.Debug('Requested new desktop size: ' +
533 size
.w
+ 'x' + size
.h
);
536 // Gets the the size of the available screen
538 let r
= this._screen
.getBoundingClientRect();
539 return { w
: r
.width
, h
: r
.height
};
543 // This is a hack because Chrome screws up the calculation
544 // for when scrollbars are needed. So to fix it we temporarily
545 // toggle them off and on.
546 const orig
= this._screen
.style
.overflow
;
547 this._screen
.style
.overflow
= 'hidden';
548 // Force Chrome to recalculate the layout by asking for
549 // an element's dimensions
550 this._screen
.getBoundingClientRect();
551 this._screen
.style
.overflow
= orig
;
559 * disconnected - permanent state
561 _updateConnectionState(state
) {
562 const oldstate
= this._rfb_connection_state
;
564 if (state
=== oldstate
) {
565 Log
.Debug("Already in state '" + state
+ "', ignoring");
569 // The 'disconnected' state is permanent for each RFB object
570 if (oldstate
=== 'disconnected') {
571 Log
.Error("Tried changing state of a disconnected RFB object");
575 // Ensure proper transitions before doing anything
578 if (oldstate
!== 'connecting') {
579 Log
.Error("Bad transition to connected state, " +
580 "previous connection state: " + oldstate
);
586 if (oldstate
!== 'disconnecting') {
587 Log
.Error("Bad transition to disconnected state, " +
588 "previous connection state: " + oldstate
);
594 if (oldstate
!== '') {
595 Log
.Error("Bad transition to connecting state, " +
596 "previous connection state: " + oldstate
);
601 case 'disconnecting':
602 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
603 Log
.Error("Bad transition to disconnecting state, " +
604 "previous connection state: " + oldstate
);
610 Log
.Error("Unknown connection state: " + state
);
614 // State change actions
616 this._rfb_connection_state
= state
;
618 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
620 if (this._disconnTimer
&& state
!== 'disconnecting') {
621 Log
.Debug("Clearing disconnect timer");
622 clearTimeout(this._disconnTimer
);
623 this._disconnTimer
= null;
625 // make sure we don't get a double event
626 this._sock
.off('close');
635 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
638 case 'disconnecting':
641 this._disconnTimer
= setTimeout(() => {
642 Log
.Error("Disconnection timed out.");
643 this._updateConnectionState('disconnected');
644 }, DISCONNECT_TIMEOUT
* 1000);
648 this.dispatchEvent(new CustomEvent(
649 "disconnect", { detail
:
650 { clean
: this._rfb_clean_disconnect
} }));
655 /* Print errors and disconnect
657 * The parameter 'details' is used for information that
658 * should be logged but not sent to the user interface.
661 switch (this._rfb_connection_state
) {
662 case 'disconnecting':
663 Log
.Error("Failed when disconnecting: " + details
);
666 Log
.Error("Failed while connected: " + details
);
669 Log
.Error("Failed when connecting: " + details
);
672 Log
.Error("RFB failure: " + details
);
675 this._rfb_clean_disconnect
= false; //This is sent to the UI
677 // Transition to disconnected without waiting for socket to close
678 this._updateConnectionState('disconnecting');
679 this._updateConnectionState('disconnected');
684 _setCapability(cap
, val
) {
685 this._capabilities
[cap
] = val
;
686 this.dispatchEvent(new CustomEvent("capabilities",
687 { detail
: { capabilities
: this._capabilities
} }));
691 if (this._sock
.rQlen
=== 0) {
692 Log
.Warn("handle_message called on an empty receive queue");
696 switch (this._rfb_connection_state
) {
698 Log
.Error("Got data while disconnected");
702 if (this._flushing
) {
705 if (!this._normal_msg()) {
708 if (this._sock
.rQlen
=== 0) {
719 _handleKeyEvent(keysym
, code
, down
) {
720 this.sendKey(keysym
, code
, down
);
723 _handleMouseButton(x
, y
, down
, bmask
) {
725 this._mouse_buttonMask
|= bmask
;
727 this._mouse_buttonMask
&= ~bmask
;
730 if (this.dragViewport
) {
731 if (down
&& !this._viewportDragging
) {
732 this._viewportDragging
= true;
733 this._viewportDragPos
= {'x': x
, 'y': y
};
734 this._viewportHasMoved
= false;
736 // Skip sending mouse events
739 this._viewportDragging
= false;
741 // If we actually performed a drag then we are done
742 // here and should not send any mouse events
743 if (this._viewportHasMoved
) {
747 // Otherwise we treat this as a mouse click event.
748 // Send the button down event here, as the button up
749 // event is sent at the end of this function.
750 RFB
.messages
.pointerEvent(this._sock
,
751 this._display
.absX(x
),
752 this._display
.absY(y
),
757 if (this._viewOnly
) { return; } // View only, skip mouse events
759 if (this._rfb_connection_state
!== 'connected') { return; }
760 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
763 _handleMouseMove(x
, y
) {
764 if (this._viewportDragging
) {
765 const deltaX
= this._viewportDragPos
.x
- x
;
766 const deltaY
= this._viewportDragPos
.y
- y
;
768 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
769 Math
.abs(deltaY
) > dragThreshold
)) {
770 this._viewportHasMoved
= true;
772 this._viewportDragPos
= {'x': x
, 'y': y
};
773 this._display
.viewportChangePos(deltaX
, deltaY
);
776 // Skip sending mouse events
780 if (this._viewOnly
) { return; } // View only, skip mouse events
782 if (this._rfb_connection_state
!== 'connected') { return; }
783 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
788 _negotiate_protocol_version() {
789 if (this._sock
.rQwait("version", 12)) {
793 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
794 Log
.Info("Server ProtocolVersion: " + sversion
);
797 case "000.000": // UltraVNC repeater
801 case "003.006": // UltraVNC
802 case "003.889": // Apple Remote Desktop
803 this._rfb_version
= 3.3;
806 this._rfb_version
= 3.7;
809 case "004.000": // Intel AMT KVM
810 case "004.001": // RealVNC 4.6
811 case "005.000": // RealVNC 5.3
812 this._rfb_version
= 3.8;
815 return this._fail("Invalid server version " + sversion
);
819 let repeaterID
= "ID:" + this._repeaterID
;
820 while (repeaterID
.length
< 250) {
823 this._sock
.send_string(repeaterID
);
827 if (this._rfb_version
> this._rfb_max_version
) {
828 this._rfb_version
= this._rfb_max_version
;
831 const cversion
= "00" + parseInt(this._rfb_version
, 10) +
832 ".00" + ((this._rfb_version
* 10) % 10);
833 this._sock
.send_string("RFB " + cversion
+ "\n");
834 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
836 this._rfb_init_state
= 'Security';
839 _negotiate_security() {
840 // Polyfill since IE and PhantomJS doesn't have
841 // TypedArray.includes()
842 function includes(item
, array
) {
843 for (let i
= 0; i
< array
.length
; i
++) {
844 if (array
[i
] === item
) {
851 if (this._rfb_version
>= 3.7) {
852 // Server sends supported list, client decides
853 const num_types
= this._sock
.rQshift8();
854 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
856 if (num_types
=== 0) {
857 this._rfb_init_state
= "SecurityReason";
858 this._security_context
= "no security types";
859 this._security_status
= 1;
860 return this._init_msg();
863 const types
= this._sock
.rQshiftBytes(num_types
);
864 Log
.Debug("Server security types: " + types
);
866 // Look for each auth in preferred order
867 if (includes(1, types
)) {
868 this._rfb_auth_scheme
= 1; // None
869 } else if (includes(22, types
)) {
870 this._rfb_auth_scheme
= 22; // XVP
871 } else if (includes(16, types
)) {
872 this._rfb_auth_scheme
= 16; // Tight
873 } else if (includes(2, types
)) {
874 this._rfb_auth_scheme
= 2; // VNC Auth
876 return this._fail("Unsupported security types (types: " + types
+ ")");
879 this._sock
.send([this._rfb_auth_scheme
]);
882 if (this._sock
.rQwait("security scheme", 4)) { return false; }
883 this._rfb_auth_scheme
= this._sock
.rQshift32();
885 if (this._rfb_auth_scheme
== 0) {
886 this._rfb_init_state
= "SecurityReason";
887 this._security_context
= "authentication scheme";
888 this._security_status
= 1;
889 return this._init_msg();
893 this._rfb_init_state
= 'Authentication';
894 Log
.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme
);
896 return this._init_msg(); // jump to authentication
899 _handle_security_reason() {
900 if (this._sock
.rQwait("reason length", 4)) {
903 const strlen
= this._sock
.rQshift32();
907 if (this._sock
.rQwait("reason", strlen
, 4)) { return false; }
908 reason
= this._sock
.rQshiftStr(strlen
);
912 this.dispatchEvent(new CustomEvent(
914 { detail
: { status
: this._security_status
,
915 reason
: reason
} }));
917 return this._fail("Security negotiation failed on " +
918 this._security_context
+
919 " (reason: " + reason
+ ")");
921 this.dispatchEvent(new CustomEvent(
923 { detail
: { status
: this._security_status
} }));
925 return this._fail("Security negotiation failed on " +
926 this._security_context
);
931 _negotiate_xvp_auth() {
932 if (!this._rfb_credentials
.username
||
933 !this._rfb_credentials
.password
||
934 !this._rfb_credentials
.target
) {
935 this.dispatchEvent(new CustomEvent(
936 "credentialsrequired",
937 { detail
: { types
: ["username", "password", "target"] } }));
941 const xvp_auth_str
= String
.fromCharCode(this._rfb_credentials
.username
.length
) +
942 String
.fromCharCode(this._rfb_credentials
.target
.length
) +
943 this._rfb_credentials
.username
+
944 this._rfb_credentials
.target
;
945 this._sock
.send_string(xvp_auth_str
);
946 this._rfb_auth_scheme
= 2;
947 return this._negotiate_authentication();
950 _negotiate_std_vnc_auth() {
951 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
953 if (!this._rfb_credentials
.password
) {
954 this.dispatchEvent(new CustomEvent(
955 "credentialsrequired",
956 { detail
: { types
: ["password"] } }));
960 // TODO(directxman12): make genDES not require an Array
961 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
962 const response
= RFB
.genDES(this._rfb_credentials
.password
, challenge
);
963 this._sock
.send(response
);
964 this._rfb_init_state
= "SecurityResult";
968 _negotiate_tight_tunnels(numTunnels
) {
969 const clientSupportedTunnelTypes
= {
970 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
972 const serverSupportedTunnelTypes
= {};
973 // receive tunnel capabilities
974 for (let i
= 0; i
< numTunnels
; i
++) {
975 const cap_code
= this._sock
.rQshift32();
976 const cap_vendor
= this._sock
.rQshiftStr(4);
977 const cap_signature
= this._sock
.rQshiftStr(8);
978 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
981 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
983 // Siemens touch panels have a VNC server that supports NOTUNNEL,
984 // but forgets to advertise it. Try to detect such servers by
985 // looking for their custom tunnel type.
986 if (serverSupportedTunnelTypes
[1] &&
987 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
988 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
989 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
990 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
993 // choose the notunnel type
994 if (serverSupportedTunnelTypes
[0]) {
995 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
996 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
997 return this._fail("Client's tunnel type had the incorrect " +
998 "vendor or signature");
1000 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1001 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1002 return false; // wait until we receive the sub auth count to continue
1004 return this._fail("Server wanted tunnels, but doesn't support " +
1005 "the notunnel type");
1009 _negotiate_tight_auth() {
1010 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
1011 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1012 const numTunnels
= this._sock
.rQshift32();
1013 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1015 this._rfb_tightvnc
= true;
1017 if (numTunnels
> 0) {
1018 this._negotiate_tight_tunnels(numTunnels
);
1019 return false; // wait until we receive the sub auth to continue
1023 // second pass, do the sub-auth negotiation
1024 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1025 const subAuthCount
= this._sock
.rQshift32();
1026 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1027 this._rfb_init_state
= 'SecurityResult';
1031 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1033 const clientSupportedTypes
= {
1038 const serverSupportedTypes
= [];
1040 for (let i
= 0; i
< subAuthCount
; i
++) {
1041 this._sock
.rQshift32(); // capNum
1042 const capabilities
= this._sock
.rQshiftStr(12);
1043 serverSupportedTypes
.push(capabilities
);
1046 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1048 for (let authType
in clientSupportedTypes
) {
1049 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1050 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1051 Log
.Debug("Selected authentication type: " + authType
);
1054 case 'STDVNOAUTH__': // no auth
1055 this._rfb_init_state
= 'SecurityResult';
1057 case 'STDVVNCAUTH_': // VNC auth
1058 this._rfb_auth_scheme
= 2;
1059 return this._init_msg();
1061 return this._fail("Unsupported tiny auth scheme " +
1062 "(scheme: " + authType
+ ")");
1067 return this._fail("No supported sub-auth types!");
1070 _negotiate_authentication() {
1071 switch (this._rfb_auth_scheme
) {
1073 if (this._rfb_version
>= 3.8) {
1074 this._rfb_init_state
= 'SecurityResult';
1077 this._rfb_init_state
= 'ClientInitialisation';
1078 return this._init_msg();
1080 case 22: // XVP auth
1081 return this._negotiate_xvp_auth();
1083 case 2: // VNC authentication
1084 return this._negotiate_std_vnc_auth();
1086 case 16: // TightVNC Security Type
1087 return this._negotiate_tight_auth();
1090 return this._fail("Unsupported auth scheme (scheme: " +
1091 this._rfb_auth_scheme
+ ")");
1095 _handle_security_result() {
1096 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1098 const status
= this._sock
.rQshift32();
1100 if (status
=== 0) { // OK
1101 this._rfb_init_state
= 'ClientInitialisation';
1102 Log
.Debug('Authentication OK');
1103 return this._init_msg();
1105 if (this._rfb_version
>= 3.8) {
1106 this._rfb_init_state
= "SecurityReason";
1107 this._security_context
= "security result";
1108 this._security_status
= status
;
1109 return this._init_msg();
1111 this.dispatchEvent(new CustomEvent(
1113 { detail
: { status
: status
} }));
1115 return this._fail("Security handshake failed");
1120 _negotiate_server_init() {
1121 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1124 const width
= this._sock
.rQshift16();
1125 const height
= this._sock
.rQshift16();
1128 const bpp
= this._sock
.rQshift8();
1129 const depth
= this._sock
.rQshift8();
1130 const big_endian
= this._sock
.rQshift8();
1131 const true_color
= this._sock
.rQshift8();
1133 const red_max
= this._sock
.rQshift16();
1134 const green_max
= this._sock
.rQshift16();
1135 const blue_max
= this._sock
.rQshift16();
1136 const red_shift
= this._sock
.rQshift8();
1137 const green_shift
= this._sock
.rQshift8();
1138 const blue_shift
= this._sock
.rQshift8();
1139 this._sock
.rQskipBytes(3); // padding
1141 // NB(directxman12): we don't want to call any callbacks or print messages until
1142 // *after* we're past the point where we could backtrack
1144 /* Connection name/title */
1145 const name_length = this._sock.rQshift32();
1146 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1147 this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
1149 if (this._rfb_tightvnc) {
1150 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1151 // In TightVNC mode, ServerInit message is extended
1152 const numServerMessages = this._sock.rQshift16();
1153 const numClientMessages = this._sock.rQshift16();
1154 const numEncodings = this._sock.rQshift16();
1155 this._sock.rQskipBytes(2); // padding
1157 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1158 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1160 // we don't actually do anything with the capability information that TIGHT sends,
1161 // so we just skip the all of this.
1163 // TIGHT server message capabilities
1164 this._sock.rQskipBytes(16 * numServerMessages);
1166 // TIGHT client message capabilities
1167 this._sock.rQskipBytes(16 * numClientMessages);
1169 // TIGHT encoding capabilities
1170 this._sock.rQskipBytes(16 * numEncodings);
1173 // NB(directxman12): these are down here so that we don't run them multiple times
1175 Log.Info("Screen: " + width + "x" + height +
1176 ", bpp: " + bpp + ", depth: " + depth +
1177 ", big_endian: " + big_endian +
1178 ", true_color: " + true_color +
1179 ", red_max: " + red_max +
1180 ", green_max: " + green_max +
1181 ", blue_max: " + blue_max +
1182 ", red_shift: " + red_shift +
1183 ", green_shift: " + green_shift +
1184 ", blue_shift: " + blue_shift);
1186 // we're past the point where we could backtrack, so it's safe to call this
1187 this.dispatchEvent(new CustomEvent(
1189 { detail: { name: this._fb_name } }));
1191 this._resize(width, height);
1193 if (!this._viewOnly) { this._keyboard.grab(); }
1194 if (!this._viewOnly) { this._mouse.grab(); }
1196 this._fb_depth = 24;
1198 if (this._fb_name === "Intel(r) AMT KVM") {
1199 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1203 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1204 this._sendEncodings();
1205 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1207 this._updateConnectionState('connected');
1214 // In preference order
1215 encs.push(encodings.encodingCopyRect);
1216 // Only supported with full depth support
1217 if (this._fb_depth == 24) {
1218 encs.push(encodings.encodingTight);
1219 encs.push(encodings.encodingTightPNG);
1220 encs.push(encodings.encodingHextile);
1221 encs.push(encodings.encodingRRE);
1223 encs.push(encodings.encodingRaw);
1225 // Psuedo-encoding settings
1226 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1227 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1229 encs.push(encodings.pseudoEncodingDesktopSize);
1230 encs.push(encodings.pseudoEncodingLastRect);
1231 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1232 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1233 encs.push(encodings.pseudoEncodingXvp);
1234 encs.push(encodings.pseudoEncodingFence);
1235 encs.push(encodings.pseudoEncodingContinuousUpdates);
1237 if (this._fb_depth == 24) {
1238 encs.push(encodings.pseudoEncodingCursor);
1241 RFB.messages.clientEncodings(this._sock, encs);
1244 /* RFB protocol initialization states:
1249 * ClientInitialization - not triggered by server message
1250 * ServerInitialization
1253 switch (this._rfb_init_state
) {
1254 case 'ProtocolVersion':
1255 return this._negotiate_protocol_version();
1258 return this._negotiate_security();
1260 case 'Authentication':
1261 return this._negotiate_authentication();
1263 case 'SecurityResult':
1264 return this._handle_security_result();
1266 case 'SecurityReason':
1267 return this._handle_security_reason();
1269 case 'ClientInitialisation':
1270 this._sock
.send([this._shared
? 1 : 0]); // ClientInitialisation
1271 this._rfb_init_state
= 'ServerInitialisation';
1274 case 'ServerInitialisation':
1275 return this._negotiate_server_init();
1278 return this._fail("Unknown init state (state: " +
1279 this._rfb_init_state
+ ")");
1283 _handle_set_colour_map_msg() {
1284 Log
.Debug("SetColorMapEntries");
1286 return this._fail("Unexpected SetColorMapEntries message");
1289 _handle_server_cut_text() {
1290 Log
.Debug("ServerCutText");
1292 if (this._sock
.rQwait("ServerCutText header", 7, 1)) { return false; }
1293 this._sock
.rQskipBytes(3); // Padding
1294 const length
= this._sock
.rQshift32();
1295 if (this._sock
.rQwait("ServerCutText", length
, 8)) { return false; }
1297 const text
= this._sock
.rQshiftStr(length
);
1299 if (this._viewOnly
) { return true; }
1301 this.dispatchEvent(new CustomEvent(
1303 { detail
: { text
: text
} }));
1308 _handle_server_fence_msg() {
1309 if (this._sock
.rQwait("ServerFence header", 8, 1)) { return false; }
1310 this._sock
.rQskipBytes(3); // Padding
1311 let flags
= this._sock
.rQshift32();
1312 let length
= this._sock
.rQshift8();
1314 if (this._sock
.rQwait("ServerFence payload", length
, 9)) { return false; }
1317 Log
.Warn("Bad payload length (" + length
+ ") in fence response");
1321 const payload
= this._sock
.rQshiftStr(length
);
1323 this._supportsFence
= true;
1328 * (1<<0) - BlockBefore
1329 * (1<<1) - BlockAfter
1334 if (!(flags
& (1<<31))) {
1335 return this._fail("Unexpected fence response");
1338 // Filter out unsupported flags
1339 // FIXME: support syncNext
1340 flags
&= (1<<0) | (1<<1);
1342 // BlockBefore and BlockAfter are automatically handled by
1343 // the fact that we process each incoming message
1345 RFB
.messages
.clientFence(this._sock
, flags
, payload
);
1351 if (this._sock
.rQwait("XVP version and message", 3, 1)) { return false; }
1352 this._sock
.rQskipBytes(1); // Padding
1353 const xvp_ver
= this._sock
.rQshift8();
1354 const xvp_msg
= this._sock
.rQshift8();
1358 Log
.Error("XVP Operation Failed");
1361 this._rfb_xvp_ver
= xvp_ver
;
1362 Log
.Info("XVP extensions enabled (version " + this._rfb_xvp_ver
+ ")");
1363 this._setCapability("power", true);
1366 this._fail("Illegal server XVP message (msg: " + xvp_msg
+ ")");
1375 if (this._FBU
.rects
> 0) {
1378 msg_type
= this._sock
.rQshift8();
1383 case 0: // FramebufferUpdate
1384 ret
= this._framebufferUpdate();
1385 if (ret
&& !this._enabledContinuousUpdates
) {
1386 RFB
.messages
.fbUpdateRequest(this._sock
, true, 0, 0,
1387 this._fb_width
, this._fb_height
);
1391 case 1: // SetColorMapEntries
1392 return this._handle_set_colour_map_msg();
1396 this.dispatchEvent(new CustomEvent(
1401 case 3: // ServerCutText
1402 return this._handle_server_cut_text();
1404 case 150: // EndOfContinuousUpdates
1405 first
= !this._supportsContinuousUpdates
;
1406 this._supportsContinuousUpdates
= true;
1407 this._enabledContinuousUpdates
= false;
1409 this._enabledContinuousUpdates
= true;
1410 this._updateContinuousUpdates();
1411 Log
.Info("Enabling continuous updates.");
1413 // FIXME: We need to send a framebufferupdaterequest here
1414 // if we add support for turning off continuous updates
1418 case 248: // ServerFence
1419 return this._handle_server_fence_msg();
1422 return this._handle_xvp_msg();
1425 this._fail("Unexpected server message (type " + msg_type
+ ")");
1426 Log
.Debug("sock.rQslice(0, 30): " + this._sock
.rQslice(0, 30));
1432 this._flushing
= false;
1433 // Resume processing
1434 if (this._sock
.rQlen
> 0) {
1435 this._handle_message();
1439 _framebufferUpdate() {
1440 if (this._FBU
.rects
=== 0) {
1441 if (this._sock
.rQwait("FBU header", 3, 1)) { return false; }
1442 this._sock
.rQskipBytes(1); // Padding
1443 this._FBU
.rects
= this._sock
.rQshift16();
1445 // Make sure the previous frame is fully rendered first
1446 // to avoid building up an excessive queue
1447 if (this._display
.pending()) {
1448 this._flushing
= true;
1449 this._display
.flush();
1454 while (this._FBU
.rects
> 0) {
1455 if (this._FBU
.encoding
=== null) {
1456 if (this._sock
.rQwait("rect header", 12)) { return false; }
1457 /* New FramebufferUpdate */
1459 const hdr
= this._sock
.rQshiftBytes(12);
1460 this._FBU
.x
= (hdr
[0] << 8) + hdr
[1];
1461 this._FBU
.y
= (hdr
[2] << 8) + hdr
[3];
1462 this._FBU
.width
= (hdr
[4] << 8) + hdr
[5];
1463 this._FBU
.height
= (hdr
[6] << 8) + hdr
[7];
1464 this._FBU
.encoding
= parseInt((hdr
[8] << 24) + (hdr
[9] << 16) +
1465 (hdr
[10] << 8) + hdr
[11], 10);
1468 if (!this._handleRect()) {
1473 this._FBU
.encoding
= null;
1476 this._display
.flip();
1478 return true; // We finished this FBU
1482 switch (this._FBU
.encoding
) {
1483 case encodings
.pseudoEncodingLastRect
:
1484 this._FBU
.rects
= 1; // Will be decreased when we return
1487 case encodings
.pseudoEncodingCursor
:
1488 return this._handleCursor();
1490 case encodings
.pseudoEncodingQEMUExtendedKeyEvent
:
1491 // Old Safari doesn't support creating keyboard events
1493 const keyboardEvent
= document
.createEvent("keyboardEvent");
1494 if (keyboardEvent
.code
!== undefined) {
1495 this._qemuExtKeyEventSupported
= true;
1502 case encodings
.pseudoEncodingDesktopSize
:
1503 this._resize(this._FBU
.width
, this._FBU
.height
);
1506 case encodings
.pseudoEncodingExtendedDesktopSize
:
1507 return this._handleExtendedDesktopSize();
1510 return this._handleDataRect();
1515 const hotx
= this._FBU
.x
; // hotspot-x
1516 const hoty
= this._FBU
.y
; // hotspot-y
1517 const w
= this._FBU
.width
;
1518 const h
= this._FBU
.height
;
1520 const pixelslength
= w
* h
* 4;
1521 const masklength
= Math
.ceil(w
/ 8) * h
;
1523 let bytes
= pixelslength
+ masklength
;
1524 if (this._sock
.rQwait("cursor encoding", bytes
)) {
1528 // Decode from BGRX pixels + bit mask to RGBA
1529 const pixels
= this._sock
.rQshiftBytes(pixelslength
);
1530 const mask
= this._sock
.rQshiftBytes(masklength
);
1531 let rgba
= new Uint8Array(w
* h
* 4);
1534 for (let y
= 0; y
< h
; y
++) {
1535 for (let x
= 0; x
< w
; x
++) {
1536 let mask_idx
= y
* Math
.ceil(w
/ 8) + Math
.floor(x
/ 8);
1537 let alpha
= (mask
[mask_idx
] << (x
% 8)) & 0x80 ? 255 : 0;
1538 rgba
[pix_idx
] = pixels
[pix_idx
+ 2];
1539 rgba
[pix_idx
+ 1] = pixels
[pix_idx
+ 1];
1540 rgba
[pix_idx
+ 2] = pixels
[pix_idx
];
1541 rgba
[pix_idx
+ 3] = alpha
;
1546 this._updateCursor(rgba
, hotx
, hoty
, w
, h
);
1551 _handleExtendedDesktopSize() {
1552 if (this._sock
.rQwait("ExtendedDesktopSize", 4)) {
1556 const number_of_screens
= this._sock
.rQpeek8();
1558 let bytes
= 4 + (number_of_screens
* 16);
1559 if (this._sock
.rQwait("ExtendedDesktopSize", bytes
)) {
1563 const firstUpdate
= !this._supportsSetDesktopSize
;
1564 this._supportsSetDesktopSize
= true;
1566 // Normally we only apply the current resize mode after a
1567 // window resize event. However there is no such trigger on the
1568 // initial connect. And we don't know if the server supports
1569 // resizing until we've gotten here.
1571 this._requestRemoteResize();
1574 this._sock
.rQskipBytes(1); // number-of-screens
1575 this._sock
.rQskipBytes(3); // padding
1577 for (let i
= 0; i
< number_of_screens
; i
+= 1) {
1578 // Save the id and flags of the first screen
1580 this._screen_id
= this._sock
.rQshiftBytes(4); // id
1581 this._sock
.rQskipBytes(2); // x-position
1582 this._sock
.rQskipBytes(2); // y-position
1583 this._sock
.rQskipBytes(2); // width
1584 this._sock
.rQskipBytes(2); // height
1585 this._screen_flags
= this._sock
.rQshiftBytes(4); // flags
1587 this._sock
.rQskipBytes(16);
1592 * The x-position indicates the reason for the change:
1594 * 0 - server resized on its own
1595 * 1 - this client requested the resize
1596 * 2 - another client requested the resize
1599 // We need to handle errors when we requested the resize.
1600 if (this._FBU
.x
=== 1 && this._FBU
.y
!== 0) {
1602 // The y-position indicates the status code from the server
1603 switch (this._FBU
.y
) {
1605 msg
= "Resize is administratively prohibited";
1608 msg
= "Out of resources";
1611 msg
= "Invalid screen layout";
1614 msg
= "Unknown reason";
1617 Log
.Warn("Server did not accept the resize request: "
1620 this._resize(this._FBU
.width
, this._FBU
.height
);
1627 let decoder
= this._decoders
[this._FBU
.encoding
];
1629 this._fail("Unsupported encoding (encoding: " +
1630 this._FBU
.encoding
+ ")");
1635 return decoder
.decodeRect(this._FBU
.x
, this._FBU
.y
,
1636 this._FBU
.width
, this._FBU
.height
,
1637 this._sock
, this._display
,
1640 this._fail("Error decoding rect: " + err
);
1645 _updateContinuousUpdates() {
1646 if (!this._enabledContinuousUpdates
) { return; }
1648 RFB
.messages
.enableContinuousUpdates(this._sock
, true, 0, 0,
1649 this._fb_width
, this._fb_height
);
1652 _resize(width
, height
) {
1653 this._fb_width
= width
;
1654 this._fb_height
= height
;
1656 this._display
.resize(this._fb_width
, this._fb_height
);
1658 // Adjust the visible viewport based on the new dimensions
1660 this._updateScale();
1662 this._updateContinuousUpdates();
1666 if (this._rfb_xvp_ver
< ver
) { return; }
1667 Log
.Info("Sending XVP operation " + op
+ " (version " + ver
+ ")");
1668 RFB
.messages
.xvpOp(this._sock
, ver
, op
);
1671 _updateCursor(rgba
, hotx
, hoty
, w
, h
) {
1672 this._cursorImage
= {
1674 hotx
: hotx
, hoty
: hoty
, w
: w
, h
: h
,
1676 this._refreshCursor();
1679 _shouldShowDotCursor() {
1680 // Called when this._cursorImage is updated
1681 if (!this._showDotCursor
) {
1682 // User does not want to see the dot, so...
1686 // The dot should not be shown if the cursor is already visible,
1687 // i.e. contains at least one not-fully-transparent pixel.
1688 // So iterate through all alpha bytes in rgba and stop at the
1690 for (let i
= 3; i
< this._cursorImage
.rgbaPixels
.length
; i
+= 4) {
1691 if (this._cursorImage
.rgbaPixels
[i
]) {
1696 // At this point, we know that the cursor is fully transparent, and
1697 // the user wants to see the dot instead of this.
1702 const image
= this._shouldShowDotCursor() ? RFB
.cursors
.dot
: this._cursorImage
;
1703 this._cursor
.change(image
.rgbaPixels
,
1704 image
.hotx
, image
.hoty
,
1709 static genDES(password
, challenge
) {
1710 const passwordChars
= password
.split('').map(c
=> c
.charCodeAt(0));
1711 return (new DES(passwordChars
)).encrypt(challenge
);
1717 keyEvent(sock
, keysym
, down
) {
1718 const buff
= sock
._sQ
;
1719 const offset
= sock
._sQlen
;
1721 buff
[offset
] = 4; // msg-type
1722 buff
[offset
+ 1] = down
;
1724 buff
[offset
+ 2] = 0;
1725 buff
[offset
+ 3] = 0;
1727 buff
[offset
+ 4] = (keysym
>> 24);
1728 buff
[offset
+ 5] = (keysym
>> 16);
1729 buff
[offset
+ 6] = (keysym
>> 8);
1730 buff
[offset
+ 7] = keysym
;
1736 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
1737 function getRFBkeycode(xt_scancode
) {
1738 const upperByte
= (keycode
>> 8);
1739 const lowerByte
= (keycode
& 0x00ff);
1740 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
1741 return lowerByte
| 0x80;
1746 const buff
= sock
._sQ
;
1747 const offset
= sock
._sQlen
;
1749 buff
[offset
] = 255; // msg-type
1750 buff
[offset
+ 1] = 0; // sub msg-type
1752 buff
[offset
+ 2] = (down
>> 8);
1753 buff
[offset
+ 3] = down
;
1755 buff
[offset
+ 4] = (keysym
>> 24);
1756 buff
[offset
+ 5] = (keysym
>> 16);
1757 buff
[offset
+ 6] = (keysym
>> 8);
1758 buff
[offset
+ 7] = keysym
;
1760 const RFBkeycode
= getRFBkeycode(keycode
);
1762 buff
[offset
+ 8] = (RFBkeycode
>> 24);
1763 buff
[offset
+ 9] = (RFBkeycode
>> 16);
1764 buff
[offset
+ 10] = (RFBkeycode
>> 8);
1765 buff
[offset
+ 11] = RFBkeycode
;
1771 pointerEvent(sock
, x
, y
, mask
) {
1772 const buff
= sock
._sQ
;
1773 const offset
= sock
._sQlen
;
1775 buff
[offset
] = 5; // msg-type
1777 buff
[offset
+ 1] = mask
;
1779 buff
[offset
+ 2] = x
>> 8;
1780 buff
[offset
+ 3] = x
;
1782 buff
[offset
+ 4] = y
>> 8;
1783 buff
[offset
+ 5] = y
;
1789 // TODO(directxman12): make this unicode compatible?
1790 clientCutText(sock
, text
) {
1791 const buff
= sock
._sQ
;
1792 const offset
= sock
._sQlen
;
1794 buff
[offset
] = 6; // msg-type
1796 buff
[offset
+ 1] = 0; // padding
1797 buff
[offset
+ 2] = 0; // padding
1798 buff
[offset
+ 3] = 0; // padding
1800 let length
= text
.length
;
1802 buff
[offset
+ 4] = length
>> 24;
1803 buff
[offset
+ 5] = length
>> 16;
1804 buff
[offset
+ 6] = length
>> 8;
1805 buff
[offset
+ 7] = length
;
1809 // We have to keep track of from where in the text we begin creating the
1810 // buffer for the flush in the next iteration.
1813 let remaining
= length
;
1814 while (remaining
> 0) {
1816 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
1817 for (let i
= 0; i
< flushSize
; i
++) {
1818 buff
[sock
._sQlen
+ i
] = text
.charCodeAt(textOffset
+ i
);
1821 sock
._sQlen
+= flushSize
;
1824 remaining
-= flushSize
;
1825 textOffset
+= flushSize
;
1829 setDesktopSize(sock
, width
, height
, id
, flags
) {
1830 const buff
= sock
._sQ
;
1831 const offset
= sock
._sQlen
;
1833 buff
[offset
] = 251; // msg-type
1834 buff
[offset
+ 1] = 0; // padding
1835 buff
[offset
+ 2] = width
>> 8; // width
1836 buff
[offset
+ 3] = width
;
1837 buff
[offset
+ 4] = height
>> 8; // height
1838 buff
[offset
+ 5] = height
;
1840 buff
[offset
+ 6] = 1; // number-of-screens
1841 buff
[offset
+ 7] = 0; // padding
1844 buff
[offset
+ 8] = id
>> 24; // id
1845 buff
[offset
+ 9] = id
>> 16;
1846 buff
[offset
+ 10] = id
>> 8;
1847 buff
[offset
+ 11] = id
;
1848 buff
[offset
+ 12] = 0; // x-position
1849 buff
[offset
+ 13] = 0;
1850 buff
[offset
+ 14] = 0; // y-position
1851 buff
[offset
+ 15] = 0;
1852 buff
[offset
+ 16] = width
>> 8; // width
1853 buff
[offset
+ 17] = width
;
1854 buff
[offset
+ 18] = height
>> 8; // height
1855 buff
[offset
+ 19] = height
;
1856 buff
[offset
+ 20] = flags
>> 24; // flags
1857 buff
[offset
+ 21] = flags
>> 16;
1858 buff
[offset
+ 22] = flags
>> 8;
1859 buff
[offset
+ 23] = flags
;
1865 clientFence(sock
, flags
, payload
) {
1866 const buff
= sock
._sQ
;
1867 const offset
= sock
._sQlen
;
1869 buff
[offset
] = 248; // msg-type
1871 buff
[offset
+ 1] = 0; // padding
1872 buff
[offset
+ 2] = 0; // padding
1873 buff
[offset
+ 3] = 0; // padding
1875 buff
[offset
+ 4] = flags
>> 24; // flags
1876 buff
[offset
+ 5] = flags
>> 16;
1877 buff
[offset
+ 6] = flags
>> 8;
1878 buff
[offset
+ 7] = flags
;
1880 const n
= payload
.length
;
1882 buff
[offset
+ 8] = n
; // length
1884 for (let i
= 0; i
< n
; i
++) {
1885 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
1888 sock
._sQlen
+= 9 + n
;
1892 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
1893 const buff
= sock
._sQ
;
1894 const offset
= sock
._sQlen
;
1896 buff
[offset
] = 150; // msg-type
1897 buff
[offset
+ 1] = enable
; // enable-flag
1899 buff
[offset
+ 2] = x
>> 8; // x
1900 buff
[offset
+ 3] = x
;
1901 buff
[offset
+ 4] = y
>> 8; // y
1902 buff
[offset
+ 5] = y
;
1903 buff
[offset
+ 6] = width
>> 8; // width
1904 buff
[offset
+ 7] = width
;
1905 buff
[offset
+ 8] = height
>> 8; // height
1906 buff
[offset
+ 9] = height
;
1912 pixelFormat(sock
, depth
, true_color
) {
1913 const buff
= sock
._sQ
;
1914 const offset
= sock
._sQlen
;
1920 } else if (depth
> 8) {
1926 const bits
= Math
.floor(depth
/3);
1928 buff
[offset
] = 0; // msg-type
1930 buff
[offset
+ 1] = 0; // padding
1931 buff
[offset
+ 2] = 0; // padding
1932 buff
[offset
+ 3] = 0; // padding
1934 buff
[offset
+ 4] = bpp
; // bits-per-pixel
1935 buff
[offset
+ 5] = depth
; // depth
1936 buff
[offset
+ 6] = 0; // little-endian
1937 buff
[offset
+ 7] = true_color
? 1 : 0; // true-color
1939 buff
[offset
+ 8] = 0; // red-max
1940 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
1942 buff
[offset
+ 10] = 0; // green-max
1943 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
1945 buff
[offset
+ 12] = 0; // blue-max
1946 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
1948 buff
[offset
+ 14] = bits
* 2; // red-shift
1949 buff
[offset
+ 15] = bits
* 1; // green-shift
1950 buff
[offset
+ 16] = bits
* 0; // blue-shift
1952 buff
[offset
+ 17] = 0; // padding
1953 buff
[offset
+ 18] = 0; // padding
1954 buff
[offset
+ 19] = 0; // padding
1960 clientEncodings(sock
, encodings
) {
1961 const buff
= sock
._sQ
;
1962 const offset
= sock
._sQlen
;
1964 buff
[offset
] = 2; // msg-type
1965 buff
[offset
+ 1] = 0; // padding
1967 buff
[offset
+ 2] = encodings
.length
>> 8;
1968 buff
[offset
+ 3] = encodings
.length
;
1971 for (let i
= 0; i
< encodings
.length
; i
++) {
1972 const enc
= encodings
[i
];
1973 buff
[j
] = enc
>> 24;
1974 buff
[j
+ 1] = enc
>> 16;
1975 buff
[j
+ 2] = enc
>> 8;
1981 sock
._sQlen
+= j
- offset
;
1985 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
1986 const buff
= sock
._sQ
;
1987 const offset
= sock
._sQlen
;
1989 if (typeof(x
) === "undefined") { x
= 0; }
1990 if (typeof(y
) === "undefined") { y
= 0; }
1992 buff
[offset
] = 3; // msg-type
1993 buff
[offset
+ 1] = incremental
? 1 : 0;
1995 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
1996 buff
[offset
+ 3] = x
& 0xFF;
1998 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
1999 buff
[offset
+ 5] = y
& 0xFF;
2001 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2002 buff
[offset
+ 7] = w
& 0xFF;
2004 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2005 buff
[offset
+ 9] = h
& 0xFF;
2011 xvpOp(sock
, ver
, op
) {
2012 const buff
= sock
._sQ
;
2013 const offset
= sock
._sQlen
;
2015 buff
[offset
] = 250; // msg-type
2016 buff
[offset
+ 1] = 0; // padding
2018 buff
[offset
+ 2] = ver
;
2019 buff
[offset
+ 3] = op
;
2028 rgbaPixels
: new Uint8Array(),
2034 /* eslint-disable indent */
2035 rgbaPixels
: new Uint8Array([
2036 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2037 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2038 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2040 /* eslint-enable indent */