]>
git.proxmox.com Git - novnc-pve.git/blob - pveui.js
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)
7 * See README.md for usage and integration instructions.
11 /*jslint white: false, browser: true */
12 /*global window, $D, Util, WebUtil, RFB, Display */
14 // Load supporting scripts
15 window
.onscriptsload = function () { UI
.load(); };
16 window
.onload = function () { UI
.keyboardinputReset(); };
17 Util
.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
18 "keysymdef.js", "keyboard.js", "input.js", "display.js",
19 "jsunzip.js", "rfb.js", "keysym.js"]);
24 pveCommandsOpen
: false,
26 connSettingsOpen
: false,
27 popupStatusOpen
: false,
29 keyboardVisible
: false,
30 hideKeyboardTimeout
: null,
31 lastKeyboardinput
: null,
32 defaultKeyboardinputLen
: 100,
33 extraKeysVisible
: false,
38 consoletype
: undefined,
43 // Setup rfb object, load settings from browser storage, then call
44 // UI.init to setup the UI/menus
45 load: function (callback
) {
46 WebUtil
.initSettings(UI
.pve_start
, callback
);
49 // Proxmox VE related code
51 urlEncode: function(object
) {
52 var i
,value
, params
= [];
55 if (object
.hasOwnProperty(i
)) {
57 if (value
=== undefined) value
= '';
58 params
.push(encodeURIComponent(i
) + '=' + encodeURIComponent(String(value
)));
62 return params
.join('&');
65 API2Request: function(reqOpts
) {
67 reqOpts
.method
= reqOpts
.method
|| 'GET';
69 var xhr
= new XMLHttpRequest();
71 xhr
.onload = function() {
72 var scope
= reqOpts
.scope
|| this;
76 if (xhr
.readyState
=== 4) {
77 var ctype
= xhr
.getResponseHeader('Content-Type');
78 if (xhr
.status
=== 200) {
79 if (ctype
.match(/application\/json;/)) {
80 result
= JSON
.parse(xhr
.responseText
);
82 errmsg
= 'got unexpected content type ' + ctype
;
85 errmsg
= 'Error ' + xhr
.status
+ ': ' + xhr
.statusText
;
88 errmsg
= 'Connection error - server offline?';
91 if (errmsg
!== undefined) {
92 if (reqOpts
.failure
) {
93 reqOpts
.failure
.call(scope
, errmsg
);
96 if (reqOpts
.success
) {
97 reqOpts
.success
.call(scope
, result
);
100 if (reqOpts
.callback
) {
101 reqOpts
.callback
.call(scope
, errmsg
=== undefined);
105 var data
= UI
.urlEncode(reqOpts
.params
|| {});
107 if (reqOpts
.method
=== 'GET') {
108 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
+ '?' + data
);
110 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
);
112 xhr
.setRequestHeader('Cache-Control', 'no-cache');
113 if (reqOpts
.method
=== 'POST' || reqOpts
.method
=== 'PUT') {
114 xhr
.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
116 xhr
.setRequestHeader('CSRFPreventionToken', PVE
.CSRFPreventionToken
);
118 } else if (reqOpts
.method
=== 'GET') {
121 throw "unknown method";
127 pve_vm_command: function(cmd
, params
, reload
) {
130 if (UI
.consoletype
=== 'kvm') {
131 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
132 } else if (UI
.consoletype
=== 'openvz') {
133 baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
135 throw "unknown VM type";
138 var show_msg = function(klass
, msg
) {
139 // show msg for 5 seconds
140 var oldklass
= $D('noVNC-control-bar').getAttribute("class");
141 $D('noVNC-control-bar').setAttribute("class", klass
);
142 var oldmsg
= $D('noVNC_status').innerHTML
;
143 $D('noVNC_status').innerHTML
= msg
;
144 setTimeout(function() {
145 var curmsg
= $D('noVNC_status').innerHTML
;
146 if (curmsg
=== msg
) {
147 $D('noVNC_status').innerHTML
= oldmsg
;
149 var curklass
= $D('noVNC-control-bar').getAttribute("class");
150 if (curklass
=== klass
) {
151 $D('noVNC-control-bar').setAttribute("class", oldklass
);
158 url
: baseUrl
+ "/status/" + cmd
,
160 failure: function(msg
) {
161 show_msg('noVNC_status_warn', msg
);
163 success: function() {
164 show_msg('noVNC_status_normall', "VM command '" + cmd
+"' successful");
166 setTimeout(function() {
174 pveCmdStart: function() {
175 if (UI
.pveCommandsOpen
=== true) {
176 UI
.togglePVECommandPanel();
178 UI
.pve_vm_command('start', {}, true);
181 pveCmdShutdown: function() {
182 if (UI
.pveCommandsOpen
=== true) {
183 UI
.togglePVECommandPanel();
185 var msg
= gettext("Do you really want to shutdown VM {0}?");
186 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
188 if (confirm(msg
) === true) {
189 UI
.pve_vm_command('shutdown');
193 pveCmdStop: function() {
194 if (UI
.pveCommandsOpen
=== true) {
195 UI
.togglePVECommandPanel();
198 var msg
= gettext("Do you really want to stop VM {0}?");
199 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
201 if (confirm(msg
) === true) {
202 UI
.pve_vm_command('stop');
206 pveCmdReset: function() {
207 if (UI
.pveCommandsOpen
=== true) {
208 UI
.togglePVECommandPanel();
210 var msg
= gettext("Do you really want to reset VM {0}?");
211 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
213 if (confirm(msg
) === true) {
214 UI
.pve_vm_command('reset');
218 pveCmdSuspend: function() {
219 if (UI
.pveCommandsOpen
=== true) {
220 UI
.togglePVECommandPanel();
222 var msg
= gettext("Do you really want to suspend VM {0}?");
223 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
225 if (confirm(msg
) === true) {
226 UI
.pve_vm_command('suspend');
230 pveCmdResume: function() {
231 if (UI
.pveCommandsOpen
=== true) {
232 UI
.togglePVECommandPanel();
234 UI
.pve_vm_command('resume');
237 pveCmdReload: function() {
238 if (UI
.pveCommandsOpen
=== true) {
239 UI
.togglePVECommandPanel();
244 pveReload: function() {
248 pve_start: function(callback
) {
249 UI
.consoletype
= WebUtil
.getQueryVar('console');
250 UI
.vmid
= WebUtil
.getQueryVar('vmid');
251 UI
.vmname
= WebUtil
.getQueryVar('vmname');
252 UI
.nodename
= WebUtil
.getQueryVar('node');
256 var params
= { websocket
: 1 };
259 var vmcmd_btns
= ['pveStartButton', 'pveShutdownButton', 'pveStopButton', 'pveResetButton', 'pveSuspendButton', 'pveResumeButton'];
260 vmcmd_btns
.forEach(function(btn
) {
262 el
.style
.display
= "none";
263 el
.value
= gettext(el
.value
);
269 if (UI
.consoletype
=== 'kvm') {
270 var baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
271 url
= baseUrl
+ '/vncproxy';
272 wsurl
= baseUrl
+ '/vncwebsocket';
273 vmcmd_btns
.forEach(function(btn
) {
274 $D(btn
).style
.display
= "";
276 title
= "VM " + UI
.vmid
;
278 title
+= " ('" + UI
.vmname
+ "')";
280 } else if (UI
.consoletype
=== 'openvz') {
281 var baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
282 url
= baseUrl
+ '/vncproxy';
283 wsurl
= baseUrl
+ '/vncwebsocket';
284 ['pveStartButton', 'pveShutdownButton', 'pveStopButton'].forEach(function(btn
) {
285 $D(btn
).style
.display
= "";
287 title
= "CT " + UI
.vmid
;
289 title
+= " ('" + UI
.vmname
+ "')";
291 } else if (UI
.consoletype
=== 'shell') {
292 var baseUrl
= '/nodes/' + UI
.nodename
;
293 url
= baseUrl
+ '/vncshell';
294 wsurl
= baseUrl
+ '/vncwebsocket';
295 title
= "node '" + UI
.nodename
+ "'";
296 } else if (UI
.consoletype
=== 'upgrade') {
297 var baseUrl
= '/nodes/' + UI
.nodename
;
298 url
= baseUrl
+ '/vncshell';
299 wsurl
= baseUrl
+ '/vncwebsocket';
301 title
= gettext('System upgrade on node {0}');
302 title
= title
.replace(/\{0\}/, UI
.nodename
);
304 throw "implement me";
307 document
.title
= title
;
309 var start_vnc_viewer = function(param
) {
312 var wsparams
= UI
.urlEncode({
314 vncticket
: param
.ticket
317 UI
.updateSetting('host', window
.location
.hostname
);
318 UI
.updateSetting('port', window
.location
.port
);
319 UI
.updateSetting('password', param
.ticket
);
320 UI
.updateSetting('encrypt', true);
321 UI
.updateSetting('true_color', true);
322 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
323 UI
.updateSetting('shared', true);
324 UI
.updateSetting('view_only', false);
326 UI
.updateSetting('path', 'api2/json' + wsurl
+ "?" + wsparams
);
335 success: function(result
) {
336 start_vnc_viewer(result
.data
);
338 failure: function(msg
) {
339 console
.log("ERROR: " + msg
);
344 lastFBWidth
: undefined,
345 lastFBHeight
: undefined,
346 sizeUpdateTimer
: undefined,
348 updateFBSize: function(rfb
, width
, height
) {
350 UI
.lastFBWidth
= width
+ 1;
351 UI
.lastFBHeight
= height
+ 5;
353 if (UI
.sizeUpdateTimer
!== undefined) {
354 clearInterval(UI
.sizeUpdateTimer
);
356 if (UI
.getSetting('clip')) return;
358 var update_size = function() {
362 if (window
.innerHeight
) {
363 oh
= window
.innerHeight
;
364 ow
= window
.innerWidth
;
365 } else if (document
.documentElement
&&
366 document
.documentElement
.clientHeight
) {
367 oh
= document
.documentElement
.clientHeight
;
368 ow
= document
.documentElement
.clientWidth
;
369 } else if (document
.body
) {
370 oh
= document
.body
.clientHeight
;
371 ow
= document
.body
.clientWidth
;
373 throw "can't get window size";
376 // see base.css/noVNC_screen_pad
377 var toolbar_height
= 36;
379 var offsetw
= UI
.lastFBWidth
- ow
;
380 var offseth
= UI
.lastFBHeight
+ toolbar_height
- oh
;
381 if (offsetw
!== 0 || offseth
!== 0) {
382 //console.log("try resize by " + offsetw + " " + offseth);
383 window
.resizeBy(offsetw
, offseth
);
388 UI
.sizeUpdateTimer
= setInterval(update_size
, 1000);
395 // Open/close PVE connand menu
396 togglePVECommandPanel: function() {
397 // Close the description panel
398 $D('noVNC_description').style
.display
= "none";
399 // Close clipboard panel if open
400 if (UI
.clipboardOpen
=== true) {
401 UI
.toggleClipboardPanel();
403 // Close connection settings if open
404 if (UI
.connSettingsOpen
=== true) {
405 UI
.toggleConnectPanel();
407 // Close popup status panel if open
408 if (UI
.popupStatusOpen
=== true) {
409 UI
.togglePopupStatusPanel();
411 // Close XVP panel if open
412 if (UI
.xvpOpen
=== true) {
415 if (UI
.pveCommandsOpen
) {
416 $D('noVNC_pve_commands').style
.display
= "none";
417 $D('pveCommandsButton').className
= "noVNC_status_button";
418 UI
.pveCommandsOpen
= false;
420 $D('noVNC_pve_commands').style
.display
= "block";
421 $D('pveCommandsButton').className
= "noVNC_status_button_selected";
422 UI
.pveCommandsOpen
= true;
426 // Render default UI and initialize settings menu
427 start: function(callback
) {
428 var html
= '', i
, sheet
, sheets
, llevels
, port
, autoconnect
;
430 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
432 // Stylesheet selection dropdown
433 sheet
= WebUtil
.selectStylesheet();
434 sheets
= WebUtil
.getStylesheets();
435 for (i
= 0; i
< sheets
.length
; i
+= 1) {
436 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
439 // Logging selection dropdown
440 llevels
= ['error', 'warn', 'info', 'debug'];
441 for (i
= 0; i
< llevels
.length
; i
+= 1) {
442 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
445 // Settings with immediate effects
446 UI
.initSetting('logging', 'warn');
447 WebUtil
.init_logging(UI
.getSetting('logging'));
449 UI
.initSetting('stylesheet', 'default');
450 WebUtil
.selectStylesheet(null);
451 // call twice to get around webkit bug
452 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
454 UI
.initSetting('repeaterID', '');
456 UI
.rfb
= RFB({'target': $D('noVNC_canvas'),
457 'onUpdateState': UI
.updateState
,
458 'onXvpInit': UI
.updateXvpVisualState
,
459 'onClipboard': UI
.clipReceive
,
460 //'onDesktopName': UI.updateDocumentTitle,
461 'onFBResize': UI
.updateFBSize
});
464 if (autoconnect
=== 'true' || autoconnect
== '1') {
471 UI
.updateVisualState();
473 // Unfocus clipboard when over the VNC area
474 //$D('VNC_screen').onmousemove = function () {
475 // var keyboard = UI.rfb.get_keyboard();
476 // if ((! keyboard) || (! keyboard.get_focused())) {
477 // $D('VNC_clipboard_text').blur();
481 // Show mouse selector buttons on touch screen devices
482 if (UI
.isTouchDevice
) {
483 // Show mobile buttons
484 $D('noVNC_mobile_buttons').style
.display
= "inline";
486 // Remove the address bar
487 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
488 UI
.forceSetting('clip', true);
489 $D('noVNC_clip').disabled
= true;
491 UI
.initSetting('clip', false);
494 //iOS Safari does not support CSS position:fixed.
495 //This detects iOS devices and enables javascript workaround.
496 if ((navigator
.userAgent
.match(/iPhone/i)) ||
497 (navigator
.userAgent
.match(/iPod/i)) ||
498 (navigator
.userAgent
.match(/iPad/i))) {
504 $D('noVNC_host').focus();
507 Util
.addEvent(window
, 'resize', UI
.setViewClip
);
509 Util
.addEvent(window
, 'beforeunload', function () {
510 if (UI
.rfb_state
=== 'normal') {
511 return "You are currently connected.";
515 // Show description by default when hosted at for kanaka.github.com
516 if (location
.host
=== "kanaka.github.io") {
517 // Open the description dialog
518 $D('noVNC_description').style
.display
= "block";
520 // Show the connect panel on first load unless autoconnecting
521 if (autoconnect
=== UI
.connSettingsOpen
) {
522 UI
.toggleConnectPanel();
526 // Add mouse event click/focus/blur event handlers to the UI
527 UI
.addMouseHandlers();
529 if (typeof callback
=== "function") {
534 addMouseHandlers: function() {
535 // Setup interface handlers that can't be inline
536 $D("noVNC_view_drag_button").onclick
= UI
.setViewDrag
;
537 $D("noVNC_mouse_button0").onclick = function () { UI
.setMouseButton(1); };
538 $D("noVNC_mouse_button1").onclick = function () { UI
.setMouseButton(2); };
539 $D("noVNC_mouse_button2").onclick = function () { UI
.setMouseButton(4); };
540 $D("noVNC_mouse_button4").onclick = function () { UI
.setMouseButton(0); };
541 $D("showKeyboard").onclick
= UI
.showKeyboard
;
543 $D("keyboardinput").oninput
= UI
.keyInput
;
544 $D("keyboardinput").onblur
= UI
.keyInputBlur
;
546 $D("showExtraKeysButton").onclick
= UI
.showExtraKeys
;
547 $D("toggleCtrlButton").onclick
= UI
.toggleCtrl
;
548 $D("toggleAltButton").onclick
= UI
.toggleAlt
;
549 $D("sendTabButton").onclick
= UI
.sendTab
;
550 $D("sendEscButton").onclick
= UI
.sendEsc
;
552 $D("pveStartButton").onclick
= UI
.pveCmdStart
;
553 $D("pveShutdownButton").onclick
= UI
.pveCmdShutdown
;
554 $D("pveStopButton").onclick
= UI
.pveCmdStop
;
555 $D("pveResetButton").onclick
= UI
.pveCmdReset
;
556 $D("pveSuspendButton").onclick
= UI
.pveCmdSuspend
;
557 $D("pveResumeButton").onclick
= UI
.pveCmdResume
;
558 $D("pveReloadButton").onclick
= UI
.pveCmdReload
;
560 $D("sendCtrlAltDelButton").onclick
= UI
.sendCtrlAltDel
;
561 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
562 //$D("xvpRebootButton").onclick = UI.xvpReboot;
563 //$D("xvpResetButton").onclick = UI.xvpReset;
564 $D("noVNC_status").onclick
= UI
.togglePopupStatusPanel
;
565 $D("noVNC_popup_status_panel").onclick
= UI
.togglePopupStatusPanel
;
566 //$D("xvpButton").onclick = UI.toggleXvpPanel;
567 $D("clipboardButton").onclick
= UI
.toggleClipboardPanel
;
568 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
569 $D("pveCommandsButton").onclick
= UI
.togglePVECommandPanel
;
570 //$D("connectButton").onclick = UI.toggleConnectPanel;
571 //$D("disconnectButton").onclick = UI.disconnect;
572 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
574 $D("noVNC_clipboard_text").onfocus
= UI
.displayBlur
;
575 $D("noVNC_clipboard_text").onblur
= UI
.displayFocus
;
576 $D("noVNC_clipboard_text").onchange
= UI
.clipSend
;
577 $D("noVNC_clipboard_clear_button").onclick
= UI
.clipClear
;
579 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
580 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
581 //$D("noVNC_apply").onclick = UI.settingsApply;
583 //$D("noVNC_connect_button").onclick = UI.connect;
586 // Read form control compatible setting from cookie
587 getSetting: function(name
) {
588 console
.log("GET: " + name
);
589 var val
, ctrl
= $D('noVNC_' + name
);
590 val
= WebUtil
.readSetting(name
);
591 if (val
!== null && ctrl
.type
=== 'checkbox') {
592 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
601 // Update cookie and form control setting. If value is not set, then
602 // updates from control to current cookie setting.
603 updateSetting: function(name
, value
) {
605 var i
, ctrl
= $D('noVNC_' + name
);
606 // Save the cookie for this session
607 if (typeof value
!== 'undefined') {
608 WebUtil
.writeSetting(name
, value
);
611 // Update the settings control
612 value
= UI
.getSetting(name
);
614 if (ctrl
.type
=== 'checkbox') {
615 ctrl
.checked
= value
;
617 } else if (typeof ctrl
.options
!== 'undefined') {
618 for (i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
619 if (ctrl
.options
[i
].value
=== value
) {
620 ctrl
.selectedIndex
= i
;
625 /*Weird IE9 error leads to 'null' appearring
626 in textboxes instead of ''.*/
627 if (value
=== null) {
634 // Save control setting to cookie
635 saveSetting: function(name
) {
636 var val
, ctrl
= $D('noVNC_' + name
);
637 if (ctrl
.type
=== 'checkbox') {
639 } else if (typeof ctrl
.options
!== 'undefined') {
640 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
644 WebUtil
.writeSetting(name
, val
);
645 //Util.Debug("Setting saved '" + name + "=" + val + "'");
649 // Initial page load read/initialization of settings
650 initSetting: function(name
, defVal
) {
653 // Check Query string followed by cookie
654 val
= WebUtil
.getQueryVar(name
);
656 val
= WebUtil
.readSetting(name
, defVal
);
658 UI
.updateSetting(name
, val
);
659 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
663 // Force a setting to be a certain value
664 forceSetting: function(name
, val
) {
665 UI
.updateSetting(name
, val
);
670 // Show the popup status panel
671 togglePopupStatusPanel: function() {
672 var psp
= $D('noVNC_popup_status_panel');
673 if (UI
.popupStatusOpen
=== true) {
674 psp
.style
.display
= "none";
675 UI
.popupStatusOpen
= false;
677 psp
.innerHTML
= $D('noVNC_status').innerHTML
;
678 psp
.style
.display
= "block";
679 psp
.style
.left
= window
.innerWidth
/2 -
680 parseInt(window
.getComputedStyle(psp
, false).width
)/2 -30 + "px";
681 UI
.popupStatusOpen
= true;
685 // Show the XVP panel
686 toggleXvpPanel: function() {
687 // Close the description panel
688 $D('noVNC_description').style
.display
= "none";
689 if (UI
.pveCommandsOpen
=== true) {
690 UI
.togglePVECommandPanel();
692 // Close settings if open
693 if (UI
.settingsOpen
=== true) {
695 UI
.closeSettingsMenu();
697 // Close connection settings if open
698 if (UI
.connSettingsOpen
=== true) {
699 UI
.toggleConnectPanel();
701 // Close popup status panel if open
702 if (UI
.popupStatusOpen
=== true) {
703 UI
.togglePopupStatusPanel();
705 // Close clipboard panel if open
706 if (UI
.clipboardOpen
=== true) {
707 UI
.toggleClipboardPanel();
710 if (UI
.xvpOpen
=== true) {
711 //$D('noVNC_xvp').style.display = "none";
712 //$D('xvpButton').className = "noVNC_status_button";
715 //$D('noVNC_xvp').style.display = "block";
716 //$D('xvpButton').className = "noVNC_status_button_selected";
721 // Show the clipboard panel
722 toggleClipboardPanel: function() {
723 // Close the description panel
724 $D('noVNC_description').style
.display
= "none";
725 if (UI
.pveCommandsOpen
=== true) {
726 UI
.togglePVECommandPanel();
728 // Close settings if open
729 if (UI
.settingsOpen
=== true) {
731 UI
.closeSettingsMenu();
733 // Close connection settings if open
734 if (UI
.connSettingsOpen
=== true) {
735 UI
.toggleConnectPanel();
737 // Close popup status panel if open
738 if (UI
.popupStatusOpen
=== true) {
739 UI
.togglePopupStatusPanel();
741 // Close XVP panel if open
742 if (UI
.xvpOpen
=== true) {
745 // Toggle Clipboard Panel
746 if (UI
.clipboardOpen
=== true) {
747 $D('noVNC_clipboard').style
.display
= "none";
748 $D('clipboardButton').className
= "noVNC_status_button";
749 UI
.clipboardOpen
= false;
751 $D('noVNC_clipboard').style
.display
= "block";
752 $D('clipboardButton').className
= "noVNC_status_button_selected";
753 UI
.clipboardOpen
= true;
757 // Show the connection settings panel/menu
758 toggleConnectPanel: function() {
759 // Close the description panel
760 $D('noVNC_description').style
.display
= "none";
761 if (UI
.pveCommandsOpen
=== true) {
762 UI
.togglePVECommandPanel();
764 // Close connection settings if open
765 if (UI
.settingsOpen
=== true) {
767 UI
.closeSettingsMenu();
768 //$D('connectButton').className = "noVNC_status_button";
770 // Close clipboard panel if open
771 if (UI
.clipboardOpen
=== true) {
772 UI
.toggleClipboardPanel();
774 // Close popup status panel if open
775 if (UI
.popupStatusOpen
=== true) {
776 UI
.togglePopupStatusPanel();
778 // Close XVP panel if open
779 if (UI
.xvpOpen
=== true) {
783 // Toggle Connection Panel
784 if (UI
.connSettingsOpen
=== true) {
785 $D('noVNC_controls').style
.display
= "none";
786 //$D('connectButton').className = "noVNC_status_button";
787 UI
.connSettingsOpen
= false;
788 UI
.saveSetting('host');
789 UI
.saveSetting('port');
790 //UI.saveSetting('password');
792 $D('noVNC_controls').style
.display
= "block";
793 //$D('connectButton').className = "noVNC_status_button_selected";
794 UI
.connSettingsOpen
= true;
795 $D('noVNC_host').focus();
799 // Toggle the settings menu:
800 // On open, settings are refreshed from saved cookies.
801 // On close, settings are applied
802 toggleSettingsPanel: function() {
803 // Close the description panel
804 $D('noVNC_description').style
.display
= "none";
805 if (UI
.settingsOpen
) {
807 UI
.closeSettingsMenu();
809 UI
.updateSetting('encrypt');
810 UI
.updateSetting('true_color');
811 if (UI
.rfb
.get_display().get_cursor_uri()) {
812 UI
.updateSetting('cursor');
814 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
815 $D('noVNC_cursor').disabled
= true;
817 UI
.updateSetting('clip');
818 UI
.updateSetting('shared');
819 UI
.updateSetting('view_only');
820 UI
.updateSetting('path');
821 UI
.updateSetting('repeaterID');
822 UI
.updateSetting('stylesheet');
823 UI
.updateSetting('logging');
825 UI
.openSettingsMenu();
830 openSettingsMenu: function() {
831 // Close the description panel
832 $D('noVNC_description').style
.display
= "none";
833 if (UI
.pveCommandsOpen
=== true) {
834 UI
.togglePVECommandPanel();
836 // Close clipboard panel if open
837 if (UI
.clipboardOpen
=== true) {
838 UI
.toggleClipboardPanel();
840 // Close connection settings if open
841 if (UI
.connSettingsOpen
=== true) {
842 UI
.toggleConnectPanel();
844 // Close popup status panel if open
845 if (UI
.popupStatusOpen
=== true) {
846 UI
.togglePopupStatusPanel();
848 // Close XVP panel if open
849 if (UI
.xvpOpen
=== true) {
852 $D('noVNC_settings').style
.display
= "block";
853 //$D('settingsButton').className = "noVNC_status_button_selected";
854 UI
.settingsOpen
= true;
857 // Close menu (without applying settings)
858 closeSettingsMenu: function() {
859 $D('noVNC_settings').style
.display
= "none";
860 //$D('settingsButton').className = "noVNC_status_button";
861 UI
.settingsOpen
= false;
864 // Save/apply settings when 'Apply' button is pressed
865 settingsApply: function() {
866 //Util.Debug(">> settingsApply");
867 UI
.saveSetting('encrypt');
868 UI
.saveSetting('true_color');
869 if (UI
.rfb
.get_display().get_cursor_uri()) {
870 UI
.saveSetting('cursor');
872 UI
.saveSetting('clip');
873 UI
.saveSetting('shared');
874 UI
.saveSetting('view_only');
875 UI
.saveSetting('path');
876 UI
.saveSetting('repeaterID');
877 UI
.saveSetting('stylesheet');
878 UI
.saveSetting('logging');
880 // Settings with immediate (non-connected related) effect
881 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
882 WebUtil
.init_logging(UI
.getSetting('logging'));
884 UI
.setViewDrag(UI
.rfb
.get_viewportDrag());
885 //Util.Debug("<< settingsApply");
890 setPassword: function() {
891 UI
.rfb
.sendPassword($D('noVNC_password').value
);
892 //Reset connect button.
893 $D('noVNC_connect_button').value
= "Connect";
894 $D('noVNC_connect_button').onclick
= UI
.Connect
;
895 //Hide connection panel.
896 UI
.toggleConnectPanel();
900 sendCtrlAltDel: function() {
901 UI
.rfb
.sendCtrlAltDel();
904 xvpShutdown: function() {
905 UI
.rfb
.xvpShutdown();
908 xvpReboot: function() {
912 xvpReset: function() {
916 setMouseButton: function(num
) {
917 var b
, blist
= [0, 1,2,4], button
;
919 if (typeof num
=== 'undefined') {
920 // Disable mouse buttons
924 UI
.rfb
.get_mouse().set_touchButton(num
);
927 for (b
= 0; b
< blist
.length
; b
++) {
928 button
= $D('noVNC_mouse_button' + blist
[b
]);
929 if (blist
[b
] === num
) {
930 button
.style
.display
= "";
932 button
.style
.display
= "none";
934 button.style.backgroundColor = "black";
935 button.style.color = "lightgray";
936 button.style.backgroundColor = "";
937 button.style.color = "";
943 updateState: function(rfb
, state
, oldstate
, msg
) {
944 var s
, sb
, c
, d
, cad
, vd
, klass
;
945 UI
.rfb_state
= state
;
949 klass
= "noVNC_status_error";
952 klass
= "noVNC_status_normal";
955 $D('noVNC_logo').style
.display
= "block";
958 klass
= "noVNC_status_normal";
961 UI
.toggleConnectPanel();
963 $D('noVNC_connect_button').value
= "Send Password";
964 $D('noVNC_connect_button').onclick
= UI
.setPassword
;
965 $D('noVNC_password').focus();
967 klass
= "noVNC_status_warn";
970 klass
= "noVNC_status_warn";
974 if (typeof(msg
) !== 'undefined') {
975 $D('noVNC-control-bar').setAttribute("class", klass
);
976 $D('noVNC_status').innerHTML
= msg
;
979 UI
.updateVisualState();
982 // Disable/enable controls depending on connection state
983 updateVisualState: function() {
984 var connected
= UI
.rfb_state
=== 'normal' ? true : false;
986 //Util.Debug(">> updateVisualState");
987 $D('noVNC_encrypt').disabled
= connected
;
988 $D('noVNC_true_color').disabled
= connected
;
989 if (UI
.rfb
&& UI
.rfb
.get_display() &&
990 UI
.rfb
.get_display().get_cursor_uri()) {
991 $D('noVNC_cursor').disabled
= connected
;
993 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
994 $D('noVNC_cursor').disabled
= true;
996 $D('noVNC_shared').disabled
= connected
;
997 $D('noVNC_view_only').disabled
= connected
;
998 $D('noVNC_path').disabled
= connected
;
999 $D('noVNC_repeaterID').disabled
= connected
;
1003 UI
.setMouseButton(1);
1004 $D('clipboardButton').style
.display
= (UI
.consoletype
!== 'kvm') ? "inline" : "none";
1005 $D('showKeyboard').style
.display
= "inline";
1006 $D('noVNC_extra_keys').style
.display
= "";
1007 $D('sendCtrlAltDelButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
1009 UI
.setMouseButton();
1010 $D('clipboardButton').style
.display
= "none";
1011 $D('showKeyboard').style
.display
= "none";
1012 $D('noVNC_extra_keys').style
.display
= "none";
1013 $D('sendCtrlAltDelButton').style
.display
= "none";
1014 UI
.updateXvpVisualState(0);
1017 // State change disables viewport dragging.
1018 // It is enabled (toggled) by direct click on the button
1019 UI
.setViewDrag(false);
1021 switch (UI
.rfb_state
) {
1025 case 'disconnected':
1026 //$D('connectButton').style.display = "";
1027 //$D('disconnectButton').style.display = "none";
1030 //$D('connectButton').style.display = "none";
1031 //$D('disconnectButton').style.display = "";
1035 //Util.Debug("<< updateVisualState");
1038 // Disable/enable XVP button
1039 updateXvpVisualState: function(ver
) {
1042 //$D('xvpButton').style.display = 'inline';
1044 //$D('xvpButton').style.display = 'none';
1045 // Close XVP panel if open
1046 if (UI
.xvpOpen
=== true) {
1047 UI
.toggleXvpPanel();
1053 // Display the desktop name in the document title
1054 updateDocumentTitle: function(rfb
, name
) {
1055 document
.title
= name
+ " - noVNC";
1059 clipReceive: function(rfb
, text
) {
1060 Util
.Debug(">> UI.clipReceive: " + text
.substr(0,40) + "...");
1061 $D('noVNC_clipboard_text').value
= text
;
1062 Util
.Debug("<< UI.clipReceive");
1066 connect: function() {
1067 var host
, port
, password
, path
;
1069 UI
.closeSettingsMenu();
1070 UI
.toggleConnectPanel();
1072 host
= $D('noVNC_host').value
;
1073 port
= $D('noVNC_port').value
;
1074 password
= $D('noVNC_password').value
;
1075 path
= $D('noVNC_path').value
;
1076 if ((!host
) || (!port
)) {
1077 throw("Must set host and port");
1080 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1081 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1082 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1083 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1084 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1085 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1087 UI
.rfb
.connect(host
, port
, password
, path
);
1090 setTimeout(UI
.setBarPosition
, 100);
1091 $D('noVNC_logo').style
.display
= "none";
1094 disconnect: function() {
1095 UI
.closeSettingsMenu();
1096 UI
.rfb
.disconnect();
1098 $D('noVNC_logo').style
.display
= "block";
1099 UI
.connSettingsOpen
= false;
1100 UI
.toggleConnectPanel();
1103 displayBlur: function() {
1104 UI
.rfb
.get_keyboard().set_focused(false);
1105 UI
.rfb
.get_mouse().set_focused(false);
1108 displayFocus: function() {
1109 UI
.rfb
.get_keyboard().set_focused(true);
1110 UI
.rfb
.get_mouse().set_focused(true);
1113 clipClear: function() {
1114 $D('noVNC_clipboard_text').value
= "";
1115 UI
.rfb
.clipboardPasteFrom("");
1118 clipSend: function() {
1119 var text
= $D('noVNC_clipboard_text').value
;
1120 Util
.Debug(">> UI.clipSend: " + text
.substr(0,40) + "...");
1121 UI
.rfb
.clipboardPasteFrom(text
);
1122 Util
.Debug("<< UI.clipSend");
1126 // Enable/disable and configure viewport clipping
1127 setViewClip: function(clip
) {
1128 var display
, cur_clip
, pos
, new_w
, new_h
;
1131 display
= UI
.rfb
.get_display();
1136 cur_clip
= display
.get_viewport();
1138 if (typeof(clip
) !== 'boolean') {
1139 // Use current setting
1140 clip
= UI
.getSetting('clip');
1143 if (clip
&& !cur_clip
) {
1145 UI
.updateSetting('clip', true);
1146 } else if (!clip
&& cur_clip
) {
1147 // Turn clipping off
1148 UI
.updateSetting('clip', false);
1149 display
.set_viewport(false);
1150 $D('noVNC_canvas').style
.position
= 'static';
1151 display
.viewportChange();
1153 if (UI
.getSetting('clip')) {
1154 // If clipping, update clipping settings
1155 $D('noVNC_canvas').style
.position
= 'absolute';
1156 pos
= Util
.getPosition($D('noVNC_canvas'));
1157 new_w
= window
.innerWidth
- pos
.x
;
1158 new_h
= window
.innerHeight
- pos
.y
;
1159 display
.set_viewport(true);
1160 display
.viewportChange(0, 0, new_w
, new_h
);
1164 // Toggle/set/unset the viewport drag/move button
1165 setViewDrag: function(drag
) {
1166 var vmb
= $D('noVNC_view_drag_button');
1167 if (!UI
.rfb
) { return; }
1169 if (UI
.rfb_state
=== 'normal' &&
1170 UI
.rfb
.get_display().get_viewport()) {
1171 vmb
.style
.display
= "inline";
1173 vmb
.style
.display
= "none";
1176 if (typeof(drag
) === "undefined" ||
1177 typeof(drag
) === "object") {
1178 // If not specified, then toggle
1179 drag
= !UI
.rfb
.get_viewportDrag();
1182 vmb
.className
= "noVNC_status_button_selected";
1183 UI
.rfb
.set_viewportDrag(true);
1185 vmb
.className
= "noVNC_status_button";
1186 UI
.rfb
.set_viewportDrag(false);
1190 // On touch devices, show the OS keyboard
1191 showKeyboard: function() {
1193 kbi
= $D('keyboardinput');
1194 skb
= $D('showKeyboard');
1195 l
= kbi
.value
.length
;
1196 if(UI
.keyboardVisible
=== false) {
1198 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1199 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1200 UI
.keyboardVisible
= true;
1201 skb
.className
= "noVNC_status_button_selected";
1202 } else if(UI
.keyboardVisible
=== true) {
1204 skb
.className
= "noVNC_status_button";
1205 UI
.keyboardVisible
= false;
1209 keepKeyboard: function() {
1210 clearTimeout(UI
.hideKeyboardTimeout
);
1211 if(UI
.keyboardVisible
=== true) {
1212 $D('keyboardinput').focus();
1213 $D('showKeyboard').className
= "noVNC_status_button_selected";
1214 } else if(UI
.keyboardVisible
=== false) {
1215 $D('keyboardinput').blur();
1216 $D('showKeyboard').className
= "noVNC_status_button";
1220 keyboardinputReset: function() {
1221 var kbi
= $D('keyboardinput');
1222 kbi
.value
= Array(UI
.defaultKeyboardinputLen
).join("_");
1223 UI
.lastKeyboardinput
= kbi
.value
;
1226 // When normal keyboard events are left uncought, use the input events from
1227 // the keyboardinput element instead and generate the corresponding key events.
1228 // This code is required since some browsers on Android are inconsistent in
1229 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1230 keyInput: function(event
) {
1231 var newValue
, oldValue
, newLen
, oldLen
;
1232 newValue
= event
.target
.value
;
1233 oldValue
= UI
.lastKeyboardinput
;
1236 // Try to check caret position since whitespace at the end
1237 // will not be considered by value.length in some browsers
1238 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1240 // selectionStart is undefined in Google Chrome
1241 newLen
= newValue
.length
;
1243 oldLen
= oldValue
.length
;
1246 var inputs
= newLen
- oldLen
;
1248 backspaces
= -inputs
;
1252 // Compare the old string with the new to account for
1253 // text-corrections or other input that modify existing text
1254 for (var i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1255 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1256 inputs
= newLen
- i
;
1257 backspaces
= oldLen
- i
;
1262 // Send the key events
1263 for (var i
= 0; i
< backspaces
; i
++)
1264 UI
.rfb
.sendKey(XK_BackSpace
);
1265 for (var i
= newLen
- inputs
; i
< newLen
; i
++)
1266 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1268 // Control the text content length in the keyboardinput element
1269 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1270 UI
.keyboardinputReset();
1271 } else if (newLen
< 1) {
1272 // There always have to be some text in the keyboardinput
1273 // element with which backspace can interact.
1274 UI
.keyboardinputReset();
1275 // This sometimes causes the keyboard to disappear for a second
1276 // but it is required for the android keyboard to recognize that
1277 // text has been added to the field
1278 event
.target
.blur();
1279 // This has to be ran outside of the input handler in order to work
1280 setTimeout(function() { UI
.keepKeyboard(); }, 0);
1283 UI
.lastKeyboardinput
= newValue
;
1287 keyInputBlur: function() {
1288 $D('showKeyboard').className
= "noVNC_status_button";
1289 //Weird bug in iOS if you change keyboardVisible
1290 //here it does not actually occur so next time
1291 //you click keyboard icon it doesnt work.
1292 UI
.hideKeyboardTimeout
= setTimeout(function() { UI
.setKeyboard(); },100);
1295 showExtraKeys: function() {
1297 if(UI
.extraKeysVisible
=== false) {
1298 $D('toggleCtrlButton').style
.display
= "inline";
1299 $D('toggleAltButton').style
.display
= "inline";
1300 $D('sendTabButton').style
.display
= "inline";
1301 $D('sendEscButton').style
.display
= "inline";
1302 $D('showExtraKeysButton').className
= "noVNC_status_button_selected";
1303 UI
.extraKeysVisible
= true;
1304 } else if(UI
.extraKeysVisible
=== true) {
1305 $D('toggleCtrlButton').style
.display
= "";
1306 $D('toggleAltButton').style
.display
= "";
1307 $D('sendTabButton').style
.display
= "";
1308 $D('sendEscButton').style
.display
= "";
1309 $D('showExtraKeysButton').className
= "noVNC_status_button";
1310 UI
.extraKeysVisible
= false;
1314 toggleCtrl: function() {
1316 if(UI
.ctrlOn
=== false) {
1317 UI
.rfb
.sendKey(XK_Control_L
, true);
1318 $D('toggleCtrlButton').className
= "noVNC_status_button_selected";
1320 } else if(UI
.ctrlOn
=== true) {
1321 UI
.rfb
.sendKey(XK_Control_L
, false);
1322 $D('toggleCtrlButton').className
= "noVNC_status_button";
1327 toggleAlt: function() {
1329 if(UI
.altOn
=== false) {
1330 UI
.rfb
.sendKey(XK_Alt_L
, true);
1331 $D('toggleAltButton').className
= "noVNC_status_button_selected";
1333 } else if(UI
.altOn
=== true) {
1334 UI
.rfb
.sendKey(XK_Alt_L
, false);
1335 $D('toggleAltButton').className
= "noVNC_status_button";
1340 sendTab: function() {
1342 UI
.rfb
.sendKey(XK_Tab
);
1345 sendEsc: function() {
1347 UI
.rfb
.sendKey(XK_Escape
);
1350 setKeyboard: function() {
1351 UI
.keyboardVisible
= false;
1354 // iOS < Version 5 does not support position fixed. Javascript workaround:
1355 setOnscroll: function() {
1356 window
.onscroll = function() {
1357 UI
.setBarPosition();
1361 setResize: function () {
1362 window
.onResize = function() {
1363 UI
.setBarPosition();
1367 //Helper to add options to dropdown.
1368 addOption: function(selectbox
,text
,value
)
1370 var optn
= document
.createElement("OPTION");
1373 selectbox
.options
.add(optn
);
1376 setBarPosition: function() {
1377 $D('noVNC-control-bar').style
.top
= (window
.pageYOffset
) + 'px';
1378 $D('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1380 var vncwidth
= $D('noVNC_screen').style
.offsetWidth
;
1381 $D('noVNC-control-bar').style
.width
= vncwidth
+ 'px';