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