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
13var UI = {
14
15rfb_state : 'loaded',
16settingsOpen : false,
17connSettingsOpen : false,
18clipboardOpen: false,
19keyboardVisible: false,
20
21// Render default UI and initialize settings menu
22load: 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
116getSetting: 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.
131updateSetting: 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
163saveSetting: 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
178initSetting: 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
192forceSetting: function(name, val) {
193    UI.updateSetting(name, val);
194    return val;
195},
196
197
198// Show the clipboard panel
199toggleClipboardPanel: 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
224toggleConnectPanel: 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
253toggleSettingsPanel: 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
281openSettingsMenu: 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)
297closeSettingsMenu: 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
304settingsApply: 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
329setPassword: 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
339sendCtrlAltDel: function() {
340    UI.rfb.sendCtrlAltDel();
341},
342
343setMouseButton: 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
370updateState: 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
413updateVisualState: 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
465clipReceive: 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
472connect: 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
499disconnect: 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
508displayBlur: function() {
509    UI.rfb.get_keyboard().set_focused(false);
510    UI.rfb.get_mouse().set_focused(false);
511},
512
513displayFocus: function() {
514    UI.rfb.get_keyboard().set_focused(true);
515    UI.rfb.get_mouse().set_focused(true);
516},
517
518clipClear: function() {
519    $D('noVNC_clipboard_text').value = "";
520    UI.rfb.clipboardPasteFrom("");
521},
522
523clipSend: 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
532setViewClip: 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
570setViewDrag: 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
595showKeyboard: 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
607keyInputBlur: 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
615setKeyboard: function() {
616    UI.keyboardVisible = false;
617},
618
619// iOS < Version 5 does not support position fixed. Javascript workaround:
620setOnscroll: function() {
621    window.onscroll = function() {
622        UI.setBarPosition();
623    };
624},
625
626setResize: function () {
627    window.onResize = function() {
628        UI.setBarPosition();
629    };
630},
631
632//Helper to add options to dropdown.
633addOption: 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
641setBarPosition: 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