Home | History | Annotate | Download | only in include
      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 
      9 "use strict";
     10 /*jslint white: false, browser: true */
     11 /*global window, $D, Util, WebUtil, RFB, Display */
     12 
     13 var UI = {
     14 
     15 rfb_state : 'loaded',
     16 settingsOpen : false,
     17 connSettingsOpen : false,
     18 clipboardOpen: false,
     19 keyboardVisible: false,
     20 
     21 // Render default UI and initialize settings menu
     22 load: function() {
     23     var html = '', i, sheet, sheets, llevels;
     24 
     25     // Stylesheet selection dropdown
     26     sheet = WebUtil.selectStylesheet();
     27     sheets = WebUtil.getStylesheets();
     28     for (i = 0; i < sheets.length; i += 1) {
     29         UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
     30     }
     31 
     32     // Logging selection dropdown
     33     llevels = ['error', 'warn', 'info', 'debug'];
     34     for (i = 0; i < llevels.length; i += 1) {
     35         UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
     36     }
     37 
     38     // Settings with immediate effects
     39     UI.initSetting('logging', 'warn');
     40     WebUtil.init_logging(UI.getSetting('logging'));
     41 
     42     UI.initSetting('stylesheet', 'default');
     43     WebUtil.selectStylesheet(null);
     44     // call twice to get around webkit bug
     45     WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
     46 
     47     /* Populate the controls if defaults are provided in the URL */
     48     UI.initSetting('host', window.location.hostname);
     49     UI.initSetting('port', window.location.port);
     50     UI.initSetting('password', '');
     51     UI.initSetting('encrypt', (window.location.protocol === "https:"));
     52     UI.initSetting('true_color', true);
     53     UI.initSetting('cursor', false);
     54     UI.initSetting('shared', true);
     55     UI.initSetting('view_only', false);
     56     UI.initSetting('connectTimeout', 2);
     57     UI.initSetting('path', 'websockify');
     58 
     59     UI.rfb = RFB({'target': $D('noVNC_canvas'),
     60                   'onUpdateState': UI.updateState,
     61                   'onClipboard': UI.clipReceive});
     62     UI.updateVisualState();
     63 
     64     // Unfocus clipboard when over the VNC area
     65     //$D('VNC_screen').onmousemove = function () {
     66     //         var keyboard = UI.rfb.get_keyboard();
     67     //        if ((! keyboard) || (! keyboard.get_focused())) {
     68     //            $D('VNC_clipboard_text').blur();
     69     //         }
     70     //    };
     71 
     72     // Show mouse selector buttons on touch screen devices
     73     if ('ontouchstart' in document.documentElement) {
     74         // Show mobile buttons
     75         $D('noVNC_mobile_buttons').style.display = "inline";
     76         UI.setMouseButton();
     77         // Remove the address bar
     78         setTimeout(function() { window.scrollTo(0, 1); }, 100);
     79         UI.forceSetting('clip', true);
     80         $D('noVNC_clip').disabled = true;
     81     } else {
     82         UI.initSetting('clip', false);
     83     }
     84 
     85     //iOS Safari does not support CSS position:fixed.
     86     //This detects iOS devices and enables javascript workaround.
     87     if ((navigator.userAgent.match(/iPhone/i)) ||
     88         (navigator.userAgent.match(/iPod/i)) ||
     89         (navigator.userAgent.match(/iPad/i))) {
     90         //UI.setOnscroll();
     91         //UI.setResize();
     92     }
     93 
     94     $D('noVNC_host').focus();
     95 
     96     UI.setViewClip();
     97     Util.addEvent(window, 'resize', UI.setViewClip);
     98 
     99     Util.addEvent(window, 'beforeunload', function () {
    100         if (UI.rfb_state === 'normal') {
    101             return "You are currently connected.";
    102         }
    103     } );
    104 
    105     // Show description by default when hosted at for kanaka.github.com
    106     if (location.host === "kanaka.github.com") {
    107         // Open the description dialog
    108         $D('noVNC_description').style.display = "block";
    109     } else {
    110         // Open the connect panel on first load
    111         UI.toggleConnectPanel();
    112     }
    113 },
    114 
    115 // Read form control compatible setting from cookie
    116 getSetting: function(name) {
    117     var val, ctrl = $D('noVNC_' + name);
    118     val = WebUtil.readCookie(name);
    119     if (ctrl.type === 'checkbox') {
    120         if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
    121             val = false;
    122         } else {
    123             val = true;
    124         }
    125     }
    126     return val;
    127 },
    128 
    129 // Update cookie and form control setting. If value is not set, then
    130 // updates from control to current cookie setting.
    131 updateSetting: function(name, value) {
    132 
    133     var i, ctrl = $D('noVNC_' + name);
    134     // Save the cookie for this session
    135     if (typeof value !== 'undefined') {
    136         WebUtil.createCookie(name, value);
    137     }
    138 
    139     // Update the settings control
    140     value = UI.getSetting(name);
    141 
    142     if (ctrl.type === 'checkbox') {
    143         ctrl.checked = value;
    144 
    145     } else if (typeof ctrl.options !== 'undefined') {
    146         for (i = 0; i < ctrl.options.length; i += 1) {
    147             if (ctrl.options[i].value === value) {
    148                 ctrl.selectedIndex = i;
    149                 break;
    150             }
    151         }
    152     } else {
    153         /*Weird IE9 error leads to 'null' appearring
    154         in textboxes instead of ''.*/
    155         if (value === null) {
    156             value = "";
    157         }
    158         ctrl.value = value;
    159     }
    160 },
    161 
    162 // Save control setting to cookie
    163 saveSetting: function(name) {
    164     var val, ctrl = $D('noVNC_' + name);
    165     if (ctrl.type === 'checkbox') {
    166         val = ctrl.checked;
    167     } else if (typeof ctrl.options !== 'undefined') {
    168         val = ctrl.options[ctrl.selectedIndex].value;
    169     } else {
    170         val = ctrl.value;
    171     }
    172     WebUtil.createCookie(name, val);
    173     //Util.Debug("Setting saved '" + name + "=" + val + "'");
    174     return val;
    175 },
    176 
    177 // Initial page load read/initialization of settings
    178 initSetting: function(name, defVal) {
    179     var val;
    180 
    181     // Check Query string followed by cookie
    182     val = WebUtil.getQueryVar(name);
    183     if (val === null) {
    184         val = WebUtil.readCookie(name, defVal);
    185     }
    186     UI.updateSetting(name, val);
    187  //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
    188     return val;
    189 },
    190 
    191 // Force a setting to be a certain value
    192 forceSetting: function(name, val) {
    193     UI.updateSetting(name, val);
    194     return val;
    195 },
    196 
    197 
    198 // Show the clipboard panel
    199 toggleClipboardPanel: function() {
    200     // Close the description panel
    201     $D('noVNC_description').style.display = "none";
    202     //Close settings if open
    203     if (UI.settingsOpen === true) {
    204         UI.settingsApply();
    205         UI.closeSettingsMenu();
    206     }
    207     //Close connection settings if open
    208     if (UI.connSettingsOpen === true) {
    209         UI.toggleConnectPanel();
    210     }
    211     //Toggle Clipboard Panel
    212     if (UI.clipboardOpen === true) {
    213         $D('noVNC_clipboard').style.display = "none";
    214         $D('clipboardButton').className = "noVNC_status_button";
    215         UI.clipboardOpen = false;
    216     } else {
    217         $D('noVNC_clipboard').style.display = "block";
    218         $D('clipboardButton').className = "noVNC_status_button_selected";
    219         UI.clipboardOpen = true;
    220     }
    221 },
    222 
    223 // Show the connection settings panel/menu
    224 toggleConnectPanel: function() {
    225     // Close the description panel
    226     $D('noVNC_description').style.display = "none";
    227     //Close connection settings if open
    228     if (UI.settingsOpen === true) {
    229         UI.settingsApply();
    230         UI.closeSettingsMenu();
    231         $D('connectButton').className = "noVNC_status_button";
    232     }
    233     if (UI.clipboardOpen === true) {
    234         UI.toggleClipboardPanel();
    235     }
    236 
    237     //Toggle Connection Panel
    238     if (UI.connSettingsOpen === true) {
    239         $D('noVNC_controls').style.display = "none";
    240         $D('connectButton').className = "noVNC_status_button";
    241         UI.connSettingsOpen = false;
    242     } else {
    243         $D('noVNC_controls').style.display = "block";
    244         $D('connectButton').className = "noVNC_status_button_selected";
    245         UI.connSettingsOpen = true;
    246         $D('noVNC_host').focus();
    247     }
    248 },
    249 
    250 // Toggle the settings menu:
    251 //   On open, settings are refreshed from saved cookies.
    252 //   On close, settings are applied
    253 toggleSettingsPanel: function() {
    254     // Close the description panel
    255     $D('noVNC_description').style.display = "none";
    256     if (UI.settingsOpen) {
    257         UI.settingsApply();
    258         UI.closeSettingsMenu();
    259     } else {
    260         UI.updateSetting('encrypt');
    261         UI.updateSetting('true_color');
    262         if (UI.rfb.get_display().get_cursor_uri()) {
    263             UI.updateSetting('cursor');
    264         } else {
    265             UI.updateSetting('cursor', false);
    266             $D('noVNC_cursor').disabled = true;
    267         }
    268         UI.updateSetting('clip');
    269         UI.updateSetting('shared');
    270         UI.updateSetting('view_only');
    271         UI.updateSetting('connectTimeout');
    272         UI.updateSetting('path');
    273         UI.updateSetting('stylesheet');
    274         UI.updateSetting('logging');
    275 
    276         UI.openSettingsMenu();
    277     }
    278 },
    279 
    280 // Open menu
    281 openSettingsMenu: function() {
    282     // Close the description panel
    283     $D('noVNC_description').style.display = "none";
    284     if (UI.clipboardOpen === true) {
    285         UI.toggleClipboardPanel();
    286     }
    287     //Close connection settings if open
    288     if (UI.connSettingsOpen === true) {
    289         UI.toggleConnectPanel();
    290     }
    291     $D('noVNC_settings').style.display = "block";
    292     $D('settingsButton').className = "noVNC_status_button_selected";
    293     UI.settingsOpen = true;
    294 },
    295 
    296 // Close menu (without applying settings)
    297 closeSettingsMenu: function() {
    298     $D('noVNC_settings').style.display = "none";
    299     $D('settingsButton').className = "noVNC_status_button";
    300     UI.settingsOpen = false;
    301 },
    302 
    303 // Save/apply settings when 'Apply' button is pressed
    304 settingsApply: function() {
    305     //Util.Debug(">> settingsApply");
    306     UI.saveSetting('encrypt');
    307     UI.saveSetting('true_color');
    308     if (UI.rfb.get_display().get_cursor_uri()) {
    309         UI.saveSetting('cursor');
    310     }
    311     UI.saveSetting('clip');
    312     UI.saveSetting('shared');
    313     UI.saveSetting('view_only');
    314     UI.saveSetting('connectTimeout');
    315     UI.saveSetting('path');
    316     UI.saveSetting('stylesheet');
    317     UI.saveSetting('logging');
    318 
    319     // Settings with immediate (non-connected related) effect
    320     WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
    321     WebUtil.init_logging(UI.getSetting('logging'));
    322     UI.setViewClip();
    323     UI.setViewDrag(UI.rfb.get_viewportDrag());
    324     //Util.Debug("<< settingsApply");
    325 },
    326 
    327 
    328 
    329 setPassword: function() {
    330     UI.rfb.sendPassword($D('noVNC_password').value);
    331     //Reset connect button.
    332     $D('noVNC_connect_button').value = "Connect";
    333     $D('noVNC_connect_button').onclick = UI.Connect;
    334     //Hide connection panel.
    335     UI.toggleConnectPanel();
    336     return false;
    337 },
    338 
    339 sendCtrlAltDel: function() {
    340     UI.rfb.sendCtrlAltDel();
    341 },
    342 
    343 setMouseButton: function(num) {
    344     var b, blist = [0, 1,2,4], button;
    345 
    346     if (typeof num === 'undefined') {
    347         // Disable mouse buttons
    348         num = -1;
    349     }
    350     if (UI.rfb) {
    351         UI.rfb.get_mouse().set_touchButton(num);
    352     }
    353 
    354     for (b = 0; b < blist.length; b++) {
    355         button = $D('noVNC_mouse_button' + blist[b]);
    356         if (blist[b] === num) {
    357             button.style.display = "";
    358         } else {
    359             button.style.display = "none";
    360             /*
    361             button.style.backgroundColor = "black";
    362             button.style.color = "lightgray";
    363             button.style.backgroundColor = "";
    364             button.style.color = "";
    365             */
    366         }
    367     }
    368 },
    369 
    370 updateState: function(rfb, state, oldstate, msg) {
    371     var s, sb, c, d, cad, vd, klass;
    372     UI.rfb_state = state;
    373     s = $D('noVNC_status');
    374     sb = $D('noVNC_status_bar');
    375     switch (state) {
    376         case 'failed':
    377         case 'fatal':
    378             klass = "noVNC_status_error";
    379             break;
    380         case 'normal':
    381             klass = "noVNC_status_normal";
    382             break;
    383         case 'disconnected':
    384             $D('noVNC_logo').style.display = "block";
    385             // Fall through
    386         case 'loaded':
    387             klass = "noVNC_status_normal";
    388             break;
    389         case 'password':
    390             UI.toggleConnectPanel();
    391 
    392             $D('noVNC_connect_button').value = "Send Password";
    393             $D('noVNC_connect_button').onclick = UI.setPassword;
    394             $D('noVNC_password').focus();
    395 
    396             klass = "noVNC_status_warn";
    397             break;
    398         default:
    399             klass = "noVNC_status_warn";
    400             break;
    401     }
    402 
    403     if (typeof(msg) !== 'undefined') {
    404         s.setAttribute("class", klass);
    405         sb.setAttribute("class", klass);
    406         s.innerHTML = msg;
    407     }
    408 
    409     UI.updateVisualState();
    410 },
    411 
    412 // Disable/enable controls depending on connection state
    413 updateVisualState: function() {
    414     var connected = UI.rfb_state === 'normal' ? true : false;
    415 
    416     //Util.Debug(">> updateVisualState");
    417     $D('noVNC_encrypt').disabled = connected;
    418     $D('noVNC_true_color').disabled = connected;
    419     if (UI.rfb && UI.rfb.get_display() &&
    420         UI.rfb.get_display().get_cursor_uri()) {
    421         $D('noVNC_cursor').disabled = connected;
    422     } else {
    423         UI.updateSetting('cursor', false);
    424         $D('noVNC_cursor').disabled = true;
    425     }
    426     $D('noVNC_shared').disabled = connected;
    427     $D('noVNC_view_only').disabled = connected;
    428     $D('noVNC_connectTimeout').disabled = connected;
    429     $D('noVNC_path').disabled = connected;
    430 
    431     if (connected) {
    432         UI.setViewClip();
    433         UI.setMouseButton(1);
    434         $D('clipboardButton').style.display = "inline";
    435         $D('showKeyboard').style.display = "inline";
    436         $D('sendCtrlAltDelButton').style.display = "inline";
    437     } else {
    438         UI.setMouseButton();
    439         $D('clipboardButton').style.display = "none";
    440         $D('showKeyboard').style.display = "none";
    441         $D('sendCtrlAltDelButton').style.display = "none";
    442     }
    443     // State change disables viewport dragging.
    444     // It is enabled (toggled) by direct click on the button
    445     UI.setViewDrag(false);
    446 
    447     switch (UI.rfb_state) {
    448         case 'fatal':
    449         case 'failed':
    450         case 'loaded':
    451         case 'disconnected':
    452             $D('connectButton').style.display = "";
    453             $D('disconnectButton').style.display = "none";
    454             break;
    455         default:
    456             $D('connectButton').style.display = "none";
    457             $D('disconnectButton').style.display = "";
    458             break;
    459     }
    460 
    461     //Util.Debug("<< updateVisualState");
    462 },
    463 
    464 
    465 clipReceive: function(rfb, text) {
    466     Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
    467     $D('noVNC_clipboard_text').value = text;
    468     Util.Debug("<< UI.clipReceive");
    469 },
    470 
    471 
    472 connect: function() {
    473     var host, port, password, path;
    474 
    475     UI.closeSettingsMenu();
    476     UI.toggleConnectPanel();
    477 
    478     host = $D('noVNC_host').value;
    479     port = $D('noVNC_port').value;
    480     password = $D('noVNC_password').value;
    481     path = $D('noVNC_path').value;
    482     if ((!host) || (!port)) {
    483         throw("Must set host and port");
    484     }
    485 
    486     UI.rfb.set_encrypt(UI.getSetting('encrypt'));
    487     UI.rfb.set_true_color(UI.getSetting('true_color'));
    488     UI.rfb.set_local_cursor(UI.getSetting('cursor'));
    489     UI.rfb.set_shared(UI.getSetting('shared'));
    490     UI.rfb.set_view_only(UI.getSetting('view_only'));
    491     UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
    492 
    493     UI.rfb.connect(host, port, password, path);
    494     //Close dialog.
    495     setTimeout(UI.setBarPosition, 100);
    496     $D('noVNC_logo').style.display = "none";
    497 },
    498 
    499 disconnect: function() {
    500     UI.closeSettingsMenu();
    501     UI.rfb.disconnect();
    502 
    503     $D('noVNC_logo').style.display = "block";
    504     UI.connSettingsOpen = false;
    505     UI.toggleConnectPanel();
    506 },
    507 
    508 displayBlur: function() {
    509     UI.rfb.get_keyboard().set_focused(false);
    510     UI.rfb.get_mouse().set_focused(false);
    511 },
    512 
    513 displayFocus: function() {
    514     UI.rfb.get_keyboard().set_focused(true);
    515     UI.rfb.get_mouse().set_focused(true);
    516 },
    517 
    518 clipClear: function() {
    519     $D('noVNC_clipboard_text').value = "";
    520     UI.rfb.clipboardPasteFrom("");
    521 },
    522 
    523 clipSend: function() {
    524     var text = $D('noVNC_clipboard_text').value;
    525     Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
    526     UI.rfb.clipboardPasteFrom(text);
    527     Util.Debug("<< UI.clipSend");
    528 },
    529 
    530 
    531 // Enable/disable and configure viewport clipping
    532 setViewClip: function(clip) {
    533     var display, cur_clip, pos, new_w, new_h;
    534 
    535     if (UI.rfb) {
    536         display = UI.rfb.get_display();
    537     } else {
    538         return;
    539     }
    540 
    541     cur_clip = display.get_viewport();
    542 
    543     if (typeof(clip) !== 'boolean') {
    544         // Use current setting
    545         clip = UI.getSetting('clip');
    546     }
    547 
    548     if (clip && !cur_clip) {
    549         // Turn clipping on
    550         UI.updateSetting('clip', true);
    551     } else if (!clip && cur_clip) {
    552         // Turn clipping off
    553         UI.updateSetting('clip', false);
    554         display.set_viewport(false);
    555         $D('noVNC_canvas').style.position = 'static';
    556         display.viewportChange();
    557     }
    558     if (UI.getSetting('clip')) {
    559         // If clipping, update clipping settings
    560         $D('noVNC_canvas').style.position = 'absolute';
    561         pos = Util.getPosition($D('noVNC_canvas'));
    562         new_w = window.innerWidth - pos.x;
    563         new_h = window.innerHeight - pos.y;
    564         display.set_viewport(true);
    565         display.viewportChange(0, 0, new_w, new_h);
    566     }
    567 },
    568 
    569 // Toggle/set/unset the viewport drag/move button
    570 setViewDrag: function(drag) {
    571     var vmb = $D('noVNC_view_drag_button');
    572     if (!UI.rfb) { return; }
    573 
    574     if (UI.rfb_state === 'normal' &&
    575         UI.rfb.get_display().get_viewport()) {
    576         vmb.style.display = "inline";
    577     } else {
    578         vmb.style.display = "none";
    579     }
    580 
    581     if (typeof(drag) === "undefined") {
    582         // If not specified, then toggle
    583         drag = !UI.rfb.get_viewportDrag();
    584     }
    585     if (drag) {
    586         vmb.className = "noVNC_status_button_selected";
    587         UI.rfb.set_viewportDrag(true);
    588     } else {
    589         vmb.className = "noVNC_status_button";
    590         UI.rfb.set_viewportDrag(false);
    591     }
    592 },
    593 
    594 // On touch devices, show the OS keyboard
    595 showKeyboard: function() {
    596     if(UI.keyboardVisible === false) {
    597         $D('keyboardinput').focus();
    598         UI.keyboardVisible = true;
    599         $D('showKeyboard').className = "noVNC_status_button_selected";
    600     } else if(UI.keyboardVisible === true) {
    601         $D('keyboardinput').blur();
    602         $D('showKeyboard').className = "noVNC_status_button";
    603         UI.keyboardVisible = false;
    604     }
    605 },
    606 
    607 keyInputBlur: function() {
    608     $D('showKeyboard').className = "noVNC_status_button";
    609     //Weird bug in iOS if you change keyboardVisible
    610     //here it does not actually occur so next time
    611     //you click keyboard icon it doesnt work.
    612     setTimeout(function() { UI.setKeyboard(); },100);
    613 },
    614 
    615 setKeyboard: function() {
    616     UI.keyboardVisible = false;
    617 },
    618 
    619 // iOS < Version 5 does not support position fixed. Javascript workaround:
    620 setOnscroll: function() {
    621     window.onscroll = function() {
    622         UI.setBarPosition();
    623     };
    624 },
    625 
    626 setResize: function () {
    627     window.onResize = function() {
    628         UI.setBarPosition();
    629     };
    630 },
    631 
    632 //Helper to add options to dropdown.
    633 addOption: function(selectbox,text,value )
    634 {
    635     var optn = document.createElement("OPTION");
    636     optn.text = text;
    637     optn.value = value;
    638     selectbox.options.add(optn);
    639 },
    640 
    641 setBarPosition: function() {
    642     $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
    643     $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
    644 
    645     var vncwidth = $D('noVNC_screen').style.offsetWidth;
    646     $D('noVNC-control-bar').style.width = vncwidth + 'px';
    647 }
    648 
    649 };
    650 
    651 
    652 
    653 
    654