]>
Commit | Line | Data |
---|---|---|
a7a89626 JM |
1 | /* |
2 | * noVNC: HTML5 VNC client | |
3 | * Copyright (C) 2010 Joel Martin | |
4 | * Licensed under LGPL-3 (see LICENSE.LGPL-3) | |
5 | * | |
6 | * See README.md for usage and integration instructions. | |
7 | */ | |
8 | ||
9 | "use strict"; | |
8db09746 | 10 | /*jslint white: false, browser: true, bitwise: false */ |
a7a89626 JM |
11 | /*global window, WebSocket, Util, Canvas, VNC_native_ws, Base64, DES */ |
12 | ||
a7a89626 | 13 | |
8db09746 JM |
14 | function RFB(conf) { |
15 | ||
16 | conf = conf || {}; // Configuration | |
17 | var that = {}, // Public API interface | |
18 | ||
19 | // Pre-declare private functions used before definitions (jslint) | |
20 | init_vars, updateState, init_msg, normal_msg, recv_message, | |
21 | framebufferUpdate, show_timings, | |
22 | ||
23 | pixelFormat, clientEncodings, fbUpdateRequest, | |
24 | keyEvent, pointerEvent, clientCutText, | |
25 | ||
26 | extract_data_uri, scan_tight_imgs, | |
27 | ||
28 | send_array, checkEvents, // Overridable for testing | |
29 | ||
30 | ||
31 | // | |
32 | // Private RFB namespace variables | |
33 | // | |
34 | rfb_host = '', | |
35 | rfb_port = 5900, | |
36 | rfb_password = '', | |
37 | ||
38 | rfb_state = 'disconnected', | |
39 | rfb_version = 0, | |
40 | rfb_max_version= 3.8, | |
41 | rfb_auth_scheme= '', | |
42 | rfb_shared = 1, | |
43 | ||
44 | ||
45 | // In preference order | |
46 | encodings = [ | |
47 | ['COPYRECT', 0x01 ], | |
48 | ['TIGHT_PNG', -260 ], | |
49 | ['HEXTILE', 0x05 ], | |
50 | ['RRE', 0x02 ], | |
51 | ['RAW', 0x00 ], | |
52 | ['DesktopSize', -223 ], | |
53 | ['Cursor', -239 ], | |
54 | ||
55 | // Psuedo-encoding settings | |
56 | ['JPEG_quality_lo', -32 ], | |
57 | //['JPEG_quality_hi', -23 ], | |
58 | ['compress_lo', -255 ] | |
59 | //['compress_hi', -247 ] | |
60 | ], | |
61 | ||
62 | encHandlers = {}, | |
63 | encNames = {}, | |
64 | ||
65 | ws = null, // Web Socket object | |
66 | canvas = null, // Canvas object | |
67 | sendID = null, // Send Queue check timer | |
68 | ||
69 | // Receive and send queues | |
70 | RQ = [], // Receive Queue | |
71 | SQ = "", // Send Queue | |
72 | ||
73 | // Frame buffer update state | |
74 | FBU = { | |
75 | rects : 0, | |
76 | subrects : 0, // RRE and HEXTILE | |
77 | lines : 0, // RAW | |
78 | tiles : 0, // HEXTILE | |
79 | bytes : 0, | |
80 | x : 0, | |
81 | y : 0, | |
82 | width : 0, | |
83 | height : 0, | |
84 | encoding : 0, | |
85 | subencoding : -1, | |
86 | background : null, | |
87 | imgs : [] // TIGHT_PNG image queue | |
88 | }, | |
89 | ||
90 | fb_Bpp = 4, | |
91 | fb_depth = 3, | |
92 | fb_width = 0, | |
93 | fb_height = 0, | |
94 | fb_name = "", | |
95 | ||
96 | cuttext = 'none', // ServerCutText wait state | |
97 | cuttext_length = 0, | |
98 | ||
99 | scan_imgs_rate = 100, | |
100 | last_req_time = 0, | |
101 | rre_chunk_sz = 100, | |
102 | ||
103 | timing = { | |
104 | last_fbu : 0, | |
105 | fbu_total : 0, | |
106 | fbu_total_cnt : 0, | |
107 | full_fbu_total : 0, | |
108 | full_fbu_cnt : 0, | |
109 | ||
110 | fbu_rt_start : 0, | |
111 | fbu_rt_total : 0, | |
112 | fbu_rt_cnt : 0, | |
113 | ||
114 | history : [], | |
115 | history_start : 0, | |
116 | h_time : 0, | |
117 | h_rects : 0, | |
118 | h_fbus : 0, | |
119 | h_bytes : 0, | |
120 | h_pixels : 0 | |
121 | }, | |
122 | ||
123 | test_mode = false, | |
124 | ||
125 | /* Mouse state */ | |
126 | mouse_buttonMask = 0, | |
127 | mouse_arr = []; | |
128 | ||
129 | ||
130 | // | |
131 | // Configuration settings | |
132 | // | |
133 | ||
134 | // VNC viewport rendering Canvas | |
135 | Util.conf_default(conf, that, 'target', 'VNC_canvas'); | |
136 | ||
137 | Util.conf_default(conf, that, 'encrypt', false, true); | |
138 | Util.conf_default(conf, that, 'true_color', true, true); | |
139 | // false means UTF-8 on the wire | |
140 | Util.conf_default(conf, that, 'b64encode', true, true); | |
141 | Util.conf_default(conf, that, 'local_cursor', true, true); | |
142 | ||
143 | // time to wait for connection | |
144 | Util.conf_default(conf, that, 'connectTimeout', 2000); | |
145 | // frequency to check for send/receive | |
146 | Util.conf_default(conf, that, 'check_rate', 217); | |
147 | // frequency to send frameBufferUpdate requests | |
148 | Util.conf_default(conf, that, 'fbu_req_rate', 1413); | |
149 | ||
150 | // state update callback | |
151 | Util.conf_default(conf, that, 'updateState', function () { | |
152 | Util.Debug(">> externalUpdateState stub"); }); | |
153 | // clipboard contents received callback | |
154 | Util.conf_default(conf, that, 'clipboardReceive', function () { | |
155 | Util.Debug(">> clipboardReceive stub"); }); | |
156 | ||
157 | ||
158 | // Override/add some specific getters/setters | |
159 | that.set_local_cursor = function(cursor) { | |
a7a89626 | 160 | if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) { |
8db09746 | 161 | conf.local_cursor = false; |
a7a89626 | 162 | } else { |
8db09746 JM |
163 | if (canvas.get_cursor_uri()) { |
164 | conf.local_cursor = true; | |
a7a89626 JM |
165 | } else { |
166 | Util.Warn("Browser does not support local cursor"); | |
167 | } | |
168 | } | |
8db09746 | 169 | }; |
a7a89626 | 170 | |
8db09746 JM |
171 | that.get_canvas = function() { |
172 | return canvas; | |
173 | }; | |
a7a89626 | 174 | |
8db09746 JM |
175 | |
176 | ||
177 | ||
178 | // | |
179 | // Private functions | |
180 | // | |
181 | ||
182 | // | |
183 | // Setup routines | |
184 | // | |
185 | ||
186 | // Create the public API interface | |
187 | function constructor() { | |
a7a89626 | 188 | var i; |
8db09746 | 189 | //Util.Debug(">> init"); |
a7a89626 | 190 | |
8db09746 JM |
191 | // Create lookup tables based encoding number |
192 | for (i=0; i < encodings.length; i+=1) { | |
193 | encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]]; | |
194 | encNames[encodings[i][1]] = encodings[i][0]; | |
a7a89626 | 195 | } |
8db09746 | 196 | // Initialize canvas |
a7a89626 | 197 | try { |
8db09746 | 198 | canvas = new Canvas({'target': conf.target}); |
a7a89626 | 199 | } catch (exc) { |
8db09746 JM |
200 | Util.Error("Canvas exception: " + exc); |
201 | updateState('fatal', "No working Canvas"); | |
a7a89626 JM |
202 | } |
203 | ||
8db09746 JM |
204 | //Util.Debug("<< init"); |
205 | return that; // Return the public API interface | |
206 | } | |
a7a89626 | 207 | |
8db09746 JM |
208 | function init_ws() { |
209 | //Util.Debug(">> init_ws"); | |
a7a89626 | 210 | |
8db09746 JM |
211 | var uri = "", vars = []; |
212 | if (conf.encrypt) { | |
213 | uri = "wss://"; | |
214 | } else { | |
215 | uri = "ws://"; | |
216 | } | |
217 | uri += rfb_host + ":" + rfb_port + "/"; | |
218 | if (conf.b64encode) { | |
219 | vars.push("b64encode"); | |
a7a89626 | 220 | } |
8db09746 JM |
221 | if (vars.length > 0) { |
222 | uri += "?" + vars.join("&"); | |
223 | } | |
224 | Util.Info("connecting to " + uri); | |
225 | ws = new WebSocket(uri); | |
a7a89626 | 226 | |
8db09746 JM |
227 | ws.onmessage = recv_message; |
228 | ws.onopen = function(e) { | |
229 | Util.Debug(">> WebSocket.onopen"); | |
230 | if (rfb_state === "connect") { | |
231 | updateState('ProtocolVersion', "Starting VNC handshake"); | |
232 | } else { | |
233 | updateState('failed', "Got unexpected WebSockets connection"); | |
234 | } | |
235 | Util.Debug("<< WebSocket.onopen"); | |
236 | }; | |
237 | ws.onclose = function(e) { | |
238 | Util.Debug(">> WebSocket.onclose"); | |
239 | if (rfb_state === 'normal') { | |
240 | updateState('failed', 'Server disconnected'); | |
241 | } else if (rfb_state === 'ProtocolVersion') { | |
242 | updateState('failed', 'Failed to connect to server'); | |
243 | } else { | |
244 | updateState('disconnected', 'VNC disconnected'); | |
245 | } | |
246 | Util.Debug("<< WebSocket.onclose"); | |
247 | }; | |
248 | ws.onerror = function(e) { | |
249 | Util.Debug(">> WebSocket.onerror"); | |
250 | updateState('failed', "WebSocket error"); | |
251 | Util.Debug("<< WebSocket.onerror"); | |
252 | }; | |
a7a89626 | 253 | |
8db09746 JM |
254 | setTimeout(function () { |
255 | if (ws.readyState === WebSocket.CONNECTING) { | |
256 | updateState('failed', "Connect timeout"); | |
257 | } | |
258 | }, conf.connectTimeout); | |
a7a89626 | 259 | |
8db09746 JM |
260 | //Util.Debug("<< init_ws"); |
261 | } | |
a7a89626 | 262 | |
8db09746 JM |
263 | init_vars = function() { |
264 | /* Reset state */ | |
265 | cuttext = 'none'; | |
266 | cuttext_length = 0; | |
267 | RQ = []; | |
268 | SQ = ""; | |
269 | FBU.rects = 0; | |
270 | FBU.subrects = 0; // RRE and HEXTILE | |
271 | FBU.lines = 0; // RAW | |
272 | FBU.tiles = 0; // HEXTILE | |
273 | FBU.imgs = []; // TIGHT_PNG image queue | |
274 | mouse_buttonMask = 0; | |
275 | mouse_arr = []; | |
276 | ||
277 | timing.history_start = 0; | |
278 | timing.history = []; | |
279 | timing.h_fbus = 0; | |
280 | timing.h_rects = 0; | |
281 | timing.h_bytes = 0; | |
282 | timing.h_pixels = 0; | |
283 | }; | |
284 | ||
285 | // | |
286 | // Utility routines | |
287 | // | |
a7a89626 JM |
288 | |
289 | ||
290 | /* | |
8db09746 JM |
291 | * Running states: |
292 | * disconnected - idle state | |
293 | * normal - connected | |
294 | * | |
295 | * Page states: | |
296 | * loaded - page load, equivalent to disconnected | |
297 | * connect - starting initialization | |
298 | * password - waiting for password | |
299 | * failed - abnormal transition to disconnected | |
300 | * fatal - failed to load page, or fatal error | |
301 | * | |
302 | * VNC initialization states: | |
303 | * ProtocolVersion | |
304 | * Security | |
305 | * Authentication | |
306 | * SecurityResult | |
307 | * ServerInitialization | |
a7a89626 | 308 | */ |
8db09746 JM |
309 | updateState = function(state, statusMsg) { |
310 | var func, cmsg, oldstate = rfb_state; | |
311 | if (state === oldstate) { | |
312 | /* Already here, ignore */ | |
313 | Util.Debug("Already in state '" + state + "', ignoring."); | |
314 | return; | |
315 | } | |
a7a89626 | 316 | |
8db09746 JM |
317 | if (oldstate === 'fatal') { |
318 | Util.Error("Fatal error, cannot continue"); | |
319 | } | |
a7a89626 | 320 | |
8db09746 JM |
321 | if ((state === 'failed') || (state === 'fatal')) { |
322 | func = Util.Error; | |
323 | } else { | |
324 | func = Util.Warn; | |
325 | } | |
a7a89626 | 326 | |
8db09746 JM |
327 | cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : ""; |
328 | func("New state '" + state + "', was '" + oldstate + "'." + cmsg); | |
a7a89626 | 329 | |
8db09746 JM |
330 | if ((oldstate === 'failed') && (state === 'disconnected')) { |
331 | // Do disconnect action, but stay in failed state. | |
332 | rfb_state = 'failed'; | |
333 | } else { | |
334 | rfb_state = state; | |
335 | } | |
a7a89626 | 336 | |
8db09746 JM |
337 | switch (state) { |
338 | case 'loaded': | |
339 | case 'disconnected': | |
a7a89626 | 340 | |
8db09746 JM |
341 | if (sendID) { |
342 | clearInterval(sendID); | |
343 | sendID = null; | |
a7a89626 | 344 | } |
8db09746 JM |
345 | |
346 | if (ws) { | |
347 | if (ws.readyState === WebSocket.OPEN) { | |
348 | ws.close(); | |
349 | } | |
350 | ws.onmessage = function (e) { return; }; | |
a7a89626 | 351 | } |
8db09746 JM |
352 | |
353 | if (canvas && canvas.getContext()) { | |
354 | canvas.stop(); | |
355 | if (! /__debug__$/i.test(document.location.href)) { | |
356 | canvas.clear(); | |
357 | } | |
a7a89626 JM |
358 | } |
359 | ||
8db09746 | 360 | show_timings(); |
a7a89626 | 361 | |
a7a89626 JM |
362 | break; |
363 | ||
a7a89626 | 364 | |
8db09746 JM |
365 | case 'connect': |
366 | init_vars(); | |
a7a89626 | 367 | |
8db09746 JM |
368 | if ((ws) && (ws.readyState === WebSocket.OPEN)) { |
369 | ws.close(); | |
a7a89626 | 370 | } |
8db09746 JM |
371 | init_ws(); // onopen transitions to 'ProtocolVersion' |
372 | ||
a7a89626 JM |
373 | break; |
374 | ||
a7a89626 | 375 | |
8db09746 JM |
376 | case 'password': |
377 | // Ignore password state by default | |
378 | break; | |
a7a89626 | 379 | |
a7a89626 | 380 | |
8db09746 JM |
381 | case 'normal': |
382 | if ((oldstate === 'disconnected') || (oldstate === 'failed')) { | |
383 | Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'"); | |
384 | } | |
a7a89626 | 385 | |
8db09746 | 386 | break; |
a7a89626 | 387 | |
a7a89626 | 388 | |
8db09746 JM |
389 | case 'failed': |
390 | if (oldstate === 'disconnected') { | |
391 | Util.Error("Invalid transition from 'disconnected' to 'failed'"); | |
392 | } | |
393 | if (oldstate === 'normal') { | |
394 | Util.Error("Error while connected."); | |
395 | } | |
396 | if (oldstate === 'init') { | |
397 | Util.Error("Error while initializing."); | |
a7a89626 JM |
398 | } |
399 | ||
8db09746 JM |
400 | if ((ws) && (ws.readyState === WebSocket.OPEN)) { |
401 | ws.close(); | |
a7a89626 | 402 | } |
8db09746 JM |
403 | // Make sure we transition to disconnected |
404 | setTimeout(function() { updateState('disconnected'); }, 50); | |
405 | ||
a7a89626 | 406 | break; |
a7a89626 JM |
407 | |
408 | ||
8db09746 JM |
409 | default: |
410 | // Invalid state transition | |
a7a89626 | 411 | |
8db09746 | 412 | } |
a7a89626 | 413 | |
8db09746 JM |
414 | if ((oldstate === 'failed') && (state === 'disconnected')) { |
415 | // Leave the failed message | |
416 | conf.updateState(that, state, oldstate); | |
417 | } else { | |
418 | conf.updateState(that, state, oldstate, statusMsg); | |
419 | } | |
420 | }; | |
421 | ||
422 | function encode_message(arr) { | |
423 | if (conf.b64encode) { | |
424 | /* base64 encode */ | |
425 | SQ = SQ + Base64.encode(arr); | |
426 | } else { | |
427 | /* UTF-8 encode. 0 -> 256 to avoid WebSockets framing */ | |
428 | SQ = SQ + arr.map(function (num) { | |
429 | if (num === 0) { | |
430 | return String.fromCharCode(256); | |
431 | } else { | |
432 | return String.fromCharCode(num); | |
433 | } | |
434 | } ).join(''); | |
435 | } | |
436 | } | |
437 | ||
438 | function decode_message(data) { | |
439 | var i, length; | |
440 | //Util.Debug(">> decode_message: " + data); | |
441 | if (conf.b64encode) { | |
442 | /* base64 decode */ | |
443 | RQ = RQ.concat(Base64.decode(data, 0)); | |
444 | } else { | |
445 | /* UTF-8 decode. 256 -> 0 to WebSockets framing */ | |
446 | length = data.length; | |
447 | for (i=0; i < length; i += 1) { | |
448 | RQ.push(data.charCodeAt(i) % 256); | |
449 | } | |
450 | } | |
451 | //Util.Debug(">> decode_message, RQ: " + RQ); | |
452 | } | |
453 | ||
454 | function handle_message() { | |
455 | //Util.Debug("RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); | |
456 | if (RQ.length == 0) { | |
457 | Util.Warn("handle_message called on empty receive queue"); | |
458 | return; | |
459 | } | |
460 | switch (rfb_state) { | |
461 | case 'disconnected': | |
462 | Util.Error("Got data while disconnected"); | |
463 | break; | |
464 | case 'failed': | |
465 | Util.Warn("Giving up!"); | |
466 | that.disconnect(); | |
467 | break; | |
468 | case 'normal': | |
469 | if (normal_msg() && RQ.length > 0) { | |
470 | // true means we can continue processing | |
471 | Util.Debug("More data to process"); | |
472 | // Give other events a chance to run | |
473 | setTimeout(handle_message, 10); | |
474 | } | |
475 | break; | |
476 | default: | |
477 | init_msg(); | |
478 | break; | |
479 | } | |
480 | } | |
481 | ||
482 | recv_message = function(e) { | |
483 | //Util.Debug(">> recv_message"); | |
484 | ||
485 | try { | |
486 | decode_message(e.data); | |
487 | if (RQ.length > 0) { | |
488 | handle_message(); | |
489 | } else { | |
490 | Util.Debug("Ignoring empty message"); | |
491 | } | |
492 | } catch (exc) { | |
493 | if (typeof exc.stack !== 'undefined') { | |
494 | Util.Warn("recv_message, caught exception: " + exc.stack); | |
495 | } else if (typeof exc.description !== 'undefined') { | |
496 | Util.Warn("recv_message, caught exception: " + exc.description); | |
497 | } else { | |
498 | Util.Warn("recv_message, caught exception:" + exc); | |
499 | } | |
500 | if (typeof exc.name !== 'undefined') { | |
501 | updateState('failed', exc.name + ": " + exc.message); | |
502 | } else { | |
503 | updateState('failed', exc); | |
504 | } | |
505 | } | |
506 | //Util.Debug("<< recv_message"); | |
507 | }; | |
508 | ||
509 | // overridable for testing | |
510 | send_array = function(arr) { | |
511 | //Util.Debug(">> send_array: " + arr); | |
512 | encode_message(arr); | |
513 | if (ws.bufferedAmount === 0) { | |
514 | //Util.Debug("arr: " + arr); | |
515 | //Util.Debug("SQ: " + SQ); | |
516 | ws.send(SQ); | |
517 | SQ = ""; | |
518 | } else { | |
519 | Util.Debug("Delaying send"); | |
520 | } | |
521 | }; | |
522 | ||
523 | function send_string(str) { | |
524 | //Util.Debug(">> send_string: " + str); | |
525 | send_array(str.split('').map( | |
526 | function (chr) { return chr.charCodeAt(0); } ) ); | |
527 | } | |
528 | ||
529 | function genDES(password, challenge) { | |
530 | var i, passwd, response; | |
531 | passwd = []; | |
532 | response = challenge.slice(); | |
533 | for (i=0; i < password.length; i += 1) { | |
534 | passwd.push(password.charCodeAt(i)); | |
535 | } | |
536 | ||
537 | DES.setKeys(passwd); | |
538 | DES.encrypt(response, 0, response, 0); | |
539 | DES.encrypt(response, 8, response, 8); | |
540 | return response; | |
541 | } | |
542 | ||
543 | function flushClient() { | |
544 | if (mouse_arr.length > 0) { | |
545 | //send_array(mouse_arr.concat(fbUpdateRequest(1))); | |
546 | send_array(mouse_arr); | |
547 | setTimeout(function() { | |
548 | send_array(fbUpdateRequest(1)); | |
549 | }, 50); | |
550 | ||
551 | mouse_arr = []; | |
552 | return true; | |
553 | } else { | |
554 | return false; | |
555 | } | |
556 | } | |
557 | ||
558 | // overridable for testing | |
559 | checkEvents = function() { | |
560 | var now; | |
561 | if (rfb_state === 'normal') { | |
562 | if (! flushClient()) { | |
563 | now = new Date().getTime(); | |
564 | if (now > last_req_time + conf.fbu_req_rate) { | |
565 | last_req_time = now; | |
566 | send_array(fbUpdateRequest(1)); | |
567 | } | |
568 | } | |
569 | } | |
570 | setTimeout(checkEvents, conf.check_rate); | |
571 | }; | |
572 | ||
573 | function keyPress(keysym, down) { | |
574 | var arr; | |
575 | arr = keyEvent(keysym, down); | |
576 | arr = arr.concat(fbUpdateRequest(1)); | |
577 | send_array(arr); | |
578 | } | |
579 | ||
580 | function mouseButton(x, y, down, bmask) { | |
581 | if (down) { | |
582 | mouse_buttonMask |= bmask; | |
583 | } else { | |
584 | mouse_buttonMask ^= bmask; | |
585 | } | |
586 | mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); | |
587 | flushClient(); | |
588 | } | |
589 | ||
590 | function mouseMove(x, y) { | |
591 | //Util.Debug('>> mouseMove ' + x + "," + y); | |
592 | mouse_arr = mouse_arr.concat( pointerEvent(x, y) ); | |
593 | } | |
594 | ||
595 | ||
596 | function update_timings() { | |
597 | var now, offset; | |
598 | now = (new Date()).getTime(); | |
599 | timing.history.push([now, | |
600 | timing.h_fbus, | |
601 | timing.h_rects, | |
602 | timing.h_bytes, | |
603 | timing.h_pixels]); | |
604 | timing.h_fbus = 0; | |
605 | timing.h_rects = 0; | |
606 | timing.h_bytes = 0; | |
607 | timing.h_pixels = 0; | |
608 | if ((rfb_state !== 'disconnected') && (rfb_state !== 'failed')) { | |
609 | // Try for every second | |
610 | offset = (now - timing.history_start) % 1000; | |
611 | if (offset < 500) { | |
612 | setTimeout(update_timings, 1000 - offset); | |
613 | } else { | |
614 | setTimeout(update_timings, 2000 - offset); | |
615 | } | |
616 | } | |
617 | } | |
618 | ||
619 | show_timings = function() { | |
620 | var i, history, msg, | |
621 | delta, tot_time = 0, tot_fbus = 0, tot_rects = 0, | |
622 | tot_bytes = 0, tot_pixels = 0; | |
623 | if (timing.history_start === 0) { return; } | |
624 | //Util.Debug(">> show_timings"); | |
625 | update_timings(); // Final accumulate | |
626 | msg = "\nTimings\n"; | |
627 | msg += " time: fbus,rects,bytes,pixels\n"; | |
628 | for (i=0; i < timing.history.length; i += 1) { | |
629 | history = timing.history[i]; | |
630 | delta = ((history[0]-timing.history_start)/1000); | |
631 | tot_time = delta; | |
632 | tot_fbus += history[1]; | |
633 | tot_rects += history[2]; | |
634 | tot_bytes += history[3]; | |
635 | tot_pixels += history[4]; | |
636 | ||
637 | msg += " " + delta.toFixed(3); | |
638 | msg += ": " + history.slice(1) + "\n"; | |
639 | } | |
640 | msg += "\nTotals:\n"; | |
641 | msg += " time: fbus,rects,bytes,pixels\n"; | |
642 | msg += " " + tot_time.toFixed(3); | |
643 | msg += ": " + tot_fbus + "," + tot_rects; | |
644 | msg += "," + tot_bytes + "," + tot_pixels; | |
645 | Util.Info(msg); | |
646 | //Util.Debug("<< show_timings"); | |
647 | }; | |
648 | ||
649 | // | |
650 | // Server message handlers | |
651 | // | |
652 | ||
653 | // RFB/VNC initialisation message handler | |
654 | init_msg = function() { | |
655 | //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']"); | |
656 | ||
657 | var strlen, reason, reason_len, sversion, cversion, | |
658 | i, types, num_types, challenge, response, bpp, depth, | |
659 | big_endian, true_color, name_length; | |
660 | ||
661 | //Util.Debug("RQ (" + RQ.length + ") " + RQ); | |
662 | switch (rfb_state) { | |
663 | ||
664 | case 'ProtocolVersion' : | |
665 | if (RQ.length < 12) { | |
666 | updateState('failed', | |
667 | "Disconnected: incomplete protocol version"); | |
668 | return; | |
669 | } | |
670 | sversion = RQ.shiftStr(12).substr(4,7); | |
671 | Util.Info("Server ProtocolVersion: " + sversion); | |
672 | switch (sversion) { | |
673 | case "003.003": rfb_version = 3.3; break; | |
674 | case "003.007": rfb_version = 3.7; break; | |
675 | case "003.008": rfb_version = 3.8; break; | |
676 | default: | |
677 | updateState('failed', | |
678 | "Invalid server version " + sversion); | |
679 | return; | |
680 | } | |
681 | if (rfb_version > rfb_max_version) { | |
682 | rfb_version = rfb_max_version; | |
683 | } | |
684 | ||
685 | if (! test_mode) { | |
686 | sendID = setInterval(function() { | |
687 | // Send updates either at a rate of one update | |
688 | // every 50ms, or whatever slower rate the network | |
689 | // can handle. | |
690 | if (ws.bufferedAmount === 0) { | |
691 | if (SQ) { | |
692 | ws.send(SQ); | |
693 | SQ = ""; | |
694 | } | |
695 | } else { | |
696 | Util.Debug("Delaying send"); | |
697 | } | |
698 | }, 50); | |
699 | } | |
700 | ||
701 | cversion = "00" + parseInt(rfb_version,10) + | |
702 | ".00" + ((rfb_version * 10) % 10); | |
703 | send_string("RFB " + cversion + "\n"); | |
704 | updateState('Security', "Sent ProtocolVersion: " + sversion); | |
705 | break; | |
706 | ||
707 | case 'Security' : | |
708 | if (rfb_version >= 3.7) { | |
709 | num_types = RQ.shift8(); | |
710 | if (num_types === 0) { | |
711 | strlen = RQ.shift32(); | |
712 | reason = RQ.shiftStr(strlen); | |
713 | updateState('failed', | |
714 | "Disconnected: security failure: " + reason); | |
715 | return; | |
716 | } | |
717 | rfb_auth_scheme = 0; | |
718 | types = RQ.shiftBytes(num_types); | |
719 | Util.Debug("Server security types: " + types); | |
720 | for (i=0; i < types.length; i+=1) { | |
721 | if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) { | |
722 | rfb_auth_scheme = types[i]; | |
723 | } | |
724 | } | |
725 | if (rfb_auth_scheme === 0) { | |
726 | updateState('failed', | |
727 | "Disconnected: unsupported security types: " + types); | |
728 | return; | |
729 | } | |
730 | ||
731 | send_array([rfb_auth_scheme]); | |
732 | } else { | |
733 | if (RQ.length < 4) { | |
734 | updateState('failed', "Invalid security frame"); | |
735 | return; | |
736 | } | |
737 | rfb_auth_scheme = RQ.shift32(); | |
738 | } | |
739 | updateState('Authentication', | |
740 | "Authenticating using scheme: " + rfb_auth_scheme); | |
741 | init_msg(); // Recursive fallthrough (workaround JSLint complaint) | |
742 | break; | |
743 | ||
744 | case 'Authentication' : | |
745 | //Util.Debug("Security auth scheme: " + rfb_auth_scheme); | |
746 | switch (rfb_auth_scheme) { | |
747 | case 0: // connection failed | |
748 | if (RQ.length < 4) { | |
749 | //Util.Debug(" waiting for auth reason bytes"); | |
750 | return; | |
751 | } | |
752 | strlen = RQ.shift32(); | |
753 | reason = RQ.shiftStr(strlen); | |
754 | updateState('failed', | |
755 | "Disconnected: auth failure: " + reason); | |
756 | return; | |
757 | case 1: // no authentication | |
758 | updateState('SecurityResult'); | |
759 | break; | |
760 | case 2: // VNC authentication | |
761 | if (rfb_password.length === 0) { | |
762 | updateState('password', "Password Required"); | |
763 | return; | |
764 | } | |
765 | if (RQ.length < 16) { | |
766 | //Util.Debug(" waiting for auth challenge bytes"); | |
767 | return; | |
768 | } | |
769 | challenge = RQ.shiftBytes(16); | |
770 | //Util.Debug("Password: " + rfb_password); | |
771 | //Util.Debug("Challenge: " + challenge + | |
772 | // " (" + challenge.length + ")"); | |
773 | response = genDES(rfb_password, challenge); | |
774 | //Util.Debug("Response: " + response + | |
775 | // " (" + response.length + ")"); | |
776 | ||
777 | //Util.Debug("Sending DES encrypted auth response"); | |
778 | send_array(response); | |
779 | updateState('SecurityResult'); | |
780 | break; | |
781 | default: | |
782 | updateState('failed', | |
783 | "Disconnected: unsupported auth scheme: " + | |
784 | rfb_auth_scheme); | |
785 | return; | |
786 | } | |
787 | break; | |
788 | ||
789 | case 'SecurityResult' : | |
790 | if (RQ.length < 4) { | |
791 | updateState('failed', "Invalid VNC auth response"); | |
792 | return; | |
793 | } | |
794 | switch (RQ.shift32()) { | |
795 | case 0: // OK | |
796 | updateState('ServerInitialisation', "Authentication OK"); | |
797 | break; | |
798 | case 1: // failed | |
799 | if (rfb_version >= 3.8) { | |
800 | reason_len = RQ.shift32(); | |
801 | reason = RQ.shiftStr(reason_len); | |
802 | updateState('failed', reason); | |
803 | } else { | |
804 | updateState('failed', "Authentication failed"); | |
805 | } | |
806 | return; | |
807 | case 2: // too-many | |
808 | updateState('failed', | |
809 | "Disconnected: too many auth attempts"); | |
810 | return; | |
811 | } | |
812 | send_array([rfb_shared]); // ClientInitialisation | |
813 | break; | |
814 | ||
815 | case 'ServerInitialisation' : | |
816 | if (RQ.length < 24) { | |
817 | updateState('failed', "Invalid server initialisation"); | |
818 | return; | |
819 | } | |
820 | ||
821 | /* Screen size */ | |
822 | fb_width = RQ.shift16(); | |
823 | fb_height = RQ.shift16(); | |
824 | ||
825 | /* PIXEL_FORMAT */ | |
826 | bpp = RQ.shift8(); | |
827 | depth = RQ.shift8(); | |
828 | big_endian = RQ.shift8(); | |
829 | true_color = RQ.shift8(); | |
830 | ||
831 | Util.Info("Screen: " + fb_width + "x" + fb_height + | |
832 | ", bpp: " + bpp + ", depth: " + depth + | |
833 | ", big_endian: " + big_endian + | |
834 | ", true_color: " + true_color); | |
835 | ||
836 | /* Connection name/title */ | |
837 | RQ.shiftStr(12); | |
838 | name_length = RQ.shift32(); | |
839 | fb_name = RQ.shiftStr(name_length); | |
840 | ||
841 | canvas.resize(fb_width, fb_height, conf.true_color); | |
842 | canvas.start(keyPress, mouseButton, mouseMove); | |
843 | ||
844 | if (conf.true_color) { | |
845 | fb_Bpp = 4; | |
846 | fb_depth = 3; | |
847 | } else { | |
848 | fb_Bpp = 1; | |
849 | fb_depth = 1; | |
850 | } | |
851 | ||
852 | response = pixelFormat(); | |
853 | response = response.concat(clientEncodings()); | |
854 | response = response.concat(fbUpdateRequest(0)); | |
855 | timing.fbu_rt_start = (new Date()).getTime(); | |
856 | send_array(response); | |
857 | ||
858 | /* Start pushing/polling */ | |
859 | setTimeout(checkEvents, conf.check_rate); | |
860 | setTimeout(scan_tight_imgs, scan_imgs_rate); | |
861 | timing.history_start = (new Date()).getTime(); | |
862 | setTimeout(update_timings, 1000); | |
863 | ||
864 | if (conf.encrypt) { | |
865 | updateState('normal', "Connected (encrypted) to: " + fb_name); | |
866 | } else { | |
867 | updateState('normal', "Connected (unencrypted) to: " + fb_name); | |
868 | } | |
869 | break; | |
870 | } | |
871 | //Util.Debug("<< init_msg"); | |
872 | }; | |
873 | ||
874 | ||
875 | /* Normal RFB/VNC server message handler */ | |
876 | normal_msg = function() { | |
877 | //Util.Debug(">> normal_msg"); | |
878 | ||
879 | var ret = true, msg_type, | |
880 | c, first_colour, num_colours, red, green, blue; | |
881 | ||
882 | //Util.Debug(">> msg RQ.slice(0,10): " + RQ.slice(0,20)); | |
883 | //Util.Debug(">> msg RQ.slice(-10,-1): " + RQ.slice(RQ.length-10,RQ.length)); | |
884 | if (FBU.rects > 0) { | |
885 | msg_type = 0; | |
886 | } else if (cuttext !== 'none') { | |
a7a89626 JM |
887 | msg_type = 3; |
888 | } else { | |
889 | msg_type = RQ.shift8(); | |
890 | } | |
891 | switch (msg_type) { | |
892 | case 0: // FramebufferUpdate | |
8db09746 | 893 | ret = framebufferUpdate(); // false means need more data |
a7a89626 JM |
894 | break; |
895 | case 1: // SetColourMapEntries | |
896 | Util.Debug("SetColourMapEntries"); | |
897 | RQ.shift8(); // Padding | |
898 | first_colour = RQ.shift16(); // First colour | |
899 | num_colours = RQ.shift16(); | |
900 | for (c=0; c < num_colours; c+=1) { | |
901 | red = RQ.shift16(); | |
902 | //Util.Debug("red before: " + red); | |
903 | red = parseInt(red / 256, 10); | |
904 | //Util.Debug("red after: " + red); | |
905 | green = parseInt(RQ.shift16() / 256, 10); | |
906 | blue = parseInt(RQ.shift16() / 256, 10); | |
8db09746 | 907 | canvas.set_colourMap([red, green, blue], first_colour + c); |
a7a89626 JM |
908 | } |
909 | Util.Info("Registered " + num_colours + " colourMap entries"); | |
8db09746 | 910 | //Util.Debug("colourMap: " + canvas.get_colourMap()); |
a7a89626 JM |
911 | break; |
912 | case 2: // Bell | |
913 | Util.Warn("Bell (unsupported)"); | |
914 | break; | |
915 | case 3: // ServerCutText | |
916 | Util.Debug("ServerCutText"); | |
917 | Util.Debug("RQ:" + RQ.slice(0,20)); | |
8db09746 JM |
918 | if (cuttext === 'none') { |
919 | cuttext = 'header'; | |
a7a89626 | 920 | } |
8db09746 | 921 | if (cuttext === 'header') { |
a7a89626 JM |
922 | if (RQ.length < 7) { |
923 | //Util.Debug("waiting for ServerCutText header"); | |
924 | return false; | |
925 | } | |
926 | RQ.shiftBytes(3); // Padding | |
8db09746 | 927 | cuttext_length = RQ.shift32(); |
a7a89626 | 928 | } |
8db09746 JM |
929 | cuttext = 'bytes'; |
930 | if (RQ.length < cuttext_length) { | |
a7a89626 JM |
931 | //Util.Debug("waiting for ServerCutText bytes"); |
932 | return false; | |
933 | } | |
8db09746 JM |
934 | conf.clipboardReceive(that, RQ.shiftStr(cuttext_length)); |
935 | cuttext = 'none'; | |
a7a89626 JM |
936 | break; |
937 | default: | |
8db09746 | 938 | updateState('failed', |
a7a89626 JM |
939 | "Disconnected: illegal server message type " + msg_type); |
940 | Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); | |
941 | break; | |
942 | } | |
943 | //Util.Debug("<< normal_msg"); | |
944 | return ret; | |
8db09746 | 945 | }; |
a7a89626 | 946 | |
8db09746 JM |
947 | framebufferUpdate = function() { |
948 | var now, fbu_rt_diff, last_bytes, last_rects, ret = true; | |
a7a89626 JM |
949 | |
950 | if (FBU.rects === 0) { | |
951 | //Util.Debug("New FBU: RQ.slice(0,20): " + RQ.slice(0,20)); | |
952 | if (RQ.length < 3) { | |
953 | RQ.unshift(0); // FBU msg_type | |
954 | Util.Debug(" waiting for FBU header bytes"); | |
955 | return false; | |
956 | } | |
957 | RQ.shift8(); | |
958 | FBU.rects = RQ.shift16(); | |
959 | //Util.Debug("FramebufferUpdate, rects:" + FBU.rects); | |
960 | FBU.bytes = 0; | |
961 | timing.cur_fbu = 0; | |
962 | timing.h_fbus += 1; | |
963 | if (timing.fbu_rt_start > 0) { | |
964 | now = (new Date()).getTime(); | |
965 | Util.Info("First FBU latency: " + (now - timing.fbu_rt_start)); | |
966 | } | |
967 | } | |
968 | ||
42b2246c | 969 | while (FBU.rects > 0) { |
8db09746 | 970 | if (rfb_state !== "normal") { |
42b2246c JM |
971 | return false; |
972 | } | |
973 | if (RQ.length < FBU.bytes) { | |
974 | return false; | |
975 | } | |
a7a89626 JM |
976 | if (FBU.bytes === 0) { |
977 | if (RQ.length < 12) { | |
978 | //Util.Debug(" waiting for rect header bytes"); | |
979 | return false; | |
980 | } | |
981 | /* New FramebufferUpdate */ | |
982 | FBU.x = RQ.shift16(); | |
983 | FBU.y = RQ.shift16(); | |
984 | FBU.width = RQ.shift16(); | |
985 | FBU.height = RQ.shift16(); | |
986 | FBU.encoding = parseInt(RQ.shift32(), 10); | |
987 | timing.h_bytes += 12; | |
988 | ||
8db09746 | 989 | if (encNames[FBU.encoding]) { |
a7a89626 JM |
990 | // Debug: |
991 | /* | |
992 | var msg = "FramebufferUpdate rects:" + FBU.rects; | |
993 | msg += " x: " + FBU.x + " y: " + FBU.y | |
994 | msg += " width: " + FBU.width + " height: " + FBU.height; | |
995 | msg += " encoding:" + FBU.encoding; | |
8db09746 | 996 | msg += "(" + encNames[FBU.encoding] + ")"; |
a7a89626 JM |
997 | msg += ", RQ.length: " + RQ.length; |
998 | Util.Debug(msg); | |
999 | */ | |
1000 | } else { | |
8db09746 | 1001 | updateState('failed', |
a7a89626 JM |
1002 | "Disconnected: unsupported encoding " + |
1003 | FBU.encoding); | |
1004 | return false; | |
1005 | } | |
1006 | } | |
1007 | ||
1008 | timing.last_fbu = (new Date()).getTime(); | |
1009 | last_bytes = RQ.length; | |
1010 | last_rects = FBU.rects; | |
1011 | ||
42b2246c | 1012 | // false ret means need more data |
8db09746 | 1013 | ret = encHandlers[FBU.encoding](); |
a7a89626 JM |
1014 | |
1015 | now = (new Date()).getTime(); | |
1016 | timing.cur_fbu += (now - timing.last_fbu); | |
1017 | timing.h_bytes += last_bytes-RQ.length; | |
1018 | ||
1019 | if (FBU.rects < last_rects) { | |
1020 | // Some work was done | |
1021 | timing.h_rects += last_rects-FBU.rects; | |
1022 | timing.h_pixels += FBU.width*FBU.height; | |
1023 | } | |
1024 | ||
1025 | if (FBU.rects === 0) { | |
8db09746 JM |
1026 | if (((FBU.width === fb_width) && |
1027 | (FBU.height === fb_height)) || | |
a7a89626 JM |
1028 | (timing.fbu_rt_start > 0)) { |
1029 | timing.full_fbu_total += timing.cur_fbu; | |
1030 | timing.full_fbu_cnt += 1; | |
1031 | Util.Info("Timing of full FBU, cur: " + | |
1032 | timing.cur_fbu + ", total: " + | |
1033 | timing.full_fbu_total + ", cnt: " + | |
1034 | timing.full_fbu_cnt + ", avg: " + | |
1035 | (timing.full_fbu_total / | |
1036 | timing.full_fbu_cnt)); | |
1037 | } | |
1038 | if (timing.fbu_rt_start > 0) { | |
1039 | fbu_rt_diff = now - timing.fbu_rt_start; | |
1040 | timing.fbu_rt_total += fbu_rt_diff; | |
1041 | timing.fbu_rt_cnt += 1; | |
1042 | Util.Info("full FBU round-trip, cur: " + | |
1043 | fbu_rt_diff + ", total: " + | |
1044 | timing.fbu_rt_total + ", cnt: " + | |
1045 | timing.fbu_rt_cnt + ", avg: " + | |
1046 | (timing.fbu_rt_total / | |
1047 | timing.fbu_rt_cnt)); | |
1048 | timing.fbu_rt_start = 0; | |
1049 | } | |
1050 | } | |
a7a89626 JM |
1051 | } |
1052 | return ret; | |
8db09746 | 1053 | }; |
a7a89626 | 1054 | |
8db09746 JM |
1055 | // |
1056 | // FramebufferUpdate encodings | |
1057 | // | |
a7a89626 | 1058 | |
8db09746 | 1059 | encHandlers.RAW = function display_raw() { |
a7a89626 JM |
1060 | //Util.Debug(">> display_raw"); |
1061 | ||
8db09746 | 1062 | var cur_y, cur_height; |
a7a89626 JM |
1063 | |
1064 | if (FBU.lines === 0) { | |
1065 | FBU.lines = FBU.height; | |
1066 | } | |
8db09746 | 1067 | FBU.bytes = FBU.width * fb_Bpp; // At least a line |
a7a89626 JM |
1068 | if (RQ.length < FBU.bytes) { |
1069 | //Util.Debug(" waiting for " + | |
1070 | // (FBU.bytes - RQ.length) + " RAW bytes"); | |
42b2246c | 1071 | return false; |
a7a89626 JM |
1072 | } |
1073 | cur_y = FBU.y + (FBU.height - FBU.lines); | |
1074 | cur_height = Math.min(FBU.lines, | |
8db09746 JM |
1075 | Math.floor(RQ.length/(FBU.width * fb_Bpp))); |
1076 | canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); | |
1077 | RQ.shiftBytes(FBU.width * cur_height * fb_Bpp); | |
a7a89626 JM |
1078 | FBU.lines -= cur_height; |
1079 | ||
1080 | if (FBU.lines > 0) { | |
8db09746 | 1081 | FBU.bytes = FBU.width * fb_Bpp; // At least another line |
a7a89626 JM |
1082 | } else { |
1083 | FBU.rects -= 1; | |
1084 | FBU.bytes = 0; | |
1085 | } | |
42b2246c | 1086 | return true; |
8db09746 | 1087 | }; |
a7a89626 | 1088 | |
8db09746 | 1089 | encHandlers.COPYRECT = function display_copy_rect() { |
a7a89626 JM |
1090 | //Util.Debug(">> display_copy_rect"); |
1091 | ||
8db09746 | 1092 | var old_x, old_y; |
a7a89626 JM |
1093 | |
1094 | if (RQ.length < 4) { | |
1095 | //Util.Debug(" waiting for " + | |
1096 | // (FBU.bytes - RQ.length) + " COPYRECT bytes"); | |
42b2246c | 1097 | return false; |
a7a89626 JM |
1098 | } |
1099 | old_x = RQ.shift16(); | |
1100 | old_y = RQ.shift16(); | |
8db09746 | 1101 | canvas.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height); |
a7a89626 JM |
1102 | FBU.rects -= 1; |
1103 | FBU.bytes = 0; | |
42b2246c | 1104 | return true; |
8db09746 JM |
1105 | }; |
1106 | ||
1107 | encHandlers.RRE = function display_rre() { | |
1108 | //Util.Debug(">> display_rre (" + RQ.length + " bytes)"); | |
1109 | var color, x, y, width, height, chunk; | |
a7a89626 | 1110 | |
a7a89626 | 1111 | if (FBU.subrects === 0) { |
8db09746 | 1112 | if (RQ.length < 4 + fb_Bpp) { |
a7a89626 | 1113 | //Util.Debug(" waiting for " + |
8db09746 | 1114 | // (4 + fb_Bpp - RQ.length) + " RRE bytes"); |
42b2246c | 1115 | return false; |
a7a89626 JM |
1116 | } |
1117 | FBU.subrects = RQ.shift32(); | |
8db09746 JM |
1118 | color = RQ.shiftBytes(fb_Bpp); // Background |
1119 | canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); | |
a7a89626 | 1120 | } |
8db09746 JM |
1121 | while ((FBU.subrects > 0) && (RQ.length >= (fb_Bpp + 8))) { |
1122 | color = RQ.shiftBytes(fb_Bpp); | |
a7a89626 JM |
1123 | x = RQ.shift16(); |
1124 | y = RQ.shift16(); | |
1125 | width = RQ.shift16(); | |
1126 | height = RQ.shift16(); | |
8db09746 | 1127 | canvas.fillRect(FBU.x + x, FBU.y + y, width, height, color); |
a7a89626 JM |
1128 | FBU.subrects -= 1; |
1129 | } | |
1130 | //Util.Debug(" display_rre: rects: " + FBU.rects + | |
1131 | // ", FBU.subrects: " + FBU.subrects); | |
1132 | ||
1133 | if (FBU.subrects > 0) { | |
8db09746 JM |
1134 | chunk = Math.min(rre_chunk_sz, FBU.subrects); |
1135 | FBU.bytes = (fb_Bpp + 8) * chunk; | |
a7a89626 JM |
1136 | } else { |
1137 | FBU.rects -= 1; | |
1138 | FBU.bytes = 0; | |
1139 | } | |
1140 | //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes); | |
42b2246c | 1141 | return true; |
8db09746 | 1142 | }; |
a7a89626 | 1143 | |
8db09746 | 1144 | encHandlers.HEXTILE = function display_hextile() { |
a7a89626 | 1145 | //Util.Debug(">> display_hextile"); |
8db09746 | 1146 | var subencoding, subrects, idx, tile, color, cur_tile, |
a7a89626 JM |
1147 | tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh; |
1148 | ||
1149 | if (FBU.tiles === 0) { | |
1150 | FBU.tiles_x = Math.ceil(FBU.width/16); | |
1151 | FBU.tiles_y = Math.ceil(FBU.height/16); | |
1152 | FBU.total_tiles = FBU.tiles_x * FBU.tiles_y; | |
1153 | FBU.tiles = FBU.total_tiles; | |
1154 | } | |
1155 | ||
1156 | /* FBU.bytes comes in as 1, RQ.length at least 1 */ | |
1157 | while (FBU.tiles > 0) { | |
1158 | FBU.bytes = 1; | |
1159 | if (RQ.length < FBU.bytes) { | |
1160 | //Util.Debug(" waiting for HEXTILE subencoding byte"); | |
42b2246c | 1161 | return false; |
a7a89626 JM |
1162 | } |
1163 | subencoding = RQ[0]; // Peek | |
1164 | if (subencoding > 30) { // Raw | |
8db09746 | 1165 | updateState('failed', |
a7a89626 JM |
1166 | "Disconnected: illegal hextile subencoding " + subencoding); |
1167 | //Util.Debug("RQ.slice(0,30):" + RQ.slice(0,30)); | |
42b2246c | 1168 | return false; |
a7a89626 JM |
1169 | } |
1170 | subrects = 0; | |
1171 | cur_tile = FBU.total_tiles - FBU.tiles; | |
1172 | tile_x = cur_tile % FBU.tiles_x; | |
1173 | tile_y = Math.floor(cur_tile / FBU.tiles_x); | |
1174 | x = FBU.x + tile_x * 16; | |
1175 | y = FBU.y + tile_y * 16; | |
1176 | w = Math.min(16, (FBU.x + FBU.width) - x); | |
1177 | h = Math.min(16, (FBU.y + FBU.height) - y); | |
1178 | ||
1179 | /* Figure out how much we are expecting */ | |
1180 | if (subencoding & 0x01) { // Raw | |
1181 | //Util.Debug(" Raw subencoding"); | |
8db09746 | 1182 | FBU.bytes += w * h * fb_Bpp; |
a7a89626 JM |
1183 | } else { |
1184 | if (subencoding & 0x02) { // Background | |
8db09746 | 1185 | FBU.bytes += fb_Bpp; |
a7a89626 JM |
1186 | } |
1187 | if (subencoding & 0x04) { // Foreground | |
8db09746 | 1188 | FBU.bytes += fb_Bpp; |
a7a89626 JM |
1189 | } |
1190 | if (subencoding & 0x08) { // AnySubrects | |
1191 | FBU.bytes += 1; // Since we aren't shifting it off | |
1192 | if (RQ.length < FBU.bytes) { | |
1193 | /* Wait for subrects byte */ | |
1194 | //Util.Debug(" waiting for hextile subrects header byte"); | |
42b2246c | 1195 | return false; |
a7a89626 JM |
1196 | } |
1197 | subrects = RQ[FBU.bytes-1]; // Peek | |
1198 | if (subencoding & 0x10) { // SubrectsColoured | |
8db09746 | 1199 | FBU.bytes += subrects * (fb_Bpp + 2); |
a7a89626 JM |
1200 | } else { |
1201 | FBU.bytes += subrects * 2; | |
1202 | } | |
1203 | } | |
1204 | } | |
1205 | ||
1206 | //Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) + | |
1207 | // ", subencoding:" + subencoding + | |
1208 | // "(last: " + FBU.lastsubencoding + "), subrects:" + | |
1209 | // subrects + ", tile:" + tile_x + "," + tile_y + | |
1210 | // " [" + x + "," + y + "]@" + w + "x" + h + | |
1211 | // ", d.length:" + RQ.length + ", bytes:" + FBU.bytes + | |
1212 | // " last:" + RQ.slice(FBU.bytes-10, FBU.bytes) + | |
1213 | // " next:" + RQ.slice(FBU.bytes-1, FBU.bytes+10)); | |
1214 | if (RQ.length < FBU.bytes) { | |
1215 | //Util.Debug(" waiting for " + | |
1216 | // (FBU.bytes - RQ.length) + " hextile bytes"); | |
42b2246c | 1217 | return false; |
a7a89626 JM |
1218 | } |
1219 | ||
1220 | /* We know the encoding and have a whole tile */ | |
1221 | FBU.subencoding = RQ[0]; | |
1222 | idx = 1; | |
1223 | if (FBU.subencoding === 0) { | |
1224 | if (FBU.lastsubencoding & 0x01) { | |
1225 | /* Weird: ignore blanks after RAW */ | |
1226 | Util.Debug(" Ignoring blank after RAW"); | |
1227 | } else { | |
8db09746 | 1228 | canvas.fillRect(x, y, w, h, FBU.background); |
a7a89626 JM |
1229 | } |
1230 | } else if (FBU.subencoding & 0x01) { // Raw | |
8db09746 | 1231 | canvas.blitImage(x, y, w, h, RQ, idx); |
a7a89626 JM |
1232 | } else { |
1233 | if (FBU.subencoding & 0x02) { // Background | |
8db09746 JM |
1234 | FBU.background = RQ.slice(idx, idx + fb_Bpp); |
1235 | idx += fb_Bpp; | |
a7a89626 JM |
1236 | } |
1237 | if (FBU.subencoding & 0x04) { // Foreground | |
8db09746 JM |
1238 | FBU.foreground = RQ.slice(idx, idx + fb_Bpp); |
1239 | idx += fb_Bpp; | |
a7a89626 JM |
1240 | } |
1241 | ||
8db09746 | 1242 | tile = canvas.getTile(x, y, w, h, FBU.background); |
a7a89626 JM |
1243 | if (FBU.subencoding & 0x08) { // AnySubrects |
1244 | subrects = RQ[idx]; | |
1245 | idx += 1; | |
1246 | for (s = 0; s < subrects; s += 1) { | |
1247 | if (FBU.subencoding & 0x10) { // SubrectsColoured | |
8db09746 JM |
1248 | color = RQ.slice(idx, idx + fb_Bpp); |
1249 | idx += fb_Bpp; | |
a7a89626 JM |
1250 | } else { |
1251 | color = FBU.foreground; | |
1252 | } | |
1253 | xy = RQ[idx]; | |
1254 | idx += 1; | |
1255 | sx = (xy >> 4); | |
1256 | sy = (xy & 0x0f); | |
1257 | ||
1258 | wh = RQ[idx]; | |
1259 | idx += 1; | |
1260 | sw = (wh >> 4) + 1; | |
1261 | sh = (wh & 0x0f) + 1; | |
1262 | ||
8db09746 | 1263 | canvas.setSubTile(tile, sx, sy, sw, sh, color); |
a7a89626 JM |
1264 | } |
1265 | } | |
8db09746 | 1266 | canvas.putTile(tile); |
a7a89626 JM |
1267 | } |
1268 | RQ.shiftBytes(FBU.bytes); | |
1269 | FBU.lastsubencoding = FBU.subencoding; | |
1270 | FBU.bytes = 0; | |
1271 | FBU.tiles -= 1; | |
1272 | } | |
1273 | ||
1274 | if (FBU.tiles === 0) { | |
1275 | FBU.rects -= 1; | |
1276 | } | |
1277 | ||
1278 | //Util.Debug("<< display_hextile"); | |
42b2246c | 1279 | return true; |
8db09746 | 1280 | }; |
a7a89626 JM |
1281 | |
1282 | ||
8db09746 | 1283 | encHandlers.TIGHT_PNG = function display_tight_png() { |
a7a89626 | 1284 | //Util.Debug(">> display_tight_png"); |
8db09746 | 1285 | var ctl, cmode, clength, getCLength, color, img; |
a7a89626 JM |
1286 | //Util.Debug(" FBU.rects: " + FBU.rects); |
1287 | //Util.Debug(" RQ.length: " + RQ.length); | |
1288 | //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20)); | |
1289 | ||
1290 | ||
1291 | FBU.bytes = 1; // compression-control byte | |
1292 | if (RQ.length < FBU.bytes) { | |
1293 | Util.Debug(" waiting for TIGHT compression-control byte"); | |
42b2246c | 1294 | return false; |
a7a89626 JM |
1295 | } |
1296 | ||
1297 | // Get 'compact length' header and data size | |
1298 | getCLength = function (arr, offset) { | |
1299 | var header = 1, data = 0; | |
1300 | data += arr[offset + 0] & 0x7f; | |
1301 | if (arr[offset + 0] & 0x80) { | |
1302 | header += 1; | |
1303 | data += (arr[offset + 1] & 0x7f) << 7; | |
1304 | if (arr[offset + 1] & 0x80) { | |
1305 | header += 1; | |
1306 | data += arr[offset + 2] << 14; | |
1307 | } | |
1308 | } | |
1309 | return [header, data]; | |
1310 | }; | |
1311 | ||
1312 | ctl = RQ[0]; | |
1313 | switch (ctl >> 4) { | |
1314 | case 0x08: cmode = "fill"; break; | |
1315 | case 0x09: cmode = "jpeg"; break; | |
1316 | case 0x0A: cmode = "png"; break; | |
1317 | default: throw("Illegal basic compression received, ctl: " + ctl); | |
1318 | } | |
1319 | switch (cmode) { | |
1320 | // fill uses fb_depth because TPIXELs drop the padding byte | |
8db09746 | 1321 | case "fill": FBU.bytes += fb_depth; break; // TPIXEL |
a7a89626 JM |
1322 | case "jpeg": FBU.bytes += 3; break; // max clength |
1323 | case "png": FBU.bytes += 3; break; // max clength | |
1324 | } | |
1325 | ||
1326 | if (RQ.length < FBU.bytes) { | |
1327 | Util.Debug(" waiting for TIGHT " + cmode + " bytes"); | |
42b2246c | 1328 | return false; |
a7a89626 JM |
1329 | } |
1330 | ||
8db09746 | 1331 | //Util.Debug(" RQ.slice(0,20): " + RQ.slice(0,20) + " (" + RQ.length + ")"); |
a7a89626 JM |
1332 | //Util.Debug(" cmode: " + cmode); |
1333 | ||
1334 | // Determine FBU.bytes | |
1335 | switch (cmode) { | |
1336 | case "fill": | |
1337 | RQ.shift8(); // shift off ctl | |
8db09746 JM |
1338 | color = RQ.shiftBytes(fb_depth); |
1339 | canvas.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color); | |
a7a89626 JM |
1340 | break; |
1341 | case "jpeg": | |
1342 | case "png": | |
1343 | clength = getCLength(RQ, 1); | |
1344 | FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data | |
1345 | if (RQ.length < FBU.bytes) { | |
1346 | Util.Debug(" waiting for TIGHT " + cmode + " bytes"); | |
42b2246c | 1347 | return false; |
a7a89626 JM |
1348 | } |
1349 | ||
1350 | // We have everything, render it | |
1351 | //Util.Debug(" png, RQ.length: " + RQ.length + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]); | |
1352 | RQ.shiftBytes(1 + clength[0]); // shift off ctl + compact length | |
1353 | img = new Image(); | |
8db09746 | 1354 | img.onload = scan_tight_imgs; |
a7a89626 JM |
1355 | FBU.imgs.push([img, FBU.x, FBU.y]); |
1356 | img.src = "data:image/" + cmode + | |
8db09746 | 1357 | extract_data_uri(RQ.shiftBytes(clength[1])); |
a7a89626 JM |
1358 | img = null; |
1359 | break; | |
1360 | } | |
1361 | FBU.bytes = 0; | |
1362 | FBU.rects -= 1; | |
1363 | //Util.Debug(" ending RQ.length: " + RQ.length); | |
1364 | //Util.Debug(" ending RQ.slice(0,20): " + RQ.slice(0,20)); | |
1365 | //Util.Debug("<< display_tight_png"); | |
42b2246c | 1366 | return true; |
8db09746 | 1367 | }; |
a7a89626 | 1368 | |
8db09746 | 1369 | extract_data_uri = function(arr) { |
a7a89626 JM |
1370 | //var i, stra = []; |
1371 | //for (i=0; i< arr.length; i += 1) { | |
1372 | // stra.push(String.fromCharCode(arr[i])); | |
1373 | //} | |
1374 | //return "," + escape(stra.join('')); | |
1375 | return ";base64," + Base64.encode(arr); | |
8db09746 | 1376 | }; |
a7a89626 | 1377 | |
8db09746 JM |
1378 | scan_tight_imgs = function() { |
1379 | var img, imgs, ctx; | |
1380 | ctx = canvas.getContext(); | |
1381 | if (rfb_state === 'normal') { | |
1382 | imgs = FBU.imgs; | |
a7a89626 JM |
1383 | while ((imgs.length > 0) && (imgs[0][0].complete)) { |
1384 | img = imgs.shift(); | |
8db09746 | 1385 | ctx.drawImage(img[0], img[1], img[2]); |
a7a89626 | 1386 | } |
8db09746 | 1387 | setTimeout(scan_tight_imgs, scan_imgs_rate); |
a7a89626 | 1388 | } |
8db09746 | 1389 | }; |
a7a89626 | 1390 | |
8db09746 JM |
1391 | encHandlers.DesktopSize = function set_desktopsize() { |
1392 | Util.Debug(">> set_desktopsize"); | |
1393 | fb_width = FBU.width; | |
1394 | fb_height = FBU.height; | |
1395 | canvas.clear(); | |
1396 | canvas.resize(fb_width, fb_height); | |
1397 | timing.fbu_rt_start = (new Date()).getTime(); | |
1398 | // Send a new non-incremental request | |
1399 | send_array(fbUpdateRequest(0)); | |
a7a89626 | 1400 | |
8db09746 JM |
1401 | FBU.bytes = 0; |
1402 | FBU.rects -= 1; | |
a7a89626 | 1403 | |
8db09746 JM |
1404 | Util.Debug("<< set_desktopsize"); |
1405 | return true; | |
1406 | }; | |
a7a89626 | 1407 | |
8db09746 JM |
1408 | encHandlers.Cursor = function set_cursor() { |
1409 | var x, y, w, h, pixelslength, masklength; | |
1410 | //Util.Debug(">> set_cursor"); | |
1411 | x = FBU.x; // hotspot-x | |
1412 | y = FBU.y; // hotspot-y | |
1413 | w = FBU.width; | |
1414 | h = FBU.height; | |
a7a89626 | 1415 | |
8db09746 JM |
1416 | pixelslength = w * h * fb_Bpp; |
1417 | masklength = Math.floor((w + 7) / 8) * h; | |
a7a89626 | 1418 | |
8db09746 JM |
1419 | if (RQ.length < (pixelslength + masklength)) { |
1420 | //Util.Debug("waiting for cursor encoding bytes"); | |
1421 | FBU.bytes = pixelslength + masklength; | |
1422 | return false; | |
a7a89626 JM |
1423 | } |
1424 | ||
8db09746 | 1425 | //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h); |
a7a89626 | 1426 | |
8db09746 JM |
1427 | canvas.changeCursor(RQ.shiftBytes(pixelslength), |
1428 | RQ.shiftBytes(masklength), | |
1429 | x, y, w, h); | |
a7a89626 | 1430 | |
8db09746 JM |
1431 | FBU.bytes = 0; |
1432 | FBU.rects -= 1; | |
a7a89626 | 1433 | |
8db09746 JM |
1434 | //Util.Debug("<< set_cursor"); |
1435 | return true; | |
1436 | }; | |
a7a89626 | 1437 | |
8db09746 JM |
1438 | encHandlers.JPEG_quality_lo = function set_jpeg_quality() { |
1439 | Util.Error("Server sent jpeg_quality pseudo-encoding"); | |
1440 | }; | |
a7a89626 | 1441 | |
8db09746 JM |
1442 | encHandlers.compress_lo = function set_compress_level() { |
1443 | Util.Error("Server sent compress level pseudo-encoding"); | |
1444 | }; | |
a7a89626 | 1445 | |
8db09746 JM |
1446 | /* |
1447 | * Client message routines | |
1448 | */ | |
a7a89626 | 1449 | |
8db09746 JM |
1450 | pixelFormat = function() { |
1451 | //Util.Debug(">> pixelFormat"); | |
1452 | var arr; | |
1453 | arr = [0]; // msg-type | |
1454 | arr.push8(0); // padding | |
1455 | arr.push8(0); // padding | |
1456 | arr.push8(0); // padding | |
a7a89626 | 1457 | |
8db09746 JM |
1458 | arr.push8(fb_Bpp * 8); // bits-per-pixel |
1459 | arr.push8(fb_depth * 8); // depth | |
1460 | arr.push8(0); // little-endian | |
1461 | arr.push8(conf.true_color ? 1 : 0); // true-color | |
a7a89626 | 1462 | |
8db09746 JM |
1463 | arr.push16(255); // red-max |
1464 | arr.push16(255); // green-max | |
1465 | arr.push16(255); // blue-max | |
1466 | arr.push8(0); // red-shift | |
1467 | arr.push8(8); // green-shift | |
1468 | arr.push8(16); // blue-shift | |
a7a89626 | 1469 | |
8db09746 JM |
1470 | arr.push8(0); // padding |
1471 | arr.push8(0); // padding | |
1472 | arr.push8(0); // padding | |
1473 | //Util.Debug("<< pixelFormat"); | |
1474 | return arr; | |
1475 | }; | |
a7a89626 | 1476 | |
8db09746 JM |
1477 | clientEncodings = function() { |
1478 | //Util.Debug(">> clientEncodings"); | |
1479 | var arr, i, encList = []; | |
a7a89626 | 1480 | |
8db09746 JM |
1481 | for (i=0; i<encodings.length; i += 1) { |
1482 | if ((encodings[i][0] === "Cursor") && | |
1483 | (! conf.local_cursor)) { | |
1484 | Util.Debug("Skipping Cursor pseudo-encoding"); | |
1485 | } else { | |
1486 | //Util.Debug("Adding encoding: " + encodings[i][0]); | |
1487 | encList.push(encodings[i][1]); | |
1488 | } | |
1489 | } | |
a7a89626 | 1490 | |
8db09746 JM |
1491 | arr = [2]; // msg-type |
1492 | arr.push8(0); // padding | |
a7a89626 | 1493 | |
8db09746 JM |
1494 | arr.push16(encList.length); // encoding count |
1495 | for (i=0; i < encList.length; i += 1) { | |
1496 | arr.push32(encList[i]); | |
1497 | } | |
1498 | //Util.Debug("<< clientEncodings: " + arr); | |
1499 | return arr; | |
1500 | }; | |
a7a89626 | 1501 | |
8db09746 JM |
1502 | fbUpdateRequest = function(incremental, x, y, xw, yw) { |
1503 | //Util.Debug(">> fbUpdateRequest"); | |
1504 | if (!x) { x = 0; } | |
1505 | if (!y) { y = 0; } | |
1506 | if (!xw) { xw = fb_width; } | |
1507 | if (!yw) { yw = fb_height; } | |
1508 | var arr; | |
1509 | arr = [3]; // msg-type | |
1510 | arr.push8(incremental); | |
1511 | arr.push16(x); | |
1512 | arr.push16(y); | |
1513 | arr.push16(xw); | |
1514 | arr.push16(yw); | |
1515 | //Util.Debug("<< fbUpdateRequest"); | |
1516 | return arr; | |
1517 | }; | |
a7a89626 | 1518 | |
8db09746 JM |
1519 | keyEvent = function(keysym, down) { |
1520 | //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down); | |
1521 | var arr; | |
1522 | arr = [4]; // msg-type | |
1523 | arr.push8(down); | |
1524 | arr.push16(0); | |
1525 | arr.push32(keysym); | |
1526 | //Util.Debug("<< keyEvent"); | |
1527 | return arr; | |
1528 | }; | |
a7a89626 | 1529 | |
8db09746 JM |
1530 | pointerEvent = function(x, y) { |
1531 | //Util.Debug(">> pointerEvent, x,y: " + x + "," + y + | |
1532 | // " , mask: " + mouse_buttonMask); | |
1533 | var arr; | |
1534 | arr = [5]; // msg-type | |
1535 | arr.push8(mouse_buttonMask); | |
1536 | arr.push16(x); | |
1537 | arr.push16(y); | |
1538 | //Util.Debug("<< pointerEvent"); | |
1539 | return arr; | |
1540 | }; | |
a7a89626 | 1541 | |
8db09746 JM |
1542 | clientCutText = function(text) { |
1543 | //Util.Debug(">> clientCutText"); | |
1544 | var arr; | |
1545 | arr = [6]; // msg-type | |
1546 | arr.push8(0); // padding | |
1547 | arr.push8(0); // padding | |
1548 | arr.push8(0); // padding | |
1549 | arr.push32(text.length); | |
1550 | arr.pushStr(text); | |
1551 | //Util.Debug("<< clientCutText:" + arr); | |
1552 | return arr; | |
1553 | }; | |
a7a89626 | 1554 | |
a7a89626 | 1555 | |
a7a89626 | 1556 | |
8db09746 JM |
1557 | // |
1558 | // Public API interface functions | |
1559 | // | |
a7a89626 | 1560 | |
8db09746 | 1561 | that.init = function () { |
a7a89626 | 1562 | |
8db09746 | 1563 | init_vars(); |
a7a89626 | 1564 | |
8db09746 JM |
1565 | /* Check web-socket-js if no builtin WebSocket support */ |
1566 | if (VNC_native_ws) { | |
1567 | Util.Info("Using native WebSockets"); | |
1568 | updateState('loaded', 'noVNC ready (using native WebSockets)'); | |
a7a89626 | 1569 | } else { |
8db09746 JM |
1570 | Util.Warn("Using web-socket-js flash bridge"); |
1571 | if ((! Util.Flash) || | |
1572 | (Util.Flash.version < 9)) { | |
1573 | updateState('fatal', "WebSockets or Adobe Flash is required"); | |
1574 | } else if (document.location.href.substr(0, 7) === "file://") { | |
1575 | updateState('fatal', | |
1576 | "'file://' URL is incompatible with Adobe Flash"); | |
a7a89626 | 1577 | } else { |
8db09746 | 1578 | updateState('loaded', 'noVNC ready (using Flash WebSockets emulation)'); |
a7a89626 JM |
1579 | } |
1580 | } | |
8db09746 | 1581 | }; |
a7a89626 | 1582 | |
8db09746 JM |
1583 | that.connect = function(host, port, password) { |
1584 | //Util.Debug(">> connect"); | |
a7a89626 | 1585 | |
8db09746 JM |
1586 | // Make sure we have done init checks |
1587 | if ((rfb_state !== 'loaded') && (rfb_state !== 'fatal')) { | |
1588 | that.init(); | |
a7a89626 | 1589 | } |
a7a89626 | 1590 | |
8db09746 JM |
1591 | rfb_host = host; |
1592 | rfb_port = port; | |
1593 | rfb_password = (password !== undefined) ? password : ""; | |
a7a89626 | 1594 | |
8db09746 JM |
1595 | if ((!rfb_host) || (!rfb_port)) { |
1596 | updateState('failed', "Must set host and port"); | |
1597 | return; | |
a7a89626 | 1598 | } |
a7a89626 | 1599 | |
8db09746 JM |
1600 | updateState('connect'); |
1601 | //Util.Debug("<< connect"); | |
a7a89626 | 1602 | |
8db09746 | 1603 | }; |
a7a89626 | 1604 | |
8db09746 JM |
1605 | that.disconnect = function() { |
1606 | //Util.Debug(">> disconnect"); | |
1607 | updateState('disconnected', 'Disconnected'); | |
1608 | //Util.Debug("<< disconnect"); | |
1609 | }; | |
a7a89626 | 1610 | |
8db09746 JM |
1611 | that.sendPassword = function(passwd) { |
1612 | rfb_password = passwd; | |
1613 | rfb_state = "Authentication"; | |
1614 | setTimeout(init_msg, 1); | |
1615 | }; | |
1616 | ||
1617 | that.sendCtrlAltDel = function() { | |
1618 | if (rfb_state !== "normal") { return false; } | |
1619 | Util.Info("Sending Ctrl-Alt-Del"); | |
1620 | var arr = []; | |
1621 | arr = arr.concat(keyEvent(0xFFE3, 1)); // Control | |
1622 | arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt | |
1623 | arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete | |
1624 | arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete | |
1625 | arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt | |
1626 | arr = arr.concat(keyEvent(0xFFE3, 0)); // Control | |
1627 | arr = arr.concat(fbUpdateRequest(1)); | |
1628 | send_array(arr); | |
1629 | }; | |
1630 | ||
1631 | that.clipboardPasteFrom = function(text) { | |
1632 | if (rfb_state !== "normal") { return; } | |
1633 | //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "..."); | |
1634 | send_array(clientCutText(text)); | |
1635 | //Util.Debug("<< clipboardPasteFrom"); | |
1636 | }; | |
1637 | ||
1638 | that.testMode = function(override_send_array) { | |
1639 | // Overridable internal functions for testing | |
1640 | test_mode = true; | |
1641 | send_array = override_send_array; | |
1642 | that.recv_message = recv_message; // Expose it | |
1643 | ||
1644 | checkEvents = function () { /* Stub Out */ }; | |
1645 | that.connect = function(host, port, password) { | |
1646 | rfb_host = host; | |
1647 | rfb_port = port; | |
1648 | rfb_password = password; | |
1649 | updateState('ProtocolVersion', "Starting VNC handshake"); | |
1650 | }; | |
1651 | }; | |
1652 | ||
1653 | ||
1654 | return constructor(); // Return the public API interface | |
a7a89626 | 1655 | |
8db09746 | 1656 | } // End of RFB() |