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;
35 export default class RFB
extends EventTargetMixin
{
36 constructor(target
, url
, options
) {
38 throw new Error("Must specify target");
41 throw new Error("Must specify URL");
46 this._target
= target
;
50 options
= options
|| {};
51 this._rfb_credentials
= options
.credentials
|| {};
52 this._shared
= 'shared' in options
? !!options
.shared
: true;
53 this._repeaterID
= options
.repeaterID
|| '';
54 this._showDotCursor
= options
.showDotCursor
|| false;
57 this._rfb_connection_state
= '';
58 this._rfb_init_state
= '';
59 this._rfb_auth_scheme
= '';
60 this._rfb_clean_disconnect
= true;
62 // Server capabilities
63 this._rfb_version
= 0;
64 this._rfb_max_version
= 3.8;
65 this._rfb_tightvnc
= false;
66 this._rfb_xvp_ver
= 0;
73 this._capabilities
= { power
: false };
75 this._supportsFence
= false;
77 this._supportsContinuousUpdates
= false;
78 this._enabledContinuousUpdates
= false;
80 this._supportsSetDesktopSize
= false;
82 this._screen_flags
= 0;
84 this._qemuExtKeyEventSupported
= false;
87 this._sock
= null; // Websock object
88 this._display
= null; // Display object
89 this._flushing
= false; // Display flushing state
90 this._keyboard
= null; // Keyboard input handler object
91 this._mouse
= null; // Mouse input handler object
94 this._disconnTimer
= null; // disconnection timer
95 this._resizeTimeout
= null; // resize rate limiting
110 this._mouse_buttonMask
= 0;
111 this._mouse_arr
= [];
112 this._viewportDragging
= false;
113 this._viewportDragPos
= {};
114 this._viewportHasMoved
= false;
116 // Bound event handlers
117 this._eventHandlers
= {
118 focusCanvas
: this._focusCanvas
.bind(this),
119 windowResize
: this._windowResize
.bind(this),
123 Log
.Debug(">> RFB.constructor");
125 // Create DOM elements
126 this._screen
= document
.createElement('div');
127 this._screen
.style
.display
= 'flex';
128 this._screen
.style
.width
= '100%';
129 this._screen
.style
.height
= '100%';
130 this._screen
.style
.overflow
= 'auto';
131 this._screen
.style
.backgroundColor
= 'rgb(40, 40, 40)';
132 this._canvas
= document
.createElement('canvas');
133 this._canvas
.style
.margin
= 'auto';
134 // Some browsers add an outline on focus
135 this._canvas
.style
.outline
= 'none';
136 // IE miscalculates width without this :(
137 this._canvas
.style
.flexShrink
= '0';
138 this._canvas
.width
= 0;
139 this._canvas
.height
= 0;
140 this._canvas
.tabIndex
= -1;
141 this._screen
.appendChild(this._canvas
);
144 this._cursor
= new Cursor();
146 // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
147 // it. Result: no cursor at all until a window border or an edit field
148 // is hit blindly. But there are also VNC servers that draw the cursor
149 // in the framebuffer and don't send the empty local cursor. There is
150 // no way to satisfy both sides.
152 // The spec is unclear on this "initial cursor" issue. Many other
153 // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
154 // initial cursor instead.
155 this._cursorImage
= RFB
.cursors
.none
;
157 // populate decoder array with objects
158 this._decoders
[encodings
.encodingRaw
] = new RawDecoder();
159 this._decoders
[encodings
.encodingCopyRect
] = new CopyRectDecoder();
160 this._decoders
[encodings
.encodingRRE
] = new RREDecoder();
161 this._decoders
[encodings
.encodingHextile
] = new HextileDecoder();
162 this._decoders
[encodings
.encodingTight
] = new TightDecoder();
163 this._decoders
[encodings
.encodingTightPNG
] = new TightPNGDecoder();
165 // NB: nothing that needs explicit teardown should be done
166 // before this point, since this can throw an exception
168 this._display
= new Display(this._canvas
);
170 Log
.Error("Display exception: " + exc
);
173 this._display
.onflush
= this._onFlush
.bind(this);
174 this._display
.clear();
176 this._keyboard
= new Keyboard(this._canvas
);
177 this._keyboard
.onkeyevent
= this._handleKeyEvent
.bind(this);
179 this._mouse
= new Mouse(this._canvas
);
180 this._mouse
.onmousebutton
= this._handleMouseButton
.bind(this);
181 this._mouse
.onmousemove
= this._handleMouseMove
.bind(this);
183 this._sock
= new Websock();
184 this._sock
.on('message', this._handle_message
.bind(this));
185 this._sock
.on('open', () => {
186 if ((this._rfb_connection_state
=== 'connecting') &&
187 (this._rfb_init_state
=== '')) {
188 this._rfb_init_state
= 'ProtocolVersion';
189 Log
.Debug("Starting VNC handshake");
191 this._fail("Unexpected server connection while " +
192 this._rfb_connection_state
);
195 this._sock
.on('close', (e
) => {
196 Log
.Debug("WebSocket on-close event");
199 msg
= "(code: " + e
.code
;
201 msg
+= ", reason: " + e
.reason
;
205 switch (this._rfb_connection_state
) {
207 this._fail("Connection closed " + msg
);
210 // Handle disconnects that were initiated server-side
211 this._updateConnectionState('disconnecting');
212 this._updateConnectionState('disconnected');
214 case 'disconnecting':
215 // Normal disconnection path
216 this._updateConnectionState('disconnected');
219 this._fail("Unexpected server disconnect " +
220 "when already disconnected " + msg
);
223 this._fail("Unexpected server disconnect before connecting " +
227 this._sock
.off('close');
229 this._sock
.on('error', e
=> Log
.Warn("WebSocket on-error event"));
231 // Slight delay of the actual connection so that the caller has
232 // time to set up callbacks
233 setTimeout(this._updateConnectionState
.bind(this, 'connecting'));
235 Log
.Debug("<< RFB.constructor");
237 // ===== PROPERTIES =====
239 this.dragViewport
= false;
240 this.focusOnClick
= true;
242 this._viewOnly
= false;
243 this._clipViewport
= false;
244 this._scaleViewport
= false;
245 this._resizeSession
= false;
248 // ===== PROPERTIES =====
250 get viewOnly() { return this._viewOnly
; }
251 set viewOnly(viewOnly
) {
252 this._viewOnly
= viewOnly
;
254 if (this._rfb_connection_state
=== "connecting" ||
255 this._rfb_connection_state
=== "connected") {
257 this._keyboard
.ungrab();
258 this._mouse
.ungrab();
260 this._keyboard
.grab();
266 get capabilities() { return this._capabilities
; }
268 get touchButton() { return this._mouse
.touchButton
; }
269 set touchButton(button
) { this._mouse
.touchButton
= button
; }
271 get clipViewport() { return this._clipViewport
; }
272 set clipViewport(viewport
) {
273 this._clipViewport
= viewport
;
277 get scaleViewport() { return this._scaleViewport
; }
278 set scaleViewport(scale
) {
279 this._scaleViewport
= scale
;
280 // Scaling trumps clipping, so we may need to adjust
281 // clipping when enabling or disabling scaling
282 if (scale
&& this._clipViewport
) {
286 if (!scale
&& this._clipViewport
) {
291 get resizeSession() { return this._resizeSession
; }
292 set resizeSession(resize
) {
293 this._resizeSession
= resize
;
295 this._requestRemoteResize();
299 get showDotCursor() { return this._showDotCursor
; }
300 set showDotCursor(show
) {
301 this._showDotCursor
= show
;
302 this._refreshCursor();
305 // ===== PUBLIC METHODS =====
308 this._updateConnectionState('disconnecting');
309 this._sock
.off('error');
310 this._sock
.off('message');
311 this._sock
.off('open');
314 sendCredentials(creds
) {
315 this._rfb_credentials
= creds
;
316 setTimeout(this._init_msg
.bind(this), 0);
320 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
321 Log
.Info("Sending Ctrl-Alt-Del");
323 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", true);
324 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", true);
325 this.sendKey(KeyTable
.XK_Delete
, "Delete", true);
326 this.sendKey(KeyTable
.XK_Delete
, "Delete", false);
327 this.sendKey(KeyTable
.XK_Alt_L
, "AltLeft", false);
328 this.sendKey(KeyTable
.XK_Control_L
, "ControlLeft", false);
343 // Send a key press. If 'down' is not specified then send a down key
344 // followed by an up key.
345 sendKey(keysym
, code
, down
) {
346 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
348 if (down
=== undefined) {
349 this.sendKey(keysym
, code
, true);
350 this.sendKey(keysym
, code
, false);
354 const scancode
= XtScancode
[code
];
356 if (this._qemuExtKeyEventSupported
&& scancode
) {
358 keysym
= keysym
|| 0;
360 Log
.Info("Sending key (" + (down
? "down" : "up") + "): keysym " + keysym
+ ", scancode " + scancode
);
362 RFB
.messages
.QEMUExtendedKeyEvent(this._sock
, keysym
, down
, scancode
);
367 Log
.Info("Sending keysym (" + (down
? "down" : "up") + "): " + keysym
);
368 RFB
.messages
.keyEvent(this._sock
, keysym
, down
? 1 : 0);
373 this._canvas
.focus();
380 clipboardPasteFrom(text
) {
381 if (this._rfb_connection_state
!== 'connected' || this._viewOnly
) { return; }
382 RFB
.messages
.clientCutText(this._sock
, text
);
385 // ===== PRIVATE METHODS =====
388 Log
.Debug(">> RFB.connect");
390 Log
.Info("connecting to " + this._url
);
393 // WebSocket.onopen transitions to the RFB init states
394 this._sock
.open(this._url
, ['binary']);
396 if (e
.name
=== 'SyntaxError') {
397 this._fail("Invalid host or port (" + e
+ ")");
399 this._fail("Error when opening socket (" + e
+ ")");
403 // Make our elements part of the page
404 this._target
.appendChild(this._screen
);
406 this._cursor
.attach(this._canvas
);
407 this._refreshCursor();
409 // Monitor size changes of the screen
410 // FIXME: Use ResizeObserver, or hidden overflow
411 window
.addEventListener('resize', this._eventHandlers
.windowResize
);
413 // Always grab focus on some kind of click event
414 this._canvas
.addEventListener("mousedown", this._eventHandlers
.focusCanvas
);
415 this._canvas
.addEventListener("touchstart", this._eventHandlers
.focusCanvas
);
417 Log
.Debug("<< RFB.connect");
421 Log
.Debug(">> RFB.disconnect");
422 this._cursor
.detach();
423 this._canvas
.removeEventListener("mousedown", this._eventHandlers
.focusCanvas
);
424 this._canvas
.removeEventListener("touchstart", this._eventHandlers
.focusCanvas
);
425 window
.removeEventListener('resize', this._eventHandlers
.windowResize
);
426 this._keyboard
.ungrab();
427 this._mouse
.ungrab();
430 this._target
.removeChild(this._screen
);
432 if (e
.name
=== 'NotFoundError') {
433 // Some cases where the initial connection fails
434 // can disconnect before the _screen is created
439 clearTimeout(this._resizeTimeout
);
440 Log
.Debug("<< RFB.disconnect");
443 _focusCanvas(event
) {
444 // Respect earlier handlers' request to not do side-effects
445 if (event
.defaultPrevented
) {
449 if (!this.focusOnClick
) {
456 _windowResize(event
) {
457 // If the window resized then our screen element might have
458 // as well. Update the viewport dimensions.
459 window
.requestAnimationFrame(() => {
464 if (this._resizeSession
) {
465 // Request changing the resolution of the remote display to
466 // the size of the local browser viewport.
468 // In order to not send multiple requests before the browser-resize
469 // is finished we wait 0.5 seconds before sending the request.
470 clearTimeout(this._resizeTimeout
);
471 this._resizeTimeout
= setTimeout(this._requestRemoteResize
.bind(this), 500);
475 // Update state of clipping in Display object, and make sure the
476 // configured viewport matches the current screen size
478 const cur_clip
= this._display
.clipViewport
;
479 let new_clip
= this._clipViewport
;
481 if (this._scaleViewport
) {
482 // Disable viewport clipping if we are scaling
486 if (cur_clip
!== new_clip
) {
487 this._display
.clipViewport
= new_clip
;
491 // When clipping is enabled, the screen is limited to
492 // the size of the container.
493 const size
= this._screenSize();
494 this._display
.viewportChangeSize(size
.w
, size
.h
);
495 this._fixScrollbars();
500 if (!this._scaleViewport
) {
501 this._display
.scale
= 1.0;
503 const size
= this._screenSize();
504 this._display
.autoscale(size
.w
, size
.h
);
506 this._fixScrollbars();
509 // Requests a change of remote desktop size. This message is an extension
510 // and may only be sent if we have received an ExtendedDesktopSize message
511 _requestRemoteResize() {
512 clearTimeout(this._resizeTimeout
);
513 this._resizeTimeout
= null;
515 if (!this._resizeSession
|| this._viewOnly
||
516 !this._supportsSetDesktopSize
) {
520 const size
= this._screenSize();
521 RFB
.messages
.setDesktopSize(this._sock
,
522 Math
.floor(size
.w
), Math
.floor(size
.h
),
523 this._screen_id
, this._screen_flags
);
525 Log
.Debug('Requested new desktop size: ' +
526 size
.w
+ 'x' + size
.h
);
529 // Gets the the size of the available screen
531 let r
= this._screen
.getBoundingClientRect();
532 return { w
: r
.width
, h
: r
.height
};
536 // This is a hack because Chrome screws up the calculation
537 // for when scrollbars are needed. So to fix it we temporarily
538 // toggle them off and on.
539 const orig
= this._screen
.style
.overflow
;
540 this._screen
.style
.overflow
= 'hidden';
541 // Force Chrome to recalculate the layout by asking for
542 // an element's dimensions
543 this._screen
.getBoundingClientRect();
544 this._screen
.style
.overflow
= orig
;
552 * disconnected - permanent state
554 _updateConnectionState(state
) {
555 const oldstate
= this._rfb_connection_state
;
557 if (state
=== oldstate
) {
558 Log
.Debug("Already in state '" + state
+ "', ignoring");
562 // The 'disconnected' state is permanent for each RFB object
563 if (oldstate
=== 'disconnected') {
564 Log
.Error("Tried changing state of a disconnected RFB object");
568 // Ensure proper transitions before doing anything
571 if (oldstate
!== 'connecting') {
572 Log
.Error("Bad transition to connected state, " +
573 "previous connection state: " + oldstate
);
579 if (oldstate
!== 'disconnecting') {
580 Log
.Error("Bad transition to disconnected state, " +
581 "previous connection state: " + oldstate
);
587 if (oldstate
!== '') {
588 Log
.Error("Bad transition to connecting state, " +
589 "previous connection state: " + oldstate
);
594 case 'disconnecting':
595 if (oldstate
!== 'connected' && oldstate
!== 'connecting') {
596 Log
.Error("Bad transition to disconnecting state, " +
597 "previous connection state: " + oldstate
);
603 Log
.Error("Unknown connection state: " + state
);
607 // State change actions
609 this._rfb_connection_state
= state
;
611 Log
.Debug("New state '" + state
+ "', was '" + oldstate
+ "'.");
613 if (this._disconnTimer
&& state
!== 'disconnecting') {
614 Log
.Debug("Clearing disconnect timer");
615 clearTimeout(this._disconnTimer
);
616 this._disconnTimer
= null;
618 // make sure we don't get a double event
619 this._sock
.off('close');
628 this.dispatchEvent(new CustomEvent("connect", { detail
: {} }));
631 case 'disconnecting':
634 this._disconnTimer
= setTimeout(() => {
635 Log
.Error("Disconnection timed out.");
636 this._updateConnectionState('disconnected');
637 }, DISCONNECT_TIMEOUT
* 1000);
641 this.dispatchEvent(new CustomEvent(
642 "disconnect", { detail
:
643 { clean
: this._rfb_clean_disconnect
} }));
648 /* Print errors and disconnect
650 * The parameter 'details' is used for information that
651 * should be logged but not sent to the user interface.
654 switch (this._rfb_connection_state
) {
655 case 'disconnecting':
656 Log
.Error("Failed when disconnecting: " + details
);
659 Log
.Error("Failed while connected: " + details
);
662 Log
.Error("Failed when connecting: " + details
);
665 Log
.Error("RFB failure: " + details
);
668 this._rfb_clean_disconnect
= false; //This is sent to the UI
670 // Transition to disconnected without waiting for socket to close
671 this._updateConnectionState('disconnecting');
672 this._updateConnectionState('disconnected');
677 _setCapability(cap
, val
) {
678 this._capabilities
[cap
] = val
;
679 this.dispatchEvent(new CustomEvent("capabilities",
680 { detail
: { capabilities
: this._capabilities
} }));
684 if (this._sock
.rQlen
=== 0) {
685 Log
.Warn("handle_message called on an empty receive queue");
689 switch (this._rfb_connection_state
) {
691 Log
.Error("Got data while disconnected");
695 if (this._flushing
) {
698 if (!this._normal_msg()) {
701 if (this._sock
.rQlen
=== 0) {
712 _handleKeyEvent(keysym
, code
, down
) {
713 this.sendKey(keysym
, code
, down
);
716 _handleMouseButton(x
, y
, down
, bmask
) {
718 this._mouse_buttonMask
|= bmask
;
720 this._mouse_buttonMask
&= ~bmask
;
723 if (this.dragViewport
) {
724 if (down
&& !this._viewportDragging
) {
725 this._viewportDragging
= true;
726 this._viewportDragPos
= {'x': x
, 'y': y
};
727 this._viewportHasMoved
= false;
729 // Skip sending mouse events
732 this._viewportDragging
= false;
734 // If we actually performed a drag then we are done
735 // here and should not send any mouse events
736 if (this._viewportHasMoved
) {
740 // Otherwise we treat this as a mouse click event.
741 // Send the button down event here, as the button up
742 // event is sent at the end of this function.
743 RFB
.messages
.pointerEvent(this._sock
,
744 this._display
.absX(x
),
745 this._display
.absY(y
),
750 if (this._viewOnly
) { return; } // View only, skip mouse events
752 if (this._rfb_connection_state
!== 'connected') { return; }
753 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
756 _handleMouseMove(x
, y
) {
757 if (this._viewportDragging
) {
758 const deltaX
= this._viewportDragPos
.x
- x
;
759 const deltaY
= this._viewportDragPos
.y
- y
;
761 if (this._viewportHasMoved
|| (Math
.abs(deltaX
) > dragThreshold
||
762 Math
.abs(deltaY
) > dragThreshold
)) {
763 this._viewportHasMoved
= true;
765 this._viewportDragPos
= {'x': x
, 'y': y
};
766 this._display
.viewportChangePos(deltaX
, deltaY
);
769 // Skip sending mouse events
773 if (this._viewOnly
) { return; } // View only, skip mouse events
775 if (this._rfb_connection_state
!== 'connected') { return; }
776 RFB
.messages
.pointerEvent(this._sock
, this._display
.absX(x
), this._display
.absY(y
), this._mouse_buttonMask
);
781 _negotiate_protocol_version() {
782 if (this._sock
.rQlen
< 12) {
783 return this._fail("Received incomplete protocol version.");
786 const sversion
= this._sock
.rQshiftStr(12).substr(4, 7);
787 Log
.Info("Server ProtocolVersion: " + sversion
);
790 case "000.000": // UltraVNC repeater
794 case "003.006": // UltraVNC
795 case "003.889": // Apple Remote Desktop
796 this._rfb_version
= 3.3;
799 this._rfb_version
= 3.7;
802 case "004.000": // Intel AMT KVM
803 case "004.001": // RealVNC 4.6
804 case "005.000": // RealVNC 5.3
805 this._rfb_version
= 3.8;
808 return this._fail("Invalid server version " + sversion
);
812 let repeaterID
= "ID:" + this._repeaterID
;
813 while (repeaterID
.length
< 250) {
816 this._sock
.send_string(repeaterID
);
820 if (this._rfb_version
> this._rfb_max_version
) {
821 this._rfb_version
= this._rfb_max_version
;
824 const cversion
= "00" + parseInt(this._rfb_version
, 10) +
825 ".00" + ((this._rfb_version
* 10) % 10);
826 this._sock
.send_string("RFB " + cversion
+ "\n");
827 Log
.Debug('Sent ProtocolVersion: ' + cversion
);
829 this._rfb_init_state
= 'Security';
832 _negotiate_security() {
833 // Polyfill since IE and PhantomJS doesn't have
834 // TypedArray.includes()
835 function includes(item
, array
) {
836 for (let i
= 0; i
< array
.length
; i
++) {
837 if (array
[i
] === item
) {
844 if (this._rfb_version
>= 3.7) {
845 // Server sends supported list, client decides
846 const num_types
= this._sock
.rQshift8();
847 if (this._sock
.rQwait("security type", num_types
, 1)) { return false; }
849 if (num_types
=== 0) {
850 return this._handle_security_failure("no security types");
853 const types
= this._sock
.rQshiftBytes(num_types
);
854 Log
.Debug("Server security types: " + types
);
856 // Look for each auth in preferred order
857 this._rfb_auth_scheme
= 0;
858 if (includes(1, types
)) {
859 this._rfb_auth_scheme
= 1; // None
860 } else if (includes(22, types
)) {
861 this._rfb_auth_scheme
= 22; // XVP
862 } else if (includes(16, types
)) {
863 this._rfb_auth_scheme
= 16; // Tight
864 } else if (includes(2, types
)) {
865 this._rfb_auth_scheme
= 2; // VNC Auth
867 return this._fail("Unsupported security types (types: " + types
+ ")");
870 this._sock
.send([this._rfb_auth_scheme
]);
873 if (this._sock
.rQwait("security scheme", 4)) { return false; }
874 this._rfb_auth_scheme
= this._sock
.rQshift32();
877 this._rfb_init_state
= 'Authentication';
878 Log
.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme
);
880 return this._init_msg(); // jump to authentication
884 * Get the security failure reason if sent from the server and
885 * send the 'securityfailure' event.
887 * - The optional parameter context can be used to add some extra
888 * context to the log output.
890 * - The optional parameter security_result_status can be used to
891 * add a custom status code to the event.
893 _handle_security_failure(context
, security_result_status
) {
895 if (typeof context
=== 'undefined') {
898 context
= " on " + context
;
901 if (typeof security_result_status
=== 'undefined') {
902 security_result_status
= 1; // fail
905 if (this._sock
.rQwait("reason length", 4)) {
908 const strlen
= this._sock
.rQshift32();
912 if (this._sock
.rQwait("reason", strlen
, 8)) { return false; }
913 reason
= this._sock
.rQshiftStr(strlen
);
917 this.dispatchEvent(new CustomEvent(
919 { detail
: { status
: security_result_status
, reason
: reason
} }));
921 return this._fail("Security negotiation failed" + context
+
922 " (reason: " + reason
+ ")");
924 this.dispatchEvent(new CustomEvent(
926 { detail
: { status
: security_result_status
} }));
928 return this._fail("Security negotiation failed" + context
);
933 _negotiate_xvp_auth() {
934 if (!this._rfb_credentials
.username
||
935 !this._rfb_credentials
.password
||
936 !this._rfb_credentials
.target
) {
937 this.dispatchEvent(new CustomEvent(
938 "credentialsrequired",
939 { detail
: { types
: ["username", "password", "target"] } }));
943 const xvp_auth_str
= String
.fromCharCode(this._rfb_credentials
.username
.length
) +
944 String
.fromCharCode(this._rfb_credentials
.target
.length
) +
945 this._rfb_credentials
.username
+
946 this._rfb_credentials
.target
;
947 this._sock
.send_string(xvp_auth_str
);
948 this._rfb_auth_scheme
= 2;
949 return this._negotiate_authentication();
952 _negotiate_std_vnc_auth() {
953 if (this._sock
.rQwait("auth challenge", 16)) { return false; }
955 if (!this._rfb_credentials
.password
) {
956 this.dispatchEvent(new CustomEvent(
957 "credentialsrequired",
958 { detail
: { types
: ["password"] } }));
962 // TODO(directxman12): make genDES not require an Array
963 const challenge
= Array
.prototype.slice
.call(this._sock
.rQshiftBytes(16));
964 const response
= RFB
.genDES(this._rfb_credentials
.password
, challenge
);
965 this._sock
.send(response
);
966 this._rfb_init_state
= "SecurityResult";
970 _negotiate_tight_tunnels(numTunnels
) {
971 const clientSupportedTunnelTypes
= {
972 0: { vendor
: 'TGHT', signature
: 'NOTUNNEL' }
974 const serverSupportedTunnelTypes
= {};
975 // receive tunnel capabilities
976 for (let i
= 0; i
< numTunnels
; i
++) {
977 const cap_code
= this._sock
.rQshift32();
978 const cap_vendor
= this._sock
.rQshiftStr(4);
979 const cap_signature
= this._sock
.rQshiftStr(8);
980 serverSupportedTunnelTypes
[cap_code
] = { vendor
: cap_vendor
, signature
: cap_signature
};
983 Log
.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes
);
985 // Siemens touch panels have a VNC server that supports NOTUNNEL,
986 // but forgets to advertise it. Try to detect such servers by
987 // looking for their custom tunnel type.
988 if (serverSupportedTunnelTypes
[1] &&
989 (serverSupportedTunnelTypes
[1].vendor
=== "SICR") &&
990 (serverSupportedTunnelTypes
[1].signature
=== "SCHANNEL")) {
991 Log
.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
992 serverSupportedTunnelTypes
[0] = { vendor
: 'TGHT', signature
: 'NOTUNNEL' };
995 // choose the notunnel type
996 if (serverSupportedTunnelTypes
[0]) {
997 if (serverSupportedTunnelTypes
[0].vendor
!= clientSupportedTunnelTypes
[0].vendor
||
998 serverSupportedTunnelTypes
[0].signature
!= clientSupportedTunnelTypes
[0].signature
) {
999 return this._fail("Client's tunnel type had the incorrect " +
1000 "vendor or signature");
1002 Log
.Debug("Selected tunnel type: " + clientSupportedTunnelTypes
[0]);
1003 this._sock
.send([0, 0, 0, 0]); // use NOTUNNEL
1004 return false; // wait until we receive the sub auth count to continue
1006 return this._fail("Server wanted tunnels, but doesn't support " +
1007 "the notunnel type");
1011 _negotiate_tight_auth() {
1012 if (!this._rfb_tightvnc
) { // first pass, do the tunnel negotiation
1013 if (this._sock
.rQwait("num tunnels", 4)) { return false; }
1014 const numTunnels
= this._sock
.rQshift32();
1015 if (numTunnels
> 0 && this._sock
.rQwait("tunnel capabilities", 16 * numTunnels
, 4)) { return false; }
1017 this._rfb_tightvnc
= true;
1019 if (numTunnels
> 0) {
1020 this._negotiate_tight_tunnels(numTunnels
);
1021 return false; // wait until we receive the sub auth to continue
1025 // second pass, do the sub-auth negotiation
1026 if (this._sock
.rQwait("sub auth count", 4)) { return false; }
1027 const subAuthCount
= this._sock
.rQshift32();
1028 if (subAuthCount
=== 0) { // empty sub-auth list received means 'no auth' subtype selected
1029 this._rfb_init_state
= 'SecurityResult';
1033 if (this._sock
.rQwait("sub auth capabilities", 16 * subAuthCount
, 4)) { return false; }
1035 const clientSupportedTypes
= {
1040 const serverSupportedTypes
= [];
1042 for (let i
= 0; i
< subAuthCount
; i
++) {
1043 this._sock
.rQshift32(); // capNum
1044 const capabilities
= this._sock
.rQshiftStr(12);
1045 serverSupportedTypes
.push(capabilities
);
1048 Log
.Debug("Server Tight authentication types: " + serverSupportedTypes
);
1050 for (let authType
in clientSupportedTypes
) {
1051 if (serverSupportedTypes
.indexOf(authType
) != -1) {
1052 this._sock
.send([0, 0, 0, clientSupportedTypes
[authType
]]);
1053 Log
.Debug("Selected authentication type: " + authType
);
1056 case 'STDVNOAUTH__': // no auth
1057 this._rfb_init_state
= 'SecurityResult';
1059 case 'STDVVNCAUTH_': // VNC auth
1060 this._rfb_auth_scheme
= 2;
1061 return this._init_msg();
1063 return this._fail("Unsupported tiny auth scheme " +
1064 "(scheme: " + authType
+ ")");
1069 return this._fail("No supported sub-auth types!");
1072 _negotiate_authentication() {
1073 switch (this._rfb_auth_scheme
) {
1074 case 0: // connection failed
1075 return this._handle_security_failure("authentication scheme");
1078 if (this._rfb_version
>= 3.8) {
1079 this._rfb_init_state
= 'SecurityResult';
1082 this._rfb_init_state
= 'ClientInitialisation';
1083 return this._init_msg();
1085 case 22: // XVP auth
1086 return this._negotiate_xvp_auth();
1088 case 2: // VNC authentication
1089 return this._negotiate_std_vnc_auth();
1091 case 16: // TightVNC Security Type
1092 return this._negotiate_tight_auth();
1095 return this._fail("Unsupported auth scheme (scheme: " +
1096 this._rfb_auth_scheme
+ ")");
1100 _handle_security_result() {
1101 if (this._sock
.rQwait('VNC auth response ', 4)) { return false; }
1103 const status
= this._sock
.rQshift32();
1105 if (status
=== 0) { // OK
1106 this._rfb_init_state
= 'ClientInitialisation';
1107 Log
.Debug('Authentication OK');
1108 return this._init_msg();
1110 if (this._rfb_version
>= 3.8) {
1111 return this._handle_security_failure("security result", status
);
1113 this.dispatchEvent(new CustomEvent(
1115 { detail
: { status
: status
} }));
1117 return this._fail("Security handshake failed");
1122 _negotiate_server_init() {
1123 if (this._sock
.rQwait("server initialization", 24)) { return false; }
1126 const width
= this._sock
.rQshift16();
1127 const height
= this._sock
.rQshift16();
1130 const bpp
= this._sock
.rQshift8();
1131 const depth
= this._sock
.rQshift8();
1132 const big_endian
= this._sock
.rQshift8();
1133 const true_color
= this._sock
.rQshift8();
1135 const red_max
= this._sock
.rQshift16();
1136 const green_max
= this._sock
.rQshift16();
1137 const blue_max
= this._sock
.rQshift16();
1138 const red_shift
= this._sock
.rQshift8();
1139 const green_shift
= this._sock
.rQshift8();
1140 const blue_shift
= this._sock
.rQshift8();
1141 this._sock
.rQskipBytes(3); // padding
1143 // NB(directxman12): we don't want to call any callbacks or print messages until
1144 // *after* we're past the point where we could backtrack
1146 /* Connection name/title */
1147 const name_length = this._sock.rQshift32();
1148 if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
1149 this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
1151 if (this._rfb_tightvnc) {
1152 if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
1153 // In TightVNC mode, ServerInit message is extended
1154 const numServerMessages = this._sock.rQshift16();
1155 const numClientMessages = this._sock.rQshift16();
1156 const numEncodings = this._sock.rQshift16();
1157 this._sock.rQskipBytes(2); // padding
1159 const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
1160 if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
1162 // we don't actually do anything with the capability information that TIGHT sends,
1163 // so we just skip the all of this.
1165 // TIGHT server message capabilities
1166 this._sock.rQskipBytes(16 * numServerMessages);
1168 // TIGHT client message capabilities
1169 this._sock.rQskipBytes(16 * numClientMessages);
1171 // TIGHT encoding capabilities
1172 this._sock.rQskipBytes(16 * numEncodings);
1175 // NB(directxman12): these are down here so that we don't run them multiple times
1177 Log.Info("Screen: " + width + "x" + height +
1178 ", bpp: " + bpp + ", depth: " + depth +
1179 ", big_endian: " + big_endian +
1180 ", true_color: " + true_color +
1181 ", red_max: " + red_max +
1182 ", green_max: " + green_max +
1183 ", blue_max: " + blue_max +
1184 ", red_shift: " + red_shift +
1185 ", green_shift: " + green_shift +
1186 ", blue_shift: " + blue_shift);
1188 if (big_endian !== 0) {
1189 Log.Warn("Server native endian is not little endian");
1192 if (red_shift !== 16) {
1193 Log.Warn("Server native red-shift is not 16");
1196 if (blue_shift !== 0) {
1197 Log.Warn("Server native blue-shift is not 0");
1200 // we're past the point where we could backtrack, so it's safe to call this
1201 this.dispatchEvent(new CustomEvent(
1203 { detail: { name: this._fb_name } }));
1205 this._resize(width, height);
1207 if (!this._viewOnly) { this._keyboard.grab(); }
1208 if (!this._viewOnly) { this._mouse.grab(); }
1210 this._fb_depth = 24;
1212 if (this._fb_name === "Intel(r) AMT KVM") {
1213 Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
1217 RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
1218 this._sendEncodings();
1219 RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
1221 this._updateConnectionState('connected');
1228 // In preference order
1229 encs.push(encodings.encodingCopyRect);
1230 // Only supported with full depth support
1231 if (this._fb_depth == 24) {
1232 encs.push(encodings.encodingTight);
1233 encs.push(encodings.encodingTightPNG);
1234 encs.push(encodings.encodingHextile);
1235 encs.push(encodings.encodingRRE);
1237 encs.push(encodings.encodingRaw);
1239 // Psuedo-encoding settings
1240 encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
1241 encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
1243 encs.push(encodings.pseudoEncodingDesktopSize);
1244 encs.push(encodings.pseudoEncodingLastRect);
1245 encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
1246 encs.push(encodings.pseudoEncodingExtendedDesktopSize);
1247 encs.push(encodings.pseudoEncodingXvp);
1248 encs.push(encodings.pseudoEncodingFence);
1249 encs.push(encodings.pseudoEncodingContinuousUpdates);
1251 if (this._fb_depth == 24) {
1252 encs.push(encodings.pseudoEncodingCursor);
1255 RFB.messages.clientEncodings(this._sock, encs);
1258 /* RFB protocol initialization states:
1263 * ClientInitialization - not triggered by server message
1264 * ServerInitialization
1267 switch (this._rfb_init_state
) {
1268 case 'ProtocolVersion':
1269 return this._negotiate_protocol_version();
1272 return this._negotiate_security();
1274 case 'Authentication':
1275 return this._negotiate_authentication();
1277 case 'SecurityResult':
1278 return this._handle_security_result();
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
) {
1722 for (let i
= 0; i
< password
.length
; i
++) {
1723 passwd
.push(password
.charCodeAt(i
));
1725 return (new DES(passwd
)).encrypt(challenge
);
1731 keyEvent(sock
, keysym
, down
) {
1732 const buff
= sock
._sQ
;
1733 const offset
= sock
._sQlen
;
1735 buff
[offset
] = 4; // msg-type
1736 buff
[offset
+ 1] = down
;
1738 buff
[offset
+ 2] = 0;
1739 buff
[offset
+ 3] = 0;
1741 buff
[offset
+ 4] = (keysym
>> 24);
1742 buff
[offset
+ 5] = (keysym
>> 16);
1743 buff
[offset
+ 6] = (keysym
>> 8);
1744 buff
[offset
+ 7] = keysym
;
1750 QEMUExtendedKeyEvent(sock
, keysym
, down
, keycode
) {
1751 function getRFBkeycode(xt_scancode
) {
1752 const upperByte
= (keycode
>> 8);
1753 const lowerByte
= (keycode
& 0x00ff);
1754 if (upperByte
=== 0xe0 && lowerByte
< 0x7f) {
1755 return lowerByte
| 0x80;
1760 const buff
= sock
._sQ
;
1761 const offset
= sock
._sQlen
;
1763 buff
[offset
] = 255; // msg-type
1764 buff
[offset
+ 1] = 0; // sub msg-type
1766 buff
[offset
+ 2] = (down
>> 8);
1767 buff
[offset
+ 3] = down
;
1769 buff
[offset
+ 4] = (keysym
>> 24);
1770 buff
[offset
+ 5] = (keysym
>> 16);
1771 buff
[offset
+ 6] = (keysym
>> 8);
1772 buff
[offset
+ 7] = keysym
;
1774 const RFBkeycode
= getRFBkeycode(keycode
);
1776 buff
[offset
+ 8] = (RFBkeycode
>> 24);
1777 buff
[offset
+ 9] = (RFBkeycode
>> 16);
1778 buff
[offset
+ 10] = (RFBkeycode
>> 8);
1779 buff
[offset
+ 11] = RFBkeycode
;
1785 pointerEvent(sock
, x
, y
, mask
) {
1786 const buff
= sock
._sQ
;
1787 const offset
= sock
._sQlen
;
1789 buff
[offset
] = 5; // msg-type
1791 buff
[offset
+ 1] = mask
;
1793 buff
[offset
+ 2] = x
>> 8;
1794 buff
[offset
+ 3] = x
;
1796 buff
[offset
+ 4] = y
>> 8;
1797 buff
[offset
+ 5] = y
;
1803 // TODO(directxman12): make this unicode compatible?
1804 clientCutText(sock
, text
) {
1805 const buff
= sock
._sQ
;
1806 const offset
= sock
._sQlen
;
1808 buff
[offset
] = 6; // msg-type
1810 buff
[offset
+ 1] = 0; // padding
1811 buff
[offset
+ 2] = 0; // padding
1812 buff
[offset
+ 3] = 0; // padding
1814 let length
= text
.length
;
1816 buff
[offset
+ 4] = length
>> 24;
1817 buff
[offset
+ 5] = length
>> 16;
1818 buff
[offset
+ 6] = length
>> 8;
1819 buff
[offset
+ 7] = length
;
1823 // We have to keep track of from where in the text we begin creating the
1824 // buffer for the flush in the next iteration.
1827 let remaining
= length
;
1828 while (remaining
> 0) {
1830 let flushSize
= Math
.min(remaining
, (sock
._sQbufferSize
- sock
._sQlen
));
1831 if (flushSize
<= 0) {
1832 this._fail("Clipboard contents could not be sent");
1836 for (let i
= 0; i
< flushSize
; i
++) {
1837 buff
[sock
._sQlen
+ i
] = text
.charCodeAt(textOffset
+ i
);
1840 sock
._sQlen
+= flushSize
;
1843 remaining
-= flushSize
;
1844 textOffset
+= flushSize
;
1848 setDesktopSize(sock
, width
, height
, id
, flags
) {
1849 const buff
= sock
._sQ
;
1850 const offset
= sock
._sQlen
;
1852 buff
[offset
] = 251; // msg-type
1853 buff
[offset
+ 1] = 0; // padding
1854 buff
[offset
+ 2] = width
>> 8; // width
1855 buff
[offset
+ 3] = width
;
1856 buff
[offset
+ 4] = height
>> 8; // height
1857 buff
[offset
+ 5] = height
;
1859 buff
[offset
+ 6] = 1; // number-of-screens
1860 buff
[offset
+ 7] = 0; // padding
1863 buff
[offset
+ 8] = id
>> 24; // id
1864 buff
[offset
+ 9] = id
>> 16;
1865 buff
[offset
+ 10] = id
>> 8;
1866 buff
[offset
+ 11] = id
;
1867 buff
[offset
+ 12] = 0; // x-position
1868 buff
[offset
+ 13] = 0;
1869 buff
[offset
+ 14] = 0; // y-position
1870 buff
[offset
+ 15] = 0;
1871 buff
[offset
+ 16] = width
>> 8; // width
1872 buff
[offset
+ 17] = width
;
1873 buff
[offset
+ 18] = height
>> 8; // height
1874 buff
[offset
+ 19] = height
;
1875 buff
[offset
+ 20] = flags
>> 24; // flags
1876 buff
[offset
+ 21] = flags
>> 16;
1877 buff
[offset
+ 22] = flags
>> 8;
1878 buff
[offset
+ 23] = flags
;
1884 clientFence(sock
, flags
, payload
) {
1885 const buff
= sock
._sQ
;
1886 const offset
= sock
._sQlen
;
1888 buff
[offset
] = 248; // msg-type
1890 buff
[offset
+ 1] = 0; // padding
1891 buff
[offset
+ 2] = 0; // padding
1892 buff
[offset
+ 3] = 0; // padding
1894 buff
[offset
+ 4] = flags
>> 24; // flags
1895 buff
[offset
+ 5] = flags
>> 16;
1896 buff
[offset
+ 6] = flags
>> 8;
1897 buff
[offset
+ 7] = flags
;
1899 const n
= payload
.length
;
1901 buff
[offset
+ 8] = n
; // length
1903 for (let i
= 0; i
< n
; i
++) {
1904 buff
[offset
+ 9 + i
] = payload
.charCodeAt(i
);
1907 sock
._sQlen
+= 9 + n
;
1911 enableContinuousUpdates(sock
, enable
, x
, y
, width
, height
) {
1912 const buff
= sock
._sQ
;
1913 const offset
= sock
._sQlen
;
1915 buff
[offset
] = 150; // msg-type
1916 buff
[offset
+ 1] = enable
; // enable-flag
1918 buff
[offset
+ 2] = x
>> 8; // x
1919 buff
[offset
+ 3] = x
;
1920 buff
[offset
+ 4] = y
>> 8; // y
1921 buff
[offset
+ 5] = y
;
1922 buff
[offset
+ 6] = width
>> 8; // width
1923 buff
[offset
+ 7] = width
;
1924 buff
[offset
+ 8] = height
>> 8; // height
1925 buff
[offset
+ 9] = height
;
1931 pixelFormat(sock
, depth
, true_color
) {
1932 const buff
= sock
._sQ
;
1933 const offset
= sock
._sQlen
;
1939 } else if (depth
> 8) {
1945 const bits
= Math
.floor(depth
/3);
1947 buff
[offset
] = 0; // msg-type
1949 buff
[offset
+ 1] = 0; // padding
1950 buff
[offset
+ 2] = 0; // padding
1951 buff
[offset
+ 3] = 0; // padding
1953 buff
[offset
+ 4] = bpp
; // bits-per-pixel
1954 buff
[offset
+ 5] = depth
; // depth
1955 buff
[offset
+ 6] = 0; // little-endian
1956 buff
[offset
+ 7] = true_color
? 1 : 0; // true-color
1958 buff
[offset
+ 8] = 0; // red-max
1959 buff
[offset
+ 9] = (1 << bits
) - 1; // red-max
1961 buff
[offset
+ 10] = 0; // green-max
1962 buff
[offset
+ 11] = (1 << bits
) - 1; // green-max
1964 buff
[offset
+ 12] = 0; // blue-max
1965 buff
[offset
+ 13] = (1 << bits
) - 1; // blue-max
1967 buff
[offset
+ 14] = bits
* 2; // red-shift
1968 buff
[offset
+ 15] = bits
* 1; // green-shift
1969 buff
[offset
+ 16] = bits
* 0; // blue-shift
1971 buff
[offset
+ 17] = 0; // padding
1972 buff
[offset
+ 18] = 0; // padding
1973 buff
[offset
+ 19] = 0; // padding
1979 clientEncodings(sock
, encodings
) {
1980 const buff
= sock
._sQ
;
1981 const offset
= sock
._sQlen
;
1983 buff
[offset
] = 2; // msg-type
1984 buff
[offset
+ 1] = 0; // padding
1986 buff
[offset
+ 2] = encodings
.length
>> 8;
1987 buff
[offset
+ 3] = encodings
.length
;
1990 for (let i
= 0; i
< encodings
.length
; i
++) {
1991 const enc
= encodings
[i
];
1992 buff
[j
] = enc
>> 24;
1993 buff
[j
+ 1] = enc
>> 16;
1994 buff
[j
+ 2] = enc
>> 8;
2000 sock
._sQlen
+= j
- offset
;
2004 fbUpdateRequest(sock
, incremental
, x
, y
, w
, h
) {
2005 const buff
= sock
._sQ
;
2006 const offset
= sock
._sQlen
;
2008 if (typeof(x
) === "undefined") { x
= 0; }
2009 if (typeof(y
) === "undefined") { y
= 0; }
2011 buff
[offset
] = 3; // msg-type
2012 buff
[offset
+ 1] = incremental
? 1 : 0;
2014 buff
[offset
+ 2] = (x
>> 8) & 0xFF;
2015 buff
[offset
+ 3] = x
& 0xFF;
2017 buff
[offset
+ 4] = (y
>> 8) & 0xFF;
2018 buff
[offset
+ 5] = y
& 0xFF;
2020 buff
[offset
+ 6] = (w
>> 8) & 0xFF;
2021 buff
[offset
+ 7] = w
& 0xFF;
2023 buff
[offset
+ 8] = (h
>> 8) & 0xFF;
2024 buff
[offset
+ 9] = h
& 0xFF;
2030 xvpOp(sock
, ver
, op
) {
2031 const buff
= sock
._sQ
;
2032 const offset
= sock
._sQlen
;
2034 buff
[offset
] = 250; // msg-type
2035 buff
[offset
+ 1] = 0; // padding
2037 buff
[offset
+ 2] = ver
;
2038 buff
[offset
+ 3] = op
;
2047 rgbaPixels
: new Uint8Array(),
2053 /* eslint-disable indent */
2054 rgbaPixels
: new Uint8Array([
2055 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2056 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
2057 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
2059 /* eslint-enable indent */