| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055 |
- /*
- * noVNC: HTML5 VNC client
- * Copyright (C) 2018 The noVNC Authors
- * Licensed under MPL 2.0 (see LICENSE.txt)
- *
- * See README.md for usage and integration instructions.
- *
- */
- import * as Log from './util/logging.js';
- import { decodeUTF8 } from './util/strings.js';
- import { dragThreshold } from './util/browser.js';
- import EventTargetMixin from './util/eventtarget.js';
- import Display from "./display.js";
- import Keyboard from "./input/keyboard.js";
- import Mouse from "./input/mouse.js";
- import Cursor from "./util/cursor.js";
- import Websock from "./websock.js";
- import DES from "./des.js";
- import KeyTable from "./input/keysym.js";
- import XtScancode from "./input/xtscancodes.js";
- import { encodings } from "./encodings.js";
- import "./util/polyfill.js";
- import RawDecoder from "./decoders/raw.js";
- import CopyRectDecoder from "./decoders/copyrect.js";
- import RREDecoder from "./decoders/rre.js";
- import HextileDecoder from "./decoders/hextile.js";
- import TightDecoder from "./decoders/tight.js";
- import TightPNGDecoder from "./decoders/tightpng.js";
- // How many seconds to wait for a disconnect to finish
- const DISCONNECT_TIMEOUT = 3;
- const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
- export default class RFB extends EventTargetMixin {
- constructor(target, url, options) {
- if (!target) {
- throw new Error("Must specify target");
- }
- if (!url) {
- throw new Error("Must specify URL");
- }
- super();
- this._target = target;
- this._url = url;
- // Connection details
- options = options || {};
- this._rfb_credentials = options.credentials || {};
- this._shared = 'shared' in options ? !!options.shared : true;
- this._repeaterID = options.repeaterID || '';
- this._showDotCursor = options.showDotCursor || false;
- // Internal state
- this._rfb_connection_state = '';
- this._rfb_init_state = '';
- this._rfb_auth_scheme = -1;
- this._rfb_clean_disconnect = true;
- // Server capabilities
- this._rfb_version = 0;
- this._rfb_max_version = 3.8;
- this._rfb_tightvnc = false;
- this._rfb_xvp_ver = 0;
- this._fb_width = 0;
- this._fb_height = 0;
- this._fb_name = "";
- this._capabilities = { power: false };
- this._supportsFence = false;
- this._supportsContinuousUpdates = false;
- this._enabledContinuousUpdates = false;
- this._supportsSetDesktopSize = false;
- this._screen_id = 0;
- this._screen_flags = 0;
- this._qemuExtKeyEventSupported = false;
- // Internal objects
- this._sock = null; // Websock object
- this._display = null; // Display object
- this._flushing = false; // Display flushing state
- this._keyboard = null; // Keyboard input handler object
- this._mouse = null; // Mouse input handler object
- // Timers
- this._disconnTimer = null; // disconnection timer
- this._resizeTimeout = null; // resize rate limiting
- // Decoder states
- this._decoders = {};
- this._FBU = {
- rects: 0,
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- encoding: null,
- };
- // Mouse state
- this._mouse_buttonMask = 0;
- this._mouse_arr = [];
- this._viewportDragging = false;
- this._viewportDragPos = {};
- this._viewportHasMoved = false;
- // Bound event handlers
- this._eventHandlers = {
- focusCanvas: this._focusCanvas.bind(this),
- windowResize: this._windowResize.bind(this),
- };
- // main setup
- Log.Debug(">> RFB.constructor");
- // Create DOM elements
- this._screen = document.createElement('div');
- this._screen.style.display = 'flex';
- this._screen.style.width = '100%';
- this._screen.style.height = '100%';
- this._screen.style.overflow = 'auto';
- this._screen.style.background = DEFAULT_BACKGROUND;
- this._canvas = document.createElement('canvas');
- this._canvas.style.margin = 'auto';
- // Some browsers add an outline on focus
- this._canvas.style.outline = 'none';
- // IE miscalculates width without this :(
- this._canvas.style.flexShrink = '0';
- this._canvas.width = 0;
- this._canvas.height = 0;
- this._canvas.tabIndex = -1;
- this._screen.appendChild(this._canvas);
- // Cursor
- this._cursor = new Cursor();
- // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
- // it. Result: no cursor at all until a window border or an edit field
- // is hit blindly. But there are also VNC servers that draw the cursor
- // in the framebuffer and don't send the empty local cursor. There is
- // no way to satisfy both sides.
- //
- // The spec is unclear on this "initial cursor" issue. Many other
- // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
- // initial cursor instead.
- this._cursorImage = RFB.cursors.none;
- // populate decoder array with objects
- this._decoders[encodings.encodingRaw] = new RawDecoder();
- this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
- this._decoders[encodings.encodingRRE] = new RREDecoder();
- this._decoders[encodings.encodingHextile] = new HextileDecoder();
- this._decoders[encodings.encodingTight] = new TightDecoder();
- this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
- // NB: nothing that needs explicit teardown should be done
- // before this point, since this can throw an exception
- try {
- this._display = new Display(this._canvas);
- } catch (exc) {
- Log.Error("Display exception: " + exc);
- throw exc;
- }
- this._display.onflush = this._onFlush.bind(this);
- this._display.clear();
- this._keyboard = new Keyboard(this._canvas);
- this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
- this._mouse = new Mouse(this._canvas);
- this._mouse.onmousebutton = this._handleMouseButton.bind(this);
- this._mouse.onmousemove = this._handleMouseMove.bind(this);
- this._sock = new Websock();
- this._sock.on('message', () => {
- this._handle_message();
- });
- this._sock.on('open', () => {
- if ((this._rfb_connection_state === 'connecting') &&
- (this._rfb_init_state === '')) {
- this._rfb_init_state = 'ProtocolVersion';
- Log.Debug("Starting VNC handshake");
- } else {
- this._fail("Unexpected server connection while " +
- this._rfb_connection_state);
- }
- });
- this._sock.on('close', (e) => {
- Log.Debug("WebSocket on-close event");
- let msg = "";
- if (e.code) {
- msg = "(code: " + e.code;
- if (e.reason) {
- msg += ", reason: " + e.reason;
- }
- msg += ")";
- }
- switch (this._rfb_connection_state) {
- case 'connecting':
- this._fail("Connection closed " + msg);
- break;
- case 'connected':
- // Handle disconnects that were initiated server-side
- this._updateConnectionState('disconnecting');
- this._updateConnectionState('disconnected');
- break;
- case 'disconnecting':
- // Normal disconnection path
- this._updateConnectionState('disconnected');
- break;
- case 'disconnected':
- this._fail("Unexpected server disconnect " +
- "when already disconnected " + msg);
- break;
- default:
- this._fail("Unexpected server disconnect before connecting " +
- msg);
- break;
- }
- this._sock.off('close');
- });
- this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
- // Slight delay of the actual connection so that the caller has
- // time to set up callbacks
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
- Log.Debug("<< RFB.constructor");
- // ===== PROPERTIES =====
- this.dragViewport = false;
- this.focusOnClick = true;
- this._viewOnly = false;
- this._clipViewport = false;
- this._scaleViewport = false;
- this._resizeSession = false;
- }
- // ===== PROPERTIES =====
- get viewOnly() { return this._viewOnly; }
- set viewOnly(viewOnly) {
- this._viewOnly = viewOnly;
- if (this._rfb_connection_state === "connecting" ||
- this._rfb_connection_state === "connected") {
- if (viewOnly) {
- this._keyboard.ungrab();
- this._mouse.ungrab();
- } else {
- this._keyboard.grab();
- this._mouse.grab();
- }
- }
- }
- get capabilities() { return this._capabilities; }
- get touchButton() { return this._mouse.touchButton; }
- set touchButton(button) { this._mouse.touchButton = button; }
- get clipViewport() { return this._clipViewport; }
- set clipViewport(viewport) {
- this._clipViewport = viewport;
- this._updateClip();
- }
- get scaleViewport() { return this._scaleViewport; }
- set scaleViewport(scale) {
- this._scaleViewport = scale;
- // Scaling trumps clipping, so we may need to adjust
- // clipping when enabling or disabling scaling
- if (scale && this._clipViewport) {
- this._updateClip();
- }
- this._updateScale();
- if (!scale && this._clipViewport) {
- this._updateClip();
- }
- }
- get resizeSession() { return this._resizeSession; }
- set resizeSession(resize) {
- this._resizeSession = resize;
- if (resize) {
- this._requestRemoteResize();
- }
- }
- get showDotCursor() { return this._showDotCursor; }
- set showDotCursor(show) {
- this._showDotCursor = show;
- this._refreshCursor();
- }
- get background() { return this._screen.style.background; }
- set background(cssValue) { this._screen.style.background = cssValue; }
- // ===== PUBLIC METHODS =====
- disconnect() {
- this._updateConnectionState('disconnecting');
- this._sock.off('error');
- this._sock.off('message');
- this._sock.off('open');
- }
- sendCredentials(creds) {
- this._rfb_credentials = creds;
- setTimeout(this._init_msg.bind(this), 0);
- }
- sendCtrlAltDel() {
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
- Log.Info("Sending Ctrl-Alt-Del");
- this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
- this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
- this.sendKey(KeyTable.XK_Delete, "Delete", true);
- this.sendKey(KeyTable.XK_Delete, "Delete", false);
- this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
- this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
- }
- machineShutdown() {
- this._xvpOp(1, 2);
- }
- machineReboot() {
- this._xvpOp(1, 3);
- }
- machineReset() {
- this._xvpOp(1, 4);
- }
- // Send a key press. If 'down' is not specified then send a down key
- // followed by an up key.
- sendKey(keysym, code, down) {
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
- if (down === undefined) {
- this.sendKey(keysym, code, true);
- this.sendKey(keysym, code, false);
- return;
- }
- const scancode = XtScancode[code];
- if (this._qemuExtKeyEventSupported && scancode) {
- // 0 is NoSymbol
- keysym = keysym || 0;
- Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
- RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
- } else {
- if (!keysym) {
- return;
- }
- Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
- RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
- }
- }
- focus() {
- this._canvas.focus();
- }
- blur() {
- this._canvas.blur();
- }
- clipboardPasteFrom(text) {
- if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
- RFB.messages.clientCutText(this._sock, text);
- }
- // ===== PRIVATE METHODS =====
- _connect() {
- Log.Debug(">> RFB.connect");
- Log.Info("connecting to " + this._url);
- try {
- // WebSocket.onopen transitions to the RFB init states
- this._sock.open(this._url, ['binary']);
- } catch (e) {
- if (e.name === 'SyntaxError') {
- this._fail("Invalid host or port (" + e + ")");
- } else {
- this._fail("Error when opening socket (" + e + ")");
- }
- }
- // Make our elements part of the page
- this._target.appendChild(this._screen);
- this._cursor.attach(this._canvas);
- this._refreshCursor();
- // Monitor size changes of the screen
- // FIXME: Use ResizeObserver, or hidden overflow
- window.addEventListener('resize', this._eventHandlers.windowResize);
- // Always grab focus on some kind of click event
- this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
- this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
- Log.Debug("<< RFB.connect");
- }
- _disconnect() {
- Log.Debug(">> RFB.disconnect");
- this._cursor.detach();
- this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
- this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
- window.removeEventListener('resize', this._eventHandlers.windowResize);
- this._keyboard.ungrab();
- this._mouse.ungrab();
- this._sock.close();
- try {
- this._target.removeChild(this._screen);
- } catch (e) {
- if (e.name === 'NotFoundError') {
- // Some cases where the initial connection fails
- // can disconnect before the _screen is created
- } else {
- throw e;
- }
- }
- clearTimeout(this._resizeTimeout);
- Log.Debug("<< RFB.disconnect");
- }
- _focusCanvas(event) {
- // Respect earlier handlers' request to not do side-effects
- if (event.defaultPrevented) {
- return;
- }
- if (!this.focusOnClick) {
- return;
- }
- this.focus();
- }
- _windowResize(event) {
- // If the window resized then our screen element might have
- // as well. Update the viewport dimensions.
- window.requestAnimationFrame(() => {
- this._updateClip();
- this._updateScale();
- });
- if (this._resizeSession) {
- // Request changing the resolution of the remote display to
- // the size of the local browser viewport.
- // In order to not send multiple requests before the browser-resize
- // is finished we wait 0.5 seconds before sending the request.
- clearTimeout(this._resizeTimeout);
- this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
- }
- }
- // Update state of clipping in Display object, and make sure the
- // configured viewport matches the current screen size
- _updateClip() {
- const cur_clip = this._display.clipViewport;
- let new_clip = this._clipViewport;
- if (this._scaleViewport) {
- // Disable viewport clipping if we are scaling
- new_clip = false;
- }
- if (cur_clip !== new_clip) {
- this._display.clipViewport = new_clip;
- }
- if (new_clip) {
- // When clipping is enabled, the screen is limited to
- // the size of the container.
- const size = this._screenSize();
- this._display.viewportChangeSize(size.w, size.h);
- this._fixScrollbars();
- }
- }
- _updateScale() {
- if (!this._scaleViewport) {
- this._display.scale = 1.0;
- } else {
- const size = this._screenSize();
- this._display.autoscale(size.w, size.h);
- }
- this._fixScrollbars();
- }
- // Requests a change of remote desktop size. This message is an extension
- // and may only be sent if we have received an ExtendedDesktopSize message
- _requestRemoteResize() {
- clearTimeout(this._resizeTimeout);
- this._resizeTimeout = null;
- if (!this._resizeSession || this._viewOnly ||
- !this._supportsSetDesktopSize) {
- return;
- }
- const size = this._screenSize();
- RFB.messages.setDesktopSize(this._sock,
- Math.floor(size.w), Math.floor(size.h),
- this._screen_id, this._screen_flags);
- Log.Debug('Requested new desktop size: ' +
- size.w + 'x' + size.h);
- }
- // Gets the the size of the available screen
- _screenSize() {
- let r = this._screen.getBoundingClientRect();
- return { w: r.width, h: r.height };
- }
- _fixScrollbars() {
- // This is a hack because Chrome screws up the calculation
- // for when scrollbars are needed. So to fix it we temporarily
- // toggle them off and on.
- const orig = this._screen.style.overflow;
- this._screen.style.overflow = 'hidden';
- // Force Chrome to recalculate the layout by asking for
- // an element's dimensions
- this._screen.getBoundingClientRect();
- this._screen.style.overflow = orig;
- }
- /*
- * Connection states:
- * connecting
- * connected
- * disconnecting
- * disconnected - permanent state
- */
- _updateConnectionState(state) {
- const oldstate = this._rfb_connection_state;
- if (state === oldstate) {
- Log.Debug("Already in state '" + state + "', ignoring");
- return;
- }
- // The 'disconnected' state is permanent for each RFB object
- if (oldstate === 'disconnected') {
- Log.Error("Tried changing state of a disconnected RFB object");
- return;
- }
- // Ensure proper transitions before doing anything
- switch (state) {
- case 'connected':
- if (oldstate !== 'connecting') {
- Log.Error("Bad transition to connected state, " +
- "previous connection state: " + oldstate);
- return;
- }
- break;
- case 'disconnected':
- if (oldstate !== 'disconnecting') {
- Log.Error("Bad transition to disconnected state, " +
- "previous connection state: " + oldstate);
- return;
- }
- break;
- case 'connecting':
- if (oldstate !== '') {
- Log.Error("Bad transition to connecting state, " +
- "previous connection state: " + oldstate);
- return;
- }
- break;
- case 'disconnecting':
- if (oldstate !== 'connected' && oldstate !== 'connecting') {
- Log.Error("Bad transition to disconnecting state, " +
- "previous connection state: " + oldstate);
- return;
- }
- break;
- default:
- Log.Error("Unknown connection state: " + state);
- return;
- }
- // State change actions
- this._rfb_connection_state = state;
- Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
- if (this._disconnTimer && state !== 'disconnecting') {
- Log.Debug("Clearing disconnect timer");
- clearTimeout(this._disconnTimer);
- this._disconnTimer = null;
- // make sure we don't get a double event
- this._sock.off('close');
- }
- switch (state) {
- case 'connecting':
- this._connect();
- break;
- case 'connected':
- this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
- break;
- case 'disconnecting':
- this._disconnect();
- this._disconnTimer = setTimeout(() => {
- Log.Error("Disconnection timed out.");
- this._updateConnectionState('disconnected');
- }, DISCONNECT_TIMEOUT * 1000);
- break;
- case 'disconnected':
- this.dispatchEvent(new CustomEvent(
- "disconnect", { detail:
- { clean: this._rfb_clean_disconnect } }));
- break;
- }
- }
- /* Print errors and disconnect
- *
- * The parameter 'details' is used for information that
- * should be logged but not sent to the user interface.
- */
- _fail(details) {
- switch (this._rfb_connection_state) {
- case 'disconnecting':
- Log.Error("Failed when disconnecting: " + details);
- break;
- case 'connected':
- Log.Error("Failed while connected: " + details);
- break;
- case 'connecting':
- Log.Error("Failed when connecting: " + details);
- break;
- default:
- Log.Error("RFB failure: " + details);
- break;
- }
- this._rfb_clean_disconnect = false; //This is sent to the UI
- // Transition to disconnected without waiting for socket to close
- this._updateConnectionState('disconnecting');
- this._updateConnectionState('disconnected');
- return false;
- }
- _setCapability(cap, val) {
- this._capabilities[cap] = val;
- this.dispatchEvent(new CustomEvent("capabilities",
- { detail: { capabilities: this._capabilities } }));
- }
- _handle_message() {
- if (this._sock.rQlen === 0) {
- Log.Warn("handle_message called on an empty receive queue");
- return;
- }
- switch (this._rfb_connection_state) {
- case 'disconnected':
- Log.Error("Got data while disconnected");
- break;
- case 'connected':
- while (true) {
- if (this._flushing) {
- break;
- }
- if (!this._normal_msg()) {
- break;
- }
- if (this._sock.rQlen === 0) {
- break;
- }
- }
- break;
- default:
- this._init_msg();
- break;
- }
- }
- _handleKeyEvent(keysym, code, down) {
- this.sendKey(keysym, code, down);
- }
- _handleMouseButton(x, y, down, bmask) {
- if (down) {
- this._mouse_buttonMask |= bmask;
- } else {
- this._mouse_buttonMask &= ~bmask;
- }
- if (this.dragViewport) {
- if (down && !this._viewportDragging) {
- this._viewportDragging = true;
- this._viewportDragPos = {'x': x, 'y': y};
- this._viewportHasMoved = false;
- // Skip sending mouse events
- return;
- } else {
- this._viewportDragging = false;
- // If we actually performed a drag then we are done
- // here and should not send any mouse events
- if (this._viewportHasMoved) {
- return;
- }
- // Otherwise we treat this as a mouse click event.
- // Send the button down event here, as the button up
- // event is sent at the end of this function.
- RFB.messages.pointerEvent(this._sock,
- this._display.absX(x),
- this._display.absY(y),
- bmask);
- }
- }
- if (this._viewOnly) { return; } // View only, skip mouse events
- if (this._rfb_connection_state !== 'connected') { return; }
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
- }
- _handleMouseMove(x, y) {
- if (this._viewportDragging) {
- const deltaX = this._viewportDragPos.x - x;
- const deltaY = this._viewportDragPos.y - y;
- if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
- Math.abs(deltaY) > dragThreshold)) {
- this._viewportHasMoved = true;
- this._viewportDragPos = {'x': x, 'y': y};
- this._display.viewportChangePos(deltaX, deltaY);
- }
- // Skip sending mouse events
- return;
- }
- if (this._viewOnly) { return; } // View only, skip mouse events
- if (this._rfb_connection_state !== 'connected') { return; }
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
- }
- // Message Handlers
- _negotiate_protocol_version() {
- if (this._sock.rQwait("version", 12)) {
- return false;
- }
- const sversion = this._sock.rQshiftStr(12).substr(4, 7);
- Log.Info("Server ProtocolVersion: " + sversion);
- let is_repeater = 0;
- switch (sversion) {
- case "000.000": // UltraVNC repeater
- is_repeater = 1;
- break;
- case "003.003":
- case "003.006": // UltraVNC
- case "003.889": // Apple Remote Desktop
- this._rfb_version = 3.3;
- break;
- case "003.007":
- this._rfb_version = 3.7;
- break;
- case "003.008":
- case "004.000": // Intel AMT KVM
- case "004.001": // RealVNC 4.6
- case "005.000": // RealVNC 5.3
- this._rfb_version = 3.8;
- break;
- default:
- return this._fail("Invalid server version " + sversion);
- }
- if (is_repeater) {
- let repeaterID = "ID:" + this._repeaterID;
- while (repeaterID.length < 250) {
- repeaterID += "\0";
- }
- this._sock.send_string(repeaterID);
- return true;
- }
- if (this._rfb_version > this._rfb_max_version) {
- this._rfb_version = this._rfb_max_version;
- }
- const cversion = "00" + parseInt(this._rfb_version, 10) +
- ".00" + ((this._rfb_version * 10) % 10);
- this._sock.send_string("RFB " + cversion + "\n");
- Log.Debug('Sent ProtocolVersion: ' + cversion);
- this._rfb_init_state = 'Security';
- }
- _negotiate_security() {
- // Polyfill since IE and PhantomJS doesn't have
- // TypedArray.includes()
- function includes(item, array) {
- for (let i = 0; i < array.length; i++) {
- if (array[i] === item) {
- return true;
- }
- }
- return false;
- }
- if (this._rfb_version >= 3.7) {
- // Server sends supported list, client decides
- const num_types = this._sock.rQshift8();
- if (this._sock.rQwait("security type", num_types, 1)) { return false; }
- if (num_types === 0) {
- this._rfb_init_state = "SecurityReason";
- this._security_context = "no security types";
- this._security_status = 1;
- return this._init_msg();
- }
- const types = this._sock.rQshiftBytes(num_types);
- Log.Debug("Server security types: " + types);
- // Look for each auth in preferred order
- if (includes(1, types)) {
- this._rfb_auth_scheme = 1; // None
- } else if (includes(22, types)) {
- this._rfb_auth_scheme = 22; // XVP
- } else if (includes(16, types)) {
- this._rfb_auth_scheme = 16; // Tight
- } else if (includes(2, types)) {
- this._rfb_auth_scheme = 2; // VNC Auth
- } else {
- return this._fail("Unsupported security types (types: " + types + ")");
- }
- this._sock.send([this._rfb_auth_scheme]);
- } else {
- // Server decides
- if (this._sock.rQwait("security scheme", 4)) { return false; }
- this._rfb_auth_scheme = this._sock.rQshift32();
- if (this._rfb_auth_scheme == 0) {
- this._rfb_init_state = "SecurityReason";
- this._security_context = "authentication scheme";
- this._security_status = 1;
- return this._init_msg();
- }
- }
- this._rfb_init_state = 'Authentication';
- Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
- return this._init_msg(); // jump to authentication
- }
- _handle_security_reason() {
- if (this._sock.rQwait("reason length", 4)) {
- return false;
- }
- const strlen = this._sock.rQshift32();
- let reason = "";
- if (strlen > 0) {
- if (this._sock.rQwait("reason", strlen, 4)) { return false; }
- reason = this._sock.rQshiftStr(strlen);
- }
- if (reason !== "") {
- this.dispatchEvent(new CustomEvent(
- "securityfailure",
- { detail: { status: this._security_status,
- reason: reason } }));
- return this._fail("Security negotiation failed on " +
- this._security_context +
- " (reason: " + reason + ")");
- } else {
- this.dispatchEvent(new CustomEvent(
- "securityfailure",
- { detail: { status: this._security_status } }));
- return this._fail("Security negotiation failed on " +
- this._security_context);
- }
- }
- // authentication
- _negotiate_xvp_auth() {
- if (!this._rfb_credentials.username ||
- !this._rfb_credentials.password ||
- !this._rfb_credentials.target) {
- this.dispatchEvent(new CustomEvent(
- "credentialsrequired",
- { detail: { types: ["username", "password", "target"] } }));
- return false;
- }
- const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
- String.fromCharCode(this._rfb_credentials.target.length) +
- this._rfb_credentials.username +
- this._rfb_credentials.target;
- this._sock.send_string(xvp_auth_str);
- this._rfb_auth_scheme = 2;
- return this._negotiate_authentication();
- }
- _negotiate_std_vnc_auth() {
- if (this._sock.rQwait("auth challenge", 16)) { return false; }
- if (!this._rfb_credentials.password) {
- this.dispatchEvent(new CustomEvent(
- "credentialsrequired",
- { detail: { types: ["password"] } }));
- return false;
- }
- // TODO(directxman12): make genDES not require an Array
- const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
- const response = RFB.genDES(this._rfb_credentials.password, challenge);
- this._sock.send(response);
- this._rfb_init_state = "SecurityResult";
- return true;
- }
- _negotiate_tight_tunnels(numTunnels) {
- const clientSupportedTunnelTypes = {
- 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
- };
- const serverSupportedTunnelTypes = {};
- // receive tunnel capabilities
- for (let i = 0; i < numTunnels; i++) {
- const cap_code = this._sock.rQshift32();
- const cap_vendor = this._sock.rQshiftStr(4);
- const cap_signature = this._sock.rQshiftStr(8);
- serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
- }
- Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
- // Siemens touch panels have a VNC server that supports NOTUNNEL,
- // but forgets to advertise it. Try to detect such servers by
- // looking for their custom tunnel type.
- if (serverSupportedTunnelTypes[1] &&
- (serverSupportedTunnelTypes[1].vendor === "SICR") &&
- (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
- Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
- serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
- }
- // choose the notunnel type
- if (serverSupportedTunnelTypes[0]) {
- if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
- serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
- return this._fail("Client's tunnel type had the incorrect " +
- "vendor or signature");
- }
- Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
- this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
- return false; // wait until we receive the sub auth count to continue
- } else {
- return this._fail("Server wanted tunnels, but doesn't support " +
- "the notunnel type");
- }
- }
- _negotiate_tight_auth() {
- if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
- if (this._sock.rQwait("num tunnels", 4)) { return false; }
- const numTunnels = this._sock.rQshift32();
- if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
- this._rfb_tightvnc = true;
- if (numTunnels > 0) {
- this._negotiate_tight_tunnels(numTunnels);
- return false; // wait until we receive the sub auth to continue
- }
- }
- // second pass, do the sub-auth negotiation
- if (this._sock.rQwait("sub auth count", 4)) { return false; }
- const subAuthCount = this._sock.rQshift32();
- if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
- this._rfb_init_state = 'SecurityResult';
- return true;
- }
- if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
- const clientSupportedTypes = {
- 'STDVNOAUTH__': 1,
- 'STDVVNCAUTH_': 2
- };
- const serverSupportedTypes = [];
- for (let i = 0; i < subAuthCount; i++) {
- this._sock.rQshift32(); // capNum
- const capabilities = this._sock.rQshiftStr(12);
- serverSupportedTypes.push(capabilities);
- }
- Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
- for (let authType in clientSupportedTypes) {
- if (serverSupportedTypes.indexOf(authType) != -1) {
- this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
- Log.Debug("Selected authentication type: " + authType);
- switch (authType) {
- case 'STDVNOAUTH__': // no auth
- this._rfb_init_state = 'SecurityResult';
- return true;
- case 'STDVVNCAUTH_': // VNC auth
- this._rfb_auth_scheme = 2;
- return this._init_msg();
- default:
- return this._fail("Unsupported tiny auth scheme " +
- "(scheme: " + authType + ")");
- }
- }
- }
- return this._fail("No supported sub-auth types!");
- }
- _negotiate_authentication() {
- switch (this._rfb_auth_scheme) {
- case 1: // no auth
- if (this._rfb_version >= 3.8) {
- this._rfb_init_state = 'SecurityResult';
- return true;
- }
- this._rfb_init_state = 'ClientInitialisation';
- return this._init_msg();
- case 22: // XVP auth
- return this._negotiate_xvp_auth();
- case 2: // VNC authentication
- return this._negotiate_std_vnc_auth();
- case 16: // TightVNC Security Type
- return this._negotiate_tight_auth();
- default:
- return this._fail("Unsupported auth scheme (scheme: " +
- this._rfb_auth_scheme + ")");
- }
- }
- _handle_security_result() {
- if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
- const status = this._sock.rQshift32();
- if (status === 0) { // OK
- this._rfb_init_state = 'ClientInitialisation';
- Log.Debug('Authentication OK');
- return this._init_msg();
- } else {
- if (this._rfb_version >= 3.8) {
- this._rfb_init_state = "SecurityReason";
- this._security_context = "security result";
- this._security_status = status;
- return this._init_msg();
- } else {
- this.dispatchEvent(new CustomEvent(
- "securityfailure",
- { detail: { status: status } }));
- return this._fail("Security handshake failed");
- }
- }
- }
- _negotiate_server_init() {
- if (this._sock.rQwait("server initialization", 24)) { return false; }
- /* Screen size */
- const width = this._sock.rQshift16();
- const height = this._sock.rQshift16();
- /* PIXEL_FORMAT */
- const bpp = this._sock.rQshift8();
- const depth = this._sock.rQshift8();
- const big_endian = this._sock.rQshift8();
- const true_color = this._sock.rQshift8();
- const red_max = this._sock.rQshift16();
- const green_max = this._sock.rQshift16();
- const blue_max = this._sock.rQshift16();
- const red_shift = this._sock.rQshift8();
- const green_shift = this._sock.rQshift8();
- const blue_shift = this._sock.rQshift8();
- this._sock.rQskipBytes(3); // padding
- // NB(directxman12): we don't want to call any callbacks or print messages until
- // *after* we're past the point where we could backtrack
- /* Connection name/title */
- const name_length = this._sock.rQshift32();
- if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
- this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
- if (this._rfb_tightvnc) {
- if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
- // In TightVNC mode, ServerInit message is extended
- const numServerMessages = this._sock.rQshift16();
- const numClientMessages = this._sock.rQshift16();
- const numEncodings = this._sock.rQshift16();
- this._sock.rQskipBytes(2); // padding
- const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
- if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
- // we don't actually do anything with the capability information that TIGHT sends,
- // so we just skip the all of this.
- // TIGHT server message capabilities
- this._sock.rQskipBytes(16 * numServerMessages);
- // TIGHT client message capabilities
- this._sock.rQskipBytes(16 * numClientMessages);
- // TIGHT encoding capabilities
- this._sock.rQskipBytes(16 * numEncodings);
- }
- // NB(directxman12): these are down here so that we don't run them multiple times
- // if we backtrack
- Log.Info("Screen: " + width + "x" + height +
- ", bpp: " + bpp + ", depth: " + depth +
- ", big_endian: " + big_endian +
- ", true_color: " + true_color +
- ", red_max: " + red_max +
- ", green_max: " + green_max +
- ", blue_max: " + blue_max +
- ", red_shift: " + red_shift +
- ", green_shift: " + green_shift +
- ", blue_shift: " + blue_shift);
- if (big_endian !== 0) {
- Log.Warn("Server native endian is not little endian");
- }
- if (red_shift !== 16) {
- Log.Warn("Server native red-shift is not 16");
- }
- if (blue_shift !== 0) {
- Log.Warn("Server native blue-shift is not 0");
- }
- // we're past the point where we could backtrack, so it's safe to call this
- this.dispatchEvent(new CustomEvent(
- "desktopname",
- { detail: { name: this._fb_name } }));
- this._resize(width, height);
- if (!this._viewOnly) { this._keyboard.grab(); }
- if (!this._viewOnly) { this._mouse.grab(); }
- this._fb_depth = 24;
- if (this._fb_name === "Intel(r) AMT KVM") {
- Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
- this._fb_depth = 8;
- }
- RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
- this._sendEncodings();
- RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
- this._updateConnectionState('connected');
- return true;
- }
- _sendEncodings() {
- const encs = [];
- // In preference order
- encs.push(encodings.encodingCopyRect);
- // Only supported with full depth support
- if (this._fb_depth == 24) {
- encs.push(encodings.encodingTight);
- encs.push(encodings.encodingTightPNG);
- encs.push(encodings.encodingHextile);
- encs.push(encodings.encodingRRE);
- }
- encs.push(encodings.encodingRaw);
- // Psuedo-encoding settings
- encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
- encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
- encs.push(encodings.pseudoEncodingDesktopSize);
- encs.push(encodings.pseudoEncodingLastRect);
- encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
- encs.push(encodings.pseudoEncodingExtendedDesktopSize);
- encs.push(encodings.pseudoEncodingXvp);
- encs.push(encodings.pseudoEncodingFence);
- encs.push(encodings.pseudoEncodingContinuousUpdates);
- if (this._fb_depth == 24) {
- encs.push(encodings.pseudoEncodingCursor);
- }
- RFB.messages.clientEncodings(this._sock, encs);
- }
- /* RFB protocol initialization states:
- * ProtocolVersion
- * Security
- * Authentication
- * SecurityResult
- * ClientInitialization - not triggered by server message
- * ServerInitialization
- */
- _init_msg() {
- switch (this._rfb_init_state) {
- case 'ProtocolVersion':
- return this._negotiate_protocol_version();
- case 'Security':
- return this._negotiate_security();
- case 'Authentication':
- return this._negotiate_authentication();
- case 'SecurityResult':
- return this._handle_security_result();
- case 'SecurityReason':
- return this._handle_security_reason();
- case 'ClientInitialisation':
- this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
- this._rfb_init_state = 'ServerInitialisation';
- return true;
- case 'ServerInitialisation':
- return this._negotiate_server_init();
- default:
- return this._fail("Unknown init state (state: " +
- this._rfb_init_state + ")");
- }
- }
- _handle_set_colour_map_msg() {
- Log.Debug("SetColorMapEntries");
- return this._fail("Unexpected SetColorMapEntries message");
- }
- _handle_server_cut_text() {
- Log.Debug("ServerCutText");
- if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
- this._sock.rQskipBytes(3); // Padding
- const length = this._sock.rQshift32();
- if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
- const text = this._sock.rQshiftStr(length);
- if (this._viewOnly) { return true; }
- this.dispatchEvent(new CustomEvent(
- "clipboard",
- { detail: { text: text } }));
- return true;
- }
- _handle_server_fence_msg() {
- if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
- this._sock.rQskipBytes(3); // Padding
- let flags = this._sock.rQshift32();
- let length = this._sock.rQshift8();
- if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
- if (length > 64) {
- Log.Warn("Bad payload length (" + length + ") in fence response");
- length = 64;
- }
- const payload = this._sock.rQshiftStr(length);
- this._supportsFence = true;
- /*
- * Fence flags
- *
- * (1<<0) - BlockBefore
- * (1<<1) - BlockAfter
- * (1<<2) - SyncNext
- * (1<<31) - Request
- */
- if (!(flags & (1<<31))) {
- return this._fail("Unexpected fence response");
- }
- // Filter out unsupported flags
- // FIXME: support syncNext
- flags &= (1<<0) | (1<<1);
- // BlockBefore and BlockAfter are automatically handled by
- // the fact that we process each incoming message
- // synchronuosly.
- RFB.messages.clientFence(this._sock, flags, payload);
- return true;
- }
- _handle_xvp_msg() {
- if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
- this._sock.rQskipBytes(1); // Padding
- const xvp_ver = this._sock.rQshift8();
- const xvp_msg = this._sock.rQshift8();
- switch (xvp_msg) {
- case 0: // XVP_FAIL
- Log.Error("XVP Operation Failed");
- break;
- case 1: // XVP_INIT
- this._rfb_xvp_ver = xvp_ver;
- Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
- this._setCapability("power", true);
- break;
- default:
- this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
- break;
- }
- return true;
- }
- _normal_msg() {
- let msg_type;
- if (this._FBU.rects > 0) {
- msg_type = 0;
- } else {
- msg_type = this._sock.rQshift8();
- }
- let first, ret;
- switch (msg_type) {
- case 0: // FramebufferUpdate
- ret = this._framebufferUpdate();
- if (ret && !this._enabledContinuousUpdates) {
- RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
- this._fb_width, this._fb_height);
- }
- return ret;
- case 1: // SetColorMapEntries
- return this._handle_set_colour_map_msg();
- case 2: // Bell
- Log.Debug("Bell");
- this.dispatchEvent(new CustomEvent(
- "bell",
- { detail: {} }));
- return true;
- case 3: // ServerCutText
- return this._handle_server_cut_text();
- case 150: // EndOfContinuousUpdates
- first = !this._supportsContinuousUpdates;
- this._supportsContinuousUpdates = true;
- this._enabledContinuousUpdates = false;
- if (first) {
- this._enabledContinuousUpdates = true;
- this._updateContinuousUpdates();
- Log.Info("Enabling continuous updates.");
- } else {
- // FIXME: We need to send a framebufferupdaterequest here
- // if we add support for turning off continuous updates
- }
- return true;
- case 248: // ServerFence
- return this._handle_server_fence_msg();
- case 250: // XVP
- return this._handle_xvp_msg();
- default:
- this._fail("Unexpected server message (type " + msg_type + ")");
- Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
- return true;
- }
- }
- _onFlush() {
- this._flushing = false;
- // Resume processing
- if (this._sock.rQlen > 0) {
- this._handle_message();
- }
- }
- _framebufferUpdate() {
- if (this._FBU.rects === 0) {
- if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
- this._sock.rQskipBytes(1); // Padding
- this._FBU.rects = this._sock.rQshift16();
- // Make sure the previous frame is fully rendered first
- // to avoid building up an excessive queue
- if (this._display.pending()) {
- this._flushing = true;
- this._display.flush();
- return false;
- }
- }
- while (this._FBU.rects > 0) {
- if (this._FBU.encoding === null) {
- if (this._sock.rQwait("rect header", 12)) { return false; }
- /* New FramebufferUpdate */
- const hdr = this._sock.rQshiftBytes(12);
- this._FBU.x = (hdr[0] << 8) + hdr[1];
- this._FBU.y = (hdr[2] << 8) + hdr[3];
- this._FBU.width = (hdr[4] << 8) + hdr[5];
- this._FBU.height = (hdr[6] << 8) + hdr[7];
- this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
- (hdr[10] << 8) + hdr[11], 10);
- }
- if (!this._handleRect()) {
- return false;
- }
- this._FBU.rects--;
- this._FBU.encoding = null;
- }
- this._display.flip();
- return true; // We finished this FBU
- }
- _handleRect() {
- switch (this._FBU.encoding) {
- case encodings.pseudoEncodingLastRect:
- this._FBU.rects = 1; // Will be decreased when we return
- return true;
- case encodings.pseudoEncodingCursor:
- return this._handleCursor();
- case encodings.pseudoEncodingQEMUExtendedKeyEvent:
- // Old Safari doesn't support creating keyboard events
- try {
- const keyboardEvent = document.createEvent("keyboardEvent");
- if (keyboardEvent.code !== undefined) {
- this._qemuExtKeyEventSupported = true;
- }
- } catch (err) {
- // Do nothing
- }
- return true;
- case encodings.pseudoEncodingDesktopSize:
- this._resize(this._FBU.width, this._FBU.height);
- return true;
- case encodings.pseudoEncodingExtendedDesktopSize:
- return this._handleExtendedDesktopSize();
- default:
- return this._handleDataRect();
- }
- }
- _handleCursor() {
- const hotx = this._FBU.x; // hotspot-x
- const hoty = this._FBU.y; // hotspot-y
- const w = this._FBU.width;
- const h = this._FBU.height;
- const pixelslength = w * h * 4;
- const masklength = Math.ceil(w / 8) * h;
- let bytes = pixelslength + masklength;
- if (this._sock.rQwait("cursor encoding", bytes)) {
- return false;
- }
- // Decode from BGRX pixels + bit mask to RGBA
- const pixels = this._sock.rQshiftBytes(pixelslength);
- const mask = this._sock.rQshiftBytes(masklength);
- let rgba = new Uint8Array(w * h * 4);
- let pix_idx = 0;
- for (let y = 0; y < h; y++) {
- for (let x = 0; x < w; x++) {
- let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
- let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0;
- rgba[pix_idx ] = pixels[pix_idx + 2];
- rgba[pix_idx + 1] = pixels[pix_idx + 1];
- rgba[pix_idx + 2] = pixels[pix_idx];
- rgba[pix_idx + 3] = alpha;
- pix_idx += 4;
- }
- }
- this._updateCursor(rgba, hotx, hoty, w, h);
- return true;
- }
- _handleExtendedDesktopSize() {
- if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
- return false;
- }
- const number_of_screens = this._sock.rQpeek8();
- let bytes = 4 + (number_of_screens * 16);
- if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
- return false;
- }
- const firstUpdate = !this._supportsSetDesktopSize;
- this._supportsSetDesktopSize = true;
- // Normally we only apply the current resize mode after a
- // window resize event. However there is no such trigger on the
- // initial connect. And we don't know if the server supports
- // resizing until we've gotten here.
- if (firstUpdate) {
- this._requestRemoteResize();
- }
- this._sock.rQskipBytes(1); // number-of-screens
- this._sock.rQskipBytes(3); // padding
- for (let i = 0; i < number_of_screens; i += 1) {
- // Save the id and flags of the first screen
- if (i === 0) {
- this._screen_id = this._sock.rQshiftBytes(4); // id
- this._sock.rQskipBytes(2); // x-position
- this._sock.rQskipBytes(2); // y-position
- this._sock.rQskipBytes(2); // width
- this._sock.rQskipBytes(2); // height
- this._screen_flags = this._sock.rQshiftBytes(4); // flags
- } else {
- this._sock.rQskipBytes(16);
- }
- }
- /*
- * The x-position indicates the reason for the change:
- *
- * 0 - server resized on its own
- * 1 - this client requested the resize
- * 2 - another client requested the resize
- */
- // We need to handle errors when we requested the resize.
- if (this._FBU.x === 1 && this._FBU.y !== 0) {
- let msg = "";
- // The y-position indicates the status code from the server
- switch (this._FBU.y) {
- case 1:
- msg = "Resize is administratively prohibited";
- break;
- case 2:
- msg = "Out of resources";
- break;
- case 3:
- msg = "Invalid screen layout";
- break;
- default:
- msg = "Unknown reason";
- break;
- }
- Log.Warn("Server did not accept the resize request: "
- + msg);
- } else {
- this._resize(this._FBU.width, this._FBU.height);
- }
- return true;
- }
- _handleDataRect() {
- let decoder = this._decoders[this._FBU.encoding];
- if (!decoder) {
- this._fail("Unsupported encoding (encoding: " +
- this._FBU.encoding + ")");
- return false;
- }
- try {
- return decoder.decodeRect(this._FBU.x, this._FBU.y,
- this._FBU.width, this._FBU.height,
- this._sock, this._display,
- this._fb_depth);
- } catch (err) {
- this._fail("Error decoding rect: " + err);
- return false;
- }
- }
- _updateContinuousUpdates() {
- if (!this._enabledContinuousUpdates) { return; }
- RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
- this._fb_width, this._fb_height);
- }
- _resize(width, height) {
- this._fb_width = width;
- this._fb_height = height;
- this._display.resize(this._fb_width, this._fb_height);
- // Adjust the visible viewport based on the new dimensions
- this._updateClip();
- this._updateScale();
- this._updateContinuousUpdates();
- }
- _xvpOp(ver, op) {
- if (this._rfb_xvp_ver < ver) { return; }
- Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
- RFB.messages.xvpOp(this._sock, ver, op);
- }
- _updateCursor(rgba, hotx, hoty, w, h) {
- this._cursorImage = {
- rgbaPixels: rgba,
- hotx: hotx, hoty: hoty, w: w, h: h,
- };
- this._refreshCursor();
- }
- _shouldShowDotCursor() {
- // Called when this._cursorImage is updated
- if (!this._showDotCursor) {
- // User does not want to see the dot, so...
- return false;
- }
- // The dot should not be shown if the cursor is already visible,
- // i.e. contains at least one not-fully-transparent pixel.
- // So iterate through all alpha bytes in rgba and stop at the
- // first non-zero.
- for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
- if (this._cursorImage.rgbaPixels[i]) {
- return false;
- }
- }
- // At this point, we know that the cursor is fully transparent, and
- // the user wants to see the dot instead of this.
- return true;
- }
- _refreshCursor() {
- const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
- this._cursor.change(image.rgbaPixels,
- image.hotx, image.hoty,
- image.w, image.h
- );
- }
- static genDES(password, challenge) {
- const passwordChars = password.split('').map(c => c.charCodeAt(0));
- return (new DES(passwordChars)).encrypt(challenge);
- }
- }
- // Class Methods
- RFB.messages = {
- keyEvent(sock, keysym, down) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 4; // msg-type
- buff[offset + 1] = down;
- buff[offset + 2] = 0;
- buff[offset + 3] = 0;
- buff[offset + 4] = (keysym >> 24);
- buff[offset + 5] = (keysym >> 16);
- buff[offset + 6] = (keysym >> 8);
- buff[offset + 7] = keysym;
- sock._sQlen += 8;
- sock.flush();
- },
- QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
- function getRFBkeycode(xt_scancode) {
- const upperByte = (keycode >> 8);
- const lowerByte = (keycode & 0x00ff);
- if (upperByte === 0xe0 && lowerByte < 0x7f) {
- return lowerByte | 0x80;
- }
- return xt_scancode;
- }
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 255; // msg-type
- buff[offset + 1] = 0; // sub msg-type
- buff[offset + 2] = (down >> 8);
- buff[offset + 3] = down;
- buff[offset + 4] = (keysym >> 24);
- buff[offset + 5] = (keysym >> 16);
- buff[offset + 6] = (keysym >> 8);
- buff[offset + 7] = keysym;
- const RFBkeycode = getRFBkeycode(keycode);
- buff[offset + 8] = (RFBkeycode >> 24);
- buff[offset + 9] = (RFBkeycode >> 16);
- buff[offset + 10] = (RFBkeycode >> 8);
- buff[offset + 11] = RFBkeycode;
- sock._sQlen += 12;
- sock.flush();
- },
- pointerEvent(sock, x, y, mask) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 5; // msg-type
- buff[offset + 1] = mask;
- buff[offset + 2] = x >> 8;
- buff[offset + 3] = x;
- buff[offset + 4] = y >> 8;
- buff[offset + 5] = y;
- sock._sQlen += 6;
- sock.flush();
- },
- // TODO(directxman12): make this unicode compatible?
- clientCutText(sock, text) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 6; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = 0; // padding
- buff[offset + 3] = 0; // padding
- let length = text.length;
- buff[offset + 4] = length >> 24;
- buff[offset + 5] = length >> 16;
- buff[offset + 6] = length >> 8;
- buff[offset + 7] = length;
- sock._sQlen += 8;
- // We have to keep track of from where in the text we begin creating the
- // buffer for the flush in the next iteration.
- let textOffset = 0;
- let remaining = length;
- while (remaining > 0) {
- let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
- for (let i = 0; i < flushSize; i++) {
- buff[sock._sQlen + i] = text.charCodeAt(textOffset + i);
- }
- sock._sQlen += flushSize;
- sock.flush();
- remaining -= flushSize;
- textOffset += flushSize;
- }
- },
- setDesktopSize(sock, width, height, id, flags) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 251; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = width >> 8; // width
- buff[offset + 3] = width;
- buff[offset + 4] = height >> 8; // height
- buff[offset + 5] = height;
- buff[offset + 6] = 1; // number-of-screens
- buff[offset + 7] = 0; // padding
- // screen array
- buff[offset + 8] = id >> 24; // id
- buff[offset + 9] = id >> 16;
- buff[offset + 10] = id >> 8;
- buff[offset + 11] = id;
- buff[offset + 12] = 0; // x-position
- buff[offset + 13] = 0;
- buff[offset + 14] = 0; // y-position
- buff[offset + 15] = 0;
- buff[offset + 16] = width >> 8; // width
- buff[offset + 17] = width;
- buff[offset + 18] = height >> 8; // height
- buff[offset + 19] = height;
- buff[offset + 20] = flags >> 24; // flags
- buff[offset + 21] = flags >> 16;
- buff[offset + 22] = flags >> 8;
- buff[offset + 23] = flags;
- sock._sQlen += 24;
- sock.flush();
- },
- clientFence(sock, flags, payload) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 248; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = 0; // padding
- buff[offset + 3] = 0; // padding
- buff[offset + 4] = flags >> 24; // flags
- buff[offset + 5] = flags >> 16;
- buff[offset + 6] = flags >> 8;
- buff[offset + 7] = flags;
- const n = payload.length;
- buff[offset + 8] = n; // length
- for (let i = 0; i < n; i++) {
- buff[offset + 9 + i] = payload.charCodeAt(i);
- }
- sock._sQlen += 9 + n;
- sock.flush();
- },
- enableContinuousUpdates(sock, enable, x, y, width, height) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 150; // msg-type
- buff[offset + 1] = enable; // enable-flag
- buff[offset + 2] = x >> 8; // x
- buff[offset + 3] = x;
- buff[offset + 4] = y >> 8; // y
- buff[offset + 5] = y;
- buff[offset + 6] = width >> 8; // width
- buff[offset + 7] = width;
- buff[offset + 8] = height >> 8; // height
- buff[offset + 9] = height;
- sock._sQlen += 10;
- sock.flush();
- },
- pixelFormat(sock, depth, true_color) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- let bpp;
- if (depth > 16) {
- bpp = 32;
- } else if (depth > 8) {
- bpp = 16;
- } else {
- bpp = 8;
- }
- const bits = Math.floor(depth/3);
- buff[offset] = 0; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = 0; // padding
- buff[offset + 3] = 0; // padding
- buff[offset + 4] = bpp; // bits-per-pixel
- buff[offset + 5] = depth; // depth
- buff[offset + 6] = 0; // little-endian
- buff[offset + 7] = true_color ? 1 : 0; // true-color
- buff[offset + 8] = 0; // red-max
- buff[offset + 9] = (1 << bits) - 1; // red-max
- buff[offset + 10] = 0; // green-max
- buff[offset + 11] = (1 << bits) - 1; // green-max
- buff[offset + 12] = 0; // blue-max
- buff[offset + 13] = (1 << bits) - 1; // blue-max
- buff[offset + 14] = bits * 2; // red-shift
- buff[offset + 15] = bits * 1; // green-shift
- buff[offset + 16] = bits * 0; // blue-shift
- buff[offset + 17] = 0; // padding
- buff[offset + 18] = 0; // padding
- buff[offset + 19] = 0; // padding
- sock._sQlen += 20;
- sock.flush();
- },
- clientEncodings(sock, encodings) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 2; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = encodings.length >> 8;
- buff[offset + 3] = encodings.length;
- let j = offset + 4;
- for (let i = 0; i < encodings.length; i++) {
- const enc = encodings[i];
- buff[j] = enc >> 24;
- buff[j + 1] = enc >> 16;
- buff[j + 2] = enc >> 8;
- buff[j + 3] = enc;
- j += 4;
- }
- sock._sQlen += j - offset;
- sock.flush();
- },
- fbUpdateRequest(sock, incremental, x, y, w, h) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- if (typeof(x) === "undefined") { x = 0; }
- if (typeof(y) === "undefined") { y = 0; }
- buff[offset] = 3; // msg-type
- buff[offset + 1] = incremental ? 1 : 0;
- buff[offset + 2] = (x >> 8) & 0xFF;
- buff[offset + 3] = x & 0xFF;
- buff[offset + 4] = (y >> 8) & 0xFF;
- buff[offset + 5] = y & 0xFF;
- buff[offset + 6] = (w >> 8) & 0xFF;
- buff[offset + 7] = w & 0xFF;
- buff[offset + 8] = (h >> 8) & 0xFF;
- buff[offset + 9] = h & 0xFF;
- sock._sQlen += 10;
- sock.flush();
- },
- xvpOp(sock, ver, op) {
- const buff = sock._sQ;
- const offset = sock._sQlen;
- buff[offset] = 250; // msg-type
- buff[offset + 1] = 0; // padding
- buff[offset + 2] = ver;
- buff[offset + 3] = op;
- sock._sQlen += 4;
- sock.flush();
- }
- };
- RFB.cursors = {
- none: {
- rgbaPixels: new Uint8Array(),
- w: 0, h: 0,
- hotx: 0, hoty: 0,
- },
- dot: {
- /* eslint-disable indent */
- rgbaPixels: new Uint8Array([
- 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
- 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
- 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
- ]),
- /* eslint-enable indent */
- w: 3, h: 3,
- hotx: 1, hoty: 1,
- }
- };
|