Home | History | Annotate | Download | only in include
      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 
     13 var 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