1/* 2 * noVNC: HTML5 VNC client 3 * Copyright (C) 2012 Joel Martin 4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB 5 * Licensed under MPL 2.0 (see LICENSE.txt) 6 * 7 * See README.md for usage and integration instructions. 8 */ 9 10/* jslint white: false, browser: true */ 11/* global window, $D, Util, WebUtil, RFB, Display */ 12 13var UI; 14 15(function () { 16 "use strict"; 17 18 // Load supporting scripts 19 window.onscriptsload = function () { UI.load(); }; 20 window.onload = function () { UI.keyboardinputReset(); }; 21 Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js", 22 "keysymdef.js", "keyboard.js", "input.js", "display.js", 23 "jsunzip.js", "rfb.js", "keysym.js"]); 24 25 var UI = { 26 27 rfb_state : 'loaded', 28 settingsOpen : false, 29 connSettingsOpen : false, 30 popupStatusOpen : false, 31 clipboardOpen: false, 32 keyboardVisible: false, 33 hideKeyboardTimeout: null, 34 lastKeyboardinput: null, 35 defaultKeyboardinputLen: 100, 36 extraKeysVisible: false, 37 ctrlOn: false, 38 altOn: false, 39 isTouchDevice: false, 40 41 // Setup rfb object, load settings from browser storage, then call 42 // UI.init to setup the UI/menus 43 load: function (callback) { 44 WebUtil.initSettings(UI.start, callback); 45 }, 46 47 // Render default UI and initialize settings menu 48 start: function(callback) { 49 UI.isTouchDevice = 'ontouchstart' in document.documentElement; 50 51 // Stylesheet selection dropdown 52 var sheet = WebUtil.selectStylesheet(); 53 var sheets = WebUtil.getStylesheets(); 54 var i; 55 for (i = 0; i < sheets.length; i += 1) { 56 UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title); 57 } 58 59 // Logging selection dropdown 60 var llevels = ['error', 'warn', 'info', 'debug']; 61 for (i = 0; i < llevels.length; i += 1) { 62 UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]); 63 } 64 65 // Settings with immediate effects 66 UI.initSetting('logging', 'warn'); 67 WebUtil.init_logging(UI.getSetting('logging')); 68 69 UI.initSetting('stylesheet', 'default'); 70 WebUtil.selectStylesheet(null); 71 // call twice to get around webkit bug 72 WebUtil.selectStylesheet(UI.getSetting('stylesheet')); 73 74 // if port == 80 (or 443) then it won't be present and should be 75 // set manually 76 var port = window.location.port; 77 if (!port) { 78 if (window.location.protocol.substring(0,5) == 'https') { 79 port = 443; 80 } 81 else if (window.location.protocol.substring(0,4) == 'http') { 82 port = 80; 83 } 84 } 85 86 /* Populate the controls if defaults are provided in the URL */ 87 UI.initSetting('host', window.location.hostname); 88 UI.initSetting('port', port); 89 UI.initSetting('password', ''); 90 UI.initSetting('encrypt', (window.location.protocol === "https:")); 91 UI.initSetting('true_color', true); 92 UI.initSetting('cursor', !UI.isTouchDevice); 93 UI.initSetting('shared', true); 94 UI.initSetting('view_only', false); 95 UI.initSetting('path', 'websockify'); 96 UI.initSetting('repeaterID', ''); 97 98 UI.rfb = new RFB({'target': $D('noVNC_canvas'), 99 'onUpdateState': UI.updateState, 100 'onXvpInit': UI.updateXvpVisualState, 101 'onClipboard': UI.clipReceive, 102 'onDesktopName': UI.updateDocumentTitle}); 103 104 var autoconnect = WebUtil.getQueryVar('autoconnect', false); 105 if (autoconnect === 'true' || autoconnect == '1') { 106 autoconnect = true; 107 UI.connect(); 108 } else { 109 autoconnect = false; 110 } 111 112 UI.updateVisualState(); 113 114 // Show mouse selector buttons on touch screen devices 115 if (UI.isTouchDevice) { 116 // Show mobile buttons 117 $D('noVNC_mobile_buttons').style.display = "inline"; 118 UI.setMouseButton(); 119 // Remove the address bar 120 setTimeout(function() { window.scrollTo(0, 1); }, 100); 121 UI.forceSetting('clip', true); 122 $D('noVNC_clip').disabled = true; 123 } else { 124 UI.initSetting('clip', false); 125 } 126 127 //iOS Safari does not support CSS position:fixed. 128 //This detects iOS devices and enables javascript workaround. 129 if ((navigator.userAgent.match(/iPhone/i)) || 130 (navigator.userAgent.match(/iPod/i)) || 131 (navigator.userAgent.match(/iPad/i))) { 132 //UI.setOnscroll(); 133 //UI.setResize(); 134 } 135 UI.setBarPosition(); 136 137 $D('noVNC_host').focus(); 138 139 UI.setViewClip(); 140 Util.addEvent(window, 'resize', UI.setViewClip); 141 142 Util.addEvent(window, 'beforeunload', function () { 143 if (UI.rfb_state === 'normal') { 144 return "You are currently connected."; 145 } 146 } ); 147 148 // Show description by default when hosted at for kanaka.github.com 149 if (location.host === "kanaka.github.io") { 150 // Open the description dialog 151 $D('noVNC_description').style.display = "block"; 152 } else { 153 // Show the connect panel on first load unless autoconnecting 154 if (autoconnect === UI.connSettingsOpen) { 155 UI.toggleConnectPanel(); 156 } 157 } 158 159 // Add mouse event click/focus/blur event handlers to the UI 160 UI.addMouseHandlers(); 161 162 if (typeof callback === "function") { 163 callback(UI.rfb); 164 } 165 }, 166 167 addMouseHandlers: function() { 168 // Setup interface handlers that can't be inline 169 $D("noVNC_view_drag_button").onclick = UI.setViewDrag; 170 $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); }; 171 $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); }; 172 $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); }; 173 $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); }; 174 $D("showKeyboard").onclick = UI.showKeyboard; 175 176 $D("keyboardinput").oninput = UI.keyInput; 177 $D("keyboardinput").onblur = UI.keyInputBlur; 178 179 $D("showExtraKeysButton").onclick = UI.showExtraKeys; 180 $D("toggleCtrlButton").onclick = UI.toggleCtrl; 181 $D("toggleAltButton").onclick = UI.toggleAlt; 182 $D("sendTabButton").onclick = UI.sendTab; 183 $D("sendEscButton").onclick = UI.sendEsc; 184 185 $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel; 186 $D("xvpShutdownButton").onclick = UI.xvpShutdown; 187 $D("xvpRebootButton").onclick = UI.xvpReboot; 188 $D("xvpResetButton").onclick = UI.xvpReset; 189 $D("noVNC_status").onclick = UI.togglePopupStatusPanel; 190 $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel; 191 $D("xvpButton").onclick = UI.toggleXvpPanel; 192 $D("clipboardButton").onclick = UI.toggleClipboardPanel; 193 $D("settingsButton").onclick = UI.toggleSettingsPanel; 194 $D("connectButton").onclick = UI.toggleConnectPanel; 195 $D("disconnectButton").onclick = UI.disconnect; 196 $D("descriptionButton").onclick = UI.toggleConnectPanel; 197 198 $D("noVNC_clipboard_text").onfocus = UI.displayBlur; 199 $D("noVNC_clipboard_text").onblur = UI.displayFocus; 200 $D("noVNC_clipboard_text").onchange = UI.clipSend; 201 $D("noVNC_clipboard_clear_button").onclick = UI.clipClear; 202 203 $D("noVNC_settings_menu").onmouseover = UI.displayBlur; 204 $D("noVNC_settings_menu").onmouseover = UI.displayFocus; 205 $D("noVNC_apply").onclick = UI.settingsApply; 206 207 $D("noVNC_connect_button").onclick = UI.connect; 208 }, 209 210 // Read form control compatible setting from cookie 211 getSetting: function(name) { 212 var ctrl = $D('noVNC_' + name); 213 var val = WebUtil.readSetting(name); 214 if (val !== null && ctrl.type === 'checkbox') { 215 if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) { 216 val = false; 217 } else { 218 val = true; 219 } 220 } 221 return val; 222 }, 223 224 // Update cookie and form control setting. If value is not set, then 225 // updates from control to current cookie setting. 226 updateSetting: function(name, value) { 227 228 // Save the cookie for this session 229 if (typeof value !== 'undefined') { 230 WebUtil.writeSetting(name, value); 231 } 232 233 // Update the settings control 234 value = UI.getSetting(name); 235 236 var ctrl = $D('noVNC_' + name); 237 if (ctrl.type === 'checkbox') { 238 ctrl.checked = value; 239 240 } else if (typeof ctrl.options !== 'undefined') { 241 for (var i = 0; i < ctrl.options.length; i += 1) { 242 if (ctrl.options[i].value === value) { 243 ctrl.selectedIndex = i; 244 break; 245 } 246 } 247 } else { 248 /*Weird IE9 error leads to 'null' appearring 249 in textboxes instead of ''.*/ 250 if (value === null) { 251 value = ""; 252 } 253 ctrl.value = value; 254 } 255 }, 256 257 // Save control setting to cookie 258 saveSetting: function(name) { 259 var val, ctrl = $D('noVNC_' + name); 260 if (ctrl.type === 'checkbox') { 261 val = ctrl.checked; 262 } else if (typeof ctrl.options !== 'undefined') { 263 val = ctrl.options[ctrl.selectedIndex].value; 264 } else { 265 val = ctrl.value; 266 } 267 WebUtil.writeSetting(name, val); 268 //Util.Debug("Setting saved '" + name + "=" + val + "'"); 269 return val; 270 }, 271 272 // Initial page load read/initialization of settings 273 initSetting: function(name, defVal) { 274 // Check Query string followed by cookie 275 var val = WebUtil.getQueryVar(name); 276 if (val === null) { 277 val = WebUtil.readSetting(name, defVal); 278 } 279 UI.updateSetting(name, val); 280 return val; 281 }, 282 283 // Force a setting to be a certain value 284 forceSetting: function(name, val) { 285 UI.updateSetting(name, val); 286 return val; 287 }, 288 289 290 // Show the popup status panel 291 togglePopupStatusPanel: function() { 292 var psp = $D('noVNC_popup_status_panel'); 293 if (UI.popupStatusOpen === true) { 294 psp.style.display = "none"; 295 UI.popupStatusOpen = false; 296 } else { 297 psp.innerHTML = $D('noVNC_status').innerHTML; 298 psp.style.display = "block"; 299 psp.style.left = window.innerWidth/2 - 300 parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px"; 301 UI.popupStatusOpen = true; 302 } 303 }, 304 305 // Show the XVP panel 306 toggleXvpPanel: function() { 307 // Close the description panel 308 $D('noVNC_description').style.display = "none"; 309 // Close settings if open 310 if (UI.settingsOpen === true) { 311 UI.settingsApply(); 312 UI.closeSettingsMenu(); 313 } 314 // Close connection settings if open 315 if (UI.connSettingsOpen === true) { 316 UI.toggleConnectPanel(); 317 } 318 // Close popup status panel if open 319 if (UI.popupStatusOpen === true) { 320 UI.togglePopupStatusPanel(); 321 } 322 // Close clipboard panel if open 323 if (UI.clipboardOpen === true) { 324 UI.toggleClipboardPanel(); 325 } 326 // Toggle XVP panel 327 if (UI.xvpOpen === true) { 328 $D('noVNC_xvp').style.display = "none"; 329 $D('xvpButton').className = "noVNC_status_button"; 330 UI.xvpOpen = false; 331 } else { 332 $D('noVNC_xvp').style.display = "block"; 333 $D('xvpButton').className = "noVNC_status_button_selected"; 334 UI.xvpOpen = true; 335 } 336 }, 337 338 // Show the clipboard panel 339 toggleClipboardPanel: function() { 340 // Close the description panel 341 $D('noVNC_description').style.display = "none"; 342 // Close settings if open 343 if (UI.settingsOpen === true) { 344 UI.settingsApply(); 345 UI.closeSettingsMenu(); 346 } 347 // Close connection settings if open 348 if (UI.connSettingsOpen === true) { 349 UI.toggleConnectPanel(); 350 } 351 // Close popup status panel if open 352 if (UI.popupStatusOpen === true) { 353 UI.togglePopupStatusPanel(); 354 } 355 // Close XVP panel if open 356 if (UI.xvpOpen === true) { 357 UI.toggleXvpPanel(); 358 } 359 // Toggle Clipboard Panel 360 if (UI.clipboardOpen === true) { 361 $D('noVNC_clipboard').style.display = "none"; 362 $D('clipboardButton').className = "noVNC_status_button"; 363 UI.clipboardOpen = false; 364 } else { 365 $D('noVNC_clipboard').style.display = "block"; 366 $D('clipboardButton').className = "noVNC_status_button_selected"; 367 UI.clipboardOpen = true; 368 } 369 }, 370 371 // Show the connection settings panel/menu 372 toggleConnectPanel: function() { 373 // Close the description panel 374 $D('noVNC_description').style.display = "none"; 375 // Close connection settings if open 376 if (UI.settingsOpen === true) { 377 UI.settingsApply(); 378 UI.closeSettingsMenu(); 379 $D('connectButton').className = "noVNC_status_button"; 380 } 381 // Close clipboard panel if open 382 if (UI.clipboardOpen === true) { 383 UI.toggleClipboardPanel(); 384 } 385 // Close popup status panel if open 386 if (UI.popupStatusOpen === true) { 387 UI.togglePopupStatusPanel(); 388 } 389 // Close XVP panel if open 390 if (UI.xvpOpen === true) { 391 UI.toggleXvpPanel(); 392 } 393 394 // Toggle Connection Panel 395 if (UI.connSettingsOpen === true) { 396 $D('noVNC_controls').style.display = "none"; 397 $D('connectButton').className = "noVNC_status_button"; 398 UI.connSettingsOpen = false; 399 UI.saveSetting('host'); 400 UI.saveSetting('port'); 401 //UI.saveSetting('password'); 402 } else { 403 $D('noVNC_controls').style.display = "block"; 404 $D('connectButton').className = "noVNC_status_button_selected"; 405 UI.connSettingsOpen = true; 406 $D('noVNC_host').focus(); 407 } 408 }, 409 410 // Toggle the settings menu: 411 // On open, settings are refreshed from saved cookies. 412 // On close, settings are applied 413 toggleSettingsPanel: function() { 414 // Close the description panel 415 $D('noVNC_description').style.display = "none"; 416 if (UI.settingsOpen) { 417 UI.settingsApply(); 418 UI.closeSettingsMenu(); 419 } else { 420 UI.updateSetting('encrypt'); 421 UI.updateSetting('true_color'); 422 if (UI.rfb.get_display().get_cursor_uri()) { 423 UI.updateSetting('cursor'); 424 } else { 425 UI.updateSetting('cursor', !UI.isTouchDevice); 426 $D('noVNC_cursor').disabled = true; 427 } 428 UI.updateSetting('clip'); 429 UI.updateSetting('shared'); 430 UI.updateSetting('view_only'); 431 UI.updateSetting('path'); 432 UI.updateSetting('repeaterID'); 433 UI.updateSetting('stylesheet'); 434 UI.updateSetting('logging'); 435 436 UI.openSettingsMenu(); 437 } 438 }, 439 440 // Open menu 441 openSettingsMenu: function() { 442 // Close the description panel 443 $D('noVNC_description').style.display = "none"; 444 // Close clipboard panel if open 445 if (UI.clipboardOpen === true) { 446 UI.toggleClipboardPanel(); 447 } 448 // Close connection settings if open 449 if (UI.connSettingsOpen === true) { 450 UI.toggleConnectPanel(); 451 } 452 // Close popup status panel if open 453 if (UI.popupStatusOpen === true) { 454 UI.togglePopupStatusPanel(); 455 } 456 // Close XVP panel if open 457 if (UI.xvpOpen === true) { 458 UI.toggleXvpPanel(); 459 } 460 $D('noVNC_settings').style.display = "block"; 461 $D('settingsButton').className = "noVNC_status_button_selected"; 462 UI.settingsOpen = true; 463 }, 464 465 // Close menu (without applying settings) 466 closeSettingsMenu: function() { 467 $D('noVNC_settings').style.display = "none"; 468 $D('settingsButton').className = "noVNC_status_button"; 469 UI.settingsOpen = false; 470 }, 471 472 // Save/apply settings when 'Apply' button is pressed 473 settingsApply: function() { 474 //Util.Debug(">> settingsApply"); 475 UI.saveSetting('encrypt'); 476 UI.saveSetting('true_color'); 477 if (UI.rfb.get_display().get_cursor_uri()) { 478 UI.saveSetting('cursor'); 479 } 480 UI.saveSetting('clip'); 481 UI.saveSetting('shared'); 482 UI.saveSetting('view_only'); 483 UI.saveSetting('path'); 484 UI.saveSetting('repeaterID'); 485 UI.saveSetting('stylesheet'); 486 UI.saveSetting('logging'); 487 488 // Settings with immediate (non-connected related) effect 489 WebUtil.selectStylesheet(UI.getSetting('stylesheet')); 490 WebUtil.init_logging(UI.getSetting('logging')); 491 UI.setViewClip(); 492 UI.setViewDrag(UI.rfb.get_viewportDrag()); 493 //Util.Debug("<< settingsApply"); 494 }, 495 496 497 498 setPassword: function() { 499 UI.rfb.sendPassword($D('noVNC_password').value); 500 //Reset connect button. 501 $D('noVNC_connect_button').value = "Connect"; 502 $D('noVNC_connect_button').onclick = UI.Connect; 503 //Hide connection panel. 504 UI.toggleConnectPanel(); 505 return false; 506 }, 507 508 sendCtrlAltDel: function() { 509 UI.rfb.sendCtrlAltDel(); 510 }, 511 512 xvpShutdown: function() { 513 UI.rfb.xvpShutdown(); 514 }, 515 516 xvpReboot: function() { 517 UI.rfb.xvpReboot(); 518 }, 519 520 xvpReset: function() { 521 UI.rfb.xvpReset(); 522 }, 523 524 setMouseButton: function(num) { 525 if (typeof num === 'undefined') { 526 // Disable mouse buttons 527 num = -1; 528 } 529 if (UI.rfb) { 530 UI.rfb.get_mouse().set_touchButton(num); 531 } 532 533 var blist = [0, 1,2,4]; 534 for (var b = 0; b < blist.length; b++) { 535 var button = $D('noVNC_mouse_button' + blist[b]); 536 if (blist[b] === num) { 537 button.style.display = ""; 538 } else { 539 button.style.display = "none"; 540 } 541 } 542 }, 543 544 updateState: function(rfb, state, oldstate, msg) { 545 UI.rfb_state = state; 546 var klass; 547 switch (state) { 548 case 'failed': 549 case 'fatal': 550 klass = "noVNC_status_error"; 551 break; 552 case 'normal': 553 klass = "noVNC_status_normal"; 554 break; 555 case 'disconnected': 556 $D('noVNC_logo').style.display = "block"; 557 /* falls through */ 558 case 'loaded': 559 klass = "noVNC_status_normal"; 560 break; 561 case 'password': 562 UI.toggleConnectPanel(); 563 564 $D('noVNC_connect_button').value = "Send Password"; 565 $D('noVNC_connect_button').onclick = UI.setPassword; 566 $D('noVNC_password').focus(); 567 568 klass = "noVNC_status_warn"; 569 break; 570 default: 571 klass = "noVNC_status_warn"; 572 break; 573 } 574 575 if (typeof(msg) !== 'undefined') { 576 $D('noVNC-control-bar').setAttribute("class", klass); 577 $D('noVNC_status').innerHTML = msg; 578 } 579 580 UI.updateVisualState(); 581 }, 582 583 // Disable/enable controls depending on connection state 584 updateVisualState: function() { 585 var connected = UI.rfb_state === 'normal' ? true : false; 586 587 //Util.Debug(">> updateVisualState"); 588 $D('noVNC_encrypt').disabled = connected; 589 $D('noVNC_true_color').disabled = connected; 590 if (UI.rfb && UI.rfb.get_display() && 591 UI.rfb.get_display().get_cursor_uri()) { 592 $D('noVNC_cursor').disabled = connected; 593 } else { 594 UI.updateSetting('cursor', !UI.isTouchDevice); 595 $D('noVNC_cursor').disabled = true; 596 } 597 $D('noVNC_shared').disabled = connected; 598 $D('noVNC_view_only').disabled = connected; 599 $D('noVNC_path').disabled = connected; 600 $D('noVNC_repeaterID').disabled = connected; 601 602 if (connected) { 603 UI.setViewClip(); 604 UI.setMouseButton(1); 605 $D('clipboardButton').style.display = "inline"; 606 $D('showKeyboard').style.display = "inline"; 607 $D('noVNC_extra_keys').style.display = ""; 608 $D('sendCtrlAltDelButton').style.display = "inline"; 609 } else { 610 UI.setMouseButton(); 611 $D('clipboardButton').style.display = "none"; 612 $D('showKeyboard').style.display = "none"; 613 $D('noVNC_extra_keys').style.display = "none"; 614 $D('sendCtrlAltDelButton').style.display = "none"; 615 UI.updateXvpVisualState(0); 616 } 617 618 // State change disables viewport dragging. 619 // It is enabled (toggled) by direct click on the button 620 UI.setViewDrag(false); 621 622 switch (UI.rfb_state) { 623 case 'fatal': 624 case 'failed': 625 case 'loaded': 626 case 'disconnected': 627 $D('connectButton').style.display = ""; 628 $D('disconnectButton').style.display = "none"; 629 break; 630 default: 631 $D('connectButton').style.display = "none"; 632 $D('disconnectButton').style.display = ""; 633 break; 634 } 635 636 //Util.Debug("<< updateVisualState"); 637 }, 638 639 // Disable/enable XVP button 640 updateXvpVisualState: function(ver) { 641 if (ver >= 1) { 642 $D('xvpButton').style.display = 'inline'; 643 } else { 644 $D('xvpButton').style.display = 'none'; 645 // Close XVP panel if open 646 if (UI.xvpOpen === true) { 647 UI.toggleXvpPanel(); 648 } 649 } 650 }, 651 652 // Display the desktop name in the document title 653 updateDocumentTitle: function(rfb, name) { 654 document.title = name + " - noVNC"; 655 }, 656 657 clipReceive: function(rfb, text) { 658 Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "..."); 659 $D('noVNC_clipboard_text').value = text; 660 Util.Debug("<< UI.clipReceive"); 661 }, 662 663 connect: function() { 664 UI.closeSettingsMenu(); 665 UI.toggleConnectPanel(); 666 667 var host = $D('noVNC_host').value; 668 var port = $D('noVNC_port').value; 669 var password = $D('noVNC_password').value; 670 var path = $D('noVNC_path').value; 671 if ((!host) || (!port)) { 672 throw new Error("Must set host and port"); 673 } 674 675 UI.rfb.set_encrypt(UI.getSetting('encrypt')); 676 UI.rfb.set_true_color(UI.getSetting('true_color')); 677 UI.rfb.set_local_cursor(UI.getSetting('cursor')); 678 UI.rfb.set_shared(UI.getSetting('shared')); 679 UI.rfb.set_view_only(UI.getSetting('view_only')); 680 UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); 681 682 UI.rfb.connect(host, port, password, path); 683 684 //Close dialog. 685 setTimeout(UI.setBarPosition, 100); 686 $D('noVNC_logo').style.display = "none"; 687 }, 688 689 disconnect: function() { 690 UI.closeSettingsMenu(); 691 UI.rfb.disconnect(); 692 693 $D('noVNC_logo').style.display = "block"; 694 UI.connSettingsOpen = false; 695 UI.toggleConnectPanel(); 696 }, 697 698 displayBlur: function() { 699 UI.rfb.get_keyboard().set_focused(false); 700 UI.rfb.get_mouse().set_focused(false); 701 }, 702 703 displayFocus: function() { 704 UI.rfb.get_keyboard().set_focused(true); 705 UI.rfb.get_mouse().set_focused(true); 706 }, 707 708 clipClear: function() { 709 $D('noVNC_clipboard_text').value = ""; 710 UI.rfb.clipboardPasteFrom(""); 711 }, 712 713 clipSend: function() { 714 var text = $D('noVNC_clipboard_text').value; 715 Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "..."); 716 UI.rfb.clipboardPasteFrom(text); 717 Util.Debug("<< UI.clipSend"); 718 }, 719 720 // Enable/disable and configure viewport clipping 721 setViewClip: function(clip) { 722 var display; 723 if (UI.rfb) { 724 display = UI.rfb.get_display(); 725 } else { 726 return; 727 } 728 729 var cur_clip = display.get_viewport(); 730 731 if (typeof(clip) !== 'boolean') { 732 // Use current setting 733 clip = UI.getSetting('clip'); 734 } 735 736 if (clip && !cur_clip) { 737 // Turn clipping on 738 UI.updateSetting('clip', true); 739 } else if (!clip && cur_clip) { 740 // Turn clipping off 741 UI.updateSetting('clip', false); 742 display.set_viewport(false); 743 $D('noVNC_canvas').style.position = 'static'; 744 display.viewportChange(); 745 } 746 if (UI.getSetting('clip')) { 747 // If clipping, update clipping settings 748 $D('noVNC_canvas').style.position = 'absolute'; 749 var pos = Util.getPosition($D('noVNC_canvas')); 750 var new_w = window.innerWidth - pos.x; 751 var new_h = window.innerHeight - pos.y; 752 display.set_viewport(true); 753 display.viewportChange(0, 0, new_w, new_h); 754 } 755 }, 756 757 // Toggle/set/unset the viewport drag/move button 758 setViewDrag: function(drag) { 759 var vmb = $D('noVNC_view_drag_button'); 760 if (!UI.rfb) { return; } 761 762 if (UI.rfb_state === 'normal' && 763 UI.rfb.get_display().get_viewport()) { 764 vmb.style.display = "inline"; 765 } else { 766 vmb.style.display = "none"; 767 } 768 769 if (typeof(drag) === "undefined" || 770 typeof(drag) === "object") { 771 // If not specified, then toggle 772 drag = !UI.rfb.get_viewportDrag(); 773 } 774 if (drag) { 775 vmb.className = "noVNC_status_button_selected"; 776 UI.rfb.set_viewportDrag(true); 777 } else { 778 vmb.className = "noVNC_status_button"; 779 UI.rfb.set_viewportDrag(false); 780 } 781 }, 782 783 // On touch devices, show the OS keyboard 784 showKeyboard: function() { 785 var kbi = $D('keyboardinput'); 786 var skb = $D('showKeyboard'); 787 var l = kbi.value.length; 788 if(UI.keyboardVisible === false) { 789 kbi.focus(); 790 try { kbi.setSelectionRange(l, l); } // Move the caret to the end 791 catch (err) {} // setSelectionRange is undefined in Google Chrome 792 UI.keyboardVisible = true; 793 skb.className = "noVNC_status_button_selected"; 794 } else if(UI.keyboardVisible === true) { 795 kbi.blur(); 796 skb.className = "noVNC_status_button"; 797 UI.keyboardVisible = false; 798 } 799 }, 800 801 keepKeyboard: function() { 802 clearTimeout(UI.hideKeyboardTimeout); 803 if(UI.keyboardVisible === true) { 804 $D('keyboardinput').focus(); 805 $D('showKeyboard').className = "noVNC_status_button_selected"; 806 } else if(UI.keyboardVisible === false) { 807 $D('keyboardinput').blur(); 808 $D('showKeyboard').className = "noVNC_status_button"; 809 } 810 }, 811 812 keyboardinputReset: function() { 813 var kbi = $D('keyboardinput'); 814 kbi.value = new Array(UI.defaultKeyboardinputLen).join("_"); 815 UI.lastKeyboardinput = kbi.value; 816 }, 817 818 // When normal keyboard events are left uncought, use the input events from 819 // the keyboardinput element instead and generate the corresponding key events. 820 // This code is required since some browsers on Android are inconsistent in 821 // sending keyCodes in the normal keyboard events when using on screen keyboards. 822 keyInput: function(event) { 823 var newValue = event.target.value; 824 var oldValue = UI.lastKeyboardinput; 825 826 var newLen; 827 try { 828 // Try to check caret position since whitespace at the end 829 // will not be considered by value.length in some browsers 830 newLen = Math.max(event.target.selectionStart, newValue.length); 831 } catch (err) { 832 // selectionStart is undefined in Google Chrome 833 newLen = newValue.length; 834 } 835 var oldLen = oldValue.length; 836 837 var backspaces; 838 var inputs = newLen - oldLen; 839 if (inputs < 0) { 840 backspaces = -inputs; 841 } else { 842 backspaces = 0; 843 } 844 845 // Compare the old string with the new to account for 846 // text-corrections or other input that modify existing text 847 var i; 848 for (i = 0; i < Math.min(oldLen, newLen); i++) { 849 if (newValue.charAt(i) != oldValue.charAt(i)) { 850 inputs = newLen - i; 851 backspaces = oldLen - i; 852 break; 853 } 854 } 855 856 // Send the key events 857 for (i = 0; i < backspaces; i++) { 858 UI.rfb.sendKey(XK_BackSpace); 859 } 860 for (i = newLen - inputs; i < newLen; i++) { 861 UI.rfb.sendKey(newValue.charCodeAt(i)); 862 } 863 864 // Control the text content length in the keyboardinput element 865 if (newLen > 2 * UI.defaultKeyboardinputLen) { 866 UI.keyboardinputReset(); 867 } else if (newLen < 1) { 868 // There always have to be some text in the keyboardinput 869 // element with which backspace can interact. 870 UI.keyboardinputReset(); 871 // This sometimes causes the keyboard to disappear for a second 872 // but it is required for the android keyboard to recognize that 873 // text has been added to the field 874 event.target.blur(); 875 // This has to be ran outside of the input handler in order to work 876 setTimeout(function() { UI.keepKeyboard(); }, 0); 877 } else { 878 UI.lastKeyboardinput = newValue; 879 } 880 }, 881 882 keyInputBlur: function() { 883 $D('showKeyboard').className = "noVNC_status_button"; 884 //Weird bug in iOS if you change keyboardVisible 885 //here it does not actually occur so next time 886 //you click keyboard icon it doesnt work. 887 UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100); 888 }, 889 890 showExtraKeys: function() { 891 UI.keepKeyboard(); 892 if(UI.extraKeysVisible === false) { 893 $D('toggleCtrlButton').style.display = "inline"; 894 $D('toggleAltButton').style.display = "inline"; 895 $D('sendTabButton').style.display = "inline"; 896 $D('sendEscButton').style.display = "inline"; 897 $D('showExtraKeysButton').className = "noVNC_status_button_selected"; 898 UI.extraKeysVisible = true; 899 } else if(UI.extraKeysVisible === true) { 900 $D('toggleCtrlButton').style.display = ""; 901 $D('toggleAltButton').style.display = ""; 902 $D('sendTabButton').style.display = ""; 903 $D('sendEscButton').style.display = ""; 904 $D('showExtraKeysButton').className = "noVNC_status_button"; 905 UI.extraKeysVisible = false; 906 } 907 }, 908 909 toggleCtrl: function() { 910 UI.keepKeyboard(); 911 if(UI.ctrlOn === false) { 912 UI.rfb.sendKey(XK_Control_L, true); 913 $D('toggleCtrlButton').className = "noVNC_status_button_selected"; 914 UI.ctrlOn = true; 915 } else if(UI.ctrlOn === true) { 916 UI.rfb.sendKey(XK_Control_L, false); 917 $D('toggleCtrlButton').className = "noVNC_status_button"; 918 UI.ctrlOn = false; 919 } 920 }, 921 922 toggleAlt: function() { 923 UI.keepKeyboard(); 924 if(UI.altOn === false) { 925 UI.rfb.sendKey(XK_Alt_L, true); 926 $D('toggleAltButton').className = "noVNC_status_button_selected"; 927 UI.altOn = true; 928 } else if(UI.altOn === true) { 929 UI.rfb.sendKey(XK_Alt_L, false); 930 $D('toggleAltButton').className = "noVNC_status_button"; 931 UI.altOn = false; 932 } 933 }, 934 935 sendTab: function() { 936 UI.keepKeyboard(); 937 UI.rfb.sendKey(XK_Tab); 938 }, 939 940 sendEsc: function() { 941 UI.keepKeyboard(); 942 UI.rfb.sendKey(XK_Escape); 943 }, 944 945 setKeyboard: function() { 946 UI.keyboardVisible = false; 947 }, 948 949 // iOS < Version 5 does not support position fixed. Javascript workaround: 950 setOnscroll: function() { 951 window.onscroll = function() { 952 UI.setBarPosition(); 953 }; 954 }, 955 956 setResize: function () { 957 window.onResize = function() { 958 UI.setBarPosition(); 959 }; 960 }, 961 962 //Helper to add options to dropdown. 963 addOption: function(selectbox, text, value) { 964 var optn = document.createElement("OPTION"); 965 optn.text = text; 966 optn.value = value; 967 selectbox.options.add(optn); 968 }, 969 970 setBarPosition: function() { 971 $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px'; 972 $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px'; 973 974 var vncwidth = $D('noVNC_screen').style.offsetWidth; 975 $D('noVNC-control-bar').style.width = vncwidth + 'px'; 976 } 977 978 }; 979})(); 980