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