rfb.js 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2018 The noVNC Authors
  4. * Licensed under MPL 2.0 (see LICENSE.txt)
  5. *
  6. * See README.md for usage and integration instructions.
  7. *
  8. */
  9. import * as Log from './util/logging.js';
  10. import { decodeUTF8 } from './util/strings.js';
  11. import { dragThreshold } from './util/browser.js';
  12. import EventTargetMixin from './util/eventtarget.js';
  13. import Display from "./display.js";
  14. import Keyboard from "./input/keyboard.js";
  15. import Mouse from "./input/mouse.js";
  16. import Cursor from "./util/cursor.js";
  17. import Websock from "./websock.js";
  18. import DES from "./des.js";
  19. import KeyTable from "./input/keysym.js";
  20. import XtScancode from "./input/xtscancodes.js";
  21. import { encodings } from "./encodings.js";
  22. import "./util/polyfill.js";
  23. import RawDecoder from "./decoders/raw.js";
  24. import CopyRectDecoder from "./decoders/copyrect.js";
  25. import RREDecoder from "./decoders/rre.js";
  26. import HextileDecoder from "./decoders/hextile.js";
  27. import TightDecoder from "./decoders/tight.js";
  28. import TightPNGDecoder from "./decoders/tightpng.js";
  29. // How many seconds to wait for a disconnect to finish
  30. const DISCONNECT_TIMEOUT = 3;
  31. const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
  32. export default class RFB extends EventTargetMixin {
  33. constructor(target, url, options) {
  34. if (!target) {
  35. throw new Error("Must specify target");
  36. }
  37. if (!url) {
  38. throw new Error("Must specify URL");
  39. }
  40. super();
  41. this._target = target;
  42. this._url = url;
  43. // Connection details
  44. options = options || {};
  45. this._rfb_credentials = options.credentials || {};
  46. this._shared = 'shared' in options ? !!options.shared : true;
  47. this._repeaterID = options.repeaterID || '';
  48. this._showDotCursor = options.showDotCursor || false;
  49. // Internal state
  50. this._rfb_connection_state = '';
  51. this._rfb_init_state = '';
  52. this._rfb_auth_scheme = -1;
  53. this._rfb_clean_disconnect = true;
  54. // Server capabilities
  55. this._rfb_version = 0;
  56. this._rfb_max_version = 3.8;
  57. this._rfb_tightvnc = false;
  58. this._rfb_xvp_ver = 0;
  59. this._fb_width = 0;
  60. this._fb_height = 0;
  61. this._fb_name = "";
  62. this._capabilities = { power: false };
  63. this._supportsFence = false;
  64. this._supportsContinuousUpdates = false;
  65. this._enabledContinuousUpdates = false;
  66. this._supportsSetDesktopSize = false;
  67. this._screen_id = 0;
  68. this._screen_flags = 0;
  69. this._qemuExtKeyEventSupported = false;
  70. // Internal objects
  71. this._sock = null; // Websock object
  72. this._display = null; // Display object
  73. this._flushing = false; // Display flushing state
  74. this._keyboard = null; // Keyboard input handler object
  75. this._mouse = null; // Mouse input handler object
  76. // Timers
  77. this._disconnTimer = null; // disconnection timer
  78. this._resizeTimeout = null; // resize rate limiting
  79. // Decoder states
  80. this._decoders = {};
  81. this._FBU = {
  82. rects: 0,
  83. x: 0,
  84. y: 0,
  85. width: 0,
  86. height: 0,
  87. encoding: null,
  88. };
  89. // Mouse state
  90. this._mouse_buttonMask = 0;
  91. this._mouse_arr = [];
  92. this._viewportDragging = false;
  93. this._viewportDragPos = {};
  94. this._viewportHasMoved = false;
  95. // Bound event handlers
  96. this._eventHandlers = {
  97. focusCanvas: this._focusCanvas.bind(this),
  98. windowResize: this._windowResize.bind(this),
  99. };
  100. // main setup
  101. Log.Debug(">> RFB.constructor");
  102. // Create DOM elements
  103. this._screen = document.createElement('div');
  104. this._screen.style.display = 'flex';
  105. this._screen.style.width = '100%';
  106. this._screen.style.height = '100%';
  107. this._screen.style.overflow = 'auto';
  108. this._screen.style.background = DEFAULT_BACKGROUND;
  109. this._canvas = document.createElement('canvas');
  110. this._canvas.style.margin = 'auto';
  111. // Some browsers add an outline on focus
  112. this._canvas.style.outline = 'none';
  113. // IE miscalculates width without this :(
  114. this._canvas.style.flexShrink = '0';
  115. this._canvas.width = 0;
  116. this._canvas.height = 0;
  117. this._canvas.tabIndex = -1;
  118. this._screen.appendChild(this._canvas);
  119. // Cursor
  120. this._cursor = new Cursor();
  121. // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
  122. // it. Result: no cursor at all until a window border or an edit field
  123. // is hit blindly. But there are also VNC servers that draw the cursor
  124. // in the framebuffer and don't send the empty local cursor. There is
  125. // no way to satisfy both sides.
  126. //
  127. // The spec is unclear on this "initial cursor" issue. Many other
  128. // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
  129. // initial cursor instead.
  130. this._cursorImage = RFB.cursors.none;
  131. // populate decoder array with objects
  132. this._decoders[encodings.encodingRaw] = new RawDecoder();
  133. this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
  134. this._decoders[encodings.encodingRRE] = new RREDecoder();
  135. this._decoders[encodings.encodingHextile] = new HextileDecoder();
  136. this._decoders[encodings.encodingTight] = new TightDecoder();
  137. this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
  138. // NB: nothing that needs explicit teardown should be done
  139. // before this point, since this can throw an exception
  140. try {
  141. this._display = new Display(this._canvas);
  142. } catch (exc) {
  143. Log.Error("Display exception: " + exc);
  144. throw exc;
  145. }
  146. this._display.onflush = this._onFlush.bind(this);
  147. this._display.clear();
  148. this._keyboard = new Keyboard(this._canvas);
  149. this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
  150. this._mouse = new Mouse(this._canvas);
  151. this._mouse.onmousebutton = this._handleMouseButton.bind(this);
  152. this._mouse.onmousemove = this._handleMouseMove.bind(this);
  153. this._sock = new Websock();
  154. this._sock.on('message', () => {
  155. this._handle_message();
  156. });
  157. this._sock.on('open', () => {
  158. if ((this._rfb_connection_state === 'connecting') &&
  159. (this._rfb_init_state === '')) {
  160. this._rfb_init_state = 'ProtocolVersion';
  161. Log.Debug("Starting VNC handshake");
  162. } else {
  163. this._fail("Unexpected server connection while " +
  164. this._rfb_connection_state);
  165. }
  166. });
  167. this._sock.on('close', (e) => {
  168. Log.Debug("WebSocket on-close event");
  169. let msg = "";
  170. if (e.code) {
  171. msg = "(code: " + e.code;
  172. if (e.reason) {
  173. msg += ", reason: " + e.reason;
  174. }
  175. msg += ")";
  176. }
  177. switch (this._rfb_connection_state) {
  178. case 'connecting':
  179. this._fail("Connection closed " + msg);
  180. break;
  181. case 'connected':
  182. // Handle disconnects that were initiated server-side
  183. this._updateConnectionState('disconnecting');
  184. this._updateConnectionState('disconnected');
  185. break;
  186. case 'disconnecting':
  187. // Normal disconnection path
  188. this._updateConnectionState('disconnected');
  189. break;
  190. case 'disconnected':
  191. this._fail("Unexpected server disconnect " +
  192. "when already disconnected " + msg);
  193. break;
  194. default:
  195. this._fail("Unexpected server disconnect before connecting " +
  196. msg);
  197. break;
  198. }
  199. this._sock.off('close');
  200. });
  201. this._sock.on('error', e => Log.Warn("WebSocket on-error event"));
  202. // Slight delay of the actual connection so that the caller has
  203. // time to set up callbacks
  204. setTimeout(this._updateConnectionState.bind(this, 'connecting'));
  205. Log.Debug("<< RFB.constructor");
  206. // ===== PROPERTIES =====
  207. this.dragViewport = false;
  208. this.focusOnClick = true;
  209. this._viewOnly = false;
  210. this._clipViewport = false;
  211. this._scaleViewport = false;
  212. this._resizeSession = false;
  213. }
  214. // ===== PROPERTIES =====
  215. get viewOnly() { return this._viewOnly; }
  216. set viewOnly(viewOnly) {
  217. this._viewOnly = viewOnly;
  218. if (this._rfb_connection_state === "connecting" ||
  219. this._rfb_connection_state === "connected") {
  220. if (viewOnly) {
  221. this._keyboard.ungrab();
  222. this._mouse.ungrab();
  223. } else {
  224. this._keyboard.grab();
  225. this._mouse.grab();
  226. }
  227. }
  228. }
  229. get capabilities() { return this._capabilities; }
  230. get touchButton() { return this._mouse.touchButton; }
  231. set touchButton(button) { this._mouse.touchButton = button; }
  232. get clipViewport() { return this._clipViewport; }
  233. set clipViewport(viewport) {
  234. this._clipViewport = viewport;
  235. this._updateClip();
  236. }
  237. get scaleViewport() { return this._scaleViewport; }
  238. set scaleViewport(scale) {
  239. this._scaleViewport = scale;
  240. // Scaling trumps clipping, so we may need to adjust
  241. // clipping when enabling or disabling scaling
  242. if (scale && this._clipViewport) {
  243. this._updateClip();
  244. }
  245. this._updateScale();
  246. if (!scale && this._clipViewport) {
  247. this._updateClip();
  248. }
  249. }
  250. get resizeSession() { return this._resizeSession; }
  251. set resizeSession(resize) {
  252. this._resizeSession = resize;
  253. if (resize) {
  254. this._requestRemoteResize();
  255. }
  256. }
  257. get showDotCursor() { return this._showDotCursor; }
  258. set showDotCursor(show) {
  259. this._showDotCursor = show;
  260. this._refreshCursor();
  261. }
  262. get background() { return this._screen.style.background; }
  263. set background(cssValue) { this._screen.style.background = cssValue; }
  264. // ===== PUBLIC METHODS =====
  265. disconnect() {
  266. this._updateConnectionState('disconnecting');
  267. this._sock.off('error');
  268. this._sock.off('message');
  269. this._sock.off('open');
  270. }
  271. sendCredentials(creds) {
  272. this._rfb_credentials = creds;
  273. setTimeout(this._init_msg.bind(this), 0);
  274. }
  275. sendCtrlAltDel() {
  276. if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
  277. Log.Info("Sending Ctrl-Alt-Del");
  278. this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
  279. this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
  280. this.sendKey(KeyTable.XK_Delete, "Delete", true);
  281. this.sendKey(KeyTable.XK_Delete, "Delete", false);
  282. this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
  283. this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
  284. }
  285. machineShutdown() {
  286. this._xvpOp(1, 2);
  287. }
  288. machineReboot() {
  289. this._xvpOp(1, 3);
  290. }
  291. machineReset() {
  292. this._xvpOp(1, 4);
  293. }
  294. // Send a key press. If 'down' is not specified then send a down key
  295. // followed by an up key.
  296. sendKey(keysym, code, down) {
  297. if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
  298. if (down === undefined) {
  299. this.sendKey(keysym, code, true);
  300. this.sendKey(keysym, code, false);
  301. return;
  302. }
  303. const scancode = XtScancode[code];
  304. if (this._qemuExtKeyEventSupported && scancode) {
  305. // 0 is NoSymbol
  306. keysym = keysym || 0;
  307. Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
  308. RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
  309. } else {
  310. if (!keysym) {
  311. return;
  312. }
  313. Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
  314. RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
  315. }
  316. }
  317. focus() {
  318. this._canvas.focus();
  319. }
  320. blur() {
  321. this._canvas.blur();
  322. }
  323. clipboardPasteFrom(text) {
  324. if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
  325. RFB.messages.clientCutText(this._sock, text);
  326. }
  327. // ===== PRIVATE METHODS =====
  328. _connect() {
  329. Log.Debug(">> RFB.connect");
  330. Log.Info("connecting to " + this._url);
  331. try {
  332. // WebSocket.onopen transitions to the RFB init states
  333. this._sock.open(this._url, ['binary']);
  334. } catch (e) {
  335. if (e.name === 'SyntaxError') {
  336. this._fail("Invalid host or port (" + e + ")");
  337. } else {
  338. this._fail("Error when opening socket (" + e + ")");
  339. }
  340. }
  341. // Make our elements part of the page
  342. this._target.appendChild(this._screen);
  343. this._cursor.attach(this._canvas);
  344. this._refreshCursor();
  345. // Monitor size changes of the screen
  346. // FIXME: Use ResizeObserver, or hidden overflow
  347. window.addEventListener('resize', this._eventHandlers.windowResize);
  348. // Always grab focus on some kind of click event
  349. this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
  350. this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
  351. Log.Debug("<< RFB.connect");
  352. }
  353. _disconnect() {
  354. Log.Debug(">> RFB.disconnect");
  355. this._cursor.detach();
  356. this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
  357. this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
  358. window.removeEventListener('resize', this._eventHandlers.windowResize);
  359. this._keyboard.ungrab();
  360. this._mouse.ungrab();
  361. this._sock.close();
  362. try {
  363. this._target.removeChild(this._screen);
  364. } catch (e) {
  365. if (e.name === 'NotFoundError') {
  366. // Some cases where the initial connection fails
  367. // can disconnect before the _screen is created
  368. } else {
  369. throw e;
  370. }
  371. }
  372. clearTimeout(this._resizeTimeout);
  373. Log.Debug("<< RFB.disconnect");
  374. }
  375. _focusCanvas(event) {
  376. // Respect earlier handlers' request to not do side-effects
  377. if (event.defaultPrevented) {
  378. return;
  379. }
  380. if (!this.focusOnClick) {
  381. return;
  382. }
  383. this.focus();
  384. }
  385. _windowResize(event) {
  386. // If the window resized then our screen element might have
  387. // as well. Update the viewport dimensions.
  388. window.requestAnimationFrame(() => {
  389. this._updateClip();
  390. this._updateScale();
  391. });
  392. if (this._resizeSession) {
  393. // Request changing the resolution of the remote display to
  394. // the size of the local browser viewport.
  395. // In order to not send multiple requests before the browser-resize
  396. // is finished we wait 0.5 seconds before sending the request.
  397. clearTimeout(this._resizeTimeout);
  398. this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
  399. }
  400. }
  401. // Update state of clipping in Display object, and make sure the
  402. // configured viewport matches the current screen size
  403. _updateClip() {
  404. const cur_clip = this._display.clipViewport;
  405. let new_clip = this._clipViewport;
  406. if (this._scaleViewport) {
  407. // Disable viewport clipping if we are scaling
  408. new_clip = false;
  409. }
  410. if (cur_clip !== new_clip) {
  411. this._display.clipViewport = new_clip;
  412. }
  413. if (new_clip) {
  414. // When clipping is enabled, the screen is limited to
  415. // the size of the container.
  416. const size = this._screenSize();
  417. this._display.viewportChangeSize(size.w, size.h);
  418. this._fixScrollbars();
  419. }
  420. }
  421. _updateScale() {
  422. if (!this._scaleViewport) {
  423. this._display.scale = 1.0;
  424. } else {
  425. const size = this._screenSize();
  426. this._display.autoscale(size.w, size.h);
  427. }
  428. this._fixScrollbars();
  429. }
  430. // Requests a change of remote desktop size. This message is an extension
  431. // and may only be sent if we have received an ExtendedDesktopSize message
  432. _requestRemoteResize() {
  433. clearTimeout(this._resizeTimeout);
  434. this._resizeTimeout = null;
  435. if (!this._resizeSession || this._viewOnly ||
  436. !this._supportsSetDesktopSize) {
  437. return;
  438. }
  439. const size = this._screenSize();
  440. RFB.messages.setDesktopSize(this._sock,
  441. Math.floor(size.w), Math.floor(size.h),
  442. this._screen_id, this._screen_flags);
  443. Log.Debug('Requested new desktop size: ' +
  444. size.w + 'x' + size.h);
  445. }
  446. // Gets the the size of the available screen
  447. _screenSize() {
  448. let r = this._screen.getBoundingClientRect();
  449. return { w: r.width, h: r.height };
  450. }
  451. _fixScrollbars() {
  452. // This is a hack because Chrome screws up the calculation
  453. // for when scrollbars are needed. So to fix it we temporarily
  454. // toggle them off and on.
  455. const orig = this._screen.style.overflow;
  456. this._screen.style.overflow = 'hidden';
  457. // Force Chrome to recalculate the layout by asking for
  458. // an element's dimensions
  459. this._screen.getBoundingClientRect();
  460. this._screen.style.overflow = orig;
  461. }
  462. /*
  463. * Connection states:
  464. * connecting
  465. * connected
  466. * disconnecting
  467. * disconnected - permanent state
  468. */
  469. _updateConnectionState(state) {
  470. const oldstate = this._rfb_connection_state;
  471. if (state === oldstate) {
  472. Log.Debug("Already in state '" + state + "', ignoring");
  473. return;
  474. }
  475. // The 'disconnected' state is permanent for each RFB object
  476. if (oldstate === 'disconnected') {
  477. Log.Error("Tried changing state of a disconnected RFB object");
  478. return;
  479. }
  480. // Ensure proper transitions before doing anything
  481. switch (state) {
  482. case 'connected':
  483. if (oldstate !== 'connecting') {
  484. Log.Error("Bad transition to connected state, " +
  485. "previous connection state: " + oldstate);
  486. return;
  487. }
  488. break;
  489. case 'disconnected':
  490. if (oldstate !== 'disconnecting') {
  491. Log.Error("Bad transition to disconnected state, " +
  492. "previous connection state: " + oldstate);
  493. return;
  494. }
  495. break;
  496. case 'connecting':
  497. if (oldstate !== '') {
  498. Log.Error("Bad transition to connecting state, " +
  499. "previous connection state: " + oldstate);
  500. return;
  501. }
  502. break;
  503. case 'disconnecting':
  504. if (oldstate !== 'connected' && oldstate !== 'connecting') {
  505. Log.Error("Bad transition to disconnecting state, " +
  506. "previous connection state: " + oldstate);
  507. return;
  508. }
  509. break;
  510. default:
  511. Log.Error("Unknown connection state: " + state);
  512. return;
  513. }
  514. // State change actions
  515. this._rfb_connection_state = state;
  516. Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
  517. if (this._disconnTimer && state !== 'disconnecting') {
  518. Log.Debug("Clearing disconnect timer");
  519. clearTimeout(this._disconnTimer);
  520. this._disconnTimer = null;
  521. // make sure we don't get a double event
  522. this._sock.off('close');
  523. }
  524. switch (state) {
  525. case 'connecting':
  526. this._connect();
  527. break;
  528. case 'connected':
  529. this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
  530. break;
  531. case 'disconnecting':
  532. this._disconnect();
  533. this._disconnTimer = setTimeout(() => {
  534. Log.Error("Disconnection timed out.");
  535. this._updateConnectionState('disconnected');
  536. }, DISCONNECT_TIMEOUT * 1000);
  537. break;
  538. case 'disconnected':
  539. this.dispatchEvent(new CustomEvent(
  540. "disconnect", { detail:
  541. { clean: this._rfb_clean_disconnect } }));
  542. break;
  543. }
  544. }
  545. /* Print errors and disconnect
  546. *
  547. * The parameter 'details' is used for information that
  548. * should be logged but not sent to the user interface.
  549. */
  550. _fail(details) {
  551. switch (this._rfb_connection_state) {
  552. case 'disconnecting':
  553. Log.Error("Failed when disconnecting: " + details);
  554. break;
  555. case 'connected':
  556. Log.Error("Failed while connected: " + details);
  557. break;
  558. case 'connecting':
  559. Log.Error("Failed when connecting: " + details);
  560. break;
  561. default:
  562. Log.Error("RFB failure: " + details);
  563. break;
  564. }
  565. this._rfb_clean_disconnect = false; //This is sent to the UI
  566. // Transition to disconnected without waiting for socket to close
  567. this._updateConnectionState('disconnecting');
  568. this._updateConnectionState('disconnected');
  569. return false;
  570. }
  571. _setCapability(cap, val) {
  572. this._capabilities[cap] = val;
  573. this.dispatchEvent(new CustomEvent("capabilities",
  574. { detail: { capabilities: this._capabilities } }));
  575. }
  576. _handle_message() {
  577. if (this._sock.rQlen === 0) {
  578. Log.Warn("handle_message called on an empty receive queue");
  579. return;
  580. }
  581. switch (this._rfb_connection_state) {
  582. case 'disconnected':
  583. Log.Error("Got data while disconnected");
  584. break;
  585. case 'connected':
  586. while (true) {
  587. if (this._flushing) {
  588. break;
  589. }
  590. if (!this._normal_msg()) {
  591. break;
  592. }
  593. if (this._sock.rQlen === 0) {
  594. break;
  595. }
  596. }
  597. break;
  598. default:
  599. this._init_msg();
  600. break;
  601. }
  602. }
  603. _handleKeyEvent(keysym, code, down) {
  604. this.sendKey(keysym, code, down);
  605. }
  606. _handleMouseButton(x, y, down, bmask) {
  607. if (down) {
  608. this._mouse_buttonMask |= bmask;
  609. } else {
  610. this._mouse_buttonMask &= ~bmask;
  611. }
  612. if (this.dragViewport) {
  613. if (down && !this._viewportDragging) {
  614. this._viewportDragging = true;
  615. this._viewportDragPos = {'x': x, 'y': y};
  616. this._viewportHasMoved = false;
  617. // Skip sending mouse events
  618. return;
  619. } else {
  620. this._viewportDragging = false;
  621. // If we actually performed a drag then we are done
  622. // here and should not send any mouse events
  623. if (this._viewportHasMoved) {
  624. return;
  625. }
  626. // Otherwise we treat this as a mouse click event.
  627. // Send the button down event here, as the button up
  628. // event is sent at the end of this function.
  629. RFB.messages.pointerEvent(this._sock,
  630. this._display.absX(x),
  631. this._display.absY(y),
  632. bmask);
  633. }
  634. }
  635. if (this._viewOnly) { return; } // View only, skip mouse events
  636. if (this._rfb_connection_state !== 'connected') { return; }
  637. RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
  638. }
  639. _handleMouseMove(x, y) {
  640. if (this._viewportDragging) {
  641. const deltaX = this._viewportDragPos.x - x;
  642. const deltaY = this._viewportDragPos.y - y;
  643. if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
  644. Math.abs(deltaY) > dragThreshold)) {
  645. this._viewportHasMoved = true;
  646. this._viewportDragPos = {'x': x, 'y': y};
  647. this._display.viewportChangePos(deltaX, deltaY);
  648. }
  649. // Skip sending mouse events
  650. return;
  651. }
  652. if (this._viewOnly) { return; } // View only, skip mouse events
  653. if (this._rfb_connection_state !== 'connected') { return; }
  654. RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
  655. }
  656. // Message Handlers
  657. _negotiate_protocol_version() {
  658. if (this._sock.rQwait("version", 12)) {
  659. return false;
  660. }
  661. const sversion = this._sock.rQshiftStr(12).substr(4, 7);
  662. Log.Info("Server ProtocolVersion: " + sversion);
  663. let is_repeater = 0;
  664. switch (sversion) {
  665. case "000.000": // UltraVNC repeater
  666. is_repeater = 1;
  667. break;
  668. case "003.003":
  669. case "003.006": // UltraVNC
  670. case "003.889": // Apple Remote Desktop
  671. this._rfb_version = 3.3;
  672. break;
  673. case "003.007":
  674. this._rfb_version = 3.7;
  675. break;
  676. case "003.008":
  677. case "004.000": // Intel AMT KVM
  678. case "004.001": // RealVNC 4.6
  679. case "005.000": // RealVNC 5.3
  680. this._rfb_version = 3.8;
  681. break;
  682. default:
  683. return this._fail("Invalid server version " + sversion);
  684. }
  685. if (is_repeater) {
  686. let repeaterID = "ID:" + this._repeaterID;
  687. while (repeaterID.length < 250) {
  688. repeaterID += "\0";
  689. }
  690. this._sock.send_string(repeaterID);
  691. return true;
  692. }
  693. if (this._rfb_version > this._rfb_max_version) {
  694. this._rfb_version = this._rfb_max_version;
  695. }
  696. const cversion = "00" + parseInt(this._rfb_version, 10) +
  697. ".00" + ((this._rfb_version * 10) % 10);
  698. this._sock.send_string("RFB " + cversion + "\n");
  699. Log.Debug('Sent ProtocolVersion: ' + cversion);
  700. this._rfb_init_state = 'Security';
  701. }
  702. _negotiate_security() {
  703. // Polyfill since IE and PhantomJS doesn't have
  704. // TypedArray.includes()
  705. function includes(item, array) {
  706. for (let i = 0; i < array.length; i++) {
  707. if (array[i] === item) {
  708. return true;
  709. }
  710. }
  711. return false;
  712. }
  713. if (this._rfb_version >= 3.7) {
  714. // Server sends supported list, client decides
  715. const num_types = this._sock.rQshift8();
  716. if (this._sock.rQwait("security type", num_types, 1)) { return false; }
  717. if (num_types === 0) {
  718. this._rfb_init_state = "SecurityReason";
  719. this._security_context = "no security types";
  720. this._security_status = 1;
  721. return this._init_msg();
  722. }
  723. const types = this._sock.rQshiftBytes(num_types);
  724. Log.Debug("Server security types: " + types);
  725. // Look for each auth in preferred order
  726. if (includes(1, types)) {
  727. this._rfb_auth_scheme = 1; // None
  728. } else if (includes(22, types)) {
  729. this._rfb_auth_scheme = 22; // XVP
  730. } else if (includes(16, types)) {
  731. this._rfb_auth_scheme = 16; // Tight
  732. } else if (includes(2, types)) {
  733. this._rfb_auth_scheme = 2; // VNC Auth
  734. } else {
  735. return this._fail("Unsupported security types (types: " + types + ")");
  736. }
  737. this._sock.send([this._rfb_auth_scheme]);
  738. } else {
  739. // Server decides
  740. if (this._sock.rQwait("security scheme", 4)) { return false; }
  741. this._rfb_auth_scheme = this._sock.rQshift32();
  742. if (this._rfb_auth_scheme == 0) {
  743. this._rfb_init_state = "SecurityReason";
  744. this._security_context = "authentication scheme";
  745. this._security_status = 1;
  746. return this._init_msg();
  747. }
  748. }
  749. this._rfb_init_state = 'Authentication';
  750. Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
  751. return this._init_msg(); // jump to authentication
  752. }
  753. _handle_security_reason() {
  754. if (this._sock.rQwait("reason length", 4)) {
  755. return false;
  756. }
  757. const strlen = this._sock.rQshift32();
  758. let reason = "";
  759. if (strlen > 0) {
  760. if (this._sock.rQwait("reason", strlen, 4)) { return false; }
  761. reason = this._sock.rQshiftStr(strlen);
  762. }
  763. if (reason !== "") {
  764. this.dispatchEvent(new CustomEvent(
  765. "securityfailure",
  766. { detail: { status: this._security_status,
  767. reason: reason } }));
  768. return this._fail("Security negotiation failed on " +
  769. this._security_context +
  770. " (reason: " + reason + ")");
  771. } else {
  772. this.dispatchEvent(new CustomEvent(
  773. "securityfailure",
  774. { detail: { status: this._security_status } }));
  775. return this._fail("Security negotiation failed on " +
  776. this._security_context);
  777. }
  778. }
  779. // authentication
  780. _negotiate_xvp_auth() {
  781. if (!this._rfb_credentials.username ||
  782. !this._rfb_credentials.password ||
  783. !this._rfb_credentials.target) {
  784. this.dispatchEvent(new CustomEvent(
  785. "credentialsrequired",
  786. { detail: { types: ["username", "password", "target"] } }));
  787. return false;
  788. }
  789. const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
  790. String.fromCharCode(this._rfb_credentials.target.length) +
  791. this._rfb_credentials.username +
  792. this._rfb_credentials.target;
  793. this._sock.send_string(xvp_auth_str);
  794. this._rfb_auth_scheme = 2;
  795. return this._negotiate_authentication();
  796. }
  797. _negotiate_std_vnc_auth() {
  798. if (this._sock.rQwait("auth challenge", 16)) { return false; }
  799. if (!this._rfb_credentials.password) {
  800. this.dispatchEvent(new CustomEvent(
  801. "credentialsrequired",
  802. { detail: { types: ["password"] } }));
  803. return false;
  804. }
  805. // TODO(directxman12): make genDES not require an Array
  806. const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
  807. const response = RFB.genDES(this._rfb_credentials.password, challenge);
  808. this._sock.send(response);
  809. this._rfb_init_state = "SecurityResult";
  810. return true;
  811. }
  812. _negotiate_tight_tunnels(numTunnels) {
  813. const clientSupportedTunnelTypes = {
  814. 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
  815. };
  816. const serverSupportedTunnelTypes = {};
  817. // receive tunnel capabilities
  818. for (let i = 0; i < numTunnels; i++) {
  819. const cap_code = this._sock.rQshift32();
  820. const cap_vendor = this._sock.rQshiftStr(4);
  821. const cap_signature = this._sock.rQshiftStr(8);
  822. serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
  823. }
  824. Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
  825. // Siemens touch panels have a VNC server that supports NOTUNNEL,
  826. // but forgets to advertise it. Try to detect such servers by
  827. // looking for their custom tunnel type.
  828. if (serverSupportedTunnelTypes[1] &&
  829. (serverSupportedTunnelTypes[1].vendor === "SICR") &&
  830. (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
  831. Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
  832. serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
  833. }
  834. // choose the notunnel type
  835. if (serverSupportedTunnelTypes[0]) {
  836. if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
  837. serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
  838. return this._fail("Client's tunnel type had the incorrect " +
  839. "vendor or signature");
  840. }
  841. Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
  842. this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
  843. return false; // wait until we receive the sub auth count to continue
  844. } else {
  845. return this._fail("Server wanted tunnels, but doesn't support " +
  846. "the notunnel type");
  847. }
  848. }
  849. _negotiate_tight_auth() {
  850. if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
  851. if (this._sock.rQwait("num tunnels", 4)) { return false; }
  852. const numTunnels = this._sock.rQshift32();
  853. if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
  854. this._rfb_tightvnc = true;
  855. if (numTunnels > 0) {
  856. this._negotiate_tight_tunnels(numTunnels);
  857. return false; // wait until we receive the sub auth to continue
  858. }
  859. }
  860. // second pass, do the sub-auth negotiation
  861. if (this._sock.rQwait("sub auth count", 4)) { return false; }
  862. const subAuthCount = this._sock.rQshift32();
  863. if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
  864. this._rfb_init_state = 'SecurityResult';
  865. return true;
  866. }
  867. if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
  868. const clientSupportedTypes = {
  869. 'STDVNOAUTH__': 1,
  870. 'STDVVNCAUTH_': 2
  871. };
  872. const serverSupportedTypes = [];
  873. for (let i = 0; i < subAuthCount; i++) {
  874. this._sock.rQshift32(); // capNum
  875. const capabilities = this._sock.rQshiftStr(12);
  876. serverSupportedTypes.push(capabilities);
  877. }
  878. Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
  879. for (let authType in clientSupportedTypes) {
  880. if (serverSupportedTypes.indexOf(authType) != -1) {
  881. this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
  882. Log.Debug("Selected authentication type: " + authType);
  883. switch (authType) {
  884. case 'STDVNOAUTH__': // no auth
  885. this._rfb_init_state = 'SecurityResult';
  886. return true;
  887. case 'STDVVNCAUTH_': // VNC auth
  888. this._rfb_auth_scheme = 2;
  889. return this._init_msg();
  890. default:
  891. return this._fail("Unsupported tiny auth scheme " +
  892. "(scheme: " + authType + ")");
  893. }
  894. }
  895. }
  896. return this._fail("No supported sub-auth types!");
  897. }
  898. _negotiate_authentication() {
  899. switch (this._rfb_auth_scheme) {
  900. case 1: // no auth
  901. if (this._rfb_version >= 3.8) {
  902. this._rfb_init_state = 'SecurityResult';
  903. return true;
  904. }
  905. this._rfb_init_state = 'ClientInitialisation';
  906. return this._init_msg();
  907. case 22: // XVP auth
  908. return this._negotiate_xvp_auth();
  909. case 2: // VNC authentication
  910. return this._negotiate_std_vnc_auth();
  911. case 16: // TightVNC Security Type
  912. return this._negotiate_tight_auth();
  913. default:
  914. return this._fail("Unsupported auth scheme (scheme: " +
  915. this._rfb_auth_scheme + ")");
  916. }
  917. }
  918. _handle_security_result() {
  919. if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
  920. const status = this._sock.rQshift32();
  921. if (status === 0) { // OK
  922. this._rfb_init_state = 'ClientInitialisation';
  923. Log.Debug('Authentication OK');
  924. return this._init_msg();
  925. } else {
  926. if (this._rfb_version >= 3.8) {
  927. this._rfb_init_state = "SecurityReason";
  928. this._security_context = "security result";
  929. this._security_status = status;
  930. return this._init_msg();
  931. } else {
  932. this.dispatchEvent(new CustomEvent(
  933. "securityfailure",
  934. { detail: { status: status } }));
  935. return this._fail("Security handshake failed");
  936. }
  937. }
  938. }
  939. _negotiate_server_init() {
  940. if (this._sock.rQwait("server initialization", 24)) { return false; }
  941. /* Screen size */
  942. const width = this._sock.rQshift16();
  943. const height = this._sock.rQshift16();
  944. /* PIXEL_FORMAT */
  945. const bpp = this._sock.rQshift8();
  946. const depth = this._sock.rQshift8();
  947. const big_endian = this._sock.rQshift8();
  948. const true_color = this._sock.rQshift8();
  949. const red_max = this._sock.rQshift16();
  950. const green_max = this._sock.rQshift16();
  951. const blue_max = this._sock.rQshift16();
  952. const red_shift = this._sock.rQshift8();
  953. const green_shift = this._sock.rQshift8();
  954. const blue_shift = this._sock.rQshift8();
  955. this._sock.rQskipBytes(3); // padding
  956. // NB(directxman12): we don't want to call any callbacks or print messages until
  957. // *after* we're past the point where we could backtrack
  958. /* Connection name/title */
  959. const name_length = this._sock.rQshift32();
  960. if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
  961. this._fb_name = decodeUTF8(this._sock.rQshiftStr(name_length));
  962. if (this._rfb_tightvnc) {
  963. if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
  964. // In TightVNC mode, ServerInit message is extended
  965. const numServerMessages = this._sock.rQshift16();
  966. const numClientMessages = this._sock.rQshift16();
  967. const numEncodings = this._sock.rQshift16();
  968. this._sock.rQskipBytes(2); // padding
  969. const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
  970. if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
  971. // we don't actually do anything with the capability information that TIGHT sends,
  972. // so we just skip the all of this.
  973. // TIGHT server message capabilities
  974. this._sock.rQskipBytes(16 * numServerMessages);
  975. // TIGHT client message capabilities
  976. this._sock.rQskipBytes(16 * numClientMessages);
  977. // TIGHT encoding capabilities
  978. this._sock.rQskipBytes(16 * numEncodings);
  979. }
  980. // NB(directxman12): these are down here so that we don't run them multiple times
  981. // if we backtrack
  982. Log.Info("Screen: " + width + "x" + height +
  983. ", bpp: " + bpp + ", depth: " + depth +
  984. ", big_endian: " + big_endian +
  985. ", true_color: " + true_color +
  986. ", red_max: " + red_max +
  987. ", green_max: " + green_max +
  988. ", blue_max: " + blue_max +
  989. ", red_shift: " + red_shift +
  990. ", green_shift: " + green_shift +
  991. ", blue_shift: " + blue_shift);
  992. if (big_endian !== 0) {
  993. Log.Warn("Server native endian is not little endian");
  994. }
  995. if (red_shift !== 16) {
  996. Log.Warn("Server native red-shift is not 16");
  997. }
  998. if (blue_shift !== 0) {
  999. Log.Warn("Server native blue-shift is not 0");
  1000. }
  1001. // we're past the point where we could backtrack, so it's safe to call this
  1002. this.dispatchEvent(new CustomEvent(
  1003. "desktopname",
  1004. { detail: { name: this._fb_name } }));
  1005. this._resize(width, height);
  1006. if (!this._viewOnly) { this._keyboard.grab(); }
  1007. if (!this._viewOnly) { this._mouse.grab(); }
  1008. this._fb_depth = 24;
  1009. if (this._fb_name === "Intel(r) AMT KVM") {
  1010. Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
  1011. this._fb_depth = 8;
  1012. }
  1013. RFB.messages.pixelFormat(this._sock, this._fb_depth, true);
  1014. this._sendEncodings();
  1015. RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fb_width, this._fb_height);
  1016. this._updateConnectionState('connected');
  1017. return true;
  1018. }
  1019. _sendEncodings() {
  1020. const encs = [];
  1021. // In preference order
  1022. encs.push(encodings.encodingCopyRect);
  1023. // Only supported with full depth support
  1024. if (this._fb_depth == 24) {
  1025. encs.push(encodings.encodingTight);
  1026. encs.push(encodings.encodingTightPNG);
  1027. encs.push(encodings.encodingHextile);
  1028. encs.push(encodings.encodingRRE);
  1029. }
  1030. encs.push(encodings.encodingRaw);
  1031. // Psuedo-encoding settings
  1032. encs.push(encodings.pseudoEncodingQualityLevel0 + 6);
  1033. encs.push(encodings.pseudoEncodingCompressLevel0 + 2);
  1034. encs.push(encodings.pseudoEncodingDesktopSize);
  1035. encs.push(encodings.pseudoEncodingLastRect);
  1036. encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
  1037. encs.push(encodings.pseudoEncodingExtendedDesktopSize);
  1038. encs.push(encodings.pseudoEncodingXvp);
  1039. encs.push(encodings.pseudoEncodingFence);
  1040. encs.push(encodings.pseudoEncodingContinuousUpdates);
  1041. if (this._fb_depth == 24) {
  1042. encs.push(encodings.pseudoEncodingCursor);
  1043. }
  1044. RFB.messages.clientEncodings(this._sock, encs);
  1045. }
  1046. /* RFB protocol initialization states:
  1047. * ProtocolVersion
  1048. * Security
  1049. * Authentication
  1050. * SecurityResult
  1051. * ClientInitialization - not triggered by server message
  1052. * ServerInitialization
  1053. */
  1054. _init_msg() {
  1055. switch (this._rfb_init_state) {
  1056. case 'ProtocolVersion':
  1057. return this._negotiate_protocol_version();
  1058. case 'Security':
  1059. return this._negotiate_security();
  1060. case 'Authentication':
  1061. return this._negotiate_authentication();
  1062. case 'SecurityResult':
  1063. return this._handle_security_result();
  1064. case 'SecurityReason':
  1065. return this._handle_security_reason();
  1066. case 'ClientInitialisation':
  1067. this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
  1068. this._rfb_init_state = 'ServerInitialisation';
  1069. return true;
  1070. case 'ServerInitialisation':
  1071. return this._negotiate_server_init();
  1072. default:
  1073. return this._fail("Unknown init state (state: " +
  1074. this._rfb_init_state + ")");
  1075. }
  1076. }
  1077. _handle_set_colour_map_msg() {
  1078. Log.Debug("SetColorMapEntries");
  1079. return this._fail("Unexpected SetColorMapEntries message");
  1080. }
  1081. _handle_server_cut_text() {
  1082. Log.Debug("ServerCutText");
  1083. if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
  1084. this._sock.rQskipBytes(3); // Padding
  1085. const length = this._sock.rQshift32();
  1086. if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
  1087. const text = this._sock.rQshiftStr(length);
  1088. if (this._viewOnly) { return true; }
  1089. this.dispatchEvent(new CustomEvent(
  1090. "clipboard",
  1091. { detail: { text: text } }));
  1092. return true;
  1093. }
  1094. _handle_server_fence_msg() {
  1095. if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
  1096. this._sock.rQskipBytes(3); // Padding
  1097. let flags = this._sock.rQshift32();
  1098. let length = this._sock.rQshift8();
  1099. if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
  1100. if (length > 64) {
  1101. Log.Warn("Bad payload length (" + length + ") in fence response");
  1102. length = 64;
  1103. }
  1104. const payload = this._sock.rQshiftStr(length);
  1105. this._supportsFence = true;
  1106. /*
  1107. * Fence flags
  1108. *
  1109. * (1<<0) - BlockBefore
  1110. * (1<<1) - BlockAfter
  1111. * (1<<2) - SyncNext
  1112. * (1<<31) - Request
  1113. */
  1114. if (!(flags & (1<<31))) {
  1115. return this._fail("Unexpected fence response");
  1116. }
  1117. // Filter out unsupported flags
  1118. // FIXME: support syncNext
  1119. flags &= (1<<0) | (1<<1);
  1120. // BlockBefore and BlockAfter are automatically handled by
  1121. // the fact that we process each incoming message
  1122. // synchronuosly.
  1123. RFB.messages.clientFence(this._sock, flags, payload);
  1124. return true;
  1125. }
  1126. _handle_xvp_msg() {
  1127. if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
  1128. this._sock.rQskipBytes(1); // Padding
  1129. const xvp_ver = this._sock.rQshift8();
  1130. const xvp_msg = this._sock.rQshift8();
  1131. switch (xvp_msg) {
  1132. case 0: // XVP_FAIL
  1133. Log.Error("XVP Operation Failed");
  1134. break;
  1135. case 1: // XVP_INIT
  1136. this._rfb_xvp_ver = xvp_ver;
  1137. Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
  1138. this._setCapability("power", true);
  1139. break;
  1140. default:
  1141. this._fail("Illegal server XVP message (msg: " + xvp_msg + ")");
  1142. break;
  1143. }
  1144. return true;
  1145. }
  1146. _normal_msg() {
  1147. let msg_type;
  1148. if (this._FBU.rects > 0) {
  1149. msg_type = 0;
  1150. } else {
  1151. msg_type = this._sock.rQshift8();
  1152. }
  1153. let first, ret;
  1154. switch (msg_type) {
  1155. case 0: // FramebufferUpdate
  1156. ret = this._framebufferUpdate();
  1157. if (ret && !this._enabledContinuousUpdates) {
  1158. RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
  1159. this._fb_width, this._fb_height);
  1160. }
  1161. return ret;
  1162. case 1: // SetColorMapEntries
  1163. return this._handle_set_colour_map_msg();
  1164. case 2: // Bell
  1165. Log.Debug("Bell");
  1166. this.dispatchEvent(new CustomEvent(
  1167. "bell",
  1168. { detail: {} }));
  1169. return true;
  1170. case 3: // ServerCutText
  1171. return this._handle_server_cut_text();
  1172. case 150: // EndOfContinuousUpdates
  1173. first = !this._supportsContinuousUpdates;
  1174. this._supportsContinuousUpdates = true;
  1175. this._enabledContinuousUpdates = false;
  1176. if (first) {
  1177. this._enabledContinuousUpdates = true;
  1178. this._updateContinuousUpdates();
  1179. Log.Info("Enabling continuous updates.");
  1180. } else {
  1181. // FIXME: We need to send a framebufferupdaterequest here
  1182. // if we add support for turning off continuous updates
  1183. }
  1184. return true;
  1185. case 248: // ServerFence
  1186. return this._handle_server_fence_msg();
  1187. case 250: // XVP
  1188. return this._handle_xvp_msg();
  1189. default:
  1190. this._fail("Unexpected server message (type " + msg_type + ")");
  1191. Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
  1192. return true;
  1193. }
  1194. }
  1195. _onFlush() {
  1196. this._flushing = false;
  1197. // Resume processing
  1198. if (this._sock.rQlen > 0) {
  1199. this._handle_message();
  1200. }
  1201. }
  1202. _framebufferUpdate() {
  1203. if (this._FBU.rects === 0) {
  1204. if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
  1205. this._sock.rQskipBytes(1); // Padding
  1206. this._FBU.rects = this._sock.rQshift16();
  1207. // Make sure the previous frame is fully rendered first
  1208. // to avoid building up an excessive queue
  1209. if (this._display.pending()) {
  1210. this._flushing = true;
  1211. this._display.flush();
  1212. return false;
  1213. }
  1214. }
  1215. while (this._FBU.rects > 0) {
  1216. if (this._FBU.encoding === null) {
  1217. if (this._sock.rQwait("rect header", 12)) { return false; }
  1218. /* New FramebufferUpdate */
  1219. const hdr = this._sock.rQshiftBytes(12);
  1220. this._FBU.x = (hdr[0] << 8) + hdr[1];
  1221. this._FBU.y = (hdr[2] << 8) + hdr[3];
  1222. this._FBU.width = (hdr[4] << 8) + hdr[5];
  1223. this._FBU.height = (hdr[6] << 8) + hdr[7];
  1224. this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
  1225. (hdr[10] << 8) + hdr[11], 10);
  1226. }
  1227. if (!this._handleRect()) {
  1228. return false;
  1229. }
  1230. this._FBU.rects--;
  1231. this._FBU.encoding = null;
  1232. }
  1233. this._display.flip();
  1234. return true; // We finished this FBU
  1235. }
  1236. _handleRect() {
  1237. switch (this._FBU.encoding) {
  1238. case encodings.pseudoEncodingLastRect:
  1239. this._FBU.rects = 1; // Will be decreased when we return
  1240. return true;
  1241. case encodings.pseudoEncodingCursor:
  1242. return this._handleCursor();
  1243. case encodings.pseudoEncodingQEMUExtendedKeyEvent:
  1244. // Old Safari doesn't support creating keyboard events
  1245. try {
  1246. const keyboardEvent = document.createEvent("keyboardEvent");
  1247. if (keyboardEvent.code !== undefined) {
  1248. this._qemuExtKeyEventSupported = true;
  1249. }
  1250. } catch (err) {
  1251. // Do nothing
  1252. }
  1253. return true;
  1254. case encodings.pseudoEncodingDesktopSize:
  1255. this._resize(this._FBU.width, this._FBU.height);
  1256. return true;
  1257. case encodings.pseudoEncodingExtendedDesktopSize:
  1258. return this._handleExtendedDesktopSize();
  1259. default:
  1260. return this._handleDataRect();
  1261. }
  1262. }
  1263. _handleCursor() {
  1264. const hotx = this._FBU.x; // hotspot-x
  1265. const hoty = this._FBU.y; // hotspot-y
  1266. const w = this._FBU.width;
  1267. const h = this._FBU.height;
  1268. const pixelslength = w * h * 4;
  1269. const masklength = Math.ceil(w / 8) * h;
  1270. let bytes = pixelslength + masklength;
  1271. if (this._sock.rQwait("cursor encoding", bytes)) {
  1272. return false;
  1273. }
  1274. // Decode from BGRX pixels + bit mask to RGBA
  1275. const pixels = this._sock.rQshiftBytes(pixelslength);
  1276. const mask = this._sock.rQshiftBytes(masklength);
  1277. let rgba = new Uint8Array(w * h * 4);
  1278. let pix_idx = 0;
  1279. for (let y = 0; y < h; y++) {
  1280. for (let x = 0; x < w; x++) {
  1281. let mask_idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
  1282. let alpha = (mask[mask_idx] << (x % 8)) & 0x80 ? 255 : 0;
  1283. rgba[pix_idx ] = pixels[pix_idx + 2];
  1284. rgba[pix_idx + 1] = pixels[pix_idx + 1];
  1285. rgba[pix_idx + 2] = pixels[pix_idx];
  1286. rgba[pix_idx + 3] = alpha;
  1287. pix_idx += 4;
  1288. }
  1289. }
  1290. this._updateCursor(rgba, hotx, hoty, w, h);
  1291. return true;
  1292. }
  1293. _handleExtendedDesktopSize() {
  1294. if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
  1295. return false;
  1296. }
  1297. const number_of_screens = this._sock.rQpeek8();
  1298. let bytes = 4 + (number_of_screens * 16);
  1299. if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
  1300. return false;
  1301. }
  1302. const firstUpdate = !this._supportsSetDesktopSize;
  1303. this._supportsSetDesktopSize = true;
  1304. // Normally we only apply the current resize mode after a
  1305. // window resize event. However there is no such trigger on the
  1306. // initial connect. And we don't know if the server supports
  1307. // resizing until we've gotten here.
  1308. if (firstUpdate) {
  1309. this._requestRemoteResize();
  1310. }
  1311. this._sock.rQskipBytes(1); // number-of-screens
  1312. this._sock.rQskipBytes(3); // padding
  1313. for (let i = 0; i < number_of_screens; i += 1) {
  1314. // Save the id and flags of the first screen
  1315. if (i === 0) {
  1316. this._screen_id = this._sock.rQshiftBytes(4); // id
  1317. this._sock.rQskipBytes(2); // x-position
  1318. this._sock.rQskipBytes(2); // y-position
  1319. this._sock.rQskipBytes(2); // width
  1320. this._sock.rQskipBytes(2); // height
  1321. this._screen_flags = this._sock.rQshiftBytes(4); // flags
  1322. } else {
  1323. this._sock.rQskipBytes(16);
  1324. }
  1325. }
  1326. /*
  1327. * The x-position indicates the reason for the change:
  1328. *
  1329. * 0 - server resized on its own
  1330. * 1 - this client requested the resize
  1331. * 2 - another client requested the resize
  1332. */
  1333. // We need to handle errors when we requested the resize.
  1334. if (this._FBU.x === 1 && this._FBU.y !== 0) {
  1335. let msg = "";
  1336. // The y-position indicates the status code from the server
  1337. switch (this._FBU.y) {
  1338. case 1:
  1339. msg = "Resize is administratively prohibited";
  1340. break;
  1341. case 2:
  1342. msg = "Out of resources";
  1343. break;
  1344. case 3:
  1345. msg = "Invalid screen layout";
  1346. break;
  1347. default:
  1348. msg = "Unknown reason";
  1349. break;
  1350. }
  1351. Log.Warn("Server did not accept the resize request: "
  1352. + msg);
  1353. } else {
  1354. this._resize(this._FBU.width, this._FBU.height);
  1355. }
  1356. return true;
  1357. }
  1358. _handleDataRect() {
  1359. let decoder = this._decoders[this._FBU.encoding];
  1360. if (!decoder) {
  1361. this._fail("Unsupported encoding (encoding: " +
  1362. this._FBU.encoding + ")");
  1363. return false;
  1364. }
  1365. try {
  1366. return decoder.decodeRect(this._FBU.x, this._FBU.y,
  1367. this._FBU.width, this._FBU.height,
  1368. this._sock, this._display,
  1369. this._fb_depth);
  1370. } catch (err) {
  1371. this._fail("Error decoding rect: " + err);
  1372. return false;
  1373. }
  1374. }
  1375. _updateContinuousUpdates() {
  1376. if (!this._enabledContinuousUpdates) { return; }
  1377. RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
  1378. this._fb_width, this._fb_height);
  1379. }
  1380. _resize(width, height) {
  1381. this._fb_width = width;
  1382. this._fb_height = height;
  1383. this._display.resize(this._fb_width, this._fb_height);
  1384. // Adjust the visible viewport based on the new dimensions
  1385. this._updateClip();
  1386. this._updateScale();
  1387. this._updateContinuousUpdates();
  1388. }
  1389. _xvpOp(ver, op) {
  1390. if (this._rfb_xvp_ver < ver) { return; }
  1391. Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
  1392. RFB.messages.xvpOp(this._sock, ver, op);
  1393. }
  1394. _updateCursor(rgba, hotx, hoty, w, h) {
  1395. this._cursorImage = {
  1396. rgbaPixels: rgba,
  1397. hotx: hotx, hoty: hoty, w: w, h: h,
  1398. };
  1399. this._refreshCursor();
  1400. }
  1401. _shouldShowDotCursor() {
  1402. // Called when this._cursorImage is updated
  1403. if (!this._showDotCursor) {
  1404. // User does not want to see the dot, so...
  1405. return false;
  1406. }
  1407. // The dot should not be shown if the cursor is already visible,
  1408. // i.e. contains at least one not-fully-transparent pixel.
  1409. // So iterate through all alpha bytes in rgba and stop at the
  1410. // first non-zero.
  1411. for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
  1412. if (this._cursorImage.rgbaPixels[i]) {
  1413. return false;
  1414. }
  1415. }
  1416. // At this point, we know that the cursor is fully transparent, and
  1417. // the user wants to see the dot instead of this.
  1418. return true;
  1419. }
  1420. _refreshCursor() {
  1421. const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
  1422. this._cursor.change(image.rgbaPixels,
  1423. image.hotx, image.hoty,
  1424. image.w, image.h
  1425. );
  1426. }
  1427. static genDES(password, challenge) {
  1428. const passwordChars = password.split('').map(c => c.charCodeAt(0));
  1429. return (new DES(passwordChars)).encrypt(challenge);
  1430. }
  1431. }
  1432. // Class Methods
  1433. RFB.messages = {
  1434. keyEvent(sock, keysym, down) {
  1435. const buff = sock._sQ;
  1436. const offset = sock._sQlen;
  1437. buff[offset] = 4; // msg-type
  1438. buff[offset + 1] = down;
  1439. buff[offset + 2] = 0;
  1440. buff[offset + 3] = 0;
  1441. buff[offset + 4] = (keysym >> 24);
  1442. buff[offset + 5] = (keysym >> 16);
  1443. buff[offset + 6] = (keysym >> 8);
  1444. buff[offset + 7] = keysym;
  1445. sock._sQlen += 8;
  1446. sock.flush();
  1447. },
  1448. QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
  1449. function getRFBkeycode(xt_scancode) {
  1450. const upperByte = (keycode >> 8);
  1451. const lowerByte = (keycode & 0x00ff);
  1452. if (upperByte === 0xe0 && lowerByte < 0x7f) {
  1453. return lowerByte | 0x80;
  1454. }
  1455. return xt_scancode;
  1456. }
  1457. const buff = sock._sQ;
  1458. const offset = sock._sQlen;
  1459. buff[offset] = 255; // msg-type
  1460. buff[offset + 1] = 0; // sub msg-type
  1461. buff[offset + 2] = (down >> 8);
  1462. buff[offset + 3] = down;
  1463. buff[offset + 4] = (keysym >> 24);
  1464. buff[offset + 5] = (keysym >> 16);
  1465. buff[offset + 6] = (keysym >> 8);
  1466. buff[offset + 7] = keysym;
  1467. const RFBkeycode = getRFBkeycode(keycode);
  1468. buff[offset + 8] = (RFBkeycode >> 24);
  1469. buff[offset + 9] = (RFBkeycode >> 16);
  1470. buff[offset + 10] = (RFBkeycode >> 8);
  1471. buff[offset + 11] = RFBkeycode;
  1472. sock._sQlen += 12;
  1473. sock.flush();
  1474. },
  1475. pointerEvent(sock, x, y, mask) {
  1476. const buff = sock._sQ;
  1477. const offset = sock._sQlen;
  1478. buff[offset] = 5; // msg-type
  1479. buff[offset + 1] = mask;
  1480. buff[offset + 2] = x >> 8;
  1481. buff[offset + 3] = x;
  1482. buff[offset + 4] = y >> 8;
  1483. buff[offset + 5] = y;
  1484. sock._sQlen += 6;
  1485. sock.flush();
  1486. },
  1487. // TODO(directxman12): make this unicode compatible?
  1488. clientCutText(sock, text) {
  1489. const buff = sock._sQ;
  1490. const offset = sock._sQlen;
  1491. buff[offset] = 6; // msg-type
  1492. buff[offset + 1] = 0; // padding
  1493. buff[offset + 2] = 0; // padding
  1494. buff[offset + 3] = 0; // padding
  1495. let length = text.length;
  1496. buff[offset + 4] = length >> 24;
  1497. buff[offset + 5] = length >> 16;
  1498. buff[offset + 6] = length >> 8;
  1499. buff[offset + 7] = length;
  1500. sock._sQlen += 8;
  1501. // We have to keep track of from where in the text we begin creating the
  1502. // buffer for the flush in the next iteration.
  1503. let textOffset = 0;
  1504. let remaining = length;
  1505. while (remaining > 0) {
  1506. let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
  1507. for (let i = 0; i < flushSize; i++) {
  1508. buff[sock._sQlen + i] = text.charCodeAt(textOffset + i);
  1509. }
  1510. sock._sQlen += flushSize;
  1511. sock.flush();
  1512. remaining -= flushSize;
  1513. textOffset += flushSize;
  1514. }
  1515. },
  1516. setDesktopSize(sock, width, height, id, flags) {
  1517. const buff = sock._sQ;
  1518. const offset = sock._sQlen;
  1519. buff[offset] = 251; // msg-type
  1520. buff[offset + 1] = 0; // padding
  1521. buff[offset + 2] = width >> 8; // width
  1522. buff[offset + 3] = width;
  1523. buff[offset + 4] = height >> 8; // height
  1524. buff[offset + 5] = height;
  1525. buff[offset + 6] = 1; // number-of-screens
  1526. buff[offset + 7] = 0; // padding
  1527. // screen array
  1528. buff[offset + 8] = id >> 24; // id
  1529. buff[offset + 9] = id >> 16;
  1530. buff[offset + 10] = id >> 8;
  1531. buff[offset + 11] = id;
  1532. buff[offset + 12] = 0; // x-position
  1533. buff[offset + 13] = 0;
  1534. buff[offset + 14] = 0; // y-position
  1535. buff[offset + 15] = 0;
  1536. buff[offset + 16] = width >> 8; // width
  1537. buff[offset + 17] = width;
  1538. buff[offset + 18] = height >> 8; // height
  1539. buff[offset + 19] = height;
  1540. buff[offset + 20] = flags >> 24; // flags
  1541. buff[offset + 21] = flags >> 16;
  1542. buff[offset + 22] = flags >> 8;
  1543. buff[offset + 23] = flags;
  1544. sock._sQlen += 24;
  1545. sock.flush();
  1546. },
  1547. clientFence(sock, flags, payload) {
  1548. const buff = sock._sQ;
  1549. const offset = sock._sQlen;
  1550. buff[offset] = 248; // msg-type
  1551. buff[offset + 1] = 0; // padding
  1552. buff[offset + 2] = 0; // padding
  1553. buff[offset + 3] = 0; // padding
  1554. buff[offset + 4] = flags >> 24; // flags
  1555. buff[offset + 5] = flags >> 16;
  1556. buff[offset + 6] = flags >> 8;
  1557. buff[offset + 7] = flags;
  1558. const n = payload.length;
  1559. buff[offset + 8] = n; // length
  1560. for (let i = 0; i < n; i++) {
  1561. buff[offset + 9 + i] = payload.charCodeAt(i);
  1562. }
  1563. sock._sQlen += 9 + n;
  1564. sock.flush();
  1565. },
  1566. enableContinuousUpdates(sock, enable, x, y, width, height) {
  1567. const buff = sock._sQ;
  1568. const offset = sock._sQlen;
  1569. buff[offset] = 150; // msg-type
  1570. buff[offset + 1] = enable; // enable-flag
  1571. buff[offset + 2] = x >> 8; // x
  1572. buff[offset + 3] = x;
  1573. buff[offset + 4] = y >> 8; // y
  1574. buff[offset + 5] = y;
  1575. buff[offset + 6] = width >> 8; // width
  1576. buff[offset + 7] = width;
  1577. buff[offset + 8] = height >> 8; // height
  1578. buff[offset + 9] = height;
  1579. sock._sQlen += 10;
  1580. sock.flush();
  1581. },
  1582. pixelFormat(sock, depth, true_color) {
  1583. const buff = sock._sQ;
  1584. const offset = sock._sQlen;
  1585. let bpp;
  1586. if (depth > 16) {
  1587. bpp = 32;
  1588. } else if (depth > 8) {
  1589. bpp = 16;
  1590. } else {
  1591. bpp = 8;
  1592. }
  1593. const bits = Math.floor(depth/3);
  1594. buff[offset] = 0; // msg-type
  1595. buff[offset + 1] = 0; // padding
  1596. buff[offset + 2] = 0; // padding
  1597. buff[offset + 3] = 0; // padding
  1598. buff[offset + 4] = bpp; // bits-per-pixel
  1599. buff[offset + 5] = depth; // depth
  1600. buff[offset + 6] = 0; // little-endian
  1601. buff[offset + 7] = true_color ? 1 : 0; // true-color
  1602. buff[offset + 8] = 0; // red-max
  1603. buff[offset + 9] = (1 << bits) - 1; // red-max
  1604. buff[offset + 10] = 0; // green-max
  1605. buff[offset + 11] = (1 << bits) - 1; // green-max
  1606. buff[offset + 12] = 0; // blue-max
  1607. buff[offset + 13] = (1 << bits) - 1; // blue-max
  1608. buff[offset + 14] = bits * 2; // red-shift
  1609. buff[offset + 15] = bits * 1; // green-shift
  1610. buff[offset + 16] = bits * 0; // blue-shift
  1611. buff[offset + 17] = 0; // padding
  1612. buff[offset + 18] = 0; // padding
  1613. buff[offset + 19] = 0; // padding
  1614. sock._sQlen += 20;
  1615. sock.flush();
  1616. },
  1617. clientEncodings(sock, encodings) {
  1618. const buff = sock._sQ;
  1619. const offset = sock._sQlen;
  1620. buff[offset] = 2; // msg-type
  1621. buff[offset + 1] = 0; // padding
  1622. buff[offset + 2] = encodings.length >> 8;
  1623. buff[offset + 3] = encodings.length;
  1624. let j = offset + 4;
  1625. for (let i = 0; i < encodings.length; i++) {
  1626. const enc = encodings[i];
  1627. buff[j] = enc >> 24;
  1628. buff[j + 1] = enc >> 16;
  1629. buff[j + 2] = enc >> 8;
  1630. buff[j + 3] = enc;
  1631. j += 4;
  1632. }
  1633. sock._sQlen += j - offset;
  1634. sock.flush();
  1635. },
  1636. fbUpdateRequest(sock, incremental, x, y, w, h) {
  1637. const buff = sock._sQ;
  1638. const offset = sock._sQlen;
  1639. if (typeof(x) === "undefined") { x = 0; }
  1640. if (typeof(y) === "undefined") { y = 0; }
  1641. buff[offset] = 3; // msg-type
  1642. buff[offset + 1] = incremental ? 1 : 0;
  1643. buff[offset + 2] = (x >> 8) & 0xFF;
  1644. buff[offset + 3] = x & 0xFF;
  1645. buff[offset + 4] = (y >> 8) & 0xFF;
  1646. buff[offset + 5] = y & 0xFF;
  1647. buff[offset + 6] = (w >> 8) & 0xFF;
  1648. buff[offset + 7] = w & 0xFF;
  1649. buff[offset + 8] = (h >> 8) & 0xFF;
  1650. buff[offset + 9] = h & 0xFF;
  1651. sock._sQlen += 10;
  1652. sock.flush();
  1653. },
  1654. xvpOp(sock, ver, op) {
  1655. const buff = sock._sQ;
  1656. const offset = sock._sQlen;
  1657. buff[offset] = 250; // msg-type
  1658. buff[offset + 1] = 0; // padding
  1659. buff[offset + 2] = ver;
  1660. buff[offset + 3] = op;
  1661. sock._sQlen += 4;
  1662. sock.flush();
  1663. }
  1664. };
  1665. RFB.cursors = {
  1666. none: {
  1667. rgbaPixels: new Uint8Array(),
  1668. w: 0, h: 0,
  1669. hotx: 0, hoty: 0,
  1670. },
  1671. dot: {
  1672. /* eslint-disable indent */
  1673. rgbaPixels: new Uint8Array([
  1674. 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
  1675. 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
  1676. 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
  1677. ]),
  1678. /* eslint-enable indent */
  1679. w: 3, h: 3,
  1680. hotx: 1, hoty: 1,
  1681. }
  1682. };