SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
19 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%
119.6 KB · 3,416 lines javascript
Raw Blame History
1/*2 * noVNC: HTML5 VNC client3 * Copyright (C) 2020 The noVNC authors4 * Licensed under MPL 2.0 (see LICENSE.txt)5 *6 * See README.md for usage and integration instructions.7 *8 */910import { toUnsigned32bit, toSigned32bit } from './util/int.js';11import * as Log from './util/logging.js';12import { encodeUTF8, decodeUTF8 } from './util/strings.js';13import { dragThreshold, supportsWebCodecsH264Decode } from './util/browser.js';14import { clientToElement } from './util/element.js';15import { setCapture } from './util/events.js';16import EventTargetMixin from './util/eventtarget.js';17import Display from "./display.js";18import Inflator from "./inflator.js";19import Deflator from "./deflator.js";20import Keyboard from "./input/keyboard.js";21import GestureHandler from "./input/gesturehandler.js";22import Cursor from "./util/cursor.js";23import Websock from "./websock.js";24import KeyTable from "./input/keysym.js";25import XtScancode from "./input/xtscancodes.js";26import { encodings } from "./encodings.js";27import RSAAESAuthenticationState from "./ra2.js";28import legacyCrypto from "./crypto/crypto.js";2930import RawDecoder from "./decoders/raw.js";31import CopyRectDecoder from "./decoders/copyrect.js";32import RREDecoder from "./decoders/rre.js";33import HextileDecoder from "./decoders/hextile.js";34import ZlibDecoder from './decoders/zlib.js';35import TightDecoder from "./decoders/tight.js";36import TightPNGDecoder from "./decoders/tightpng.js";37import ZRLEDecoder from "./decoders/zrle.js";38import JPEGDecoder from "./decoders/jpeg.js";39import H264Decoder from "./decoders/h264.js";4041// How many seconds to wait for a disconnect to finish42const DISCONNECT_TIMEOUT = 3;43const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';4445// Minimum wait (ms) between two mouse moves46const MOUSE_MOVE_DELAY = 17;4748// Wheel thresholds49const WHEEL_STEP = 50; // Pixels needed for one step50const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step5152// Gesture thresholds53const GESTURE_ZOOMSENS = 75;54const GESTURE_SCRLSENS = 50;55const DOUBLE_TAP_TIMEOUT = 1000;56const DOUBLE_TAP_THRESHOLD = 50;5758// Security types59const securityTypeNone              = 1;60const securityTypeVNCAuth           = 2;61const securityTypeRA2ne             = 6;62const securityTypeTight             = 16;63const securityTypeVeNCrypt          = 19;64const securityTypeXVP               = 22;65const securityTypeARD               = 30;66const securityTypeMSLogonII         = 113;6768// Special Tight security types69const securityTypeUnixLogon         = 129;7071// VeNCrypt security types72const securityTypePlain             = 256;7374// Extended clipboard pseudo-encoding formats75const extendedClipboardFormatText   = 1;76/*eslint-disable no-unused-vars */77const extendedClipboardFormatRtf    = 1 << 1;78const extendedClipboardFormatHtml   = 1 << 2;79const extendedClipboardFormatDib    = 1 << 3;80const extendedClipboardFormatFiles  = 1 << 4;81/*eslint-enable */8283// Extended clipboard pseudo-encoding actions84const extendedClipboardActionCaps    = 1 << 24;85const extendedClipboardActionRequest = 1 << 25;86const extendedClipboardActionPeek    = 1 << 26;87const extendedClipboardActionNotify  = 1 << 27;88const extendedClipboardActionProvide = 1 << 28;8990export default class RFB extends EventTargetMixin {91    constructor(target, urlOrChannel, options) {92        if (!target) {93            throw new Error("Must specify target");94        }95        if (!urlOrChannel) {96            throw new Error("Must specify URL, WebSocket or RTCDataChannel");97        }9899        // We rely on modern APIs which might not be available in an100        // insecure context101        if (!window.isSecureContext) {102            Log.Error("noVNC requires a secure context (TLS). Expect crashes!");103        }104105        super();106107        this._target = target;108109        if (typeof urlOrChannel === "string") {110            this._url = urlOrChannel;111        } else {112            this._url = null;113            this._rawChannel = urlOrChannel;114        }115116        // Connection details117        options = options || {};118        this._rfbCredentials = options.credentials || {};119        this._shared = 'shared' in options ? !!options.shared : true;120        this._repeaterID = options.repeaterID || '';121        this._wsProtocols = options.wsProtocols || [];122123        // Internal state124        this._rfbConnectionState = '';125        this._rfbInitState = '';126        this._rfbAuthScheme = -1;127        this._rfbCleanDisconnect = true;128        this._rfbRSAAESAuthenticationState = null;129130        // Server capabilities131        this._rfbVersion = 0;132        this._rfbMaxVersion = 3.8;133        this._rfbTightVNC = false;134        this._rfbVeNCryptState = 0;135        this._rfbXvpVer = 0;136137        this._fbWidth = 0;138        this._fbHeight = 0;139140        this._fbName = "";141142        this._capabilities = { power: false };143144        this._supportsFence = false;145146        this._supportsContinuousUpdates = false;147        this._enabledContinuousUpdates = false;148149        this._supportsSetDesktopSize = false;150        this._screenID = 0;151        this._screenFlags = 0;152        this._pendingRemoteResize = false;153        this._lastResize = 0;154155        this._qemuExtKeyEventSupported = false;156157        this._extendedPointerEventSupported = false;158159        this._clipboardText = null;160        this._clipboardServerCapabilitiesActions = {};161        this._clipboardServerCapabilitiesFormats = {};162163        // Internal objects164        this._sock = null;              // Websock object165        this._display = null;           // Display object166        this._flushing = false;         // Display flushing state167        this._keyboard = null;          // Keyboard input handler object168        this._gestures = null;          // Gesture input handler object169        this._resizeObserver = null;    // Resize observer object170171        // Timers172        this._disconnTimer = null;      // disconnection timer173        this._resizeTimeout = null;     // resize rate limiting174        this._mouseMoveTimer = null;175176        // Decoder states177        this._decoders = {};178179        this._FBU = {180            rects: 0,181            x: 0,182            y: 0,183            width: 0,184            height: 0,185            encoding: null,186        };187188        // Mouse state189        this._mousePos = {};190        this._mouseButtonMask = 0;191        this._mouseLastMoveTime = 0;192        this._viewportDragging = false;193        this._viewportDragPos = {};194        this._viewportHasMoved = false;195        this._accumulatedWheelDeltaX = 0;196        this._accumulatedWheelDeltaY = 0;197198        // Gesture state199        this._gestureLastTapTime = null;200        this._gestureFirstDoubleTapEv = null;201        this._gestureLastMagnitudeX = 0;202        this._gestureLastMagnitudeY = 0;203204        // Bound event handlers205        this._eventHandlers = {206            focusCanvas: this._focusCanvas.bind(this),207            handleResize: this._handleResize.bind(this),208            handleMouse: this._handleMouse.bind(this),209            handleWheel: this._handleWheel.bind(this),210            handleGesture: this._handleGesture.bind(this),211            handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),212            handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),213        };214215        // main setup216        Log.Debug(">> RFB.constructor");217218        // Create DOM elements219        this._screen = document.createElement('div');220        this._screen.style.display = 'flex';221        this._screen.style.width = '100%';222        this._screen.style.height = '100%';223        this._screen.style.overflow = 'auto';224        this._screen.style.background = DEFAULT_BACKGROUND;225        this._canvas = document.createElement('canvas');226        this._canvas.style.margin = 'auto';227        // Some browsers add an outline on focus228        this._canvas.style.outline = 'none';229        this._canvas.width = 0;230        this._canvas.height = 0;231        this._canvas.tabIndex = -1;232        this._screen.appendChild(this._canvas);233234        // Cursor235        this._cursor = new Cursor();236237        // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes238        // it. Result: no cursor at all until a window border or an edit field239        // is hit blindly. But there are also VNC servers that draw the cursor240        // in the framebuffer and don't send the empty local cursor. There is241        // no way to satisfy both sides.242        //243        // The spec is unclear on this "initial cursor" issue. Many other244        // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the245        // initial cursor instead.246        this._cursorImage = RFB.cursors.none;247248        // populate decoder array with objects249        this._decoders[encodings.encodingRaw] = new RawDecoder();250        this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();251        this._decoders[encodings.encodingRRE] = new RREDecoder();252        this._decoders[encodings.encodingHextile] = new HextileDecoder();253        this._decoders[encodings.encodingZlib] = new ZlibDecoder();254        this._decoders[encodings.encodingTight] = new TightDecoder();255        this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();256        this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();257        this._decoders[encodings.encodingJPEG] = new JPEGDecoder();258        this._decoders[encodings.encodingH264] = new H264Decoder();259260        // NB: nothing that needs explicit teardown should be done261        // before this point, since this can throw an exception262        try {263            this._display = new Display(this._canvas);264        } catch (exc) {265            Log.Error("Display exception: " + exc);266            throw exc;267        }268269        this._keyboard = new Keyboard(this._canvas);270        this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);271        this._remoteCapsLock = null; // Null indicates unknown or irrelevant272        this._remoteNumLock = null;273274        this._gestures = new GestureHandler();275276        this._sock = new Websock();277        this._sock.on('open', this._socketOpen.bind(this));278        this._sock.on('close', this._socketClose.bind(this));279        this._sock.on('message', this._handleMessage.bind(this));280        this._sock.on('error', this._socketError.bind(this));281282        this._expectedClientWidth = null;283        this._expectedClientHeight = null;284        this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);285286        // All prepared, kick off the connection287        this._updateConnectionState('connecting');288289        Log.Debug("<< RFB.constructor");290291        // ===== PROPERTIES =====292293        this.dragViewport = false;294        this.focusOnClick = true;295296        this._viewOnly = false;297        this._clipViewport = false;298        this._clippingViewport = false;299        this._scaleViewport = false;300        this._resizeSession = false;301302        this._showDotCursor = false;303        if (options.showDotCursor !== undefined) {304            Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");305            this._showDotCursor = options.showDotCursor;306        }307308        this._qualityLevel = 6;309        this._compressionLevel = 2;310    }311312    // ===== PROPERTIES =====313314    get viewOnly() { return this._viewOnly; }315    set viewOnly(viewOnly) {316        this._viewOnly = viewOnly;317318        if (this._rfbConnectionState === "connecting" ||319            this._rfbConnectionState === "connected") {320            if (viewOnly) {321                this._keyboard.ungrab();322            } else {323                this._keyboard.grab();324            }325        }326    }327328    get capabilities() { return this._capabilities; }329330    get clippingViewport() { return this._clippingViewport; }331    _setClippingViewport(on) {332        if (on === this._clippingViewport) {333            return;334        }335        this._clippingViewport = on;336        this.dispatchEvent(new CustomEvent("clippingviewport",337                                           { detail: this._clippingViewport }));338    }339340    get touchButton() { return 0; }341    set touchButton(button) { Log.Warn("Using old API!"); }342343    get clipViewport() { return this._clipViewport; }344    set clipViewport(viewport) {345        this._clipViewport = viewport;346        this._updateClip();347    }348349    get scaleViewport() { return this._scaleViewport; }350    set scaleViewport(scale) {351        this._scaleViewport = scale;352        // Scaling trumps clipping, so we may need to adjust353        // clipping when enabling or disabling scaling354        if (scale && this._clipViewport) {355            this._updateClip();356        }357        this._updateScale();358        if (!scale && this._clipViewport) {359            this._updateClip();360        }361    }362363    get resizeSession() { return this._resizeSession; }364    set resizeSession(resize) {365        this._resizeSession = resize;366        if (resize) {367            this._requestRemoteResize();368        }369    }370371    get showDotCursor() { return this._showDotCursor; }372    set showDotCursor(show) {373        this._showDotCursor = show;374        this._refreshCursor();375    }376377    get background() { return this._screen.style.background; }378    set background(cssValue) { this._screen.style.background = cssValue; }379380    get qualityLevel() {381        return this._qualityLevel;382    }383    set qualityLevel(qualityLevel) {384        if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {385            Log.Error("qualityLevel must be an integer between 0 and 9");386            return;387        }388389        if (this._qualityLevel === qualityLevel) {390            return;391        }392393        this._qualityLevel = qualityLevel;394395        if (this._rfbConnectionState === 'connected') {396            this._sendEncodings();397        }398    }399400    get compressionLevel() {401        return this._compressionLevel;402    }403    set compressionLevel(compressionLevel) {404        if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {405            Log.Error("compressionLevel must be an integer between 0 and 9");406            return;407        }408409        if (this._compressionLevel === compressionLevel) {410            return;411        }412413        this._compressionLevel = compressionLevel;414415        if (this._rfbConnectionState === 'connected') {416            this._sendEncodings();417        }418    }419420    // ===== PUBLIC METHODS =====421422    disconnect() {423        this._updateConnectionState('disconnecting');424        this._sock.off('error');425        this._sock.off('message');426        this._sock.off('open');427        if (this._rfbRSAAESAuthenticationState !== null) {428            this._rfbRSAAESAuthenticationState.disconnect();429        }430    }431432    approveServer() {433        if (this._rfbRSAAESAuthenticationState !== null) {434            this._rfbRSAAESAuthenticationState.approveServer();435        }436    }437438    sendCredentials(creds) {439        this._rfbCredentials = creds;440        this._resumeAuthentication();441    }442443    sendCtrlAltDel() {444        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }445        Log.Info("Sending Ctrl-Alt-Del");446447        this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);448        this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);449        this.sendKey(KeyTable.XK_Delete, "Delete", true);450        this.sendKey(KeyTable.XK_Delete, "Delete", false);451        this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);452        this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);453    }454455    machineShutdown() {456        this._xvpOp(1, 2);457    }458459    machineReboot() {460        this._xvpOp(1, 3);461    }462463    machineReset() {464        this._xvpOp(1, 4);465    }466467    // Send a key press. If 'down' is not specified then send a down key468    // followed by an up key.469    sendKey(keysym, code, down) {470        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }471472        if (down === undefined) {473            this.sendKey(keysym, code, true);474            this.sendKey(keysym, code, false);475            return;476        }477478        const scancode = XtScancode[code];479480        if (this._qemuExtKeyEventSupported && scancode) {481            // 0 is NoSymbol482            keysym = keysym || 0;483484            Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);485486            RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);487        } else {488            if (!keysym) {489                return;490            }491            Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);492            RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);493        }494    }495496    focus(options) {497        this._canvas.focus(options);498    }499500    blur() {501        this._canvas.blur();502    }503504    clipboardPasteFrom(text) {505        if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }506507        if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&508            this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {509510            this._clipboardText = text;511            RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);512        } else {513            let length, i;514            let data;515516            length = 0;517            // eslint-disable-next-line no-unused-vars518            for (let codePoint of text) {519                length++;520            }521522            data = new Uint8Array(length);523524            i = 0;525            for (let codePoint of text) {526                let code = codePoint.codePointAt(0);527528                /* Only ISO 8859-1 is supported */529                if (code > 0xff) {530                    code = 0x3f; // '?'531                }532533                data[i++] = code;534            }535536            RFB.messages.clientCutText(this._sock, data);537        }538    }539540    getImageData() {541        return this._display.getImageData();542    }543544    toDataURL(type, encoderOptions) {545        return this._display.toDataURL(type, encoderOptions);546    }547548    toBlob(callback, type, quality) {549        return this._display.toBlob(callback, type, quality);550    }551552    // ===== PRIVATE METHODS =====553554    _connect() {555        Log.Debug(">> RFB.connect");556557        if (this._url) {558            Log.Info(`connecting to ${this._url}`);559            this._sock.open(this._url, this._wsProtocols);560        } else {561            Log.Info(`attaching ${this._rawChannel} to Websock`);562            this._sock.attach(this._rawChannel);563564            if (this._sock.readyState === 'closed') {565                throw Error("Cannot use already closed WebSocket/RTCDataChannel");566            }567568            if (this._sock.readyState === 'open') {569                // FIXME: _socketOpen() can in theory call _fail(), which570                //        isn't allowed this early, but I'm not sure that can571                //        happen without a bug messing up our state variables572                this._socketOpen();573            }574        }575576        // Make our elements part of the page577        this._target.appendChild(this._screen);578579        this._gestures.attach(this._canvas);580581        this._cursor.attach(this._canvas);582        this._refreshCursor();583584        // Monitor size changes of the screen element585        this._resizeObserver.observe(this._screen);586587        // Always grab focus on some kind of click event588        this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);589        this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);590591        // Mouse events592        this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);593        this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);594        this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);595        // Prevent middle-click pasting (see handler for why we bind to document)596        this._canvas.addEventListener('click', this._eventHandlers.handleMouse);597        // preventDefault() on mousedown doesn't stop this event for some598        // reason so we have to explicitly block it599        this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);600601        // Wheel events602        this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);603604        // Gesture events605        this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);606        this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);607        this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);608609        Log.Debug("<< RFB.connect");610    }611612    _disconnect() {613        Log.Debug(">> RFB.disconnect");614        this._cursor.detach();615        this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);616        this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);617        this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);618        this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);619        this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);620        this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);621        this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);622        this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);623        this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);624        this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);625        this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);626        this._resizeObserver.disconnect();627        this._keyboard.ungrab();628        this._gestures.detach();629        this._sock.close();630        try {631            this._target.removeChild(this._screen);632        } catch (e) {633            if (e.name === 'NotFoundError') {634                // Some cases where the initial connection fails635                // can disconnect before the _screen is created636            } else {637                throw e;638            }639        }640        clearTimeout(this._resizeTimeout);641        clearTimeout(this._mouseMoveTimer);642        Log.Debug("<< RFB.disconnect");643    }644645    _socketOpen() {646        if ((this._rfbConnectionState === 'connecting') &&647            (this._rfbInitState === '')) {648            this._rfbInitState = 'ProtocolVersion';649            Log.Debug("Starting VNC handshake");650        } else {651            this._fail("Unexpected server connection while " +652                       this._rfbConnectionState);653        }654    }655656    _socketClose(e) {657        Log.Debug("WebSocket on-close event");658        let msg = "";659        if (e.code) {660            msg = "(code: " + e.code;661            if (e.reason) {662                msg += ", reason: " + e.reason;663            }664            msg += ")";665        }666        switch (this._rfbConnectionState) {667            case 'connecting':668                this._fail("Connection closed " + msg);669                break;670            case 'connected':671                // Handle disconnects that were initiated server-side672                this._updateConnectionState('disconnecting');673                this._updateConnectionState('disconnected');674                break;675            case 'disconnecting':676                // Normal disconnection path677                this._updateConnectionState('disconnected');678                break;679            case 'disconnected':680                this._fail("Unexpected server disconnect " +681                           "when already disconnected " + msg);682                break;683            default:684                this._fail("Unexpected server disconnect before connecting " +685                           msg);686                break;687        }688        this._sock.off('close');689        // Delete reference to raw channel to allow cleanup.690        this._rawChannel = null;691    }692693    _socketError(e) {694        Log.Warn("WebSocket on-error event");695    }696697    _focusCanvas(event) {698        if (!this.focusOnClick) {699            return;700        }701702        this.focus({ preventScroll: true });703    }704705    _setDesktopName(name) {706        this._fbName = name;707        this.dispatchEvent(new CustomEvent(708            "desktopname",709            { detail: { name: this._fbName } }));710    }711712    _saveExpectedClientSize() {713        this._expectedClientWidth = this._screen.clientWidth;714        this._expectedClientHeight = this._screen.clientHeight;715    }716717    _currentClientSize() {718        return [this._screen.clientWidth, this._screen.clientHeight];719    }720721    _clientHasExpectedSize() {722        const [currentWidth, currentHeight] = this._currentClientSize();723        return currentWidth == this._expectedClientWidth &&724            currentHeight == this._expectedClientHeight;725    }726727    // Handle browser window resizes728    _handleResize() {729        // Don't change anything if the client size is already as expected730        if (this._clientHasExpectedSize()) {731            return;732        }733        // If the window resized then our screen element might have734        // as well. Update the viewport dimensions.735        window.requestAnimationFrame(() => {736            this._updateClip();737            this._updateScale();738            this._saveExpectedClientSize();739        });740741        // Request changing the resolution of the remote display to742        // the size of the local browser viewport.743        this._requestRemoteResize();744    }745746    // Update state of clipping in Display object, and make sure the747    // configured viewport matches the current screen size748    _updateClip() {749        const curClip = this._display.clipViewport;750        let newClip = this._clipViewport;751752        if (this._scaleViewport) {753            // Disable viewport clipping if we are scaling754            newClip = false;755        }756757        if (curClip !== newClip) {758            this._display.clipViewport = newClip;759        }760761        if (newClip) {762            // When clipping is enabled, the screen is limited to763            // the size of the container.764            const size = this._screenSize();765            this._display.viewportChangeSize(size.w, size.h);766            this._fixScrollbars();767            this._setClippingViewport(size.w < this._display.width ||768                                      size.h < this._display.height);769        } else {770            this._setClippingViewport(false);771        }772773        // When changing clipping we might show or hide scrollbars.774        // This causes the expected client dimensions to change.775        if (curClip !== newClip) {776            this._saveExpectedClientSize();777        }778    }779780    _updateScale() {781        if (!this._scaleViewport) {782            this._display.scale = 1.0;783        } else {784            const size = this._screenSize();785            this._display.autoscale(size.w, size.h);786        }787        this._fixScrollbars();788    }789790    // Requests a change of remote desktop size. This message is an extension791    // and may only be sent if we have received an ExtendedDesktopSize message792    _requestRemoteResize() {793        if (!this._resizeSession) {794            return;795        }796        if (this._viewOnly) {797            return;798        }799        if (!this._supportsSetDesktopSize) {800            return;801        }802803        // Rate limit to one pending resize at a time804        if (this._pendingRemoteResize) {805            return;806        }807808        // And no more than once every 100ms809        if ((Date.now() - this._lastResize) < 100) {810            clearTimeout(this._resizeTimeout);811            this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this),812                                             100 - (Date.now() - this._lastResize));813            return;814        }815        this._resizeTimeout = null;816817        const size = this._screenSize();818819        // Do we actually change anything?820        if (size.w === this._fbWidth && size.h === this._fbHeight) {821            return;822        }823824        this._pendingRemoteResize = true;825        this._lastResize = Date.now();826        RFB.messages.setDesktopSize(this._sock,827                                    Math.floor(size.w), Math.floor(size.h),828                                    this._screenID, this._screenFlags);829830        Log.Debug('Requested new desktop size: ' +831                   size.w + 'x' + size.h);832    }833834    // Gets the the size of the available screen835    _screenSize() {836        let r = this._screen.getBoundingClientRect();837        return { w: r.width, h: r.height };838    }839840    _fixScrollbars() {841        // This is a hack because Safari on macOS screws up the calculation842        // for when scrollbars are needed. We get scrollbars when making the843        // browser smaller, despite remote resize being enabled. So to fix it844        // we temporarily toggle them off and on.845        const orig = this._screen.style.overflow;846        this._screen.style.overflow = 'hidden';847        // Force Safari to recalculate the layout by asking for848        // an element's dimensions849        this._screen.getBoundingClientRect();850        this._screen.style.overflow = orig;851    }852853    /*854     * Connection states:855     *   connecting856     *   connected857     *   disconnecting858     *   disconnected - permanent state859     */860    _updateConnectionState(state) {861        const oldstate = this._rfbConnectionState;862863        if (state === oldstate) {864            Log.Debug("Already in state '" + state + "', ignoring");865            return;866        }867868        // The 'disconnected' state is permanent for each RFB object869        if (oldstate === 'disconnected') {870            Log.Error("Tried changing state of a disconnected RFB object");871            return;872        }873874        // Ensure proper transitions before doing anything875        switch (state) {876            case 'connected':877                if (oldstate !== 'connecting') {878                    Log.Error("Bad transition to connected state, " +879                               "previous connection state: " + oldstate);880                    return;881                }882                break;883884            case 'disconnected':885                if (oldstate !== 'disconnecting') {886                    Log.Error("Bad transition to disconnected state, " +887                               "previous connection state: " + oldstate);888                    return;889                }890                break;891892            case 'connecting':893                if (oldstate !== '') {894                    Log.Error("Bad transition to connecting state, " +895                               "previous connection state: " + oldstate);896                    return;897                }898                break;899900            case 'disconnecting':901                if (oldstate !== 'connected' && oldstate !== 'connecting') {902                    Log.Error("Bad transition to disconnecting state, " +903                               "previous connection state: " + oldstate);904                    return;905                }906                break;907908            default:909                Log.Error("Unknown connection state: " + state);910                return;911        }912913        // State change actions914915        this._rfbConnectionState = state;916917        Log.Debug("New state '" + state + "', was '" + oldstate + "'.");918919        if (this._disconnTimer && state !== 'disconnecting') {920            Log.Debug("Clearing disconnect timer");921            clearTimeout(this._disconnTimer);922            this._disconnTimer = null;923924            // make sure we don't get a double event925            this._sock.off('close');926        }927928        switch (state) {929            case 'connecting':930                this._connect();931                break;932933            case 'connected':934                this.dispatchEvent(new CustomEvent("connect", { detail: {} }));935                break;936937            case 'disconnecting':938                this._disconnect();939940                this._disconnTimer = setTimeout(() => {941                    Log.Error("Disconnection timed out.");942                    this._updateConnectionState('disconnected');943                }, DISCONNECT_TIMEOUT * 1000);944                break;945946            case 'disconnected':947                this.dispatchEvent(new CustomEvent(948                    "disconnect", { detail:949                                    { clean: this._rfbCleanDisconnect } }));950                break;951        }952    }953954    /* Print errors and disconnect955     *956     * The parameter 'details' is used for information that957     * should be logged but not sent to the user interface.958     */959    _fail(details) {960        switch (this._rfbConnectionState) {961            case 'disconnecting':962                Log.Error("Failed when disconnecting: " + details);963                break;964            case 'connected':965                Log.Error("Failed while connected: " + details);966                break;967            case 'connecting':968                Log.Error("Failed when connecting: " + details);969                break;970            default:971                Log.Error("RFB failure: " + details);972                break;973        }974        this._rfbCleanDisconnect = false; //This is sent to the UI975976        // Transition to disconnected without waiting for socket to close977        this._updateConnectionState('disconnecting');978        this._updateConnectionState('disconnected');979980        return false;981    }982983    _setCapability(cap, val) {984        this._capabilities[cap] = val;985        this.dispatchEvent(new CustomEvent("capabilities",986                                           { detail: { capabilities: this._capabilities } }));987    }988989    _handleMessage() {990        if (this._sock.rQwait("message", 1)) {991            Log.Warn("handleMessage called on an empty receive queue");992            return;993        }994995        switch (this._rfbConnectionState) {996            case 'disconnected':997                Log.Error("Got data while disconnected");998                break;999            case 'connected':1000                while (true) {1001                    if (this._flushing) {1002                        break;1003                    }1004                    if (!this._normalMsg()) {1005                        break;1006                    }1007                    if (this._sock.rQwait("message", 1)) {1008                        break;1009                    }1010                }1011                break;1012            case 'connecting':1013                while (this._rfbConnectionState === 'connecting') {1014                    if (!this._initMsg()) {1015                        break;1016                    }1017                }1018                break;1019            default:1020                Log.Error("Got data while in an invalid state");1021                break;1022        }1023    }10241025    _handleKeyEvent(keysym, code, down, numlock, capslock) {1026        // If remote state of capslock is known, and it doesn't match the local led state of1027        // the keyboard, we send a capslock keypress first to bring it into sync.1028        // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync1029        // we clear the remote state so that we don't send duplicate or spurious fixes,1030        // since it may take some time to receive the new remote CapsLock state.1031        if (code == 'CapsLock' && down) {1032            this._remoteCapsLock = null;1033        }1034        if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {1035            Log.Debug("Fixing remote caps lock");10361037            this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);1038            this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);1039            // We clear the remote capsLock state when we do this to prevent issues with doing this twice1040            // before we receive an update of the the remote state.1041            this._remoteCapsLock = null;1042        }10431044        // Logic for numlock is exactly the same.1045        if (code == 'NumLock' && down) {1046            this._remoteNumLock = null;1047        }1048        if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {1049            Log.Debug("Fixing remote num lock");1050            this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);1051            this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);1052            this._remoteNumLock = null;1053        }1054        this.sendKey(keysym, code, down);1055    }10561057    static _convertButtonMask(buttons) {1058        /* The bits in MouseEvent.buttons property correspond1059         * to the following mouse buttons:1060         *     0: Left1061         *     1: Right1062         *     2: Middle1063         *     3: Back1064         *     4: Forward1065         *1066         * These bits needs to be converted to what they are defined as1067         * in the RFB protocol.1068         */10691070        const buttonMaskMap = {1071            0: 1 << 0, // Left1072            1: 1 << 2, // Right1073            2: 1 << 1, // Middle1074            3: 1 << 7, // Back1075            4: 1 << 8, // Forward1076        };10771078        let bmask = 0;1079        for (let i = 0; i < 5; i++) {1080            if (buttons & (1 << i)) {1081                bmask |= buttonMaskMap[i];1082            }1083        }1084        return bmask;1085    }10861087    _handleMouse(ev) {1088        /*1089         * We don't check connection status or viewOnly here as the1090         * mouse events might be used to control the viewport1091         */10921093        if (ev.type === 'click') {1094            /*1095             * Note: This is only needed for the 'click' event as it fails1096             *       to fire properly for the target element so we have1097             *       to listen on the document element instead.1098             */1099            if (ev.target !== this._canvas) {1100                return;1101            }1102        }11031104        // FIXME: if we're in view-only and not dragging,1105        //        should we stop events?1106        ev.stopPropagation();1107        ev.preventDefault();11081109        if ((ev.type === 'click') || (ev.type === 'contextmenu')) {1110            return;1111        }11121113        let pos = clientToElement(ev.clientX, ev.clientY,1114                                  this._canvas);11151116        let bmask = RFB._convertButtonMask(ev.buttons);11171118        let down = ev.type == 'mousedown';1119        switch (ev.type) {1120            case 'mousedown':1121            case 'mouseup':1122                if (this.dragViewport) {1123                    if (down && !this._viewportDragging) {1124                        this._viewportDragging = true;1125                        this._viewportDragPos = {'x': pos.x, 'y': pos.y};1126                        this._viewportHasMoved = false;11271128                        this._flushMouseMoveTimer(pos.x, pos.y);11291130                        // Skip sending mouse events, instead save the current1131                        // mouse mask so we can send it later.1132                        this._mouseButtonMask = bmask;1133                        break;1134                    } else {1135                        this._viewportDragging = false;11361137                        // If we actually performed a drag then we are done1138                        // here and should not send any mouse events1139                        if (this._viewportHasMoved) {1140                            this._mouseButtonMask = bmask;1141                            break;1142                        }1143                        // Otherwise we treat this as a mouse click event.1144                        // Send the previously saved button mask, followed1145                        // by the current button mask at the end of this1146                        // function.1147                        this._sendMouse(pos.x, pos.y,  this._mouseButtonMask);1148                    }1149                }1150                if (down) {1151                    setCapture(this._canvas);1152                }1153                this._handleMouseButton(pos.x, pos.y, bmask);1154                break;1155            case 'mousemove':1156                if (this._viewportDragging) {1157                    const deltaX = this._viewportDragPos.x - pos.x;1158                    const deltaY = this._viewportDragPos.y - pos.y;11591160                    if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||1161                                                   Math.abs(deltaY) > dragThreshold)) {1162                        this._viewportHasMoved = true;11631164                        this._viewportDragPos = {'x': pos.x, 'y': pos.y};1165                        this._display.viewportChangePos(deltaX, deltaY);1166                    }11671168                    // Skip sending mouse events1169                    break;1170                }1171                this._handleMouseMove(pos.x, pos.y);1172                break;1173        }1174    }11751176    _handleMouseButton(x, y, bmask) {1177        // Flush waiting move event first1178        this._flushMouseMoveTimer(x, y);11791180        this._mouseButtonMask = bmask;1181        this._sendMouse(x, y, this._mouseButtonMask);1182    }11831184    _handleMouseMove(x, y) {1185        this._mousePos = { 'x': x, 'y': y };11861187        // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms1188        if (this._mouseMoveTimer == null) {11891190            const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;1191            if (timeSinceLastMove > MOUSE_MOVE_DELAY) {1192                this._sendMouse(x, y, this._mouseButtonMask);1193                this._mouseLastMoveTime = Date.now();1194            } else {1195                // Too soon since the latest move, wait the remaining time1196                this._mouseMoveTimer = setTimeout(() => {1197                    this._handleDelayedMouseMove();1198                }, MOUSE_MOVE_DELAY - timeSinceLastMove);1199            }1200        }1201    }12021203    _handleDelayedMouseMove() {1204        this._mouseMoveTimer = null;1205        this._sendMouse(this._mousePos.x, this._mousePos.y,1206                        this._mouseButtonMask);1207        this._mouseLastMoveTime = Date.now();1208    }12091210    _sendMouse(x, y, mask) {1211        if (this._rfbConnectionState !== 'connected') { return; }1212        if (this._viewOnly) { return; } // View only, skip mouse events12131214        // Highest bit in mask is never sent to the server1215        if (mask & 0x8000) {1216            throw new Error("Illegal mouse button mask (mask: " + mask + ")");1217        }12181219        let extendedMouseButtons = mask & 0x7f80;12201221        if (this._extendedPointerEventSupported && extendedMouseButtons) {1222            RFB.messages.extendedPointerEvent(this._sock, this._display.absX(x),1223                                              this._display.absY(y), mask);1224        } else {1225            RFB.messages.pointerEvent(this._sock, this._display.absX(x),1226                                      this._display.absY(y), mask);1227        }1228    }12291230    _handleWheel(ev) {1231        if (this._rfbConnectionState !== 'connected') { return; }1232        if (this._viewOnly) { return; } // View only, skip mouse events12331234        ev.stopPropagation();1235        ev.preventDefault();12361237        let pos = clientToElement(ev.clientX, ev.clientY,1238                                  this._canvas);12391240        let bmask = RFB._convertButtonMask(ev.buttons);1241        let dX = ev.deltaX;1242        let dY = ev.deltaY;12431244        // Pixel units unless it's non-zero.1245        // Note that if deltamode is line or page won't matter since we aren't1246        // sending the mouse wheel delta to the server anyway.1247        // The difference between pixel and line can be important however since1248        // we have a threshold that can be smaller than the line height.1249        if (ev.deltaMode !== 0) {1250            dX *= WHEEL_LINE_HEIGHT;1251            dY *= WHEEL_LINE_HEIGHT;1252        }12531254        // Mouse wheel events are sent in steps over VNC. This means that the VNC1255        // protocol can't handle a wheel event with specific distance or speed.1256        // Therefor, if we get a lot of small mouse wheel events we combine them.1257        this._accumulatedWheelDeltaX += dX;1258        this._accumulatedWheelDeltaY += dY;125912601261        // Generate a mouse wheel step event when the accumulated delta1262        // for one of the axes is large enough.1263        if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {1264            if (this._accumulatedWheelDeltaX < 0) {1265                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 5);1266                this._handleMouseButton(pos.x, pos.y, bmask);1267            } else if (this._accumulatedWheelDeltaX > 0) {1268                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 6);1269                this._handleMouseButton(pos.x, pos.y, bmask);1270            }12711272            this._accumulatedWheelDeltaX = 0;1273        }1274        if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {1275            if (this._accumulatedWheelDeltaY < 0) {1276                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 3);1277                this._handleMouseButton(pos.x, pos.y, bmask);1278            } else if (this._accumulatedWheelDeltaY > 0) {1279                this._handleMouseButton(pos.x, pos.y, bmask | 1 << 4);1280                this._handleMouseButton(pos.x, pos.y, bmask);1281            }12821283            this._accumulatedWheelDeltaY = 0;1284        }1285    }12861287    _fakeMouseMove(ev, elementX, elementY) {1288        this._handleMouseMove(elementX, elementY);1289        this._cursor.move(ev.detail.clientX, ev.detail.clientY);1290    }12911292    _handleTapEvent(ev, bmask) {1293        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,1294                                  this._canvas);12951296        // If the user quickly taps multiple times we assume they meant to1297        // hit the same spot, so slightly adjust coordinates12981299        if ((this._gestureLastTapTime !== null) &&1300            ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&1301            (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {1302            let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;1303            let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;1304            let distance = Math.hypot(dx, dy);13051306            if (distance < DOUBLE_TAP_THRESHOLD) {1307                pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,1308                                      this._gestureFirstDoubleTapEv.detail.clientY,1309                                      this._canvas);1310            } else {1311                this._gestureFirstDoubleTapEv = ev;1312            }1313        } else {1314            this._gestureFirstDoubleTapEv = ev;1315        }1316        this._gestureLastTapTime = Date.now();13171318        this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);1319        this._handleMouseButton(pos.x, pos.y, bmask);1320        this._handleMouseButton(pos.x, pos.y, 0x0);1321    }13221323    _handleGesture(ev) {1324        let magnitude;13251326        let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,1327                                  this._canvas);1328        switch (ev.type) {1329            case 'gesturestart':1330                switch (ev.detail.type) {1331                    case 'onetap':1332                        this._handleTapEvent(ev, 0x1);1333                        break;1334                    case 'twotap':1335                        this._handleTapEvent(ev, 0x4);1336                        break;1337                    case 'threetap':1338                        this._handleTapEvent(ev, 0x2);1339                        break;1340                    case 'drag':1341                        if (this.dragViewport) {1342                            this._viewportHasMoved = false;1343                            this._viewportDragging = true;1344                            this._viewportDragPos = {'x': pos.x, 'y': pos.y};1345                        } else {1346                            this._fakeMouseMove(ev, pos.x, pos.y);1347                            this._handleMouseButton(pos.x, pos.y, 0x1);1348                        }1349                        break;1350                    case 'longpress':1351                        if (this.dragViewport) {1352                            // If dragViewport is true, we need to wait to see1353                            // if we have dragged outside the threshold before1354                            // sending any events to the server.1355                            this._viewportHasMoved = false;1356                            this._viewportDragPos = {'x': pos.x, 'y': pos.y};1357                        } else {1358                            this._fakeMouseMove(ev, pos.x, pos.y);1359                            this._handleMouseButton(pos.x, pos.y, 0x4);1360                        }1361                        break;1362                    case 'twodrag':1363                        this._gestureLastMagnitudeX = ev.detail.magnitudeX;1364                        this._gestureLastMagnitudeY = ev.detail.magnitudeY;1365                        this._fakeMouseMove(ev, pos.x, pos.y);1366                        break;1367                    case 'pinch':1368                        this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,1369                                                                 ev.detail.magnitudeY);1370                        this._fakeMouseMove(ev, pos.x, pos.y);1371                        break;1372                }1373                break;13741375            case 'gesturemove':1376                switch (ev.detail.type) {1377                    case 'onetap':1378                    case 'twotap':1379                    case 'threetap':1380                        break;1381                    case 'drag':1382                    case 'longpress':1383                        if (this.dragViewport) {1384                            this._viewportDragging = true;1385                            const deltaX = this._viewportDragPos.x - pos.x;1386                            const deltaY = this._viewportDragPos.y - pos.y;13871388                            if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||1389                                                           Math.abs(deltaY) > dragThreshold)) {1390                                this._viewportHasMoved = true;13911392                                this._viewportDragPos = {'x': pos.x, 'y': pos.y};1393                                this._display.viewportChangePos(deltaX, deltaY);1394                            }1395                        } else {1396                            this._fakeMouseMove(ev, pos.x, pos.y);1397                        }1398                        break;1399                    case 'twodrag':1400                        // Always scroll in the same position.1401                        // We don't know if the mouse was moved so we need to move it1402                        // every update.1403                        this._fakeMouseMove(ev, pos.x, pos.y);1404                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {1405                            this._handleMouseButton(pos.x, pos.y, 0x8);1406                            this._handleMouseButton(pos.x, pos.y, 0x0);1407                            this._gestureLastMagnitudeY += GESTURE_SCRLSENS;1408                        }1409                        while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {1410                            this._handleMouseButton(pos.x, pos.y, 0x10);1411                            this._handleMouseButton(pos.x, pos.y, 0x0);1412                            this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;1413                        }1414                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {1415                            this._handleMouseButton(pos.x, pos.y, 0x20);1416                            this._handleMouseButton(pos.x, pos.y, 0x0);1417                            this._gestureLastMagnitudeX += GESTURE_SCRLSENS;1418                        }1419                        while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {1420                            this._handleMouseButton(pos.x, pos.y, 0x40);1421                            this._handleMouseButton(pos.x, pos.y, 0x0);1422                            this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;1423                        }1424                        break;1425                    case 'pinch':1426                        // Always scroll in the same position.1427                        // We don't know if the mouse was moved so we need to move it1428                        // every update.1429                        this._fakeMouseMove(ev, pos.x, pos.y);1430                        magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);1431                        if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {1432                            this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);1433                            while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {1434                                this._handleMouseButton(pos.x, pos.y, 0x8);1435                                this._handleMouseButton(pos.x, pos.y, 0x0);1436                                this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;1437                            }1438                            while ((magnitude -  this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {1439                                this._handleMouseButton(pos.x, pos.y, 0x10);1440                                this._handleMouseButton(pos.x, pos.y, 0x0);1441                                this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;1442                            }1443                        }1444                        this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);1445                        break;1446                }1447                break;14481449            case 'gestureend':1450                switch (ev.detail.type) {1451                    case 'onetap':1452                    case 'twotap':1453                    case 'threetap':1454                    case 'pinch':1455                    case 'twodrag':1456                        break;1457                    case 'drag':1458                        if (this.dragViewport) {1459                            this._viewportDragging = false;1460                        } else {1461                            this._fakeMouseMove(ev, pos.x, pos.y);1462                            this._handleMouseButton(pos.x, pos.y, 0x0);1463                        }1464                        break;1465                    case 'longpress':1466                        if (this._viewportHasMoved) {1467                            // We don't want to send any events if we have moved1468                            // our viewport1469                            break;1470                        }14711472                        if (this.dragViewport && !this._viewportHasMoved) {1473                            this._fakeMouseMove(ev, pos.x, pos.y);1474                            // If dragViewport is true, we need to wait to see1475                            // if we have dragged outside the threshold before1476                            // sending any events to the server.1477                            this._handleMouseButton(pos.x, pos.y, 0x4);1478                            this._handleMouseButton(pos.x, pos.y, 0x0);1479                            this._viewportDragging = false;1480                        } else {1481                            this._fakeMouseMove(ev, pos.x, pos.y);1482                            this._handleMouseButton(pos.x, pos.y, 0x0);1483                        }1484                        break;1485                }1486                break;1487        }1488    }14891490    _flushMouseMoveTimer(x, y) {1491        if (this._mouseMoveTimer !== null) {1492            clearTimeout(this._mouseMoveTimer);1493            this._mouseMoveTimer = null;1494            this._sendMouse(x, y, this._mouseButtonMask);1495        }1496    }14971498    // Message handlers14991500    _negotiateProtocolVersion() {1501        if (this._sock.rQwait("version", 12)) {1502            return false;1503        }15041505        const sversion = this._sock.rQshiftStr(12).substr(4, 7);1506        Log.Info("Server ProtocolVersion: " + sversion);1507        let isRepeater = 0;1508        switch (sversion) {1509            case "000.000":  // UltraVNC repeater1510                isRepeater = 1;1511                break;1512            case "003.003":1513            case "003.006":  // UltraVNC1514                this._rfbVersion = 3.3;1515                break;1516            case "003.007":1517                this._rfbVersion = 3.7;1518                break;1519            case "003.008":1520            case "003.889":  // Apple Remote Desktop1521            case "004.000":  // Intel AMT KVM1522            case "004.001":  // RealVNC 4.61523            case "005.000":  // RealVNC 5.31524                this._rfbVersion = 3.8;1525                break;1526            default:1527                return this._fail("Invalid server version " + sversion);1528        }15291530        if (isRepeater) {1531            let repeaterID = "ID:" + this._repeaterID;1532            while (repeaterID.length < 250) {1533                repeaterID += "\0";1534            }1535            this._sock.sQpushString(repeaterID);1536            this._sock.flush();1537            return true;1538        }15391540        if (this._rfbVersion > this._rfbMaxVersion) {1541            this._rfbVersion = this._rfbMaxVersion;1542        }15431544        const cversion = "00" + parseInt(this._rfbVersion, 10) +1545                       ".00" + ((this._rfbVersion * 10) % 10);1546        this._sock.sQpushString("RFB " + cversion + "\n");1547        this._sock.flush();1548        Log.Debug('Sent ProtocolVersion: ' + cversion);15491550        this._rfbInitState = 'Security';1551    }15521553    _isSupportedSecurityType(type) {1554        const clientTypes = [1555            securityTypeNone,1556            securityTypeVNCAuth,1557            securityTypeRA2ne,1558            securityTypeTight,1559            securityTypeVeNCrypt,1560            securityTypeXVP,1561            securityTypeARD,1562            securityTypeMSLogonII,1563            securityTypePlain,1564        ];15651566        return clientTypes.includes(type);1567    }15681569    _negotiateSecurity() {1570        if (this._rfbVersion >= 3.7) {1571            // Server sends supported list, client decides1572            const numTypes = this._sock.rQshift8();1573            if (this._sock.rQwait("security type", numTypes, 1)) { return false; }15741575            if (numTypes === 0) {1576                this._rfbInitState = "SecurityReason";1577                this._securityContext = "no security types";1578                this._securityStatus = 1;1579                return true;1580            }15811582            const types = this._sock.rQshiftBytes(numTypes);1583            Log.Debug("Server security types: " + types);15841585            // Look for a matching security type in the order that the1586            // server prefers1587            this._rfbAuthScheme = -1;1588            for (let type of types) {1589                if (this._isSupportedSecurityType(type)) {1590                    this._rfbAuthScheme = type;1591                    break;1592                }1593            }15941595            if (this._rfbAuthScheme === -1) {1596                return this._fail("Unsupported security types (types: " + types + ")");1597            }15981599            this._sock.sQpush8(this._rfbAuthScheme);1600            this._sock.flush();1601        } else {1602            // Server decides1603            if (this._sock.rQwait("security scheme", 4)) { return false; }1604            this._rfbAuthScheme = this._sock.rQshift32();16051606            if (this._rfbAuthScheme == 0) {1607                this._rfbInitState = "SecurityReason";1608                this._securityContext = "authentication scheme";1609                this._securityStatus = 1;1610                return true;1611            }1612        }16131614        this._rfbInitState = 'Authentication';1615        Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);16161617        return true;1618    }16191620    _handleSecurityReason() {1621        if (this._sock.rQwait("reason length", 4)) {1622            return false;1623        }1624        const strlen = this._sock.rQshift32();1625        let reason = "";16261627        if (strlen > 0) {1628            if (this._sock.rQwait("reason", strlen, 4)) { return false; }1629            reason = this._sock.rQshiftStr(strlen);1630        }16311632        if (reason !== "") {1633            this.dispatchEvent(new CustomEvent(1634                "securityfailure",1635                { detail: { status: this._securityStatus,1636                            reason: reason } }));16371638            return this._fail("Security negotiation failed on " +1639                              this._securityContext +1640                              " (reason: " + reason + ")");1641        } else {1642            this.dispatchEvent(new CustomEvent(1643                "securityfailure",1644                { detail: { status: this._securityStatus } }));16451646            return this._fail("Security negotiation failed on " +1647                              this._securityContext);1648        }1649    }16501651    // authentication1652    _negotiateXvpAuth() {1653        if (this._rfbCredentials.username === undefined ||1654            this._rfbCredentials.password === undefined ||1655            this._rfbCredentials.target === undefined) {1656            this.dispatchEvent(new CustomEvent(1657                "credentialsrequired",1658                { detail: { types: ["username", "password", "target"] } }));1659            return false;1660        }16611662        this._sock.sQpush8(this._rfbCredentials.username.length);1663        this._sock.sQpush8(this._rfbCredentials.target.length);1664        this._sock.sQpushString(this._rfbCredentials.username);1665        this._sock.sQpushString(this._rfbCredentials.target);16661667        this._sock.flush();16681669        this._rfbAuthScheme = securityTypeVNCAuth;16701671        return this._negotiateAuthentication();1672    }16731674    // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype1675    _negotiateVeNCryptAuth() {16761677        // waiting for VeNCrypt version1678        if (this._rfbVeNCryptState == 0) {1679            if (this._sock.rQwait("vencrypt version", 2)) { return false; }16801681            const major = this._sock.rQshift8();1682            const minor = this._sock.rQshift8();16831684            if (!(major == 0 && minor == 2)) {1685                return this._fail("Unsupported VeNCrypt version " + major + "." + minor);1686            }16871688            this._sock.sQpush8(0);1689            this._sock.sQpush8(2);1690            this._sock.flush();1691            this._rfbVeNCryptState = 1;1692        }16931694        // waiting for ACK1695        if (this._rfbVeNCryptState == 1) {1696            if (this._sock.rQwait("vencrypt ack", 1)) { return false; }16971698            const res = this._sock.rQshift8();16991700            if (res != 0) {1701                return this._fail("VeNCrypt failure " + res);1702            }17031704            this._rfbVeNCryptState = 2;1705        }1706        // must fall through here (i.e. no "else if"), beacause we may have already received1707        // the subtypes length and won't be called again17081709        if (this._rfbVeNCryptState == 2) { // waiting for subtypes length1710            if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; }17111712            const subtypesLength = this._sock.rQshift8();1713            if (subtypesLength < 1) {1714                return this._fail("VeNCrypt subtypes empty");1715            }17161717            this._rfbVeNCryptSubtypesLength = subtypesLength;1718            this._rfbVeNCryptState = 3;1719        }17201721        // waiting for subtypes list1722        if (this._rfbVeNCryptState == 3) {1723            if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }17241725            const subtypes = [];1726            for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {1727                subtypes.push(this._sock.rQshift32());1728            }17291730            // Look for a matching security type in the order that the1731            // server prefers1732            this._rfbAuthScheme = -1;1733            for (let type of subtypes) {1734                // Avoid getting in to a loop1735                if (type === securityTypeVeNCrypt) {1736                    continue;1737                }17381739                if (this._isSupportedSecurityType(type)) {1740                    this._rfbAuthScheme = type;1741                    break;1742                }1743            }17441745            if (this._rfbAuthScheme === -1) {1746                return this._fail("Unsupported security types (types: " + subtypes + ")");1747            }17481749            this._sock.sQpush32(this._rfbAuthScheme);1750            this._sock.flush();17511752            this._rfbVeNCryptState = 4;1753            return true;1754        }1755    }17561757    _negotiatePlainAuth() {1758        if (this._rfbCredentials.username === undefined ||1759            this._rfbCredentials.password === undefined) {1760            this.dispatchEvent(new CustomEvent(1761                "credentialsrequired",1762                { detail: { types: ["username", "password"] } }));1763            return false;1764        }17651766        const user = encodeUTF8(this._rfbCredentials.username);1767        const pass = encodeUTF8(this._rfbCredentials.password);17681769        this._sock.sQpush32(user.length);1770        this._sock.sQpush32(pass.length);1771        this._sock.sQpushString(user);1772        this._sock.sQpushString(pass);1773        this._sock.flush();17741775        this._rfbInitState = "SecurityResult";1776        return true;1777    }17781779    _negotiateStdVNCAuth() {1780        if (this._sock.rQwait("auth challenge", 16)) { return false; }17811782        if (this._rfbCredentials.password === undefined) {1783            this.dispatchEvent(new CustomEvent(1784                "credentialsrequired",1785                { detail: { types: ["password"] } }));1786            return false;1787        }17881789        // TODO(directxman12): make genDES not require an Array1790        const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));1791        const response = RFB.genDES(this._rfbCredentials.password, challenge);1792        this._sock.sQpushBytes(response);1793        this._sock.flush();1794        this._rfbInitState = "SecurityResult";1795        return true;1796    }17971798    _negotiateARDAuth() {17991800        if (this._rfbCredentials.username === undefined ||1801            this._rfbCredentials.password === undefined) {1802            this.dispatchEvent(new CustomEvent(1803                "credentialsrequired",1804                { detail: { types: ["username", "password"] } }));1805            return false;1806        }18071808        if (this._rfbCredentials.ardPublicKey != undefined &&1809            this._rfbCredentials.ardCredentials != undefined) {1810            // if the async web crypto is done return the results1811            this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);1812            this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);1813            this._sock.flush();1814            this._rfbCredentials.ardCredentials = null;1815            this._rfbCredentials.ardPublicKey = null;1816            this._rfbInitState = "SecurityResult";1817            return true;1818        }18191820        if (this._sock.rQwait("read ard", 4)) { return false; }18211822        let generator = this._sock.rQshiftBytes(2);   // DH base generator value18231824        let keyLength = this._sock.rQshift16();18251826        if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }18271828        // read the server values1829        let prime = this._sock.rQshiftBytes(keyLength);  // predetermined prime modulus1830        let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key18311832        let clientKey = legacyCrypto.generateKey(1833            { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);1834        this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);18351836        return false;1837    }18381839    async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {1840        const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);1841        const sharedKey = legacyCrypto.deriveBits(1842            { name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);18431844        const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);1845        const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);18461847        const credentials = window.crypto.getRandomValues(new Uint8Array(128));1848        for (let i = 0; i < username.length; i++) {1849            credentials[i] = username.charCodeAt(i);1850        }1851        credentials[username.length] = 0;1852        for (let i = 0; i < password.length; i++) {1853            credentials[64 + i] = password.charCodeAt(i);1854        }1855        credentials[64 + password.length] = 0;18561857        const key = await legacyCrypto.digest("MD5", sharedKey);1858        const cipher = await legacyCrypto.importKey(1859            "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);1860        const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);18611862        this._rfbCredentials.ardCredentials = encrypted;1863        this._rfbCredentials.ardPublicKey = clientPublicKey;18641865        this._resumeAuthentication();1866    }18671868    _negotiateTightUnixAuth() {1869        if (this._rfbCredentials.username === undefined ||1870            this._rfbCredentials.password === undefined) {1871            this.dispatchEvent(new CustomEvent(1872                "credentialsrequired",1873                { detail: { types: ["username", "password"] } }));1874            return false;1875        }18761877        this._sock.sQpush32(this._rfbCredentials.username.length);1878        this._sock.sQpush32(this._rfbCredentials.password.length);1879        this._sock.sQpushString(this._rfbCredentials.username);1880        this._sock.sQpushString(this._rfbCredentials.password);1881        this._sock.flush();18821883        this._rfbInitState = "SecurityResult";1884        return true;1885    }18861887    _negotiateTightTunnels(numTunnels) {1888        const clientSupportedTunnelTypes = {1889            0: { vendor: 'TGHT', signature: 'NOTUNNEL' }1890        };1891        const serverSupportedTunnelTypes = {};1892        // receive tunnel capabilities1893        for (let i = 0; i < numTunnels; i++) {1894            const capCode = this._sock.rQshift32();1895            const capVendor = this._sock.rQshiftStr(4);1896            const capSignature = this._sock.rQshiftStr(8);1897            serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };1898        }18991900        Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);19011902        // Siemens touch panels have a VNC server that supports NOTUNNEL,1903        // but forgets to advertise it. Try to detect such servers by1904        // looking for their custom tunnel type.1905        if (serverSupportedTunnelTypes[1] &&1906            (serverSupportedTunnelTypes[1].vendor === "SICR") &&1907            (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {1908            Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");1909            serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };1910        }19111912        // choose the notunnel type1913        if (serverSupportedTunnelTypes[0]) {1914            if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||1915                serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {1916                return this._fail("Client's tunnel type had the incorrect " +1917                                  "vendor or signature");1918            }1919            Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);1920            this._sock.sQpush32(0); // use NOTUNNEL1921            this._sock.flush();1922            return false; // wait until we receive the sub auth count to continue1923        } else {1924            return this._fail("Server wanted tunnels, but doesn't support " +1925                              "the notunnel type");1926        }1927    }19281929    _negotiateTightAuth() {1930        if (!this._rfbTightVNC) {  // first pass, do the tunnel negotiation1931            if (this._sock.rQwait("num tunnels", 4)) { return false; }1932            const numTunnels = this._sock.rQshift32();1933            if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }19341935            this._rfbTightVNC = true;19361937            if (numTunnels > 0) {1938                this._negotiateTightTunnels(numTunnels);1939                return false;  // wait until we receive the sub auth to continue1940            }1941        }19421943        // second pass, do the sub-auth negotiation1944        if (this._sock.rQwait("sub auth count", 4)) { return false; }1945        const subAuthCount = this._sock.rQshift32();1946        if (subAuthCount === 0) {  // empty sub-auth list received means 'no auth' subtype selected1947            this._rfbInitState = 'SecurityResult';1948            return true;1949        }19501951        if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }19521953        const clientSupportedTypes = {1954            'STDVNOAUTH__': 1,1955            'STDVVNCAUTH_': 2,1956            'TGHTULGNAUTH': 1291957        };19581959        const serverSupportedTypes = [];19601961        for (let i = 0; i < subAuthCount; i++) {1962            this._sock.rQshift32(); // capNum1963            const capabilities = this._sock.rQshiftStr(12);1964            serverSupportedTypes.push(capabilities);1965        }19661967        Log.Debug("Server Tight authentication types: " + serverSupportedTypes);19681969        for (let authType in clientSupportedTypes) {1970            if (serverSupportedTypes.indexOf(authType) != -1) {1971                this._sock.sQpush32(clientSupportedTypes[authType]);1972                this._sock.flush();1973                Log.Debug("Selected authentication type: " + authType);19741975                switch (authType) {1976                    case 'STDVNOAUTH__':  // no auth1977                        this._rfbInitState = 'SecurityResult';1978                        return true;1979                    case 'STDVVNCAUTH_':1980                        this._rfbAuthScheme = securityTypeVNCAuth;1981                        return true;1982                    case 'TGHTULGNAUTH':1983                        this._rfbAuthScheme = securityTypeUnixLogon;1984                        return true;1985                    default:1986                        return this._fail("Unsupported tiny auth scheme " +1987                                          "(scheme: " + authType + ")");1988                }1989            }1990        }19911992        return this._fail("No supported sub-auth types!");1993    }19941995    _handleRSAAESCredentialsRequired(event) {1996        this.dispatchEvent(event);1997    }19981999    _handleRSAAESServerVerification(event) {2000        this.dispatchEvent(event);2001    }20022003    _negotiateRA2neAuth() {2004        if (this._rfbRSAAESAuthenticationState === null) {2005            this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);2006            this._rfbRSAAESAuthenticationState.addEventListener(2007                "serververification", this._eventHandlers.handleRSAAESServerVerification);2008            this._rfbRSAAESAuthenticationState.addEventListener(2009                "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);2010        }2011        this._rfbRSAAESAuthenticationState.checkInternalEvents();2012        if (!this._rfbRSAAESAuthenticationState.hasStarted) {2013            this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()2014                .catch((e) => {2015                    if (e.message !== "disconnect normally") {2016                        this._fail(e.message);2017                    }2018                })2019                .then(() => {2020                    this._rfbInitState = "SecurityResult";2021                    return true;2022                }).finally(() => {2023                    this._rfbRSAAESAuthenticationState.removeEventListener(2024                        "serververification", this._eventHandlers.handleRSAAESServerVerification);2025                    this._rfbRSAAESAuthenticationState.removeEventListener(2026                        "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);2027                    this._rfbRSAAESAuthenticationState = null;2028                });2029        }2030        return false;2031    }20322033    _negotiateMSLogonIIAuth() {2034        if (this._sock.rQwait("mslogonii dh param", 24)) { return false; }20352036        if (this._rfbCredentials.username === undefined ||2037            this._rfbCredentials.password === undefined) {2038            this.dispatchEvent(new CustomEvent(2039                "credentialsrequired",2040                { detail: { types: ["username", "password"] } }));2041            return false;2042        }20432044        const g = this._sock.rQshiftBytes(8);2045        const p = this._sock.rQshiftBytes(8);2046        const A = this._sock.rQshiftBytes(8);2047        const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);2048        const B = legacyCrypto.exportKey("raw", dhKey.publicKey);2049        const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);20502051        const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);2052        const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);2053        const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);2054        let usernameBytes = new Uint8Array(256);2055        let passwordBytes = new Uint8Array(64);2056        window.crypto.getRandomValues(usernameBytes);2057        window.crypto.getRandomValues(passwordBytes);2058        for (let i = 0; i < username.length; i++) {2059            usernameBytes[i] = username.charCodeAt(i);2060        }2061        usernameBytes[username.length] = 0;2062        for (let i = 0; i < password.length; i++) {2063            passwordBytes[i] = password.charCodeAt(i);2064        }2065        passwordBytes[password.length] = 0;2066        usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);2067        passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);2068        this._sock.sQpushBytes(B);2069        this._sock.sQpushBytes(usernameBytes);2070        this._sock.sQpushBytes(passwordBytes);2071        this._sock.flush();2072        this._rfbInitState = "SecurityResult";2073        return true;2074    }20752076    _negotiateAuthentication() {2077        switch (this._rfbAuthScheme) {2078            case securityTypeNone:2079                if (this._rfbVersion >= 3.8) {2080                    this._rfbInitState = 'SecurityResult';2081                } else {2082                    this._rfbInitState = 'ClientInitialisation';2083                }2084                return true;20852086            case securityTypeXVP:2087                return this._negotiateXvpAuth();20882089            case securityTypeARD:2090                return this._negotiateARDAuth();20912092            case securityTypeVNCAuth:2093                return this._negotiateStdVNCAuth();20942095            case securityTypeTight:2096                return this._negotiateTightAuth();20972098            case securityTypeVeNCrypt:2099                return this._negotiateVeNCryptAuth();21002101            case securityTypePlain:2102                return this._negotiatePlainAuth();21032104            case securityTypeUnixLogon:2105                return this._negotiateTightUnixAuth();21062107            case securityTypeRA2ne:2108                return this._negotiateRA2neAuth();21092110            case securityTypeMSLogonII:2111                return this._negotiateMSLogonIIAuth();21122113            default:2114                return this._fail("Unsupported auth scheme (scheme: " +2115                                  this._rfbAuthScheme + ")");2116        }2117    }21182119    _handleSecurityResult() {2120        if (this._sock.rQwait('VNC auth response ', 4)) { return false; }21212122        const status = this._sock.rQshift32();21232124        if (status === 0) { // OK2125            this._rfbInitState = 'ClientInitialisation';2126            Log.Debug('Authentication OK');2127            return true;2128        } else {2129            if (this._rfbVersion >= 3.8) {2130                this._rfbInitState = "SecurityReason";2131                this._securityContext = "security result";2132                this._securityStatus = status;2133                return true;2134            } else {2135                this.dispatchEvent(new CustomEvent(2136                    "securityfailure",2137                    { detail: { status: status } }));21382139                return this._fail("Security handshake failed");2140            }2141        }2142    }21432144    _negotiateServerInit() {2145        if (this._sock.rQwait("server initialization", 24)) { return false; }21462147        /* Screen size */2148        const width = this._sock.rQshift16();2149        const height = this._sock.rQshift16();21502151        /* PIXEL_FORMAT */2152        const bpp         = this._sock.rQshift8();2153        const depth       = this._sock.rQshift8();2154        const bigEndian  = this._sock.rQshift8();2155        const trueColor  = this._sock.rQshift8();21562157        const redMax     = this._sock.rQshift16();2158        const greenMax   = this._sock.rQshift16();2159        const blueMax    = this._sock.rQshift16();2160        const redShift   = this._sock.rQshift8();2161        const greenShift = this._sock.rQshift8();2162        const blueShift  = this._sock.rQshift8();2163        this._sock.rQskipBytes(3);  // padding21642165        // NB(directxman12): we don't want to call any callbacks or print messages until2166        //                   *after* we're past the point where we could backtrack21672168        /* Connection name/title */2169        const nameLength = this._sock.rQshift32();2170        if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }2171        let name = this._sock.rQshiftStr(nameLength);2172        name = decodeUTF8(name, true);21732174        if (this._rfbTightVNC) {2175            if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }2176            // In TightVNC mode, ServerInit message is extended2177            const numServerMessages = this._sock.rQshift16();2178            const numClientMessages = this._sock.rQshift16();2179            const numEncodings = this._sock.rQshift16();2180            this._sock.rQskipBytes(2);  // padding21812182            const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;2183            if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }21842185            // we don't actually do anything with the capability information that TIGHT sends,2186            // so we just skip the all of this.21872188            // TIGHT server message capabilities2189            this._sock.rQskipBytes(16 * numServerMessages);21902191            // TIGHT client message capabilities2192            this._sock.rQskipBytes(16 * numClientMessages);21932194            // TIGHT encoding capabilities2195            this._sock.rQskipBytes(16 * numEncodings);2196        }21972198        // NB(directxman12): these are down here so that we don't run them multiple times2199        //                   if we backtrack2200        Log.Info("Screen: " + width + "x" + height +2201                  ", bpp: " + bpp + ", depth: " + depth +2202                  ", bigEndian: " + bigEndian +2203                  ", trueColor: " + trueColor +2204                  ", redMax: " + redMax +2205                  ", greenMax: " + greenMax +2206                  ", blueMax: " + blueMax +2207                  ", redShift: " + redShift +2208                  ", greenShift: " + greenShift +2209                  ", blueShift: " + blueShift);22102211        // we're past the point where we could backtrack, so it's safe to call this2212        this._setDesktopName(name);2213        this._resize(width, height);22142215        if (!this._viewOnly) { this._keyboard.grab(); }22162217        this._fbDepth = 24;22182219        if (this._fbName === "Intel(r) AMT KVM") {2220            Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");2221            this._fbDepth = 8;2222        }22232224        RFB.messages.pixelFormat(this._sock, this._fbDepth, true);2225        this._sendEncodings();2226        RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);22272228        this._updateConnectionState('connected');2229        return true;2230    }22312232    _sendEncodings() {2233        const encs = [];22342235        // In preference order2236        encs.push(encodings.encodingCopyRect);2237        // Only supported with full depth support2238        if (this._fbDepth == 24) {2239            if (supportsWebCodecsH264Decode) {2240                encs.push(encodings.encodingH264);2241            }2242            encs.push(encodings.encodingTight);2243            encs.push(encodings.encodingTightPNG);2244            encs.push(encodings.encodingZRLE);2245            encs.push(encodings.encodingJPEG);2246            encs.push(encodings.encodingHextile);2247            encs.push(encodings.encodingRRE);2248            encs.push(encodings.encodingZlib);2249        }2250        encs.push(encodings.encodingRaw);22512252        // Psuedo-encoding settings2253        encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);2254        encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);22552256        encs.push(encodings.pseudoEncodingDesktopSize);2257        encs.push(encodings.pseudoEncodingLastRect);2258        encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);2259        encs.push(encodings.pseudoEncodingQEMULedEvent);2260        encs.push(encodings.pseudoEncodingExtendedDesktopSize);2261        encs.push(encodings.pseudoEncodingXvp);2262        encs.push(encodings.pseudoEncodingFence);2263        encs.push(encodings.pseudoEncodingContinuousUpdates);2264        encs.push(encodings.pseudoEncodingDesktopName);2265        encs.push(encodings.pseudoEncodingExtendedClipboard);2266        encs.push(encodings.pseudoEncodingExtendedMouseButtons);22672268        if (this._fbDepth == 24) {2269            encs.push(encodings.pseudoEncodingVMwareCursor);2270            encs.push(encodings.pseudoEncodingCursor);2271        }22722273        RFB.messages.clientEncodings(this._sock, encs);2274    }22752276    /* RFB protocol initialization states:2277     *   ProtocolVersion2278     *   Security2279     *   Authentication2280     *   SecurityResult2281     *   ClientInitialization - not triggered by server message2282     *   ServerInitialization2283     */2284    _initMsg() {2285        switch (this._rfbInitState) {2286            case 'ProtocolVersion':2287                return this._negotiateProtocolVersion();22882289            case 'Security':2290                return this._negotiateSecurity();22912292            case 'Authentication':2293                return this._negotiateAuthentication();22942295            case 'SecurityResult':2296                return this._handleSecurityResult();22972298            case 'SecurityReason':2299                return this._handleSecurityReason();23002301            case 'ClientInitialisation':2302                this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation2303                this._sock.flush();2304                this._rfbInitState = 'ServerInitialisation';2305                return true;23062307            case 'ServerInitialisation':2308                return this._negotiateServerInit();23092310            default:2311                return this._fail("Unknown init state (state: " +2312                                  this._rfbInitState + ")");2313        }2314    }23152316    // Resume authentication handshake after it was paused for some2317    // reason, e.g. waiting for a password from the user2318    _resumeAuthentication() {2319        // We use setTimeout() so it's run in its own context, just like2320        // it originally did via the WebSocket's event handler2321        setTimeout(this._initMsg.bind(this), 0);2322    }23232324    _handleSetColourMapMsg() {2325        Log.Debug("SetColorMapEntries");23262327        return this._fail("Unexpected SetColorMapEntries message");2328    }23292330    _handleServerCutText() {2331        Log.Debug("ServerCutText");23322333        if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }23342335        this._sock.rQskipBytes(3);  // Padding23362337        let length = this._sock.rQshift32();2338        length = toSigned32bit(length);23392340        if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }23412342        if (length >= 0) {2343            //Standard msg2344            const text = this._sock.rQshiftStr(length);2345            if (this._viewOnly) {2346                return true;2347            }23482349            this.dispatchEvent(new CustomEvent(2350                "clipboard",2351                { detail: { text: text } }));23522353        } else {2354            //Extended msg.2355            length = Math.abs(length);2356            const flags = this._sock.rQshift32();2357            let formats = flags & 0x0000FFFF;2358            let actions = flags & 0xFF000000;23592360            let isCaps = (!!(actions & extendedClipboardActionCaps));2361            if (isCaps) {2362                this._clipboardServerCapabilitiesFormats = {};2363                this._clipboardServerCapabilitiesActions = {};23642365                // Update our server capabilities for Formats2366                for (let i = 0; i <= 15; i++) {2367                    let index = 1 << i;23682369                    // Check if format flag is set.2370                    if ((formats & index)) {2371                        this._clipboardServerCapabilitiesFormats[index] = true;2372                        // We don't send unsolicited clipboard, so we2373                        // ignore the size2374                        this._sock.rQshift32();2375                    }2376                }23772378                // Update our server capabilities for Actions2379                for (let i = 24; i <= 31; i++) {2380                    let index = 1 << i;2381                    this._clipboardServerCapabilitiesActions[index] = !!(actions & index);2382                }23832384                /*  Caps handling done, send caps with the clients2385                    capabilities set as a response */2386                let clientActions = [2387                    extendedClipboardActionCaps,2388                    extendedClipboardActionRequest,2389                    extendedClipboardActionPeek,2390                    extendedClipboardActionNotify,2391                    extendedClipboardActionProvide2392                ];2393                RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});23942395            } else if (actions === extendedClipboardActionRequest) {2396                if (this._viewOnly) {2397                    return true;2398                }23992400                // Check if server has told us it can handle Provide and there is clipboard data to send.2401                if (this._clipboardText != null &&2402                    this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {24032404                    if (formats & extendedClipboardFormatText) {2405                        RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);2406                    }2407                }24082409            } else if (actions === extendedClipboardActionPeek) {2410                if (this._viewOnly) {2411                    return true;2412                }24132414                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {24152416                    if (this._clipboardText != null) {2417                        RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);2418                    } else {2419                        RFB.messages.extendedClipboardNotify(this._sock, []);2420                    }2421                }24222423            } else if (actions === extendedClipboardActionNotify) {2424                if (this._viewOnly) {2425                    return true;2426                }24272428                if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {24292430                    if (formats & extendedClipboardFormatText) {2431                        RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);2432                    }2433                }24342435            } else if (actions === extendedClipboardActionProvide) {2436                if (this._viewOnly) {2437                    return true;2438                }24392440                if (!(formats & extendedClipboardFormatText)) {2441                    return true;2442                }2443                // Ignore what we had in our clipboard client side.2444                this._clipboardText = null;24452446                // FIXME: Should probably verify that this data was actually requested2447                let zlibStream = this._sock.rQshiftBytes(length - 4);2448                let streamInflator = new Inflator();2449                let textData = null;24502451                streamInflator.setInput(zlibStream);2452                for (let i = 0; i <= 15; i++) {2453                    let format = 1 << i;24542455                    if (formats & format) {24562457                        let size = 0x00;2458                        let sizeArray = streamInflator.inflate(4);24592460                        size |= (sizeArray[0] << 24);2461                        size |= (sizeArray[1] << 16);2462                        size |= (sizeArray[2] << 8);2463                        size |= (sizeArray[3]);2464                        let chunk = streamInflator.inflate(size);24652466                        if (format === extendedClipboardFormatText) {2467                            textData = chunk;2468                        }2469                    }2470                }2471                streamInflator.setInput(null);24722473                if (textData !== null) {2474                    let tmpText = "";2475                    for (let i = 0; i < textData.length; i++) {2476                        tmpText += String.fromCharCode(textData[i]);2477                    }2478                    textData = tmpText;24792480                    textData = decodeUTF8(textData);2481                    if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {2482                        textData = textData.slice(0, -1);2483                    }24842485                    textData = textData.replaceAll("\r\n", "\n");24862487                    this.dispatchEvent(new CustomEvent(2488                        "clipboard",2489                        { detail: { text: textData } }));2490                }2491            } else {2492                return this._fail("Unexpected action in extended clipboard message: " + actions);2493            }2494        }2495        return true;2496    }24972498    _handleServerFenceMsg() {2499        if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }2500        this._sock.rQskipBytes(3); // Padding2501        let flags = this._sock.rQshift32();2502        let length = this._sock.rQshift8();25032504        if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }25052506        if (length > 64) {2507            Log.Warn("Bad payload length (" + length + ") in fence response");2508            length = 64;2509        }25102511        const payload = this._sock.rQshiftStr(length);25122513        this._supportsFence = true;25142515        /*2516         * Fence flags2517         *2518         *  (1<<0)  - BlockBefore2519         *  (1<<1)  - BlockAfter2520         *  (1<<2)  - SyncNext2521         *  (1<<31) - Request2522         */25232524        if (!(flags & (1<<31))) {2525            return this._fail("Unexpected fence response");2526        }25272528        // Filter out unsupported flags2529        // FIXME: support syncNext2530        flags &= (1<<0) | (1<<1);25312532        // BlockBefore and BlockAfter are automatically handled by2533        // the fact that we process each incoming message2534        // synchronuosly.2535        RFB.messages.clientFence(this._sock, flags, payload);25362537        return true;2538    }25392540    _handleXvpMsg() {2541        if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }2542        this._sock.rQskipBytes(1);  // Padding2543        const xvpVer = this._sock.rQshift8();2544        const xvpMsg = this._sock.rQshift8();25452546        switch (xvpMsg) {2547            case 0:  // XVP_FAIL2548                Log.Error("XVP operation failed");2549                break;2550            case 1:  // XVP_INIT2551                this._rfbXvpVer = xvpVer;2552                Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");2553                this._setCapability("power", true);2554                break;2555            default:2556                this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");2557                break;2558        }25592560        return true;2561    }25622563    _normalMsg() {2564        let msgType;2565        if (this._FBU.rects > 0) {2566            msgType = 0;2567        } else {2568            msgType = this._sock.rQshift8();2569        }25702571        let first, ret;2572        switch (msgType) {2573            case 0:  // FramebufferUpdate2574                ret = this._framebufferUpdate();2575                if (ret && !this._enabledContinuousUpdates) {2576                    RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,2577                                                 this._fbWidth, this._fbHeight);2578                }2579                return ret;25802581            case 1:  // SetColorMapEntries2582                return this._handleSetColourMapMsg();25832584            case 2:  // Bell2585                Log.Debug("Bell");2586                this.dispatchEvent(new CustomEvent(2587                    "bell",2588                    { detail: {} }));2589                return true;25902591            case 3:  // ServerCutText2592                return this._handleServerCutText();25932594            case 150: // EndOfContinuousUpdates2595                first = !this._supportsContinuousUpdates;2596                this._supportsContinuousUpdates = true;2597                this._enabledContinuousUpdates = false;2598                if (first) {2599                    this._enabledContinuousUpdates = true;2600                    this._updateContinuousUpdates();2601                    Log.Info("Enabling continuous updates.");2602                } else {2603                    // FIXME: We need to send a framebufferupdaterequest here2604                    // if we add support for turning off continuous updates2605                }2606                return true;26072608            case 248: // ServerFence2609                return this._handleServerFenceMsg();26102611            case 250:  // XVP2612                return this._handleXvpMsg();26132614            default:2615                this._fail("Unexpected server message (type " + msgType + ")");2616                Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));2617                return true;2618        }2619    }26202621    _framebufferUpdate() {2622        if (this._FBU.rects === 0) {2623            if (this._sock.rQwait("FBU header", 3, 1)) { return false; }2624            this._sock.rQskipBytes(1);  // Padding2625            this._FBU.rects = this._sock.rQshift16();26262627            // Make sure the previous frame is fully rendered first2628            // to avoid building up an excessive queue2629            if (this._display.pending()) {2630                this._flushing = true;2631                this._display.flush()2632                    .then(() => {2633                        this._flushing = false;2634                        // Resume processing2635                        if (!this._sock.rQwait("message", 1)) {2636                            this._handleMessage();2637                        }2638                    });2639                return false;2640            }2641        }26422643        while (this._FBU.rects > 0) {2644            if (this._FBU.encoding === null) {2645                if (this._sock.rQwait("rect header", 12)) { return false; }2646                /* New FramebufferUpdate */26472648                this._FBU.x = this._sock.rQshift16();2649                this._FBU.y = this._sock.rQshift16();2650                this._FBU.width = this._sock.rQshift16();2651                this._FBU.height = this._sock.rQshift16();2652                this._FBU.encoding = this._sock.rQshift32();2653                /* Encodings are signed */2654                this._FBU.encoding >>= 0;2655            }26562657            if (!this._handleRect()) {2658                return false;2659            }26602661            this._FBU.rects--;2662            this._FBU.encoding = null;2663        }26642665        this._display.flip();26662667        return true;  // We finished this FBU2668    }26692670    _handleRect() {2671        switch (this._FBU.encoding) {2672            case encodings.pseudoEncodingLastRect:2673                this._FBU.rects = 1; // Will be decreased when we return2674                return true;26752676            case encodings.pseudoEncodingVMwareCursor:2677                return this._handleVMwareCursor();26782679            case encodings.pseudoEncodingCursor:2680                return this._handleCursor();26812682            case encodings.pseudoEncodingQEMUExtendedKeyEvent:2683                this._qemuExtKeyEventSupported = true;2684                return true;26852686            case encodings.pseudoEncodingDesktopName:2687                return this._handleDesktopName();26882689            case encodings.pseudoEncodingDesktopSize:2690                this._resize(this._FBU.width, this._FBU.height);2691                return true;26922693            case encodings.pseudoEncodingExtendedDesktopSize:2694                return this._handleExtendedDesktopSize();26952696            case encodings.pseudoEncodingExtendedMouseButtons:2697                this._extendedPointerEventSupported = true;2698                return true;26992700            case encodings.pseudoEncodingQEMULedEvent:2701                return this._handleLedEvent();27022703            default:2704                return this._handleDataRect();2705        }2706    }27072708    _handleVMwareCursor() {2709        const hotx = this._FBU.x;  // hotspot-x2710        const hoty = this._FBU.y;  // hotspot-y2711        const w = this._FBU.width;2712        const h = this._FBU.height;2713        if (this._sock.rQwait("VMware cursor encoding", 1)) {2714            return false;2715        }27162717        const cursorType = this._sock.rQshift8();27182719        this._sock.rQshift8(); //Padding27202721        let rgba;2722        const bytesPerPixel = 4;27232724        //Classic cursor2725        if (cursorType == 0) {2726            //Used to filter away unimportant bits.2727            //OR is used for correct conversion in js.2728            const PIXEL_MASK = 0xffffff00 | 0;2729            rgba = new Array(w * h * bytesPerPixel);27302731            if (this._sock.rQwait("VMware cursor classic encoding",2732                                  (w * h * bytesPerPixel) * 2, 2)) {2733                return false;2734            }27352736            let andMask = new Array(w * h);2737            for (let pixel = 0; pixel < (w * h); pixel++) {2738                andMask[pixel] = this._sock.rQshift32();2739            }27402741            let xorMask = new Array(w * h);2742            for (let pixel = 0; pixel < (w * h); pixel++) {2743                xorMask[pixel] = this._sock.rQshift32();2744            }27452746            for (let pixel = 0; pixel < (w * h); pixel++) {2747                if (andMask[pixel] == 0) {2748                    //Fully opaque pixel2749                    let bgr = xorMask[pixel];2750                    let r   = bgr >> 8  & 0xff;2751                    let g   = bgr >> 16 & 0xff;2752                    let b   = bgr >> 24 & 0xff;27532754                    rgba[(pixel * bytesPerPixel)     ] = r;    //r2755                    rgba[(pixel * bytesPerPixel) + 1 ] = g;    //g2756                    rgba[(pixel * bytesPerPixel) + 2 ] = b;    //b2757                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a27582759                } else if ((andMask[pixel] & PIXEL_MASK) ==2760                           PIXEL_MASK) {2761                    //Only screen value matters, no mouse colouring2762                    if (xorMask[pixel] == 0) {2763                        //Transparent pixel2764                        rgba[(pixel * bytesPerPixel)     ] = 0x00;2765                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;2766                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;2767                        rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;27682769                    } else if ((xorMask[pixel] & PIXEL_MASK) ==2770                               PIXEL_MASK) {2771                        //Inverted pixel, not supported in browsers.2772                        //Fully opaque instead.2773                        rgba[(pixel * bytesPerPixel)     ] = 0x00;2774                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;2775                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;2776                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;27772778                    } else {2779                        //Unhandled xorMask2780                        rgba[(pixel * bytesPerPixel)     ] = 0x00;2781                        rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;2782                        rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;2783                        rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;2784                    }27852786                } else {2787                    //Unhandled andMask2788                    rgba[(pixel * bytesPerPixel)     ] = 0x00;2789                    rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;2790                    rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;2791                    rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;2792                }2793            }27942795        //Alpha cursor.2796        } else if (cursorType == 1) {2797            if (this._sock.rQwait("VMware cursor alpha encoding",2798                                  (w * h * 4), 2)) {2799                return false;2800            }28012802            rgba = new Array(w * h * bytesPerPixel);28032804            for (let pixel = 0; pixel < (w * h); pixel++) {2805                let data = this._sock.rQshift32();28062807                rgba[(pixel * 4)     ] = data >> 24 & 0xff; //r2808                rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g2809                rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff;  //b2810                rgba[(pixel * 4) + 3 ] = data & 0xff;       //a2811            }28122813        } else {2814            Log.Warn("The given cursor type is not supported: "2815                      + cursorType + " given.");2816            return false;2817        }28182819        this._updateCursor(rgba, hotx, hoty, w, h);28202821        return true;2822    }28232824    _handleCursor() {2825        const hotx = this._FBU.x;  // hotspot-x2826        const hoty = this._FBU.y;  // hotspot-y2827        const w = this._FBU.width;2828        const h = this._FBU.height;28292830        const pixelslength = w * h * 4;2831        const masklength = Math.ceil(w / 8) * h;28322833        let bytes = pixelslength + masklength;2834        if (this._sock.rQwait("cursor encoding", bytes)) {2835            return false;2836        }28372838        // Decode from BGRX pixels + bit mask to RGBA2839        const pixels = this._sock.rQshiftBytes(pixelslength);2840        const mask = this._sock.rQshiftBytes(masklength);2841        let rgba = new Uint8Array(w * h * 4);28422843        let pixIdx = 0;2844        for (let y = 0; y < h; y++) {2845            for (let x = 0; x < w; x++) {2846                let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);2847                let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;2848                rgba[pixIdx    ] = pixels[pixIdx + 2];2849                rgba[pixIdx + 1] = pixels[pixIdx + 1];2850                rgba[pixIdx + 2] = pixels[pixIdx];2851                rgba[pixIdx + 3] = alpha;2852                pixIdx += 4;2853            }2854        }28552856        this._updateCursor(rgba, hotx, hoty, w, h);28572858        return true;2859    }28602861    _handleDesktopName() {2862        if (this._sock.rQwait("DesktopName", 4)) {2863            return false;2864        }28652866        let length = this._sock.rQshift32();28672868        if (this._sock.rQwait("DesktopName", length, 4)) {2869            return false;2870        }28712872        let name = this._sock.rQshiftStr(length);2873        name = decodeUTF8(name, true);28742875        this._setDesktopName(name);28762877        return true;2878    }28792880    _handleLedEvent() {2881        if (this._sock.rQwait("LED status", 1)) {2882            return false;2883        }28842885        let data = this._sock.rQshift8();2886        // ScrollLock state can be retrieved with data & 1. This is currently not needed.2887        let numLock = data & 2 ? true : false;2888        let capsLock = data & 4 ? true : false;2889        this._remoteCapsLock = capsLock;2890        this._remoteNumLock = numLock;28912892        return true;2893    }28942895    _handleExtendedDesktopSize() {2896        if (this._sock.rQwait("ExtendedDesktopSize", 4)) {2897            return false;2898        }28992900        const numberOfScreens = this._sock.rQpeek8();29012902        let bytes = 4 + (numberOfScreens * 16);2903        if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {2904            return false;2905        }29062907        const firstUpdate = !this._supportsSetDesktopSize;2908        this._supportsSetDesktopSize = true;29092910        this._sock.rQskipBytes(1);  // number-of-screens2911        this._sock.rQskipBytes(3);  // padding29122913        for (let i = 0; i < numberOfScreens; i += 1) {2914            // Save the id and flags of the first screen2915            if (i === 0) {2916                this._screenID = this._sock.rQshift32();    // id2917                this._sock.rQskipBytes(2);                  // x-position2918                this._sock.rQskipBytes(2);                  // y-position2919                this._sock.rQskipBytes(2);                  // width2920                this._sock.rQskipBytes(2);                  // height2921                this._screenFlags = this._sock.rQshift32(); // flags2922            } else {2923                this._sock.rQskipBytes(16);2924            }2925        }29262927        /*2928         * The x-position indicates the reason for the change:2929         *2930         *  0 - server resized on its own2931         *  1 - this client requested the resize2932         *  2 - another client requested the resize2933         */29342935        if (this._FBU.x === 1) {2936            this._pendingRemoteResize = false;2937        }29382939        // We need to handle errors when we requested the resize.2940        if (this._FBU.x === 1 && this._FBU.y !== 0) {2941            let msg = "";2942            // The y-position indicates the status code from the server2943            switch (this._FBU.y) {2944                case 1:2945                    msg = "Resize is administratively prohibited";2946                    break;2947                case 2:2948                    msg = "Out of resources";2949                    break;2950                case 3:2951                    msg = "Invalid screen layout";2952                    break;2953                default:2954                    msg = "Unknown reason";2955                    break;2956            }2957            Log.Warn("Server did not accept the resize request: "2958                     + msg);2959        } else {2960            this._resize(this._FBU.width, this._FBU.height);2961        }29622963        // Normally we only apply the current resize mode after a2964        // window resize event. However there is no such trigger on the2965        // initial connect. And we don't know if the server supports2966        // resizing until we've gotten here.2967        if (firstUpdate) {2968            this._requestRemoteResize();2969        }29702971        if (this._FBU.x === 1 && this._FBU.y === 0) {2972            // We might have resized again whilst waiting for the2973            // previous request, so check if we are in sync2974            this._requestRemoteResize();2975        }29762977        return true;2978    }29792980    _handleDataRect() {2981        let decoder = this._decoders[this._FBU.encoding];2982        if (!decoder) {2983            this._fail("Unsupported encoding (encoding: " +2984                       this._FBU.encoding + ")");2985            return false;2986        }29872988        try {2989            return decoder.decodeRect(this._FBU.x, this._FBU.y,2990                                      this._FBU.width, this._FBU.height,2991                                      this._sock, this._display,2992                                      this._fbDepth);2993        } catch (err) {2994            this._fail("Error decoding rect: " + err);2995            return false;2996        }2997    }29982999    _updateContinuousUpdates() {3000        if (!this._enabledContinuousUpdates) { return; }30013002        RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,3003                                             this._fbWidth, this._fbHeight);3004    }30053006    // Handle resize-messages from the server3007    _resize(width, height) {3008        this._fbWidth = width;3009        this._fbHeight = height;30103011        this._display.resize(this._fbWidth, this._fbHeight);30123013        // Adjust the visible viewport based on the new dimensions3014        this._updateClip();3015        this._updateScale();30163017        this._updateContinuousUpdates();30183019        // Keep this size until browser client size changes3020        this._saveExpectedClientSize();3021    }30223023    _xvpOp(ver, op) {3024        if (this._rfbXvpVer < ver) { return; }3025        Log.Info("Sending XVP operation " + op + " (version " + ver + ")");3026        RFB.messages.xvpOp(this._sock, ver, op);3027    }30283029    _updateCursor(rgba, hotx, hoty, w, h) {3030        this._cursorImage = {3031            rgbaPixels: rgba,3032            hotx: hotx, hoty: hoty, w: w, h: h,3033        };3034        this._refreshCursor();3035    }30363037    _shouldShowDotCursor() {3038        // Called when this._cursorImage is updated3039        if (!this._showDotCursor) {3040            // User does not want to see the dot, so...3041            return false;3042        }30433044        // The dot should not be shown if the cursor is already visible,3045        // i.e. contains at least one not-fully-transparent pixel.3046        // So iterate through all alpha bytes in rgba and stop at the3047        // first non-zero.3048        for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {3049            if (this._cursorImage.rgbaPixels[i]) {3050                return false;3051            }3052        }30533054        // At this point, we know that the cursor is fully transparent, and3055        // the user wants to see the dot instead of this.3056        return true;3057    }30583059    _refreshCursor() {3060        if (this._rfbConnectionState !== "connecting" &&3061            this._rfbConnectionState !== "connected") {3062            return;3063        }3064        const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;3065        this._cursor.change(image.rgbaPixels,3066                            image.hotx, image.hoty,3067                            image.w, image.h3068        );3069    }30703071    static genDES(password, challenge) {3072        const passwordChars = password.split('').map(c => c.charCodeAt(0));3073        const key = legacyCrypto.importKey(3074            "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);3075        return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);3076    }3077}30783079// Class Methods3080RFB.messages = {3081    keyEvent(sock, keysym, down) {3082        sock.sQpush8(4); // msg-type3083        sock.sQpush8(down);30843085        sock.sQpush16(0);30863087        sock.sQpush32(keysym);30883089        sock.flush();3090    },30913092    QEMUExtendedKeyEvent(sock, keysym, down, keycode) {3093        function getRFBkeycode(xtScanCode) {3094            const upperByte = (keycode >> 8);3095            const lowerByte = (keycode & 0x00ff);3096            if (upperByte === 0xe0 && lowerByte < 0x7f) {3097                return lowerByte | 0x80;3098            }3099            return xtScanCode;3100        }31013102        sock.sQpush8(255); // msg-type3103        sock.sQpush8(0); // sub msg-type31043105        sock.sQpush16(down);31063107        sock.sQpush32(keysym);31083109        const RFBkeycode = getRFBkeycode(keycode);31103111        sock.sQpush32(RFBkeycode);31123113        sock.flush();3114    },31153116    pointerEvent(sock, x, y, mask) {3117        sock.sQpush8(5); // msg-type31183119        // Marker bit must be set to 0, otherwise the server might3120        // confuse the marker bit with the highest bit in a normal3121        // PointerEvent message.3122        mask = mask & 0x7f;3123        sock.sQpush8(mask);31243125        sock.sQpush16(x);3126        sock.sQpush16(y);31273128        sock.flush();3129    },31303131    extendedPointerEvent(sock, x, y, mask) {3132        sock.sQpush8(5); // msg-type31333134        let higherBits = (mask >> 7) & 0xff;31353136        // Bits 2-7 are reserved3137        if (higherBits & 0xfc) {3138            throw new Error("Invalid mouse button mask: " + mask);3139        }31403141        let lowerBits = mask & 0x7f;3142        lowerBits |= 0x80; // Set marker bit to 131433144        sock.sQpush8(lowerBits);3145        sock.sQpush16(x);3146        sock.sQpush16(y);3147        sock.sQpush8(higherBits);31483149        sock.flush();3150    },31513152    // Used to build Notify and Request data.3153    _buildExtendedClipboardFlags(actions, formats) {3154        let data = new Uint8Array(4);3155        let formatFlag = 0x00000000;3156        let actionFlag = 0x00000000;31573158        for (let i = 0; i < actions.length; i++) {3159            actionFlag |= actions[i];3160        }31613162        for (let i = 0; i < formats.length; i++) {3163            formatFlag |= formats[i];3164        }31653166        data[0] = actionFlag >> 24; // Actions3167        data[1] = 0x00;             // Reserved3168        data[2] = 0x00;             // Reserved3169        data[3] = formatFlag;       // Formats31703171        return data;3172    },31733174    extendedClipboardProvide(sock, formats, inData) {3175        // Deflate incomming data and their sizes3176        let deflator = new Deflator();3177        let dataToDeflate = [];31783179        for (let i = 0; i < formats.length; i++) {3180            // We only support the format Text at this time3181            if (formats[i] != extendedClipboardFormatText) {3182                throw new Error("Unsupported extended clipboard format for Provide message.");3183            }31843185            // Change lone \r or \n into \r\n as defined in rfbproto3186            inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");31873188            // Check if it already has \03189            let text = encodeUTF8(inData[i] + "\0");31903191            dataToDeflate.push( (text.length >> 24) & 0xFF,3192                                (text.length >> 16) & 0xFF,3193                                (text.length >>  8) & 0xFF,3194                                (text.length & 0xFF));31953196            for (let j = 0; j < text.length; j++) {3197                dataToDeflate.push(text.charCodeAt(j));3198            }3199        }32003201        let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));32023203        // Build data  to send3204        let data = new Uint8Array(4 + deflatedData.length);3205        data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],3206                                                           formats));3207        data.set(deflatedData, 4);32083209        RFB.messages.clientCutText(sock, data, true);3210    },32113212    extendedClipboardNotify(sock, formats) {3213        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],3214                                                              formats);3215        RFB.messages.clientCutText(sock, flags, true);3216    },32173218    extendedClipboardRequest(sock, formats) {3219        let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],3220                                                              formats);3221        RFB.messages.clientCutText(sock, flags, true);3222    },32233224    extendedClipboardCaps(sock, actions, formats) {3225        let formatKeys = Object.keys(formats);3226        let data  = new Uint8Array(4 + (4 * formatKeys.length));32273228        formatKeys.map(x => parseInt(x));3229        formatKeys.sort((a, b) =>  a - b);32303231        data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));32323233        let loopOffset = 4;3234        for (let i = 0; i < formatKeys.length; i++) {3235            data[loopOffset]     = formats[formatKeys[i]] >> 24;3236            data[loopOffset + 1] = formats[formatKeys[i]] >> 16;3237            data[loopOffset + 2] = formats[formatKeys[i]] >> 8;3238            data[loopOffset + 3] = formats[formatKeys[i]] >> 0;32393240            loopOffset += 4;3241            data[3] |= (1 << formatKeys[i]); // Update our format flags3242        }32433244        RFB.messages.clientCutText(sock, data, true);3245    },32463247    clientCutText(sock, data, extended = false) {3248        sock.sQpush8(6); // msg-type32493250        sock.sQpush8(0); // padding3251        sock.sQpush8(0); // padding3252        sock.sQpush8(0); // padding32533254        let length;3255        if (extended) {3256            length = toUnsigned32bit(-data.length);3257        } else {3258            length = data.length;3259        }32603261        sock.sQpush32(length);3262        sock.sQpushBytes(data);3263        sock.flush();3264    },32653266    setDesktopSize(sock, width, height, id, flags) {3267        sock.sQpush8(251); // msg-type32683269        sock.sQpush8(0); // padding32703271        sock.sQpush16(width);3272        sock.sQpush16(height);32733274        sock.sQpush8(1); // number-of-screens32753276        sock.sQpush8(0); // padding32773278        // screen array3279        sock.sQpush32(id);3280        sock.sQpush16(0); // x-position3281        sock.sQpush16(0); // y-position3282        sock.sQpush16(width);3283        sock.sQpush16(height);3284        sock.sQpush32(flags);32853286        sock.flush();3287    },32883289    clientFence(sock, flags, payload) {3290        sock.sQpush8(248); // msg-type32913292        sock.sQpush8(0); // padding3293        sock.sQpush8(0); // padding3294        sock.sQpush8(0); // padding32953296        sock.sQpush32(flags);32973298        sock.sQpush8(payload.length);3299        sock.sQpushString(payload);33003301        sock.flush();3302    },33033304    enableContinuousUpdates(sock, enable, x, y, width, height) {3305        sock.sQpush8(150); // msg-type33063307        sock.sQpush8(enable);33083309        sock.sQpush16(x);3310        sock.sQpush16(y);3311        sock.sQpush16(width);3312        sock.sQpush16(height);33133314        sock.flush();3315    },33163317    pixelFormat(sock, depth, trueColor) {3318        let bpp;33193320        if (depth > 16) {3321            bpp = 32;3322        } else if (depth > 8) {3323            bpp = 16;3324        } else {3325            bpp = 8;3326        }33273328        const bits = Math.floor(depth/3);33293330        sock.sQpush8(0); // msg-type33313332        sock.sQpush8(0); // padding3333        sock.sQpush8(0); // padding3334        sock.sQpush8(0); // padding33353336        sock.sQpush8(bpp);3337        sock.sQpush8(depth);3338        sock.sQpush8(0); // little-endian3339        sock.sQpush8(trueColor ? 1 : 0);33403341        sock.sQpush16((1 << bits) - 1); // red-max3342        sock.sQpush16((1 << bits) - 1); // green-max3343        sock.sQpush16((1 << bits) - 1); // blue-max33443345        sock.sQpush8(bits * 0); // red-shift3346        sock.sQpush8(bits * 1); // green-shift3347        sock.sQpush8(bits * 2); // blue-shift33483349        sock.sQpush8(0); // padding3350        sock.sQpush8(0); // padding3351        sock.sQpush8(0); // padding33523353        sock.flush();3354    },33553356    clientEncodings(sock, encodings) {3357        sock.sQpush8(2); // msg-type33583359        sock.sQpush8(0); // padding33603361        sock.sQpush16(encodings.length);3362        for (let i = 0; i < encodings.length; i++) {3363            sock.sQpush32(encodings[i]);3364        }33653366        sock.flush();3367    },33683369    fbUpdateRequest(sock, incremental, x, y, w, h) {3370        if (typeof(x) === "undefined") { x = 0; }3371        if (typeof(y) === "undefined") { y = 0; }33723373        sock.sQpush8(3); // msg-type33743375        sock.sQpush8(incremental ? 1 : 0);33763377        sock.sQpush16(x);3378        sock.sQpush16(y);3379        sock.sQpush16(w);3380        sock.sQpush16(h);33813382        sock.flush();3383    },33843385    xvpOp(sock, ver, op) {3386        sock.sQpush8(250); // msg-type33873388        sock.sQpush8(0); // padding33893390        sock.sQpush8(ver);3391        sock.sQpush8(op);33923393        sock.flush();3394    }3395};33963397RFB.cursors = {3398    none: {3399        rgbaPixels: new Uint8Array(),3400        w: 0, h: 0,3401        hotx: 0, hoty: 0,3402    },34033404    dot: {3405        /* eslint-disable indent */3406        rgbaPixels: new Uint8Array([3407            255, 255, 255, 255,   0,   0,   0, 255, 255, 255, 255, 255,3408              0,   0,   0, 255,   0,   0,   0,   0,   0,   0,  0,  255,3409            255, 255, 255, 255,   0,   0,   0, 255, 255, 255, 255, 255,3410        ]),3411        /* eslint-enable indent */3412        w: 3, h: 3,3413        hotx: 1, hoty: 1,3414    }3415};3416