rfb.js 50 KB

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