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