]>
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 * This is a modified version of the original 'ui.js' file, used
8 * for the proxmox console.
12 /*jslint white: false, browser: true */
13 /*global window, $D, Util, WebUtil, RFB, Display */
15 // Load supporting scripts
16 window
.onscriptsload = function () { UI
.load(); };
17 window
.onload = function () { UI
.keyboardinputReset(); };
18 Util
.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
19 "keysymdef.js", "keyboard.js", "input.js", "display.js",
20 "jsunzip.js", "rfb.js", "keysym.js"]);
25 pveAllowMigratedVMTest
: false, // set to true after a succesful conection
26 pveCommandsOpen
: false,
28 connSettingsOpen
: false,
29 popupStatusOpen
: false,
31 sendKeysVisible
: false,
32 keyboardVisible
: false,
33 hideKeyboardTimeout
: null,
34 lastKeyboardinput
: null,
35 defaultKeyboardinputLen
: 100,
36 extraKeysVisible
: false,
41 consoletype
: undefined,
46 // Setup rfb object, load settings from browser storage, then call
47 // UI.init to setup the UI/menus
48 load: function (callback
) {
49 WebUtil
.initSettings(UI
.pve_start
, callback
);
52 // Proxmox VE related code
54 urlEncode: function(object
) {
55 var i
,value
, params
= [];
58 if (object
.hasOwnProperty(i
)) {
60 if (value
=== undefined) value
= '';
61 params
.push(encodeURIComponent(i
) + '=' + encodeURIComponent(String(value
)));
65 return params
.join('&');
68 API2Request: function(reqOpts
) {
70 reqOpts
.method
= reqOpts
.method
|| 'GET';
72 var xhr
= new XMLHttpRequest();
74 xhr
.onload = function() {
75 var scope
= reqOpts
.scope
|| this;
79 if (xhr
.readyState
=== 4) {
80 var ctype
= xhr
.getResponseHeader('Content-Type');
81 if (xhr
.status
=== 200) {
82 if (ctype
.match(/application\/json;/)) {
83 result
= JSON
.parse(xhr
.responseText
);
85 errmsg
= 'got unexpected content type ' + ctype
;
88 errmsg
= 'Error ' + xhr
.status
+ ': ' + xhr
.statusText
;
91 errmsg
= 'Connection error - server offline?';
94 if (errmsg
!== undefined) {
95 if (reqOpts
.failure
) {
96 reqOpts
.failure
.call(scope
, errmsg
);
99 if (reqOpts
.success
) {
100 reqOpts
.success
.call(scope
, result
);
103 if (reqOpts
.callback
) {
104 reqOpts
.callback
.call(scope
, errmsg
=== undefined);
108 var data
= UI
.urlEncode(reqOpts
.params
|| {});
110 if (reqOpts
.method
=== 'GET') {
111 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
+ '?' + data
);
113 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
);
115 xhr
.setRequestHeader('Cache-Control', 'no-cache');
116 if (reqOpts
.method
=== 'POST' || reqOpts
.method
=== 'PUT') {
117 xhr
.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
118 xhr
.setRequestHeader('CSRFPreventionToken', PVE
.CSRFPreventionToken
);
120 } else if (reqOpts
.method
=== 'GET') {
123 throw "unknown method";
129 // show msg for 5 seconds
130 pve_show_msg: function(klass
, msg
, permanant
) {
131 var oldklass
= $D('noVNC-control-bar').getAttribute("class");
132 $D('noVNC-control-bar').setAttribute("class", klass
);
133 var oldmsg
= $D('noVNC_status').innerHTML
;
134 $D('noVNC_status').innerHTML
= msg
;
135 if (typeof permanent
!== 'undefined' && permanent
) return;
137 setTimeout(function() {
138 var curmsg
= $D('noVNC_status').innerHTML
;
139 if (curmsg
=== msg
) {
140 $D('noVNC_status').innerHTML
= oldmsg
;
142 var curklass
= $D('noVNC-control-bar').getAttribute("class");
143 if (curklass
=== klass
) {
144 $D('noVNC-control-bar').setAttribute("class", oldklass
);
149 pve_detect_migrated_vm: function() {
150 if (!(UI
.consoletype
=== 'kvm' || UI
.consoletype
=== 'lxc')) {
154 // try to detect migrated VM
156 url
: '/cluster/resources',
158 success: function(result
) {
159 var list
= result
.data
;
160 list
.every(function(item
) {
161 if ((item
.type
=== 'qemu' || item
.type
=== 'lxc') &&
162 (item
.vmid
== UI
.vmid
)) {
163 var url
= "?" + UI
.urlEncode({
164 console
: UI
.consoletype
,
171 return false; // break
179 pve_vm_command: function(cmd
, params
, reload
) {
182 if (UI
.consoletype
=== 'kvm') {
183 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
184 } else if (UI
.consoletype
=== 'lxc') {
185 baseUrl
= '/nodes/' + UI
.nodename
+ '/lxc/' + UI
.vmid
;
187 throw "unknown VM type";
192 url
: baseUrl
+ "/status/" + cmd
,
194 failure: function(msg
) {
195 UI
.pve_show_msg('noVNC_status_warn', msg
);
197 success: function() {
198 UI
.pve_show_msg('noVNC_status_normall', "VM command '" + cmd
+"' successful");
200 setTimeout(function() {
208 pveCmdStart: function() {
209 if (UI
.pveCommandsOpen
=== true) {
210 UI
.togglePVECommandPanel();
212 UI
.pve_vm_command('start', {}, true);
215 pveCmdShutdown: function() {
216 if (UI
.pveCommandsOpen
=== true) {
217 UI
.togglePVECommandPanel();
219 var msg
= gettext("Do you really want to shutdown VM {0}?");
220 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
222 if (confirm(msg
) === true) {
223 UI
.pve_vm_command('shutdown');
227 pveCmdStop: function() {
228 if (UI
.pveCommandsOpen
=== true) {
229 UI
.togglePVECommandPanel();
232 var msg
= gettext("Do you really want to stop VM {0}?");
233 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
235 if (confirm(msg
) === true) {
236 UI
.pve_vm_command('stop');
240 pveCmdReset: function() {
241 if (UI
.pveCommandsOpen
=== true) {
242 UI
.togglePVECommandPanel();
244 var msg
= gettext("Do you really want to reset VM {0}?");
245 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
247 if (confirm(msg
) === true) {
248 UI
.pve_vm_command('reset');
252 pveCmdSuspend: function() {
253 if (UI
.pveCommandsOpen
=== true) {
254 UI
.togglePVECommandPanel();
256 var msg
= gettext("Do you really want to suspend VM {0}?");
257 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
259 if (confirm(msg
) === true) {
260 UI
.pve_vm_command('suspend');
264 pveCmdResume: function() {
265 if (UI
.pveCommandsOpen
=== true) {
266 UI
.togglePVECommandPanel();
268 UI
.pve_vm_command('resume');
271 pveCmdReload: function() {
272 if (UI
.pveCommandsOpen
=== true) {
273 UI
.togglePVECommandPanel();
278 pveReload: function() {
282 pve_send_key: function(keyname
) {
285 if (UI
.consoletype
=== 'kvm') {
286 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
288 throw "send key not implemented";
292 params
: { key
: keyname
},
293 url
: baseUrl
+ '/sendkey',
295 failure: function(msg
) {
296 UI
.pve_show_msg('noVNC_status_warn', msg
);
301 pve_start: function(callback
) {
302 UI
.consoletype
= WebUtil
.getQueryVar('console');
303 UI
.vmid
= WebUtil
.getQueryVar('vmid');
304 UI
.vmname
= WebUtil
.getQueryVar('vmname');
305 UI
.nodename
= WebUtil
.getQueryVar('node');
309 var params
= { websocket
: 1 };
312 // add pve command buttons
313 var cmdpanel
= $D('noVNC_pve_command_menu');
316 text
: gettext('Start'),
317 handler
: UI
.pveCmdStart
,
318 enable
: { kvm
: 1, lxc
: 1 }
321 text
: gettext('Shutdown'),
322 handler
: UI
.pveCmdShutdown
,
323 enable
: { kvm
: 1, lxc
: 1 }
326 text
: gettext('Stop'),
327 handler
: UI
.pveCmdStop
,
328 enable
: { kvm
: 1, lxc
: 1 }
331 text
: gettext('Reset'),
332 handler
: UI
.pveCmdReset
,
336 text
: gettext('Suspend'),
337 handler
: UI
.pveCmdSuspend
,
341 text
: gettext('Resume'),
342 handler
: UI
.pveCmdResume
,
346 text
: gettext('Reload'),
347 handler
: UI
.pveCmdReload
,
351 buttonlist
.forEach(function(btn
) {
352 if (btn
.enable
.any
|| btn
.enable
[UI
.consoletype
]) {
353 var el
= document
.createElement('input');
354 el
.setAttribute('type', 'button');
355 el
.setAttribute('value', btn
.text
);
356 el
.onclick
= btn
.handler
;
357 el
.style
.display
= "block";
358 el
.style
.width
= "100%";
359 el
.style
.minWidth
= "150px";
360 cmdpanel
.appendChild(el
);
364 // add sendKeys buttons
365 var skpanel
= $D('noVNC_send_keys_panel');
369 text
: 'Tab', handler: function() {
370 UI
.pve_send_key('tab');
374 text
: 'Ctrl-Alt-Delete', handler: function() {
375 UI
.pve_send_key('ctrl-alt-delete');
379 text
: 'Ctrl-Alt-Backspace', handler: function() {
380 UI
.pve_send_key('ctrl-alt-backspace');
384 text
: 'Ctrl-Alt-F1', handler: function() {
385 UI
.pve_send_key('ctrl-alt-f1');
389 text
: 'Ctrl-Alt-F2', handler: function() {
390 UI
.pve_send_key('ctrl-alt-f2');
394 text
: 'Ctrl-Alt-F3', handler: function() {
395 UI
.pve_send_key('ctrl-alt-f3');
399 text
: 'Ctrl-Alt-F4', handler: function() {
400 UI
.pve_send_key('ctrl-alt-f4');
404 text
: 'Ctrl-Alt-F5', handler: function() {
405 UI
.pve_send_key('ctrl-alt-f5');
409 text
: 'Ctrl-Alt-F6', handler: function() {
410 UI
.pve_send_key('ctrl-alt-f6');
414 text
: 'Ctrl-Alt-F7', handler: function() {
415 UI
.pve_send_key('ctrl-alt-f7');
419 text
: 'Ctrl-Alt-F8', handler: function() {
420 UI
.pve_send_key('ctrl-alt-f8');
424 text
: 'Ctrl-Alt-F9', handler: function() {
425 UI
.pve_send_key('ctrl-alt-f9');
429 text
: 'Ctrl-Alt-F10', handler: function() {
430 UI
.pve_send_key('ctrl-alt-f10');
434 text
: 'Ctrl-Alt-F11', handler: function() {
435 UI
.pve_send_key('ctrl-alt-f11');
439 text
: 'Ctrl-Alt-F12', handler: function() {
440 UI
.pve_send_key('ctrl-alt-f12');
445 buttonlist
.forEach(function(btn
) {
446 var el
= document
.createElement('input');
447 el
.setAttribute('type', 'button');
448 el
.setAttribute('value', btn
.text
);
449 el
.onclick = function(handler
) {
451 if (UI
.sendKeysVisible
=== true) {
452 UI
.togglePVESendKeysPanel();
458 el
.style
.display
= "block";
459 el
.style
.width
= "100%";
460 el
.style
.minWidth
= "150px";
461 skpanel
.appendChild(el
);
466 if (UI
.consoletype
=== 'kvm') {
467 var baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
468 url
= baseUrl
+ '/vncproxy';
469 wsurl
= baseUrl
+ '/vncwebsocket';
470 title
= "VM " + UI
.vmid
;
472 title
+= " ('" + UI
.vmname
+ "')";
474 } else if (UI
.consoletype
=== 'lxc') {
475 var baseUrl
= '/nodes/' + UI
.nodename
+ '/lxc/' + UI
.vmid
;
476 url
= baseUrl
+ '/vncproxy';
477 wsurl
= baseUrl
+ '/vncwebsocket';
478 title
= "CT " + UI
.vmid
;
480 title
+= " ('" + UI
.vmname
+ "')";
482 } else if (UI
.consoletype
=== 'shell') {
483 var baseUrl
= '/nodes/' + UI
.nodename
;
484 url
= baseUrl
+ '/vncshell';
485 wsurl
= baseUrl
+ '/vncwebsocket';
486 title
= "node '" + UI
.nodename
+ "'";
487 } else if (UI
.consoletype
=== 'upgrade') {
488 var baseUrl
= '/nodes/' + UI
.nodename
;
489 url
= baseUrl
+ '/vncshell';
490 wsurl
= baseUrl
+ '/vncwebsocket';
492 title
= gettext('System upgrade on node {0}');
493 title
= title
.replace(/\{0\}/, UI
.nodename
);
495 throw "implement me";
498 document
.title
= title
;
500 var start_vnc_viewer = function(param
) {
501 var wsparams
= UI
.urlEncode({
503 vncticket
: param
.ticket
506 UI
.updateSetting('host', window
.location
.hostname
);
507 UI
.updateSetting('port', window
.location
.port
|| 443);
508 UI
.updateSetting('password', param
.ticket
);
509 UI
.updateSetting('encrypt', true);
510 UI
.updateSetting('true_color', true);
511 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
512 UI
.updateSetting('shared', true);
513 UI
.updateSetting('view_only', false);
515 UI
.updateSetting('path', 'api2/json' + wsurl
+ "?" + wsparams
);
524 success: function(result
) {
525 start_vnc_viewer(result
.data
);
527 failure: function(msg
) {
528 UI
.pve_show_msg('noVNC_status_error', msg
, 1);
533 lastFBWidth
: undefined,
534 lastFBHeight
: undefined,
535 sizeUpdateTimer
: undefined,
537 updateFBSize: function(rfb
, width
, height
) {
539 // Note1: CSS Canvas size is wrong by a few pixels in Chrome
540 // Note2: window size must be even number for firefox
541 UI
.lastFBWidth
= Math
.floor((width
+ 1)/2)*2;;
542 UI
.lastFBHeight
= Math
.floor((height
+ 6)/2)*2;
544 if (UI
.sizeUpdateTimer
!== undefined) {
545 clearInterval(UI
.sizeUpdateTimer
);
547 if (UI
.getSetting('clip')) return;
549 var update_size = function() {
553 if (window
.innerHeight
) {
554 oh
= window
.innerHeight
;
555 ow
= window
.innerWidth
;
556 } else if (document
.documentElement
&&
557 document
.documentElement
.clientHeight
) {
558 oh
= document
.documentElement
.clientHeight
;
559 ow
= document
.documentElement
.clientWidth
;
560 } else if (document
.body
) {
561 oh
= document
.body
.clientHeight
;
562 ow
= document
.body
.clientWidth
;
564 throw "can't get window size";
567 // see base.css/noVNC_screen_pad
568 var toolbar_height
= 36;
570 var offsetw
= UI
.lastFBWidth
- ow
;
571 var offseth
= UI
.lastFBHeight
+ toolbar_height
- oh
;
572 if (offsetw
!== 0 || offseth
!== 0) {
573 //console.log("try resize by " + offsetw + " " + offseth);
574 window
.resizeBy(offsetw
, offseth
);
579 UI
.sizeUpdateTimer
= setInterval(update_size
, 1000);
586 // Open/close PVE commandand menu
587 togglePVECommandPanel: function() {
588 // Close the description panel
589 $D('noVNC_description').style
.display
= "none";
590 if (UI
.sendKeysVisible
=== true) {
591 UI
.togglePVESendKeysPanel();
593 // Close clipboard panel if open
594 if (UI
.clipboardOpen
=== true) {
595 UI
.toggleClipboardPanel();
597 // Close connection settings if open
598 if (UI
.connSettingsOpen
=== true) {
599 UI
.toggleConnectPanel();
601 // Close popup status panel if open
602 if (UI
.popupStatusOpen
=== true) {
603 UI
.togglePopupStatusPanel();
605 // Close XVP panel if open
606 if (UI
.xvpOpen
=== true) {
609 if (UI
.pveCommandsOpen
) {
610 $D('noVNC_pve_commands').style
.display
= "none";
611 $D('pveCommandsButton').className
= "noVNC_status_button";
612 UI
.pveCommandsOpen
= false;
614 $D('noVNC_pve_commands').style
.display
= "block";
615 $D('pveCommandsButton').className
= "noVNC_status_button_selected";
616 UI
.pveCommandsOpen
= true;
620 // Open/close PVE SendKeys menu
621 togglePVESendKeysPanel: function() {
622 // Close the description panel
623 $D('noVNC_description').style
.display
= "none";
624 if (UI
.pveCommandsOpen
=== true) {
625 UI
.togglePVECommandPanel();
627 // Close clipboard panel if open
628 if (UI
.clipboardOpen
=== true) {
629 UI
.toggleClipboardPanel();
631 // Close connection settings if open
632 if (UI
.connSettingsOpen
=== true) {
633 UI
.toggleConnectPanel();
635 // Close popup status panel if open
636 if (UI
.popupStatusOpen
=== true) {
637 UI
.togglePopupStatusPanel();
639 // Close XVP panel if open
640 if (UI
.xvpOpen
=== true) {
643 if (UI
.sendKeysVisible
) {
644 $D('noVNC_send_keys').style
.display
= "none";
645 $D('showSendKeysButton').className
= "noVNC_status_button";
646 UI
.sendKeysVisible
= false;
648 $D('noVNC_send_keys').style
.display
= "block";
649 $D('showSendKeysButton').className
= "noVNC_status_button_selected";
650 UI
.sendKeysVisible
= true;
654 // Render default UI and initialize settings menu
655 start: function(callback
) {
656 var html
= '', i
, sheet
, sheets
, llevels
, port
, autoconnect
;
658 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
660 // Stylesheet selection dropdown
661 sheet
= WebUtil
.selectStylesheet();
662 sheets
= WebUtil
.getStylesheets();
663 for (i
= 0; i
< sheets
.length
; i
+= 1) {
664 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
667 // Logging selection dropdown
668 llevels
= ['error', 'warn', 'info', 'debug'];
669 for (i
= 0; i
< llevels
.length
; i
+= 1) {
670 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
673 // Settings with immediate effects
674 UI
.initSetting('logging', 'warn');
675 WebUtil
.init_logging(UI
.getSetting('logging'));
677 UI
.initSetting('stylesheet', 'default');
678 WebUtil
.selectStylesheet(null);
679 // call twice to get around webkit bug
680 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
682 UI
.initSetting('repeaterID', '');
684 UI
.rfb
= RFB({'target': $D('noVNC_canvas'),
685 'onUpdateState': UI
.updateState
,
686 'onXvpInit': UI
.updateXvpVisualState
,
687 'onClipboard': UI
.clipReceive
,
688 //'onDesktopName': UI.updateDocumentTitle,
689 'onFBResize': UI
.updateFBSize
});
692 if (autoconnect
=== 'true' || autoconnect
== '1') {
699 UI
.updateVisualState();
701 // Unfocus clipboard when over the VNC area
702 //$D('VNC_screen').onmousemove = function () {
703 // var keyboard = UI.rfb.get_keyboard();
704 // if ((! keyboard) || (! keyboard.get_focused())) {
705 // $D('VNC_clipboard_text').blur();
709 // Show mouse selector buttons on touch screen devices
710 if (UI
.isTouchDevice
) {
711 // Show mobile buttons
712 $D('noVNC_mobile_buttons').style
.display
= "inline";
713 $D('showSendKeysButton').style
.display
= "none";
715 // Remove the address bar
716 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
717 UI
.forceSetting('clip', true);
718 $D('noVNC_clip').disabled
= true;
720 $D('showSendKeysButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
721 UI
.initSetting('clip', false);
724 //iOS Safari does not support CSS position:fixed.
725 //This detects iOS devices and enables javascript workaround.
726 if ((navigator
.userAgent
.match(/iPhone/i)) ||
727 (navigator
.userAgent
.match(/iPod/i)) ||
728 (navigator
.userAgent
.match(/iPad/i))) {
734 $D('noVNC_host').focus();
737 Util
.addEvent(window
, 'resize', UI
.setViewClip
);
739 Util
.addEvent(window
, 'beforeunload', function () {
740 if (UI
.rfb_state
=== 'normal') {
741 return "You are currently connected.";
745 // Show description by default when hosted at for kanaka.github.com
746 if (location
.host
=== "kanaka.github.io") {
747 // Open the description dialog
748 $D('noVNC_description').style
.display
= "block";
750 // Show the connect panel on first load unless autoconnecting
751 if (autoconnect
=== UI
.connSettingsOpen
) {
752 UI
.toggleConnectPanel();
756 // Add mouse event click/focus/blur event handlers to the UI
757 UI
.addMouseHandlers();
759 if (typeof callback
=== "function") {
764 addMouseHandlers: function() {
765 // Setup interface handlers that can't be inline
766 $D("noVNC_view_drag_button").onclick
= UI
.setViewDrag
;
767 $D("noVNC_mouse_button0").onclick = function () { UI
.setMouseButton(1); };
768 $D("noVNC_mouse_button1").onclick = function () { UI
.setMouseButton(2); };
769 $D("noVNC_mouse_button2").onclick = function () { UI
.setMouseButton(4); };
770 $D("noVNC_mouse_button4").onclick = function () { UI
.setMouseButton(0); };
771 $D("showKeyboard").onclick
= UI
.showKeyboard
;
773 $D("keyboardinput").oninput
= UI
.keyInput
;
774 $D("keyboardinput").onblur
= UI
.keyInputBlur
;
776 $D("showExtraKeysButton").onclick
= UI
.showExtraKeys
;
777 $D("toggleCtrlButton").onclick
= UI
.toggleCtrl
;
778 $D("toggleAltButton").onclick
= UI
.toggleAlt
;
779 $D("sendTabButton").onclick
= UI
.sendTab
;
780 $D("sendEscButton").onclick
= UI
.sendEsc
;
782 $D("showSendKeysButton").onclick
= UI
.togglePVESendKeysPanel
;
784 $D("sendCtrlAltDelButton").onclick
= UI
.sendCtrlAltDel
;
785 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
786 //$D("xvpRebootButton").onclick = UI.xvpReboot;
787 //$D("xvpResetButton").onclick = UI.xvpReset;
788 // disable popup, because it does not provide more info?
789 //$D("noVNC_status").onclick = UI.togglePopupStatusPanel;
790 $D("noVNC_popup_status_panel").onclick
= UI
.togglePopupStatusPanel
;
791 //$D("xvpButton").onclick = UI.toggleXvpPanel;
792 $D("clipboardButton").onclick
= UI
.toggleClipboardPanel
;
793 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
794 $D("pveCommandsButton").onclick
= UI
.togglePVECommandPanel
;
795 //$D("connectButton").onclick = UI.toggleConnectPanel;
796 //$D("disconnectButton").onclick = UI.disconnect;
797 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
799 $D("noVNC_clipboard_text").onfocus
= UI
.displayBlur
;
800 $D("noVNC_clipboard_text").onblur
= UI
.displayFocus
;
801 $D("noVNC_clipboard_text").onchange
= UI
.clipSend
;
802 $D("noVNC_clipboard_clear_button").onclick
= UI
.clipClear
;
804 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
805 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
806 //$D("noVNC_apply").onclick = UI.settingsApply;
808 //$D("noVNC_connect_button").onclick = UI.connect;
811 // Read form control compatible setting from cookie
812 getSetting: function(name
) {
813 var val
, ctrl
= $D('noVNC_' + name
);
814 val
= WebUtil
.readSetting(name
);
815 if (val
!== null && ctrl
.type
=== 'checkbox') {
816 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
825 // Update cookie and form control setting. If value is not set, then
826 // updates from control to current cookie setting.
827 updateSetting: function(name
, value
) {
829 var i
, ctrl
= $D('noVNC_' + name
);
830 // Save the cookie for this session
831 if (typeof value
!== 'undefined') {
832 WebUtil
.writeSetting(name
, value
);
835 // Update the settings control
836 value
= UI
.getSetting(name
);
838 if (ctrl
.type
=== 'checkbox') {
839 ctrl
.checked
= value
;
841 } else if (typeof ctrl
.options
!== 'undefined') {
842 for (i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
843 if (ctrl
.options
[i
].value
=== value
) {
844 ctrl
.selectedIndex
= i
;
849 /*Weird IE9 error leads to 'null' appearring
850 in textboxes instead of ''.*/
851 if (value
=== null) {
858 // Save control setting to cookie
859 saveSetting: function(name
) {
860 var val
, ctrl
= $D('noVNC_' + name
);
861 if (ctrl
.type
=== 'checkbox') {
863 } else if (typeof ctrl
.options
!== 'undefined') {
864 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
868 WebUtil
.writeSetting(name
, val
);
869 //Util.Debug("Setting saved '" + name + "=" + val + "'");
873 // Initial page load read/initialization of settings
874 initSetting: function(name
, defVal
) {
877 // Check Query string followed by cookie
878 val
= WebUtil
.getQueryVar(name
);
880 val
= WebUtil
.readSetting(name
, defVal
);
882 UI
.updateSetting(name
, val
);
883 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
887 // Force a setting to be a certain value
888 forceSetting: function(name
, val
) {
889 UI
.updateSetting(name
, val
);
894 // Show the popup status panel
895 togglePopupStatusPanel: function() {
896 var psp
= $D('noVNC_popup_status_panel');
897 if (UI
.popupStatusOpen
=== true) {
898 psp
.style
.display
= "none";
899 UI
.popupStatusOpen
= false;
901 psp
.innerHTML
= $D('noVNC_status').innerHTML
;
902 psp
.style
.display
= "block";
903 psp
.style
.left
= window
.innerWidth
/2 -
904 parseInt(window
.getComputedStyle(psp
, false).width
)/2 -30 + "px";
905 UI
.popupStatusOpen
= true;
909 // Show the XVP panel
910 toggleXvpPanel: function() {
911 // Close the description panel
912 $D('noVNC_description').style
.display
= "none";
913 if (UI
.pveCommandsOpen
=== true) {
914 UI
.togglePVECommandPanel();
916 // Close settings if open
917 if (UI
.settingsOpen
=== true) {
919 UI
.closeSettingsMenu();
921 // Close connection settings if open
922 if (UI
.connSettingsOpen
=== true) {
923 UI
.toggleConnectPanel();
925 // Close popup status panel if open
926 if (UI
.popupStatusOpen
=== true) {
927 UI
.togglePopupStatusPanel();
929 // Close clipboard panel if open
930 if (UI
.clipboardOpen
=== true) {
931 UI
.toggleClipboardPanel();
934 if (UI
.xvpOpen
=== true) {
935 //$D('noVNC_xvp').style.display = "none";
936 //$D('xvpButton').className = "noVNC_status_button";
939 //$D('noVNC_xvp').style.display = "block";
940 //$D('xvpButton').className = "noVNC_status_button_selected";
945 // Show the clipboard panel
946 toggleClipboardPanel: function() {
947 // Close the description panel
948 $D('noVNC_description').style
.display
= "none";
949 if (UI
.pveCommandsOpen
=== true) {
950 UI
.togglePVECommandPanel();
952 if (UI
.sendKeysVisible
=== true) {
953 UI
.togglePVESendKeysPanel();
955 // Close settings if open
956 if (UI
.settingsOpen
=== true) {
958 UI
.closeSettingsMenu();
960 // Close connection settings if open
961 if (UI
.connSettingsOpen
=== true) {
962 UI
.toggleConnectPanel();
964 // Close popup status panel if open
965 if (UI
.popupStatusOpen
=== true) {
966 UI
.togglePopupStatusPanel();
968 // Close XVP panel if open
969 if (UI
.xvpOpen
=== true) {
972 // Toggle Clipboard Panel
973 if (UI
.clipboardOpen
=== true) {
974 $D('noVNC_clipboard').style
.display
= "none";
975 $D('clipboardButton').className
= "noVNC_status_button";
976 UI
.clipboardOpen
= false;
978 $D('noVNC_clipboard').style
.display
= "block";
979 $D('clipboardButton').className
= "noVNC_status_button_selected";
980 UI
.clipboardOpen
= true;
984 // Show the connection settings panel/menu
985 toggleConnectPanel: function() {
986 // Close the description panel
987 $D('noVNC_description').style
.display
= "none";
988 if (UI
.pveCommandsOpen
=== true) {
989 UI
.togglePVECommandPanel();
991 // Close connection settings if open
992 if (UI
.settingsOpen
=== true) {
994 UI
.closeSettingsMenu();
995 //$D('connectButton').className = "noVNC_status_button";
997 // Close clipboard panel if open
998 if (UI
.clipboardOpen
=== true) {
999 UI
.toggleClipboardPanel();
1001 // Close popup status panel if open
1002 if (UI
.popupStatusOpen
=== true) {
1003 UI
.togglePopupStatusPanel();
1005 // Close XVP panel if open
1006 if (UI
.xvpOpen
=== true) {
1007 UI
.toggleXvpPanel();
1010 // Toggle Connection Panel
1011 if (UI
.connSettingsOpen
=== true) {
1012 $D('noVNC_controls').style
.display
= "none";
1013 //$D('connectButton').className = "noVNC_status_button";
1014 UI
.connSettingsOpen
= false;
1015 UI
.saveSetting('host');
1016 UI
.saveSetting('port');
1017 //UI.saveSetting('password');
1019 $D('noVNC_controls').style
.display
= "block";
1020 //$D('connectButton').className = "noVNC_status_button_selected";
1021 UI
.connSettingsOpen
= true;
1022 $D('noVNC_host').focus();
1026 // Toggle the settings menu:
1027 // On open, settings are refreshed from saved cookies.
1028 // On close, settings are applied
1029 toggleSettingsPanel: function() {
1030 // Close the description panel
1031 $D('noVNC_description').style
.display
= "none";
1032 if (UI
.settingsOpen
) {
1034 UI
.closeSettingsMenu();
1036 UI
.updateSetting('encrypt');
1037 UI
.updateSetting('true_color');
1038 if (UI
.rfb
.get_display().get_cursor_uri()) {
1039 UI
.updateSetting('cursor');
1041 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
1042 $D('noVNC_cursor').disabled
= true;
1044 UI
.updateSetting('clip');
1045 UI
.updateSetting('shared');
1046 UI
.updateSetting('view_only');
1047 UI
.updateSetting('path');
1048 UI
.updateSetting('repeaterID');
1049 UI
.updateSetting('stylesheet');
1050 UI
.updateSetting('logging');
1052 UI
.openSettingsMenu();
1057 openSettingsMenu: function() {
1058 // Close the description panel
1059 $D('noVNC_description').style
.display
= "none";
1060 if (UI
.pveCommandsOpen
=== true) {
1061 UI
.togglePVECommandPanel();
1063 // Close clipboard panel if open
1064 if (UI
.clipboardOpen
=== true) {
1065 UI
.toggleClipboardPanel();
1067 // Close connection settings if open
1068 if (UI
.connSettingsOpen
=== true) {
1069 UI
.toggleConnectPanel();
1071 // Close popup status panel if open
1072 if (UI
.popupStatusOpen
=== true) {
1073 UI
.togglePopupStatusPanel();
1075 // Close XVP panel if open
1076 if (UI
.xvpOpen
=== true) {
1077 UI
.toggleXvpPanel();
1079 $D('noVNC_settings').style
.display
= "block";
1080 //$D('settingsButton').className = "noVNC_status_button_selected";
1081 UI
.settingsOpen
= true;
1084 // Close menu (without applying settings)
1085 closeSettingsMenu: function() {
1086 $D('noVNC_settings').style
.display
= "none";
1087 //$D('settingsButton').className = "noVNC_status_button";
1088 UI
.settingsOpen
= false;
1091 // Save/apply settings when 'Apply' button is pressed
1092 settingsApply: function() {
1093 //Util.Debug(">> settingsApply");
1094 UI
.saveSetting('encrypt');
1095 UI
.saveSetting('true_color');
1096 if (UI
.rfb
.get_display().get_cursor_uri()) {
1097 UI
.saveSetting('cursor');
1099 UI
.saveSetting('clip');
1100 UI
.saveSetting('shared');
1101 UI
.saveSetting('view_only');
1102 UI
.saveSetting('path');
1103 UI
.saveSetting('repeaterID');
1104 UI
.saveSetting('stylesheet');
1105 UI
.saveSetting('logging');
1107 // Settings with immediate (non-connected related) effect
1108 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
1109 WebUtil
.init_logging(UI
.getSetting('logging'));
1111 UI
.setViewDrag(UI
.rfb
.get_viewportDrag());
1112 //Util.Debug("<< settingsApply");
1117 setPassword: function() {
1118 UI
.rfb
.sendPassword($D('noVNC_password').value
);
1119 //Reset connect button.
1120 $D('noVNC_connect_button').value
= "Connect";
1121 $D('noVNC_connect_button').onclick
= UI
.Connect
;
1122 //Hide connection panel.
1123 UI
.toggleConnectPanel();
1127 sendCtrlAltDel: function() {
1128 UI
.rfb
.sendCtrlAltDel();
1131 xvpShutdown: function() {
1132 UI
.rfb
.xvpShutdown();
1135 xvpReboot: function() {
1139 xvpReset: function() {
1143 setMouseButton: function(num
) {
1144 var b
, blist
= [0, 1,2,4], button
;
1146 if (typeof num
=== 'undefined') {
1147 // Disable mouse buttons
1151 UI
.rfb
.get_mouse().set_touchButton(num
);
1154 for (b
= 0; b
< blist
.length
; b
++) {
1155 button
= $D('noVNC_mouse_button' + blist
[b
]);
1156 if (blist
[b
] === num
) {
1157 button
.style
.display
= "";
1159 button
.style
.display
= "none";
1161 button.style.backgroundColor = "black";
1162 button.style.color = "lightgray";
1163 button.style.backgroundColor = "";
1164 button.style.color = "";
1170 updateState: function(rfb
, state
, oldstate
, msg
) {
1171 var s
, sb
, c
, d
, cad
, vd
, klass
;
1172 UI
.rfb_state
= state
;
1176 klass
= "noVNC_status_error";
1179 klass
= "noVNC_status_normal";
1180 UI
.pveAllowMigratedVMTest
= true;
1182 case 'disconnected':
1183 $D('noVNC_logo').style
.display
= "block";
1184 if (UI
.pveAllowMigratedVMTest
) {
1185 UI
.pveAllowMigratedVMTest
= false;
1186 UI
.pve_detect_migrated_vm();
1190 klass
= "noVNC_status_normal";
1193 UI
.toggleConnectPanel();
1195 $D('noVNC_connect_button').value
= "Send Password";
1196 $D('noVNC_connect_button').onclick
= UI
.setPassword
;
1197 $D('noVNC_password').focus();
1199 klass
= "noVNC_status_warn";
1202 klass
= "noVNC_status_warn";
1206 if (typeof(msg
) !== 'undefined') {
1207 $D('noVNC-control-bar').setAttribute("class", klass
);
1208 $D('noVNC_status').innerHTML
= msg
;
1211 UI
.updateVisualState();
1214 // Disable/enable controls depending on connection state
1215 updateVisualState: function() {
1216 var connected
= UI
.rfb_state
=== 'normal' ? true : false;
1218 //Util.Debug(">> updateVisualState");
1219 $D('noVNC_encrypt').disabled
= connected
;
1220 $D('noVNC_true_color').disabled
= connected
;
1221 if (UI
.rfb
&& UI
.rfb
.get_display() &&
1222 UI
.rfb
.get_display().get_cursor_uri()) {
1223 $D('noVNC_cursor').disabled
= connected
;
1225 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
1226 $D('noVNC_cursor').disabled
= true;
1228 $D('noVNC_shared').disabled
= connected
;
1229 $D('noVNC_view_only').disabled
= connected
;
1230 $D('noVNC_path').disabled
= connected
;
1231 $D('noVNC_repeaterID').disabled
= connected
;
1235 UI
.setMouseButton(1);
1236 $D('clipboardButton').style
.display
= (UI
.consoletype
!== 'kvm') ? "inline" : "none";
1237 $D('showKeyboard').style
.display
= "inline";
1238 $D('noVNC_extra_keys').style
.display
= "";
1240 UI
.setMouseButton();
1241 $D('clipboardButton').style
.display
= "none";
1242 $D('showKeyboard').style
.display
= "none";
1243 $D('noVNC_extra_keys').style
.display
= "none";
1244 UI
.updateXvpVisualState(0);
1247 // State change disables viewport dragging.
1248 // It is enabled (toggled) by direct click on the button
1249 UI
.setViewDrag(false);
1251 switch (UI
.rfb_state
) {
1255 case 'disconnected':
1256 //$D('connectButton').style.display = "";
1257 //$D('disconnectButton').style.display = "none";
1260 //$D('connectButton').style.display = "none";
1261 //$D('disconnectButton').style.display = "";
1265 //Util.Debug("<< updateVisualState");
1268 // Disable/enable XVP button
1269 updateXvpVisualState: function(ver
) {
1272 //$D('xvpButton').style.display = 'inline';
1274 //$D('xvpButton').style.display = 'none';
1275 // Close XVP panel if open
1276 if (UI
.xvpOpen
=== true) {
1277 UI
.toggleXvpPanel();
1283 // Display the desktop name in the document title
1284 updateDocumentTitle: function(rfb
, name
) {
1285 document
.title
= name
+ " - noVNC";
1289 clipReceive: function(rfb
, text
) {
1290 Util
.Debug(">> UI.clipReceive: " + text
.substr(0,40) + "...");
1291 $D('noVNC_clipboard_text').value
= text
;
1292 Util
.Debug("<< UI.clipReceive");
1296 connect: function() {
1297 var host
, port
, password
, path
;
1299 UI
.closeSettingsMenu();
1300 UI
.toggleConnectPanel();
1302 host
= $D('noVNC_host').value
;
1303 port
= $D('noVNC_port').value
;
1304 password
= $D('noVNC_password').value
;
1305 path
= $D('noVNC_path').value
;
1306 if ((!host
) || (!port
)) {
1307 throw("Must set host and port");
1310 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1311 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1312 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1313 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1314 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1315 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1317 UI
.rfb
.connect(host
, port
, password
, path
);
1320 setTimeout(UI
.setBarPosition
, 100);
1321 $D('noVNC_logo').style
.display
= "none";
1324 disconnect: function() {
1325 UI
.closeSettingsMenu();
1326 UI
.rfb
.disconnect();
1328 $D('noVNC_logo').style
.display
= "block";
1329 UI
.connSettingsOpen
= false;
1330 UI
.toggleConnectPanel();
1333 displayBlur: function() {
1334 UI
.rfb
.get_keyboard().set_focused(false);
1335 UI
.rfb
.get_mouse().set_focused(false);
1338 displayFocus: function() {
1339 UI
.rfb
.get_keyboard().set_focused(true);
1340 UI
.rfb
.get_mouse().set_focused(true);
1343 clipClear: function() {
1344 $D('noVNC_clipboard_text').value
= "";
1345 UI
.rfb
.clipboardPasteFrom("");
1348 clipSend: function() {
1349 var text
= $D('noVNC_clipboard_text').value
;
1350 Util
.Debug(">> UI.clipSend: " + text
.substr(0,40) + "...");
1351 UI
.rfb
.clipboardPasteFrom(text
);
1352 Util
.Debug("<< UI.clipSend");
1356 // Enable/disable and configure viewport clipping
1357 setViewClip: function(clip
) {
1358 var display
, cur_clip
, pos
, new_w
, new_h
;
1361 display
= UI
.rfb
.get_display();
1366 cur_clip
= display
.get_viewport();
1368 if (typeof(clip
) !== 'boolean') {
1369 // Use current setting
1370 clip
= UI
.getSetting('clip');
1373 if (clip
&& !cur_clip
) {
1375 UI
.updateSetting('clip', true);
1376 } else if (!clip
&& cur_clip
) {
1377 // Turn clipping off
1378 UI
.updateSetting('clip', false);
1379 display
.set_viewport(false);
1380 $D('noVNC_canvas').style
.position
= 'static';
1381 display
.viewportChange();
1383 if (UI
.getSetting('clip')) {
1384 // If clipping, update clipping settings
1385 $D('noVNC_canvas').style
.position
= 'absolute';
1386 pos
= Util
.getPosition($D('noVNC_canvas'));
1387 new_w
= window
.innerWidth
- pos
.x
;
1388 new_h
= window
.innerHeight
- pos
.y
;
1389 display
.set_viewport(true);
1390 display
.viewportChange(0, 0, new_w
, new_h
);
1394 // Toggle/set/unset the viewport drag/move button
1395 setViewDrag: function(drag
) {
1396 var vmb
= $D('noVNC_view_drag_button');
1397 if (!UI
.rfb
) { return; }
1399 if (UI
.rfb_state
=== 'normal' &&
1400 UI
.rfb
.get_display().get_viewport()) {
1401 vmb
.style
.display
= "inline";
1403 vmb
.style
.display
= "none";
1406 if (typeof(drag
) === "undefined" ||
1407 typeof(drag
) === "object") {
1408 // If not specified, then toggle
1409 drag
= !UI
.rfb
.get_viewportDrag();
1412 vmb
.className
= "noVNC_status_button_selected";
1413 UI
.rfb
.set_viewportDrag(true);
1415 vmb
.className
= "noVNC_status_button";
1416 UI
.rfb
.set_viewportDrag(false);
1420 // On touch devices, show the OS keyboard
1421 showKeyboard: function() {
1423 kbi
= $D('keyboardinput');
1424 skb
= $D('showKeyboard');
1425 l
= kbi
.value
.length
;
1426 if(UI
.keyboardVisible
=== false) {
1428 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1429 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1430 UI
.keyboardVisible
= true;
1431 skb
.className
= "noVNC_status_button_selected";
1432 } else if(UI
.keyboardVisible
=== true) {
1434 skb
.className
= "noVNC_status_button";
1435 UI
.keyboardVisible
= false;
1439 keepKeyboard: function() {
1440 clearTimeout(UI
.hideKeyboardTimeout
);
1441 if(UI
.keyboardVisible
=== true) {
1442 $D('keyboardinput').focus();
1443 $D('showKeyboard').className
= "noVNC_status_button_selected";
1444 } else if(UI
.keyboardVisible
=== false) {
1445 $D('keyboardinput').blur();
1446 $D('showKeyboard').className
= "noVNC_status_button";
1450 keyboardinputReset: function() {
1451 var kbi
= $D('keyboardinput');
1452 kbi
.value
= Array(UI
.defaultKeyboardinputLen
).join("_");
1453 UI
.lastKeyboardinput
= kbi
.value
;
1456 // When normal keyboard events are left uncought, use the input events from
1457 // the keyboardinput element instead and generate the corresponding key events.
1458 // This code is required since some browsers on Android are inconsistent in
1459 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1460 keyInput: function(event
) {
1461 var newValue
, oldValue
, newLen
, oldLen
;
1462 newValue
= event
.target
.value
;
1463 oldValue
= UI
.lastKeyboardinput
;
1466 // Try to check caret position since whitespace at the end
1467 // will not be considered by value.length in some browsers
1468 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1470 // selectionStart is undefined in Google Chrome
1471 newLen
= newValue
.length
;
1473 oldLen
= oldValue
.length
;
1476 var inputs
= newLen
- oldLen
;
1478 backspaces
= -inputs
;
1482 // Compare the old string with the new to account for
1483 // text-corrections or other input that modify existing text
1484 for (var i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1485 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1486 inputs
= newLen
- i
;
1487 backspaces
= oldLen
- i
;
1492 // Send the key events
1493 for (var i
= 0; i
< backspaces
; i
++)
1494 UI
.rfb
.sendKey(XK_BackSpace
);
1495 for (var i
= newLen
- inputs
; i
< newLen
; i
++)
1496 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1498 // Control the text content length in the keyboardinput element
1499 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1500 UI
.keyboardinputReset();
1501 } else if (newLen
< 1) {
1502 // There always have to be some text in the keyboardinput
1503 // element with which backspace can interact.
1504 UI
.keyboardinputReset();
1505 // This sometimes causes the keyboard to disappear for a second
1506 // but it is required for the android keyboard to recognize that
1507 // text has been added to the field
1508 event
.target
.blur();
1509 // This has to be ran outside of the input handler in order to work
1510 setTimeout(function() { UI
.keepKeyboard(); }, 0);
1513 UI
.lastKeyboardinput
= newValue
;
1517 keyInputBlur: function() {
1518 $D('showKeyboard').className
= "noVNC_status_button";
1519 //Weird bug in iOS if you change keyboardVisible
1520 //here it does not actually occur so next time
1521 //you click keyboard icon it doesnt work.
1522 UI
.hideKeyboardTimeout
= setTimeout(function() { UI
.setKeyboard(); },100);
1525 showExtraKeys: function() {
1527 if(UI
.extraKeysVisible
=== false) {
1528 $D('toggleCtrlButton').style
.display
= "inline";
1529 $D('toggleAltButton').style
.display
= "inline";
1530 $D('sendTabButton').style
.display
= "inline";
1531 $D('sendEscButton').style
.display
= "inline";
1532 $D('sendCtrlAltDelButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
1533 $D('showExtraKeysButton').className
= "noVNC_status_button_selected";
1534 UI
.extraKeysVisible
= true;
1535 } else if(UI
.extraKeysVisible
=== true) {
1536 $D('toggleCtrlButton').style
.display
= "";
1537 $D('toggleAltButton').style
.display
= "";
1538 $D('sendTabButton').style
.display
= "";
1539 $D('sendEscButton').style
.display
= "";
1540 $D('sendCtrlAltDelButton').style
.display
= "";
1541 $D('showExtraKeysButton').className
= "noVNC_status_button";
1542 UI
.extraKeysVisible
= false;
1546 toggleCtrl: function() {
1548 if(UI
.ctrlOn
=== false) {
1549 UI
.rfb
.sendKey(XK_Control_L
, true);
1550 $D('toggleCtrlButton').className
= "noVNC_status_button_selected";
1552 } else if(UI
.ctrlOn
=== true) {
1553 UI
.rfb
.sendKey(XK_Control_L
, false);
1554 $D('toggleCtrlButton').className
= "noVNC_status_button";
1559 toggleAlt: function() {
1561 if(UI
.altOn
=== false) {
1562 UI
.rfb
.sendKey(XK_Alt_L
, true);
1563 $D('toggleAltButton').className
= "noVNC_status_button_selected";
1565 } else if(UI
.altOn
=== true) {
1566 UI
.rfb
.sendKey(XK_Alt_L
, false);
1567 $D('toggleAltButton').className
= "noVNC_status_button";
1572 sendTab: function() {
1574 UI
.rfb
.sendKey(XK_Tab
);
1577 sendEsc: function() {
1579 UI
.rfb
.sendKey(XK_Escape
);
1582 setKeyboard: function() {
1583 UI
.keyboardVisible
= false;
1586 // iOS < Version 5 does not support position fixed. Javascript workaround:
1587 setOnscroll: function() {
1588 window
.onscroll = function() {
1589 UI
.setBarPosition();
1593 setResize: function () {
1594 window
.onResize = function() {
1595 UI
.setBarPosition();
1599 //Helper to add options to dropdown.
1600 addOption: function(selectbox
,text
,value
)
1602 var optn
= document
.createElement("OPTION");
1605 selectbox
.options
.add(optn
);
1608 setBarPosition: function() {
1609 $D('noVNC-control-bar').style
.top
= (window
.pageYOffset
) + 'px';
1610 $D('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1612 var vncwidth
= $D('noVNC_screen').style
.offsetWidth
;
1613 $D('noVNC-control-bar').style
.width
= vncwidth
+ 'px';