]>
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 translate_btn_text = function(btn
, hide
) {
262 if (hide
) el
.style
.display
= "none";
263 el
.value
= gettext(el
.value
);
266 var vmcmd_btns
= ['pveStartButton', 'pveShutdownButton', 'pveStopButton', 'pveResetButton', 'pveSuspendButton', 'pveResumeButton'];
267 vmcmd_btns
.forEach(function(btn
) {
268 translate_btn_text(btn
, true);
271 translate_btn_text('pveReloadButton', false);
275 if (UI
.consoletype
=== 'kvm') {
276 var baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
277 url
= baseUrl
+ '/vncproxy';
278 wsurl
= baseUrl
+ '/vncwebsocket';
279 vmcmd_btns
.forEach(function(btn
) {
280 $D(btn
).style
.display
= "";
282 title
= "VM " + UI
.vmid
;
284 title
+= " ('" + UI
.vmname
+ "')";
286 } else if (UI
.consoletype
=== 'openvz') {
287 var baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
288 url
= baseUrl
+ '/vncproxy';
289 wsurl
= baseUrl
+ '/vncwebsocket';
290 ['pveStartButton', 'pveShutdownButton', 'pveStopButton'].forEach(function(btn
) {
291 $D(btn
).style
.display
= "";
293 title
= "CT " + UI
.vmid
;
295 title
+= " ('" + UI
.vmname
+ "')";
297 } else if (UI
.consoletype
=== 'shell') {
298 var baseUrl
= '/nodes/' + UI
.nodename
;
299 url
= baseUrl
+ '/vncshell';
300 wsurl
= baseUrl
+ '/vncwebsocket';
301 title
= "node '" + UI
.nodename
+ "'";
302 } else if (UI
.consoletype
=== 'upgrade') {
303 var baseUrl
= '/nodes/' + UI
.nodename
;
304 url
= baseUrl
+ '/vncshell';
305 wsurl
= baseUrl
+ '/vncwebsocket';
307 title
= gettext('System upgrade on node {0}');
308 title
= title
.replace(/\{0\}/, UI
.nodename
);
310 throw "implement me";
313 document
.title
= title
;
315 var start_vnc_viewer = function(param
) {
316 var wsparams
= UI
.urlEncode({
318 vncticket
: param
.ticket
321 UI
.updateSetting('host', window
.location
.hostname
);
322 UI
.updateSetting('port', window
.location
.port
);
323 UI
.updateSetting('password', param
.ticket
);
324 UI
.updateSetting('encrypt', true);
325 UI
.updateSetting('true_color', true);
326 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
327 UI
.updateSetting('shared', true);
328 UI
.updateSetting('view_only', false);
330 UI
.updateSetting('path', 'api2/json' + wsurl
+ "?" + wsparams
);
339 success: function(result
) {
340 start_vnc_viewer(result
.data
);
342 failure: function(msg
) {
343 UI
.pve_show_msg('noVNC_status_error', msg
, 1);
348 lastFBWidth
: undefined,
349 lastFBHeight
: undefined,
350 sizeUpdateTimer
: undefined,
352 updateFBSize: function(rfb
, width
, height
) {
354 UI
.lastFBWidth
= width
+ 2;
355 UI
.lastFBHeight
= height
+ 6;
357 if (UI
.sizeUpdateTimer
!== undefined) {
358 clearInterval(UI
.sizeUpdateTimer
);
360 if (UI
.getSetting('clip')) return;
362 var update_size = function() {
366 if (window
.innerHeight
) {
367 oh
= window
.innerHeight
;
368 ow
= window
.innerWidth
;
369 } else if (document
.documentElement
&&
370 document
.documentElement
.clientHeight
) {
371 oh
= document
.documentElement
.clientHeight
;
372 ow
= document
.documentElement
.clientWidth
;
373 } else if (document
.body
) {
374 oh
= document
.body
.clientHeight
;
375 ow
= document
.body
.clientWidth
;
377 throw "can't get window size";
380 // see base.css/noVNC_screen_pad
381 var toolbar_height
= 36;
383 var offsetw
= UI
.lastFBWidth
- ow
;
384 var offseth
= UI
.lastFBHeight
+ toolbar_height
- oh
;
385 if (offsetw
!== 0 || offseth
!== 0) {
386 //console.log("try resize by " + offsetw + " " + offseth);
387 window
.resizeBy(offsetw
, offseth
);
392 UI
.sizeUpdateTimer
= setInterval(update_size
, 1000);
399 // Open/close PVE connand menu
400 togglePVECommandPanel: function() {
401 // Close the description panel
402 $D('noVNC_description').style
.display
= "none";
403 // Close clipboard panel if open
404 if (UI
.clipboardOpen
=== true) {
405 UI
.toggleClipboardPanel();
407 // Close connection settings if open
408 if (UI
.connSettingsOpen
=== true) {
409 UI
.toggleConnectPanel();
411 // Close popup status panel if open
412 if (UI
.popupStatusOpen
=== true) {
413 UI
.togglePopupStatusPanel();
415 // Close XVP panel if open
416 if (UI
.xvpOpen
=== true) {
419 if (UI
.pveCommandsOpen
) {
420 $D('noVNC_pve_commands').style
.display
= "none";
421 $D('pveCommandsButton').className
= "noVNC_status_button";
422 UI
.pveCommandsOpen
= false;
424 $D('noVNC_pve_commands').style
.display
= "block";
425 $D('pveCommandsButton').className
= "noVNC_status_button_selected";
426 UI
.pveCommandsOpen
= true;
430 // Render default UI and initialize settings menu
431 start: function(callback
) {
432 var html
= '', i
, sheet
, sheets
, llevels
, port
, autoconnect
;
434 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
436 // Stylesheet selection dropdown
437 sheet
= WebUtil
.selectStylesheet();
438 sheets
= WebUtil
.getStylesheets();
439 for (i
= 0; i
< sheets
.length
; i
+= 1) {
440 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
443 // Logging selection dropdown
444 llevels
= ['error', 'warn', 'info', 'debug'];
445 for (i
= 0; i
< llevels
.length
; i
+= 1) {
446 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
449 // Settings with immediate effects
450 UI
.initSetting('logging', 'warn');
451 WebUtil
.init_logging(UI
.getSetting('logging'));
453 UI
.initSetting('stylesheet', 'default');
454 WebUtil
.selectStylesheet(null);
455 // call twice to get around webkit bug
456 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
458 UI
.initSetting('repeaterID', '');
460 UI
.rfb
= RFB({'target': $D('noVNC_canvas'),
461 'onUpdateState': UI
.updateState
,
462 'onXvpInit': UI
.updateXvpVisualState
,
463 'onClipboard': UI
.clipReceive
,
464 //'onDesktopName': UI.updateDocumentTitle,
465 'onFBResize': UI
.updateFBSize
});
468 if (autoconnect
=== 'true' || autoconnect
== '1') {
475 UI
.updateVisualState();
477 // Unfocus clipboard when over the VNC area
478 //$D('VNC_screen').onmousemove = function () {
479 // var keyboard = UI.rfb.get_keyboard();
480 // if ((! keyboard) || (! keyboard.get_focused())) {
481 // $D('VNC_clipboard_text').blur();
485 // Show mouse selector buttons on touch screen devices
486 if (UI
.isTouchDevice
) {
487 // Show mobile buttons
488 $D('noVNC_mobile_buttons').style
.display
= "inline";
490 // Remove the address bar
491 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
492 UI
.forceSetting('clip', true);
493 $D('noVNC_clip').disabled
= true;
495 UI
.initSetting('clip', false);
498 //iOS Safari does not support CSS position:fixed.
499 //This detects iOS devices and enables javascript workaround.
500 if ((navigator
.userAgent
.match(/iPhone/i)) ||
501 (navigator
.userAgent
.match(/iPod/i)) ||
502 (navigator
.userAgent
.match(/iPad/i))) {
508 $D('noVNC_host').focus();
511 Util
.addEvent(window
, 'resize', UI
.setViewClip
);
513 Util
.addEvent(window
, 'beforeunload', function () {
514 if (UI
.rfb_state
=== 'normal') {
515 return "You are currently connected.";
519 // Show description by default when hosted at for kanaka.github.com
520 if (location
.host
=== "kanaka.github.io") {
521 // Open the description dialog
522 $D('noVNC_description').style
.display
= "block";
524 // Show the connect panel on first load unless autoconnecting
525 if (autoconnect
=== UI
.connSettingsOpen
) {
526 UI
.toggleConnectPanel();
530 // Add mouse event click/focus/blur event handlers to the UI
531 UI
.addMouseHandlers();
533 if (typeof callback
=== "function") {
538 addMouseHandlers: function() {
539 // Setup interface handlers that can't be inline
540 $D("noVNC_view_drag_button").onclick
= UI
.setViewDrag
;
541 $D("noVNC_mouse_button0").onclick = function () { UI
.setMouseButton(1); };
542 $D("noVNC_mouse_button1").onclick = function () { UI
.setMouseButton(2); };
543 $D("noVNC_mouse_button2").onclick = function () { UI
.setMouseButton(4); };
544 $D("noVNC_mouse_button4").onclick = function () { UI
.setMouseButton(0); };
545 $D("showKeyboard").onclick
= UI
.showKeyboard
;
547 $D("keyboardinput").oninput
= UI
.keyInput
;
548 $D("keyboardinput").onblur
= UI
.keyInputBlur
;
550 $D("showExtraKeysButton").onclick
= UI
.showExtraKeys
;
551 $D("toggleCtrlButton").onclick
= UI
.toggleCtrl
;
552 $D("toggleAltButton").onclick
= UI
.toggleAlt
;
553 $D("sendTabButton").onclick
= UI
.sendTab
;
554 $D("sendEscButton").onclick
= UI
.sendEsc
;
556 $D("pveStartButton").onclick
= UI
.pveCmdStart
;
557 $D("pveShutdownButton").onclick
= UI
.pveCmdShutdown
;
558 $D("pveStopButton").onclick
= UI
.pveCmdStop
;
559 $D("pveResetButton").onclick
= UI
.pveCmdReset
;
560 $D("pveSuspendButton").onclick
= UI
.pveCmdSuspend
;
561 $D("pveResumeButton").onclick
= UI
.pveCmdResume
;
562 $D("pveReloadButton").onclick
= UI
.pveCmdReload
;
564 $D("sendCtrlAltDelButton").onclick
= UI
.sendCtrlAltDel
;
565 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
566 //$D("xvpRebootButton").onclick = UI.xvpReboot;
567 //$D("xvpResetButton").onclick = UI.xvpReset;
568 $D("noVNC_status").onclick
= UI
.togglePopupStatusPanel
;
569 $D("noVNC_popup_status_panel").onclick
= UI
.togglePopupStatusPanel
;
570 //$D("xvpButton").onclick = UI.toggleXvpPanel;
571 $D("clipboardButton").onclick
= UI
.toggleClipboardPanel
;
572 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
573 $D("pveCommandsButton").onclick
= UI
.togglePVECommandPanel
;
574 //$D("connectButton").onclick = UI.toggleConnectPanel;
575 //$D("disconnectButton").onclick = UI.disconnect;
576 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
578 $D("noVNC_clipboard_text").onfocus
= UI
.displayBlur
;
579 $D("noVNC_clipboard_text").onblur
= UI
.displayFocus
;
580 $D("noVNC_clipboard_text").onchange
= UI
.clipSend
;
581 $D("noVNC_clipboard_clear_button").onclick
= UI
.clipClear
;
583 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
584 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
585 //$D("noVNC_apply").onclick = UI.settingsApply;
587 //$D("noVNC_connect_button").onclick = UI.connect;
590 // Read form control compatible setting from cookie
591 getSetting: function(name
) {
592 var val
, ctrl
= $D('noVNC_' + name
);
593 val
= WebUtil
.readSetting(name
);
594 if (val
!== null && ctrl
.type
=== 'checkbox') {
595 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
604 // Update cookie and form control setting. If value is not set, then
605 // updates from control to current cookie setting.
606 updateSetting: function(name
, value
) {
608 var i
, ctrl
= $D('noVNC_' + name
);
609 // Save the cookie for this session
610 if (typeof value
!== 'undefined') {
611 WebUtil
.writeSetting(name
, value
);
614 // Update the settings control
615 value
= UI
.getSetting(name
);
617 if (ctrl
.type
=== 'checkbox') {
618 ctrl
.checked
= value
;
620 } else if (typeof ctrl
.options
!== 'undefined') {
621 for (i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
622 if (ctrl
.options
[i
].value
=== value
) {
623 ctrl
.selectedIndex
= i
;
628 /*Weird IE9 error leads to 'null' appearring
629 in textboxes instead of ''.*/
630 if (value
=== null) {
637 // Save control setting to cookie
638 saveSetting: function(name
) {
639 var val
, ctrl
= $D('noVNC_' + name
);
640 if (ctrl
.type
=== 'checkbox') {
642 } else if (typeof ctrl
.options
!== 'undefined') {
643 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
647 WebUtil
.writeSetting(name
, val
);
648 //Util.Debug("Setting saved '" + name + "=" + val + "'");
652 // Initial page load read/initialization of settings
653 initSetting: function(name
, defVal
) {
656 // Check Query string followed by cookie
657 val
= WebUtil
.getQueryVar(name
);
659 val
= WebUtil
.readSetting(name
, defVal
);
661 UI
.updateSetting(name
, val
);
662 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
666 // Force a setting to be a certain value
667 forceSetting: function(name
, val
) {
668 UI
.updateSetting(name
, val
);
673 // Show the popup status panel
674 togglePopupStatusPanel: function() {
675 var psp
= $D('noVNC_popup_status_panel');
676 if (UI
.popupStatusOpen
=== true) {
677 psp
.style
.display
= "none";
678 UI
.popupStatusOpen
= false;
680 psp
.innerHTML
= $D('noVNC_status').innerHTML
;
681 psp
.style
.display
= "block";
682 psp
.style
.left
= window
.innerWidth
/2 -
683 parseInt(window
.getComputedStyle(psp
, false).width
)/2 -30 + "px";
684 UI
.popupStatusOpen
= true;
688 // Show the XVP panel
689 toggleXvpPanel: function() {
690 // Close the description panel
691 $D('noVNC_description').style
.display
= "none";
692 if (UI
.pveCommandsOpen
=== true) {
693 UI
.togglePVECommandPanel();
695 // Close settings if open
696 if (UI
.settingsOpen
=== true) {
698 UI
.closeSettingsMenu();
700 // Close connection settings if open
701 if (UI
.connSettingsOpen
=== true) {
702 UI
.toggleConnectPanel();
704 // Close popup status panel if open
705 if (UI
.popupStatusOpen
=== true) {
706 UI
.togglePopupStatusPanel();
708 // Close clipboard panel if open
709 if (UI
.clipboardOpen
=== true) {
710 UI
.toggleClipboardPanel();
713 if (UI
.xvpOpen
=== true) {
714 //$D('noVNC_xvp').style.display = "none";
715 //$D('xvpButton').className = "noVNC_status_button";
718 //$D('noVNC_xvp').style.display = "block";
719 //$D('xvpButton').className = "noVNC_status_button_selected";
724 // Show the clipboard panel
725 toggleClipboardPanel: function() {
726 // Close the description panel
727 $D('noVNC_description').style
.display
= "none";
728 if (UI
.pveCommandsOpen
=== true) {
729 UI
.togglePVECommandPanel();
731 // Close settings if open
732 if (UI
.settingsOpen
=== true) {
734 UI
.closeSettingsMenu();
736 // Close connection settings if open
737 if (UI
.connSettingsOpen
=== true) {
738 UI
.toggleConnectPanel();
740 // Close popup status panel if open
741 if (UI
.popupStatusOpen
=== true) {
742 UI
.togglePopupStatusPanel();
744 // Close XVP panel if open
745 if (UI
.xvpOpen
=== true) {
748 // Toggle Clipboard Panel
749 if (UI
.clipboardOpen
=== true) {
750 $D('noVNC_clipboard').style
.display
= "none";
751 $D('clipboardButton').className
= "noVNC_status_button";
752 UI
.clipboardOpen
= false;
754 $D('noVNC_clipboard').style
.display
= "block";
755 $D('clipboardButton').className
= "noVNC_status_button_selected";
756 UI
.clipboardOpen
= true;
760 // Show the connection settings panel/menu
761 toggleConnectPanel: function() {
762 // Close the description panel
763 $D('noVNC_description').style
.display
= "none";
764 if (UI
.pveCommandsOpen
=== true) {
765 UI
.togglePVECommandPanel();
767 // Close connection settings if open
768 if (UI
.settingsOpen
=== true) {
770 UI
.closeSettingsMenu();
771 //$D('connectButton').className = "noVNC_status_button";
773 // Close clipboard panel if open
774 if (UI
.clipboardOpen
=== true) {
775 UI
.toggleClipboardPanel();
777 // Close popup status panel if open
778 if (UI
.popupStatusOpen
=== true) {
779 UI
.togglePopupStatusPanel();
781 // Close XVP panel if open
782 if (UI
.xvpOpen
=== true) {
786 // Toggle Connection Panel
787 if (UI
.connSettingsOpen
=== true) {
788 $D('noVNC_controls').style
.display
= "none";
789 //$D('connectButton').className = "noVNC_status_button";
790 UI
.connSettingsOpen
= false;
791 UI
.saveSetting('host');
792 UI
.saveSetting('port');
793 //UI.saveSetting('password');
795 $D('noVNC_controls').style
.display
= "block";
796 //$D('connectButton').className = "noVNC_status_button_selected";
797 UI
.connSettingsOpen
= true;
798 $D('noVNC_host').focus();
802 // Toggle the settings menu:
803 // On open, settings are refreshed from saved cookies.
804 // On close, settings are applied
805 toggleSettingsPanel: function() {
806 // Close the description panel
807 $D('noVNC_description').style
.display
= "none";
808 if (UI
.settingsOpen
) {
810 UI
.closeSettingsMenu();
812 UI
.updateSetting('encrypt');
813 UI
.updateSetting('true_color');
814 if (UI
.rfb
.get_display().get_cursor_uri()) {
815 UI
.updateSetting('cursor');
817 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
818 $D('noVNC_cursor').disabled
= true;
820 UI
.updateSetting('clip');
821 UI
.updateSetting('shared');
822 UI
.updateSetting('view_only');
823 UI
.updateSetting('path');
824 UI
.updateSetting('repeaterID');
825 UI
.updateSetting('stylesheet');
826 UI
.updateSetting('logging');
828 UI
.openSettingsMenu();
833 openSettingsMenu: function() {
834 // Close the description panel
835 $D('noVNC_description').style
.display
= "none";
836 if (UI
.pveCommandsOpen
=== true) {
837 UI
.togglePVECommandPanel();
839 // Close clipboard panel if open
840 if (UI
.clipboardOpen
=== true) {
841 UI
.toggleClipboardPanel();
843 // Close connection settings if open
844 if (UI
.connSettingsOpen
=== true) {
845 UI
.toggleConnectPanel();
847 // Close popup status panel if open
848 if (UI
.popupStatusOpen
=== true) {
849 UI
.togglePopupStatusPanel();
851 // Close XVP panel if open
852 if (UI
.xvpOpen
=== true) {
855 $D('noVNC_settings').style
.display
= "block";
856 //$D('settingsButton').className = "noVNC_status_button_selected";
857 UI
.settingsOpen
= true;
860 // Close menu (without applying settings)
861 closeSettingsMenu: function() {
862 $D('noVNC_settings').style
.display
= "none";
863 //$D('settingsButton').className = "noVNC_status_button";
864 UI
.settingsOpen
= false;
867 // Save/apply settings when 'Apply' button is pressed
868 settingsApply: function() {
869 //Util.Debug(">> settingsApply");
870 UI
.saveSetting('encrypt');
871 UI
.saveSetting('true_color');
872 if (UI
.rfb
.get_display().get_cursor_uri()) {
873 UI
.saveSetting('cursor');
875 UI
.saveSetting('clip');
876 UI
.saveSetting('shared');
877 UI
.saveSetting('view_only');
878 UI
.saveSetting('path');
879 UI
.saveSetting('repeaterID');
880 UI
.saveSetting('stylesheet');
881 UI
.saveSetting('logging');
883 // Settings with immediate (non-connected related) effect
884 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
885 WebUtil
.init_logging(UI
.getSetting('logging'));
887 UI
.setViewDrag(UI
.rfb
.get_viewportDrag());
888 //Util.Debug("<< settingsApply");
893 setPassword: function() {
894 UI
.rfb
.sendPassword($D('noVNC_password').value
);
895 //Reset connect button.
896 $D('noVNC_connect_button').value
= "Connect";
897 $D('noVNC_connect_button').onclick
= UI
.Connect
;
898 //Hide connection panel.
899 UI
.toggleConnectPanel();
903 sendCtrlAltDel: function() {
904 UI
.rfb
.sendCtrlAltDel();
907 xvpShutdown: function() {
908 UI
.rfb
.xvpShutdown();
911 xvpReboot: function() {
915 xvpReset: function() {
919 setMouseButton: function(num
) {
920 var b
, blist
= [0, 1,2,4], button
;
922 if (typeof num
=== 'undefined') {
923 // Disable mouse buttons
927 UI
.rfb
.get_mouse().set_touchButton(num
);
930 for (b
= 0; b
< blist
.length
; b
++) {
931 button
= $D('noVNC_mouse_button' + blist
[b
]);
932 if (blist
[b
] === num
) {
933 button
.style
.display
= "";
935 button
.style
.display
= "none";
937 button.style.backgroundColor = "black";
938 button.style.color = "lightgray";
939 button.style.backgroundColor = "";
940 button.style.color = "";
946 updateState: function(rfb
, state
, oldstate
, msg
) {
947 var s
, sb
, c
, d
, cad
, vd
, klass
;
948 UI
.rfb_state
= state
;
952 klass
= "noVNC_status_error";
955 klass
= "noVNC_status_normal";
958 $D('noVNC_logo').style
.display
= "block";
961 klass
= "noVNC_status_normal";
964 UI
.toggleConnectPanel();
966 $D('noVNC_connect_button').value
= "Send Password";
967 $D('noVNC_connect_button').onclick
= UI
.setPassword
;
968 $D('noVNC_password').focus();
970 klass
= "noVNC_status_warn";
973 klass
= "noVNC_status_warn";
977 if (typeof(msg
) !== 'undefined') {
978 $D('noVNC-control-bar').setAttribute("class", klass
);
979 $D('noVNC_status').innerHTML
= msg
;
982 UI
.updateVisualState();
985 // Disable/enable controls depending on connection state
986 updateVisualState: function() {
987 var connected
= UI
.rfb_state
=== 'normal' ? true : false;
989 //Util.Debug(">> updateVisualState");
990 $D('noVNC_encrypt').disabled
= connected
;
991 $D('noVNC_true_color').disabled
= connected
;
992 if (UI
.rfb
&& UI
.rfb
.get_display() &&
993 UI
.rfb
.get_display().get_cursor_uri()) {
994 $D('noVNC_cursor').disabled
= connected
;
996 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
997 $D('noVNC_cursor').disabled
= true;
999 $D('noVNC_shared').disabled
= connected
;
1000 $D('noVNC_view_only').disabled
= connected
;
1001 $D('noVNC_path').disabled
= connected
;
1002 $D('noVNC_repeaterID').disabled
= connected
;
1006 UI
.setMouseButton(1);
1007 $D('clipboardButton').style
.display
= (UI
.consoletype
!== 'kvm') ? "inline" : "none";
1008 $D('showKeyboard').style
.display
= "inline";
1009 $D('noVNC_extra_keys').style
.display
= "";
1010 $D('sendCtrlAltDelButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
1012 UI
.setMouseButton();
1013 $D('clipboardButton').style
.display
= "none";
1014 $D('showKeyboard').style
.display
= "none";
1015 $D('noVNC_extra_keys').style
.display
= "none";
1016 $D('sendCtrlAltDelButton').style
.display
= "none";
1017 UI
.updateXvpVisualState(0);
1020 // State change disables viewport dragging.
1021 // It is enabled (toggled) by direct click on the button
1022 UI
.setViewDrag(false);
1024 switch (UI
.rfb_state
) {
1028 case 'disconnected':
1029 //$D('connectButton').style.display = "";
1030 //$D('disconnectButton').style.display = "none";
1033 //$D('connectButton').style.display = "none";
1034 //$D('disconnectButton').style.display = "";
1038 //Util.Debug("<< updateVisualState");
1041 // Disable/enable XVP button
1042 updateXvpVisualState: function(ver
) {
1045 //$D('xvpButton').style.display = 'inline';
1047 //$D('xvpButton').style.display = 'none';
1048 // Close XVP panel if open
1049 if (UI
.xvpOpen
=== true) {
1050 UI
.toggleXvpPanel();
1056 // Display the desktop name in the document title
1057 updateDocumentTitle: function(rfb
, name
) {
1058 document
.title
= name
+ " - noVNC";
1062 clipReceive: function(rfb
, text
) {
1063 Util
.Debug(">> UI.clipReceive: " + text
.substr(0,40) + "...");
1064 $D('noVNC_clipboard_text').value
= text
;
1065 Util
.Debug("<< UI.clipReceive");
1069 connect: function() {
1070 var host
, port
, password
, path
;
1072 UI
.closeSettingsMenu();
1073 UI
.toggleConnectPanel();
1075 host
= $D('noVNC_host').value
;
1076 port
= $D('noVNC_port').value
;
1077 password
= $D('noVNC_password').value
;
1078 path
= $D('noVNC_path').value
;
1079 if ((!host
) || (!port
)) {
1080 throw("Must set host and port");
1083 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1084 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1085 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1086 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1087 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1088 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1090 UI
.rfb
.connect(host
, port
, password
, path
);
1093 setTimeout(UI
.setBarPosition
, 100);
1094 $D('noVNC_logo').style
.display
= "none";
1097 disconnect: function() {
1098 UI
.closeSettingsMenu();
1099 UI
.rfb
.disconnect();
1101 $D('noVNC_logo').style
.display
= "block";
1102 UI
.connSettingsOpen
= false;
1103 UI
.toggleConnectPanel();
1106 displayBlur: function() {
1107 UI
.rfb
.get_keyboard().set_focused(false);
1108 UI
.rfb
.get_mouse().set_focused(false);
1111 displayFocus: function() {
1112 UI
.rfb
.get_keyboard().set_focused(true);
1113 UI
.rfb
.get_mouse().set_focused(true);
1116 clipClear: function() {
1117 $D('noVNC_clipboard_text').value
= "";
1118 UI
.rfb
.clipboardPasteFrom("");
1121 clipSend: function() {
1122 var text
= $D('noVNC_clipboard_text').value
;
1123 Util
.Debug(">> UI.clipSend: " + text
.substr(0,40) + "...");
1124 UI
.rfb
.clipboardPasteFrom(text
);
1125 Util
.Debug("<< UI.clipSend");
1129 // Enable/disable and configure viewport clipping
1130 setViewClip: function(clip
) {
1131 var display
, cur_clip
, pos
, new_w
, new_h
;
1134 display
= UI
.rfb
.get_display();
1139 cur_clip
= display
.get_viewport();
1141 if (typeof(clip
) !== 'boolean') {
1142 // Use current setting
1143 clip
= UI
.getSetting('clip');
1146 if (clip
&& !cur_clip
) {
1148 UI
.updateSetting('clip', true);
1149 } else if (!clip
&& cur_clip
) {
1150 // Turn clipping off
1151 UI
.updateSetting('clip', false);
1152 display
.set_viewport(false);
1153 $D('noVNC_canvas').style
.position
= 'static';
1154 display
.viewportChange();
1156 if (UI
.getSetting('clip')) {
1157 // If clipping, update clipping settings
1158 $D('noVNC_canvas').style
.position
= 'absolute';
1159 pos
= Util
.getPosition($D('noVNC_canvas'));
1160 new_w
= window
.innerWidth
- pos
.x
;
1161 new_h
= window
.innerHeight
- pos
.y
;
1162 display
.set_viewport(true);
1163 display
.viewportChange(0, 0, new_w
, new_h
);
1167 // Toggle/set/unset the viewport drag/move button
1168 setViewDrag: function(drag
) {
1169 var vmb
= $D('noVNC_view_drag_button');
1170 if (!UI
.rfb
) { return; }
1172 if (UI
.rfb_state
=== 'normal' &&
1173 UI
.rfb
.get_display().get_viewport()) {
1174 vmb
.style
.display
= "inline";
1176 vmb
.style
.display
= "none";
1179 if (typeof(drag
) === "undefined" ||
1180 typeof(drag
) === "object") {
1181 // If not specified, then toggle
1182 drag
= !UI
.rfb
.get_viewportDrag();
1185 vmb
.className
= "noVNC_status_button_selected";
1186 UI
.rfb
.set_viewportDrag(true);
1188 vmb
.className
= "noVNC_status_button";
1189 UI
.rfb
.set_viewportDrag(false);
1193 // On touch devices, show the OS keyboard
1194 showKeyboard: function() {
1196 kbi
= $D('keyboardinput');
1197 skb
= $D('showKeyboard');
1198 l
= kbi
.value
.length
;
1199 if(UI
.keyboardVisible
=== false) {
1201 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1202 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1203 UI
.keyboardVisible
= true;
1204 skb
.className
= "noVNC_status_button_selected";
1205 } else if(UI
.keyboardVisible
=== true) {
1207 skb
.className
= "noVNC_status_button";
1208 UI
.keyboardVisible
= false;
1212 keepKeyboard: function() {
1213 clearTimeout(UI
.hideKeyboardTimeout
);
1214 if(UI
.keyboardVisible
=== true) {
1215 $D('keyboardinput').focus();
1216 $D('showKeyboard').className
= "noVNC_status_button_selected";
1217 } else if(UI
.keyboardVisible
=== false) {
1218 $D('keyboardinput').blur();
1219 $D('showKeyboard').className
= "noVNC_status_button";
1223 keyboardinputReset: function() {
1224 var kbi
= $D('keyboardinput');
1225 kbi
.value
= Array(UI
.defaultKeyboardinputLen
).join("_");
1226 UI
.lastKeyboardinput
= kbi
.value
;
1229 // When normal keyboard events are left uncought, use the input events from
1230 // the keyboardinput element instead and generate the corresponding key events.
1231 // This code is required since some browsers on Android are inconsistent in
1232 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1233 keyInput: function(event
) {
1234 var newValue
, oldValue
, newLen
, oldLen
;
1235 newValue
= event
.target
.value
;
1236 oldValue
= UI
.lastKeyboardinput
;
1239 // Try to check caret position since whitespace at the end
1240 // will not be considered by value.length in some browsers
1241 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1243 // selectionStart is undefined in Google Chrome
1244 newLen
= newValue
.length
;
1246 oldLen
= oldValue
.length
;
1249 var inputs
= newLen
- oldLen
;
1251 backspaces
= -inputs
;
1255 // Compare the old string with the new to account for
1256 // text-corrections or other input that modify existing text
1257 for (var i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1258 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1259 inputs
= newLen
- i
;
1260 backspaces
= oldLen
- i
;
1265 // Send the key events
1266 for (var i
= 0; i
< backspaces
; i
++)
1267 UI
.rfb
.sendKey(XK_BackSpace
);
1268 for (var i
= newLen
- inputs
; i
< newLen
; i
++)
1269 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1271 // Control the text content length in the keyboardinput element
1272 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1273 UI
.keyboardinputReset();
1274 } else if (newLen
< 1) {
1275 // There always have to be some text in the keyboardinput
1276 // element with which backspace can interact.
1277 UI
.keyboardinputReset();
1278 // This sometimes causes the keyboard to disappear for a second
1279 // but it is required for the android keyboard to recognize that
1280 // text has been added to the field
1281 event
.target
.blur();
1282 // This has to be ran outside of the input handler in order to work
1283 setTimeout(function() { UI
.keepKeyboard(); }, 0);
1286 UI
.lastKeyboardinput
= newValue
;
1290 keyInputBlur: function() {
1291 $D('showKeyboard').className
= "noVNC_status_button";
1292 //Weird bug in iOS if you change keyboardVisible
1293 //here it does not actually occur so next time
1294 //you click keyboard icon it doesnt work.
1295 UI
.hideKeyboardTimeout
= setTimeout(function() { UI
.setKeyboard(); },100);
1298 showExtraKeys: function() {
1300 if(UI
.extraKeysVisible
=== false) {
1301 $D('toggleCtrlButton').style
.display
= "inline";
1302 $D('toggleAltButton').style
.display
= "inline";
1303 $D('sendTabButton').style
.display
= "inline";
1304 $D('sendEscButton').style
.display
= "inline";
1305 $D('showExtraKeysButton').className
= "noVNC_status_button_selected";
1306 UI
.extraKeysVisible
= true;
1307 } else if(UI
.extraKeysVisible
=== true) {
1308 $D('toggleCtrlButton').style
.display
= "";
1309 $D('toggleAltButton').style
.display
= "";
1310 $D('sendTabButton').style
.display
= "";
1311 $D('sendEscButton').style
.display
= "";
1312 $D('showExtraKeysButton').className
= "noVNC_status_button";
1313 UI
.extraKeysVisible
= false;
1317 toggleCtrl: function() {
1319 if(UI
.ctrlOn
=== false) {
1320 UI
.rfb
.sendKey(XK_Control_L
, true);
1321 $D('toggleCtrlButton').className
= "noVNC_status_button_selected";
1323 } else if(UI
.ctrlOn
=== true) {
1324 UI
.rfb
.sendKey(XK_Control_L
, false);
1325 $D('toggleCtrlButton').className
= "noVNC_status_button";
1330 toggleAlt: function() {
1332 if(UI
.altOn
=== false) {
1333 UI
.rfb
.sendKey(XK_Alt_L
, true);
1334 $D('toggleAltButton').className
= "noVNC_status_button_selected";
1336 } else if(UI
.altOn
=== true) {
1337 UI
.rfb
.sendKey(XK_Alt_L
, false);
1338 $D('toggleAltButton').className
= "noVNC_status_button";
1343 sendTab: function() {
1345 UI
.rfb
.sendKey(XK_Tab
);
1348 sendEsc: function() {
1350 UI
.rfb
.sendKey(XK_Escape
);
1353 setKeyboard: function() {
1354 UI
.keyboardVisible
= false;
1357 // iOS < Version 5 does not support position fixed. Javascript workaround:
1358 setOnscroll: function() {
1359 window
.onscroll = function() {
1360 UI
.setBarPosition();
1364 setResize: function () {
1365 window
.onResize = function() {
1366 UI
.setBarPosition();
1370 //Helper to add options to dropdown.
1371 addOption: function(selectbox
,text
,value
)
1373 var optn
= document
.createElement("OPTION");
1376 selectbox
.options
.add(optn
);
1379 setBarPosition: function() {
1380 $D('noVNC-control-bar').style
.top
= (window
.pageYOffset
) + 'px';
1381 $D('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1383 var vncwidth
= $D('noVNC_screen').style
.offsetWidth
;
1384 $D('noVNC-control-bar').style
.width
= vncwidth
+ 'px';