rfb.js 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  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. ['ExtendedDesktopSize', -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. this._sock.off('close'); // make sure we don't get a double event
  413. }
  414. switch (state) {
  415. case 'normal':
  416. if (oldstate === 'disconnected' || oldstate === 'failed') {
  417. Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
  418. }
  419. break;
  420. case 'connect':
  421. this._init_vars();
  422. this._connect();
  423. // WebSocket.onopen transitions to 'ProtocolVersion'
  424. break;
  425. case 'disconnect':
  426. this._disconnTimer = setTimeout(function () {
  427. this._fail("Disconnect timeout");
  428. }.bind(this), this._disconnectTimeout * 1000);
  429. this._print_stats();
  430. // WebSocket.onclose transitions to 'disconnected'
  431. break;
  432. case 'failed':
  433. if (oldstate === 'disconnected') {
  434. Util.Error("Invalid transition from 'disconnected' to 'failed'");
  435. } else if (oldstate === 'normal') {
  436. Util.Error("Error while connected.");
  437. } else if (oldstate === 'init') {
  438. Util.Error("Error while initializing.");
  439. }
  440. // Make sure we transition to disconnected
  441. setTimeout(function () {
  442. this._updateState('disconnected');
  443. }.bind(this), 50);
  444. break;
  445. default:
  446. // No state change action to take
  447. }
  448. if (oldstate === 'failed' && state === 'disconnected') {
  449. this._onUpdateState(this, state, oldstate);
  450. } else {
  451. this._onUpdateState(this, state, oldstate, statusMsg);
  452. }
  453. },
  454. _fail: function (msg) {
  455. this._updateState('failed', msg);
  456. return false;
  457. },
  458. _handle_message: function () {
  459. if (this._sock.rQlen() === 0) {
  460. Util.Warn("handle_message called on an empty receive queue");
  461. return;
  462. }
  463. switch (this._rfb_state) {
  464. case 'disconnected':
  465. case 'failed':
  466. Util.Error("Got data while disconnected");
  467. break;
  468. case 'normal':
  469. if (this._normal_msg() && this._sock.rQlen() > 0) {
  470. // true means we can continue processing
  471. // Give other events a chance to run
  472. if (this._msgTimer === null) {
  473. Util.Debug("More data to process, creating timer");
  474. this._msgTimer = setTimeout(function () {
  475. this._msgTimer = null;
  476. this._handle_message();
  477. }.bind(this), 10);
  478. } else {
  479. Util.Debug("More data to process, existing timer");
  480. }
  481. }
  482. break;
  483. default:
  484. this._init_msg();
  485. break;
  486. }
  487. },
  488. _checkEvents: function () {
  489. if (this._rfb_state === 'normal' && !this._viewportDragging && this._mouse_arr.length > 0) {
  490. this._sock.send(this._mouse_arr);
  491. this._mouse_arr = [];
  492. }
  493. },
  494. _handleKeyPress: function (keysym, down) {
  495. if (this._view_only) { return; } // View only, skip keyboard, events
  496. this._sock.send(RFB.messages.keyEvent(keysym, down));
  497. },
  498. _handleMouseButton: function (x, y, down, bmask) {
  499. if (down) {
  500. this._mouse_buttonMask |= bmask;
  501. } else {
  502. this._mouse_buttonMask ^= bmask;
  503. }
  504. if (this._viewportDrag) {
  505. if (down && !this._viewportDragging) {
  506. this._viewportDragging = true;
  507. this._viewportDragPos = {'x': x, 'y': y};
  508. // Skip sending mouse events
  509. return;
  510. } else {
  511. this._viewportDragging = false;
  512. }
  513. }
  514. if (this._view_only) { return; } // View only, skip mouse events
  515. this._mouse_arr = this._mouse_arr.concat(
  516. RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
  517. this._sock.send(this._mouse_arr);
  518. this._mouse_arr = [];
  519. },
  520. _handleMouseMove: function (x, y) {
  521. if (this._viewportDragging) {
  522. var deltaX = this._viewportDragPos.x - x;
  523. var deltaY = this._viewportDragPos.y - y;
  524. this._viewportDragPos = {'x': x, 'y': y};
  525. this._display.viewportChangePos(deltaX, deltaY);
  526. // Skip sending mouse events
  527. return;
  528. }
  529. if (this._view_only) { return; } // View only, skip mouse events
  530. this._mouse_arr = this._mouse_arr.concat(
  531. RFB.messages.pointerEvent(this._display.absX(x), this._display.absY(y), this._mouse_buttonMask));
  532. this._checkEvents();
  533. },
  534. // Message Handlers
  535. _negotiate_protocol_version: function () {
  536. if (this._sock.rQlen() < 12) {
  537. return this._fail("Incomplete protocol version");
  538. }
  539. var sversion = this._sock.rQshiftStr(12).substr(4, 7);
  540. Util.Info("Server ProtocolVersion: " + sversion);
  541. var is_repeater = 0;
  542. switch (sversion) {
  543. case "000.000": // UltraVNC repeater
  544. is_repeater = 1;
  545. break;
  546. case "003.003":
  547. case "003.006": // UltraVNC
  548. case "003.889": // Apple Remote Desktop
  549. this._rfb_version = 3.3;
  550. break;
  551. case "003.007":
  552. this._rfb_version = 3.7;
  553. break;
  554. case "003.008":
  555. case "004.000": // Intel AMT KVM
  556. case "004.001": // RealVNC 4.6
  557. this._rfb_version = 3.8;
  558. break;
  559. default:
  560. return this._fail("Invalid server version " + sversion);
  561. }
  562. if (is_repeater) {
  563. var repeaterID = this._repeaterID;
  564. while (repeaterID.length < 250) {
  565. repeaterID += "\0";
  566. }
  567. this._sock.send_string(repeaterID);
  568. return true;
  569. }
  570. if (this._rfb_version > this._rfb_max_version) {
  571. this._rfb_version = this._rfb_max_version;
  572. }
  573. // Send updates either at a rate of 1 update per 50ms, or
  574. // whatever slower rate the network can handle
  575. this._sendTimer = setInterval(this._sock.flush.bind(this._sock), 50);
  576. var cversion = "00" + parseInt(this._rfb_version, 10) +
  577. ".00" + ((this._rfb_version * 10) % 10);
  578. this._sock.send_string("RFB " + cversion + "\n");
  579. this._updateState('Security', 'Sent ProtocolVersion: ' + cversion);
  580. },
  581. _negotiate_security: function () {
  582. if (this._rfb_version >= 3.7) {
  583. // Server sends supported list, client decides
  584. var num_types = this._sock.rQshift8();
  585. if (this._sock.rQwait("security type", num_types, 1)) { return false; }
  586. if (num_types === 0) {
  587. var strlen = this._sock.rQshift32();
  588. var reason = this._sock.rQshiftStr(strlen);
  589. return this._fail("Security failure: " + reason);
  590. }
  591. this._rfb_auth_scheme = 0;
  592. var types = this._sock.rQshiftBytes(num_types);
  593. Util.Debug("Server security types: " + types);
  594. for (var i = 0; i < types.length; i++) {
  595. if (types[i] > this._rfb_auth_scheme && (types[i] <= 16 || types[i] == 22)) {
  596. this._rfb_auth_scheme = types[i];
  597. }
  598. }
  599. if (this._rfb_auth_scheme === 0) {
  600. return this._fail("Unsupported security types: " + types);
  601. }
  602. this._sock.send([this._rfb_auth_scheme]);
  603. } else {
  604. // Server decides
  605. if (this._sock.rQwait("security scheme", 4)) { return false; }
  606. this._rfb_auth_scheme = this._sock.rQshift32();
  607. }
  608. this._updateState('Authentication', 'Authenticating using scheme: ' + this._rfb_auth_scheme);
  609. return this._init_msg(); // jump to authentication
  610. },
  611. // authentication
  612. _negotiate_xvp_auth: function () {
  613. var xvp_sep = this._xvp_password_sep;
  614. var xvp_auth = this._rfb_password.split(xvp_sep);
  615. if (xvp_auth.length < 3) {
  616. this._updateState('password', 'XVP credentials required (user' + xvp_sep +
  617. 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password);
  618. this._onPasswordRequired(this);
  619. return false;
  620. }
  621. var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
  622. String.fromCharCode(xvp_auth[1].length) +
  623. xvp_auth[0] +
  624. xvp_auth[1];
  625. this._sock.send_string(xvp_auth_str);
  626. this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
  627. this._rfb_auth_scheme = 2;
  628. return this._negotiate_authentication();
  629. },
  630. _negotiate_std_vnc_auth: function () {
  631. if (this._rfb_password.length === 0) {
  632. // Notify via both callbacks since it's kind of
  633. // an RFB state change and a UI interface issue
  634. this._updateState('password', "Password Required");
  635. this._onPasswordRequired(this);
  636. }
  637. if (this._sock.rQwait("auth challenge", 16)) { return false; }
  638. var challenge = this._sock.rQshiftBytes(16);
  639. var response = RFB.genDES(this._rfb_password, challenge);
  640. this._sock.send(response);
  641. this._updateState("SecurityResult");
  642. return true;
  643. },
  644. _negotiate_tight_tunnels: function (numTunnels) {
  645. var clientSupportedTunnelTypes = {
  646. 0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
  647. };
  648. var serverSupportedTunnelTypes = {};
  649. // receive tunnel capabilities
  650. for (var i = 0; i < numTunnels; i++) {
  651. var cap_code = this._sock.rQshift32();
  652. var cap_vendor = this._sock.rQshiftStr(4);
  653. var cap_signature = this._sock.rQshiftStr(8);
  654. serverSupportedTunnelTypes[cap_code] = { vendor: cap_vendor, signature: cap_signature };
  655. }
  656. // choose the notunnel type
  657. if (serverSupportedTunnelTypes[0]) {
  658. if (serverSupportedTunnelTypes[0].vendor != clientSupportedTunnelTypes[0].vendor ||
  659. serverSupportedTunnelTypes[0].signature != clientSupportedTunnelTypes[0].signature) {
  660. return this._fail("Client's tunnel type had the incorrect vendor or signature");
  661. }
  662. this._sock.send([0, 0, 0, 0]); // use NOTUNNEL
  663. return false; // wait until we receive the sub auth count to continue
  664. } else {
  665. return this._fail("Server wanted tunnels, but doesn't support the notunnel type");
  666. }
  667. },
  668. _negotiate_tight_auth: function () {
  669. if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
  670. if (this._sock.rQwait("num tunnels", 4)) { return false; }
  671. var numTunnels = this._sock.rQshift32();
  672. if (numTunnels > 0 && this._sock.rQwait("tunnel capabilities", 16 * numTunnels, 4)) { return false; }
  673. this._rfb_tightvnc = true;
  674. if (numTunnels > 0) {
  675. this._negotiate_tight_tunnels(numTunnels);
  676. return false; // wait until we receive the sub auth to continue
  677. }
  678. }
  679. // second pass, do the sub-auth negotiation
  680. if (this._sock.rQwait("sub auth count", 4)) { return false; }
  681. var subAuthCount = this._sock.rQshift32();
  682. if (this._sock.rQwait("sub auth capabilities", 16 * subAuthCount, 4)) { return false; }
  683. var clientSupportedTypes = {
  684. 'STDVNOAUTH__': 1,
  685. 'STDVVNCAUTH_': 2
  686. };
  687. var serverSupportedTypes = [];
  688. for (var i = 0; i < subAuthCount; i++) {
  689. var capNum = this._sock.rQshift32();
  690. var capabilities = this._sock.rQshiftStr(12);
  691. serverSupportedTypes.push(capabilities);
  692. }
  693. for (var authType in clientSupportedTypes) {
  694. if (serverSupportedTypes.indexOf(authType) != -1) {
  695. this._sock.send([0, 0, 0, clientSupportedTypes[authType]]);
  696. switch (authType) {
  697. case 'STDVNOAUTH__': // no auth
  698. this._updateState('SecurityResult');
  699. return true;
  700. case 'STDVVNCAUTH_': // VNC auth
  701. this._rfb_auth_scheme = 2;
  702. return this._init_msg();
  703. default:
  704. return this._fail("Unsupported tiny auth scheme: " + authType);
  705. }
  706. }
  707. }
  708. this._fail("No supported sub-auth types!");
  709. },
  710. _negotiate_authentication: function () {
  711. switch (this._rfb_auth_scheme) {
  712. case 0: // connection failed
  713. if (this._sock.rQwait("auth reason", 4)) { return false; }
  714. var strlen = this._sock.rQshift32();
  715. var reason = this._sock.rQshiftStr(strlen);
  716. return this._fail("Auth failure: " + reason);
  717. case 1: // no auth
  718. if (this._rfb_version >= 3.8) {
  719. this._updateState('SecurityResult');
  720. return true;
  721. }
  722. this._updateState('ClientInitialisation', "No auth required");
  723. return this._init_msg();
  724. case 22: // XVP auth
  725. return this._negotiate_xvp_auth();
  726. case 2: // VNC authentication
  727. return this._negotiate_std_vnc_auth();
  728. case 16: // TightVNC Security Type
  729. return this._negotiate_tight_auth();
  730. default:
  731. return this._fail("Unsupported auth scheme: " + this._rfb_auth_scheme);
  732. }
  733. },
  734. _handle_security_result: function () {
  735. if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
  736. switch (this._sock.rQshift32()) {
  737. case 0: // OK
  738. this._updateState('ClientInitialisation', 'Authentication OK');
  739. return this._init_msg();
  740. case 1: // failed
  741. if (this._rfb_version >= 3.8) {
  742. var length = this._sock.rQshift32();
  743. if (this._sock.rQwait("SecurityResult reason", length, 8)) { return false; }
  744. var reason = this._sock.rQshiftStr(length);
  745. return this._fail(reason);
  746. } else {
  747. return this._fail("Authentication failure");
  748. }
  749. return false;
  750. case 2:
  751. return this._fail("Too many auth attempts");
  752. }
  753. },
  754. _negotiate_server_init: function () {
  755. if (this._sock.rQwait("server initialization", 24)) { return false; }
  756. /* Screen size */
  757. this._fb_width = this._sock.rQshift16();
  758. this._fb_height = this._sock.rQshift16();
  759. /* PIXEL_FORMAT */
  760. var bpp = this._sock.rQshift8();
  761. var depth = this._sock.rQshift8();
  762. var big_endian = this._sock.rQshift8();
  763. var true_color = this._sock.rQshift8();
  764. var red_max = this._sock.rQshift16();
  765. var green_max = this._sock.rQshift16();
  766. var blue_max = this._sock.rQshift16();
  767. var red_shift = this._sock.rQshift8();
  768. var green_shift = this._sock.rQshift8();
  769. var blue_shift = this._sock.rQshift8();
  770. this._sock.rQskipBytes(3); // padding
  771. // NB(directxman12): we don't want to call any callbacks or print messages until
  772. // *after* we're past the point where we could backtrack
  773. /* Connection name/title */
  774. var name_length = this._sock.rQshift32();
  775. if (this._sock.rQwait('server init name', name_length, 24)) { return false; }
  776. this._fb_name = Util.decodeUTF8(this._sock.rQshiftStr(name_length));
  777. if (this._rfb_tightvnc) {
  778. if (this._sock.rQwait('TightVNC extended server init header', 8, 24 + name_length)) { return false; }
  779. // In TightVNC mode, ServerInit message is extended
  780. var numServerMessages = this._sock.rQshift16();
  781. var numClientMessages = this._sock.rQshift16();
  782. var numEncodings = this._sock.rQshift16();
  783. this._sock.rQskipBytes(2); // padding
  784. var totalMessagesLength = (numServerMessages + numClientMessages + numEncodings) * 16;
  785. if (this._sock.rQwait('TightVNC extended server init header', totalMessagesLength, 32 + name_length)) { return false; }
  786. var i;
  787. for (i = 0; i < numServerMessages; i++) {
  788. var srvMsg = this._sock.rQshiftStr(16);
  789. }
  790. for (i = 0; i < numClientMessages; i++) {
  791. var clientMsg = this._sock.rQshiftStr(16);
  792. }
  793. for (i = 0; i < numEncodings; i++) {
  794. var encoding = this._sock.rQshiftStr(16);
  795. }
  796. }
  797. // NB(directxman12): these are down here so that we don't run them multiple times
  798. // if we backtrack
  799. Util.Info("Screen: " + this._fb_width + "x" + this._fb_height +
  800. ", bpp: " + bpp + ", depth: " + depth +
  801. ", big_endian: " + big_endian +
  802. ", true_color: " + true_color +
  803. ", red_max: " + red_max +
  804. ", green_max: " + green_max +
  805. ", blue_max: " + blue_max +
  806. ", red_shift: " + red_shift +
  807. ", green_shift: " + green_shift +
  808. ", blue_shift: " + blue_shift);
  809. if (big_endian !== 0) {
  810. Util.Warn("Server native endian is not little endian");
  811. }
  812. if (red_shift !== 16) {
  813. Util.Warn("Server native red-shift is not 16");
  814. }
  815. if (blue_shift !== 0) {
  816. Util.Warn("Server native blue-shift is not 0");
  817. }
  818. // we're past the point where we could backtrack, so it's safe to call this
  819. this._onDesktopName(this, this._fb_name);
  820. if (this._true_color && this._fb_name === "Intel(r) AMT KVM") {
  821. Util.Warn("Intel AMT KVM only supports 8/16 bit depths. Disabling true color");
  822. this._true_color = false;
  823. }
  824. this._display.set_true_color(this._true_color);
  825. this._display.resize(this._fb_width, this._fb_height);
  826. this._onFBResize(this, this._fb_width, this._fb_height);
  827. this._keyboard.grab();
  828. this._mouse.grab();
  829. if (this._true_color) {
  830. this._fb_Bpp = 4;
  831. this._fb_depth = 3;
  832. } else {
  833. this._fb_Bpp = 1;
  834. this._fb_depth = 1;
  835. }
  836. var response = RFB.messages.pixelFormat(this._fb_Bpp, this._fb_depth, this._true_color);
  837. response = response.concat(
  838. RFB.messages.clientEncodings(this._encodings, this._local_cursor, this._true_color));
  839. response = response.concat(
  840. RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
  841. this._fb_width, this._fb_height));
  842. this._timing.fbu_rt_start = (new Date()).getTime();
  843. this._timing.pixels = 0;
  844. this._sock.send(response);
  845. this._checkEvents();
  846. if (this._encrypt) {
  847. this._updateState('normal', 'Connected (encrypted) to: ' + this._fb_name);
  848. } else {
  849. this._updateState('normal', 'Connected (unencrypted) to: ' + this._fb_name);
  850. }
  851. },
  852. _init_msg: function () {
  853. switch (this._rfb_state) {
  854. case 'ProtocolVersion':
  855. return this._negotiate_protocol_version();
  856. case 'Security':
  857. return this._negotiate_security();
  858. case 'Authentication':
  859. return this._negotiate_authentication();
  860. case 'SecurityResult':
  861. return this._handle_security_result();
  862. case 'ClientInitialisation':
  863. this._sock.send([this._shared ? 1 : 0]); // ClientInitialisation
  864. this._updateState('ServerInitialisation', "Authentication OK");
  865. return true;
  866. case 'ServerInitialisation':
  867. return this._negotiate_server_init();
  868. }
  869. },
  870. _handle_set_colour_map_msg: function () {
  871. Util.Debug("SetColorMapEntries");
  872. this._sock.rQskip8(); // Padding
  873. var first_colour = this._sock.rQshift16();
  874. var num_colours = this._sock.rQshift16();
  875. if (this._sock.rQwait('SetColorMapEntries', num_colours * 6, 6)) { return false; }
  876. for (var c = 0; c < num_colours; c++) {
  877. var red = parseInt(this._sock.rQshift16() / 256, 10);
  878. var green = parseInt(this._sock.rQshift16() / 256, 10);
  879. var blue = parseInt(this._sock.rQshift16() / 256, 10);
  880. this._display.set_colourMap([blue, green, red], first_colour + c);
  881. }
  882. Util.Debug("colourMap: " + this._display.get_colourMap());
  883. Util.Info("Registered " + num_colours + " colourMap entries");
  884. return true;
  885. },
  886. _handle_server_cut_text: function () {
  887. Util.Debug("ServerCutText");
  888. if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
  889. this._sock.rQskipBytes(3); // Padding
  890. var length = this._sock.rQshift32();
  891. if (this._sock.rQwait("ServerCutText", length, 8)) { return false; }
  892. var text = this._sock.rQshiftStr(length);
  893. this._onClipboard(this, text);
  894. return true;
  895. },
  896. _handle_xvp_msg: function () {
  897. if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
  898. this._sock.rQskip8(); // Padding
  899. var xvp_ver = this._sock.rQshift8();
  900. var xvp_msg = this._sock.rQshift8();
  901. switch (xvp_msg) {
  902. case 0: // XVP_FAIL
  903. this._updateState(this._rfb_state, "Operation Failed");
  904. break;
  905. case 1: // XVP_INIT
  906. this._rfb_xvp_ver = xvp_ver;
  907. Util.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
  908. this._onXvpInit(this._rfb_xvp_ver);
  909. break;
  910. default:
  911. this._fail("Disconnected: illegal server XVP message " + xvp_msg);
  912. break;
  913. }
  914. return true;
  915. },
  916. _normal_msg: function () {
  917. var msg_type;
  918. if (this._FBU.rects > 0) {
  919. msg_type = 0;
  920. } else {
  921. msg_type = this._sock.rQshift8();
  922. }
  923. switch (msg_type) {
  924. case 0: // FramebufferUpdate
  925. var ret = this._framebufferUpdate();
  926. if (ret) {
  927. this._sock.send(RFB.messages.fbUpdateRequests(this._display.getCleanDirtyReset(),
  928. this._fb_width, this._fb_height));
  929. }
  930. return ret;
  931. case 1: // SetColorMapEntries
  932. return this._handle_set_colour_map_msg();
  933. case 2: // Bell
  934. Util.Debug("Bell");
  935. this._onBell(this);
  936. return true;
  937. case 3: // ServerCutText
  938. return this._handle_server_cut_text();
  939. case 250: // XVP
  940. return this._handle_xvp_msg();
  941. default:
  942. this._fail("Disconnected: illegal server message type " + msg_type);
  943. Util.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
  944. return true;
  945. }
  946. },
  947. _framebufferUpdate: function () {
  948. var ret = true;
  949. var now;
  950. if (this._FBU.rects === 0) {
  951. if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
  952. this._sock.rQskip8(); // Padding
  953. this._FBU.rects = this._sock.rQshift16();
  954. this._FBU.bytes = 0;
  955. this._timing.cur_fbu = 0;
  956. if (this._timing.fbu_rt_start > 0) {
  957. now = (new Date()).getTime();
  958. Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
  959. }
  960. }
  961. while (this._FBU.rects > 0) {
  962. if (this._rfb_state !== "normal") { return false; }
  963. if (this._sock.rQwait("FBU", this._FBU.bytes)) { return false; }
  964. if (this._FBU.bytes === 0) {
  965. if (this._sock.rQwait("rect header", 12)) { return false; }
  966. /* New FramebufferUpdate */
  967. var hdr = this._sock.rQshiftBytes(12);
  968. this._FBU.x = (hdr[0] << 8) + hdr[1];
  969. this._FBU.y = (hdr[2] << 8) + hdr[3];
  970. this._FBU.width = (hdr[4] << 8) + hdr[5];
  971. this._FBU.height = (hdr[6] << 8) + hdr[7];
  972. this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
  973. (hdr[10] << 8) + hdr[11], 10);
  974. this._onFBUReceive(this,
  975. {'x': this._FBU.x, 'y': this._FBU.y,
  976. 'width': this._FBU.width, 'height': this._FBU.height,
  977. 'encoding': this._FBU.encoding,
  978. 'encodingName': this._encNames[this._FBU.encoding]});
  979. if (!this._encNames[this._FBU.encoding]) {
  980. this._fail("Disconnected: unsupported encoding " +
  981. this._FBU.encoding);
  982. return false;
  983. }
  984. }
  985. this._timing.last_fbu = (new Date()).getTime();
  986. ret = this._encHandlers[this._FBU.encoding]();
  987. now = (new Date()).getTime();
  988. this._timing.cur_fbu += (now - this._timing.last_fbu);
  989. if (ret) {
  990. this._encStats[this._FBU.encoding][0]++;
  991. this._encStats[this._FBU.encoding][1]++;
  992. this._timing.pixels += this._FBU.width * this._FBU.height;
  993. }
  994. if (this._timing.pixels >= (this._fb_width * this._fb_height)) {
  995. if ((this._FBU.width === this._fb_width && this._FBU.height === this._fb_height) ||
  996. this._timing.fbu_rt_start > 0) {
  997. this._timing.full_fbu_total += this._timing.cur_fbu;
  998. this._timing.full_fbu_cnt++;
  999. Util.Info("Timing of full FBU, curr: " +
  1000. this._timing.cur_fbu + ", total: " +
  1001. this._timing.full_fbu_total + ", cnt: " +
  1002. this._timing.full_fbu_cnt + ", avg: " +
  1003. (this._timing.full_fbu_total / this._timing.full_fbu_cnt));
  1004. }
  1005. if (this._timing.fbu_rt_start > 0) {
  1006. var fbu_rt_diff = now - this._timing.fbu_rt_start;
  1007. this._timing.fbu_rt_total += fbu_rt_diff;
  1008. this._timing.fbu_rt_cnt++;
  1009. Util.Info("full FBU round-trip, cur: " +
  1010. fbu_rt_diff + ", total: " +
  1011. this._timing.fbu_rt_total + ", cnt: " +
  1012. this._timing.fbu_rt_cnt + ", avg: " +
  1013. (this._timing.fbu_rt_total / this._timing.fbu_rt_cnt));
  1014. this._timing.fbu_rt_start = 0;
  1015. }
  1016. }
  1017. if (!ret) { return ret; } // need more data
  1018. }
  1019. this._onFBUComplete(this,
  1020. {'x': this._FBU.x, 'y': this._FBU.y,
  1021. 'width': this._FBU.width, 'height': this._FBU.height,
  1022. 'encoding': this._FBU.encoding,
  1023. 'encodingName': this._encNames[this._FBU.encoding]});
  1024. return true; // We finished this FBU
  1025. },
  1026. };
  1027. Util.make_properties(RFB, [
  1028. ['target', 'wo', 'dom'], // VNC display rendering Canvas object
  1029. ['focusContainer', 'wo', 'dom'], // DOM element that captures keyboard input
  1030. ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
  1031. ['true_color', 'rw', 'bool'], // Request true color pixel data
  1032. ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
  1033. ['shared', 'rw', 'bool'], // Request shared mode
  1034. ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
  1035. ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
  1036. ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
  1037. ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
  1038. ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
  1039. ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
  1040. // Callback functions
  1041. ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change
  1042. ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb): VNC password is required
  1043. ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
  1044. ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
  1045. ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
  1046. ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
  1047. ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
  1048. ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
  1049. ['onXvpInit', 'rw', 'func'], // onXvpInit(version): XVP extensions active for this connection
  1050. ]);
  1051. RFB.prototype.set_local_cursor = function (cursor) {
  1052. if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
  1053. this._local_cursor = false;
  1054. this._display.disableLocalCursor(); //Only show server-side cursor
  1055. } else {
  1056. if (this._display.get_cursor_uri()) {
  1057. this._local_cursor = true;
  1058. } else {
  1059. Util.Warn("Browser does not support local cursor");
  1060. }
  1061. }
  1062. };
  1063. RFB.prototype.get_display = function () { return this._display; };
  1064. RFB.prototype.get_keyboard = function () { return this._keyboard; };
  1065. RFB.prototype.get_mouse = function () { return this._mouse; };
  1066. // Class Methods
  1067. RFB.messages = {
  1068. keyEvent: function (keysym, down) {
  1069. var arr = [4];
  1070. arr.push8(down);
  1071. arr.push16(0);
  1072. arr.push32(keysym);
  1073. return arr;
  1074. },
  1075. pointerEvent: function (x, y, mask) {
  1076. var arr = [5]; // msg-type
  1077. arr.push8(mask);
  1078. arr.push16(x);
  1079. arr.push16(y);
  1080. return arr;
  1081. },
  1082. // TODO(directxman12): make this unicode compatible?
  1083. clientCutText: function (text) {
  1084. var arr = [6]; // msg-type
  1085. arr.push8(0); // padding
  1086. arr.push8(0); // padding
  1087. arr.push8(0); // padding
  1088. arr.push32(text.length);
  1089. var n = text.length;
  1090. for (var i = 0; i < n; i++) {
  1091. arr.push(text.charCodeAt(i));
  1092. }
  1093. return arr;
  1094. },
  1095. pixelFormat: function (bpp, depth, true_color) {
  1096. var arr = [0]; // msg-type
  1097. arr.push8(0); // padding
  1098. arr.push8(0); // padding
  1099. arr.push8(0); // padding
  1100. arr.push8(bpp * 8); // bits-per-pixel
  1101. arr.push8(depth * 8); // depth
  1102. arr.push8(0); // little-endian
  1103. arr.push8(true_color ? 1 : 0); // true-color
  1104. arr.push16(255); // red-max
  1105. arr.push16(255); // green-max
  1106. arr.push16(255); // blue-max
  1107. arr.push8(16); // red-shift
  1108. arr.push8(8); // green-shift
  1109. arr.push8(0); // blue-shift
  1110. arr.push8(0); // padding
  1111. arr.push8(0); // padding
  1112. arr.push8(0); // padding
  1113. return arr;
  1114. },
  1115. clientEncodings: function (encodings, local_cursor, true_color) {
  1116. var i, encList = [];
  1117. for (i = 0; i < encodings.length; i++) {
  1118. if (encodings[i][0] === "Cursor" && !local_cursor) {
  1119. Util.Debug("Skipping Cursor pseudo-encoding");
  1120. } else if (encodings[i][0] === "TIGHT" && !true_color) {
  1121. // TODO: remove this when we have tight+non-true-color
  1122. Util.Warn("Skipping tight as it is only supported with true color");
  1123. } else {
  1124. encList.push(encodings[i][1]);
  1125. }
  1126. }
  1127. var arr = [2]; // msg-type
  1128. arr.push8(0); // padding
  1129. arr.push16(encList.length); // encoding count
  1130. for (i = 0; i < encList.length; i++) {
  1131. arr.push32(encList[i]);
  1132. }
  1133. return arr;
  1134. },
  1135. fbUpdateRequests: function (cleanDirty, fb_width, fb_height) {
  1136. var arr = [];
  1137. var cb = cleanDirty.cleanBox;
  1138. var w, h;
  1139. if (cb.w > 0 && cb.h > 0) {
  1140. w = typeof cb.w === "undefined" ? fb_width : cb.w;
  1141. h = typeof cb.h === "undefined" ? fb_height : cb.h;
  1142. // Request incremental for clean box
  1143. arr = arr.concat(RFB.messages.fbUpdateRequest(1, cb.x, cb.y, w, h));
  1144. }
  1145. for (var i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
  1146. var db = cleanDirty.dirtyBoxes[i];
  1147. // Force all (non-incremental) for dirty box
  1148. w = typeof db.w === "undefined" ? fb_width : db.w;
  1149. h = typeof db.h === "undefined" ? fb_height : db.h;
  1150. arr = arr.concat(RFB.messages.fbUpdateRequest(0, db.x, db.y, w, h));
  1151. }
  1152. return arr;
  1153. },
  1154. fbUpdateRequest: function (incremental, x, y, w, h) {
  1155. if (typeof(x) === "undefined") { x = 0; }
  1156. if (typeof(y) === "undefined") { y = 0; }
  1157. var arr = [3]; // msg-type
  1158. arr.push8(incremental);
  1159. arr.push16(x);
  1160. arr.push16(y);
  1161. arr.push16(w);
  1162. arr.push16(h);
  1163. return arr;
  1164. }
  1165. };
  1166. RFB.genDES = function (password, challenge) {
  1167. var passwd = [];
  1168. for (var i = 0; i < password.length; i++) {
  1169. passwd.push(password.charCodeAt(i));
  1170. }
  1171. return (new DES(passwd)).encrypt(challenge);
  1172. };
  1173. RFB.extract_data_uri = function (arr) {
  1174. return ";base64," + Base64.encode(arr);
  1175. };
  1176. RFB.encodingHandlers = {
  1177. RAW: function () {
  1178. if (this._FBU.lines === 0) {
  1179. this._FBU.lines = this._FBU.height;
  1180. }
  1181. this._FBU.bytes = this._FBU.width * this._fb_Bpp; // at least a line
  1182. if (this._sock.rQwait("RAW", this._FBU.bytes)) { return false; }
  1183. var cur_y = this._FBU.y + (this._FBU.height - this._FBU.lines);
  1184. var curr_height = Math.min(this._FBU.lines,
  1185. Math.floor(this._sock.rQlen() / (this._FBU.width * this._fb_Bpp)));
  1186. this._display.blitImage(this._FBU.x, cur_y, this._FBU.width,
  1187. curr_height, this._sock.get_rQ(),
  1188. this._sock.get_rQi());
  1189. this._sock.rQskipBytes(this._FBU.width * curr_height * this._fb_Bpp);
  1190. this._FBU.lines -= curr_height;
  1191. if (this._FBU.lines > 0) {
  1192. this._FBU.bytes = this._FBU.width * this._fb_Bpp; // At least another line
  1193. } else {
  1194. this._FBU.rects--;
  1195. this._FBU.bytes = 0;
  1196. }
  1197. return true;
  1198. },
  1199. COPYRECT: function () {
  1200. this._FBU.bytes = 4;
  1201. if (this._sock.rQwait("COPYRECT", 4)) { return false; }
  1202. this._display.renderQ_push({
  1203. 'type': 'copy',
  1204. 'old_x': this._sock.rQshift16(),
  1205. 'old_y': this._sock.rQshift16(),
  1206. 'x': this._FBU.x,
  1207. 'y': this._FBU.y,
  1208. 'width': this._FBU.width,
  1209. 'height': this._FBU.height
  1210. });
  1211. this._FBU.rects--;
  1212. this._FBU.bytes = 0;
  1213. return true;
  1214. },
  1215. RRE: function () {
  1216. var color;
  1217. if (this._FBU.subrects === 0) {
  1218. this._FBU.bytes = 4 + this._fb_Bpp;
  1219. if (this._sock.rQwait("RRE", 4 + this._fb_Bpp)) { return false; }
  1220. this._FBU.subrects = this._sock.rQshift32();
  1221. color = this._sock.rQshiftBytes(this._fb_Bpp); // Background
  1222. this._display.fillRect(this._FBU.x, this._FBU.y, this._FBU.width, this._FBU.height, color);
  1223. }
  1224. while (this._FBU.subrects > 0 && this._sock.rQlen() >= (this._fb_Bpp + 8)) {
  1225. color = this._sock.rQshiftBytes(this._fb_Bpp);
  1226. var x = this._sock.rQshift16();
  1227. var y = this._sock.rQshift16();
  1228. var width = this._sock.rQshift16();
  1229. var height = this._sock.rQshift16();
  1230. this._display.fillRect(this._FBU.x + x, this._FBU.y + y, width, height, color);
  1231. this._FBU.subrects--;
  1232. }
  1233. if (this._FBU.subrects > 0) {
  1234. var chunk = Math.min(this._rre_chunk_sz, this._FBU.subrects);
  1235. this._FBU.bytes = (this._fb_Bpp + 8) * chunk;
  1236. } else {
  1237. this._FBU.rects--;
  1238. this._FBU.bytes = 0;
  1239. }
  1240. return true;
  1241. },
  1242. HEXTILE: function () {
  1243. var rQ = this._sock.get_rQ();
  1244. var rQi = this._sock.get_rQi();
  1245. if (this._FBU.tiles === 0) {
  1246. this._FBU.tiles_x = Math.ceil(this._FBU.width / 16);
  1247. this._FBU.tiles_y = Math.ceil(this._FBU.height / 16);
  1248. this._FBU.total_tiles = this._FBU.tiles_x * this._FBU.tiles_y;
  1249. this._FBU.tiles = this._FBU.total_tiles;
  1250. }
  1251. while (this._FBU.tiles > 0) {
  1252. this._FBU.bytes = 1;
  1253. if (this._sock.rQwait("HEXTILE subencoding", this._FBU.bytes)) { return false; }
  1254. var subencoding = rQ[rQi]; // Peek
  1255. if (subencoding > 30) { // Raw
  1256. this._fail("Disconnected: illegal hextile subencoding " + subencoding);
  1257. return false;
  1258. }
  1259. var subrects = 0;
  1260. var curr_tile = this._FBU.total_tiles - this._FBU.tiles;
  1261. var tile_x = curr_tile % this._FBU.tiles_x;
  1262. var tile_y = Math.floor(curr_tile / this._FBU.tiles_x);
  1263. var x = this._FBU.x + tile_x * 16;
  1264. var y = this._FBU.y + tile_y * 16;
  1265. var w = Math.min(16, (this._FBU.x + this._FBU.width) - x);
  1266. var h = Math.min(16, (this._FBU.y + this._FBU.height) - y);
  1267. // Figure out how much we are expecting
  1268. if (subencoding & 0x01) { // Raw
  1269. this._FBU.bytes += w * h * this._fb_Bpp;
  1270. } else {
  1271. if (subencoding & 0x02) { // Background
  1272. this._FBU.bytes += this._fb_Bpp;
  1273. }
  1274. if (subencoding & 0x04) { // Foreground
  1275. this._FBU.bytes += this._fb_Bpp;
  1276. }
  1277. if (subencoding & 0x08) { // AnySubrects
  1278. this._FBU.bytes++; // Since we aren't shifting it off
  1279. if (this._sock.rQwait("hextile subrects header", this._FBU.bytes)) { return false; }
  1280. subrects = rQ[rQi + this._FBU.bytes - 1]; // Peek
  1281. if (subencoding & 0x10) { // SubrectsColoured
  1282. this._FBU.bytes += subrects * (this._fb_Bpp + 2);
  1283. } else {
  1284. this._FBU.bytes += subrects * 2;
  1285. }
  1286. }
  1287. }
  1288. if (this._sock.rQwait("hextile", this._FBU.bytes)) { return false; }
  1289. // We know the encoding and have a whole tile
  1290. this._FBU.subencoding = rQ[rQi];
  1291. rQi++;
  1292. if (this._FBU.subencoding === 0) {
  1293. if (this._FBU.lastsubencoding & 0x01) {
  1294. // Weird: ignore blanks are RAW
  1295. Util.Debug(" Ignoring blank after RAW");
  1296. } else {
  1297. this._display.fillRect(x, y, w, h, this._FBU.background);
  1298. }
  1299. } else if (this._FBU.subencoding & 0x01) { // Raw
  1300. this._display.blitImage(x, y, w, h, rQ, rQi);
  1301. rQi += this._FBU.bytes - 1;
  1302. } else {
  1303. if (this._FBU.subencoding & 0x02) { // Background
  1304. this._FBU.background = rQ.slice(rQi, rQi + this._fb_Bpp);
  1305. rQi += this._fb_Bpp;
  1306. }
  1307. if (this._FBU.subencoding & 0x04) { // Foreground
  1308. this._FBU.foreground = rQ.slice(rQi, rQi + this._fb_Bpp);
  1309. rQi += this._fb_Bpp;
  1310. }
  1311. this._display.startTile(x, y, w, h, this._FBU.background);
  1312. if (this._FBU.subencoding & 0x08) { // AnySubrects
  1313. subrects = rQ[rQi];
  1314. rQi++;
  1315. for (var s = 0; s < subrects; s++) {
  1316. var color;
  1317. if (this._FBU.subencoding & 0x10) { // SubrectsColoured
  1318. color = rQ.slice(rQi, rQi + this._fb_Bpp);
  1319. rQi += this._fb_Bpp;
  1320. } else {
  1321. color = this._FBU.foreground;
  1322. }
  1323. var xy = rQ[rQi];
  1324. rQi++;
  1325. var sx = (xy >> 4);
  1326. var sy = (xy & 0x0f);
  1327. var wh = rQ[rQi];
  1328. rQi++;
  1329. var sw = (wh >> 4) + 1;
  1330. var sh = (wh & 0x0f) + 1;
  1331. this._display.subTile(sx, sy, sw, sh, color);
  1332. }
  1333. }
  1334. this._display.finishTile();
  1335. }
  1336. this._sock.set_rQi(rQi);
  1337. this._FBU.lastsubencoding = this._FBU.subencoding;
  1338. this._FBU.bytes = 0;
  1339. this._FBU.tiles--;
  1340. }
  1341. if (this._FBU.tiles === 0) {
  1342. this._FBU.rects--;
  1343. }
  1344. return true;
  1345. },
  1346. getTightCLength: function (arr) {
  1347. var header = 1, data = 0;
  1348. data += arr[0] & 0x7f;
  1349. if (arr[0] & 0x80) {
  1350. header++;
  1351. data += (arr[1] & 0x7f) << 7;
  1352. if (arr[1] & 0x80) {
  1353. header++;
  1354. data += arr[2] << 14;
  1355. }
  1356. }
  1357. return [header, data];
  1358. },
  1359. display_tight: function (isTightPNG) {
  1360. if (this._fb_depth === 1) {
  1361. this._fail("Tight protocol handler only implements true color mode");
  1362. }
  1363. this._FBU.bytes = 1; // compression-control byte
  1364. if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
  1365. var checksum = function (data) {
  1366. var sum = 0;
  1367. for (var i = 0; i < data.length; i++) {
  1368. sum += data[i];
  1369. if (sum > 65536) sum -= 65536;
  1370. }
  1371. return sum;
  1372. };
  1373. var resetStreams = 0;
  1374. var streamId = -1;
  1375. var decompress = function (data) {
  1376. for (var i = 0; i < 4; i++) {
  1377. if ((resetStreams >> i) & 1) {
  1378. this._FBU.zlibs[i].reset();
  1379. Util.Info("Reset zlib stream " + i);
  1380. }
  1381. }
  1382. var uncompressed = this._FBU.zlibs[streamId].uncompress(data, 0);
  1383. if (uncompressed.status !== 0) {
  1384. Util.Error("Invalid data in zlib stream");
  1385. }
  1386. return uncompressed.data;
  1387. }.bind(this);
  1388. var indexedToRGB = function (data, numColors, palette, width, height) {
  1389. // Convert indexed (palette based) image data to RGB
  1390. // TODO: reduce number of calculations inside loop
  1391. var dest = [];
  1392. var x, y, dp, sp;
  1393. if (numColors === 2) {
  1394. var w = Math.floor((width + 7) / 8);
  1395. var w1 = Math.floor(width / 8);
  1396. for (y = 0; y < height; y++) {
  1397. var b;
  1398. for (x = 0; x < w1; x++) {
  1399. for (b = 7; b >= 0; b--) {
  1400. dp = (y * width + x * 8 + 7 - b) * 3;
  1401. sp = (data[y * w + x] >> b & 1) * 3;
  1402. dest[dp] = palette[sp];
  1403. dest[dp + 1] = palette[sp + 1];
  1404. dest[dp + 2] = palette[sp + 2];
  1405. }
  1406. }
  1407. for (b = 7; b >= 8 - width % 8; b--) {
  1408. dp = (y * width + x * 8 + 7 - b) * 3;
  1409. sp = (data[y * w + x] >> b & 1) * 3;
  1410. dest[dp] = palette[sp];
  1411. dest[dp + 1] = palette[sp + 1];
  1412. dest[dp + 2] = palette[sp + 2];
  1413. }
  1414. }
  1415. } else {
  1416. for (y = 0; y < height; y++) {
  1417. for (x = 0; x < width; x++) {
  1418. dp = (y * width + x) * 3;
  1419. sp = data[y * width + x] * 3;
  1420. dest[dp] = palette[sp];
  1421. dest[dp + 1] = palette[sp + 1];
  1422. dest[dp + 2] = palette[sp + 2];
  1423. }
  1424. }
  1425. }
  1426. return dest;
  1427. }.bind(this);
  1428. var rQ = this._sock.get_rQ();
  1429. var rQi = this._sock.get_rQi();
  1430. var cmode, clength, data;
  1431. var handlePalette = function () {
  1432. var numColors = rQ[rQi + 2] + 1;
  1433. var paletteSize = numColors * this._fb_depth;
  1434. this._FBU.bytes += paletteSize;
  1435. if (this._sock.rQwait("TIGHT palette " + cmode, this._FBU.bytes)) { return false; }
  1436. var bpp = (numColors <= 2) ? 1 : 8;
  1437. var rowSize = Math.floor((this._FBU.width * bpp + 7) / 8);
  1438. var raw = false;
  1439. if (rowSize * this._FBU.height < 12) {
  1440. raw = true;
  1441. clength = [0, rowSize * this._FBU.height];
  1442. } else {
  1443. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(3 + paletteSize,
  1444. 3 + paletteSize + 3));
  1445. }
  1446. this._FBU.bytes += clength[0] + clength[1];
  1447. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1448. // Shift ctl, filter id, num colors, palette entries, and clength off
  1449. this._sock.rQskipBytes(3);
  1450. var palette = this._sock.rQshiftBytes(paletteSize);
  1451. this._sock.rQskipBytes(clength[0]);
  1452. if (raw) {
  1453. data = this._sock.rQshiftBytes(clength[1]);
  1454. } else {
  1455. data = decompress(this._sock.rQshiftBytes(clength[1]));
  1456. }
  1457. // Convert indexed (palette based) image data to RGB
  1458. var rgb = indexedToRGB(data, numColors, palette, this._FBU.width, this._FBU.height);
  1459. this._display.renderQ_push({
  1460. 'type': 'blitRgb',
  1461. 'data': rgb,
  1462. 'x': this._FBU.x,
  1463. 'y': this._FBU.y,
  1464. 'width': this._FBU.width,
  1465. 'height': this._FBU.height
  1466. });
  1467. return true;
  1468. }.bind(this);
  1469. var handleCopy = function () {
  1470. var raw = false;
  1471. var uncompressedSize = this._FBU.width * this._FBU.height * this._fb_depth;
  1472. if (uncompressedSize < 12) {
  1473. raw = true;
  1474. clength = [0, uncompressedSize];
  1475. } else {
  1476. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
  1477. }
  1478. this._FBU.bytes = 1 + clength[0] + clength[1];
  1479. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1480. // Shift ctl, clength off
  1481. this._sock.rQshiftBytes(1 + clength[0]);
  1482. if (raw) {
  1483. data = this._sock.rQshiftBytes(clength[1]);
  1484. } else {
  1485. data = decompress(this._sock.rQshiftBytes(clength[1]));
  1486. }
  1487. this._display.renderQ_push({
  1488. 'type': 'blitRgb',
  1489. 'data': data,
  1490. 'x': this._FBU.x,
  1491. 'y': this._FBU.y,
  1492. 'width': this._FBU.width,
  1493. 'height': this._FBU.height
  1494. });
  1495. return true;
  1496. }.bind(this);
  1497. var ctl = this._sock.rQpeek8();
  1498. // Keep tight reset bits
  1499. resetStreams = ctl & 0xF;
  1500. // Figure out filter
  1501. ctl = ctl >> 4;
  1502. streamId = ctl & 0x3;
  1503. if (ctl === 0x08) cmode = "fill";
  1504. else if (ctl === 0x09) cmode = "jpeg";
  1505. else if (ctl === 0x0A) cmode = "png";
  1506. else if (ctl & 0x04) cmode = "filter";
  1507. else if (ctl < 0x04) cmode = "copy";
  1508. else return this._fail("Illegal tight compression received, ctl: " + ctl);
  1509. if (isTightPNG && (cmode === "filter" || cmode === "copy")) {
  1510. return this._fail("filter/copy received in tightPNG mode");
  1511. }
  1512. switch (cmode) {
  1513. // fill use fb_depth because TPIXELs drop the padding byte
  1514. case "fill": // TPIXEL
  1515. this._FBU.bytes += this._fb_depth;
  1516. break;
  1517. case "jpeg": // max clength
  1518. this._FBU.bytes += 3;
  1519. break;
  1520. case "png": // max clength
  1521. this._FBU.bytes += 3;
  1522. break;
  1523. case "filter": // filter id + num colors if palette
  1524. this._FBU.bytes += 2;
  1525. break;
  1526. case "copy":
  1527. break;
  1528. }
  1529. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1530. // Determine FBU.bytes
  1531. switch (cmode) {
  1532. case "fill":
  1533. this._sock.rQskip8(); // shift off ctl
  1534. var color = this._sock.rQshiftBytes(this._fb_depth);
  1535. this._display.renderQ_push({
  1536. 'type': 'fill',
  1537. 'x': this._FBU.x,
  1538. 'y': this._FBU.y,
  1539. 'width': this._FBU.width,
  1540. 'height': this._FBU.height,
  1541. 'color': [color[2], color[1], color[0]]
  1542. });
  1543. break;
  1544. case "png":
  1545. case "jpeg":
  1546. clength = RFB.encodingHandlers.getTightCLength(this._sock.rQslice(1, 4));
  1547. this._FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
  1548. if (this._sock.rQwait("TIGHT " + cmode, this._FBU.bytes)) { return false; }
  1549. // We have everything, render it
  1550. this._sock.rQskipBytes(1 + clength[0]); // shift off clt + compact length
  1551. var img = new Image();
  1552. img.src = "data: image/" + cmode +
  1553. RFB.extract_data_uri(this._sock.rQshiftBytes(clength[1]));
  1554. this._display.renderQ_push({
  1555. 'type': 'img',
  1556. 'img': img,
  1557. 'x': this._FBU.x,
  1558. 'y': this._FBU.y
  1559. });
  1560. img = null;
  1561. break;
  1562. case "filter":
  1563. var filterId = rQ[rQi + 1];
  1564. if (filterId === 1) {
  1565. if (!handlePalette()) { return false; }
  1566. } else {
  1567. // Filter 0, Copy could be valid here, but servers don't send it as an explicit filter
  1568. // Filter 2, Gradient is valid but not use if jpeg is enabled
  1569. // TODO(directxman12): why aren't we just calling '_fail' here
  1570. throw new Error("Unsupported tight subencoding received, filter: " + filterId);
  1571. }
  1572. break;
  1573. case "copy":
  1574. if (!handleCopy()) { return false; }
  1575. break;
  1576. }
  1577. this._FBU.bytes = 0;
  1578. this._FBU.rects--;
  1579. return true;
  1580. },
  1581. TIGHT: function () { return this._encHandlers.display_tight(false); },
  1582. TIGHT_PNG: function () { return this._encHandlers.display_tight(true); },
  1583. last_rect: function () {
  1584. this._FBU.rects = 0;
  1585. return true;
  1586. },
  1587. handle_FB_resize: function () {
  1588. this._fb_width = this._FBU.width;
  1589. this._fb_height = this._FBU.height;
  1590. this._display.resize(this._fb_width, this._fb_height);
  1591. this._onFBResize(this, this._fb_width, this._fb_height);
  1592. this._timing.fbu_rt_start = (new Date()).getTime();
  1593. this._FBU.bytes = 0;
  1594. this._FBU.rects -= 1;
  1595. return true;
  1596. },
  1597. ExtendedDesktopSize: function () {
  1598. this._FBU.bytes = 1;
  1599. if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
  1600. this._supportsSetDesktopSize = true;
  1601. var number_of_screens = this._sock.rQpeek8();
  1602. this._FBU.bytes = 4 + (number_of_screens * 16);
  1603. if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
  1604. this._sock.rQskipBytes(1); // number-of-screens
  1605. this._sock.rQskipBytes(3); // padding
  1606. for (var i=0; i<number_of_screens; i += 1) {
  1607. // Save the id and flags of the first screen
  1608. if (i == 0) {
  1609. this._screen_id = this._sock.rQshiftBytes(4); // id
  1610. this._sock.rQskipBytes(2); // x-position
  1611. this._sock.rQskipBytes(2); // y-position
  1612. this._sock.rQskipBytes(2); // width
  1613. this._sock.rQskipBytes(2); // height
  1614. this._screen_flags = this._sock.rQshiftBytes(4); // flags
  1615. } else {
  1616. this._sock.rQskipBytes(16);
  1617. }
  1618. }
  1619. /*
  1620. * The x-position indicates the reason for the change:
  1621. *
  1622. * 0 - server resized on its own
  1623. * 1 - this client requested the resize
  1624. * 2 - another client requested the resize
  1625. */
  1626. // We need to handle errors when we requested the resize.
  1627. if (this._FBU.x == 1 && this._FBU.y != 0) {
  1628. var msg = "";
  1629. // The y-position indicates the status code from the server
  1630. switch (this._FBU.y) {
  1631. case 1:
  1632. msg = "Resize is administratively prohibited";
  1633. break;
  1634. case 2:
  1635. msg = "Out of resources";
  1636. break;
  1637. case 3:
  1638. msg = "Invalid screen layout";
  1639. break;
  1640. default:
  1641. msg = "Unknown reason";
  1642. break;
  1643. }
  1644. Util.Info("Server did not accept the resize request: " + msg);
  1645. return true;
  1646. }
  1647. this._encHandlers.handle_FB_resize();
  1648. return true;
  1649. },
  1650. DesktopSize: function () {
  1651. this._encHandlers.handle_FB_resize();
  1652. return true;
  1653. },
  1654. Cursor: function () {
  1655. Util.Debug(">> set_cursor");
  1656. var x = this._FBU.x; // hotspot-x
  1657. var y = this._FBU.y; // hotspot-y
  1658. var w = this._FBU.width;
  1659. var h = this._FBU.height;
  1660. var pixelslength = w * h * this._fb_Bpp;
  1661. var masklength = Math.floor((w + 7) / 8) * h;
  1662. this._FBU.bytes = pixelslength + masklength;
  1663. if (this._sock.rQwait("cursor encoding", this._FBU.bytes)) { return false; }
  1664. this._display.changeCursor(this._sock.rQshiftBytes(pixelslength),
  1665. this._sock.rQshiftBytes(masklength),
  1666. x, y, w, h);
  1667. this._FBU.bytes = 0;
  1668. this._FBU.rects--;
  1669. Util.Debug("<< set_cursor");
  1670. return true;
  1671. },
  1672. JPEG_quality_lo: function () {
  1673. Util.Error("Server sent jpeg_quality pseudo-encoding");
  1674. },
  1675. compress_lo: function () {
  1676. Util.Error("Server sent compress level pseudo-encoding");
  1677. }
  1678. };
  1679. })();