rfb.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2011 Joel Martin
  4. * Licensed under LGPL-3 (see LICENSE.txt)
  5. *
  6. * See README.md for usage and integration instructions.
  7. */
  8. /*jslint white: false, browser: true, bitwise: false, plusplus: false */
  9. /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, noVNC_logo */
  10. function RFB(conf) {
  11. "use strict";
  12. conf = conf || {}; // Configuration
  13. var that = {}, // Public API interface
  14. // Pre-declare private functions used before definitions (jslint)
  15. init_vars, updateState, fail, handle_message,
  16. init_msg, normal_msg, framebufferUpdate, print_stats,
  17. pixelFormat, clientEncodings, fbUpdateRequest,
  18. keyEvent, pointerEvent, clientCutText,
  19. extract_data_uri, scan_tight_imgQ,
  20. keyPress, mouseButton, mouseMove,
  21. checkEvents, // Overridable for testing
  22. //
  23. // Private RFB namespace variables
  24. //
  25. rfb_host = '',
  26. rfb_port = 5900,
  27. rfb_password = '',
  28. rfb_state = 'disconnected',
  29. rfb_version = 0,
  30. rfb_max_version= 3.8,
  31. rfb_auth_scheme= '',
  32. // In preference order
  33. encodings = [
  34. ['COPYRECT', 0x01 ],
  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_hi', -23 ],
  44. ['compress_lo', -255 ]
  45. //['compress_hi', -247 ]
  46. ],
  47. encHandlers = {},
  48. encNames = {},
  49. encStats = {}, // [rectCnt, rectCntTot]
  50. ws = null, // Websock object
  51. display = null, // Display object
  52. keyboard = null, // Keyboard input handler object
  53. mouse = null, // Mouse input handler object
  54. sendTimer = null, // Send Queue check timer
  55. connTimer = null, // connection timer
  56. disconnTimer = null, // disconnection timer
  57. msgTimer = null, // queued handle_message timer
  58. // Frame buffer update state
  59. FBU = {
  60. rects : 0,
  61. subrects : 0, // RRE
  62. lines : 0, // RAW
  63. tiles : 0, // HEXTILE
  64. bytes : 0,
  65. x : 0,
  66. y : 0,
  67. width : 0,
  68. height : 0,
  69. encoding : 0,
  70. subencoding : -1,
  71. background : null,
  72. imgQ : [] // TIGHT_PNG image queue
  73. },
  74. fb_Bpp = 4,
  75. fb_depth = 3,
  76. fb_width = 0,
  77. fb_height = 0,
  78. fb_name = "",
  79. scan_imgQ_rate = 100,
  80. last_req_time = 0,
  81. rre_chunk_sz = 100,
  82. timing = {
  83. last_fbu : 0,
  84. fbu_total : 0,
  85. fbu_total_cnt : 0,
  86. full_fbu_total : 0,
  87. full_fbu_cnt : 0,
  88. fbu_rt_start : 0,
  89. fbu_rt_total : 0,
  90. fbu_rt_cnt : 0
  91. },
  92. test_mode = false,
  93. /* Mouse state */
  94. mouse_buttonMask = 0,
  95. mouse_arr = [];
  96. //
  97. // Configuration settings
  98. //
  99. function cdef(v, type, defval, desc) {
  100. Util.conf_default(conf, that, v, type, defval, desc); }
  101. cdef('target', 'dom', null, 'VNC display rendering Canvas object');
  102. cdef('focusContainer', 'dom', document, 'DOM element that captures keyboard input');
  103. cdef('encrypt', 'bool', false, 'Use TLS/SSL/wss encryption');
  104. cdef('true_color', 'bool', true, 'Request true color pixel data');
  105. cdef('local_cursor', 'bool', false, 'Request locally rendered cursor');
  106. cdef('shared', 'bool', true, 'Request shared mode');
  107. if (Websock_native) {
  108. cdef('connectTimeout', 'int', 2, 'Time (s) to wait for connection');
  109. } else {
  110. cdef('connectTimeout', 'int', 5, 'Time (s) to wait for connection');
  111. }
  112. cdef('disconnectTimeout', 'int', 3, 'Time (s) to wait for disconnection');
  113. cdef('check_rate', 'int', 217, 'Timing (ms) of send/receive check');
  114. cdef('fbu_req_rate', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests');
  115. // Callback functions
  116. cdef('onUpdateState', 'func', function() { },
  117. 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change ');
  118. cdef('onPasswordRequired', 'func', function() { },
  119. 'onPasswordRequired(rfb): VNC password is required ');
  120. cdef('onClipboard', 'func', function() { },
  121. 'onClipboard(rfb, text): RFB clipboard contents received');
  122. cdef('onBell', 'func', function() { },
  123. 'onBell(rfb): RFB Bell message received ');
  124. cdef('onFBUReceive', 'func', function() { },
  125. 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed ');
  126. cdef('onFBUComplete', 'func', function() { },
  127. 'onFBUComplete(rfb, fbu): RFB FBU received and processed ');
  128. // These callback names are deprecated
  129. cdef('updateState', 'func', function() { },
  130. 'obsolete, use onUpdateState');
  131. cdef('clipboardReceive', 'func', function() { },
  132. 'obsolete, use onClipboard');
  133. // Override/add some specific getters/setters
  134. that.set_local_cursor = function(cursor) {
  135. if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
  136. conf.local_cursor = false;
  137. } else {
  138. if (display.get_cursor_uri()) {
  139. conf.local_cursor = true;
  140. } else {
  141. Util.Warn("Browser does not support local cursor");
  142. }
  143. }
  144. };
  145. that.get_display = function() {
  146. return display;
  147. };
  148. that.get_keyboard = function() {
  149. return keyboard;
  150. };
  151. that.get_mouse = function() {
  152. return mouse;
  153. };
  154. //
  155. // Setup routines
  156. //
  157. // Create the public API interface and initialize values that stay
  158. // constant across connect/disconnect
  159. function constructor() {
  160. var i, rmode;
  161. Util.Debug(">> RFB.constructor");
  162. // Create lookup tables based encoding number
  163. for (i=0; i < encodings.length; i+=1) {
  164. encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
  165. encNames[encodings[i][1]] = encodings[i][0];
  166. encStats[encodings[i][1]] = [0, 0];
  167. }
  168. // Initialize display, mouse, keyboard, and websock
  169. try {
  170. display = new Display({'target': conf.target});
  171. } catch (exc) {
  172. Util.Error("Display exception: " + exc);
  173. updateState('fatal', "No working Display");
  174. }
  175. keyboard = new Keyboard({'target': conf.focusContainer,
  176. 'onKeyPress': keyPress});
  177. mouse = new Mouse({'target': conf.target,
  178. 'onMouseButton': mouseButton,
  179. 'onMouseMove': mouseMove});
  180. rmode = display.get_render_mode();
  181. if (typeof noVNC_logo !== 'undefined') {
  182. display.set_logo(noVNC_logo);
  183. }
  184. ws = new Websock();
  185. ws.on('message', handle_message);
  186. ws.on('open', function() {
  187. if (rfb_state === "connect") {
  188. updateState('ProtocolVersion', "Starting VNC handshake");
  189. } else {
  190. fail("Got unexpected WebSockets connection");
  191. }
  192. });
  193. ws.on('close', function() {
  194. if (rfb_state === 'disconnect') {
  195. updateState('disconnected', 'VNC disconnected');
  196. } else if (rfb_state === 'ProtocolVersion') {
  197. fail('Failed to connect to server');
  198. } else if (rfb_state in {'failed':1, 'disconnected':1}) {
  199. Util.Error("Received onclose while disconnected");
  200. } else {
  201. fail('Server disconnected');
  202. }
  203. });
  204. ws.on('error', function(e) {
  205. fail("WebSock error: " + e);
  206. });
  207. init_vars();
  208. /* Check web-socket-js if no builtin WebSocket support */
  209. if (Websock_native) {
  210. Util.Info("Using native WebSockets");
  211. updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
  212. } else {
  213. Util.Warn("Using web-socket-js bridge. Flash version: " +
  214. Util.Flash.version);
  215. if ((! Util.Flash) ||
  216. (Util.Flash.version < 9)) {
  217. updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
  218. } else if (document.location.href.substr(0, 7) === "file://") {
  219. updateState('fatal',
  220. "'file://' URL is incompatible with Adobe Flash");
  221. } else {
  222. updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
  223. }
  224. }
  225. Util.Debug("<< RFB.constructor");
  226. return that; // Return the public API interface
  227. }
  228. function connect() {
  229. Util.Debug(">> RFB.connect");
  230. var uri = "";
  231. if (conf.encrypt) {
  232. uri = "wss://";
  233. } else {
  234. uri = "ws://";
  235. }
  236. uri += rfb_host + ":" + rfb_port + "/";
  237. Util.Info("connecting to " + uri);
  238. ws.open(uri);
  239. Util.Debug("<< RFB.connect");
  240. }
  241. // Initialize variables that are reset before each connection
  242. init_vars = function() {
  243. var i;
  244. /* Reset state */
  245. ws.init();
  246. FBU.rects = 0;
  247. FBU.subrects = 0; // RRE and HEXTILE
  248. FBU.lines = 0; // RAW
  249. FBU.tiles = 0; // HEXTILE
  250. FBU.imgQ = []; // TIGHT_PNG image queue
  251. mouse_buttonMask = 0;
  252. mouse_arr = [];
  253. // Clear the per connection encoding stats
  254. for (i=0; i < encodings.length; i+=1) {
  255. encStats[encodings[i][1]][0] = 0;
  256. }
  257. };
  258. // Print statistics
  259. print_stats = function() {
  260. var i, s;
  261. Util.Info("Encoding stats for this connection:");
  262. for (i=0; i < encodings.length; i+=1) {
  263. s = encStats[encodings[i][1]];
  264. if ((s[0] + s[1]) > 0) {
  265. Util.Info(" " + encodings[i][0] + ": " +
  266. s[0] + " rects");
  267. }
  268. }
  269. Util.Info("Encoding stats since page load:");
  270. for (i=0; i < encodings.length; i+=1) {
  271. s = encStats[encodings[i][1]];
  272. if ((s[0] + s[1]) > 0) {
  273. Util.Info(" " + encodings[i][0] + ": " +
  274. s[1] + " rects");
  275. }
  276. }
  277. };
  278. //
  279. // Utility routines
  280. //
  281. /*
  282. * Page states:
  283. * loaded - page load, equivalent to disconnected
  284. * disconnected - idle state
  285. * connect - starting to connect (to ProtocolVersion)
  286. * normal - connected
  287. * disconnect - starting to disconnect
  288. * failed - abnormal disconnect
  289. * fatal - failed to load page, or fatal error
  290. *
  291. * RFB protocol initialization states:
  292. * ProtocolVersion
  293. * Security
  294. * Authentication
  295. * password - waiting for password, not part of RFB
  296. * SecurityResult
  297. * ClientInitialization - not triggered by server message
  298. * ServerInitialization (to normal)
  299. */
  300. updateState = function(state, statusMsg) {
  301. var func, cmsg, oldstate = rfb_state;
  302. if (state === oldstate) {
  303. /* Already here, ignore */
  304. Util.Debug("Already in state '" + state + "', ignoring.");
  305. return;
  306. }
  307. /*
  308. * These are disconnected states. A previous connect may
  309. * asynchronously cause a connection so make sure we are closed.
  310. */
  311. if (state in {'disconnected':1, 'loaded':1, 'connect':1,
  312. 'disconnect':1, 'failed':1, 'fatal':1}) {
  313. if (sendTimer) {
  314. clearInterval(sendTimer);
  315. sendTimer = null;
  316. }
  317. if (msgTimer) {
  318. clearInterval(msgTimer);
  319. msgTimer = null;
  320. }
  321. if (display && display.get_context()) {
  322. keyboard.ungrab();
  323. mouse.ungrab();
  324. display.defaultCursor();
  325. if ((Util.get_logging() !== 'debug') ||
  326. (state === 'loaded')) {
  327. // Show noVNC logo on load and when disconnected if
  328. // debug is off
  329. display.clear();
  330. }
  331. }
  332. ws.close();
  333. }
  334. if (oldstate === 'fatal') {
  335. Util.Error("Fatal error, cannot continue");
  336. }
  337. if ((state === 'failed') || (state === 'fatal')) {
  338. func = Util.Error;
  339. } else {
  340. func = Util.Warn;
  341. }
  342. if ((oldstate === 'failed') && (state === 'disconnected')) {
  343. // Do disconnect action, but stay in failed state.
  344. rfb_state = 'failed';
  345. } else {
  346. rfb_state = state;
  347. }
  348. cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
  349. func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
  350. if (connTimer && (rfb_state !== 'connect')) {
  351. Util.Debug("Clearing connect timer");
  352. clearInterval(connTimer);
  353. connTimer = null;
  354. }
  355. if (disconnTimer && (rfb_state !== 'disconnect')) {
  356. Util.Debug("Clearing disconnect timer");
  357. clearInterval(disconnTimer);
  358. disconnTimer = null;
  359. }
  360. switch (state) {
  361. case 'normal':
  362. if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
  363. Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
  364. }
  365. break;
  366. case 'connect':
  367. connTimer = setTimeout(function () {
  368. fail("Connect timeout");
  369. }, conf.connectTimeout * 1000);
  370. init_vars();
  371. connect();
  372. // WebSocket.onopen transitions to 'ProtocolVersion'
  373. break;
  374. case 'disconnect':
  375. if (! test_mode) {
  376. disconnTimer = setTimeout(function () {
  377. fail("Disconnect timeout");
  378. }, conf.disconnectTimeout * 1000);
  379. }
  380. print_stats();
  381. // WebSocket.onclose transitions to 'disconnected'
  382. break;
  383. case 'failed':
  384. if (oldstate === 'disconnected') {
  385. Util.Error("Invalid transition from 'disconnected' to 'failed'");
  386. }
  387. if (oldstate === 'normal') {
  388. Util.Error("Error while connected.");
  389. }
  390. if (oldstate === 'init') {
  391. Util.Error("Error while initializing.");
  392. }
  393. // Make sure we transition to disconnected
  394. setTimeout(function() { updateState('disconnected'); }, 50);
  395. break;
  396. default:
  397. // No state change action to take
  398. }
  399. if ((oldstate === 'failed') && (state === 'disconnected')) {
  400. // Leave the failed message
  401. conf.updateState(that, state, oldstate); // Obsolete
  402. conf.onUpdateState(that, state, oldstate);
  403. } else {
  404. conf.updateState(that, state, oldstate, statusMsg); // Obsolete
  405. conf.onUpdateState(that, state, oldstate, statusMsg);
  406. }
  407. };
  408. fail = function(msg) {
  409. updateState('failed', msg);
  410. return false;
  411. };
  412. handle_message = function() {
  413. //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
  414. //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
  415. if (ws.rQlen() === 0) {
  416. Util.Warn("handle_message called on empty receive queue");
  417. return;
  418. }
  419. switch (rfb_state) {
  420. case 'disconnected':
  421. case 'failed':
  422. Util.Error("Got data while disconnected");
  423. break;
  424. case 'normal':
  425. if (normal_msg() && ws.rQlen() > 0) {
  426. // true means we can continue processing
  427. // Give other events a chance to run
  428. if (msgTimer === null) {
  429. Util.Debug("More data to process, creating timer");
  430. msgTimer = setTimeout(function () {
  431. msgTimer = null;
  432. handle_message();
  433. }, 10);
  434. } else {
  435. Util.Debug("More data to process, existing timer");
  436. }
  437. }
  438. break;
  439. default:
  440. init_msg();
  441. break;
  442. }
  443. };
  444. function genDES(password, challenge) {
  445. var i, passwd = [];
  446. for (i=0; i < password.length; i += 1) {
  447. passwd.push(password.charCodeAt(i));
  448. }
  449. return (new DES(passwd)).encrypt(challenge);
  450. }
  451. function flushClient() {
  452. if (mouse_arr.length > 0) {
  453. //send(mouse_arr.concat(fbUpdateRequest(1)));
  454. ws.send(mouse_arr);
  455. setTimeout(function() {
  456. ws.send(fbUpdateRequest(1));
  457. }, 50);
  458. mouse_arr = [];
  459. return true;
  460. } else {
  461. return false;
  462. }
  463. }
  464. // overridable for testing
  465. checkEvents = function() {
  466. var now;
  467. if (rfb_state === 'normal') {
  468. if (! flushClient()) {
  469. now = new Date().getTime();
  470. if (now > last_req_time + conf.fbu_req_rate) {
  471. last_req_time = now;
  472. ws.send(fbUpdateRequest(1));
  473. }
  474. }
  475. }
  476. setTimeout(checkEvents, conf.check_rate);
  477. };
  478. keyPress = function(keysym, down) {
  479. var arr;
  480. arr = keyEvent(keysym, down);
  481. arr = arr.concat(fbUpdateRequest(1));
  482. ws.send(arr);
  483. };
  484. mouseButton = function(x, y, down, bmask) {
  485. if (down) {
  486. mouse_buttonMask |= bmask;
  487. } else {
  488. mouse_buttonMask ^= bmask;
  489. }
  490. mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
  491. flushClient();
  492. };
  493. mouseMove = function(x, y) {
  494. //Util.Debug('>> mouseMove ' + x + "," + y);
  495. mouse_arr = mouse_arr.concat( pointerEvent(x, y) );
  496. };
  497. //
  498. // Server message handlers
  499. //
  500. // RFB/VNC initialisation message handler
  501. init_msg = function() {
  502. //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
  503. var strlen, reason, length, sversion, cversion,
  504. i, types, num_types, challenge, response, bpp, depth,
  505. big_endian, true_color, name_length;
  506. //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
  507. switch (rfb_state) {
  508. case 'ProtocolVersion' :
  509. if (ws.rQlen() < 12) {
  510. return fail("Incomplete protocol version");
  511. }
  512. sversion = ws.rQshiftStr(12).substr(4,7);
  513. Util.Info("Server ProtocolVersion: " + sversion);
  514. switch (sversion) {
  515. case "003.003": rfb_version = 3.3; break;
  516. case "003.006": rfb_version = 3.3; break; // UltraVNC
  517. case "003.007": rfb_version = 3.7; break;
  518. case "003.008": rfb_version = 3.8; break;
  519. default:
  520. return fail("Invalid server version " + sversion);
  521. }
  522. if (rfb_version > rfb_max_version) {
  523. rfb_version = rfb_max_version;
  524. }
  525. if (! test_mode) {
  526. sendTimer = setInterval(function() {
  527. // Send updates either at a rate of one update
  528. // every 50ms, or whatever slower rate the network
  529. // can handle.
  530. ws.flush();
  531. }, 50);
  532. }
  533. cversion = "00" + parseInt(rfb_version,10) +
  534. ".00" + ((rfb_version * 10) % 10);
  535. ws.send_string("RFB " + cversion + "\n");
  536. updateState('Security', "Sent ProtocolVersion: " + cversion);
  537. break;
  538. case 'Security' :
  539. if (rfb_version >= 3.7) {
  540. // Server sends supported list, client decides
  541. num_types = ws.rQshift8();
  542. if (ws.rQwait("security type", num_types, 1)) { return false; }
  543. if (num_types === 0) {
  544. strlen = ws.rQshift32();
  545. reason = ws.rQshiftStr(strlen);
  546. return fail("Security failure: " + reason);
  547. }
  548. rfb_auth_scheme = 0;
  549. types = ws.rQshiftBytes(num_types);
  550. Util.Debug("Server security types: " + types);
  551. for (i=0; i < types.length; i+=1) {
  552. if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
  553. rfb_auth_scheme = types[i];
  554. }
  555. }
  556. if (rfb_auth_scheme === 0) {
  557. return fail("Unsupported security types: " + types);
  558. }
  559. ws.send([rfb_auth_scheme]);
  560. } else {
  561. // Server decides
  562. if (ws.rQwait("security scheme", 4)) { return false; }
  563. rfb_auth_scheme = ws.rQshift32();
  564. }
  565. updateState('Authentication',
  566. "Authenticating using scheme: " + rfb_auth_scheme);
  567. init_msg(); // Recursive fallthrough (workaround JSLint complaint)
  568. break;
  569. // Triggered by fallthough, not by server message
  570. case 'Authentication' :
  571. //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
  572. switch (rfb_auth_scheme) {
  573. case 0: // connection failed
  574. if (ws.rQwait("auth reason", 4)) { return false; }
  575. strlen = ws.rQshift32();
  576. reason = ws.rQshiftStr(strlen);
  577. return fail("Auth failure: " + reason);
  578. case 1: // no authentication
  579. if (rfb_version >= 3.8) {
  580. updateState('SecurityResult');
  581. return;
  582. }
  583. // Fall through to ClientInitialisation
  584. break;
  585. case 2: // VNC authentication
  586. if (rfb_password.length === 0) {
  587. // Notify via both callbacks since it is kind of
  588. // a RFB state change and a UI interface issue.
  589. updateState('password', "Password Required");
  590. conf.onPasswordRequired(that);
  591. return;
  592. }
  593. if (ws.rQwait("auth challenge", 16)) { return false; }
  594. challenge = ws.rQshiftBytes(16);
  595. //Util.Debug("Password: " + rfb_password);
  596. //Util.Debug("Challenge: " + challenge +
  597. // " (" + challenge.length + ")");
  598. response = genDES(rfb_password, challenge);
  599. //Util.Debug("Response: " + response +
  600. // " (" + response.length + ")");
  601. //Util.Debug("Sending DES encrypted auth response");
  602. ws.send(response);
  603. updateState('SecurityResult');
  604. return;
  605. default:
  606. fail("Unsupported auth scheme: " + rfb_auth_scheme);
  607. return;
  608. }
  609. updateState('ClientInitialisation', "No auth required");
  610. init_msg(); // Recursive fallthrough (workaround JSLint complaint)
  611. break;
  612. case 'SecurityResult' :
  613. if (ws.rQwait("VNC auth response ", 4)) { return false; }
  614. switch (ws.rQshift32()) {
  615. case 0: // OK
  616. // Fall through to ClientInitialisation
  617. break;
  618. case 1: // failed
  619. if (rfb_version >= 3.8) {
  620. length = ws.rQshift32();
  621. if (ws.rQwait("SecurityResult reason", length, 8)) {
  622. return false;
  623. }
  624. reason = ws.rQshiftStr(length);
  625. fail(reason);
  626. } else {
  627. fail("Authentication failed");
  628. }
  629. return;
  630. case 2: // too-many
  631. return fail("Too many auth attempts");
  632. }
  633. updateState('ClientInitialisation', "Authentication OK");
  634. init_msg(); // Recursive fallthrough (workaround JSLint complaint)
  635. break;
  636. // Triggered by fallthough, not by server message
  637. case 'ClientInitialisation' :
  638. ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
  639. updateState('ServerInitialisation', "Authentication OK");
  640. break;
  641. case 'ServerInitialisation' :
  642. if (ws.rQwait("server initialization", 24)) { return false; }
  643. /* Screen size */
  644. fb_width = ws.rQshift16();
  645. fb_height = ws.rQshift16();
  646. /* PIXEL_FORMAT */
  647. bpp = ws.rQshift8();
  648. depth = ws.rQshift8();
  649. big_endian = ws.rQshift8();
  650. true_color = ws.rQshift8();
  651. Util.Info("Screen: " + fb_width + "x" + fb_height +
  652. ", bpp: " + bpp + ", depth: " + depth +
  653. ", big_endian: " + big_endian +
  654. ", true_color: " + true_color);
  655. /* Connection name/title */
  656. ws.rQshiftStr(12);
  657. name_length = ws.rQshift32();
  658. fb_name = ws.rQshiftStr(name_length);
  659. display.set_true_color(conf.true_color);
  660. display.resize(fb_width, fb_height);
  661. keyboard.grab();
  662. mouse.grab();
  663. if (conf.true_color) {
  664. fb_Bpp = 4;
  665. fb_depth = 3;
  666. } else {
  667. fb_Bpp = 1;
  668. fb_depth = 1;
  669. }
  670. response = pixelFormat();
  671. response = response.concat(clientEncodings());
  672. response = response.concat(fbUpdateRequest(0));
  673. timing.fbu_rt_start = (new Date()).getTime();
  674. ws.send(response);
  675. /* Start pushing/polling */
  676. setTimeout(checkEvents, conf.check_rate);
  677. setTimeout(scan_tight_imgQ, scan_imgQ_rate);
  678. if (conf.encrypt) {
  679. updateState('normal', "Connected (encrypted) to: " + fb_name);
  680. } else {
  681. updateState('normal', "Connected (unencrypted) to: " + fb_name);
  682. }
  683. break;
  684. }
  685. //Util.Debug("<< init_msg");
  686. };
  687. /* Normal RFB/VNC server message handler */
  688. normal_msg = function() {
  689. //Util.Debug(">> normal_msg");
  690. var ret = true, msg_type, length, text,
  691. c, first_colour, num_colours, red, green, blue;
  692. if (FBU.rects > 0) {
  693. msg_type = 0;
  694. } else {
  695. msg_type = ws.rQshift8();
  696. }
  697. switch (msg_type) {
  698. case 0: // FramebufferUpdate
  699. ret = framebufferUpdate(); // false means need more data
  700. break;
  701. case 1: // SetColourMapEntries
  702. Util.Debug("SetColourMapEntries");
  703. ws.rQshift8(); // Padding
  704. first_colour = ws.rQshift16(); // First colour
  705. num_colours = ws.rQshift16();
  706. for (c=0; c < num_colours; c+=1) {
  707. red = ws.rQshift16();
  708. //Util.Debug("red before: " + red);
  709. red = parseInt(red / 256, 10);
  710. //Util.Debug("red after: " + red);
  711. green = parseInt(ws.rQshift16() / 256, 10);
  712. blue = parseInt(ws.rQshift16() / 256, 10);
  713. Util.Debug("*** colourMap: " + display.get_colourMap());
  714. display.set_colourMap([red, green, blue], first_colour + c);
  715. }
  716. Util.Info("Registered " + num_colours + " colourMap entries");
  717. //Util.Debug("colourMap: " + display.get_colourMap());
  718. break;
  719. case 2: // Bell
  720. Util.Debug("Bell");
  721. conf.onBell(that);
  722. break;
  723. case 3: // ServerCutText
  724. Util.Debug("ServerCutText");
  725. if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
  726. ws.rQshiftBytes(3); // Padding
  727. length = ws.rQshift32();
  728. if (ws.rQwait("ServerCutText", length, 8)) { return false; }
  729. text = ws.rQshiftStr(length);
  730. conf.clipboardReceive(that, text); // Obsolete
  731. conf.onClipboard(that, text);
  732. break;
  733. default:
  734. fail("Disconnected: illegal server message type " + msg_type);
  735. Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
  736. break;
  737. }
  738. //Util.Debug("<< normal_msg");
  739. return ret;
  740. };
  741. framebufferUpdate = function() {
  742. var now, hdr, fbu_rt_diff, ret = true;
  743. if (FBU.rects === 0) {
  744. //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
  745. if (ws.rQwait("FBU header", 3)) {
  746. ws.rQunshift8(0); // FBU msg_type
  747. return false;
  748. }
  749. ws.rQshift8(); // padding
  750. FBU.rects = ws.rQshift16();
  751. //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
  752. FBU.bytes = 0;
  753. timing.cur_fbu = 0;
  754. if (timing.fbu_rt_start > 0) {
  755. now = (new Date()).getTime();
  756. Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
  757. }
  758. }
  759. while (FBU.rects > 0) {
  760. if (rfb_state !== "normal") {
  761. return false;
  762. }
  763. if (ws.rQwait("FBU", FBU.bytes)) { return false; }
  764. if (FBU.bytes === 0) {
  765. if (ws.rQwait("rect header", 12)) { return false; }
  766. /* New FramebufferUpdate */
  767. hdr = ws.rQshiftBytes(12);
  768. FBU.x = (hdr[0] << 8) + hdr[1];
  769. FBU.y = (hdr[2] << 8) + hdr[3];
  770. FBU.width = (hdr[4] << 8) + hdr[5];
  771. FBU.height = (hdr[6] << 8) + hdr[7];
  772. FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
  773. (hdr[10] << 8) + hdr[11], 10);
  774. conf.onFBUReceive(that,
  775. {'x': FBU.x, 'y': FBU.y,
  776. 'width': FBU.width, 'height': FBU.height,
  777. 'encoding': FBU.encoding,
  778. 'encodingName': encNames[FBU.encoding]});
  779. if (encNames[FBU.encoding]) {
  780. // Debug:
  781. /*
  782. var msg = "FramebufferUpdate rects:" + FBU.rects;
  783. msg += " x: " + FBU.x + " y: " + FBU.y;
  784. msg += " width: " + FBU.width + " height: " + FBU.height;
  785. msg += " encoding:" + FBU.encoding;
  786. msg += "(" + encNames[FBU.encoding] + ")";
  787. msg += ", ws.rQlen(): " + ws.rQlen();
  788. Util.Debug(msg);
  789. */
  790. } else {
  791. fail("Disconnected: unsupported encoding " +
  792. FBU.encoding);
  793. return false;
  794. }
  795. }
  796. timing.last_fbu = (new Date()).getTime();
  797. ret = encHandlers[FBU.encoding]();
  798. now = (new Date()).getTime();
  799. timing.cur_fbu += (now - timing.last_fbu);
  800. if (ret) {
  801. encStats[FBU.encoding][0] += 1;
  802. encStats[FBU.encoding][1] += 1;
  803. }
  804. if (FBU.rects === 0) {
  805. if (((FBU.width === fb_width) &&
  806. (FBU.height === fb_height)) ||
  807. (timing.fbu_rt_start > 0)) {
  808. timing.full_fbu_total += timing.cur_fbu;
  809. timing.full_fbu_cnt += 1;
  810. Util.Info("Timing of full FBU, cur: " +
  811. timing.cur_fbu + ", total: " +
  812. timing.full_fbu_total + ", cnt: " +
  813. timing.full_fbu_cnt + ", avg: " +
  814. (timing.full_fbu_total /
  815. timing.full_fbu_cnt));
  816. }
  817. if (timing.fbu_rt_start > 0) {
  818. fbu_rt_diff = now - timing.fbu_rt_start;
  819. timing.fbu_rt_total += fbu_rt_diff;
  820. timing.fbu_rt_cnt += 1;
  821. Util.Info("full FBU round-trip, cur: " +
  822. fbu_rt_diff + ", total: " +
  823. timing.fbu_rt_total + ", cnt: " +
  824. timing.fbu_rt_cnt + ", avg: " +
  825. (timing.fbu_rt_total /
  826. timing.fbu_rt_cnt));
  827. timing.fbu_rt_start = 0;
  828. }
  829. }
  830. if (! ret) {
  831. return ret; // false ret means need more data
  832. }
  833. }
  834. conf.onFBUComplete(that,
  835. {'x': FBU.x, 'y': FBU.y,
  836. 'width': FBU.width, 'height': FBU.height,
  837. 'encoding': FBU.encoding,
  838. 'encodingName': encNames[FBU.encoding]});
  839. return true; // We finished this FBU
  840. };
  841. //
  842. // FramebufferUpdate encodings
  843. //
  844. encHandlers.RAW = function display_raw() {
  845. //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
  846. var cur_y, cur_height;
  847. if (FBU.lines === 0) {
  848. FBU.lines = FBU.height;
  849. }
  850. FBU.bytes = FBU.width * fb_Bpp; // At least a line
  851. if (ws.rQwait("RAW", FBU.bytes)) { return false; }
  852. cur_y = FBU.y + (FBU.height - FBU.lines);
  853. cur_height = Math.min(FBU.lines,
  854. Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
  855. display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
  856. ws.get_rQ(), ws.get_rQi());
  857. ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
  858. FBU.lines -= cur_height;
  859. if (FBU.lines > 0) {
  860. FBU.bytes = FBU.width * fb_Bpp; // At least another line
  861. } else {
  862. FBU.rects -= 1;
  863. FBU.bytes = 0;
  864. }
  865. //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
  866. return true;
  867. };
  868. encHandlers.COPYRECT = function display_copy_rect() {
  869. //Util.Debug(">> display_copy_rect");
  870. var old_x, old_y;
  871. if (ws.rQwait("COPYRECT", 4)) { return false; }
  872. old_x = ws.rQshift16();
  873. old_y = ws.rQshift16();
  874. display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
  875. FBU.rects -= 1;
  876. FBU.bytes = 0;
  877. return true;
  878. };
  879. encHandlers.RRE = function display_rre() {
  880. //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
  881. var color, x, y, width, height, chunk;
  882. if (FBU.subrects === 0) {
  883. if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
  884. FBU.subrects = ws.rQshift32();
  885. color = ws.rQshiftBytes(fb_Bpp); // Background
  886. display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
  887. }
  888. while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
  889. color = ws.rQshiftBytes(fb_Bpp);
  890. x = ws.rQshift16();
  891. y = ws.rQshift16();
  892. width = ws.rQshift16();
  893. height = ws.rQshift16();
  894. display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
  895. FBU.subrects -= 1;
  896. }
  897. //Util.Debug(" display_rre: rects: " + FBU.rects +
  898. // ", FBU.subrects: " + FBU.subrects);
  899. if (FBU.subrects > 0) {
  900. chunk = Math.min(rre_chunk_sz, FBU.subrects);
  901. FBU.bytes = (fb_Bpp + 8) * chunk;
  902. } else {
  903. FBU.rects -= 1;
  904. FBU.bytes = 0;
  905. }
  906. //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
  907. return true;
  908. };
  909. encHandlers.HEXTILE = function display_hextile() {
  910. //Util.Debug(">> display_hextile");
  911. var subencoding, subrects, tile, color, cur_tile,
  912. tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
  913. rQ = ws.get_rQ(), rQi = ws.get_rQi();
  914. if (FBU.tiles === 0) {
  915. FBU.tiles_x = Math.ceil(FBU.width/16);
  916. FBU.tiles_y = Math.ceil(FBU.height/16);
  917. FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
  918. FBU.tiles = FBU.total_tiles;
  919. }
  920. /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
  921. while (FBU.tiles > 0) {
  922. FBU.bytes = 1;
  923. if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
  924. subencoding = rQ[rQi]; // Peek
  925. if (subencoding > 30) { // Raw
  926. fail("Disconnected: illegal hextile subencoding " + subencoding);
  927. //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
  928. return false;
  929. }
  930. subrects = 0;
  931. cur_tile = FBU.total_tiles - FBU.tiles;
  932. tile_x = cur_tile % FBU.tiles_x;
  933. tile_y = Math.floor(cur_tile / FBU.tiles_x);
  934. x = FBU.x + tile_x * 16;
  935. y = FBU.y + tile_y * 16;
  936. w = Math.min(16, (FBU.x + FBU.width) - x);
  937. h = Math.min(16, (FBU.y + FBU.height) - y);
  938. /* Figure out how much we are expecting */
  939. if (subencoding & 0x01) { // Raw
  940. //Util.Debug(" Raw subencoding");
  941. FBU.bytes += w * h * fb_Bpp;
  942. } else {
  943. if (subencoding & 0x02) { // Background
  944. FBU.bytes += fb_Bpp;
  945. }
  946. if (subencoding & 0x04) { // Foreground
  947. FBU.bytes += fb_Bpp;
  948. }
  949. if (subencoding & 0x08) { // AnySubrects
  950. FBU.bytes += 1; // Since we aren't shifting it off
  951. if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
  952. subrects = rQ[rQi + FBU.bytes-1]; // Peek
  953. if (subencoding & 0x10) { // SubrectsColoured
  954. FBU.bytes += subrects * (fb_Bpp + 2);
  955. } else {
  956. FBU.bytes += subrects * 2;
  957. }
  958. }
  959. }
  960. /*
  961. Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
  962. " (" + tile_x + "," + tile_y + ")" +
  963. " [" + x + "," + y + "]@" + w + "x" + h +
  964. ", subenc:" + subencoding +
  965. "(last: " + FBU.lastsubencoding + "), subrects:" +
  966. subrects +
  967. ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
  968. " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
  969. " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
  970. */
  971. if (ws.rQwait("hextile", FBU.bytes)) { return false; }
  972. /* We know the encoding and have a whole tile */
  973. FBU.subencoding = rQ[rQi];
  974. rQi += 1;
  975. if (FBU.subencoding === 0) {
  976. if (FBU.lastsubencoding & 0x01) {
  977. /* Weird: ignore blanks after RAW */
  978. Util.Debug(" Ignoring blank after RAW");
  979. } else {
  980. display.fillRect(x, y, w, h, FBU.background);
  981. }
  982. } else if (FBU.subencoding & 0x01) { // Raw
  983. display.blitImage(x, y, w, h, rQ, rQi);
  984. rQi += FBU.bytes - 1;
  985. } else {
  986. if (FBU.subencoding & 0x02) { // Background
  987. FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
  988. rQi += fb_Bpp;
  989. }
  990. if (FBU.subencoding & 0x04) { // Foreground
  991. FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
  992. rQi += fb_Bpp;
  993. }
  994. tile = display.getTile(x, y, w, h, FBU.background);
  995. if (FBU.subencoding & 0x08) { // AnySubrects
  996. subrects = rQ[rQi];
  997. rQi += 1;
  998. for (s = 0; s < subrects; s += 1) {
  999. if (FBU.subencoding & 0x10) { // SubrectsColoured
  1000. color = rQ.slice(rQi, rQi + fb_Bpp);
  1001. rQi += fb_Bpp;
  1002. } else {
  1003. color = FBU.foreground;
  1004. }
  1005. xy = rQ[rQi];
  1006. rQi += 1;
  1007. sx = (xy >> 4);
  1008. sy = (xy & 0x0f);
  1009. wh = rQ[rQi];
  1010. rQi += 1;
  1011. sw = (wh >> 4) + 1;
  1012. sh = (wh & 0x0f) + 1;
  1013. display.setSubTile(tile, sx, sy, sw, sh, color);
  1014. }
  1015. }
  1016. display.putTile(tile);
  1017. }
  1018. ws.set_rQi(rQi);
  1019. FBU.lastsubencoding = FBU.subencoding;
  1020. FBU.bytes = 0;
  1021. FBU.tiles -= 1;
  1022. }
  1023. if (FBU.tiles === 0) {
  1024. FBU.rects -= 1;
  1025. }
  1026. //Util.Debug("<< display_hextile");
  1027. return true;
  1028. };
  1029. encHandlers.TIGHT_PNG = function display_tight_png() {
  1030. //Util.Debug(">> display_tight_png");
  1031. var ctl, cmode, clength, getCLength, color, img;
  1032. //Util.Debug(" FBU.rects: " + FBU.rects);
  1033. //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
  1034. FBU.bytes = 1; // compression-control byte
  1035. if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
  1036. // Get 'compact length' header and data size
  1037. getCLength = function (arr) {
  1038. var header = 1, data = 0;
  1039. data += arr[0] & 0x7f;
  1040. if (arr[0] & 0x80) {
  1041. header += 1;
  1042. data += (arr[1] & 0x7f) << 7;
  1043. if (arr[1] & 0x80) {
  1044. header += 1;
  1045. data += arr[2] << 14;
  1046. }
  1047. }
  1048. return [header, data];
  1049. };
  1050. ctl = ws.rQpeek8();
  1051. switch (ctl >> 4) {
  1052. case 0x08: cmode = "fill"; break;
  1053. case 0x09: cmode = "jpeg"; break;
  1054. case 0x0A: cmode = "png"; break;
  1055. default: throw("Illegal basic compression received, ctl: " + ctl);
  1056. }
  1057. switch (cmode) {
  1058. // fill uses fb_depth because TPIXELs drop the padding byte
  1059. case "fill": FBU.bytes += fb_depth; break; // TPIXEL
  1060. case "jpeg": FBU.bytes += 3; break; // max clength
  1061. case "png": FBU.bytes += 3; break; // max clength
  1062. }
  1063. if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
  1064. //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
  1065. //Util.Debug(" cmode: " + cmode);
  1066. // Determine FBU.bytes
  1067. switch (cmode) {
  1068. case "fill":
  1069. ws.rQshift8(); // shift off ctl
  1070. color = ws.rQshiftBytes(fb_depth);
  1071. display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
  1072. break;
  1073. case "jpeg":
  1074. case "png":
  1075. clength = getCLength(ws.rQslice(1, 4));
  1076. FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
  1077. if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
  1078. // We have everything, render it
  1079. //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
  1080. ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
  1081. img = new Image();
  1082. img.onload = scan_tight_imgQ;
  1083. FBU.imgQ.push([img, FBU.x, FBU.y]);
  1084. img.src = "data:image/" + cmode +
  1085. extract_data_uri(ws.rQshiftBytes(clength[1]));
  1086. img = null;
  1087. break;
  1088. }
  1089. FBU.bytes = 0;
  1090. FBU.rects -= 1;
  1091. //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
  1092. //Util.Debug("<< display_tight_png");
  1093. return true;
  1094. };
  1095. extract_data_uri = function(arr) {
  1096. //var i, stra = [];
  1097. //for (i=0; i< arr.length; i += 1) {
  1098. // stra.push(String.fromCharCode(arr[i]));
  1099. //}
  1100. //return "," + escape(stra.join(''));
  1101. return ";base64," + Base64.encode(arr);
  1102. };
  1103. scan_tight_imgQ = function() {
  1104. var img, imgQ, ctx;
  1105. ctx = display.get_context();
  1106. if (rfb_state === 'normal') {
  1107. imgQ = FBU.imgQ;
  1108. while ((imgQ.length > 0) && (imgQ[0][0].complete)) {
  1109. img = imgQ.shift();
  1110. ctx.drawImage(img[0], img[1], img[2]);
  1111. }
  1112. setTimeout(scan_tight_imgQ, scan_imgQ_rate);
  1113. }
  1114. };
  1115. encHandlers.DesktopSize = function set_desktopsize() {
  1116. Util.Debug(">> set_desktopsize");
  1117. fb_width = FBU.width;
  1118. fb_height = FBU.height;
  1119. display.resize(fb_width, fb_height);
  1120. timing.fbu_rt_start = (new Date()).getTime();
  1121. // Send a new non-incremental request
  1122. ws.send(fbUpdateRequest(0));
  1123. FBU.bytes = 0;
  1124. FBU.rects -= 1;
  1125. Util.Debug("<< set_desktopsize");
  1126. return true;
  1127. };
  1128. encHandlers.Cursor = function set_cursor() {
  1129. var x, y, w, h, pixelslength, masklength;
  1130. //Util.Debug(">> set_cursor");
  1131. x = FBU.x; // hotspot-x
  1132. y = FBU.y; // hotspot-y
  1133. w = FBU.width;
  1134. h = FBU.height;
  1135. pixelslength = w * h * fb_Bpp;
  1136. masklength = Math.floor((w + 7) / 8) * h;
  1137. FBU.bytes = pixelslength + masklength;
  1138. if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
  1139. //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
  1140. display.changeCursor(ws.rQshiftBytes(pixelslength),
  1141. ws.rQshiftBytes(masklength),
  1142. x, y, w, h);
  1143. FBU.bytes = 0;
  1144. FBU.rects -= 1;
  1145. //Util.Debug("<< set_cursor");
  1146. return true;
  1147. };
  1148. encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
  1149. Util.Error("Server sent jpeg_quality pseudo-encoding");
  1150. };
  1151. encHandlers.compress_lo = function set_compress_level() {
  1152. Util.Error("Server sent compress level pseudo-encoding");
  1153. };
  1154. /*
  1155. * Client message routines
  1156. */
  1157. pixelFormat = function() {
  1158. //Util.Debug(">> pixelFormat");
  1159. var arr;
  1160. arr = [0]; // msg-type
  1161. arr.push8(0); // padding
  1162. arr.push8(0); // padding
  1163. arr.push8(0); // padding
  1164. arr.push8(fb_Bpp * 8); // bits-per-pixel
  1165. arr.push8(fb_depth * 8); // depth
  1166. arr.push8(0); // little-endian
  1167. arr.push8(conf.true_color ? 1 : 0); // true-color
  1168. arr.push16(255); // red-max
  1169. arr.push16(255); // green-max
  1170. arr.push16(255); // blue-max
  1171. arr.push8(0); // red-shift
  1172. arr.push8(8); // green-shift
  1173. arr.push8(16); // blue-shift
  1174. arr.push8(0); // padding
  1175. arr.push8(0); // padding
  1176. arr.push8(0); // padding
  1177. //Util.Debug("<< pixelFormat");
  1178. return arr;
  1179. };
  1180. clientEncodings = function() {
  1181. //Util.Debug(">> clientEncodings");
  1182. var arr, i, encList = [];
  1183. for (i=0; i<encodings.length; i += 1) {
  1184. if ((encodings[i][0] === "Cursor") &&
  1185. (! conf.local_cursor)) {
  1186. Util.Debug("Skipping Cursor pseudo-encoding");
  1187. } else {
  1188. //Util.Debug("Adding encoding: " + encodings[i][0]);
  1189. encList.push(encodings[i][1]);
  1190. }
  1191. }
  1192. arr = [2]; // msg-type
  1193. arr.push8(0); // padding
  1194. arr.push16(encList.length); // encoding count
  1195. for (i=0; i < encList.length; i += 1) {
  1196. arr.push32(encList[i]);
  1197. }
  1198. //Util.Debug("<< clientEncodings: " + arr);
  1199. return arr;
  1200. };
  1201. fbUpdateRequest = function(incremental, x, y, xw, yw) {
  1202. //Util.Debug(">> fbUpdateRequest");
  1203. if (!x) { x = 0; }
  1204. if (!y) { y = 0; }
  1205. if (!xw) { xw = fb_width; }
  1206. if (!yw) { yw = fb_height; }
  1207. var arr;
  1208. arr = [3]; // msg-type
  1209. arr.push8(incremental);
  1210. arr.push16(x);
  1211. arr.push16(y);
  1212. arr.push16(xw);
  1213. arr.push16(yw);
  1214. //Util.Debug("<< fbUpdateRequest");
  1215. return arr;
  1216. };
  1217. keyEvent = function(keysym, down) {
  1218. //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
  1219. var arr;
  1220. arr = [4]; // msg-type
  1221. arr.push8(down);
  1222. arr.push16(0);
  1223. arr.push32(keysym);
  1224. //Util.Debug("<< keyEvent");
  1225. return arr;
  1226. };
  1227. pointerEvent = function(x, y) {
  1228. //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
  1229. // " , mask: " + mouse_buttonMask);
  1230. var arr;
  1231. arr = [5]; // msg-type
  1232. arr.push8(mouse_buttonMask);
  1233. arr.push16(x);
  1234. arr.push16(y);
  1235. //Util.Debug("<< pointerEvent");
  1236. return arr;
  1237. };
  1238. clientCutText = function(text) {
  1239. //Util.Debug(">> clientCutText");
  1240. var arr, i, n;
  1241. arr = [6]; // msg-type
  1242. arr.push8(0); // padding
  1243. arr.push8(0); // padding
  1244. arr.push8(0); // padding
  1245. arr.push32(text.length);
  1246. n = text.length;
  1247. for (i=0; i < n; i+=1) {
  1248. arr.push(text.charCodeAt(i));
  1249. }
  1250. //Util.Debug("<< clientCutText:" + arr);
  1251. return arr;
  1252. };
  1253. //
  1254. // Public API interface functions
  1255. //
  1256. that.connect = function(host, port, password) {
  1257. //Util.Debug(">> connect");
  1258. rfb_host = host;
  1259. rfb_port = port;
  1260. rfb_password = (password !== undefined) ? password : "";
  1261. if ((!rfb_host) || (!rfb_port)) {
  1262. return fail("Must set host and port");
  1263. }
  1264. updateState('connect');
  1265. //Util.Debug("<< connect");
  1266. };
  1267. that.disconnect = function() {
  1268. //Util.Debug(">> disconnect");
  1269. updateState('disconnect', 'Disconnecting');
  1270. //Util.Debug("<< disconnect");
  1271. };
  1272. that.sendPassword = function(passwd) {
  1273. rfb_password = passwd;
  1274. rfb_state = "Authentication";
  1275. setTimeout(init_msg, 1);
  1276. };
  1277. that.sendCtrlAltDel = function() {
  1278. if (rfb_state !== "normal") { return false; }
  1279. Util.Info("Sending Ctrl-Alt-Del");
  1280. var arr = [];
  1281. arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
  1282. arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
  1283. arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
  1284. arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
  1285. arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
  1286. arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
  1287. arr = arr.concat(fbUpdateRequest(1));
  1288. ws.send(arr);
  1289. };
  1290. // Send a key press. If 'down' is not specified then send a down key
  1291. // followed by an up key.
  1292. that.sendKey = function(code, down) {
  1293. if (rfb_state !== "normal") { return false; }
  1294. var arr = [];
  1295. if (typeof down !== 'undefined') {
  1296. Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
  1297. arr = arr.concat(keyEvent(code, down ? 1 : 0));
  1298. } else {
  1299. Util.Info("Sending key code (down + up): " + code);
  1300. arr = arr.concat(keyEvent(code, 1));
  1301. arr = arr.concat(keyEvent(code, 0));
  1302. }
  1303. arr = arr.concat(fbUpdateRequest(1));
  1304. ws.send(arr);
  1305. };
  1306. that.clipboardPasteFrom = function(text) {
  1307. if (rfb_state !== "normal") { return; }
  1308. //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
  1309. ws.send(clientCutText(text));
  1310. //Util.Debug("<< clipboardPasteFrom");
  1311. };
  1312. that.testMode = function(override_send) {
  1313. // Overridable internal functions for testing
  1314. test_mode = true;
  1315. // TODO figure out what to do here
  1316. ws.send = override_send;
  1317. that.recv_message = ws.recv_message; // Expose it
  1318. checkEvents = function () { /* Stub Out */ };
  1319. that.connect = function(host, port, password) {
  1320. rfb_host = host;
  1321. rfb_port = port;
  1322. rfb_password = password;
  1323. updateState('ProtocolVersion', "Starting VNC handshake");
  1324. };
  1325. };
  1326. return constructor(); // Return the public API interface
  1327. } // End of RFB()