]>
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');
115 xhr
.setRequestHeader('CSRFPreventionToken', PVE
.CSRFPreventionToken
);
117 } else if (reqOpts
.method
=== 'GET') {
120 throw "unknown method";
126 // show msg for 5 seconds
127 pve_show_msg: function(klass
, msg
, permanant
) {
128 var oldklass
= $D('noVNC-control-bar').getAttribute("class");
129 $D('noVNC-control-bar').setAttribute("class", klass
);
130 var oldmsg
= $D('noVNC_status').innerHTML
;
131 $D('noVNC_status').innerHTML
= msg
;
132 if (typeof permanent
!== 'undefined' && permanent
) return;
134 setTimeout(function() {
135 var curmsg
= $D('noVNC_status').innerHTML
;
136 if (curmsg
=== msg
) {
137 $D('noVNC_status').innerHTML
= oldmsg
;
139 var curklass
= $D('noVNC-control-bar').getAttribute("class");
140 if (curklass
=== klass
) {
141 $D('noVNC-control-bar').setAttribute("class", oldklass
);
146 pve_vm_command: function(cmd
, params
, reload
) {
149 if (UI
.consoletype
=== 'kvm') {
150 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
151 } else if (UI
.consoletype
=== 'openvz') {
152 baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
154 throw "unknown VM type";
159 url
: baseUrl
+ "/status/" + cmd
,
161 failure: function(msg
) {
162 UI
.pve_show_msg('noVNC_status_warn', msg
);
164 success: function() {
165 UI
.pve_show_msg('noVNC_status_normall', "VM command '" + cmd
+"' successful");
167 setTimeout(function() {
175 pveCmdStart: function() {
176 if (UI
.pveCommandsOpen
=== true) {
177 UI
.togglePVECommandPanel();
179 UI
.pve_vm_command('start', {}, true);
182 pveCmdShutdown: function() {
183 if (UI
.pveCommandsOpen
=== true) {
184 UI
.togglePVECommandPanel();
186 var msg
= gettext("Do you really want to shutdown VM {0}?");
187 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
189 if (confirm(msg
) === true) {
190 UI
.pve_vm_command('shutdown');
194 pveCmdStop: function() {
195 if (UI
.pveCommandsOpen
=== true) {
196 UI
.togglePVECommandPanel();
199 var msg
= gettext("Do you really want to stop VM {0}?");
200 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
202 if (confirm(msg
) === true) {
203 UI
.pve_vm_command('stop');
207 pveCmdReset: function() {
208 if (UI
.pveCommandsOpen
=== true) {
209 UI
.togglePVECommandPanel();
211 var msg
= gettext("Do you really want to reset VM {0}?");
212 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
214 if (confirm(msg
) === true) {
215 UI
.pve_vm_command('reset');
219 pveCmdSuspend: function() {
220 if (UI
.pveCommandsOpen
=== true) {
221 UI
.togglePVECommandPanel();
223 var msg
= gettext("Do you really want to suspend VM {0}?");
224 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
226 if (confirm(msg
) === true) {
227 UI
.pve_vm_command('suspend');
231 pveCmdResume: function() {
232 if (UI
.pveCommandsOpen
=== true) {
233 UI
.togglePVECommandPanel();
235 UI
.pve_vm_command('resume');
238 pveCmdReload: function() {
239 if (UI
.pveCommandsOpen
=== true) {
240 UI
.togglePVECommandPanel();
245 pveReload: function() {
249 pve_start: function(callback
) {
250 UI
.consoletype
= WebUtil
.getQueryVar('console');
251 UI
.vmid
= WebUtil
.getQueryVar('vmid');
252 UI
.vmname
= WebUtil
.getQueryVar('vmname');
253 UI
.nodename
= WebUtil
.getQueryVar('node');
257 var params
= { websocket
: 1 };
260 var vmcmd_btns
= ['pveStartButton', 'pveShutdownButton', 'pveStopButton', 'pveResetButton', 'pveSuspendButton', 'pveResumeButton'];
261 vmcmd_btns
.forEach(function(btn
) {
263 el
.style
.display
= "none";
264 el
.value
= gettext(el
.value
);
270 if (UI
.consoletype
=== 'kvm') {
271 var baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
272 url
= baseUrl
+ '/vncproxy';
273 wsurl
= baseUrl
+ '/vncwebsocket';
274 vmcmd_btns
.forEach(function(btn
) {
275 $D(btn
).style
.display
= "";
277 title
= "VM " + UI
.vmid
;
279 title
+= " ('" + UI
.vmname
+ "')";
281 } else if (UI
.consoletype
=== 'openvz') {
282 var baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
283 url
= baseUrl
+ '/vncproxy';
284 wsurl
= baseUrl
+ '/vncwebsocket';
285 ['pveStartButton', 'pveShutdownButton', 'pveStopButton'].forEach(function(btn
) {
286 $D(btn
).style
.display
= "";
288 title
= "CT " + UI
.vmid
;
290 title
+= " ('" + UI
.vmname
+ "')";
292 } else if (UI
.consoletype
=== 'shell') {
293 var baseUrl
= '/nodes/' + UI
.nodename
;
294 url
= baseUrl
+ '/vncshell';
295 wsurl
= baseUrl
+ '/vncwebsocket';
296 title
= "node '" + UI
.nodename
+ "'";
297 } else if (UI
.consoletype
=== 'upgrade') {
298 var baseUrl
= '/nodes/' + UI
.nodename
;
299 url
= baseUrl
+ '/vncshell';
300 wsurl
= baseUrl
+ '/vncwebsocket';
302 title
= gettext('System upgrade on node {0}');
303 title
= title
.replace(/\{0\}/, UI
.nodename
);
305 throw "implement me";
308 document
.title
= title
;
310 var start_vnc_viewer = function(param
) {
311 var wsparams
= UI
.urlEncode({
313 vncticket
: param
.ticket
316 UI
.updateSetting('host', window
.location
.hostname
);
317 UI
.updateSetting('port', window
.location
.port
);
318 UI
.updateSetting('password', param
.ticket
);
319 UI
.updateSetting('encrypt', true);
320 UI
.updateSetting('true_color', true);
321 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
322 UI
.updateSetting('shared', true);
323 UI
.updateSetting('view_only', false);
325 UI
.updateSetting('path', 'api2/json' + wsurl
+ "?" + wsparams
);
334 success: function(result
) {
335 start_vnc_viewer(result
.data
);
337 failure: function(msg
) {
338 UI
.pve_show_msg('noVNC_status_error', msg
, 1);
343 lastFBWidth
: undefined,
344 lastFBHeight
: undefined,
345 sizeUpdateTimer
: undefined,
347 updateFBSize: function(rfb
, width
, height
) {
349 UI
.lastFBWidth
= width
+ 1;
350 UI
.lastFBHeight
= height
+ 5;
352 if (UI
.sizeUpdateTimer
!== undefined) {
353 clearInterval(UI
.sizeUpdateTimer
);
355 if (UI
.getSetting('clip')) return;
357 var update_size = function() {
361 if (window
.innerHeight
) {
362 oh
= window
.innerHeight
;
363 ow
= window
.innerWidth
;
364 } else if (document
.documentElement
&&
365 document
.documentElement
.clientHeight
) {
366 oh
= document
.documentElement
.clientHeight
;
367 ow
= document
.documentElement
.clientWidth
;
368 } else if (document
.body
) {
369 oh
= document
.body
.clientHeight
;
370 ow
= document
.body
.clientWidth
;
372 throw "can't get window size";
375 // see base.css/noVNC_screen_pad
376 var toolbar_height
= 36;
378 var offsetw
= UI
.lastFBWidth
- ow
;
379 var offseth
= UI
.lastFBHeight
+ toolbar_height
- oh
;
380 if (offsetw
!== 0 || offseth
!== 0) {
381 //console.log("try resize by " + offsetw + " " + offseth);
382 window
.resizeBy(offsetw
, offseth
);
387 UI
.sizeUpdateTimer
= setInterval(update_size
, 1000);
394 // Open/close PVE connand menu
395 togglePVECommandPanel: function() {
396 // Close the description panel
397 $D('noVNC_description').style
.display
= "none";
398 // Close clipboard panel if open
399 if (UI
.clipboardOpen
=== true) {
400 UI
.toggleClipboardPanel();
402 // Close connection settings if open
403 if (UI
.connSettingsOpen
=== true) {
404 UI
.toggleConnectPanel();
406 // Close popup status panel if open
407 if (UI
.popupStatusOpen
=== true) {
408 UI
.togglePopupStatusPanel();
410 // Close XVP panel if open
411 if (UI
.xvpOpen
=== true) {
414 if (UI
.pveCommandsOpen
) {
415 $D('noVNC_pve_commands').style
.display
= "none";
416 $D('pveCommandsButton').className
= "noVNC_status_button";
417 UI
.pveCommandsOpen
= false;
419 $D('noVNC_pve_commands').style
.display
= "block";
420 $D('pveCommandsButton').className
= "noVNC_status_button_selected";
421 UI
.pveCommandsOpen
= true;
425 // Render default UI and initialize settings menu
426 start: function(callback
) {
427 var html
= '', i
, sheet
, sheets
, llevels
, port
, autoconnect
;
429 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
431 // Stylesheet selection dropdown
432 sheet
= WebUtil
.selectStylesheet();
433 sheets
= WebUtil
.getStylesheets();
434 for (i
= 0; i
< sheets
.length
; i
+= 1) {
435 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
438 // Logging selection dropdown
439 llevels
= ['error', 'warn', 'info', 'debug'];
440 for (i
= 0; i
< llevels
.length
; i
+= 1) {
441 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
444 // Settings with immediate effects
445 UI
.initSetting('logging', 'warn');
446 WebUtil
.init_logging(UI
.getSetting('logging'));
448 UI
.initSetting('stylesheet', 'default');
449 WebUtil
.selectStylesheet(null);
450 // call twice to get around webkit bug
451 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
453 UI
.initSetting('repeaterID', '');
455 UI
.rfb
= RFB({'target': $D('noVNC_canvas'),
456 'onUpdateState': UI
.updateState
,
457 'onXvpInit': UI
.updateXvpVisualState
,
458 'onClipboard': UI
.clipReceive
,
459 //'onDesktopName': UI.updateDocumentTitle,
460 'onFBResize': UI
.updateFBSize
});
463 if (autoconnect
=== 'true' || autoconnect
== '1') {
470 UI
.updateVisualState();
472 // Unfocus clipboard when over the VNC area
473 //$D('VNC_screen').onmousemove = function () {
474 // var keyboard = UI.rfb.get_keyboard();
475 // if ((! keyboard) || (! keyboard.get_focused())) {
476 // $D('VNC_clipboard_text').blur();
480 // Show mouse selector buttons on touch screen devices
481 if (UI
.isTouchDevice
) {
482 // Show mobile buttons
483 $D('noVNC_mobile_buttons').style
.display
= "inline";
485 // Remove the address bar
486 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
487 UI
.forceSetting('clip', true);
488 $D('noVNC_clip').disabled
= true;
490 UI
.initSetting('clip', false);
493 //iOS Safari does not support CSS position:fixed.
494 //This detects iOS devices and enables javascript workaround.
495 if ((navigator
.userAgent
.match(/iPhone/i)) ||
496 (navigator
.userAgent
.match(/iPod/i)) ||
497 (navigator
.userAgent
.match(/iPad/i))) {
503 $D('noVNC_host').focus();
506 Util
.addEvent(window
, 'resize', UI
.setViewClip
);
508 Util
.addEvent(window
, 'beforeunload', function () {
509 if (UI
.rfb_state
=== 'normal') {
510 return "You are currently connected.";
514 // Show description by default when hosted at for kanaka.github.com
515 if (location
.host
=== "kanaka.github.io") {
516 // Open the description dialog
517 $D('noVNC_description').style
.display
= "block";
519 // Show the connect panel on first load unless autoconnecting
520 if (autoconnect
=== UI
.connSettingsOpen
) {
521 UI
.toggleConnectPanel();
525 // Add mouse event click/focus/blur event handlers to the UI
526 UI
.addMouseHandlers();
528 if (typeof callback
=== "function") {
533 addMouseHandlers: function() {
534 // Setup interface handlers that can't be inline
535 $D("noVNC_view_drag_button").onclick
= UI
.setViewDrag
;
536 $D("noVNC_mouse_button0").onclick = function () { UI
.setMouseButton(1); };
537 $D("noVNC_mouse_button1").onclick = function () { UI
.setMouseButton(2); };
538 $D("noVNC_mouse_button2").onclick = function () { UI
.setMouseButton(4); };
539 $D("noVNC_mouse_button4").onclick = function () { UI
.setMouseButton(0); };
540 $D("showKeyboard").onclick
= UI
.showKeyboard
;
542 $D("keyboardinput").oninput
= UI
.keyInput
;
543 $D("keyboardinput").onblur
= UI
.keyInputBlur
;
545 $D("showExtraKeysButton").onclick
= UI
.showExtraKeys
;
546 $D("toggleCtrlButton").onclick
= UI
.toggleCtrl
;
547 $D("toggleAltButton").onclick
= UI
.toggleAlt
;
548 $D("sendTabButton").onclick
= UI
.sendTab
;
549 $D("sendEscButton").onclick
= UI
.sendEsc
;
551 $D("pveStartButton").onclick
= UI
.pveCmdStart
;
552 $D("pveShutdownButton").onclick
= UI
.pveCmdShutdown
;
553 $D("pveStopButton").onclick
= UI
.pveCmdStop
;
554 $D("pveResetButton").onclick
= UI
.pveCmdReset
;
555 $D("pveSuspendButton").onclick
= UI
.pveCmdSuspend
;
556 $D("pveResumeButton").onclick
= UI
.pveCmdResume
;
557 $D("pveReloadButton").onclick
= UI
.pveCmdReload
;
559 $D("sendCtrlAltDelButton").onclick
= UI
.sendCtrlAltDel
;
560 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
561 //$D("xvpRebootButton").onclick = UI.xvpReboot;
562 //$D("xvpResetButton").onclick = UI.xvpReset;
563 $D("noVNC_status").onclick
= UI
.togglePopupStatusPanel
;
564 $D("noVNC_popup_status_panel").onclick
= UI
.togglePopupStatusPanel
;
565 //$D("xvpButton").onclick = UI.toggleXvpPanel;
566 $D("clipboardButton").onclick
= UI
.toggleClipboardPanel
;
567 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
568 $D("pveCommandsButton").onclick
= UI
.togglePVECommandPanel
;
569 //$D("connectButton").onclick = UI.toggleConnectPanel;
570 //$D("disconnectButton").onclick = UI.disconnect;
571 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
573 $D("noVNC_clipboard_text").onfocus
= UI
.displayBlur
;
574 $D("noVNC_clipboard_text").onblur
= UI
.displayFocus
;
575 $D("noVNC_clipboard_text").onchange
= UI
.clipSend
;
576 $D("noVNC_clipboard_clear_button").onclick
= UI
.clipClear
;
578 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
579 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
580 //$D("noVNC_apply").onclick = UI.settingsApply;
582 //$D("noVNC_connect_button").onclick = UI.connect;
585 // Read form control compatible setting from cookie
586 getSetting: function(name
) {
587 var val
, ctrl
= $D('noVNC_' + name
);
588 val
= WebUtil
.readSetting(name
);
589 if (val
!== null && ctrl
.type
=== 'checkbox') {
590 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
599 // Update cookie and form control setting. If value is not set, then
600 // updates from control to current cookie setting.
601 updateSetting: function(name
, value
) {
603 var i
, ctrl
= $D('noVNC_' + name
);
604 // Save the cookie for this session
605 if (typeof value
!== 'undefined') {
606 WebUtil
.writeSetting(name
, value
);
609 // Update the settings control
610 value
= UI
.getSetting(name
);
612 if (ctrl
.type
=== 'checkbox') {
613 ctrl
.checked
= value
;
615 } else if (typeof ctrl
.options
!== 'undefined') {
616 for (i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
617 if (ctrl
.options
[i
].value
=== value
) {
618 ctrl
.selectedIndex
= i
;
623 /*Weird IE9 error leads to 'null' appearring
624 in textboxes instead of ''.*/
625 if (value
=== null) {
632 // Save control setting to cookie
633 saveSetting: function(name
) {
634 var val
, ctrl
= $D('noVNC_' + name
);
635 if (ctrl
.type
=== 'checkbox') {
637 } else if (typeof ctrl
.options
!== 'undefined') {
638 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
642 WebUtil
.writeSetting(name
, val
);
643 //Util.Debug("Setting saved '" + name + "=" + val + "'");
647 // Initial page load read/initialization of settings
648 initSetting: function(name
, defVal
) {
651 // Check Query string followed by cookie
652 val
= WebUtil
.getQueryVar(name
);
654 val
= WebUtil
.readSetting(name
, defVal
);
656 UI
.updateSetting(name
, val
);
657 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
661 // Force a setting to be a certain value
662 forceSetting: function(name
, val
) {
663 UI
.updateSetting(name
, val
);
668 // Show the popup status panel
669 togglePopupStatusPanel: function() {
670 var psp
= $D('noVNC_popup_status_panel');
671 if (UI
.popupStatusOpen
=== true) {
672 psp
.style
.display
= "none";
673 UI
.popupStatusOpen
= false;
675 psp
.innerHTML
= $D('noVNC_status').innerHTML
;
676 psp
.style
.display
= "block";
677 psp
.style
.left
= window
.innerWidth
/2 -
678 parseInt(window
.getComputedStyle(psp
, false).width
)/2 -30 + "px";
679 UI
.popupStatusOpen
= true;
683 // Show the XVP panel
684 toggleXvpPanel: function() {
685 // Close the description panel
686 $D('noVNC_description').style
.display
= "none";
687 if (UI
.pveCommandsOpen
=== true) {
688 UI
.togglePVECommandPanel();
690 // Close settings if open
691 if (UI
.settingsOpen
=== true) {
693 UI
.closeSettingsMenu();
695 // Close connection settings if open
696 if (UI
.connSettingsOpen
=== true) {
697 UI
.toggleConnectPanel();
699 // Close popup status panel if open
700 if (UI
.popupStatusOpen
=== true) {
701 UI
.togglePopupStatusPanel();
703 // Close clipboard panel if open
704 if (UI
.clipboardOpen
=== true) {
705 UI
.toggleClipboardPanel();
708 if (UI
.xvpOpen
=== true) {
709 //$D('noVNC_xvp').style.display = "none";
710 //$D('xvpButton').className = "noVNC_status_button";
713 //$D('noVNC_xvp').style.display = "block";
714 //$D('xvpButton').className = "noVNC_status_button_selected";
719 // Show the clipboard panel
720 toggleClipboardPanel: function() {
721 // Close the description panel
722 $D('noVNC_description').style
.display
= "none";
723 if (UI
.pveCommandsOpen
=== true) {
724 UI
.togglePVECommandPanel();
726 // Close settings if open
727 if (UI
.settingsOpen
=== true) {
729 UI
.closeSettingsMenu();
731 // Close connection settings if open
732 if (UI
.connSettingsOpen
=== true) {
733 UI
.toggleConnectPanel();
735 // Close popup status panel if open
736 if (UI
.popupStatusOpen
=== true) {
737 UI
.togglePopupStatusPanel();
739 // Close XVP panel if open
740 if (UI
.xvpOpen
=== true) {
743 // Toggle Clipboard Panel
744 if (UI
.clipboardOpen
=== true) {
745 $D('noVNC_clipboard').style
.display
= "none";
746 $D('clipboardButton').className
= "noVNC_status_button";
747 UI
.clipboardOpen
= false;
749 $D('noVNC_clipboard').style
.display
= "block";
750 $D('clipboardButton').className
= "noVNC_status_button_selected";
751 UI
.clipboardOpen
= true;
755 // Show the connection settings panel/menu
756 toggleConnectPanel: function() {
757 // Close the description panel
758 $D('noVNC_description').style
.display
= "none";
759 if (UI
.pveCommandsOpen
=== true) {
760 UI
.togglePVECommandPanel();
762 // Close connection settings if open
763 if (UI
.settingsOpen
=== true) {
765 UI
.closeSettingsMenu();
766 //$D('connectButton').className = "noVNC_status_button";
768 // Close clipboard panel if open
769 if (UI
.clipboardOpen
=== true) {
770 UI
.toggleClipboardPanel();
772 // Close popup status panel if open
773 if (UI
.popupStatusOpen
=== true) {
774 UI
.togglePopupStatusPanel();
776 // Close XVP panel if open
777 if (UI
.xvpOpen
=== true) {
781 // Toggle Connection Panel
782 if (UI
.connSettingsOpen
=== true) {
783 $D('noVNC_controls').style
.display
= "none";
784 //$D('connectButton').className = "noVNC_status_button";
785 UI
.connSettingsOpen
= false;
786 UI
.saveSetting('host');
787 UI
.saveSetting('port');
788 //UI.saveSetting('password');
790 $D('noVNC_controls').style
.display
= "block";
791 //$D('connectButton').className = "noVNC_status_button_selected";
792 UI
.connSettingsOpen
= true;
793 $D('noVNC_host').focus();
797 // Toggle the settings menu:
798 // On open, settings are refreshed from saved cookies.
799 // On close, settings are applied
800 toggleSettingsPanel: function() {
801 // Close the description panel
802 $D('noVNC_description').style
.display
= "none";
803 if (UI
.settingsOpen
) {
805 UI
.closeSettingsMenu();
807 UI
.updateSetting('encrypt');
808 UI
.updateSetting('true_color');
809 if (UI
.rfb
.get_display().get_cursor_uri()) {
810 UI
.updateSetting('cursor');
812 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
813 $D('noVNC_cursor').disabled
= true;
815 UI
.updateSetting('clip');
816 UI
.updateSetting('shared');
817 UI
.updateSetting('view_only');
818 UI
.updateSetting('path');
819 UI
.updateSetting('repeaterID');
820 UI
.updateSetting('stylesheet');
821 UI
.updateSetting('logging');
823 UI
.openSettingsMenu();
828 openSettingsMenu: function() {
829 // Close the description panel
830 $D('noVNC_description').style
.display
= "none";
831 if (UI
.pveCommandsOpen
=== true) {
832 UI
.togglePVECommandPanel();
834 // Close clipboard panel if open
835 if (UI
.clipboardOpen
=== true) {
836 UI
.toggleClipboardPanel();
838 // Close connection settings if open
839 if (UI
.connSettingsOpen
=== true) {
840 UI
.toggleConnectPanel();
842 // Close popup status panel if open
843 if (UI
.popupStatusOpen
=== true) {
844 UI
.togglePopupStatusPanel();
846 // Close XVP panel if open
847 if (UI
.xvpOpen
=== true) {
850 $D('noVNC_settings').style
.display
= "block";
851 //$D('settingsButton').className = "noVNC_status_button_selected";
852 UI
.settingsOpen
= true;
855 // Close menu (without applying settings)
856 closeSettingsMenu: function() {
857 $D('noVNC_settings').style
.display
= "none";
858 //$D('settingsButton').className = "noVNC_status_button";
859 UI
.settingsOpen
= false;
862 // Save/apply settings when 'Apply' button is pressed
863 settingsApply: function() {
864 //Util.Debug(">> settingsApply");
865 UI
.saveSetting('encrypt');
866 UI
.saveSetting('true_color');
867 if (UI
.rfb
.get_display().get_cursor_uri()) {
868 UI
.saveSetting('cursor');
870 UI
.saveSetting('clip');
871 UI
.saveSetting('shared');
872 UI
.saveSetting('view_only');
873 UI
.saveSetting('path');
874 UI
.saveSetting('repeaterID');
875 UI
.saveSetting('stylesheet');
876 UI
.saveSetting('logging');
878 // Settings with immediate (non-connected related) effect
879 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
880 WebUtil
.init_logging(UI
.getSetting('logging'));
882 UI
.setViewDrag(UI
.rfb
.get_viewportDrag());
883 //Util.Debug("<< settingsApply");
888 setPassword: function() {
889 UI
.rfb
.sendPassword($D('noVNC_password').value
);
890 //Reset connect button.
891 $D('noVNC_connect_button').value
= "Connect";
892 $D('noVNC_connect_button').onclick
= UI
.Connect
;
893 //Hide connection panel.
894 UI
.toggleConnectPanel();
898 sendCtrlAltDel: function() {
899 UI
.rfb
.sendCtrlAltDel();
902 xvpShutdown: function() {
903 UI
.rfb
.xvpShutdown();
906 xvpReboot: function() {
910 xvpReset: function() {
914 setMouseButton: function(num
) {
915 var b
, blist
= [0, 1,2,4], button
;
917 if (typeof num
=== 'undefined') {
918 // Disable mouse buttons
922 UI
.rfb
.get_mouse().set_touchButton(num
);
925 for (b
= 0; b
< blist
.length
; b
++) {
926 button
= $D('noVNC_mouse_button' + blist
[b
]);
927 if (blist
[b
] === num
) {
928 button
.style
.display
= "";
930 button
.style
.display
= "none";
932 button.style.backgroundColor = "black";
933 button.style.color = "lightgray";
934 button.style.backgroundColor = "";
935 button.style.color = "";
941 updateState: function(rfb
, state
, oldstate
, msg
) {
942 var s
, sb
, c
, d
, cad
, vd
, klass
;
943 UI
.rfb_state
= state
;
947 klass
= "noVNC_status_error";
950 klass
= "noVNC_status_normal";
953 $D('noVNC_logo').style
.display
= "block";
956 klass
= "noVNC_status_normal";
959 UI
.toggleConnectPanel();
961 $D('noVNC_connect_button').value
= "Send Password";
962 $D('noVNC_connect_button').onclick
= UI
.setPassword
;
963 $D('noVNC_password').focus();
965 klass
= "noVNC_status_warn";
968 klass
= "noVNC_status_warn";
972 if (typeof(msg
) !== 'undefined') {
973 $D('noVNC-control-bar').setAttribute("class", klass
);
974 $D('noVNC_status').innerHTML
= msg
;
977 UI
.updateVisualState();
980 // Disable/enable controls depending on connection state
981 updateVisualState: function() {
982 var connected
= UI
.rfb_state
=== 'normal' ? true : false;
984 //Util.Debug(">> updateVisualState");
985 $D('noVNC_encrypt').disabled
= connected
;
986 $D('noVNC_true_color').disabled
= connected
;
987 if (UI
.rfb
&& UI
.rfb
.get_display() &&
988 UI
.rfb
.get_display().get_cursor_uri()) {
989 $D('noVNC_cursor').disabled
= connected
;
991 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
992 $D('noVNC_cursor').disabled
= true;
994 $D('noVNC_shared').disabled
= connected
;
995 $D('noVNC_view_only').disabled
= connected
;
996 $D('noVNC_path').disabled
= connected
;
997 $D('noVNC_repeaterID').disabled
= connected
;
1001 UI
.setMouseButton(1);
1002 $D('clipboardButton').style
.display
= (UI
.consoletype
!== 'kvm') ? "inline" : "none";
1003 $D('showKeyboard').style
.display
= "inline";
1004 $D('noVNC_extra_keys').style
.display
= "";
1005 $D('sendCtrlAltDelButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
1007 UI
.setMouseButton();
1008 $D('clipboardButton').style
.display
= "none";
1009 $D('showKeyboard').style
.display
= "none";
1010 $D('noVNC_extra_keys').style
.display
= "none";
1011 $D('sendCtrlAltDelButton').style
.display
= "none";
1012 UI
.updateXvpVisualState(0);
1015 // State change disables viewport dragging.
1016 // It is enabled (toggled) by direct click on the button
1017 UI
.setViewDrag(false);
1019 switch (UI
.rfb_state
) {
1023 case 'disconnected':
1024 //$D('connectButton').style.display = "";
1025 //$D('disconnectButton').style.display = "none";
1028 //$D('connectButton').style.display = "none";
1029 //$D('disconnectButton').style.display = "";
1033 //Util.Debug("<< updateVisualState");
1036 // Disable/enable XVP button
1037 updateXvpVisualState: function(ver
) {
1040 //$D('xvpButton').style.display = 'inline';
1042 //$D('xvpButton').style.display = 'none';
1043 // Close XVP panel if open
1044 if (UI
.xvpOpen
=== true) {
1045 UI
.toggleXvpPanel();
1051 // Display the desktop name in the document title
1052 updateDocumentTitle: function(rfb
, name
) {
1053 document
.title
= name
+ " - noVNC";
1057 clipReceive: function(rfb
, text
) {
1058 Util
.Debug(">> UI.clipReceive: " + text
.substr(0,40) + "...");
1059 $D('noVNC_clipboard_text').value
= text
;
1060 Util
.Debug("<< UI.clipReceive");
1064 connect: function() {
1065 var host
, port
, password
, path
;
1067 UI
.closeSettingsMenu();
1068 UI
.toggleConnectPanel();
1070 host
= $D('noVNC_host').value
;
1071 port
= $D('noVNC_port').value
;
1072 password
= $D('noVNC_password').value
;
1073 path
= $D('noVNC_path').value
;
1074 if ((!host
) || (!port
)) {
1075 throw("Must set host and port");
1078 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1079 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1080 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1081 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1082 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1083 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1085 UI
.rfb
.connect(host
, port
, password
, path
);
1088 setTimeout(UI
.setBarPosition
, 100);
1089 $D('noVNC_logo').style
.display
= "none";
1092 disconnect: function() {
1093 UI
.closeSettingsMenu();
1094 UI
.rfb
.disconnect();
1096 $D('noVNC_logo').style
.display
= "block";
1097 UI
.connSettingsOpen
= false;
1098 UI
.toggleConnectPanel();
1101 displayBlur: function() {
1102 UI
.rfb
.get_keyboard().set_focused(false);
1103 UI
.rfb
.get_mouse().set_focused(false);
1106 displayFocus: function() {
1107 UI
.rfb
.get_keyboard().set_focused(true);
1108 UI
.rfb
.get_mouse().set_focused(true);
1111 clipClear: function() {
1112 $D('noVNC_clipboard_text').value
= "";
1113 UI
.rfb
.clipboardPasteFrom("");
1116 clipSend: function() {
1117 var text
= $D('noVNC_clipboard_text').value
;
1118 Util
.Debug(">> UI.clipSend: " + text
.substr(0,40) + "...");
1119 UI
.rfb
.clipboardPasteFrom(text
);
1120 Util
.Debug("<< UI.clipSend");
1124 // Enable/disable and configure viewport clipping
1125 setViewClip: function(clip
) {
1126 var display
, cur_clip
, pos
, new_w
, new_h
;
1129 display
= UI
.rfb
.get_display();
1134 cur_clip
= display
.get_viewport();
1136 if (typeof(clip
) !== 'boolean') {
1137 // Use current setting
1138 clip
= UI
.getSetting('clip');
1141 if (clip
&& !cur_clip
) {
1143 UI
.updateSetting('clip', true);
1144 } else if (!clip
&& cur_clip
) {
1145 // Turn clipping off
1146 UI
.updateSetting('clip', false);
1147 display
.set_viewport(false);
1148 $D('noVNC_canvas').style
.position
= 'static';
1149 display
.viewportChange();
1151 if (UI
.getSetting('clip')) {
1152 // If clipping, update clipping settings
1153 $D('noVNC_canvas').style
.position
= 'absolute';
1154 pos
= Util
.getPosition($D('noVNC_canvas'));
1155 new_w
= window
.innerWidth
- pos
.x
;
1156 new_h
= window
.innerHeight
- pos
.y
;
1157 display
.set_viewport(true);
1158 display
.viewportChange(0, 0, new_w
, new_h
);
1162 // Toggle/set/unset the viewport drag/move button
1163 setViewDrag: function(drag
) {
1164 var vmb
= $D('noVNC_view_drag_button');
1165 if (!UI
.rfb
) { return; }
1167 if (UI
.rfb_state
=== 'normal' &&
1168 UI
.rfb
.get_display().get_viewport()) {
1169 vmb
.style
.display
= "inline";
1171 vmb
.style
.display
= "none";
1174 if (typeof(drag
) === "undefined" ||
1175 typeof(drag
) === "object") {
1176 // If not specified, then toggle
1177 drag
= !UI
.rfb
.get_viewportDrag();
1180 vmb
.className
= "noVNC_status_button_selected";
1181 UI
.rfb
.set_viewportDrag(true);
1183 vmb
.className
= "noVNC_status_button";
1184 UI
.rfb
.set_viewportDrag(false);
1188 // On touch devices, show the OS keyboard
1189 showKeyboard: function() {
1191 kbi
= $D('keyboardinput');
1192 skb
= $D('showKeyboard');
1193 l
= kbi
.value
.length
;
1194 if(UI
.keyboardVisible
=== false) {
1196 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1197 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1198 UI
.keyboardVisible
= true;
1199 skb
.className
= "noVNC_status_button_selected";
1200 } else if(UI
.keyboardVisible
=== true) {
1202 skb
.className
= "noVNC_status_button";
1203 UI
.keyboardVisible
= false;
1207 keepKeyboard: function() {
1208 clearTimeout(UI
.hideKeyboardTimeout
);
1209 if(UI
.keyboardVisible
=== true) {
1210 $D('keyboardinput').focus();
1211 $D('showKeyboard').className
= "noVNC_status_button_selected";
1212 } else if(UI
.keyboardVisible
=== false) {
1213 $D('keyboardinput').blur();
1214 $D('showKeyboard').className
= "noVNC_status_button";
1218 keyboardinputReset: function() {
1219 var kbi
= $D('keyboardinput');
1220 kbi
.value
= Array(UI
.defaultKeyboardinputLen
).join("_");
1221 UI
.lastKeyboardinput
= kbi
.value
;
1224 // When normal keyboard events are left uncought, use the input events from
1225 // the keyboardinput element instead and generate the corresponding key events.
1226 // This code is required since some browsers on Android are inconsistent in
1227 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1228 keyInput: function(event
) {
1229 var newValue
, oldValue
, newLen
, oldLen
;
1230 newValue
= event
.target
.value
;
1231 oldValue
= UI
.lastKeyboardinput
;
1234 // Try to check caret position since whitespace at the end
1235 // will not be considered by value.length in some browsers
1236 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1238 // selectionStart is undefined in Google Chrome
1239 newLen
= newValue
.length
;
1241 oldLen
= oldValue
.length
;
1244 var inputs
= newLen
- oldLen
;
1246 backspaces
= -inputs
;
1250 // Compare the old string with the new to account for
1251 // text-corrections or other input that modify existing text
1252 for (var i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1253 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1254 inputs
= newLen
- i
;
1255 backspaces
= oldLen
- i
;
1260 // Send the key events
1261 for (var i
= 0; i
< backspaces
; i
++)
1262 UI
.rfb
.sendKey(XK_BackSpace
);
1263 for (var i
= newLen
- inputs
; i
< newLen
; i
++)
1264 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1266 // Control the text content length in the keyboardinput element
1267 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1268 UI
.keyboardinputReset();
1269 } else if (newLen
< 1) {
1270 // There always have to be some text in the keyboardinput
1271 // element with which backspace can interact.
1272 UI
.keyboardinputReset();
1273 // This sometimes causes the keyboard to disappear for a second
1274 // but it is required for the android keyboard to recognize that
1275 // text has been added to the field
1276 event
.target
.blur();
1277 // This has to be ran outside of the input handler in order to work
1278 setTimeout(function() { UI
.keepKeyboard(); }, 0);
1281 UI
.lastKeyboardinput
= newValue
;
1285 keyInputBlur: function() {
1286 $D('showKeyboard').className
= "noVNC_status_button";
1287 //Weird bug in iOS if you change keyboardVisible
1288 //here it does not actually occur so next time
1289 //you click keyboard icon it doesnt work.
1290 UI
.hideKeyboardTimeout
= setTimeout(function() { UI
.setKeyboard(); },100);
1293 showExtraKeys: function() {
1295 if(UI
.extraKeysVisible
=== false) {
1296 $D('toggleCtrlButton').style
.display
= "inline";
1297 $D('toggleAltButton').style
.display
= "inline";
1298 $D('sendTabButton').style
.display
= "inline";
1299 $D('sendEscButton').style
.display
= "inline";
1300 $D('showExtraKeysButton').className
= "noVNC_status_button_selected";
1301 UI
.extraKeysVisible
= true;
1302 } else if(UI
.extraKeysVisible
=== true) {
1303 $D('toggleCtrlButton').style
.display
= "";
1304 $D('toggleAltButton').style
.display
= "";
1305 $D('sendTabButton').style
.display
= "";
1306 $D('sendEscButton').style
.display
= "";
1307 $D('showExtraKeysButton').className
= "noVNC_status_button";
1308 UI
.extraKeysVisible
= false;
1312 toggleCtrl: function() {
1314 if(UI
.ctrlOn
=== false) {
1315 UI
.rfb
.sendKey(XK_Control_L
, true);
1316 $D('toggleCtrlButton').className
= "noVNC_status_button_selected";
1318 } else if(UI
.ctrlOn
=== true) {
1319 UI
.rfb
.sendKey(XK_Control_L
, false);
1320 $D('toggleCtrlButton').className
= "noVNC_status_button";
1325 toggleAlt: function() {
1327 if(UI
.altOn
=== false) {
1328 UI
.rfb
.sendKey(XK_Alt_L
, true);
1329 $D('toggleAltButton').className
= "noVNC_status_button_selected";
1331 } else if(UI
.altOn
=== true) {
1332 UI
.rfb
.sendKey(XK_Alt_L
, false);
1333 $D('toggleAltButton').className
= "noVNC_status_button";
1338 sendTab: function() {
1340 UI
.rfb
.sendKey(XK_Tab
);
1343 sendEsc: function() {
1345 UI
.rfb
.sendKey(XK_Escape
);
1348 setKeyboard: function() {
1349 UI
.keyboardVisible
= false;
1352 // iOS < Version 5 does not support position fixed. Javascript workaround:
1353 setOnscroll: function() {
1354 window
.onscroll = function() {
1355 UI
.setBarPosition();
1359 setResize: function () {
1360 window
.onResize = function() {
1361 UI
.setBarPosition();
1365 //Helper to add options to dropdown.
1366 addOption: function(selectbox
,text
,value
)
1368 var optn
= document
.createElement("OPTION");
1371 selectbox
.options
.add(optn
);
1374 setBarPosition: function() {
1375 $D('noVNC-control-bar').style
.top
= (window
.pageYOffset
) + 'px';
1376 $D('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1378 var vncwidth
= $D('noVNC_screen').style
.offsetWidth
;
1379 $D('noVNC-control-bar').style
.width
= vncwidth
+ 'px';