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