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