import * as Log from './util/logging.js';
import { encodeUTF8, decodeUTF8 } from './util/strings.js';
import { dragThreshold } from './util/browser.js';
+import { clientToElement } from './util/element.js';
+import { setCapture } from './util/events.js';
import EventTargetMixin from './util/eventtarget.js';
import Display from "./display.js";
import Inflator from "./inflator.js";
import Deflator from "./deflator.js";
import Keyboard from "./input/keyboard.js";
-import Mouse from "./input/mouse.js";
+import GestureHandler from "./input/gesturehandler.js";
import Cursor from "./util/cursor.js";
import Websock from "./websock.js";
import DES from "./des.js";
import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import { encodings } from "./encodings.js";
-import "./util/polyfill.js";
import RawDecoder from "./decoders/raw.js";
import CopyRectDecoder from "./decoders/copyrect.js";
const DISCONNECT_TIMEOUT = 3;
const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
+// Minimum wait (ms) between two mouse moves
+const MOUSE_MOVE_DELAY = 17;
+
+// Wheel thresholds
+const WHEEL_STEP = 50; // Pixels needed for one step
+const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
+
+// Gesture thresholds
+const GESTURE_ZOOMSENS = 75;
+const GESTURE_SCRLSENS = 50;
+const DOUBLE_TAP_TIMEOUT = 1000;
+const DOUBLE_TAP_THRESHOLD = 50;
+
// Extended clipboard pseudo-encoding formats
const extendedClipboardFormatText = 1;
/*eslint-disable no-unused-vars */
const extendedClipboardActionNotify = 1 << 27;
const extendedClipboardActionProvide = 1 << 28;
-
export default class RFB extends EventTargetMixin {
- constructor(target, url, options) {
+ constructor(target, urlOrChannel, options) {
if (!target) {
throw new Error("Must specify target");
}
- if (!url) {
- throw new Error("Must specify URL");
+ if (!urlOrChannel) {
+ throw new Error("Must specify URL, WebSocket or RTCDataChannel");
}
super();
this._target = target;
- this._url = url;
+
+ if (typeof urlOrChannel === "string") {
+ this._url = urlOrChannel;
+ } else {
+ this._url = null;
+ this._rawChannel = urlOrChannel;
+ }
// Connection details
options = options || {};
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
- this._mouse = null; // Mouse input handler object
+ this._gestures = null; // Gesture input handler object
// Timers
this._disconnTimer = null; // disconnection timer
this._resizeTimeout = null; // resize rate limiting
+ this._mouseMoveTimer = null;
// Decoder states
this._decoders = {};
};
// Mouse state
+ this._mousePos = {};
this._mouseButtonMask = 0;
+ this._mouseLastMoveTime = 0;
this._viewportDragging = false;
this._viewportDragPos = {};
this._viewportHasMoved = false;
+ this._accumulatedWheelDeltaX = 0;
+ this._accumulatedWheelDeltaY = 0;
+
+ // Gesture state
+ this._gestureLastTapTime = null;
+ this._gestureFirstDoubleTapEv = null;
+ this._gestureLastMagnitudeX = 0;
+ this._gestureLastMagnitudeY = 0;
// Bound event handlers
this._eventHandlers = {
focusCanvas: this._focusCanvas.bind(this),
windowResize: this._windowResize.bind(this),
+ handleMouse: this._handleMouse.bind(this),
+ handleWheel: this._handleWheel.bind(this),
+ handleGesture: this._handleGesture.bind(this),
};
// main setup
this._canvas.style.margin = 'auto';
// Some browsers add an outline on focus
this._canvas.style.outline = 'none';
- // IE miscalculates width without this :(
- this._canvas.style.flexShrink = '0';
this._canvas.width = 0;
this._canvas.height = 0;
this._canvas.tabIndex = -1;
this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
- this._mouse = new Mouse(this._canvas);
- this._mouse.onmousebutton = this._handleMouseButton.bind(this);
- this._mouse.onmousemove = this._handleMouseMove.bind(this);
+ this._gestures = new GestureHandler();
this._sock = new Websock();
this._sock.on('message', () => {
break;
}
this._sock.off('close');
+ // Delete reference to raw channel to allow cleanup.
+ this._rawChannel = null;
});
this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
// Slight delay of the actual connection so that the caller has
- // time to set up callbacks
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
+ // time to set up callbacks.
+ // This it not possible when a pre-existing socket is passed in and is just opened.
+ // If the caller creates this object in the open() callback of a socket and there's
+ // data pending doing it next tick causes a packet to be lost.
+ // This is particularly noticable for RTCDataChannel's where the other end creates
+ // the channel and the client, this end, gets notified it exists.
+ if (typeof urlOrChannel === 'string') {
+ setTimeout(this._updateConnectionState.bind(this, 'connecting'));
+ } else {
+ this._updateConnectionState('connecting');
+ }
Log.Debug("<< RFB.constructor");
this._rfbConnectionState === "connected") {
if (viewOnly) {
this._keyboard.ungrab();
- this._mouse.ungrab();
} else {
this._keyboard.grab();
- this._mouse.grab();
}
}
}
get capabilities() { return this._capabilities; }
- get touchButton() { return this._mouse.touchButton; }
- set touchButton(button) { this._mouse.touchButton = button; }
+ get touchButton() { return 0; }
+ set touchButton(button) { Log.Warn("Using old API!"); }
get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
_connect() {
Log.Debug(">> RFB.connect");
- Log.Info("connecting to " + this._url);
-
- try {
- // WebSocket.onopen transitions to the RFB init states
- this._sock.open(this._url, this._wsProtocols);
- } catch (e) {
- if (e.name === 'SyntaxError') {
- this._fail("Invalid host or port (" + e + ")");
- } else {
- this._fail("Error when opening socket (" + e + ")");
+ if (this._url) {
+ try {
+ Log.Info(`connecting to ${this._url}`);
+ this._sock.open(this._url, this._wsProtocols);
+ } catch (e) {
+ if (e.name === 'SyntaxError') {
+ this._fail("Invalid host or port (" + e + ")");
+ } else {
+ this._fail("Error when opening socket (" + e + ")");
+ }
+ }
+ } else {
+ try {
+ Log.Info(`attaching ${this._rawChannel} to Websock`);
+ this._sock.attach(this._rawChannel);
+ } catch (e) {
+ this._fail("Error attaching channel (" + e + ")");
}
}
// Make our elements part of the page
this._target.appendChild(this._screen);
+ this._gestures.attach(this._canvas);
+
this._cursor.attach(this._canvas);
this._refreshCursor();
this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
+ // Mouse events
+ this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);
+ this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);
+ this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);
+ // Prevent middle-click pasting (see handler for why we bind to document)
+ this._canvas.addEventListener('click', this._eventHandlers.handleMouse);
+ // preventDefault() on mousedown doesn't stop this event for some
+ // reason so we have to explicitly block it
+ this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
+
+ // Wheel events
+ this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
+
+ // Gesture events
+ this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
+ this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
+ this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);
+
Log.Debug("<< RFB.connect");
}
_disconnect() {
Log.Debug(">> RFB.disconnect");
this._cursor.detach();
+ this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
+ this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
+ this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
+ this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
+ this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);
+ this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);
+ this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
+ this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
+ this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
window.removeEventListener('resize', this._eventHandlers.windowResize);
this._keyboard.ungrab();
- this._mouse.ungrab();
+ this._gestures.detach();
this._sock.close();
try {
this._target.removeChild(this._screen);
}
}
clearTimeout(this._resizeTimeout);
+ clearTimeout(this._mouseMoveTimer);
Log.Debug("<< RFB.disconnect");
}
_focusCanvas(event) {
- // Respect earlier handlers' request to not do side-effects
- if (event.defaultPrevented) {
- return;
- }
-
if (!this.focusOnClick) {
return;
}
this.sendKey(keysym, code, down);
}
- _handleMouseButton(x, y, down, bmask) {
- if (down) {
- this._mouseButtonMask |= bmask;
- } else {
- this._mouseButtonMask &= ~bmask;
+ _handleMouse(ev) {
+ /*
+ * We don't check connection status or viewOnly here as the
+ * mouse events might be used to control the viewport
+ */
+
+ if (ev.type === 'click') {
+ /*
+ * Note: This is only needed for the 'click' event as it fails
+ * to fire properly for the target element so we have
+ * to listen on the document element instead.
+ */
+ if (ev.target !== this._canvas) {
+ return;
+ }
+ }
+
+ // FIXME: if we're in view-only and not dragging,
+ // should we stop events?
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
+ return;
+ }
+
+ let pos = clientToElement(ev.clientX, ev.clientY,
+ this._canvas);
+
+ switch (ev.type) {
+ case 'mousedown':
+ setCapture(this._canvas);
+ this._handleMouseButton(pos.x, pos.y,
+ true, 1 << ev.button);
+ break;
+ case 'mouseup':
+ this._handleMouseButton(pos.x, pos.y,
+ false, 1 << ev.button);
+ break;
+ case 'mousemove':
+ this._handleMouseMove(pos.x, pos.y);
+ break;
}
+ }
+ _handleMouseButton(x, y, down, bmask) {
if (this.dragViewport) {
if (down && !this._viewportDragging) {
this._viewportDragging = true;
return;
}
- if (this._viewOnly) { return; }
-
// Otherwise we treat this as a mouse click event.
// Send the button down event here, as the button up
// event is sent at the end of this function.
- RFB.messages.pointerEvent(this._sock,
- this._display.absX(x),
- this._display.absY(y),
- bmask);
+ this._sendMouse(x, y, bmask);
}
}
- if (this._viewOnly) { return; } // View only, skip mouse events
+ // Flush waiting move event first
+ if (this._mouseMoveTimer !== null) {
+ clearTimeout(this._mouseMoveTimer);
+ this._mouseMoveTimer = null;
+ this._sendMouse(x, y, this._mouseButtonMask);
+ }
- if (this._rfbConnectionState !== 'connected') { return; }
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouseButtonMask);
+ if (down) {
+ this._mouseButtonMask |= bmask;
+ } else {
+ this._mouseButtonMask &= ~bmask;
+ }
+
+ this._sendMouse(x, y, this._mouseButtonMask);
}
_handleMouseMove(x, y) {
return;
}
+ this._mousePos = { 'x': x, 'y': y };
+
+ // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
+ if (this._mouseMoveTimer == null) {
+
+ const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;
+ if (timeSinceLastMove > MOUSE_MOVE_DELAY) {
+ this._sendMouse(x, y, this._mouseButtonMask);
+ this._mouseLastMoveTime = Date.now();
+ } else {
+ // Too soon since the latest move, wait the remaining time
+ this._mouseMoveTimer = setTimeout(() => {
+ this._handleDelayedMouseMove();
+ }, MOUSE_MOVE_DELAY - timeSinceLastMove);
+ }
+ }
+ }
+
+ _handleDelayedMouseMove() {
+ this._mouseMoveTimer = null;
+ this._sendMouse(this._mousePos.x, this._mousePos.y,
+ this._mouseButtonMask);
+ this._mouseLastMoveTime = Date.now();
+ }
+
+ _sendMouse(x, y, mask) {
+ if (this._rfbConnectionState !== 'connected') { return; }
if (this._viewOnly) { return; } // View only, skip mouse events
+ RFB.messages.pointerEvent(this._sock, this._display.absX(x),
+ this._display.absY(y), mask);
+ }
+
+ _handleWheel(ev) {
if (this._rfbConnectionState !== 'connected') { return; }
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouseButtonMask);
+ if (this._viewOnly) { return; } // View only, skip mouse events
+
+ ev.stopPropagation();
+ ev.preventDefault();
+
+ let pos = clientToElement(ev.clientX, ev.clientY,
+ this._canvas);
+
+ let dX = ev.deltaX;
+ let dY = ev.deltaY;
+
+ // Pixel units unless it's non-zero.
+ // Note that if deltamode is line or page won't matter since we aren't
+ // sending the mouse wheel delta to the server anyway.
+ // The difference between pixel and line can be important however since
+ // we have a threshold that can be smaller than the line height.
+ if (ev.deltaMode !== 0) {
+ dX *= WHEEL_LINE_HEIGHT;
+ dY *= WHEEL_LINE_HEIGHT;
+ }
+
+ // Mouse wheel events are sent in steps over VNC. This means that the VNC
+ // protocol can't handle a wheel event with specific distance or speed.
+ // Therefor, if we get a lot of small mouse wheel events we combine them.
+ this._accumulatedWheelDeltaX += dX;
+ this._accumulatedWheelDeltaY += dY;
+
+ // Generate a mouse wheel step event when the accumulated delta
+ // for one of the axes is large enough.
+ if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
+ if (this._accumulatedWheelDeltaX < 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
+ } else if (this._accumulatedWheelDeltaX > 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
+ }
+
+ this._accumulatedWheelDeltaX = 0;
+ }
+ if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
+ if (this._accumulatedWheelDeltaY < 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
+ } else if (this._accumulatedWheelDeltaY > 0) {
+ this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
+ this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
+ }
+
+ this._accumulatedWheelDeltaY = 0;
+ }
+ }
+
+ _fakeMouseMove(ev, elementX, elementY) {
+ this._handleMouseMove(elementX, elementY);
+ this._cursor.move(ev.detail.clientX, ev.detail.clientY);
+ }
+
+ _handleTapEvent(ev, bmask) {
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
+ this._canvas);
+
+ // If the user quickly taps multiple times we assume they meant to
+ // hit the same spot, so slightly adjust coordinates
+
+ if ((this._gestureLastTapTime !== null) &&
+ ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&
+ (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {
+ let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;
+ let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;
+ let distance = Math.hypot(dx, dy);
+
+ if (distance < DOUBLE_TAP_THRESHOLD) {
+ pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,
+ this._gestureFirstDoubleTapEv.detail.clientY,
+ this._canvas);
+ } else {
+ this._gestureFirstDoubleTapEv = ev;
+ }
+ } else {
+ this._gestureFirstDoubleTapEv = ev;
+ }
+ this._gestureLastTapTime = Date.now();
+
+ this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
+ this._handleMouseButton(pos.x, pos.y, true, bmask);
+ this._handleMouseButton(pos.x, pos.y, false, bmask);
+ }
+
+ _handleGesture(ev) {
+ let magnitude;
+
+ let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
+ this._canvas);
+ switch (ev.type) {
+ case 'gesturestart':
+ switch (ev.detail.type) {
+ case 'onetap':
+ this._handleTapEvent(ev, 0x1);
+ break;
+ case 'twotap':
+ this._handleTapEvent(ev, 0x4);
+ break;
+ case 'threetap':
+ this._handleTapEvent(ev, 0x2);
+ break;
+ case 'drag':
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ this._handleMouseButton(pos.x, pos.y, true, 0x1);
+ break;
+ case 'longpress':
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ this._handleMouseButton(pos.x, pos.y, true, 0x4);
+ break;
+
+ case 'twodrag':
+ this._gestureLastMagnitudeX = ev.detail.magnitudeX;
+ this._gestureLastMagnitudeY = ev.detail.magnitudeY;
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ break;
+ case 'pinch':
+ this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
+ ev.detail.magnitudeY);
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ break;
+ }
+ break;
+
+ case 'gesturemove':
+ switch (ev.detail.type) {
+ case 'onetap':
+ case 'twotap':
+ case 'threetap':
+ break;
+ case 'drag':
+ case 'longpress':
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ break;
+ case 'twodrag':
+ // Always scroll in the same position.
+ // We don't know if the mouse was moved so we need to move it
+ // every update.
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
+ this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
+ }
+ while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
+ this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
+ }
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x20);
+ this._handleMouseButton(pos.x, pos.y, false, 0x20);
+ this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
+ }
+ while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x40);
+ this._handleMouseButton(pos.x, pos.y, false, 0x40);
+ this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
+ }
+ break;
+ case 'pinch':
+ // Always scroll in the same position.
+ // We don't know if the mouse was moved so we need to move it
+ // every update.
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
+ if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
+ while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x8);
+ this._handleMouseButton(pos.x, pos.y, false, 0x8);
+ this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
+ }
+ while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
+ this._handleMouseButton(pos.x, pos.y, true, 0x10);
+ this._handleMouseButton(pos.x, pos.y, false, 0x10);
+ this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
+ }
+ }
+ this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
+ break;
+ }
+ break;
+
+ case 'gestureend':
+ switch (ev.detail.type) {
+ case 'onetap':
+ case 'twotap':
+ case 'threetap':
+ case 'pinch':
+ case 'twodrag':
+ break;
+ case 'drag':
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ this._handleMouseButton(pos.x, pos.y, false, 0x1);
+ break;
+ case 'longpress':
+ this._fakeMouseMove(ev, pos.x, pos.y);
+ this._handleMouseButton(pos.x, pos.y, false, 0x4);
+ break;
+ }
+ break;
+ }
}
// Message Handlers
while (repeaterID.length < 250) {
repeaterID += "\0";
}
- this._sock.send_string(repeaterID);
+ this._sock.sendString(repeaterID);
return true;
}
const cversion = "00" + parseInt(this._rfbVersion, 10) +
".00" + ((this._rfbVersion * 10) % 10);
- this._sock.send_string("RFB " + cversion + "\n");
+ this._sock.sendString("RFB " + cversion + "\n");
Log.Debug('Sent ProtocolVersion: ' + cversion);
this._rfbInitState = 'Security';
}
_negotiateSecurity() {
- // Polyfill since IE and PhantomJS doesn't have
- // TypedArray.includes()
- function includes(item, array) {
- for (let i = 0; i < array.length; i++) {
- if (array[i] === item) {
- return true;
- }
- }
- return false;
- }
-
if (this._rfbVersion >= 3.7) {
// Server sends supported list, client decides
const numTypes = this._sock.rQshift8();
Log.Debug("Server security types: " + types);
// Look for each auth in preferred order
- if (includes(1, types)) {
+ if (types.includes(1)) {
this._rfbAuthScheme = 1; // None
- } else if (includes(22, types)) {
+ } else if (types.includes(22)) {
this._rfbAuthScheme = 22; // XVP
- } else if (includes(16, types)) {
+ } else if (types.includes(16)) {
this._rfbAuthScheme = 16; // Tight
- } else if (includes(2, types)) {
+ } else if (types.includes(2)) {
this._rfbAuthScheme = 2; // VNC Auth
- } else if (includes(19, types)) {
+ } else if (types.includes(19)) {
this._rfbAuthScheme = 19; // VeNCrypt Auth
} else {
return this._fail("Unsupported security types (types: " + types + ")");
String.fromCharCode(this._rfbCredentials.target.length) +
this._rfbCredentials.username +
this._rfbCredentials.target;
- this._sock.send_string(xvpAuthStr);
+ this._sock.sendString(xvpAuthStr);
this._rfbAuthScheme = 2;
return this._negotiateAuthentication();
}
// negotiated Plain subtype, server waits for password
if (this._rfbVeNCryptState == 4) {
- if (!this._rfbCredentials.username ||
- !this._rfbCredentials.password) {
+ if (this._rfbCredentials.username === undefined ||
+ this._rfbCredentials.password === undefined) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["username", "password"] } }));
const user = encodeUTF8(this._rfbCredentials.username);
const pass = encodeUTF8(this._rfbCredentials.password);
- // XXX we assume lengths are <= 255 (should not be an issue in the real world)
- this._sock.send([0, 0, 0, user.length]);
- this._sock.send([0, 0, 0, pass.length]);
- this._sock.send_string(user);
- this._sock.send_string(pass);
+ this._sock.send([
+ (user.length >> 24) & 0xFF,
+ (user.length >> 16) & 0xFF,
+ (user.length >> 8) & 0xFF,
+ user.length & 0xFF
+ ]);
+ this._sock.send([
+ (pass.length >> 24) & 0xFF,
+ (pass.length >> 16) & 0xFF,
+ (pass.length >> 8) & 0xFF,
+ pass.length & 0xFF
+ ]);
+ this._sock.sendString(user);
+ this._sock.sendString(pass);
this._rfbInitState = "SecurityResult";
return true;
this._sock.send([0, 0, 0, this._rfbCredentials.username.length]);
this._sock.send([0, 0, 0, this._rfbCredentials.password.length]);
- this._sock.send_string(this._rfbCredentials.username);
- this._sock.send_string(this._rfbCredentials.password);
+ this._sock.sendString(this._rfbCredentials.username);
+ this._sock.sendString(this._rfbCredentials.password);
this._rfbInitState = "SecurityResult";
return true;
}
this._resize(width, height);
if (!this._viewOnly) { this._keyboard.grab(); }
- if (!this._viewOnly) { this._mouse.grab(); }
this._fbDepth = 24;
return this._handleCursor();
case encodings.pseudoEncodingQEMUExtendedKeyEvent:
- // Old Safari doesn't support creating keyboard events
- try {
- const keyboardEvent = document.createEvent("keyboardEvent");
- if (keyboardEvent.code !== undefined) {
- this._qemuExtKeyEventSupported = true;
- }
- } catch (err) {
- // Do nothing
- }
+ this._qemuExtKeyEventSupported = true;
return true;
case encodings.pseudoEncodingDesktopName:
buff[offset + 12] = 0; // blue-max
buff[offset + 13] = (1 << bits) - 1; // blue-max
- buff[offset + 14] = bits * 2; // red-shift
+ buff[offset + 14] = bits * 0; // red-shift
buff[offset + 15] = bits * 1; // green-shift
- buff[offset + 16] = bits * 0; // blue-shift
+ buff[offset + 16] = bits * 2; // blue-shift
buff[offset + 17] = 0; // padding
buff[offset + 18] = 0; // padding