rfb.js 113 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2020 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 { toUnsigned32bit, toSigned32bit } from './util/int.js';
  10. import * as Log from './util/logging.js';
  11. import { encodeUTF8, decodeUTF8 } from './util/strings.js';
  12. import { dragThreshold } from './util/browser.js';
  13. import { clientToElement } from './util/element.js';
  14. import { setCapture } from './util/events.js';
  15. import EventTargetMixin from './util/eventtarget.js';
  16. import Display from "./display.js";
  17. import Inflator from "./inflator.js";
  18. import Deflator from "./deflator.js";
  19. import Keyboard from "./input/keyboard.js";
  20. import GestureHandler from "./input/gesturehandler.js";
  21. import Cursor from "./util/cursor.js";
  22. import Websock from "./websock.js";
  23. import KeyTable from "./input/keysym.js";
  24. import XtScancode from "./input/xtscancodes.js";
  25. import { encodings } from "./encodings.js";
  26. import RSAAESAuthenticationState from "./ra2.js";
  27. import legacyCrypto from "./crypto/crypto.js";
  28. import RawDecoder from "./decoders/raw.js";
  29. import CopyRectDecoder from "./decoders/copyrect.js";
  30. import RREDecoder from "./decoders/rre.js";
  31. import HextileDecoder from "./decoders/hextile.js";
  32. import TightDecoder from "./decoders/tight.js";
  33. import TightPNGDecoder from "./decoders/tightpng.js";
  34. import ZRLEDecoder from "./decoders/zrle.js";
  35. import JPEGDecoder from "./decoders/jpeg.js";
  36. // How many seconds to wait for a disconnect to finish
  37. const DISCONNECT_TIMEOUT = 3;
  38. const DEFAULT_BACKGROUND = 'rgb(40, 40, 40)';
  39. // Minimum wait (ms) between two mouse moves
  40. const MOUSE_MOVE_DELAY = 17;
  41. // Wheel thresholds
  42. const WHEEL_STEP = 50; // Pixels needed for one step
  43. const WHEEL_LINE_HEIGHT = 19; // Assumed pixels for one line step
  44. // Gesture thresholds
  45. const GESTURE_ZOOMSENS = 75;
  46. const GESTURE_SCRLSENS = 50;
  47. const DOUBLE_TAP_TIMEOUT = 1000;
  48. const DOUBLE_TAP_THRESHOLD = 50;
  49. // Security types
  50. const securityTypeNone = 1;
  51. const securityTypeVNCAuth = 2;
  52. const securityTypeRA2ne = 6;
  53. const securityTypeTight = 16;
  54. const securityTypeVeNCrypt = 19;
  55. const securityTypeXVP = 22;
  56. const securityTypeARD = 30;
  57. const securityTypeMSLogonII = 113;
  58. // Special Tight security types
  59. const securityTypeUnixLogon = 129;
  60. // VeNCrypt security types
  61. const securityTypePlain = 256;
  62. // Extended clipboard pseudo-encoding formats
  63. const extendedClipboardFormatText = 1;
  64. /*eslint-disable no-unused-vars */
  65. const extendedClipboardFormatRtf = 1 << 1;
  66. const extendedClipboardFormatHtml = 1 << 2;
  67. const extendedClipboardFormatDib = 1 << 3;
  68. const extendedClipboardFormatFiles = 1 << 4;
  69. /*eslint-enable */
  70. // Extended clipboard pseudo-encoding actions
  71. const extendedClipboardActionCaps = 1 << 24;
  72. const extendedClipboardActionRequest = 1 << 25;
  73. const extendedClipboardActionPeek = 1 << 26;
  74. const extendedClipboardActionNotify = 1 << 27;
  75. const extendedClipboardActionProvide = 1 << 28;
  76. export default class RFB extends EventTargetMixin {
  77. constructor(target, urlOrChannel, options) {
  78. if (!target) {
  79. throw new Error("Must specify target");
  80. }
  81. if (!urlOrChannel) {
  82. throw new Error("Must specify URL, WebSocket or RTCDataChannel");
  83. }
  84. // We rely on modern APIs which might not be available in an
  85. // insecure context
  86. if (!window.isSecureContext) {
  87. Log.Error("noVNC requires a secure context (TLS). Expect crashes!");
  88. }
  89. super();
  90. this._target = target;
  91. if (typeof urlOrChannel === "string") {
  92. this._url = urlOrChannel;
  93. } else {
  94. this._url = null;
  95. this._rawChannel = urlOrChannel;
  96. }
  97. // Connection details
  98. options = options || {};
  99. this._rfbCredentials = options.credentials || {};
  100. this._shared = 'shared' in options ? !!options.shared : true;
  101. this._repeaterID = options.repeaterID || '';
  102. this._wsProtocols = options.wsProtocols || [];
  103. // Internal state
  104. this._rfbConnectionState = '';
  105. this._rfbInitState = '';
  106. this._rfbAuthScheme = -1;
  107. this._rfbCleanDisconnect = true;
  108. this._rfbRSAAESAuthenticationState = null;
  109. // Server capabilities
  110. this._rfbVersion = 0;
  111. this._rfbMaxVersion = 3.8;
  112. this._rfbTightVNC = false;
  113. this._rfbVeNCryptState = 0;
  114. this._rfbXvpVer = 0;
  115. this._fbWidth = 0;
  116. this._fbHeight = 0;
  117. this._fbName = "";
  118. this._capabilities = { power: false };
  119. this._supportsFence = false;
  120. this._supportsContinuousUpdates = false;
  121. this._enabledContinuousUpdates = false;
  122. this._supportsSetDesktopSize = false;
  123. this._screenID = 0;
  124. this._screenFlags = 0;
  125. this._qemuExtKeyEventSupported = false;
  126. this._clipboardText = null;
  127. this._clipboardServerCapabilitiesActions = {};
  128. this._clipboardServerCapabilitiesFormats = {};
  129. // Internal objects
  130. this._sock = null; // Websock object
  131. this._display = null; // Display object
  132. this._flushing = false; // Display flushing state
  133. this._keyboard = null; // Keyboard input handler object
  134. this._gestures = null; // Gesture input handler object
  135. this._resizeObserver = null; // Resize observer object
  136. // Timers
  137. this._disconnTimer = null; // disconnection timer
  138. this._resizeTimeout = null; // resize rate limiting
  139. this._mouseMoveTimer = null;
  140. // Decoder states
  141. this._decoders = {};
  142. this._FBU = {
  143. rects: 0,
  144. x: 0,
  145. y: 0,
  146. width: 0,
  147. height: 0,
  148. encoding: null,
  149. };
  150. // Mouse state
  151. this._mousePos = {};
  152. this._mouseButtonMask = 0;
  153. this._mouseLastMoveTime = 0;
  154. this._viewportDragging = false;
  155. this._viewportDragPos = {};
  156. this._viewportHasMoved = false;
  157. this._accumulatedWheelDeltaX = 0;
  158. this._accumulatedWheelDeltaY = 0;
  159. // Gesture state
  160. this._gestureLastTapTime = null;
  161. this._gestureFirstDoubleTapEv = null;
  162. this._gestureLastMagnitudeX = 0;
  163. this._gestureLastMagnitudeY = 0;
  164. // Bound event handlers
  165. this._eventHandlers = {
  166. focusCanvas: this._focusCanvas.bind(this),
  167. handleResize: this._handleResize.bind(this),
  168. handleMouse: this._handleMouse.bind(this),
  169. handleWheel: this._handleWheel.bind(this),
  170. handleGesture: this._handleGesture.bind(this),
  171. handleRSAAESCredentialsRequired: this._handleRSAAESCredentialsRequired.bind(this),
  172. handleRSAAESServerVerification: this._handleRSAAESServerVerification.bind(this),
  173. };
  174. // main setup
  175. Log.Debug(">> RFB.constructor");
  176. // Create DOM elements
  177. this._screen = document.createElement('div');
  178. this._screen.style.display = 'flex';
  179. this._screen.style.width = '100%';
  180. this._screen.style.height = '100%';
  181. this._screen.style.overflow = 'auto';
  182. this._screen.style.background = DEFAULT_BACKGROUND;
  183. this._canvas = document.createElement('canvas');
  184. this._canvas.style.margin = 'auto';
  185. // Some browsers add an outline on focus
  186. this._canvas.style.outline = 'none';
  187. this._canvas.width = 0;
  188. this._canvas.height = 0;
  189. this._canvas.tabIndex = -1;
  190. this._screen.appendChild(this._canvas);
  191. // Cursor
  192. this._cursor = new Cursor();
  193. // XXX: TightVNC 2.8.11 sends no cursor at all until Windows changes
  194. // it. Result: no cursor at all until a window border or an edit field
  195. // is hit blindly. But there are also VNC servers that draw the cursor
  196. // in the framebuffer and don't send the empty local cursor. There is
  197. // no way to satisfy both sides.
  198. //
  199. // The spec is unclear on this "initial cursor" issue. Many other
  200. // viewers (TigerVNC, RealVNC, Remmina) display an arrow as the
  201. // initial cursor instead.
  202. this._cursorImage = RFB.cursors.none;
  203. // populate decoder array with objects
  204. this._decoders[encodings.encodingRaw] = new RawDecoder();
  205. this._decoders[encodings.encodingCopyRect] = new CopyRectDecoder();
  206. this._decoders[encodings.encodingRRE] = new RREDecoder();
  207. this._decoders[encodings.encodingHextile] = new HextileDecoder();
  208. this._decoders[encodings.encodingTight] = new TightDecoder();
  209. this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
  210. this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
  211. this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
  212. // NB: nothing that needs explicit teardown should be done
  213. // before this point, since this can throw an exception
  214. try {
  215. this._display = new Display(this._canvas);
  216. } catch (exc) {
  217. Log.Error("Display exception: " + exc);
  218. throw exc;
  219. }
  220. this._keyboard = new Keyboard(this._canvas);
  221. this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
  222. this._remoteCapsLock = null; // Null indicates unknown or irrelevant
  223. this._remoteNumLock = null;
  224. this._gestures = new GestureHandler();
  225. this._sock = new Websock();
  226. this._sock.on('open', this._socketOpen.bind(this));
  227. this._sock.on('close', this._socketClose.bind(this));
  228. this._sock.on('message', this._handleMessage.bind(this));
  229. this._sock.on('error', this._socketError.bind(this));
  230. this._expectedClientWidth = null;
  231. this._expectedClientHeight = null;
  232. this._resizeObserver = new ResizeObserver(this._eventHandlers.handleResize);
  233. // All prepared, kick off the connection
  234. this._updateConnectionState('connecting');
  235. Log.Debug("<< RFB.constructor");
  236. // ===== PROPERTIES =====
  237. this.dragViewport = false;
  238. this.focusOnClick = true;
  239. this._viewOnly = false;
  240. this._clipViewport = false;
  241. this._clippingViewport = false;
  242. this._scaleViewport = false;
  243. this._resizeSession = false;
  244. this._showDotCursor = false;
  245. if (options.showDotCursor !== undefined) {
  246. Log.Warn("Specifying showDotCursor as a RFB constructor argument is deprecated");
  247. this._showDotCursor = options.showDotCursor;
  248. }
  249. this._qualityLevel = 6;
  250. this._compressionLevel = 2;
  251. }
  252. // ===== PROPERTIES =====
  253. get viewOnly() { return this._viewOnly; }
  254. set viewOnly(viewOnly) {
  255. this._viewOnly = viewOnly;
  256. if (this._rfbConnectionState === "connecting" ||
  257. this._rfbConnectionState === "connected") {
  258. if (viewOnly) {
  259. this._keyboard.ungrab();
  260. } else {
  261. this._keyboard.grab();
  262. }
  263. }
  264. }
  265. get capabilities() { return this._capabilities; }
  266. get clippingViewport() { return this._clippingViewport; }
  267. _setClippingViewport(on) {
  268. if (on === this._clippingViewport) {
  269. return;
  270. }
  271. this._clippingViewport = on;
  272. this.dispatchEvent(new CustomEvent("clippingviewport",
  273. { detail: this._clippingViewport }));
  274. }
  275. get touchButton() { return 0; }
  276. set touchButton(button) { Log.Warn("Using old API!"); }
  277. get clipViewport() { return this._clipViewport; }
  278. set clipViewport(viewport) {
  279. this._clipViewport = viewport;
  280. this._updateClip();
  281. }
  282. get scaleViewport() { return this._scaleViewport; }
  283. set scaleViewport(scale) {
  284. this._scaleViewport = scale;
  285. // Scaling trumps clipping, so we may need to adjust
  286. // clipping when enabling or disabling scaling
  287. if (scale && this._clipViewport) {
  288. this._updateClip();
  289. }
  290. this._updateScale();
  291. if (!scale && this._clipViewport) {
  292. this._updateClip();
  293. }
  294. }
  295. get resizeSession() { return this._resizeSession; }
  296. set resizeSession(resize) {
  297. this._resizeSession = resize;
  298. if (resize) {
  299. this._requestRemoteResize();
  300. }
  301. }
  302. get showDotCursor() { return this._showDotCursor; }
  303. set showDotCursor(show) {
  304. this._showDotCursor = show;
  305. this._refreshCursor();
  306. }
  307. get background() { return this._screen.style.background; }
  308. set background(cssValue) { this._screen.style.background = cssValue; }
  309. get qualityLevel() {
  310. return this._qualityLevel;
  311. }
  312. set qualityLevel(qualityLevel) {
  313. if (!Number.isInteger(qualityLevel) || qualityLevel < 0 || qualityLevel > 9) {
  314. Log.Error("qualityLevel must be an integer between 0 and 9");
  315. return;
  316. }
  317. if (this._qualityLevel === qualityLevel) {
  318. return;
  319. }
  320. this._qualityLevel = qualityLevel;
  321. if (this._rfbConnectionState === 'connected') {
  322. this._sendEncodings();
  323. }
  324. }
  325. get compressionLevel() {
  326. return this._compressionLevel;
  327. }
  328. set compressionLevel(compressionLevel) {
  329. if (!Number.isInteger(compressionLevel) || compressionLevel < 0 || compressionLevel > 9) {
  330. Log.Error("compressionLevel must be an integer between 0 and 9");
  331. return;
  332. }
  333. if (this._compressionLevel === compressionLevel) {
  334. return;
  335. }
  336. this._compressionLevel = compressionLevel;
  337. if (this._rfbConnectionState === 'connected') {
  338. this._sendEncodings();
  339. }
  340. }
  341. // ===== PUBLIC METHODS =====
  342. disconnect() {
  343. this._updateConnectionState('disconnecting');
  344. this._sock.off('error');
  345. this._sock.off('message');
  346. this._sock.off('open');
  347. if (this._rfbRSAAESAuthenticationState !== null) {
  348. this._rfbRSAAESAuthenticationState.disconnect();
  349. }
  350. }
  351. approveServer() {
  352. if (this._rfbRSAAESAuthenticationState !== null) {
  353. this._rfbRSAAESAuthenticationState.approveServer();
  354. }
  355. }
  356. sendCredentials(creds) {
  357. this._rfbCredentials = creds;
  358. this._resumeAuthentication();
  359. }
  360. sendCtrlAltDel() {
  361. if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
  362. Log.Info("Sending Ctrl-Alt-Del");
  363. this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
  364. this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
  365. this.sendKey(KeyTable.XK_Delete, "Delete", true);
  366. this.sendKey(KeyTable.XK_Delete, "Delete", false);
  367. this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
  368. this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
  369. }
  370. machineShutdown() {
  371. this._xvpOp(1, 2);
  372. }
  373. machineReboot() {
  374. this._xvpOp(1, 3);
  375. }
  376. machineReset() {
  377. this._xvpOp(1, 4);
  378. }
  379. // Send a key press. If 'down' is not specified then send a down key
  380. // followed by an up key.
  381. sendKey(keysym, code, down) {
  382. if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
  383. if (down === undefined) {
  384. this.sendKey(keysym, code, true);
  385. this.sendKey(keysym, code, false);
  386. return;
  387. }
  388. const scancode = XtScancode[code];
  389. if (this._qemuExtKeyEventSupported && scancode) {
  390. // 0 is NoSymbol
  391. keysym = keysym || 0;
  392. Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
  393. RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
  394. } else {
  395. if (!keysym) {
  396. return;
  397. }
  398. Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
  399. RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
  400. }
  401. }
  402. focus(options) {
  403. this._canvas.focus(options);
  404. }
  405. blur() {
  406. this._canvas.blur();
  407. }
  408. clipboardPasteFrom(text) {
  409. if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; }
  410. if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] &&
  411. this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
  412. this._clipboardText = text;
  413. RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
  414. } else {
  415. let length, i;
  416. let data;
  417. length = 0;
  418. // eslint-disable-next-line no-unused-vars
  419. for (let codePoint of text) {
  420. length++;
  421. }
  422. data = new Uint8Array(length);
  423. i = 0;
  424. for (let codePoint of text) {
  425. let code = codePoint.codePointAt(0);
  426. /* Only ISO 8859-1 is supported */
  427. if (code > 0xff) {
  428. code = 0x3f; // '?'
  429. }
  430. data[i++] = code;
  431. }
  432. RFB.messages.clientCutText(this._sock, data);
  433. }
  434. }
  435. getImageData() {
  436. return this._display.getImageData();
  437. }
  438. toDataURL(type, encoderOptions) {
  439. return this._display.toDataURL(type, encoderOptions);
  440. }
  441. toBlob(callback, type, quality) {
  442. return this._display.toBlob(callback, type, quality);
  443. }
  444. // ===== PRIVATE METHODS =====
  445. _connect() {
  446. Log.Debug(">> RFB.connect");
  447. if (this._url) {
  448. Log.Info(`connecting to ${this._url}`);
  449. this._sock.open(this._url, this._wsProtocols);
  450. } else {
  451. Log.Info(`attaching ${this._rawChannel} to Websock`);
  452. this._sock.attach(this._rawChannel);
  453. if (this._sock.readyState === 'closed') {
  454. throw Error("Cannot use already closed WebSocket/RTCDataChannel");
  455. }
  456. if (this._sock.readyState === 'open') {
  457. // FIXME: _socketOpen() can in theory call _fail(), which
  458. // isn't allowed this early, but I'm not sure that can
  459. // happen without a bug messing up our state variables
  460. this._socketOpen();
  461. }
  462. }
  463. // Make our elements part of the page
  464. this._target.appendChild(this._screen);
  465. this._gestures.attach(this._canvas);
  466. this._cursor.attach(this._canvas);
  467. this._refreshCursor();
  468. // Monitor size changes of the screen element
  469. this._resizeObserver.observe(this._screen);
  470. // Always grab focus on some kind of click event
  471. this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas);
  472. this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
  473. // Mouse events
  474. this._canvas.addEventListener('mousedown', this._eventHandlers.handleMouse);
  475. this._canvas.addEventListener('mouseup', this._eventHandlers.handleMouse);
  476. this._canvas.addEventListener('mousemove', this._eventHandlers.handleMouse);
  477. // Prevent middle-click pasting (see handler for why we bind to document)
  478. this._canvas.addEventListener('click', this._eventHandlers.handleMouse);
  479. // preventDefault() on mousedown doesn't stop this event for some
  480. // reason so we have to explicitly block it
  481. this._canvas.addEventListener('contextmenu', this._eventHandlers.handleMouse);
  482. // Wheel events
  483. this._canvas.addEventListener("wheel", this._eventHandlers.handleWheel);
  484. // Gesture events
  485. this._canvas.addEventListener("gesturestart", this._eventHandlers.handleGesture);
  486. this._canvas.addEventListener("gesturemove", this._eventHandlers.handleGesture);
  487. this._canvas.addEventListener("gestureend", this._eventHandlers.handleGesture);
  488. Log.Debug("<< RFB.connect");
  489. }
  490. _disconnect() {
  491. Log.Debug(">> RFB.disconnect");
  492. this._cursor.detach();
  493. this._canvas.removeEventListener("gesturestart", this._eventHandlers.handleGesture);
  494. this._canvas.removeEventListener("gesturemove", this._eventHandlers.handleGesture);
  495. this._canvas.removeEventListener("gestureend", this._eventHandlers.handleGesture);
  496. this._canvas.removeEventListener("wheel", this._eventHandlers.handleWheel);
  497. this._canvas.removeEventListener('mousedown', this._eventHandlers.handleMouse);
  498. this._canvas.removeEventListener('mouseup', this._eventHandlers.handleMouse);
  499. this._canvas.removeEventListener('mousemove', this._eventHandlers.handleMouse);
  500. this._canvas.removeEventListener('click', this._eventHandlers.handleMouse);
  501. this._canvas.removeEventListener('contextmenu', this._eventHandlers.handleMouse);
  502. this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
  503. this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas);
  504. this._resizeObserver.disconnect();
  505. this._keyboard.ungrab();
  506. this._gestures.detach();
  507. this._sock.close();
  508. try {
  509. this._target.removeChild(this._screen);
  510. } catch (e) {
  511. if (e.name === 'NotFoundError') {
  512. // Some cases where the initial connection fails
  513. // can disconnect before the _screen is created
  514. } else {
  515. throw e;
  516. }
  517. }
  518. clearTimeout(this._resizeTimeout);
  519. clearTimeout(this._mouseMoveTimer);
  520. Log.Debug("<< RFB.disconnect");
  521. }
  522. _socketOpen() {
  523. if ((this._rfbConnectionState === 'connecting') &&
  524. (this._rfbInitState === '')) {
  525. this._rfbInitState = 'ProtocolVersion';
  526. Log.Debug("Starting VNC handshake");
  527. } else {
  528. this._fail("Unexpected server connection while " +
  529. this._rfbConnectionState);
  530. }
  531. }
  532. _socketClose(e) {
  533. Log.Debug("WebSocket on-close event");
  534. let msg = "";
  535. if (e.code) {
  536. msg = "(code: " + e.code;
  537. if (e.reason) {
  538. msg += ", reason: " + e.reason;
  539. }
  540. msg += ")";
  541. }
  542. switch (this._rfbConnectionState) {
  543. case 'connecting':
  544. this._fail("Connection closed " + msg);
  545. break;
  546. case 'connected':
  547. // Handle disconnects that were initiated server-side
  548. this._updateConnectionState('disconnecting');
  549. this._updateConnectionState('disconnected');
  550. break;
  551. case 'disconnecting':
  552. // Normal disconnection path
  553. this._updateConnectionState('disconnected');
  554. break;
  555. case 'disconnected':
  556. this._fail("Unexpected server disconnect " +
  557. "when already disconnected " + msg);
  558. break;
  559. default:
  560. this._fail("Unexpected server disconnect before connecting " +
  561. msg);
  562. break;
  563. }
  564. this._sock.off('close');
  565. // Delete reference to raw channel to allow cleanup.
  566. this._rawChannel = null;
  567. }
  568. _socketError(e) {
  569. Log.Warn("WebSocket on-error event");
  570. }
  571. _focusCanvas(event) {
  572. if (!this.focusOnClick) {
  573. return;
  574. }
  575. this.focus({ preventScroll: true });
  576. }
  577. _setDesktopName(name) {
  578. this._fbName = name;
  579. this.dispatchEvent(new CustomEvent(
  580. "desktopname",
  581. { detail: { name: this._fbName } }));
  582. }
  583. _saveExpectedClientSize() {
  584. this._expectedClientWidth = this._screen.clientWidth;
  585. this._expectedClientHeight = this._screen.clientHeight;
  586. }
  587. _currentClientSize() {
  588. return [this._screen.clientWidth, this._screen.clientHeight];
  589. }
  590. _clientHasExpectedSize() {
  591. const [currentWidth, currentHeight] = this._currentClientSize();
  592. return currentWidth == this._expectedClientWidth &&
  593. currentHeight == this._expectedClientHeight;
  594. }
  595. _handleResize() {
  596. // Don't change anything if the client size is already as expected
  597. if (this._clientHasExpectedSize()) {
  598. return;
  599. }
  600. // If the window resized then our screen element might have
  601. // as well. Update the viewport dimensions.
  602. window.requestAnimationFrame(() => {
  603. this._updateClip();
  604. this._updateScale();
  605. });
  606. if (this._resizeSession) {
  607. // Request changing the resolution of the remote display to
  608. // the size of the local browser viewport.
  609. // In order to not send multiple requests before the browser-resize
  610. // is finished we wait 0.5 seconds before sending the request.
  611. clearTimeout(this._resizeTimeout);
  612. this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
  613. }
  614. }
  615. // Update state of clipping in Display object, and make sure the
  616. // configured viewport matches the current screen size
  617. _updateClip() {
  618. const curClip = this._display.clipViewport;
  619. let newClip = this._clipViewport;
  620. if (this._scaleViewport) {
  621. // Disable viewport clipping if we are scaling
  622. newClip = false;
  623. }
  624. if (curClip !== newClip) {
  625. this._display.clipViewport = newClip;
  626. }
  627. if (newClip) {
  628. // When clipping is enabled, the screen is limited to
  629. // the size of the container.
  630. const size = this._screenSize();
  631. this._display.viewportChangeSize(size.w, size.h);
  632. this._fixScrollbars();
  633. this._setClippingViewport(size.w < this._display.width ||
  634. size.h < this._display.height);
  635. } else {
  636. this._setClippingViewport(false);
  637. }
  638. // When changing clipping we might show or hide scrollbars.
  639. // This causes the expected client dimensions to change.
  640. if (curClip !== newClip) {
  641. this._saveExpectedClientSize();
  642. }
  643. }
  644. _updateScale() {
  645. if (!this._scaleViewport) {
  646. this._display.scale = 1.0;
  647. } else {
  648. const size = this._screenSize();
  649. this._display.autoscale(size.w, size.h);
  650. }
  651. this._fixScrollbars();
  652. }
  653. // Requests a change of remote desktop size. This message is an extension
  654. // and may only be sent if we have received an ExtendedDesktopSize message
  655. _requestRemoteResize() {
  656. clearTimeout(this._resizeTimeout);
  657. this._resizeTimeout = null;
  658. if (!this._resizeSession || this._viewOnly ||
  659. !this._supportsSetDesktopSize) {
  660. return;
  661. }
  662. const size = this._screenSize();
  663. RFB.messages.setDesktopSize(this._sock,
  664. Math.floor(size.w), Math.floor(size.h),
  665. this._screenID, this._screenFlags);
  666. Log.Debug('Requested new desktop size: ' +
  667. size.w + 'x' + size.h);
  668. }
  669. // Gets the the size of the available screen
  670. _screenSize() {
  671. let r = this._screen.getBoundingClientRect();
  672. return { w: r.width, h: r.height };
  673. }
  674. _fixScrollbars() {
  675. // This is a hack because Safari on macOS screws up the calculation
  676. // for when scrollbars are needed. We get scrollbars when making the
  677. // browser smaller, despite remote resize being enabled. So to fix it
  678. // we temporarily toggle them off and on.
  679. const orig = this._screen.style.overflow;
  680. this._screen.style.overflow = 'hidden';
  681. // Force Safari to recalculate the layout by asking for
  682. // an element's dimensions
  683. this._screen.getBoundingClientRect();
  684. this._screen.style.overflow = orig;
  685. }
  686. /*
  687. * Connection states:
  688. * connecting
  689. * connected
  690. * disconnecting
  691. * disconnected - permanent state
  692. */
  693. _updateConnectionState(state) {
  694. const oldstate = this._rfbConnectionState;
  695. if (state === oldstate) {
  696. Log.Debug("Already in state '" + state + "', ignoring");
  697. return;
  698. }
  699. // The 'disconnected' state is permanent for each RFB object
  700. if (oldstate === 'disconnected') {
  701. Log.Error("Tried changing state of a disconnected RFB object");
  702. return;
  703. }
  704. // Ensure proper transitions before doing anything
  705. switch (state) {
  706. case 'connected':
  707. if (oldstate !== 'connecting') {
  708. Log.Error("Bad transition to connected state, " +
  709. "previous connection state: " + oldstate);
  710. return;
  711. }
  712. break;
  713. case 'disconnected':
  714. if (oldstate !== 'disconnecting') {
  715. Log.Error("Bad transition to disconnected state, " +
  716. "previous connection state: " + oldstate);
  717. return;
  718. }
  719. break;
  720. case 'connecting':
  721. if (oldstate !== '') {
  722. Log.Error("Bad transition to connecting state, " +
  723. "previous connection state: " + oldstate);
  724. return;
  725. }
  726. break;
  727. case 'disconnecting':
  728. if (oldstate !== 'connected' && oldstate !== 'connecting') {
  729. Log.Error("Bad transition to disconnecting state, " +
  730. "previous connection state: " + oldstate);
  731. return;
  732. }
  733. break;
  734. default:
  735. Log.Error("Unknown connection state: " + state);
  736. return;
  737. }
  738. // State change actions
  739. this._rfbConnectionState = state;
  740. Log.Debug("New state '" + state + "', was '" + oldstate + "'.");
  741. if (this._disconnTimer && state !== 'disconnecting') {
  742. Log.Debug("Clearing disconnect timer");
  743. clearTimeout(this._disconnTimer);
  744. this._disconnTimer = null;
  745. // make sure we don't get a double event
  746. this._sock.off('close');
  747. }
  748. switch (state) {
  749. case 'connecting':
  750. this._connect();
  751. break;
  752. case 'connected':
  753. this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
  754. break;
  755. case 'disconnecting':
  756. this._disconnect();
  757. this._disconnTimer = setTimeout(() => {
  758. Log.Error("Disconnection timed out.");
  759. this._updateConnectionState('disconnected');
  760. }, DISCONNECT_TIMEOUT * 1000);
  761. break;
  762. case 'disconnected':
  763. this.dispatchEvent(new CustomEvent(
  764. "disconnect", { detail:
  765. { clean: this._rfbCleanDisconnect } }));
  766. break;
  767. }
  768. }
  769. /* Print errors and disconnect
  770. *
  771. * The parameter 'details' is used for information that
  772. * should be logged but not sent to the user interface.
  773. */
  774. _fail(details) {
  775. switch (this._rfbConnectionState) {
  776. case 'disconnecting':
  777. Log.Error("Failed when disconnecting: " + details);
  778. break;
  779. case 'connected':
  780. Log.Error("Failed while connected: " + details);
  781. break;
  782. case 'connecting':
  783. Log.Error("Failed when connecting: " + details);
  784. break;
  785. default:
  786. Log.Error("RFB failure: " + details);
  787. break;
  788. }
  789. this._rfbCleanDisconnect = false; //This is sent to the UI
  790. // Transition to disconnected without waiting for socket to close
  791. this._updateConnectionState('disconnecting');
  792. this._updateConnectionState('disconnected');
  793. return false;
  794. }
  795. _setCapability(cap, val) {
  796. this._capabilities[cap] = val;
  797. this.dispatchEvent(new CustomEvent("capabilities",
  798. { detail: { capabilities: this._capabilities } }));
  799. }
  800. _handleMessage() {
  801. if (this._sock.rQwait("message", 1)) {
  802. Log.Warn("handleMessage called on an empty receive queue");
  803. return;
  804. }
  805. switch (this._rfbConnectionState) {
  806. case 'disconnected':
  807. Log.Error("Got data while disconnected");
  808. break;
  809. case 'connected':
  810. while (true) {
  811. if (this._flushing) {
  812. break;
  813. }
  814. if (!this._normalMsg()) {
  815. break;
  816. }
  817. if (this._sock.rQwait("message", 1)) {
  818. break;
  819. }
  820. }
  821. break;
  822. case 'connecting':
  823. while (this._rfbConnectionState === 'connecting') {
  824. if (!this._initMsg()) {
  825. break;
  826. }
  827. }
  828. break;
  829. default:
  830. Log.Error("Got data while in an invalid state");
  831. break;
  832. }
  833. }
  834. _handleKeyEvent(keysym, code, down, numlock, capslock) {
  835. // If remote state of capslock is known, and it doesn't match the local led state of
  836. // the keyboard, we send a capslock keypress first to bring it into sync.
  837. // If we just pressed CapsLock, or we toggled it remotely due to it being out of sync
  838. // we clear the remote state so that we don't send duplicate or spurious fixes,
  839. // since it may take some time to receive the new remote CapsLock state.
  840. if (code == 'CapsLock' && down) {
  841. this._remoteCapsLock = null;
  842. }
  843. if (this._remoteCapsLock !== null && capslock !== null && this._remoteCapsLock !== capslock && down) {
  844. Log.Debug("Fixing remote caps lock");
  845. this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', true);
  846. this.sendKey(KeyTable.XK_Caps_Lock, 'CapsLock', false);
  847. // We clear the remote capsLock state when we do this to prevent issues with doing this twice
  848. // before we receive an update of the the remote state.
  849. this._remoteCapsLock = null;
  850. }
  851. // Logic for numlock is exactly the same.
  852. if (code == 'NumLock' && down) {
  853. this._remoteNumLock = null;
  854. }
  855. if (this._remoteNumLock !== null && numlock !== null && this._remoteNumLock !== numlock && down) {
  856. Log.Debug("Fixing remote num lock");
  857. this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', true);
  858. this.sendKey(KeyTable.XK_Num_Lock, 'NumLock', false);
  859. this._remoteNumLock = null;
  860. }
  861. this.sendKey(keysym, code, down);
  862. }
  863. _handleMouse(ev) {
  864. /*
  865. * We don't check connection status or viewOnly here as the
  866. * mouse events might be used to control the viewport
  867. */
  868. if (ev.type === 'click') {
  869. /*
  870. * Note: This is only needed for the 'click' event as it fails
  871. * to fire properly for the target element so we have
  872. * to listen on the document element instead.
  873. */
  874. if (ev.target !== this._canvas) {
  875. return;
  876. }
  877. }
  878. // FIXME: if we're in view-only and not dragging,
  879. // should we stop events?
  880. ev.stopPropagation();
  881. ev.preventDefault();
  882. if ((ev.type === 'click') || (ev.type === 'contextmenu')) {
  883. return;
  884. }
  885. let pos = clientToElement(ev.clientX, ev.clientY,
  886. this._canvas);
  887. switch (ev.type) {
  888. case 'mousedown':
  889. setCapture(this._canvas);
  890. this._handleMouseButton(pos.x, pos.y,
  891. true, 1 << ev.button);
  892. break;
  893. case 'mouseup':
  894. this._handleMouseButton(pos.x, pos.y,
  895. false, 1 << ev.button);
  896. break;
  897. case 'mousemove':
  898. this._handleMouseMove(pos.x, pos.y);
  899. break;
  900. }
  901. }
  902. _handleMouseButton(x, y, down, bmask) {
  903. if (this.dragViewport) {
  904. if (down && !this._viewportDragging) {
  905. this._viewportDragging = true;
  906. this._viewportDragPos = {'x': x, 'y': y};
  907. this._viewportHasMoved = false;
  908. // Skip sending mouse events
  909. return;
  910. } else {
  911. this._viewportDragging = false;
  912. // If we actually performed a drag then we are done
  913. // here and should not send any mouse events
  914. if (this._viewportHasMoved) {
  915. return;
  916. }
  917. // Otherwise we treat this as a mouse click event.
  918. // Send the button down event here, as the button up
  919. // event is sent at the end of this function.
  920. this._sendMouse(x, y, bmask);
  921. }
  922. }
  923. // Flush waiting move event first
  924. if (this._mouseMoveTimer !== null) {
  925. clearTimeout(this._mouseMoveTimer);
  926. this._mouseMoveTimer = null;
  927. this._sendMouse(x, y, this._mouseButtonMask);
  928. }
  929. if (down) {
  930. this._mouseButtonMask |= bmask;
  931. } else {
  932. this._mouseButtonMask &= ~bmask;
  933. }
  934. this._sendMouse(x, y, this._mouseButtonMask);
  935. }
  936. _handleMouseMove(x, y) {
  937. if (this._viewportDragging) {
  938. const deltaX = this._viewportDragPos.x - x;
  939. const deltaY = this._viewportDragPos.y - y;
  940. if (this._viewportHasMoved || (Math.abs(deltaX) > dragThreshold ||
  941. Math.abs(deltaY) > dragThreshold)) {
  942. this._viewportHasMoved = true;
  943. this._viewportDragPos = {'x': x, 'y': y};
  944. this._display.viewportChangePos(deltaX, deltaY);
  945. }
  946. // Skip sending mouse events
  947. return;
  948. }
  949. this._mousePos = { 'x': x, 'y': y };
  950. // Limit many mouse move events to one every MOUSE_MOVE_DELAY ms
  951. if (this._mouseMoveTimer == null) {
  952. const timeSinceLastMove = Date.now() - this._mouseLastMoveTime;
  953. if (timeSinceLastMove > MOUSE_MOVE_DELAY) {
  954. this._sendMouse(x, y, this._mouseButtonMask);
  955. this._mouseLastMoveTime = Date.now();
  956. } else {
  957. // Too soon since the latest move, wait the remaining time
  958. this._mouseMoveTimer = setTimeout(() => {
  959. this._handleDelayedMouseMove();
  960. }, MOUSE_MOVE_DELAY - timeSinceLastMove);
  961. }
  962. }
  963. }
  964. _handleDelayedMouseMove() {
  965. this._mouseMoveTimer = null;
  966. this._sendMouse(this._mousePos.x, this._mousePos.y,
  967. this._mouseButtonMask);
  968. this._mouseLastMoveTime = Date.now();
  969. }
  970. _sendMouse(x, y, mask) {
  971. if (this._rfbConnectionState !== 'connected') { return; }
  972. if (this._viewOnly) { return; } // View only, skip mouse events
  973. RFB.messages.pointerEvent(this._sock, this._display.absX(x),
  974. this._display.absY(y), mask);
  975. }
  976. _handleWheel(ev) {
  977. if (this._rfbConnectionState !== 'connected') { return; }
  978. if (this._viewOnly) { return; } // View only, skip mouse events
  979. ev.stopPropagation();
  980. ev.preventDefault();
  981. let pos = clientToElement(ev.clientX, ev.clientY,
  982. this._canvas);
  983. let dX = ev.deltaX;
  984. let dY = ev.deltaY;
  985. // Pixel units unless it's non-zero.
  986. // Note that if deltamode is line or page won't matter since we aren't
  987. // sending the mouse wheel delta to the server anyway.
  988. // The difference between pixel and line can be important however since
  989. // we have a threshold that can be smaller than the line height.
  990. if (ev.deltaMode !== 0) {
  991. dX *= WHEEL_LINE_HEIGHT;
  992. dY *= WHEEL_LINE_HEIGHT;
  993. }
  994. // Mouse wheel events are sent in steps over VNC. This means that the VNC
  995. // protocol can't handle a wheel event with specific distance or speed.
  996. // Therefor, if we get a lot of small mouse wheel events we combine them.
  997. this._accumulatedWheelDeltaX += dX;
  998. this._accumulatedWheelDeltaY += dY;
  999. // Generate a mouse wheel step event when the accumulated delta
  1000. // for one of the axes is large enough.
  1001. if (Math.abs(this._accumulatedWheelDeltaX) >= WHEEL_STEP) {
  1002. if (this._accumulatedWheelDeltaX < 0) {
  1003. this._handleMouseButton(pos.x, pos.y, true, 1 << 5);
  1004. this._handleMouseButton(pos.x, pos.y, false, 1 << 5);
  1005. } else if (this._accumulatedWheelDeltaX > 0) {
  1006. this._handleMouseButton(pos.x, pos.y, true, 1 << 6);
  1007. this._handleMouseButton(pos.x, pos.y, false, 1 << 6);
  1008. }
  1009. this._accumulatedWheelDeltaX = 0;
  1010. }
  1011. if (Math.abs(this._accumulatedWheelDeltaY) >= WHEEL_STEP) {
  1012. if (this._accumulatedWheelDeltaY < 0) {
  1013. this._handleMouseButton(pos.x, pos.y, true, 1 << 3);
  1014. this._handleMouseButton(pos.x, pos.y, false, 1 << 3);
  1015. } else if (this._accumulatedWheelDeltaY > 0) {
  1016. this._handleMouseButton(pos.x, pos.y, true, 1 << 4);
  1017. this._handleMouseButton(pos.x, pos.y, false, 1 << 4);
  1018. }
  1019. this._accumulatedWheelDeltaY = 0;
  1020. }
  1021. }
  1022. _fakeMouseMove(ev, elementX, elementY) {
  1023. this._handleMouseMove(elementX, elementY);
  1024. this._cursor.move(ev.detail.clientX, ev.detail.clientY);
  1025. }
  1026. _handleTapEvent(ev, bmask) {
  1027. let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
  1028. this._canvas);
  1029. // If the user quickly taps multiple times we assume they meant to
  1030. // hit the same spot, so slightly adjust coordinates
  1031. if ((this._gestureLastTapTime !== null) &&
  1032. ((Date.now() - this._gestureLastTapTime) < DOUBLE_TAP_TIMEOUT) &&
  1033. (this._gestureFirstDoubleTapEv.detail.type === ev.detail.type)) {
  1034. let dx = this._gestureFirstDoubleTapEv.detail.clientX - ev.detail.clientX;
  1035. let dy = this._gestureFirstDoubleTapEv.detail.clientY - ev.detail.clientY;
  1036. let distance = Math.hypot(dx, dy);
  1037. if (distance < DOUBLE_TAP_THRESHOLD) {
  1038. pos = clientToElement(this._gestureFirstDoubleTapEv.detail.clientX,
  1039. this._gestureFirstDoubleTapEv.detail.clientY,
  1040. this._canvas);
  1041. } else {
  1042. this._gestureFirstDoubleTapEv = ev;
  1043. }
  1044. } else {
  1045. this._gestureFirstDoubleTapEv = ev;
  1046. }
  1047. this._gestureLastTapTime = Date.now();
  1048. this._fakeMouseMove(this._gestureFirstDoubleTapEv, pos.x, pos.y);
  1049. this._handleMouseButton(pos.x, pos.y, true, bmask);
  1050. this._handleMouseButton(pos.x, pos.y, false, bmask);
  1051. }
  1052. _handleGesture(ev) {
  1053. let magnitude;
  1054. let pos = clientToElement(ev.detail.clientX, ev.detail.clientY,
  1055. this._canvas);
  1056. switch (ev.type) {
  1057. case 'gesturestart':
  1058. switch (ev.detail.type) {
  1059. case 'onetap':
  1060. this._handleTapEvent(ev, 0x1);
  1061. break;
  1062. case 'twotap':
  1063. this._handleTapEvent(ev, 0x4);
  1064. break;
  1065. case 'threetap':
  1066. this._handleTapEvent(ev, 0x2);
  1067. break;
  1068. case 'drag':
  1069. this._fakeMouseMove(ev, pos.x, pos.y);
  1070. this._handleMouseButton(pos.x, pos.y, true, 0x1);
  1071. break;
  1072. case 'longpress':
  1073. this._fakeMouseMove(ev, pos.x, pos.y);
  1074. this._handleMouseButton(pos.x, pos.y, true, 0x4);
  1075. break;
  1076. case 'twodrag':
  1077. this._gestureLastMagnitudeX = ev.detail.magnitudeX;
  1078. this._gestureLastMagnitudeY = ev.detail.magnitudeY;
  1079. this._fakeMouseMove(ev, pos.x, pos.y);
  1080. break;
  1081. case 'pinch':
  1082. this._gestureLastMagnitudeX = Math.hypot(ev.detail.magnitudeX,
  1083. ev.detail.magnitudeY);
  1084. this._fakeMouseMove(ev, pos.x, pos.y);
  1085. break;
  1086. }
  1087. break;
  1088. case 'gesturemove':
  1089. switch (ev.detail.type) {
  1090. case 'onetap':
  1091. case 'twotap':
  1092. case 'threetap':
  1093. break;
  1094. case 'drag':
  1095. case 'longpress':
  1096. this._fakeMouseMove(ev, pos.x, pos.y);
  1097. break;
  1098. case 'twodrag':
  1099. // Always scroll in the same position.
  1100. // We don't know if the mouse was moved so we need to move it
  1101. // every update.
  1102. this._fakeMouseMove(ev, pos.x, pos.y);
  1103. while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) > GESTURE_SCRLSENS) {
  1104. this._handleMouseButton(pos.x, pos.y, true, 0x8);
  1105. this._handleMouseButton(pos.x, pos.y, false, 0x8);
  1106. this._gestureLastMagnitudeY += GESTURE_SCRLSENS;
  1107. }
  1108. while ((ev.detail.magnitudeY - this._gestureLastMagnitudeY) < -GESTURE_SCRLSENS) {
  1109. this._handleMouseButton(pos.x, pos.y, true, 0x10);
  1110. this._handleMouseButton(pos.x, pos.y, false, 0x10);
  1111. this._gestureLastMagnitudeY -= GESTURE_SCRLSENS;
  1112. }
  1113. while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) > GESTURE_SCRLSENS) {
  1114. this._handleMouseButton(pos.x, pos.y, true, 0x20);
  1115. this._handleMouseButton(pos.x, pos.y, false, 0x20);
  1116. this._gestureLastMagnitudeX += GESTURE_SCRLSENS;
  1117. }
  1118. while ((ev.detail.magnitudeX - this._gestureLastMagnitudeX) < -GESTURE_SCRLSENS) {
  1119. this._handleMouseButton(pos.x, pos.y, true, 0x40);
  1120. this._handleMouseButton(pos.x, pos.y, false, 0x40);
  1121. this._gestureLastMagnitudeX -= GESTURE_SCRLSENS;
  1122. }
  1123. break;
  1124. case 'pinch':
  1125. // Always scroll in the same position.
  1126. // We don't know if the mouse was moved so we need to move it
  1127. // every update.
  1128. this._fakeMouseMove(ev, pos.x, pos.y);
  1129. magnitude = Math.hypot(ev.detail.magnitudeX, ev.detail.magnitudeY);
  1130. if (Math.abs(magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
  1131. this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
  1132. while ((magnitude - this._gestureLastMagnitudeX) > GESTURE_ZOOMSENS) {
  1133. this._handleMouseButton(pos.x, pos.y, true, 0x8);
  1134. this._handleMouseButton(pos.x, pos.y, false, 0x8);
  1135. this._gestureLastMagnitudeX += GESTURE_ZOOMSENS;
  1136. }
  1137. while ((magnitude - this._gestureLastMagnitudeX) < -GESTURE_ZOOMSENS) {
  1138. this._handleMouseButton(pos.x, pos.y, true, 0x10);
  1139. this._handleMouseButton(pos.x, pos.y, false, 0x10);
  1140. this._gestureLastMagnitudeX -= GESTURE_ZOOMSENS;
  1141. }
  1142. }
  1143. this._handleKeyEvent(KeyTable.XK_Control_L, "ControlLeft", false);
  1144. break;
  1145. }
  1146. break;
  1147. case 'gestureend':
  1148. switch (ev.detail.type) {
  1149. case 'onetap':
  1150. case 'twotap':
  1151. case 'threetap':
  1152. case 'pinch':
  1153. case 'twodrag':
  1154. break;
  1155. case 'drag':
  1156. this._fakeMouseMove(ev, pos.x, pos.y);
  1157. this._handleMouseButton(pos.x, pos.y, false, 0x1);
  1158. break;
  1159. case 'longpress':
  1160. this._fakeMouseMove(ev, pos.x, pos.y);
  1161. this._handleMouseButton(pos.x, pos.y, false, 0x4);
  1162. break;
  1163. }
  1164. break;
  1165. }
  1166. }
  1167. // Message Handlers
  1168. _negotiateProtocolVersion() {
  1169. if (this._sock.rQwait("version", 12)) {
  1170. return false;
  1171. }
  1172. const sversion = this._sock.rQshiftStr(12).substr(4, 7);
  1173. Log.Info("Server ProtocolVersion: " + sversion);
  1174. let isRepeater = 0;
  1175. switch (sversion) {
  1176. case "000.000": // UltraVNC repeater
  1177. isRepeater = 1;
  1178. break;
  1179. case "003.003":
  1180. case "003.006": // UltraVNC
  1181. this._rfbVersion = 3.3;
  1182. break;
  1183. case "003.007":
  1184. this._rfbVersion = 3.7;
  1185. break;
  1186. case "003.008":
  1187. case "003.889": // Apple Remote Desktop
  1188. case "004.000": // Intel AMT KVM
  1189. case "004.001": // RealVNC 4.6
  1190. case "005.000": // RealVNC 5.3
  1191. this._rfbVersion = 3.8;
  1192. break;
  1193. default:
  1194. return this._fail("Invalid server version " + sversion);
  1195. }
  1196. if (isRepeater) {
  1197. let repeaterID = "ID:" + this._repeaterID;
  1198. while (repeaterID.length < 250) {
  1199. repeaterID += "\0";
  1200. }
  1201. this._sock.sQpushString(repeaterID);
  1202. this._sock.flush();
  1203. return true;
  1204. }
  1205. if (this._rfbVersion > this._rfbMaxVersion) {
  1206. this._rfbVersion = this._rfbMaxVersion;
  1207. }
  1208. const cversion = "00" + parseInt(this._rfbVersion, 10) +
  1209. ".00" + ((this._rfbVersion * 10) % 10);
  1210. this._sock.sQpushString("RFB " + cversion + "\n");
  1211. this._sock.flush();
  1212. Log.Debug('Sent ProtocolVersion: ' + cversion);
  1213. this._rfbInitState = 'Security';
  1214. }
  1215. _isSupportedSecurityType(type) {
  1216. const clientTypes = [
  1217. securityTypeNone,
  1218. securityTypeVNCAuth,
  1219. securityTypeRA2ne,
  1220. securityTypeTight,
  1221. securityTypeVeNCrypt,
  1222. securityTypeXVP,
  1223. securityTypeARD,
  1224. securityTypeMSLogonII,
  1225. securityTypePlain,
  1226. ];
  1227. return clientTypes.includes(type);
  1228. }
  1229. _negotiateSecurity() {
  1230. if (this._rfbVersion >= 3.7) {
  1231. // Server sends supported list, client decides
  1232. const numTypes = this._sock.rQshift8();
  1233. if (this._sock.rQwait("security type", numTypes, 1)) { return false; }
  1234. if (numTypes === 0) {
  1235. this._rfbInitState = "SecurityReason";
  1236. this._securityContext = "no security types";
  1237. this._securityStatus = 1;
  1238. return true;
  1239. }
  1240. const types = this._sock.rQshiftBytes(numTypes);
  1241. Log.Debug("Server security types: " + types);
  1242. // Look for a matching security type in the order that the
  1243. // server prefers
  1244. this._rfbAuthScheme = -1;
  1245. for (let type of types) {
  1246. if (this._isSupportedSecurityType(type)) {
  1247. this._rfbAuthScheme = type;
  1248. break;
  1249. }
  1250. }
  1251. if (this._rfbAuthScheme === -1) {
  1252. return this._fail("Unsupported security types (types: " + types + ")");
  1253. }
  1254. this._sock.sQpush8(this._rfbAuthScheme);
  1255. this._sock.flush();
  1256. } else {
  1257. // Server decides
  1258. if (this._sock.rQwait("security scheme", 4)) { return false; }
  1259. this._rfbAuthScheme = this._sock.rQshift32();
  1260. if (this._rfbAuthScheme == 0) {
  1261. this._rfbInitState = "SecurityReason";
  1262. this._securityContext = "authentication scheme";
  1263. this._securityStatus = 1;
  1264. return true;
  1265. }
  1266. }
  1267. this._rfbInitState = 'Authentication';
  1268. Log.Debug('Authenticating using scheme: ' + this._rfbAuthScheme);
  1269. return true;
  1270. }
  1271. _handleSecurityReason() {
  1272. if (this._sock.rQwait("reason length", 4)) {
  1273. return false;
  1274. }
  1275. const strlen = this._sock.rQshift32();
  1276. let reason = "";
  1277. if (strlen > 0) {
  1278. if (this._sock.rQwait("reason", strlen, 4)) { return false; }
  1279. reason = this._sock.rQshiftStr(strlen);
  1280. }
  1281. if (reason !== "") {
  1282. this.dispatchEvent(new CustomEvent(
  1283. "securityfailure",
  1284. { detail: { status: this._securityStatus,
  1285. reason: reason } }));
  1286. return this._fail("Security negotiation failed on " +
  1287. this._securityContext +
  1288. " (reason: " + reason + ")");
  1289. } else {
  1290. this.dispatchEvent(new CustomEvent(
  1291. "securityfailure",
  1292. { detail: { status: this._securityStatus } }));
  1293. return this._fail("Security negotiation failed on " +
  1294. this._securityContext);
  1295. }
  1296. }
  1297. // authentication
  1298. _negotiateXvpAuth() {
  1299. if (this._rfbCredentials.username === undefined ||
  1300. this._rfbCredentials.password === undefined ||
  1301. this._rfbCredentials.target === undefined) {
  1302. this.dispatchEvent(new CustomEvent(
  1303. "credentialsrequired",
  1304. { detail: { types: ["username", "password", "target"] } }));
  1305. return false;
  1306. }
  1307. this._sock.sQpush8(this._rfbCredentials.username.length);
  1308. this._sock.sQpush8(this._rfbCredentials.target.length);
  1309. this._sock.sQpushString(this._rfbCredentials.username);
  1310. this._sock.sQpushString(this._rfbCredentials.target);
  1311. this._sock.flush();
  1312. this._rfbAuthScheme = securityTypeVNCAuth;
  1313. return this._negotiateAuthentication();
  1314. }
  1315. // VeNCrypt authentication, currently only supports version 0.2 and only Plain subtype
  1316. _negotiateVeNCryptAuth() {
  1317. // waiting for VeNCrypt version
  1318. if (this._rfbVeNCryptState == 0) {
  1319. if (this._sock.rQwait("vencrypt version", 2)) { return false; }
  1320. const major = this._sock.rQshift8();
  1321. const minor = this._sock.rQshift8();
  1322. if (!(major == 0 && minor == 2)) {
  1323. return this._fail("Unsupported VeNCrypt version " + major + "." + minor);
  1324. }
  1325. this._sock.sQpush8(0);
  1326. this._sock.sQpush8(2);
  1327. this._sock.flush();
  1328. this._rfbVeNCryptState = 1;
  1329. }
  1330. // waiting for ACK
  1331. if (this._rfbVeNCryptState == 1) {
  1332. if (this._sock.rQwait("vencrypt ack", 1)) { return false; }
  1333. const res = this._sock.rQshift8();
  1334. if (res != 0) {
  1335. return this._fail("VeNCrypt failure " + res);
  1336. }
  1337. this._rfbVeNCryptState = 2;
  1338. }
  1339. // must fall through here (i.e. no "else if"), beacause we may have already received
  1340. // the subtypes length and won't be called again
  1341. if (this._rfbVeNCryptState == 2) { // waiting for subtypes length
  1342. if (this._sock.rQwait("vencrypt subtypes length", 1)) { return false; }
  1343. const subtypesLength = this._sock.rQshift8();
  1344. if (subtypesLength < 1) {
  1345. return this._fail("VeNCrypt subtypes empty");
  1346. }
  1347. this._rfbVeNCryptSubtypesLength = subtypesLength;
  1348. this._rfbVeNCryptState = 3;
  1349. }
  1350. // waiting for subtypes list
  1351. if (this._rfbVeNCryptState == 3) {
  1352. if (this._sock.rQwait("vencrypt subtypes", 4 * this._rfbVeNCryptSubtypesLength)) { return false; }
  1353. const subtypes = [];
  1354. for (let i = 0; i < this._rfbVeNCryptSubtypesLength; i++) {
  1355. subtypes.push(this._sock.rQshift32());
  1356. }
  1357. // Look for a matching security type in the order that the
  1358. // server prefers
  1359. this._rfbAuthScheme = -1;
  1360. for (let type of subtypes) {
  1361. // Avoid getting in to a loop
  1362. if (type === securityTypeVeNCrypt) {
  1363. continue;
  1364. }
  1365. if (this._isSupportedSecurityType(type)) {
  1366. this._rfbAuthScheme = type;
  1367. break;
  1368. }
  1369. }
  1370. if (this._rfbAuthScheme === -1) {
  1371. return this._fail("Unsupported security types (types: " + subtypes + ")");
  1372. }
  1373. this._sock.sQpush32(this._rfbAuthScheme);
  1374. this._sock.flush();
  1375. this._rfbVeNCryptState = 4;
  1376. return true;
  1377. }
  1378. }
  1379. _negotiatePlainAuth() {
  1380. if (this._rfbCredentials.username === undefined ||
  1381. this._rfbCredentials.password === undefined) {
  1382. this.dispatchEvent(new CustomEvent(
  1383. "credentialsrequired",
  1384. { detail: { types: ["username", "password"] } }));
  1385. return false;
  1386. }
  1387. const user = encodeUTF8(this._rfbCredentials.username);
  1388. const pass = encodeUTF8(this._rfbCredentials.password);
  1389. this._sock.sQpush32(user.length);
  1390. this._sock.sQpush32(pass.length);
  1391. this._sock.sQpushString(user);
  1392. this._sock.sQpushString(pass);
  1393. this._sock.flush();
  1394. this._rfbInitState = "SecurityResult";
  1395. return true;
  1396. }
  1397. _negotiateStdVNCAuth() {
  1398. if (this._sock.rQwait("auth challenge", 16)) { return false; }
  1399. if (this._rfbCredentials.password === undefined) {
  1400. this.dispatchEvent(new CustomEvent(
  1401. "credentialsrequired",
  1402. { detail: { types: ["password"] } }));
  1403. return false;
  1404. }
  1405. // TODO(directxman12): make genDES not require an Array
  1406. const challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
  1407. const response = RFB.genDES(this._rfbCredentials.password, challenge);
  1408. this._sock.sQpushBytes(response);
  1409. this._sock.flush();
  1410. this._rfbInitState = "SecurityResult";
  1411. return true;
  1412. }
  1413. _negotiateARDAuth() {
  1414. if (this._rfbCredentials.username === undefined ||
  1415. this._rfbCredentials.password === undefined) {
  1416. this.dispatchEvent(new CustomEvent(
  1417. "credentialsrequired",
  1418. { detail: { types: ["username", "password"] } }));
  1419. return false;
  1420. }
  1421. if (this._rfbCredentials.ardPublicKey != undefined &&
  1422. this._rfbCredentials.ardCredentials != undefined) {
  1423. // if the async web crypto is done return the results
  1424. this._sock.sQpushBytes(this._rfbCredentials.ardCredentials);
  1425. this._sock.sQpushBytes(this._rfbCredentials.ardPublicKey);
  1426. this._sock.flush();
  1427. this._rfbCredentials.ardCredentials = null;
  1428. this._rfbCredentials.ardPublicKey = null;
  1429. this._rfbInitState = "SecurityResult";
  1430. return true;
  1431. }
  1432. if (this._sock.rQwait("read ard", 4)) { return false; }
  1433. let generator = this._sock.rQshiftBytes(2); // DH base generator value
  1434. let keyLength = this._sock.rQshift16();
  1435. if (this._sock.rQwait("read ard keylength", keyLength*2, 4)) { return false; }
  1436. // read the server values
  1437. let prime = this._sock.rQshiftBytes(keyLength); // predetermined prime modulus
  1438. let serverPublicKey = this._sock.rQshiftBytes(keyLength); // other party's public key
  1439. let clientKey = legacyCrypto.generateKey(
  1440. { name: "DH", g: generator, p: prime }, false, ["deriveBits"]);
  1441. this._negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey);
  1442. return false;
  1443. }
  1444. async _negotiateARDAuthAsync(keyLength, serverPublicKey, clientKey) {
  1445. const clientPublicKey = legacyCrypto.exportKey("raw", clientKey.publicKey);
  1446. const sharedKey = legacyCrypto.deriveBits(
  1447. { name: "DH", public: serverPublicKey }, clientKey.privateKey, keyLength * 8);
  1448. const username = encodeUTF8(this._rfbCredentials.username).substring(0, 63);
  1449. const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
  1450. const credentials = window.crypto.getRandomValues(new Uint8Array(128));
  1451. for (let i = 0; i < username.length; i++) {
  1452. credentials[i] = username.charCodeAt(i);
  1453. }
  1454. credentials[username.length] = 0;
  1455. for (let i = 0; i < password.length; i++) {
  1456. credentials[64 + i] = password.charCodeAt(i);
  1457. }
  1458. credentials[64 + password.length] = 0;
  1459. const key = await legacyCrypto.digest("MD5", sharedKey);
  1460. const cipher = await legacyCrypto.importKey(
  1461. "raw", key, { name: "AES-ECB" }, false, ["encrypt"]);
  1462. const encrypted = await legacyCrypto.encrypt({ name: "AES-ECB" }, cipher, credentials);
  1463. this._rfbCredentials.ardCredentials = encrypted;
  1464. this._rfbCredentials.ardPublicKey = clientPublicKey;
  1465. this._resumeAuthentication();
  1466. }
  1467. _negotiateTightUnixAuth() {
  1468. if (this._rfbCredentials.username === undefined ||
  1469. this._rfbCredentials.password === undefined) {
  1470. this.dispatchEvent(new CustomEvent(
  1471. "credentialsrequired",
  1472. { detail: { types: ["username", "password"] } }));
  1473. return false;
  1474. }
  1475. this._sock.sQpush32(this._rfbCredentials.username.length);
  1476. this._sock.sQpush32(this._rfbCredentials.password.length);
  1477. this._sock.sQpushString(this._rfbCredentials.username);
  1478. this._sock.sQpushString(this._rfbCredentials.password);
  1479. this._sock.flush();
  1480. this._rfbInitState = "SecurityResult";
  1481. return true;
  1482. }
  1483. _negotiateTightTunnels(numTunnels) {
  1484. const clientSupportedTunnelTypes = {
  1485. 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
  1486. };
  1487. const serverSupportedTunnelTypes = {};
  1488. // receive tunnel capabilities
  1489. for (let i = 0; i < numTunnels; i++) {
  1490. const capCode = this._sock.rQshift32();
  1491. const capVendor = this._sock.rQshiftStr(4);
  1492. const capSignature = this._sock.rQshiftStr(8);
  1493. serverSupportedTunnelTypes[capCode] = { vendor: capVendor, signature: capSignature };
  1494. }
  1495. Log.Debug("Server Tight tunnel types: " + serverSupportedTunnelTypes);
  1496. // Siemens touch panels have a VNC server that supports NOTUNNEL,
  1497. // but forgets to advertise it. Try to detect such servers by
  1498. // looking for their custom tunnel type.
  1499. if (serverSupportedTunnelTypes[1] &&
  1500. (serverSupportedTunnelTypes[1].vendor === "SICR") &&
  1501. (serverSupportedTunnelTypes[1].signature === "SCHANNEL")) {
  1502. Log.Debug("Detected Siemens server. Assuming NOTUNNEL support.");
  1503. serverSupportedTunnelTypes[0] = { vendor: 'TGHT', signature: 'NOTUNNEL' };
  1504. }
  1505. // choose the notunnel type
  1506. if (serverSupportedTunnelTypes[0]) {
  1507. if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
  1508. serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
  1509. return this._fail("Client's tunnel type had the incorrect " +
  1510. "vendor or signature");
  1511. }
  1512. Log.Debug("Selected tunnel type: " + clientSupportedTunnelTypes[0]);
  1513. this._sock.sQpush32(0); // use NOTUNNEL
  1514. this._sock.flush();
  1515. return false; // wait until we receive the sub auth count to continue
  1516. } else {
  1517. return this._fail("Server wanted tunnels, but doesn't support " +
  1518. "the notunnel type");
  1519. }
  1520. }
  1521. _negotiateTightAuth() {
  1522. if (!this._rfbTightVNC) { // first pass, do the tunnel negotiation
  1523. if (this._sock.rQwait("num tunnels", 4)) { return false; }
  1524. const numTunnels = this._sock.rQshift32();
  1525. if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
  1526. this._rfbTightVNC = true;
  1527. if (numTunnels > 0) {
  1528. this._negotiateTightTunnels(numTunnels);
  1529. return false; // wait until we receive the sub auth to continue
  1530. }
  1531. }
  1532. // second pass, do the sub-auth negotiation
  1533. if (this._sock.rQwait("sub auth count", 4)) { return false; }
  1534. const subAuthCount = this._sock.rQshift32();
  1535. if (subAuthCount === 0) { // empty sub-auth list received means 'no auth' subtype selected
  1536. this._rfbInitState = 'SecurityResult';
  1537. return true;
  1538. }
  1539. if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
  1540. const clientSupportedTypes = {
  1541. 'STDVNOAUTH__': 1,
  1542. 'STDVVNCAUTH_': 2,
  1543. 'TGHTULGNAUTH': 129
  1544. };
  1545. const serverSupportedTypes = [];
  1546. for (let i = 0; i < subAuthCount; i++) {
  1547. this._sock.rQshift32(); // capNum
  1548. const capabilities = this._sock.rQshiftStr(12);
  1549. serverSupportedTypes.push(capabilities);
  1550. }
  1551. Log.Debug("Server Tight authentication types: " + serverSupportedTypes);
  1552. for (let authType in clientSupportedTypes) {
  1553. if (serverSupportedTypes.indexOf(authType) != -1) {
  1554. this._sock.sQpush32(clientSupportedTypes[authType]);
  1555. this._sock.flush();
  1556. Log.Debug("Selected authentication type: " + authType);
  1557. switch (authType) {
  1558. case 'STDVNOAUTH__': // no auth
  1559. this._rfbInitState = 'SecurityResult';
  1560. return true;
  1561. case 'STDVVNCAUTH_':
  1562. this._rfbAuthScheme = securityTypeVNCAuth;
  1563. return true;
  1564. case 'TGHTULGNAUTH':
  1565. this._rfbAuthScheme = securityTypeUnixLogon;
  1566. return true;
  1567. default:
  1568. return this._fail("Unsupported tiny auth scheme " +
  1569. "(scheme: " + authType + ")");
  1570. }
  1571. }
  1572. }
  1573. return this._fail("No supported sub-auth types!");
  1574. }
  1575. _handleRSAAESCredentialsRequired(event) {
  1576. this.dispatchEvent(event);
  1577. }
  1578. _handleRSAAESServerVerification(event) {
  1579. this.dispatchEvent(event);
  1580. }
  1581. _negotiateRA2neAuth() {
  1582. if (this._rfbRSAAESAuthenticationState === null) {
  1583. this._rfbRSAAESAuthenticationState = new RSAAESAuthenticationState(this._sock, () => this._rfbCredentials);
  1584. this._rfbRSAAESAuthenticationState.addEventListener(
  1585. "serververification", this._eventHandlers.handleRSAAESServerVerification);
  1586. this._rfbRSAAESAuthenticationState.addEventListener(
  1587. "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
  1588. }
  1589. this._rfbRSAAESAuthenticationState.checkInternalEvents();
  1590. if (!this._rfbRSAAESAuthenticationState.hasStarted) {
  1591. this._rfbRSAAESAuthenticationState.negotiateRA2neAuthAsync()
  1592. .catch((e) => {
  1593. if (e.message !== "disconnect normally") {
  1594. this._fail(e.message);
  1595. }
  1596. })
  1597. .then(() => {
  1598. this._rfbInitState = "SecurityResult";
  1599. return true;
  1600. }).finally(() => {
  1601. this._rfbRSAAESAuthenticationState.removeEventListener(
  1602. "serververification", this._eventHandlers.handleRSAAESServerVerification);
  1603. this._rfbRSAAESAuthenticationState.removeEventListener(
  1604. "credentialsrequired", this._eventHandlers.handleRSAAESCredentialsRequired);
  1605. this._rfbRSAAESAuthenticationState = null;
  1606. });
  1607. }
  1608. return false;
  1609. }
  1610. _negotiateMSLogonIIAuth() {
  1611. if (this._sock.rQwait("mslogonii dh param", 24)) { return false; }
  1612. if (this._rfbCredentials.username === undefined ||
  1613. this._rfbCredentials.password === undefined) {
  1614. this.dispatchEvent(new CustomEvent(
  1615. "credentialsrequired",
  1616. { detail: { types: ["username", "password"] } }));
  1617. return false;
  1618. }
  1619. const g = this._sock.rQshiftBytes(8);
  1620. const p = this._sock.rQshiftBytes(8);
  1621. const A = this._sock.rQshiftBytes(8);
  1622. const dhKey = legacyCrypto.generateKey({ name: "DH", g: g, p: p }, true, ["deriveBits"]);
  1623. const B = legacyCrypto.exportKey("raw", dhKey.publicKey);
  1624. const secret = legacyCrypto.deriveBits({ name: "DH", public: A }, dhKey.privateKey, 64);
  1625. const key = legacyCrypto.importKey("raw", secret, { name: "DES-CBC" }, false, ["encrypt"]);
  1626. const username = encodeUTF8(this._rfbCredentials.username).substring(0, 255);
  1627. const password = encodeUTF8(this._rfbCredentials.password).substring(0, 63);
  1628. let usernameBytes = new Uint8Array(256);
  1629. let passwordBytes = new Uint8Array(64);
  1630. window.crypto.getRandomValues(usernameBytes);
  1631. window.crypto.getRandomValues(passwordBytes);
  1632. for (let i = 0; i < username.length; i++) {
  1633. usernameBytes[i] = username.charCodeAt(i);
  1634. }
  1635. usernameBytes[username.length] = 0;
  1636. for (let i = 0; i < password.length; i++) {
  1637. passwordBytes[i] = password.charCodeAt(i);
  1638. }
  1639. passwordBytes[password.length] = 0;
  1640. usernameBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, usernameBytes);
  1641. passwordBytes = legacyCrypto.encrypt({ name: "DES-CBC", iv: secret }, key, passwordBytes);
  1642. this._sock.sQpushBytes(B);
  1643. this._sock.sQpushBytes(usernameBytes);
  1644. this._sock.sQpushBytes(passwordBytes);
  1645. this._sock.flush();
  1646. this._rfbInitState = "SecurityResult";
  1647. return true;
  1648. }
  1649. _negotiateAuthentication() {
  1650. switch (this._rfbAuthScheme) {
  1651. case securityTypeNone:
  1652. if (this._rfbVersion >= 3.8) {
  1653. this._rfbInitState = 'SecurityResult';
  1654. } else {
  1655. this._rfbInitState = 'ClientInitialisation';
  1656. }
  1657. return true;
  1658. case securityTypeXVP:
  1659. return this._negotiateXvpAuth();
  1660. case securityTypeARD:
  1661. return this._negotiateARDAuth();
  1662. case securityTypeVNCAuth:
  1663. return this._negotiateStdVNCAuth();
  1664. case securityTypeTight:
  1665. return this._negotiateTightAuth();
  1666. case securityTypeVeNCrypt:
  1667. return this._negotiateVeNCryptAuth();
  1668. case securityTypePlain:
  1669. return this._negotiatePlainAuth();
  1670. case securityTypeUnixLogon:
  1671. return this._negotiateTightUnixAuth();
  1672. case securityTypeRA2ne:
  1673. return this._negotiateRA2neAuth();
  1674. case securityTypeMSLogonII:
  1675. return this._negotiateMSLogonIIAuth();
  1676. default:
  1677. return this._fail("Unsupported auth scheme (scheme: " +
  1678. this._rfbAuthScheme + ")");
  1679. }
  1680. }
  1681. _handleSecurityResult() {
  1682. if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
  1683. const status = this._sock.rQshift32();
  1684. if (status === 0) { // OK
  1685. this._rfbInitState = 'ClientInitialisation';
  1686. Log.Debug('Authentication OK');
  1687. return true;
  1688. } else {
  1689. if (this._rfbVersion >= 3.8) {
  1690. this._rfbInitState = "SecurityReason";
  1691. this._securityContext = "security result";
  1692. this._securityStatus = status;
  1693. return true;
  1694. } else {
  1695. this.dispatchEvent(new CustomEvent(
  1696. "securityfailure",
  1697. { detail: { status: status } }));
  1698. return this._fail("Security handshake failed");
  1699. }
  1700. }
  1701. }
  1702. _negotiateServerInit() {
  1703. if (this._sock.rQwait("server initialization", 24)) { return false; }
  1704. /* Screen size */
  1705. const width = this._sock.rQshift16();
  1706. const height = this._sock.rQshift16();
  1707. /* PIXEL_FORMAT */
  1708. const bpp = this._sock.rQshift8();
  1709. const depth = this._sock.rQshift8();
  1710. const bigEndian = this._sock.rQshift8();
  1711. const trueColor = this._sock.rQshift8();
  1712. const redMax = this._sock.rQshift16();
  1713. const greenMax = this._sock.rQshift16();
  1714. const blueMax = this._sock.rQshift16();
  1715. const redShift = this._sock.rQshift8();
  1716. const greenShift = this._sock.rQshift8();
  1717. const blueShift = this._sock.rQshift8();
  1718. this._sock.rQskipBytes(3); // padding
  1719. // NB(directxman12): we don't want to call any callbacks or print messages until
  1720. // *after* we're past the point where we could backtrack
  1721. /* Connection name/title */
  1722. const nameLength = this._sock.rQshift32();
  1723. if (this._sock.rQwait('server init name', nameLength, 24)) { return false; }
  1724. let name = this._sock.rQshiftStr(nameLength);
  1725. name = decodeUTF8(name, true);
  1726. if (this._rfbTightVNC) {
  1727. if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + nameLength)) { return false; }
  1728. // In TightVNC mode, ServerInit message is extended
  1729. const numServerMessages = this._sock.rQshift16();
  1730. const numClientMessages = this._sock.rQshift16();
  1731. const numEncodings = this._sock.rQshift16();
  1732. this._sock.rQskipBytes(2); // padding
  1733. const totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
  1734. if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + nameLength)) { return false; }
  1735. // we don't actually do anything with the capability information that TIGHT sends,
  1736. // so we just skip the all of this.
  1737. // TIGHT server message capabilities
  1738. this._sock.rQskipBytes(16 * numServerMessages);
  1739. // TIGHT client message capabilities
  1740. this._sock.rQskipBytes(16 * numClientMessages);
  1741. // TIGHT encoding capabilities
  1742. this._sock.rQskipBytes(16 * numEncodings);
  1743. }
  1744. // NB(directxman12): these are down here so that we don't run them multiple times
  1745. // if we backtrack
  1746. Log.Info("Screen: " + width + "x" + height +
  1747. ", bpp: " + bpp + ", depth: " + depth +
  1748. ", bigEndian: " + bigEndian +
  1749. ", trueColor: " + trueColor +
  1750. ", redMax: " + redMax +
  1751. ", greenMax: " + greenMax +
  1752. ", blueMax: " + blueMax +
  1753. ", redShift: " + redShift +
  1754. ", greenShift: " + greenShift +
  1755. ", blueShift: " + blueShift);
  1756. // we're past the point where we could backtrack, so it's safe to call this
  1757. this._setDesktopName(name);
  1758. this._resize(width, height);
  1759. if (!this._viewOnly) { this._keyboard.grab(); }
  1760. this._fbDepth = 24;
  1761. if (this._fbName === "Intel(r) AMT KVM") {
  1762. Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
  1763. this._fbDepth = 8;
  1764. }
  1765. RFB.messages.pixelFormat(this._sock, this._fbDepth, true);
  1766. this._sendEncodings();
  1767. RFB.messages.fbUpdateRequest(this._sock, false, 0, 0, this._fbWidth, this._fbHeight);
  1768. this._updateConnectionState('connected');
  1769. return true;
  1770. }
  1771. _sendEncodings() {
  1772. const encs = [];
  1773. // In preference order
  1774. encs.push(encodings.encodingCopyRect);
  1775. // Only supported with full depth support
  1776. if (this._fbDepth == 24) {
  1777. encs.push(encodings.encodingTight);
  1778. encs.push(encodings.encodingTightPNG);
  1779. encs.push(encodings.encodingZRLE);
  1780. encs.push(encodings.encodingJPEG);
  1781. encs.push(encodings.encodingHextile);
  1782. encs.push(encodings.encodingRRE);
  1783. }
  1784. encs.push(encodings.encodingRaw);
  1785. // Psuedo-encoding settings
  1786. encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
  1787. encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);
  1788. encs.push(encodings.pseudoEncodingDesktopSize);
  1789. encs.push(encodings.pseudoEncodingLastRect);
  1790. encs.push(encodings.pseudoEncodingQEMUExtendedKeyEvent);
  1791. encs.push(encodings.pseudoEncodingQEMULedEvent);
  1792. encs.push(encodings.pseudoEncodingExtendedDesktopSize);
  1793. encs.push(encodings.pseudoEncodingXvp);
  1794. encs.push(encodings.pseudoEncodingFence);
  1795. encs.push(encodings.pseudoEncodingContinuousUpdates);
  1796. encs.push(encodings.pseudoEncodingDesktopName);
  1797. encs.push(encodings.pseudoEncodingExtendedClipboard);
  1798. if (this._fbDepth == 24) {
  1799. encs.push(encodings.pseudoEncodingVMwareCursor);
  1800. encs.push(encodings.pseudoEncodingCursor);
  1801. }
  1802. RFB.messages.clientEncodings(this._sock, encs);
  1803. }
  1804. /* RFB protocol initialization states:
  1805. * ProtocolVersion
  1806. * Security
  1807. * Authentication
  1808. * SecurityResult
  1809. * ClientInitialization - not triggered by server message
  1810. * ServerInitialization
  1811. */
  1812. _initMsg() {
  1813. switch (this._rfbInitState) {
  1814. case 'ProtocolVersion':
  1815. return this._negotiateProtocolVersion();
  1816. case 'Security':
  1817. return this._negotiateSecurity();
  1818. case 'Authentication':
  1819. return this._negotiateAuthentication();
  1820. case 'SecurityResult':
  1821. return this._handleSecurityResult();
  1822. case 'SecurityReason':
  1823. return this._handleSecurityReason();
  1824. case 'ClientInitialisation':
  1825. this._sock.sQpush8(this._shared ? 1 : 0); // ClientInitialisation
  1826. this._sock.flush();
  1827. this._rfbInitState = 'ServerInitialisation';
  1828. return true;
  1829. case 'ServerInitialisation':
  1830. return this._negotiateServerInit();
  1831. default:
  1832. return this._fail("Unknown init state (state: " +
  1833. this._rfbInitState + ")");
  1834. }
  1835. }
  1836. // Resume authentication handshake after it was paused for some
  1837. // reason, e.g. waiting for a password from the user
  1838. _resumeAuthentication() {
  1839. // We use setTimeout() so it's run in its own context, just like
  1840. // it originally did via the WebSocket's event handler
  1841. setTimeout(this._initMsg.bind(this), 0);
  1842. }
  1843. _handleSetColourMapMsg() {
  1844. Log.Debug("SetColorMapEntries");
  1845. return this._fail("Unexpected SetColorMapEntries message");
  1846. }
  1847. _handleServerCutText() {
  1848. Log.Debug("ServerCutText");
  1849. if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
  1850. this._sock.rQskipBytes(3); // Padding
  1851. let length = this._sock.rQshift32();
  1852. length = toSigned32bit(length);
  1853. if (this._sock.rQwait("ServerCutText content", Math.abs(length), 8)) { return false; }
  1854. if (length >= 0) {
  1855. //Standard msg
  1856. const text = this._sock.rQshiftStr(length);
  1857. if (this._viewOnly) {
  1858. return true;
  1859. }
  1860. this.dispatchEvent(new CustomEvent(
  1861. "clipboard",
  1862. { detail: { text: text } }));
  1863. } else {
  1864. //Extended msg.
  1865. length = Math.abs(length);
  1866. const flags = this._sock.rQshift32();
  1867. let formats = flags & 0x0000FFFF;
  1868. let actions = flags & 0xFF000000;
  1869. let isCaps = (!!(actions & extendedClipboardActionCaps));
  1870. if (isCaps) {
  1871. this._clipboardServerCapabilitiesFormats = {};
  1872. this._clipboardServerCapabilitiesActions = {};
  1873. // Update our server capabilities for Formats
  1874. for (let i = 0; i <= 15; i++) {
  1875. let index = 1 << i;
  1876. // Check if format flag is set.
  1877. if ((formats & index)) {
  1878. this._clipboardServerCapabilitiesFormats[index] = true;
  1879. // We don't send unsolicited clipboard, so we
  1880. // ignore the size
  1881. this._sock.rQshift32();
  1882. }
  1883. }
  1884. // Update our server capabilities for Actions
  1885. for (let i = 24; i <= 31; i++) {
  1886. let index = 1 << i;
  1887. this._clipboardServerCapabilitiesActions[index] = !!(actions & index);
  1888. }
  1889. /* Caps handling done, send caps with the clients
  1890. capabilities set as a response */
  1891. let clientActions = [
  1892. extendedClipboardActionCaps,
  1893. extendedClipboardActionRequest,
  1894. extendedClipboardActionPeek,
  1895. extendedClipboardActionNotify,
  1896. extendedClipboardActionProvide
  1897. ];
  1898. RFB.messages.extendedClipboardCaps(this._sock, clientActions, {extendedClipboardFormatText: 0});
  1899. } else if (actions === extendedClipboardActionRequest) {
  1900. if (this._viewOnly) {
  1901. return true;
  1902. }
  1903. // Check if server has told us it can handle Provide and there is clipboard data to send.
  1904. if (this._clipboardText != null &&
  1905. this._clipboardServerCapabilitiesActions[extendedClipboardActionProvide]) {
  1906. if (formats & extendedClipboardFormatText) {
  1907. RFB.messages.extendedClipboardProvide(this._sock, [extendedClipboardFormatText], [this._clipboardText]);
  1908. }
  1909. }
  1910. } else if (actions === extendedClipboardActionPeek) {
  1911. if (this._viewOnly) {
  1912. return true;
  1913. }
  1914. if (this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) {
  1915. if (this._clipboardText != null) {
  1916. RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]);
  1917. } else {
  1918. RFB.messages.extendedClipboardNotify(this._sock, []);
  1919. }
  1920. }
  1921. } else if (actions === extendedClipboardActionNotify) {
  1922. if (this._viewOnly) {
  1923. return true;
  1924. }
  1925. if (this._clipboardServerCapabilitiesActions[extendedClipboardActionRequest]) {
  1926. if (formats & extendedClipboardFormatText) {
  1927. RFB.messages.extendedClipboardRequest(this._sock, [extendedClipboardFormatText]);
  1928. }
  1929. }
  1930. } else if (actions === extendedClipboardActionProvide) {
  1931. if (this._viewOnly) {
  1932. return true;
  1933. }
  1934. if (!(formats & extendedClipboardFormatText)) {
  1935. return true;
  1936. }
  1937. // Ignore what we had in our clipboard client side.
  1938. this._clipboardText = null;
  1939. // FIXME: Should probably verify that this data was actually requested
  1940. let zlibStream = this._sock.rQshiftBytes(length - 4);
  1941. let streamInflator = new Inflator();
  1942. let textData = null;
  1943. streamInflator.setInput(zlibStream);
  1944. for (let i = 0; i <= 15; i++) {
  1945. let format = 1 << i;
  1946. if (formats & format) {
  1947. let size = 0x00;
  1948. let sizeArray = streamInflator.inflate(4);
  1949. size |= (sizeArray[0] << 24);
  1950. size |= (sizeArray[1] << 16);
  1951. size |= (sizeArray[2] << 8);
  1952. size |= (sizeArray[3]);
  1953. let chunk = streamInflator.inflate(size);
  1954. if (format === extendedClipboardFormatText) {
  1955. textData = chunk;
  1956. }
  1957. }
  1958. }
  1959. streamInflator.setInput(null);
  1960. if (textData !== null) {
  1961. let tmpText = "";
  1962. for (let i = 0; i < textData.length; i++) {
  1963. tmpText += String.fromCharCode(textData[i]);
  1964. }
  1965. textData = tmpText;
  1966. textData = decodeUTF8(textData);
  1967. if ((textData.length > 0) && "\0" === textData.charAt(textData.length - 1)) {
  1968. textData = textData.slice(0, -1);
  1969. }
  1970. textData = textData.replaceAll("\r\n", "\n");
  1971. this.dispatchEvent(new CustomEvent(
  1972. "clipboard",
  1973. { detail: { text: textData } }));
  1974. }
  1975. } else {
  1976. return this._fail("Unexpected action in extended clipboard message: " + actions);
  1977. }
  1978. }
  1979. return true;
  1980. }
  1981. _handleServerFenceMsg() {
  1982. if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
  1983. this._sock.rQskipBytes(3); // Padding
  1984. let flags = this._sock.rQshift32();
  1985. let length = this._sock.rQshift8();
  1986. if (this._sock.rQwait("ServerFence payload", length, 9)) { return false; }
  1987. if (length > 64) {
  1988. Log.Warn("Bad payload length (" + length + ") in fence response");
  1989. length = 64;
  1990. }
  1991. const payload = this._sock.rQshiftStr(length);
  1992. this._supportsFence = true;
  1993. /*
  1994. * Fence flags
  1995. *
  1996. * (1<<0) - BlockBefore
  1997. * (1<<1) - BlockAfter
  1998. * (1<<2) - SyncNext
  1999. * (1<<31) - Request
  2000. */
  2001. if (!(flags & (1<<31))) {
  2002. return this._fail("Unexpected fence response");
  2003. }
  2004. // Filter out unsupported flags
  2005. // FIXME: support syncNext
  2006. flags &= (1<<0) | (1<<1);
  2007. // BlockBefore and BlockAfter are automatically handled by
  2008. // the fact that we process each incoming message
  2009. // synchronuosly.
  2010. RFB.messages.clientFence(this._sock, flags, payload);
  2011. return true;
  2012. }
  2013. _handleXvpMsg() {
  2014. if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
  2015. this._sock.rQskipBytes(1); // Padding
  2016. const xvpVer = this._sock.rQshift8();
  2017. const xvpMsg = this._sock.rQshift8();
  2018. switch (xvpMsg) {
  2019. case 0: // XVP_FAIL
  2020. Log.Error("XVP Operation Failed");
  2021. break;
  2022. case 1: // XVP_INIT
  2023. this._rfbXvpVer = xvpVer;
  2024. Log.Info("XVP extensions enabled (version " + this._rfbXvpVer + ")");
  2025. this._setCapability("power", true);
  2026. break;
  2027. default:
  2028. this._fail("Illegal server XVP message (msg: " + xvpMsg + ")");
  2029. break;
  2030. }
  2031. return true;
  2032. }
  2033. _normalMsg() {
  2034. let msgType;
  2035. if (this._FBU.rects > 0) {
  2036. msgType = 0;
  2037. } else {
  2038. msgType = this._sock.rQshift8();
  2039. }
  2040. let first, ret;
  2041. switch (msgType) {
  2042. case 0: // FramebufferUpdate
  2043. ret = this._framebufferUpdate();
  2044. if (ret && !this._enabledContinuousUpdates) {
  2045. RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
  2046. this._fbWidth, this._fbHeight);
  2047. }
  2048. return ret;
  2049. case 1: // SetColorMapEntries
  2050. return this._handleSetColourMapMsg();
  2051. case 2: // Bell
  2052. Log.Debug("Bell");
  2053. this.dispatchEvent(new CustomEvent(
  2054. "bell",
  2055. { detail: {} }));
  2056. return true;
  2057. case 3: // ServerCutText
  2058. return this._handleServerCutText();
  2059. case 150: // EndOfContinuousUpdates
  2060. first = !this._supportsContinuousUpdates;
  2061. this._supportsContinuousUpdates = true;
  2062. this._enabledContinuousUpdates = false;
  2063. if (first) {
  2064. this._enabledContinuousUpdates = true;
  2065. this._updateContinuousUpdates();
  2066. Log.Info("Enabling continuous updates.");
  2067. } else {
  2068. // FIXME: We need to send a framebufferupdaterequest here
  2069. // if we add support for turning off continuous updates
  2070. }
  2071. return true;
  2072. case 248: // ServerFence
  2073. return this._handleServerFenceMsg();
  2074. case 250: // XVP
  2075. return this._handleXvpMsg();
  2076. default:
  2077. this._fail("Unexpected server message (type " + msgType + ")");
  2078. Log.Debug("sock.rQpeekBytes(30): " + this._sock.rQpeekBytes(30));
  2079. return true;
  2080. }
  2081. }
  2082. _framebufferUpdate() {
  2083. if (this._FBU.rects === 0) {
  2084. if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
  2085. this._sock.rQskipBytes(1); // Padding
  2086. this._FBU.rects = this._sock.rQshift16();
  2087. // Make sure the previous frame is fully rendered first
  2088. // to avoid building up an excessive queue
  2089. if (this._display.pending()) {
  2090. this._flushing = true;
  2091. this._display.flush()
  2092. .then(() => {
  2093. this._flushing = false;
  2094. // Resume processing
  2095. if (!this._sock.rQwait("message", 1)) {
  2096. this._handleMessage();
  2097. }
  2098. });
  2099. return false;
  2100. }
  2101. }
  2102. while (this._FBU.rects > 0) {
  2103. if (this._FBU.encoding === null) {
  2104. if (this._sock.rQwait("rect header", 12)) { return false; }
  2105. /* New FramebufferUpdate */
  2106. this._FBU.x = this._sock.rQshift16();
  2107. this._FBU.y = this._sock.rQshift16();
  2108. this._FBU.width = this._sock.rQshift16();
  2109. this._FBU.height = this._sock.rQshift16();
  2110. this._FBU.encoding = this._sock.rQshift32();
  2111. /* Encodings are signed */
  2112. this._FBU.encoding >>= 0;
  2113. }
  2114. if (!this._handleRect()) {
  2115. return false;
  2116. }
  2117. this._FBU.rects--;
  2118. this._FBU.encoding = null;
  2119. }
  2120. this._display.flip();
  2121. return true; // We finished this FBU
  2122. }
  2123. _handleRect() {
  2124. switch (this._FBU.encoding) {
  2125. case encodings.pseudoEncodingLastRect:
  2126. this._FBU.rects = 1; // Will be decreased when we return
  2127. return true;
  2128. case encodings.pseudoEncodingVMwareCursor:
  2129. return this._handleVMwareCursor();
  2130. case encodings.pseudoEncodingCursor:
  2131. return this._handleCursor();
  2132. case encodings.pseudoEncodingQEMUExtendedKeyEvent:
  2133. this._qemuExtKeyEventSupported = true;
  2134. return true;
  2135. case encodings.pseudoEncodingDesktopName:
  2136. return this._handleDesktopName();
  2137. case encodings.pseudoEncodingDesktopSize:
  2138. this._resize(this._FBU.width, this._FBU.height);
  2139. return true;
  2140. case encodings.pseudoEncodingExtendedDesktopSize:
  2141. return this._handleExtendedDesktopSize();
  2142. case encodings.pseudoEncodingQEMULedEvent:
  2143. return this._handleLedEvent();
  2144. default:
  2145. return this._handleDataRect();
  2146. }
  2147. }
  2148. _handleVMwareCursor() {
  2149. const hotx = this._FBU.x; // hotspot-x
  2150. const hoty = this._FBU.y; // hotspot-y
  2151. const w = this._FBU.width;
  2152. const h = this._FBU.height;
  2153. if (this._sock.rQwait("VMware cursor encoding", 1)) {
  2154. return false;
  2155. }
  2156. const cursorType = this._sock.rQshift8();
  2157. this._sock.rQshift8(); //Padding
  2158. let rgba;
  2159. const bytesPerPixel = 4;
  2160. //Classic cursor
  2161. if (cursorType == 0) {
  2162. //Used to filter away unimportant bits.
  2163. //OR is used for correct conversion in js.
  2164. const PIXEL_MASK = 0xffffff00 | 0;
  2165. rgba = new Array(w * h * bytesPerPixel);
  2166. if (this._sock.rQwait("VMware cursor classic encoding",
  2167. (w * h * bytesPerPixel) * 2, 2)) {
  2168. return false;
  2169. }
  2170. let andMask = new Array(w * h);
  2171. for (let pixel = 0; pixel < (w * h); pixel++) {
  2172. andMask[pixel] = this._sock.rQshift32();
  2173. }
  2174. let xorMask = new Array(w * h);
  2175. for (let pixel = 0; pixel < (w * h); pixel++) {
  2176. xorMask[pixel] = this._sock.rQshift32();
  2177. }
  2178. for (let pixel = 0; pixel < (w * h); pixel++) {
  2179. if (andMask[pixel] == 0) {
  2180. //Fully opaque pixel
  2181. let bgr = xorMask[pixel];
  2182. let r = bgr >> 8 & 0xff;
  2183. let g = bgr >> 16 & 0xff;
  2184. let b = bgr >> 24 & 0xff;
  2185. rgba[(pixel * bytesPerPixel) ] = r; //r
  2186. rgba[(pixel * bytesPerPixel) + 1 ] = g; //g
  2187. rgba[(pixel * bytesPerPixel) + 2 ] = b; //b
  2188. rgba[(pixel * bytesPerPixel) + 3 ] = 0xff; //a
  2189. } else if ((andMask[pixel] & PIXEL_MASK) ==
  2190. PIXEL_MASK) {
  2191. //Only screen value matters, no mouse colouring
  2192. if (xorMask[pixel] == 0) {
  2193. //Transparent pixel
  2194. rgba[(pixel * bytesPerPixel) ] = 0x00;
  2195. rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
  2196. rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
  2197. rgba[(pixel * bytesPerPixel) + 3 ] = 0x00;
  2198. } else if ((xorMask[pixel] & PIXEL_MASK) ==
  2199. PIXEL_MASK) {
  2200. //Inverted pixel, not supported in browsers.
  2201. //Fully opaque instead.
  2202. rgba[(pixel * bytesPerPixel) ] = 0x00;
  2203. rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
  2204. rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
  2205. rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
  2206. } else {
  2207. //Unhandled xorMask
  2208. rgba[(pixel * bytesPerPixel) ] = 0x00;
  2209. rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
  2210. rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
  2211. rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
  2212. }
  2213. } else {
  2214. //Unhandled andMask
  2215. rgba[(pixel * bytesPerPixel) ] = 0x00;
  2216. rgba[(pixel * bytesPerPixel) + 1 ] = 0x00;
  2217. rgba[(pixel * bytesPerPixel) + 2 ] = 0x00;
  2218. rgba[(pixel * bytesPerPixel) + 3 ] = 0xff;
  2219. }
  2220. }
  2221. //Alpha cursor.
  2222. } else if (cursorType == 1) {
  2223. if (this._sock.rQwait("VMware cursor alpha encoding",
  2224. (w * h * 4), 2)) {
  2225. return false;
  2226. }
  2227. rgba = new Array(w * h * bytesPerPixel);
  2228. for (let pixel = 0; pixel < (w * h); pixel++) {
  2229. let data = this._sock.rQshift32();
  2230. rgba[(pixel * 4) ] = data >> 24 & 0xff; //r
  2231. rgba[(pixel * 4) + 1 ] = data >> 16 & 0xff; //g
  2232. rgba[(pixel * 4) + 2 ] = data >> 8 & 0xff; //b
  2233. rgba[(pixel * 4) + 3 ] = data & 0xff; //a
  2234. }
  2235. } else {
  2236. Log.Warn("The given cursor type is not supported: "
  2237. + cursorType + " given.");
  2238. return false;
  2239. }
  2240. this._updateCursor(rgba, hotx, hoty, w, h);
  2241. return true;
  2242. }
  2243. _handleCursor() {
  2244. const hotx = this._FBU.x; // hotspot-x
  2245. const hoty = this._FBU.y; // hotspot-y
  2246. const w = this._FBU.width;
  2247. const h = this._FBU.height;
  2248. const pixelslength = w * h * 4;
  2249. const masklength = Math.ceil(w / 8) * h;
  2250. let bytes = pixelslength + masklength;
  2251. if (this._sock.rQwait("cursor encoding", bytes)) {
  2252. return false;
  2253. }
  2254. // Decode from BGRX pixels + bit mask to RGBA
  2255. const pixels = this._sock.rQshiftBytes(pixelslength);
  2256. const mask = this._sock.rQshiftBytes(masklength);
  2257. let rgba = new Uint8Array(w * h * 4);
  2258. let pixIdx = 0;
  2259. for (let y = 0; y < h; y++) {
  2260. for (let x = 0; x < w; x++) {
  2261. let maskIdx = y * Math.ceil(w / 8) + Math.floor(x / 8);
  2262. let alpha = (mask[maskIdx] << (x % 8)) & 0x80 ? 255 : 0;
  2263. rgba[pixIdx ] = pixels[pixIdx + 2];
  2264. rgba[pixIdx + 1] = pixels[pixIdx + 1];
  2265. rgba[pixIdx + 2] = pixels[pixIdx];
  2266. rgba[pixIdx + 3] = alpha;
  2267. pixIdx += 4;
  2268. }
  2269. }
  2270. this._updateCursor(rgba, hotx, hoty, w, h);
  2271. return true;
  2272. }
  2273. _handleDesktopName() {
  2274. if (this._sock.rQwait("DesktopName", 4)) {
  2275. return false;
  2276. }
  2277. let length = this._sock.rQshift32();
  2278. if (this._sock.rQwait("DesktopName", length, 4)) {
  2279. return false;
  2280. }
  2281. let name = this._sock.rQshiftStr(length);
  2282. name = decodeUTF8(name, true);
  2283. this._setDesktopName(name);
  2284. return true;
  2285. }
  2286. _handleLedEvent() {
  2287. if (this._sock.rQwait("LED Status", 1)) {
  2288. return false;
  2289. }
  2290. let data = this._sock.rQshift8();
  2291. // ScrollLock state can be retrieved with data & 1. This is currently not needed.
  2292. let numLock = data & 2 ? true : false;
  2293. let capsLock = data & 4 ? true : false;
  2294. this._remoteCapsLock = capsLock;
  2295. this._remoteNumLock = numLock;
  2296. return true;
  2297. }
  2298. _handleExtendedDesktopSize() {
  2299. if (this._sock.rQwait("ExtendedDesktopSize", 4)) {
  2300. return false;
  2301. }
  2302. const numberOfScreens = this._sock.rQpeek8();
  2303. let bytes = 4 + (numberOfScreens * 16);
  2304. if (this._sock.rQwait("ExtendedDesktopSize", bytes)) {
  2305. return false;
  2306. }
  2307. const firstUpdate = !this._supportsSetDesktopSize;
  2308. this._supportsSetDesktopSize = true;
  2309. this._sock.rQskipBytes(1); // number-of-screens
  2310. this._sock.rQskipBytes(3); // padding
  2311. for (let i = 0; i < numberOfScreens; i += 1) {
  2312. // Save the id and flags of the first screen
  2313. if (i === 0) {
  2314. this._screenID = this._sock.rQshift32(); // id
  2315. this._sock.rQskipBytes(2); // x-position
  2316. this._sock.rQskipBytes(2); // y-position
  2317. this._sock.rQskipBytes(2); // width
  2318. this._sock.rQskipBytes(2); // height
  2319. this._screenFlags = this._sock.rQshift32(); // flags
  2320. } else {
  2321. this._sock.rQskipBytes(16);
  2322. }
  2323. }
  2324. /*
  2325. * The x-position indicates the reason for the change:
  2326. *
  2327. * 0 - server resized on its own
  2328. * 1 - this client requested the resize
  2329. * 2 - another client requested the resize
  2330. */
  2331. // We need to handle errors when we requested the resize.
  2332. if (this._FBU.x === 1 && this._FBU.y !== 0) {
  2333. let msg = "";
  2334. // The y-position indicates the status code from the server
  2335. switch (this._FBU.y) {
  2336. case 1:
  2337. msg = "Resize is administratively prohibited";
  2338. break;
  2339. case 2:
  2340. msg = "Out of resources";
  2341. break;
  2342. case 3:
  2343. msg = "Invalid screen layout";
  2344. break;
  2345. default:
  2346. msg = "Unknown reason";
  2347. break;
  2348. }
  2349. Log.Warn("Server did not accept the resize request: "
  2350. + msg);
  2351. } else {
  2352. this._resize(this._FBU.width, this._FBU.height);
  2353. }
  2354. // Normally we only apply the current resize mode after a
  2355. // window resize event. However there is no such trigger on the
  2356. // initial connect. And we don't know if the server supports
  2357. // resizing until we've gotten here.
  2358. if (firstUpdate) {
  2359. this._requestRemoteResize();
  2360. }
  2361. return true;
  2362. }
  2363. _handleDataRect() {
  2364. let decoder = this._decoders[this._FBU.encoding];
  2365. if (!decoder) {
  2366. this._fail("Unsupported encoding (encoding: " +
  2367. this._FBU.encoding + ")");
  2368. return false;
  2369. }
  2370. try {
  2371. return decoder.decodeRect(this._FBU.x, this._FBU.y,
  2372. this._FBU.width, this._FBU.height,
  2373. this._sock, this._display,
  2374. this._fbDepth);
  2375. } catch (err) {
  2376. this._fail("Error decoding rect: " + err);
  2377. return false;
  2378. }
  2379. }
  2380. _updateContinuousUpdates() {
  2381. if (!this._enabledContinuousUpdates) { return; }
  2382. RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
  2383. this._fbWidth, this._fbHeight);
  2384. }
  2385. _resize(width, height) {
  2386. this._fbWidth = width;
  2387. this._fbHeight = height;
  2388. this._display.resize(this._fbWidth, this._fbHeight);
  2389. // Adjust the visible viewport based on the new dimensions
  2390. this._updateClip();
  2391. this._updateScale();
  2392. this._updateContinuousUpdates();
  2393. // Keep this size until browser client size changes
  2394. this._saveExpectedClientSize();
  2395. }
  2396. _xvpOp(ver, op) {
  2397. if (this._rfbXvpVer < ver) { return; }
  2398. Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
  2399. RFB.messages.xvpOp(this._sock, ver, op);
  2400. }
  2401. _updateCursor(rgba, hotx, hoty, w, h) {
  2402. this._cursorImage = {
  2403. rgbaPixels: rgba,
  2404. hotx: hotx, hoty: hoty, w: w, h: h,
  2405. };
  2406. this._refreshCursor();
  2407. }
  2408. _shouldShowDotCursor() {
  2409. // Called when this._cursorImage is updated
  2410. if (!this._showDotCursor) {
  2411. // User does not want to see the dot, so...
  2412. return false;
  2413. }
  2414. // The dot should not be shown if the cursor is already visible,
  2415. // i.e. contains at least one not-fully-transparent pixel.
  2416. // So iterate through all alpha bytes in rgba and stop at the
  2417. // first non-zero.
  2418. for (let i = 3; i < this._cursorImage.rgbaPixels.length; i += 4) {
  2419. if (this._cursorImage.rgbaPixels[i]) {
  2420. return false;
  2421. }
  2422. }
  2423. // At this point, we know that the cursor is fully transparent, and
  2424. // the user wants to see the dot instead of this.
  2425. return true;
  2426. }
  2427. _refreshCursor() {
  2428. if (this._rfbConnectionState !== "connecting" &&
  2429. this._rfbConnectionState !== "connected") {
  2430. return;
  2431. }
  2432. const image = this._shouldShowDotCursor() ? RFB.cursors.dot : this._cursorImage;
  2433. this._cursor.change(image.rgbaPixels,
  2434. image.hotx, image.hoty,
  2435. image.w, image.h
  2436. );
  2437. }
  2438. static genDES(password, challenge) {
  2439. const passwordChars = password.split('').map(c => c.charCodeAt(0));
  2440. const key = legacyCrypto.importKey(
  2441. "raw", passwordChars, { name: "DES-ECB" }, false, ["encrypt"]);
  2442. return legacyCrypto.encrypt({ name: "DES-ECB" }, key, challenge);
  2443. }
  2444. }
  2445. // Class Methods
  2446. RFB.messages = {
  2447. keyEvent(sock, keysym, down) {
  2448. sock.sQpush8(4); // msg-type
  2449. sock.sQpush8(down);
  2450. sock.sQpush16(0);
  2451. sock.sQpush32(keysym);
  2452. sock.flush();
  2453. },
  2454. QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
  2455. function getRFBkeycode(xtScanCode) {
  2456. const upperByte = (keycode >> 8);
  2457. const lowerByte = (keycode & 0x00ff);
  2458. if (upperByte === 0xe0 && lowerByte < 0x7f) {
  2459. return lowerByte | 0x80;
  2460. }
  2461. return xtScanCode;
  2462. }
  2463. sock.sQpush8(255); // msg-type
  2464. sock.sQpush8(0); // sub msg-type
  2465. sock.sQpush16(down);
  2466. sock.sQpush32(keysym);
  2467. const RFBkeycode = getRFBkeycode(keycode);
  2468. sock.sQpush32(RFBkeycode);
  2469. sock.flush();
  2470. },
  2471. pointerEvent(sock, x, y, mask) {
  2472. sock.sQpush8(5); // msg-type
  2473. sock.sQpush8(mask);
  2474. sock.sQpush16(x);
  2475. sock.sQpush16(y);
  2476. sock.flush();
  2477. },
  2478. // Used to build Notify and Request data.
  2479. _buildExtendedClipboardFlags(actions, formats) {
  2480. let data = new Uint8Array(4);
  2481. let formatFlag = 0x00000000;
  2482. let actionFlag = 0x00000000;
  2483. for (let i = 0; i < actions.length; i++) {
  2484. actionFlag |= actions[i];
  2485. }
  2486. for (let i = 0; i < formats.length; i++) {
  2487. formatFlag |= formats[i];
  2488. }
  2489. data[0] = actionFlag >> 24; // Actions
  2490. data[1] = 0x00; // Reserved
  2491. data[2] = 0x00; // Reserved
  2492. data[3] = formatFlag; // Formats
  2493. return data;
  2494. },
  2495. extendedClipboardProvide(sock, formats, inData) {
  2496. // Deflate incomming data and their sizes
  2497. let deflator = new Deflator();
  2498. let dataToDeflate = [];
  2499. for (let i = 0; i < formats.length; i++) {
  2500. // We only support the format Text at this time
  2501. if (formats[i] != extendedClipboardFormatText) {
  2502. throw new Error("Unsupported extended clipboard format for Provide message.");
  2503. }
  2504. // Change lone \r or \n into \r\n as defined in rfbproto
  2505. inData[i] = inData[i].replace(/\r\n|\r|\n/gm, "\r\n");
  2506. // Check if it already has \0
  2507. let text = encodeUTF8(inData[i] + "\0");
  2508. dataToDeflate.push( (text.length >> 24) & 0xFF,
  2509. (text.length >> 16) & 0xFF,
  2510. (text.length >> 8) & 0xFF,
  2511. (text.length & 0xFF));
  2512. for (let j = 0; j < text.length; j++) {
  2513. dataToDeflate.push(text.charCodeAt(j));
  2514. }
  2515. }
  2516. let deflatedData = deflator.deflate(new Uint8Array(dataToDeflate));
  2517. // Build data to send
  2518. let data = new Uint8Array(4 + deflatedData.length);
  2519. data.set(RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionProvide],
  2520. formats));
  2521. data.set(deflatedData, 4);
  2522. RFB.messages.clientCutText(sock, data, true);
  2523. },
  2524. extendedClipboardNotify(sock, formats) {
  2525. let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionNotify],
  2526. formats);
  2527. RFB.messages.clientCutText(sock, flags, true);
  2528. },
  2529. extendedClipboardRequest(sock, formats) {
  2530. let flags = RFB.messages._buildExtendedClipboardFlags([extendedClipboardActionRequest],
  2531. formats);
  2532. RFB.messages.clientCutText(sock, flags, true);
  2533. },
  2534. extendedClipboardCaps(sock, actions, formats) {
  2535. let formatKeys = Object.keys(formats);
  2536. let data = new Uint8Array(4 + (4 * formatKeys.length));
  2537. formatKeys.map(x => parseInt(x));
  2538. formatKeys.sort((a, b) => a - b);
  2539. data.set(RFB.messages._buildExtendedClipboardFlags(actions, []));
  2540. let loopOffset = 4;
  2541. for (let i = 0; i < formatKeys.length; i++) {
  2542. data[loopOffset] = formats[formatKeys[i]] >> 24;
  2543. data[loopOffset + 1] = formats[formatKeys[i]] >> 16;
  2544. data[loopOffset + 2] = formats[formatKeys[i]] >> 8;
  2545. data[loopOffset + 3] = formats[formatKeys[i]] >> 0;
  2546. loopOffset += 4;
  2547. data[3] |= (1 << formatKeys[i]); // Update our format flags
  2548. }
  2549. RFB.messages.clientCutText(sock, data, true);
  2550. },
  2551. clientCutText(sock, data, extended = false) {
  2552. sock.sQpush8(6); // msg-type
  2553. sock.sQpush8(0); // padding
  2554. sock.sQpush8(0); // padding
  2555. sock.sQpush8(0); // padding
  2556. let length;
  2557. if (extended) {
  2558. length = toUnsigned32bit(-data.length);
  2559. } else {
  2560. length = data.length;
  2561. }
  2562. sock.sQpush32(length);
  2563. sock.sQpushBytes(data);
  2564. sock.flush();
  2565. },
  2566. setDesktopSize(sock, width, height, id, flags) {
  2567. sock.sQpush8(251); // msg-type
  2568. sock.sQpush8(0); // padding
  2569. sock.sQpush16(width);
  2570. sock.sQpush16(height);
  2571. sock.sQpush8(1); // number-of-screens
  2572. sock.sQpush8(0); // padding
  2573. // screen array
  2574. sock.sQpush32(id);
  2575. sock.sQpush16(0); // x-position
  2576. sock.sQpush16(0); // y-position
  2577. sock.sQpush16(width);
  2578. sock.sQpush16(height);
  2579. sock.sQpush32(flags);
  2580. sock.flush();
  2581. },
  2582. clientFence(sock, flags, payload) {
  2583. sock.sQpush8(248); // msg-type
  2584. sock.sQpush8(0); // padding
  2585. sock.sQpush8(0); // padding
  2586. sock.sQpush8(0); // padding
  2587. sock.sQpush32(flags);
  2588. sock.sQpush8(payload.length);
  2589. sock.sQpushString(payload);
  2590. sock.flush();
  2591. },
  2592. enableContinuousUpdates(sock, enable, x, y, width, height) {
  2593. sock.sQpush8(150); // msg-type
  2594. sock.sQpush8(enable);
  2595. sock.sQpush16(x);
  2596. sock.sQpush16(y);
  2597. sock.sQpush16(width);
  2598. sock.sQpush16(height);
  2599. sock.flush();
  2600. },
  2601. pixelFormat(sock, depth, trueColor) {
  2602. let bpp;
  2603. if (depth > 16) {
  2604. bpp = 32;
  2605. } else if (depth > 8) {
  2606. bpp = 16;
  2607. } else {
  2608. bpp = 8;
  2609. }
  2610. const bits = Math.floor(depth/3);
  2611. sock.sQpush8(0); // msg-type
  2612. sock.sQpush8(0); // padding
  2613. sock.sQpush8(0); // padding
  2614. sock.sQpush8(0); // padding
  2615. sock.sQpush8(bpp);
  2616. sock.sQpush8(depth);
  2617. sock.sQpush8(0); // little-endian
  2618. sock.sQpush8(trueColor ? 1 : 0);
  2619. sock.sQpush16((1 << bits) - 1); // red-max
  2620. sock.sQpush16((1 << bits) - 1); // green-max
  2621. sock.sQpush16((1 << bits) - 1); // blue-max
  2622. sock.sQpush8(bits * 0); // red-shift
  2623. sock.sQpush8(bits * 1); // green-shift
  2624. sock.sQpush8(bits * 2); // blue-shift
  2625. sock.sQpush8(0); // padding
  2626. sock.sQpush8(0); // padding
  2627. sock.sQpush8(0); // padding
  2628. sock.flush();
  2629. },
  2630. clientEncodings(sock, encodings) {
  2631. sock.sQpush8(2); // msg-type
  2632. sock.sQpush8(0); // padding
  2633. sock.sQpush16(encodings.length);
  2634. for (let i = 0; i < encodings.length; i++) {
  2635. sock.sQpush32(encodings[i]);
  2636. }
  2637. sock.flush();
  2638. },
  2639. fbUpdateRequest(sock, incremental, x, y, w, h) {
  2640. if (typeof(x) === "undefined") { x = 0; }
  2641. if (typeof(y) === "undefined") { y = 0; }
  2642. sock.sQpush8(3); // msg-type
  2643. sock.sQpush8(incremental ? 1 : 0);
  2644. sock.sQpush16(x);
  2645. sock.sQpush16(y);
  2646. sock.sQpush16(w);
  2647. sock.sQpush16(h);
  2648. sock.flush();
  2649. },
  2650. xvpOp(sock, ver, op) {
  2651. sock.sQpush8(250); // msg-type
  2652. sock.sQpush8(0); // padding
  2653. sock.sQpush8(ver);
  2654. sock.sQpush8(op);
  2655. sock.flush();
  2656. }
  2657. };
  2658. RFB.cursors = {
  2659. none: {
  2660. rgbaPixels: new Uint8Array(),
  2661. w: 0, h: 0,
  2662. hotx: 0, hoty: 0,
  2663. },
  2664. dot: {
  2665. /* eslint-disable indent */
  2666. rgbaPixels: new Uint8Array([
  2667. 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
  2668. 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255,
  2669. 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255,
  2670. ]),
  2671. /* eslint-enable indent */
  2672. w: 3, h: 3,
  2673. hotx: 1, hoty: 1,
  2674. }
  2675. };