rfb.js 64 KB

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