rfb.js 77 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2012 Joel Martin
  4. * Copyright (C) 2013 Samuel Mannehed for Cendio AB
  5. * Licensed under MPL 2.0 (see LICENSE.txt)
  6. *
  7. * See README.md for usage and integration instructions.
  8. *
  9. * TIGHT decoder portion:
  10. * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
  11. */
  12. /*jslint white: false, browser: true */
  13. /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
  14. var RFB;
  15. (function () {
  16. "use strict";
  17. RFB = function (defaults) {
  18. if (!defaults) {
  19. defaults = {};
  20. }
  21. this._rfb_host = '';
  22. this._rfb_port = 5900;
  23. this._rfb_password = '';
  24. this._rfb_path = '';
  25. this._rfb_state = 'disconnected';
  26. this._rfb_version = 0;
  27. this._rfb_max_version = 3.8;
  28. this._rfb_auth_scheme = '';
  29. this._rfb_tightvnc = false;
  30. this._rfb_xvp_ver = 0;
  31. // In preference order
  32. this._encodings = [
  33. ['COPYRECT', 0x01 ],
  34. ['TIGHT', 0x07 ],
  35. ['TIGHT_PNG', -260 ],
  36. ['HEXTILE', 0x05 ],
  37. ['RRE', 0x02 ],
  38. ['RAW', 0x00 ],
  39. ['DesktopSize', -223 ],
  40. ['Cursor', -239 ],
  41. // Psuedo-encoding settings
  42. //['JPEG_quality_lo', -32 ],
  43. ['JPEG_quality_med', -26 ],
  44. //['JPEG_quality_hi', -23 ],
  45. //['compress_lo', -255 ],
  46. ['compress_hi', -247 ],
  47. ['last_rect', -224 ],
  48. ['xvp', -309 ],
  49. ['ext_desktop_size', -308 ]
  50. ];
  51. this._encHandlers = {};
  52. this._encNames = {};
  53. this._encStats = {};
  54. this._sock = null; // Websock object
  55. this._display = null; // Display object
  56. this._keyboard = null; // Keyboard input handler object
  57. this._mouse = null; // Mouse input handler object
  58. this._sendTimer = null; // Send Queue check timer
  59. this._disconnTimer = null; // disconnection timer
  60. this._msgTimer = null; // queued handle_msg timer
  61. // Frame buffer update state
  62. this._FBU = {
  63. rects: 0,
  64. subrects: 0, // RRE
  65. lines: 0, // RAW
  66. tiles: 0, // HEXTILE
  67. bytes: 0,
  68. x: 0,
  69. y: 0,
  70. width: 0,
  71. height: 0,
  72. encoding: 0,
  73. subencoding: -1,
  74. background: null,
  75. zlib: [] // TIGHT zlib streams
  76. };
  77. this._fb_Bpp = 4;
  78. this._fb_depth = 3;
  79. this._fb_width = 0;
  80. this._fb_height = 0;
  81. this._fb_name = "";
  82. this._rre_chunk_sz = 100;
  83. this._timing = {
  84. last_fbu: 0,
  85. fbu_total: 0,
  86. fbu_total_cnt: 0,
  87. full_fbu_total: 0,
  88. full_fbu_cnt: 0,
  89. fbu_rt_start: 0,
  90. fbu_rt_total: 0,
  91. fbu_rt_cnt: 0,
  92. pixels: 0
  93. };
  94. this._supportsSetDesktopSize = false;
  95. this._screen_id = 0;
  96. this._screen_flags = 0;
  97. // Mouse state
  98. this._mouse_buttonMask = 0;
  99. this._mouse_arr = [];
  100. this._viewportDragging = false;
  101. this._viewportDragPos = {};
  102. // set the default value on user-facing properties
  103. Util.set_defaults(this, defaults, {
  104. 'target': 'null', // VNC display rendering Canvas object
  105. 'focusContainer': document, // DOM element that captures keyboard input
  106. 'encrypt': false, // Use TLS/SSL/wss encryption
  107. 'true_color': true, // Request true color pixel data
  108. 'local_cursor': false, // Request locally rendered cursor
  109. 'shared': true, // Request shared mode
  110. 'view_only': false, // Disable client mouse/keyboard
  111. 'xvp_password_sep': '@', // Separator for XVP password fields
  112. 'disconnectTimeout': 3, // Time (s) to wait for disconnection
  113. 'wsProtocols': ['binary', 'base64'], // Protocols to use in the WebSocket connection
  114. 'repeaterID': '', // [UltraVNC] RepeaterID to connect to
  115. 'viewportDrag': false, // Move the viewport on mouse drags
  116. // Callback functions
  117. 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate, statusMsg): state update/change
  118. 'onPasswordRequired': function () { }, // onPasswordRequired(rfb): VNC password is required
  119. 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
  120. 'onBell': function () { }, // onBell(rfb): RFB Bell message received
  121. 'onFBUReceive': function () { }, // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
  122. 'onFBUComplete': function () { }, // onFBUComplete(rfb, fbu): RFB FBU received and processed
  123. 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
  124. 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
  125. 'onXvpInit': function () { }, // onXvpInit(version): XVP extensions active for this connection
  126. });
  127. // main setup
  128. Util.Debug(">> RFB.constructor");
  129. // populate encHandlers with bound versions
  130. Object.keys(RFB.encodingHandlers).forEach(function (encName) {
  131. this._encHandlers[encName] = RFB.encodingHandlers[encName].bind(this);
  132. }.bind(this));
  133. // Create lookup tables based on encoding number
  134. for (var i = 0; i < this._encodings.length; i++) {
  135. this._encHandlers[this._encodings[i][1]] = this._encHandlers[this._encodings[i][0]];
  136. this._encNames[this._encodings[i][1]] = this._encodings[i][0];
  137. this._encStats[this._encodings[i][1]] = [0, 0];
  138. }
  139. try {
  140. this._display = new Display({target: this._target});
  141. } catch (exc) {
  142. Util.Error("Display exception: " + exc);
  143. this._updateState('fatal', "No working Display");
  144. }
  145. this._keyboard = new Keyboard({target: this._focusContainer,
  146. onKeyPress: this._handleKeyPress.bind(this)});
  147. this._mouse = new Mouse({target: this._target,
  148. onMouseButton: this._handleMouseButton.bind(this),
  149. onMouseMove: this._handleMouseMove.bind(this),
  150. notify: this._keyboard.sync.bind(this._keyboard)});
  151. this._sock = new Websock();
  152. this._sock.on('message', this._handle_message.bind(this));
  153. this._sock.on('open', function () {
  154. if (this._rfb_state === 'connect') {
  155. this._updateState('ProtocolVersion', "Starting VNC handshake");
  156. } else {
  157. this._fail("Got unexpected WebSocket connection");
  158. }
  159. }.bind(this));
  160. this._sock.on('close', function (e) {
  161. Util.Warn("WebSocket on-close event");
  162. var msg = "";
  163. if (e.code) {
  164. msg = " (code: " + e.code;
  165. if (e.reason) {
  166. msg += ", reason: " + e.reason;
  167. }
  168. msg += ")";
  169. }
  170. if (this._rfb_state === 'disconnect') {
  171. this._updateState('disconnected', 'VNC disconnected' + msg);
  172. } else if (this._rfb_state === 'ProtocolVersion') {
  173. this._fail('Failed to connect to server' + msg);
  174. } else if (this._rfb_state in {'failed': 1, 'disconnected': 1}) {
  175. Util.Error("Received onclose while disconnected" + msg);
  176. } else {
  177. this._fail("Server disconnected" + msg);
  178. }
  179. this._sock.off('close');
  180. }.bind(this));
  181. this._sock.on('error', function (e) {
  182. Util.Warn("WebSocket on-error event");
  183. });
  184. this._init_vars();
  185. var rmode = this._display.get_render_mode();
  186. if (Websock_native) {
  187. Util.Info("Using native WebSockets");
  188. this._updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
  189. } else {
  190. Util.Warn("Using web-socket-js bridge. Flash version: " + Util.Flash.version);
  191. if (!Util.Flash || Util.Flash.version < 9) {
  192. this._updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash</a> is required");
  193. } else if (document.location.href.substr(0, 7) === 'file://') {
  194. this._updateState('fatal', "'file://' URL is incompatible with Adobe Flash");
  195. } else {
  196. this._updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
  197. }
  198. }
  199. Util.Debug("<< RFB.constructor");
  200. };
  201. RFB.prototype = {
  202. // Public methods
  203. connect: function (host, port, password, path) {
  204. this._rfb_host = host;
  205. this._rfb_port = port;
  206. this._rfb_password = (password !== undefined) ? password : "";
  207. this._rfb_path = (path !== undefined) ? path : "";
  208. if (!this._rfb_host || !this._rfb_port) {
  209. return this._fail("Must set host and port");
  210. }
  211. this._updateState('connect');
  212. },
  213. disconnect: function () {
  214. this._updateState('disconnect', 'Disconnecting');
  215. this._sock.off('error');
  216. this._sock.off('message');
  217. this._sock.off('open');
  218. },
  219. sendPassword: function (passwd) {
  220. this._rfb_password = passwd;
  221. this._rfb_state = 'Authentication';
  222. setTimeout(this._init_msg.bind(this), 1);
  223. },
  224. sendCtrlAltDel: function () {
  225. if (this._rfb_state !== 'normal' || this._view_only) { return false; }
  226. Util.Info("Sending Ctrl-Alt-Del");
  227. var arr = [];
  228. arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 1));
  229. arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 1));
  230. arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 1));
  231. arr = arr.concat(RFB.messages.keyEvent(XK_Delete, 0));
  232. arr = arr.concat(RFB.messages.keyEvent(XK_Alt_L, 0));
  233. arr = arr.concat(RFB.messages.keyEvent(XK_Control_L, 0));
  234. this._sock.send(arr);
  235. },
  236. xvpOp: function (ver, op) {
  237. if (this._rfb_xvp_ver < ver) { return false; }
  238. Util.Info("Sending XVP operation " + op + " (version " + ver + ")");
  239. this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
  240. return true;
  241. },
  242. xvpShutdown: function () {
  243. return this.xvpOp(1, 2);
  244. },
  245. xvpReboot: function () {
  246. return this.xvpOp(1, 3);
  247. },
  248. xvpReset: function () {
  249. return this.xvpOp(1, 4);
  250. },
  251. // Send a key press. If 'down' is not specified then send a down key
  252. // followed by an up key.
  253. sendKey: function (code, down) {
  254. if (this._rfb_state !== "normal" || this._view_only) { return false; }
  255. var arr = [];
  256. if (typeof down !== 'undefined') {
  257. Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
  258. arr = arr.concat(RFB.messages.keyEvent(code, down ? 1 : 0));
  259. } else {
  260. Util.Info("Sending key code (down + up): " + code);
  261. arr = arr.concat(RFB.messages.keyEvent(code, 1));
  262. arr = arr.concat(RFB.messages.keyEvent(code, 0));
  263. }
  264. this._sock.send(arr);
  265. },
  266. clipboardPasteFrom: function (text) {
  267. if (this._rfb_state !== 'normal') { return; }
  268. this._sock.send(RFB.messages.clientCutText(text));
  269. },
  270. setDesktopSize: function (width, height) {
  271. if (this._rfb_state !== "normal") { return; }
  272. if (this._supportsSetDesktopSize) {
  273. var arr = [251]; // msg-type
  274. arr.push8(0); // padding
  275. arr.push16(width); // width
  276. arr.push16(height); // height
  277. arr.push8(1); // number-of-screens
  278. arr.push8(0); // padding
  279. // screen array
  280. arr.push32(this._screen_id); // id
  281. arr.push16(0); // x-position
  282. arr.push16(0); // y-position
  283. arr.push16(width); // width
  284. arr.push16(height); // height
  285. arr.push32(this._screen_flags); // flags
  286. this._sock.send(arr);
  287. }
  288. },
  289. // Private methods
  290. _connect: function () {
  291. Util.Debug(">> RFB.connect");
  292. var uri;
  293. if (typeof UsingSocketIO !== 'undefined') {
  294. uri = 'http';
  295. } else {
  296. uri = this._encrypt ? 'wss' : 'ws';
  297. }
  298. uri += '://' + this._rfb_host + ':' + this._rfb_port + '/' + this._rfb_path;
  299. Util.Info("connecting to " + uri);
  300. this._sock.open(uri, this._wsProtocols);
  301. Util.Debug("<< RFB.connect");
  302. },
  303. _init_vars: function () {
  304. // reset state
  305. this._sock.init();
  306. this._FBU.rects = 0;
  307. this._FBU.subrects = 0; // RRE and HEXTILE
  308. this._FBU.lines = 0; // RAW
  309. this._FBU.tiles = 0; // HEXTILE
  310. this._FBU.zlibs = []; // TIGHT zlib encoders
  311. this._mouse_buttonMask = 0;
  312. this._mouse_arr = [];
  313. this._rfb_tightvnc = false;
  314. // Clear the per connection encoding stats
  315. var i;
  316. for (i = 0; i < this._encodings.length; i++) {
  317. this._encStats[this._encodings[i][1]][0] = 0;
  318. }
  319. for (i = 0; i < 4; i++) {
  320. this._FBU.zlibs[i] = new TINF();
  321. this._FBU.zlibs[i].init();
  322. }
  323. },
  324. _print_stats: function () {
  325. Util.Info("Encoding stats for this connection:");
  326. var i, s;
  327. for (i = 0; i < this._encodings.length; i++) {
  328. s = this._encStats[this._encodings[i][1]];
  329. if (s[0] + s[1] > 0) {
  330. Util.Info(" " + this._encodings[i][0] + ": " + s[0] + " rects");
  331. }
  332. }
  333. Util.Info("Encoding stats since page load:");
  334. for (i = 0; i < this._encodings.length; i++) {
  335. s = this._encStats[this._encodings[i][1]];
  336. Util.Info(" " + this._encodings[i][0] + ": " + s[1] + " rects");
  337. }
  338. },
  339. /*
  340. * Page states:
  341. * loaded - page load, equivalent to disconnected
  342. * disconnected - idle state
  343. * connect - starting to connect (to ProtocolVersion)
  344. * normal - connected
  345. * disconnect - starting to disconnect
  346. * failed - abnormal disconnect
  347. * fatal - failed to load page, or fatal error
  348. *
  349. * RFB protocol initialization states:
  350. * ProtocolVersion
  351. * Security
  352. * Authentication
  353. * password - waiting for password, not part of RFB
  354. * SecurityResult
  355. * ClientInitialization - not triggered by server message
  356. * ServerInitialization (to normal)
  357. */
  358. _updateState: function (state, statusMsg) {
  359. var oldstate = this._rfb_state;
  360. if (state === oldstate) {
  361. // Already here, ignore
  362. Util.Debug("Already in state '" + state + "', ignoring");
  363. }
  364. /*
  365. * These are disconnected states. A previous connect may
  366. * asynchronously cause a connection so make sure we are closed.
  367. */
  368. if (state in {'disconnected': 1, 'loaded': 1, 'connect': 1,
  369. 'disconnect': 1, 'failed': 1, 'fatal': 1}) {
  370. if (this._sendTimer) {
  371. clearInterval(this._sendTimer);
  372. this._sendTimer = null;
  373. }
  374. if (this._msgTimer) {
  375. clearInterval(this._msgTimer);
  376. this._msgTimer = null;
  377. }
  378. if (this._display && this._display.get_context()) {
  379. this._keyboard.ungrab();
  380. this._mouse.ungrab();
  381. if (state !== 'connect' && state !== 'loaded') {
  382. this._display.defaultCursor();
  383. }
  384. if (Util.get_logging() !== 'debug' || state === 'loaded') {
  385. // Show noVNC logo on load and when disconnected, unless in
  386. // debug mode
  387. this._display.clear();
  388. }
  389. }
  390. this._sock.close();
  391. }
  392. if (oldstate === 'fatal') {
  393. Util.Error('Fatal error, cannot continue');
  394. }
  395. var cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
  396. var fullmsg = "New state '" + state + "', was '" + oldstate + "'." + cmsg;
  397. if (state === 'failed' || state === 'fatal') {
  398. Util.Error(cmsg);
  399. } else {
  400. Util.Warn(cmsg);
  401. }
  402. if (oldstate === 'failed' && state === 'disconnected') {
  403. // do disconnect action, but stay in failed state
  404. this._rfb_state = 'failed';
  405. } else {
  406. this._rfb_state = state;
  407. }
  408. if (this._disconnTimer && this._rfb_state !== 'disconnect') {
  409. Util.Debug("Clearing disconnect timer");
  410. clearTimeout(this._disconnTimer);
  411. this._disconnTimer = null;
  412. }
  413. switch (state) {
  414. case 'normal':
  415. if (oldstate === 'disconnected' || oldstate === 'failed') {
  416. Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
  417. }
  418. break;
  419. case 'connect':
  420. this._init_vars();
  421. this._connect();
  422. // WebSocket.onopen transitions to 'ProtocolVersion'
  423. break;
  424. case 'disconnect':
  425. this._disconnTimer = setTimeout(function () {
  426. this._fail("Disconnect timeout");
  427. }.bind(this), this._disconnectTimeout * 1000);
  428. this._print_stats();
  429. // WebSocket.onclose transitions to 'disconnected'
  430. break;
  431. case 'failed':
  432. if (oldstate === 'disconnected') {
  433. Util.Error("Invalid transition from 'disconnected' to 'failed'");
  434. } else if (oldstate === 'normal') {
  435. Util.Error("Error while connected.");
  436. } else if (oldstate === 'init') {
  437. Util.Error("Error while initializing.");
  438. }
  439. // Make sure we transition to disconnected
  440. setTimeout(function () {
  441. this._updateState('disconnected');
  442. }.bind(this), 50);
  443. break;
  444. default:
  445. // No state change action to take
  446. }
  447. if (oldstate === 'failed' && state === 'disconnected') {
  448. this._onUpdateState(this, state, oldstate);
  449. } else {
  450. this._onUpdateState(this, state, oldstate, statusMsg);
  451. }
  452. },
  453. _fail: function (msg) {
  454. this._updateState('failed', msg);
  455. return false;
  456. },
  457. _handle_message: function () {
  458. if (this._sock.rQlen() === 0) {
  459. Util.Warn("handle_message called on an empty receive queue");
  460. return;
  461. }
  462. switch (this._rfb_state) {
  463. case 'disconnected':
  464. case 'failed':
  465. Util.Error("Got data while disconnected");
  466. break;
  467. case 'normal':
  468. if (this._normal_msg() && this._sock.rQlen() > 0) {
  469. // true means we can continue processing
  470. // Give other events a chance to run
  471. if (this._msgTimer === null) {
  472. Util.Debug("More data to process, creating timer");
  473. this._msgTimer = setTimeout(function () {
  474. this._msgTimer = null;
  475. this._handle_message();
  476. }.bind(this), 10);
  477. } else {
  478. Util.Debug("More data to process, existing timer");
  479. }
  480. }
  481. break;
  482. default:
  483. this._init_msg();
  484. break;
  485. }
  486. },
  487. _checkEvents: function () {
  488. if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
  489. this._sock.send(this._mouse_arr);
  490. this._mouse_arr = [];
  491. }
  492. },
  493. _handleKeyPress: function (keysym, down) {
  494. if (this._view_only) { return; } // View only, skip keyboard, events
  495. this._sock.send(RFB.messages.keyEvent(keysym, down));
  496. },
  497. _handleMouseButton: function (x, y, down, bmask) {
  498. if (down) {
  499. this._mouse_buttonMask |= bmask;
  500. } else {
  501. this._mouse_buttonMask ^= bmask;
  502. }
  503. if (this._viewportDrag) {
  504. if (down && !this._viewportDragging) {
  505. this._viewportDragging = true;
  506. this._viewportDragPos = {'x': x, 'y': y};
  507. // Skip sending mouse events
  508. return;
  509. } else {
  510. this._viewportDragging = false;
  511. }
  512. }
  513. if (this._view_only) { return; } // View only, skip mouse events
  514. this._mouse_arr = this._mouse_arr.concat(
  515. RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
  516. this._sock.send(this._mouse_arr);
  517. this._mouse_arr = [];
  518. },
  519. _handleMouseMove: function (x, y) {
  520. if (this._viewportDragging) {
  521. var deltaX = this._viewportDragPos.x - x;
  522. var deltaY = this._viewportDragPos.y - y;
  523. this._viewportDragPos = {'x': x, 'y': y};
  524. this._display.viewportChangePos(deltaX, deltaY);
  525. // Skip sending mouse events
  526. return;
  527. }
  528. if (this._view_only) { return; } // View only, skip mouse events
  529. this._mouse_arr = this._mouse_arr.concat(
  530. RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
  531. this._checkEvents();
  532. },
  533. // Message Handlers
  534. _negotiate_protocol_version: function () {
  535. if (this._sock.rQlen() < 12) {
  536. return this._fail("Incomplete protocol version");
  537. }
  538. var sversion = this._sock.rQshiftStr(12).substr(4, 7);
  539. Util.Info("Server ProtocolVersion: " + sversion);
  540. var is_repeater = 0;
  541. switch (sversion) {
  542. case "000.000": // UltraVNC repeater
  543. is_repeater = 1;
  544. break;
  545. case "003.003":
  546. case "003.006": // UltraVNC
  547. case "003.889": // Apple Remote Desktop
  548. this._rfb_version = 3.3;
  549. break;
  550. case "003.007":
  551. this._rfb_version = 3.7;
  552. break;
  553. case "003.008":
  554. case "004.000": // Intel AMT KVM
  555. case "004.001": // RealVNC 4.6
  556. this._rfb_version = 3.8;
  557. break;
  558. default:
  559. return this._fail("Invalid server version " + sversion);
  560. }
  561. if (is_repeater) {
  562. var repeaterID = this._repeaterID;
  563. while (repeaterID.length < 250) {
  564. repeaterID += "\0";
  565. }
  566. this._sock.send_string(repeaterID);
  567. return true;
  568. }
  569. if (this._rfb_version > this._rfb_max_version) {
  570. this._rfb_version = this._rfb_max_version;
  571. }
  572. // Send updates either at a rate of 1 update per 50ms, or
  573. // whatever slower rate the network can handle
  574. this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
  575. var cversion = "00" + parseInt(this._rfb_version, 10) +
  576. ".00" + ((this._rfb_version * 10) % 10);
  577. this._sock.send_string("RFB " + cversion + "\n");
  578. this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
  579. },
  580. _negotiate_security: function () {
  581. if (this._rfb_version >= 3.7) {
  582. // Server sends supported list, client decides
  583. var num_types = this._sock.rQshift8();
  584. if (this._sock.rQwait("security type", num_types, 1)) { return false; }
  585. if (num_types === 0) {
  586. var strlen = this._sock.rQshift32();
  587. var reason = this._sock.rQshiftStr(strlen);
  588. return this._fail("Security failure: " + reason);
  589. }
  590. this._rfb_auth_scheme = 0;
  591. var types = this._sock.rQshiftBytes(num_types);
  592. Util.Debug("Server security types: " + types);
  593. for (var i = 0; i < types.length; i++) {
  594. if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
  595. this._rfb_auth_scheme = types[i];
  596. }
  597. }
  598. if (this._rfb_auth_scheme === 0) {
  599. return this._fail("Unsupported security types: " + types);
  600. }
  601. this._sock.send([this._rfb_auth_scheme]);
  602. } else {
  603. // Server decides
  604. if (this._sock.rQwait("security scheme", 4)) { return false; }
  605. this._rfb_auth_scheme = this._sock.rQshift32();
  606. }
  607. this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
  608. return this._init_msg(); // jump to authentication
  609. },
  610. // authentication
  611. _negotiate_xvp_auth: function () {
  612. var xvp_sep = this._xvp_password_sep;
  613. var xvp_auth = this._rfb_password.split(xvp_sep);
  614. if (xvp_auth.length < 3) {
  615. this._updateState('password', 'XVP credentials required (user' + xvp_sep +
  616. 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
  617. this._onPasswordRequired(this);
  618. return false;
  619. }
  620. var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
  621. String.fromCharCode(xvp_auth[1].length) +
  622. xvp_auth[0] +
  623. xvp_auth[1];
  624. this._sock.send_string(xvp_auth_str);
  625. this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
  626. this._rfb_auth_scheme = 2;
  627. return this._negotiate_authentication();
  628. },
  629. _negotiate_std_vnc_auth: function () {
  630. if (this._rfb_password.length === 0) {
  631. // Notify via both callbacks since it's kind of
  632. // an RFB state change and a UI interface issue
  633. this._updateState('password', "Password Required");
  634. this._onPasswordRequired(this);
  635. }
  636. if (this._sock.rQwait("auth challenge", 16)) { return false; }
  637. var challenge = this._sock.rQshiftBytes(16);
  638. var response = RFB.genDES(this._rfb_password, challenge);
  639. this._sock.send(response);
  640. this._updateState("SecurityResult");
  641. return true;
  642. },
  643. _negotiate_tight_tunnels: function (numTunnels) {
  644. var clientSupportedTunnelTypes = {
  645. 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
  646. };
  647. var serverSupportedTunnelTypes = {};
  648. // receive tunnel capabilities
  649. for (var i = 0; i < numTunnels; i++) {
  650. var cap_code = this._sock.rQshift32();
  651. var cap_vendor = this._sock.rQshiftStr(4);
  652. var cap_signature = this._sock.rQshiftStr(8);
  653. serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
  654. }
  655. // choose the notunnel type
  656. if (serverSupportedTunnelTypes[0]) {
  657. if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
  658. serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
  659. return this._fail("Client's tunnel type had the incorrect vendor or signature");
  660. }
  661. this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
  662. return false; // wait until we receive the sub auth count to continue
  663. } else {
  664. return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
  665. }
  666. },
  667. _negotiate_tight_auth: function () {
  668. if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
  669. if (this._sock.rQwait("num tunnels", 4)) { return false; }
  670. var numTunnels = this._sock.rQshift32();
  671. if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
  672. this._rfb_tightvnc = true;
  673. if (numTunnels > 0) {
  674. this._negotiate_tight_tunnels(numTunnels);
  675. return false; // wait until we receive the sub auth to continue
  676. }
  677. }
  678. // second pass, do the sub-auth negotiation
  679. if (this._sock.rQwait("sub auth count", 4)) { return false; }
  680. var subAuthCount = this._sock.rQshift32();
  681. if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
  682. var clientSupportedTypes = {
  683. 'STDVNOAUTH__': 1,
  684. 'STDVVNCAUTH_': 2
  685. };
  686. var serverSupportedTypes = [];
  687. for (var i = 0; i < subAuthCount; i++) {
  688. var capNum = this._sock.rQshift32();
  689. var capabilities = this._sock.rQshiftStr(12);
  690. serverSupportedTypes.push(capabilities);
  691. }
  692. for (var authType in clientSupportedTypes) {
  693. if (serverSupportedTypes.indexOf(authType) != -1) {
  694. this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
  695. switch (authType) {
  696. case 'STDVNOAUTH__': // no auth
  697. this._updateState('SecurityResult');
  698. return true;
  699. case 'STDVVNCAUTH_': // VNC auth
  700. this._rfb_auth_scheme = 2;
  701. return this._init_msg();
  702. default:
  703. return this._fail("Unsupported tiny auth scheme: " + authType);
  704. }
  705. }
  706. }
  707. this._fail("No supported sub-auth types!");
  708. },
  709. _negotiate_authentication: function () {
  710. switch (this._rfb_auth_scheme) {
  711. case 0: // connection failed
  712. if (this._sock.rQwait("auth reason", 4)) { return false; }
  713. var strlen = this._sock.rQshift32();
  714. var reason = this._sock.rQshiftStr(strlen);
  715. return this._fail("Auth failure: " + reason);
  716. case 1: // no auth
  717. if (this._rfb_version >= 3.8) {
  718. this._updateState('SecurityResult');
  719. return true;
  720. }
  721. this._updateState('ClientInitialisation', "No auth required");
  722. return this._init_msg();
  723. case 22: // XVP auth
  724. return this._negotiate_xvp_auth();
  725. case 2: // VNC authentication
  726. return this._negotiate_std_vnc_auth();
  727. case 16: // TightVNC Security Type
  728. return this._negotiate_tight_auth();
  729. default:
  730. return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
  731. }
  732. },
  733. _handle_security_result: function () {
  734. if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
  735. switch (this._sock.rQshift32()) {
  736. case 0: // OK
  737. this._updateState('ClientInitialisation', 'Authentication OK');
  738. return this._init_msg();
  739. case 1: // failed
  740. if (this._rfb_version >= 3.8) {
  741. var length = this._sock.rQshift32();
  742. if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
  743. var reason = this._sock.rQshiftStr(length);
  744. return this._fail(reason);
  745. } else {
  746. return this._fail("Authentication failure");
  747. }
  748. return false;
  749. case 2:
  750. return this._fail("Too many auth attempts");
  751. }
  752. },
  753. _negotiate_server_init: function () {
  754. if (this._sock.rQwait("server initialization", 24)) { return false; }
  755. /* Screen size */
  756. this._fb_width = this._sock.rQshift16();
  757. this._fb_height = this._sock.rQshift16();
  758. /* PIXEL_FORMAT */
  759. var bpp = this._sock.rQshift8();
  760. var depth = this._sock.rQshift8();
  761. var big_endian = this._sock.rQshift8();
  762. var true_color = this._sock.rQshift8();
  763. var red_max = this._sock.rQshift16();
  764. var green_max = this._sock.rQshift16();
  765. var blue_max = this._sock.rQshift16();
  766. var red_shift = this._sock.rQshift8();
  767. var green_shift = this._sock.rQshift8();
  768. var blue_shift = this._sock.rQshift8();
  769. this._sock.rQskipBytes(3); // padding
  770. // NB(directxman12): we don't want to call any callbacks or print messages until
  771. // *after* we're past the point where we could backtrack
  772. /* Connection name/title */
  773. var name_length = this._sock.rQshift32();
  774. if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
  775. this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
  776. if (this._rfb_tightvnc) {
  777. if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
  778. // In TightVNC mode, ServerInit message is extended
  779. var numServerMessages = this._sock.rQshift16();
  780. var numClientMessages = this._sock.rQshift16();
  781. var numEncodings = this._sock.rQshift16();
  782. this._sock.rQskipBytes(2); // padding
  783. var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
  784. if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
  785. var i;
  786. for (i = 0; i < numServerMessages; i++) {
  787. var srvMsg = this._sock.rQshiftStr(16);
  788. }
  789. for (i = 0; i < numClientMessages; i++) {
  790. var clientMsg = this._sock.rQshiftStr(16);
  791. }
  792. for (i = 0; i < numEncodings; i++) {
  793. var encoding = this._sock.rQshiftStr(16);
  794. }
  795. }
  796. // NB(directxman12): these are down here so that we don't run them multiple times
  797. // if we backtrack
  798. Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
  799. ", bpp: " + bpp + ", depth: " + depth +
  800. ", big_endian: " + big_endian +
  801. ", true_color: " + true_color +
  802. ", red_max: " + red_max +
  803. ", green_max: " + green_max +
  804. ", blue_max: " + blue_max +
  805. ", red_shift: " + red_shift +
  806. ", green_shift: " + green_shift +
  807. ", blue_shift: " + blue_shift);
  808. if (big_endian !== 0) {
  809. Util.Warn("Server native endian is not little endian");
  810. }
  811. if (red_shift !== 16) {
  812. Util.Warn("Server native red-shift is not 16");
  813. }
  814. if (blue_shift !== 0) {
  815. Util.Warn("Server native blue-shift is not 0");
  816. }
  817. // we're past the point where we could backtrack, so it's safe to call this
  818. this._onDesktopName(this, this._fb_name);
  819. if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
  820. Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
  821. this._true_color = false;
  822. }
  823. this._display.set_true_color(this._true_color);
  824. this._display.resize(this._fb_width, this._fb_height);
  825. this._onFBResize(this, this._fb_width, this._fb_height);
  826. this._keyboard.grab();
  827. this._mouse.grab();
  828. if (this._true_color) {
  829. this._fb_Bpp = 4;
  830. this._fb_depth = 3;
  831. } else {
  832. this._fb_Bpp = 1;
  833. this._fb_depth = 1;
  834. }
  835. var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
  836. response = response.concat(
  837. RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
  838. response = response.concat(
  839. RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
  840. this._fb_width, this._fb_height));
  841. this._timing.fbu_rt_start = (new Date()).getTime();
  842. this._timing.pixels = 0;
  843. this._sock.send(response);
  844. this._checkEvents();
  845. if (this._encrypt) {
  846. this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
  847. } else {
  848. this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
  849. }
  850. },
  851. _init_msg: function () {
  852. switch (this._rfb_state) {
  853. case 'ProtocolVersion':
  854. return this._negotiate_protocol_version();
  855. case 'Security':
  856. return this._negotiate_security();
  857. case 'Authentication':
  858. return this._negotiate_authentication();
  859. case 'SecurityResult':
  860. return this._handle_security_result();
  861. case 'ClientInitialisation':
  862. this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
  863. this._updateState('ServerInitialisation', "Authentication OK");
  864. return true;
  865. case 'ServerInitialisation':
  866. return this._negotiate_server_init();
  867. }
  868. },
  869. _handle_set_colour_map_msg: function () {
  870. Util.Debug("SetColorMapEntries");
  871. this._sock.rQskip8(); // Padding
  872. var first_colour = this._sock.rQshift16();
  873. var num_colours = this._sock.rQshift16();
  874. if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
  875. for (var c = 0; c < num_colours; c++) {
  876. var red = parseInt(this._sock.rQshift16() / 256, 10);
  877. var green = parseInt(this._sock.rQshift16() / 256, 10);
  878. var blue = parseInt(this._sock.rQshift16() / 256, 10);
  879. this._display.set_colourMap([blue, green, red], first_colour + c);
  880. }
  881. Util.Debug("colourMap: " + this._display.get_colourMap());
  882. Util.Info("Registered " + num_colours + " colourMap entries");
  883. return true;
  884. },
  885. _handle_server_cut_text: function () {
  886. Util.Debug("ServerCutText");
  887. if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
  888. this._sock.rQskipBytes(3); // Padding
  889. var length = this._sock.rQshift32();
  890. if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
  891. var text = this._sock.rQshiftStr(length);
  892. this._onClipboard(this, text);
  893. return true;
  894. },
  895. _handle_xvp_msg: function () {
  896. if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
  897. this._sock.rQskip8(); // Padding
  898. var xvp_ver = this._sock.rQshift8();
  899. var xvp_msg = this._sock.rQshift8();
  900. switch (xvp_msg) {
  901. case 0: // XVP_FAIL
  902. this._updateState(this._rfb_state, "Operation Failed");
  903. break;
  904. case 1: // XVP_INIT
  905. this._rfb_xvp_ver = xvp_ver;
  906. Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
  907. this._onXvpInit(this._rfb_xvp_ver);
  908. break;
  909. default:
  910. this._fail("Disconnected: illegal server XVP message " + xvp_msg);
  911. break;
  912. }
  913. return true;
  914. },
  915. _normal_msg: function () {
  916. var msg_type;
  917. if (this._FBU.rects > 0) {
  918. msg_type = 0;
  919. } else {
  920. msg_type = this._sock.rQshift8();
  921. }
  922. switch (msg_type) {
  923. case 0: // FramebufferUpdate
  924. var ret = this._framebufferUpdate();
  925. if (ret) {
  926. this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
  927. this._fb_width, this._fb_height));
  928. }
  929. return ret;
  930. case 1: // SetColorMapEntries
  931. return this._handle_set_colour_map_msg();
  932. case 2: // Bell
  933. Util.Debug("Bell");
  934. this._onBell(this);
  935. return true;
  936. case 3: // ServerCutText
  937. return this._handle_server_cut_text();
  938. case 250: // XVP
  939. return this._handle_xvp_msg();
  940. default:
  941. this._fail("Disconnected: illegal server message type " + msg_type);
  942. Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
  943. return true;
  944. }
  945. },
  946. _framebufferUpdate: function () {
  947. var ret = true;
  948. var now;
  949. if (this._FBU.rects === 0) {
  950. if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
  951. this._sock.rQskip8(); // Padding
  952. this._FBU.rects = this._sock.rQshift16();
  953. this._FBU.bytes = 0;
  954. this._timing.cur_fbu = 0;
  955. if (this._timing.fbu_rt_start > 0) {
  956. now = (new Date()).getTime();
  957. Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
  958. }
  959. }
  960. while (this._FBU.rects > 0) {
  961. if (this._rfb_state !== "normal") { return false; }
  962. if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
  963. if (this._FBU.bytes === 0) {
  964. if (this._sock.rQwait("rect header", 12)) { return false; }
  965. /* New FramebufferUpdate */
  966. var hdr = this._sock.rQshiftBytes(12);
  967. this._FBU.x = (hdr[0] << 8) + hdr[1];
  968. this._FBU.y = (hdr[2] << 8) + hdr[3];
  969. this._FBU.width = (hdr[4] << 8) + hdr[5];
  970. this._FBU.height = (hdr[6] << 8) + hdr[7];
  971. this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
  972. (hdr[10] << 8) + hdr[11], 10);
  973. this._onFBUReceive(this,
  974. {'x': this._FBU.x, 'y': this._FBU.y,
  975. 'width': this._FBU.width, 'height': this._FBU.height,
  976. 'encoding': this._FBU.encoding,
  977. 'encodingName': this._encNames[this._FBU.encoding]});
  978. if (!this._encNames[this._FBU.encoding]) {
  979. this._fail("Disconnected: unsupported encoding " +
  980. this._FBU.encoding);
  981. return false;
  982. }
  983. }
  984. this._timing.last_fbu = (new Date()).getTime();
  985. ret = this._encHandlers[this._FBU.encoding]();
  986. now = (new Date()).getTime();
  987. this._timing.cur_fbu += (now - this._timing.last_fbu);
  988. if (ret) {
  989. this._encStats[this._FBU.encoding][0]++;
  990. this._encStats[this._FBU.encoding][1]++;
  991. this._timing.pixels += this._FBU.width * this._FBU.height;
  992. }
  993. if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
  994. if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
  995. this._timing.fbu_rt_start > 0) {
  996. this._timing.full_fbu_total += this._timing.cur_fbu;
  997. this._timing.full_fbu_cnt++;
  998. Util.Info("Timing of full FBU, curr: " +
  999. this._timing.cur_fbu + ", total: " +
  1000. this._timing.full_fbu_total + ", cnt: " +
  1001. this._timing.full_fbu_cnt + ", avg: " +
  1002. (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
  1003. }
  1004. if (this._timing.fbu_rt_start > 0) {
  1005. var fbu_rt_diff = now - this._timing.fbu_rt_start;
  1006. this._timing.fbu_rt_total += fbu_rt_diff;
  1007. this._timing.fbu_rt_cnt++;
  1008. Util.Info("full FBU round-trip, cur: " +
  1009. fbu_rt_diff + ", total: " +
  1010. this._timing.fbu_rt_total + ", cnt: " +
  1011. this._timing.fbu_rt_cnt + ", avg: " +
  1012. (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
  1013. this._timing.fbu_rt_start = 0;
  1014. }
  1015. }
  1016. if (!ret) { return ret; } // need more data
  1017. }
  1018. this._onFBUComplete(this,
  1019. {'x': this._FBU.x, 'y': this._FBU.y,
  1020. 'width': this._FBU.width, 'height': this._FBU.height,
  1021. 'encoding': this._FBU.encoding,
  1022. 'encodingName': this._encNames[this._FBU.encoding]});
  1023. return true; // We finished this FBU
  1024. },
  1025. };
  1026. Util.make_properties(RFB, [
  1027. ['target', 'wo', 'dom'], // VNC display rendering Canvas object
  1028. ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
  1029. ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
  1030. ['true_color', 'rw', 'bool'], // Request true color pixel data
  1031. ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
  1032. ['shared', 'rw', 'bool'], // Request shared mode
  1033. ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
  1034. ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
  1035. ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
  1036. ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
  1037. ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
  1038. ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
  1039. // Callback functions
  1040. ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
  1041. ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
  1042. ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
  1043. ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
  1044. ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
  1045. ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
  1046. ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
  1047. ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
  1048. ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
  1049. ]);
  1050. RFB.prototype.set_local_cursor = function (cursor) {
  1051. if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
  1052. this._local_cursor = false;
  1053. this._display.disableLocalCursor(); //Only show server-side cursor
  1054. } else {
  1055. if (this._display.get_cursor_uri()) {
  1056. this._local_cursor = true;
  1057. } else {
  1058. Util.Warn("Browser does not support local cursor");
  1059. }
  1060. }
  1061. };
  1062. RFB.prototype.get_display = function () { return this._display; };
  1063. RFB.prototype.get_keyboard = function () { return this._keyboard; };
  1064. RFB.prototype.get_mouse = function () { return this._mouse; };
  1065. // Class Methods
  1066. RFB.messages = {
  1067. keyEvent: function (keysym, down) {
  1068. var arr = [4];
  1069. arr.push8(down);
  1070. arr.push16(0);
  1071. arr.push32(keysym);
  1072. return arr;
  1073. },
  1074. pointerEvent: function (x, y, mask) {
  1075. var arr = [5]; // msg-type
  1076. arr.push8(mask);
  1077. arr.push16(x);
  1078. arr.push16(y);
  1079. return arr;
  1080. },
  1081. // TODO(directxman12): make this unicode compatible?
  1082. clientCutText: function (text) {
  1083. var arr = [6]; // msg-type
  1084. arr.push8(0); // padding
  1085. arr.push8(0); // padding
  1086. arr.push8(0); // padding
  1087. arr.push32(text.length);
  1088. var n = text.length;
  1089. for (var i = 0; i < n; i++) {
  1090. arr.push(text.charCodeAt(i));
  1091. }
  1092. return arr;
  1093. },
  1094. pixelFormat: function (bpp, depth, true_color) {
  1095. var arr = [0]; // msg-type
  1096. arr.push8(0); // padding
  1097. arr.push8(0); // padding
  1098. arr.push8(0); // padding
  1099. arr.push8(bpp * 8); // bits-per-pixel
  1100. arr.push8(depth * 8); // depth
  1101. arr.push8(0); // little-endian
  1102. arr.push8(true_color ? 1 : 0); // true-color
  1103. arr.push16(255); // red-max
  1104. arr.push16(255); // green-max
  1105. arr.push16(255); // blue-max
  1106. arr.push8(16); // red-shift
  1107. arr.push8(8); // green-shift
  1108. arr.push8(0); // blue-shift
  1109. arr.push8(0); // padding
  1110. arr.push8(0); // padding
  1111. arr.push8(0); // padding
  1112. return arr;
  1113. },
  1114. clientEncodings: function (encodings, local_cursor, true_color) {
  1115. var i, encList = [];
  1116. for (i = 0; i < encodings.length; i++) {
  1117. if (encodings[i][0] === "Cursor" && !local_cursor) {
  1118. Util.Debug("Skipping Cursor pseudo-encoding");
  1119. } else if (encodings[i][0] === "TIGHT" && !true_color) {
  1120. // TODO: remove this when we have tight+non-true-color
  1121. Util.Warn("Skipping tight as it is only supported with true color");
  1122. } else {
  1123. encList.push(encodings[i][1]);
  1124. }
  1125. }
  1126. var arr = [2]; // msg-type
  1127. arr.push8(0); // padding
  1128. arr.push16(encList.length); // encoding count
  1129. for (i = 0; i < encList.length; i++) {
  1130. arr.push32(encList[i]);
  1131. }
  1132. return arr;
  1133. },
  1134. fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
  1135. var arr = [];
  1136. var cb = cleanDirty.cleanBox;
  1137. var w, h;
  1138. if (cb.w > 0 && cb.h > 0) {
  1139. w = typeof cb.w === "undefined" ? fb_width : cb.w;
  1140. h = typeof cb.h === "undefined" ? fb_height : cb.h;
  1141. // Request incremental for clean box
  1142. arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h));
  1143. }
  1144. for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
  1145. var db = cleanDirty.dirtyBoxes[i];
  1146. // Force all (non-incremental) for dirty box
  1147. w = typeof db.w === "undefined" ? fb_width : db.w;
  1148. h = typeof db.h === "undefined" ? fb_height : db.h;
  1149. arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
  1150. }
  1151. return arr;
  1152. },
  1153. fbUpdateRequest: function (incremental, x, y, w, h) {
  1154. if (typeof(x) === "undefined") { x = 0; }
  1155. if (typeof(y) === "undefined") { y = 0; }
  1156. var arr = [3]; // msg-type
  1157. arr.push8(incremental);
  1158. arr.push16(x);
  1159. arr.push16(y);
  1160. arr.push16(w);
  1161. arr.push16(h);
  1162. return arr;
  1163. }
  1164. };
  1165. RFB.genDES = function (password, challenge) {
  1166. var passwd = [];
  1167. for (var i = 0; i < password.length; i++) {
  1168. passwd.push(password.charCodeAt(i));
  1169. }
  1170. return (new DES(passwd)).encrypt(challenge);
  1171. };
  1172. RFB.extract_data_uri = function (arr) {
  1173. return ";base64," + Base64.encode(arr);
  1174. };
  1175. RFB.encodingHandlers = {
  1176. RAW: function () {
  1177. if (this._FBU.lines === 0) {
  1178. this._FBU.lines = this._FBU.height;
  1179. }
  1180. this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
  1181. if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
  1182. var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
  1183. var curr_height = Math.min(this._FBU.lines,
  1184. Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
  1185. this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
  1186. curr_height, this._sock.get_rQ(),
  1187. this._sock.get_rQi());
  1188. this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
  1189. this._FBU.lines -= curr_height;
  1190. if (this._FBU.lines > 0) {
  1191. this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
  1192. } else {
  1193. this._FBU.rects--;
  1194. this._FBU.bytes = 0;
  1195. }
  1196. return true;
  1197. },
  1198. COPYRECT: function () {
  1199. this._FBU.bytes = 4;
  1200. if (this._sock.rQwait("COPYRECT", 4)) { return false; }
  1201. this._display.renderQ_push({
  1202. 'type': 'copy',
  1203. 'old_x': this._sock.rQshift16(),
  1204. 'old_y': this._sock.rQshift16(),
  1205. 'x': this._FBU.x,
  1206. 'y': this._FBU.y,
  1207. 'width': this._FBU.width,
  1208. 'height': this._FBU.height
  1209. });
  1210. this._FBU.rects--;
  1211. this._FBU.bytes = 0;
  1212. return true;
  1213. },
  1214. RRE: function () {
  1215. var color;
  1216. if (this._FBU.subrects === 0) {
  1217. this._FBU.bytes = 4 + this._fb_Bpp;
  1218. if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
  1219. this._FBU.subrects = this._sock.rQshift32();
  1220. color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
  1221. this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
  1222. }
  1223. while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
  1224. color = this._sock.rQshiftBytes(this._fb_Bpp);
  1225. var x = this._sock.rQshift16();
  1226. var y = this._sock.rQshift16();
  1227. var width = this._sock.rQshift16();
  1228. var height = this._sock.rQshift16();
  1229. this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
  1230. this._FBU.subrects--;
  1231. }
  1232. if (this._FBU.subrects > 0) {
  1233. var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
  1234. this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
  1235. } else {
  1236. this._FBU.rects--;
  1237. this._FBU.bytes = 0;
  1238. }
  1239. return true;
  1240. },
  1241. HEXTILE: function () {
  1242. var rQ = this._sock.get_rQ();
  1243. var rQi = this._sock.get_rQi();
  1244. if (this._FBU.tiles === 0) {
  1245. this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
  1246. this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
  1247. this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
  1248. this._FBU.tiles = this._FBU.total_tiles;
  1249. }
  1250. while (this._FBU.tiles > 0) {
  1251. this._FBU.bytes = 1;
  1252. if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
  1253. var subencoding = rQ[rQi]; // Peek
  1254. if (subencoding > 30) { // Raw
  1255. this._fail("Disconnected: illegal hextile subencoding " + subencoding);
  1256. return false;
  1257. }
  1258. var subrects = 0;
  1259. var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
  1260. var tile_x = curr_tile % this._FBU.tiles_x;
  1261. var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
  1262. var x = this._FBU.x + tile_x * 16;
  1263. var y = this._FBU.y + tile_y * 16;
  1264. var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
  1265. var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
  1266. // Figure out how much we are expecting
  1267. if (subencoding & 0x01) { // Raw
  1268. this._FBU.bytes += w * h * this._fb_Bpp;
  1269. } else {
  1270. if (subencoding & 0x02) { // Background
  1271. this._FBU.bytes += this._fb_Bpp;
  1272. }
  1273. if (subencoding & 0x04) { // Foreground
  1274. this._FBU.bytes += this._fb_Bpp;
  1275. }
  1276. if (subencoding & 0x08) { // AnySubrects
  1277. this._FBU.bytes++; // Since we aren't shifting it off
  1278. if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
  1279. subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
  1280. if (subencoding & 0x10) { // SubrectsColoured
  1281. this._FBU.bytes += subrects * (this._fb_Bpp + 2);
  1282. } else {
  1283. this._FBU.bytes += subrects * 2;
  1284. }
  1285. }
  1286. }
  1287. if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
  1288. // We know the encoding and have a whole tile
  1289. this._FBU.subencoding = rQ[rQi];
  1290. rQi++;
  1291. if (this._FBU.subencoding === 0) {
  1292. if (this._FBU.lastsubencoding & 0x01) {
  1293. // Weird: ignore blanks are RAW
  1294. Util.Debug(" Ignoring blank after RAW");
  1295. } else {
  1296. this._display.fillRect(x, y, w, h, this._FBU.background);
  1297. }
  1298. } else if (this._FBU.subencoding & 0x01) { // Raw
  1299. this._display.blitImage(x, y, w, h, rQ, rQi);
  1300. rQi += this._FBU.bytes - 1;
  1301. } else {
  1302. if (this._FBU.subencoding & 0x02) { // Background
  1303. this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
  1304. rQi += this._fb_Bpp;
  1305. }
  1306. if (this._FBU.subencoding & 0x04) { // Foreground
  1307. this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
  1308. rQi += this._fb_Bpp;
  1309. }
  1310. this._display.startTile(x, y, w, h, this._FBU.background);
  1311. if (this._FBU.subencoding & 0x08) { // AnySubrects
  1312. subrects = rQ[rQi];
  1313. rQi++;
  1314. for (var s = 0; s < subrects; s++) {
  1315. var color;
  1316. if (this._FBU.subencoding & 0x10) { // SubrectsColoured
  1317. color = rQ.slice(rQi, rQi + this._fb_Bpp);
  1318. rQi += this._fb_Bpp;
  1319. } else {
  1320. color = this._FBU.foreground;
  1321. }
  1322. var xy = rQ[rQi];
  1323. rQi++;
  1324. var sx = (xy >> 4);
  1325. var sy = (xy & 0x0f);
  1326. var wh = rQ[rQi];
  1327. rQi++;
  1328. var sw = (wh >> 4) + 1;
  1329. var sh = (wh & 0x0f) + 1;
  1330. this._display.subTile(sx, sy, sw, sh, color);
  1331. }
  1332. }
  1333. this._display.finishTile();
  1334. }
  1335. this._sock.set_rQi(rQi);
  1336. this._FBU.lastsubencoding = this._FBU.subencoding;
  1337. this._FBU.bytes = 0;
  1338. this._FBU.tiles--;
  1339. }
  1340. if (this._FBU.tiles === 0) {
  1341. this._FBU.rects--;
  1342. }
  1343. return true;
  1344. },
  1345. getTightCLength: function (arr) {
  1346. var header = 1, data = 0;
  1347. data += arr[0] & 0x7f;
  1348. if (arr[0] & 0x80) {
  1349. header++;
  1350. data += (arr[1] & 0x7f) << 7;
  1351. if (arr[1] & 0x80) {
  1352. header++;
  1353. data += arr[2] << 14;
  1354. }
  1355. }
  1356. return [header, data];
  1357. },
  1358. display_tight: function (isTightPNG) {
  1359. if (this._fb_depth === 1) {
  1360. this._fail("Tight protocol handler only implements true color mode");
  1361. }
  1362. this._FBU.bytes = 1; // compression-control byte
  1363. if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
  1364. var checksum = function (data) {
  1365. var sum = 0;
  1366. for (var i = 0; i < data.length; i++) {
  1367. sum += data[i];
  1368. if (sum > 65536) sum -= 65536;
  1369. }
  1370. return sum;
  1371. };
  1372. var resetStreams = 0;
  1373. var streamId = -1;
  1374. var decompress = function (data) {
  1375. for (var i = 0; i < 4; i++) {
  1376. if ((resetStreams >> i) & 1) {
  1377. this._FBU.zlibs[i].reset();
  1378. Util.Info("Reset zlib stream " + i);
  1379. }
  1380. }
  1381. var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
  1382. if (uncompressed.status !== 0) {
  1383. Util.Error("Invalid data in zlib stream");
  1384. }
  1385. return uncompressed.data;
  1386. }.bind(this);
  1387. var indexedToRGB = function (data, numColors, palette, width, height) {
  1388. // Convert indexed (palette based) image data to RGB
  1389. // TODO: reduce number of calculations inside loop
  1390. var dest = [];
  1391. var x, y, dp, sp;
  1392. if (numColors === 2) {
  1393. var w = Math.floor((width + 7) / 8);
  1394. var w1 = Math.floor(width / 8);
  1395. for (y = 0; y < height; y++) {
  1396. var b;
  1397. for (x = 0; x < w1; x++) {
  1398. for (b = 7; b >= 0; b--) {
  1399. dp = (y * width + x * 8 + 7 - b) * 3;
  1400. sp = (data[y * w + x] >> b & 1) * 3;
  1401. dest[dp] = palette[sp];
  1402. dest[dp + 1] = palette[sp + 1];
  1403. dest[dp + 2] = palette[sp + 2];
  1404. }
  1405. }
  1406. for (b = 7; b >= 8 - width % 8; b--) {
  1407. dp = (y * width + x * 8 + 7 - b) * 3;
  1408. sp = (data[y * w + x] >> b & 1) * 3;
  1409. dest[dp] = palette[sp];
  1410. dest[dp + 1] = palette[sp + 1];
  1411. dest[dp + 2] = palette[sp + 2];
  1412. }
  1413. }
  1414. } else {
  1415. for (y = 0; y < height; y++) {
  1416. for (x = 0; x < width; x++) {
  1417. dp = (y * width + x) * 3;
  1418. sp = data[y * width + x] * 3;
  1419. dest[dp] = palette[sp];
  1420. dest[dp + 1] = palette[sp + 1];
  1421. dest[dp + 2] = palette[sp + 2];
  1422. }
  1423. }
  1424. }
  1425. return dest;
  1426. }.bind(this);
  1427. var rQ = this._sock.get_rQ();
  1428. var rQi = this._sock.get_rQi();
  1429. var cmode, clength, data;
  1430. var handlePalette = function () {
  1431. var numColors = rQ[rQi + 2] + 1;
  1432. var paletteSize = numColors * this._fb_depth;
  1433. this._FBU.bytes += paletteSize;
  1434. if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
  1435. var bpp = (numColors <= 2) ? 1 : 8;
  1436. var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
  1437. var raw = false;
  1438. if (rowSize * this._FBU.height < 12) {
  1439. raw = true;
  1440. clength = [0, rowSize * this._FBU.height];
  1441. } else {
  1442. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
  1443. 3 + paletteSize + 3));
  1444. }
  1445. this._FBU.bytes += clength[0] + clength[1];
  1446. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1447. // Shift ctl, filter id, num colors, palette entries, and clength off
  1448. this._sock.rQskipBytes(3);
  1449. var palette = this._sock.rQshiftBytes(paletteSize);
  1450. this._sock.rQskipBytes(clength[0]);
  1451. if (raw) {
  1452. data = this._sock.rQshiftBytes(clength[1]);
  1453. } else {
  1454. data = decompress(this._sock.rQshiftBytes(clength[1]));
  1455. }
  1456. // Convert indexed (palette based) image data to RGB
  1457. var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
  1458. this._display.renderQ_push({
  1459. 'type': 'blitRgb',
  1460. 'data': rgb,
  1461. 'x': this._FBU.x,
  1462. 'y': this._FBU.y,
  1463. 'width': this._FBU.width,
  1464. 'height': this._FBU.height
  1465. });
  1466. return true;
  1467. }.bind(this);
  1468. var handleCopy = function () {
  1469. var raw = false;
  1470. var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
  1471. if (uncompressedSize < 12) {
  1472. raw = true;
  1473. clength = [0, uncompressedSize];
  1474. } else {
  1475. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
  1476. }
  1477. this._FBU.bytes = 1 + clength[0] + clength[1];
  1478. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1479. // Shift ctl, clength off
  1480. this._sock.rQshiftBytes(1 + clength[0]);
  1481. if (raw) {
  1482. data = this._sock.rQshiftBytes(clength[1]);
  1483. } else {
  1484. data = decompress(this._sock.rQshiftBytes(clength[1]));
  1485. }
  1486. this._display.renderQ_push({
  1487. 'type': 'blitRgb',
  1488. 'data': data,
  1489. 'x': this._FBU.x,
  1490. 'y': this._FBU.y,
  1491. 'width': this._FBU.width,
  1492. 'height': this._FBU.height
  1493. });
  1494. return true;
  1495. }.bind(this);
  1496. var ctl = this._sock.rQpeek8();
  1497. // Keep tight reset bits
  1498. resetStreams = ctl & 0xF;
  1499. // Figure out filter
  1500. ctl = ctl >> 4;
  1501. streamId = ctl & 0x3;
  1502. if (ctl === 0x08) cmode = "fill";
  1503. else if (ctl === 0x09) cmode = "jpeg";
  1504. else if (ctl === 0x0A) cmode = "png";
  1505. else if (ctl & 0x04) cmode = "filter";
  1506. else if (ctl < 0x04) cmode = "copy";
  1507. else return this._fail("Illegal tight compression received, ctl: " + ctl);
  1508. if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
  1509. return this._fail("filter/copy received in tightPNG mode");
  1510. }
  1511. switch (cmode) {
  1512. // fill use fb_depth because TPIXELs drop the padding byte
  1513. case "fill": // TPIXEL
  1514. this._FBU.bytes += this._fb_depth;
  1515. break;
  1516. case "jpeg": // max clength
  1517. this._FBU.bytes += 3;
  1518. break;
  1519. case "png": // max clength
  1520. this._FBU.bytes += 3;
  1521. break;
  1522. case "filter": // filter id + num colors if palette
  1523. this._FBU.bytes += 2;
  1524. break;
  1525. case "copy":
  1526. break;
  1527. }
  1528. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1529. // Determine FBU.bytes
  1530. switch (cmode) {
  1531. case "fill":
  1532. this._sock.rQskip8(); // shift off ctl
  1533. var color = this._sock.rQshiftBytes(this._fb_depth);
  1534. this._display.renderQ_push({
  1535. 'type': 'fill',
  1536. 'x': this._FBU.x,
  1537. 'y': this._FBU.y,
  1538. 'width': this._FBU.width,
  1539. 'height': this._FBU.height,
  1540. 'color': [color[2], color[1], color[0]]
  1541. });
  1542. break;
  1543. case "png":
  1544. case "jpeg":
  1545. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
  1546. this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
  1547. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1548. // We have everything, render it
  1549. this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length
  1550. var img = new Image();
  1551. img.src = "data: image/" + cmode +
  1552. RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
  1553. this._display.renderQ_push({
  1554. 'type': 'img',
  1555. 'img': img,
  1556. 'x': this._FBU.x,
  1557. 'y': this._FBU.y
  1558. });
  1559. img = null;
  1560. break;
  1561. case "filter":
  1562. var filterId = rQ[rQi + 1];
  1563. if (filterId === 1) {
  1564. if (!handlePalette()) { return false; }
  1565. } else {
  1566. // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
  1567. // Filter 2, Gradient is valid but not use if jpeg is enabled
  1568. // TODO(directxman12): why aren't we just calling '_fail' here
  1569. throw new Error("Unsupported tight subencoding received, filter: " + filterId);
  1570. }
  1571. break;
  1572. case "copy":
  1573. if (!handleCopy()) { return false; }
  1574. break;
  1575. }
  1576. this._FBU.bytes = 0;
  1577. this._FBU.rects--;
  1578. return true;
  1579. },
  1580. TIGHT: function () { return this._encHandlers.display_tight(false); },
  1581. TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
  1582. last_rect: function () {
  1583. this._FBU.rects = 0;
  1584. return true;
  1585. },
  1586. ext_desktop_size: function () {
  1587. this._FBU.bytes = 1;
  1588. if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; }
  1589. this._supportsSetDesktopSize = true;
  1590. var number_of_screens = this._sock.rQpeek8();
  1591. this._FBU.bytes = 4 + (number_of_screens * 16);
  1592. if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; }
  1593. this._sock.rQskipBytes(1); // number-of-screens
  1594. this._sock.rQskipBytes(3); // padding
  1595. for (var i=0; i<number_of_screens; i += 1) {
  1596. // Save the id and flags of the first screen
  1597. if (i == 0) {
  1598. this._screen_id = this._sock.rQshiftBytes(4); // id
  1599. this._sock.rQskipBytes(2); // x-position
  1600. this._sock.rQskipBytes(2); // y-position
  1601. this._sock.rQskipBytes(2); // width
  1602. this._sock.rQskipBytes(2); // height
  1603. this._screen_flags = this._sock.rQshiftBytes(4); // flags
  1604. } else {
  1605. this._sock.rQskipBytes(16);
  1606. }
  1607. }
  1608. if (this._FBU.x == 0 && this._FBU.y != 0) { return true; }
  1609. this._fb_width = this._FBU.width;
  1610. this._fb_height = this._FBU.height;
  1611. this._display.resize(this._fb_width, this._fb_height);
  1612. this._onFBResize(this, this._fb_width, this._fb_height);
  1613. this._FBU.bytes = 0;
  1614. this._FBU.rects -= 1;
  1615. return true;
  1616. },
  1617. DesktopSize: function () {
  1618. Util.Debug(">> set_desktopsize");
  1619. this._fb_width = this._FBU.width;
  1620. this._fb_height = this._FBU.height;
  1621. this._display.resize(this._fb_width, this._fb_height);
  1622. this._onFBResize(this, this._fb_width, this._fb_height);
  1623. this._timing.fbu_rt_start = (new Date()).getTime();
  1624. this._FBU.bytes = 0;
  1625. this._FBU.rects--;
  1626. Util.Debug("<< set_desktopsize");
  1627. return true;
  1628. },
  1629. Cursor: function () {
  1630. Util.Debug(">> set_cursor");
  1631. var x = this._FBU.x; // hotspot-x
  1632. var y = this._FBU.y; // hotspot-y
  1633. var w = this._FBU.width;
  1634. var h = this._FBU.height;
  1635. var pixelslength = w * h * this._fb_Bpp;
  1636. var masklength = Math.floor((w + 7) / 8) * h;
  1637. this._FBU.bytes = pixelslength + masklength;
  1638. if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
  1639. this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
  1640. this._sock.rQshiftBytes(masklength),
  1641. x, y, w, h);
  1642. this._FBU.bytes = 0;
  1643. this._FBU.rects--;
  1644. Util.Debug("<< set_cursor");
  1645. return true;
  1646. },
  1647. JPEG_quality_lo: function () {
  1648. Util.Error("Server sent jpeg_quality pseudo-encoding");
  1649. },
  1650. compress_lo: function () {
  1651. Util.Error("Server sent compress level pseudo-encoding");
  1652. }
  1653. };
  1654. })();