]>
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 sendKeysVisible
: false,
30 keyboardVisible
: false,
31 hideKeyboardTimeout
: null,
32 lastKeyboardinput
: null,
33 defaultKeyboardinputLen
: 100,
34 extraKeysVisible
: false,
39 consoletype
: undefined,
44 // Setup rfb object, load settings from browser storage, then call
45 // UI.init to setup the UI/menus
46 load: function (callback
) {
47 WebUtil
.initSettings(UI
.pve_start
, callback
);
50 // Proxmox VE related code
52 urlEncode: function(object
) {
53 var i
,value
, params
= [];
56 if (object
.hasOwnProperty(i
)) {
58 if (value
=== undefined) value
= '';
59 params
.push(encodeURIComponent(i
) + '=' + encodeURIComponent(String(value
)));
63 return params
.join('&');
66 API2Request: function(reqOpts
) {
68 reqOpts
.method
= reqOpts
.method
|| 'GET';
70 var xhr
= new XMLHttpRequest();
72 xhr
.onload = function() {
73 var scope
= reqOpts
.scope
|| this;
77 if (xhr
.readyState
=== 4) {
78 var ctype
= xhr
.getResponseHeader('Content-Type');
79 if (xhr
.status
=== 200) {
80 if (ctype
.match(/application\/json;/)) {
81 result
= JSON
.parse(xhr
.responseText
);
83 errmsg
= 'got unexpected content type ' + ctype
;
86 errmsg
= 'Error ' + xhr
.status
+ ': ' + xhr
.statusText
;
89 errmsg
= 'Connection error - server offline?';
92 if (errmsg
!== undefined) {
93 if (reqOpts
.failure
) {
94 reqOpts
.failure
.call(scope
, errmsg
);
97 if (reqOpts
.success
) {
98 reqOpts
.success
.call(scope
, result
);
101 if (reqOpts
.callback
) {
102 reqOpts
.callback
.call(scope
, errmsg
=== undefined);
106 var data
= UI
.urlEncode(reqOpts
.params
|| {});
108 if (reqOpts
.method
=== 'GET') {
109 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
+ '?' + data
);
111 xhr
.open(reqOpts
.method
, "/api2/json" + reqOpts
.url
);
113 xhr
.setRequestHeader('Cache-Control', 'no-cache');
114 if (reqOpts
.method
=== 'POST' || reqOpts
.method
=== 'PUT') {
115 xhr
.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
116 xhr
.setRequestHeader('CSRFPreventionToken', PVE
.CSRFPreventionToken
);
118 } else if (reqOpts
.method
=== 'GET') {
121 throw "unknown method";
127 // show msg for 5 seconds
128 pve_show_msg: function(klass
, msg
, permanant
) {
129 var oldklass
= $D('noVNC-control-bar').getAttribute("class");
130 $D('noVNC-control-bar').setAttribute("class", klass
);
131 var oldmsg
= $D('noVNC_status').innerHTML
;
132 $D('noVNC_status').innerHTML
= msg
;
133 if (typeof permanent
!== 'undefined' && permanent
) return;
135 setTimeout(function() {
136 var curmsg
= $D('noVNC_status').innerHTML
;
137 if (curmsg
=== msg
) {
138 $D('noVNC_status').innerHTML
= oldmsg
;
140 var curklass
= $D('noVNC-control-bar').getAttribute("class");
141 if (curklass
=== klass
) {
142 $D('noVNC-control-bar').setAttribute("class", oldklass
);
147 pve_vm_command: function(cmd
, params
, reload
) {
150 if (UI
.consoletype
=== 'kvm') {
151 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
152 } else if (UI
.consoletype
=== 'openvz') {
153 baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
155 throw "unknown VM type";
160 url
: baseUrl
+ "/status/" + cmd
,
162 failure: function(msg
) {
163 UI
.pve_show_msg('noVNC_status_warn', msg
);
165 success: function() {
166 UI
.pve_show_msg('noVNC_status_normall', "VM command '" + cmd
+"' successful");
168 setTimeout(function() {
176 pveCmdStart: function() {
177 if (UI
.pveCommandsOpen
=== true) {
178 UI
.togglePVECommandPanel();
180 UI
.pve_vm_command('start', {}, true);
183 pveCmdShutdown: function() {
184 if (UI
.pveCommandsOpen
=== true) {
185 UI
.togglePVECommandPanel();
187 var msg
= gettext("Do you really want to shutdown VM {0}?");
188 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
190 if (confirm(msg
) === true) {
191 UI
.pve_vm_command('shutdown');
195 pveCmdStop: function() {
196 if (UI
.pveCommandsOpen
=== true) {
197 UI
.togglePVECommandPanel();
200 var msg
= gettext("Do you really want to stop VM {0}?");
201 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
203 if (confirm(msg
) === true) {
204 UI
.pve_vm_command('stop');
208 pveCmdReset: function() {
209 if (UI
.pveCommandsOpen
=== true) {
210 UI
.togglePVECommandPanel();
212 var msg
= gettext("Do you really want to reset VM {0}?");
213 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
215 if (confirm(msg
) === true) {
216 UI
.pve_vm_command('reset');
220 pveCmdSuspend: function() {
221 if (UI
.pveCommandsOpen
=== true) {
222 UI
.togglePVECommandPanel();
224 var msg
= gettext("Do you really want to suspend VM {0}?");
225 msg
= msg
.replace(/\{0\}/, UI
.vmid
);
227 if (confirm(msg
) === true) {
228 UI
.pve_vm_command('suspend');
232 pveCmdResume: function() {
233 if (UI
.pveCommandsOpen
=== true) {
234 UI
.togglePVECommandPanel();
236 UI
.pve_vm_command('resume');
239 pveCmdReload: function() {
240 if (UI
.pveCommandsOpen
=== true) {
241 UI
.togglePVECommandPanel();
246 pveReload: function() {
250 pve_send_key: function(keyname
) {
253 if (UI
.consoletype
=== 'kvm') {
254 baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
256 throw "send key not implemented";
260 params
: { key
: keyname
},
261 url
: baseUrl
+ '/sendkey',
263 failure: function(msg
) {
264 UI
.pve_show_msg('noVNC_status_warn', msg
);
269 pve_start: function(callback
) {
270 UI
.consoletype
= WebUtil
.getQueryVar('console');
271 UI
.vmid
= WebUtil
.getQueryVar('vmid');
272 UI
.vmname
= WebUtil
.getQueryVar('vmname');
273 UI
.nodename
= WebUtil
.getQueryVar('node');
277 var params
= { websocket
: 1 };
280 var translate_btn_text = function(btn
, hide
) {
282 if (hide
) el
.style
.display
= "none";
283 el
.value
= gettext(el
.value
);
286 var vmcmd_btns
= ['pveStartButton', 'pveShutdownButton', 'pveStopButton', 'pveResetButton', 'pveSuspendButton', 'pveResumeButton'];
287 vmcmd_btns
.forEach(function(btn
) {
288 translate_btn_text(btn
, true);
291 translate_btn_text('pveReloadButton', false);
293 // add sendKeys buttons
294 var skpanel
= $D('noVNC_send_keys_panel');
298 text
: 'Tab', handler: function() {
299 UI
.pve_send_key('tab');
303 text
: 'Ctrl-Alt-Delete', handler: function() {
304 UI
.pve_send_key('ctrl-alt-delete');
308 text
: 'Ctrl-Alt-Backspace', handler: function() {
309 UI
.pve_send_key('ctrl-alt-backspace');
313 text
: 'Ctrl-Alt-F1', handler: function() {
314 UI
.pve_send_key('ctrl-alt-f1');
318 text
: 'Ctrl-Alt-F2', handler: function() {
319 UI
.pve_send_key('ctrl-alt-f2');
323 text
: 'Ctrl-Alt-F3', handler: function() {
324 UI
.pve_send_key('ctrl-alt-f3');
328 text
: 'Ctrl-Alt-F4', handler: function() {
329 UI
.pve_send_key('ctrl-alt-f4');
333 text
: 'Ctrl-Alt-F5', handler: function() {
334 UI
.pve_send_key('ctrl-alt-f5');
338 text
: 'Ctrl-Alt-F6', handler: function() {
339 UI
.pve_send_key('ctrl-alt-f6');
343 text
: 'Ctrl-Alt-F7', handler: function() {
344 UI
.pve_send_key('ctrl-alt-f7');
348 text
: 'Ctrl-Alt-F8', handler: function() {
349 UI
.pve_send_key('ctrl-alt-f8');
353 text
: 'Ctrl-Alt-F9', handler: function() {
354 UI
.pve_send_key('ctrl-alt-f9');
358 text
: 'Ctrl-Alt-F10', handler: function() {
359 UI
.pve_send_key('ctrl-alt-f10');
363 text
: 'Ctrl-Alt-F11', handler: function() {
364 UI
.pve_send_key('ctrl-alt-f11');
368 text
: 'Ctrl-Alt-F12', handler: function() {
369 UI
.pve_send_key('ctrl-alt-f12');
374 buttonlist
.forEach(function(btn
) {
375 var el
= document
.createElement('input');
376 el
.setAttribute('type', 'button');
377 el
.setAttribute('value', btn
.text
);
378 el
.onclick = function(handler
) {
380 if (UI
.sendKeysVisible
=== true) {
381 UI
.togglePVESendKeysPanel();
387 el
.style
.display
= "block";
388 el
.style
.width
= "100%";
389 el
.style
.minWidth
= "150px";
390 skpanel
.appendChild(el
);
395 if (UI
.consoletype
=== 'kvm') {
396 var baseUrl
= '/nodes/' + UI
.nodename
+ '/qemu/' + UI
.vmid
;
397 url
= baseUrl
+ '/vncproxy';
398 wsurl
= baseUrl
+ '/vncwebsocket';
399 vmcmd_btns
.forEach(function(btn
) {
400 $D(btn
).style
.display
= "";
402 title
= "VM " + UI
.vmid
;
404 title
+= " ('" + UI
.vmname
+ "')";
406 } else if (UI
.consoletype
=== 'openvz') {
407 var baseUrl
= '/nodes/' + UI
.nodename
+ '/openvz/' + UI
.vmid
;
408 url
= baseUrl
+ '/vncproxy';
409 wsurl
= baseUrl
+ '/vncwebsocket';
410 ['pveStartButton', 'pveShutdownButton', 'pveStopButton'].forEach(function(btn
) {
411 $D(btn
).style
.display
= "";
413 title
= "CT " + UI
.vmid
;
415 title
+= " ('" + UI
.vmname
+ "')";
417 } else if (UI
.consoletype
=== 'shell') {
418 var baseUrl
= '/nodes/' + UI
.nodename
;
419 url
= baseUrl
+ '/vncshell';
420 wsurl
= baseUrl
+ '/vncwebsocket';
421 title
= "node '" + UI
.nodename
+ "'";
422 } else if (UI
.consoletype
=== 'upgrade') {
423 var baseUrl
= '/nodes/' + UI
.nodename
;
424 url
= baseUrl
+ '/vncshell';
425 wsurl
= baseUrl
+ '/vncwebsocket';
427 title
= gettext('System upgrade on node {0}');
428 title
= title
.replace(/\{0\}/, UI
.nodename
);
430 throw "implement me";
433 document
.title
= title
;
435 var start_vnc_viewer = function(param
) {
436 var wsparams
= UI
.urlEncode({
438 vncticket
: param
.ticket
441 UI
.updateSetting('host', window
.location
.hostname
);
442 UI
.updateSetting('port', window
.location
.port
);
443 UI
.updateSetting('password', param
.ticket
);
444 UI
.updateSetting('encrypt', true);
445 UI
.updateSetting('true_color', true);
446 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
447 UI
.updateSetting('shared', true);
448 UI
.updateSetting('view_only', false);
450 UI
.updateSetting('path', 'api2/json' + wsurl
+ "?" + wsparams
);
459 success: function(result
) {
460 start_vnc_viewer(result
.data
);
462 failure: function(msg
) {
463 UI
.pve_show_msg('noVNC_status_error', msg
, 1);
468 lastFBWidth
: undefined,
469 lastFBHeight
: undefined,
470 sizeUpdateTimer
: undefined,
472 updateFBSize: function(rfb
, width
, height
) {
474 UI
.lastFBWidth
= width
+ 2;
475 UI
.lastFBHeight
= height
+ 6;
477 if (UI
.sizeUpdateTimer
!== undefined) {
478 clearInterval(UI
.sizeUpdateTimer
);
480 if (UI
.getSetting('clip')) return;
482 var update_size = function() {
486 if (window
.innerHeight
) {
487 oh
= window
.innerHeight
;
488 ow
= window
.innerWidth
;
489 } else if (document
.documentElement
&&
490 document
.documentElement
.clientHeight
) {
491 oh
= document
.documentElement
.clientHeight
;
492 ow
= document
.documentElement
.clientWidth
;
493 } else if (document
.body
) {
494 oh
= document
.body
.clientHeight
;
495 ow
= document
.body
.clientWidth
;
497 throw "can't get window size";
500 // see base.css/noVNC_screen_pad
501 var toolbar_height
= 36;
503 var offsetw
= UI
.lastFBWidth
- ow
;
504 var offseth
= UI
.lastFBHeight
+ toolbar_height
- oh
;
505 if (offsetw
!== 0 || offseth
!== 0) {
506 //console.log("try resize by " + offsetw + " " + offseth);
507 window
.resizeBy(offsetw
, offseth
);
512 UI
.sizeUpdateTimer
= setInterval(update_size
, 1000);
519 // Open/close PVE commandand menu
520 togglePVECommandPanel: function() {
521 // Close the description panel
522 $D('noVNC_description').style
.display
= "none";
523 if (UI
.sendKeysVisible
=== true) {
524 UI
.togglePVESendKeysPanel();
526 // Close clipboard panel if open
527 if (UI
.clipboardOpen
=== true) {
528 UI
.toggleClipboardPanel();
530 // Close connection settings if open
531 if (UI
.connSettingsOpen
=== true) {
532 UI
.toggleConnectPanel();
534 // Close popup status panel if open
535 if (UI
.popupStatusOpen
=== true) {
536 UI
.togglePopupStatusPanel();
538 // Close XVP panel if open
539 if (UI
.xvpOpen
=== true) {
542 if (UI
.pveCommandsOpen
) {
543 $D('noVNC_pve_commands').style
.display
= "none";
544 $D('pveCommandsButton').className
= "noVNC_status_button";
545 UI
.pveCommandsOpen
= false;
547 $D('noVNC_pve_commands').style
.display
= "block";
548 $D('pveCommandsButton').className
= "noVNC_status_button_selected";
549 UI
.pveCommandsOpen
= true;
553 // Open/close PVE SendKeys menu
554 togglePVESendKeysPanel: function() {
555 // Close the description panel
556 $D('noVNC_description').style
.display
= "none";
557 if (UI
.pveCommandsOpen
=== true) {
558 UI
.togglePVECommandPanel();
560 // Close clipboard panel if open
561 if (UI
.clipboardOpen
=== true) {
562 UI
.toggleClipboardPanel();
564 // Close connection settings if open
565 if (UI
.connSettingsOpen
=== true) {
566 UI
.toggleConnectPanel();
568 // Close popup status panel if open
569 if (UI
.popupStatusOpen
=== true) {
570 UI
.togglePopupStatusPanel();
572 // Close XVP panel if open
573 if (UI
.xvpOpen
=== true) {
576 if (UI
.sendKeysVisible
) {
577 $D('noVNC_send_keys').style
.display
= "none";
578 $D('showSendKeysButton').className
= "noVNC_status_button";
579 UI
.sendKeysVisible
= false;
581 $D('noVNC_send_keys').style
.display
= "block";
582 $D('showSendKeysButton').className
= "noVNC_status_button_selected";
583 UI
.sendKeysVisible
= true;
587 // Render default UI and initialize settings menu
588 start: function(callback
) {
589 var html
= '', i
, sheet
, sheets
, llevels
, port
, autoconnect
;
591 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
593 // Stylesheet selection dropdown
594 sheet
= WebUtil
.selectStylesheet();
595 sheets
= WebUtil
.getStylesheets();
596 for (i
= 0; i
< sheets
.length
; i
+= 1) {
597 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
600 // Logging selection dropdown
601 llevels
= ['error', 'warn', 'info', 'debug'];
602 for (i
= 0; i
< llevels
.length
; i
+= 1) {
603 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
606 // Settings with immediate effects
607 UI
.initSetting('logging', 'warn');
608 WebUtil
.init_logging(UI
.getSetting('logging'));
610 UI
.initSetting('stylesheet', 'default');
611 WebUtil
.selectStylesheet(null);
612 // call twice to get around webkit bug
613 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
615 UI
.initSetting('repeaterID', '');
617 UI
.rfb
= RFB({'target': $D('noVNC_canvas'),
618 'onUpdateState': UI
.updateState
,
619 'onXvpInit': UI
.updateXvpVisualState
,
620 'onClipboard': UI
.clipReceive
,
621 //'onDesktopName': UI.updateDocumentTitle,
622 'onFBResize': UI
.updateFBSize
});
625 if (autoconnect
=== 'true' || autoconnect
== '1') {
632 UI
.updateVisualState();
634 // Unfocus clipboard when over the VNC area
635 //$D('VNC_screen').onmousemove = function () {
636 // var keyboard = UI.rfb.get_keyboard();
637 // if ((! keyboard) || (! keyboard.get_focused())) {
638 // $D('VNC_clipboard_text').blur();
642 // Show mouse selector buttons on touch screen devices
643 if (UI
.isTouchDevice
) {
644 // Show mobile buttons
645 $D('noVNC_mobile_buttons').style
.display
= "inline";
646 $D('showSendKeysButton').style
.display
= "none";
648 // Remove the address bar
649 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
650 UI
.forceSetting('clip', true);
651 $D('noVNC_clip').disabled
= true;
653 $D('showSendKeysButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
654 UI
.initSetting('clip', false);
657 //iOS Safari does not support CSS position:fixed.
658 //This detects iOS devices and enables javascript workaround.
659 if ((navigator
.userAgent
.match(/iPhone/i)) ||
660 (navigator
.userAgent
.match(/iPod/i)) ||
661 (navigator
.userAgent
.match(/iPad/i))) {
667 $D('noVNC_host').focus();
670 Util
.addEvent(window
, 'resize', UI
.setViewClip
);
672 Util
.addEvent(window
, 'beforeunload', function () {
673 if (UI
.rfb_state
=== 'normal') {
674 return "You are currently connected.";
678 // Show description by default when hosted at for kanaka.github.com
679 if (location
.host
=== "kanaka.github.io") {
680 // Open the description dialog
681 $D('noVNC_description').style
.display
= "block";
683 // Show the connect panel on first load unless autoconnecting
684 if (autoconnect
=== UI
.connSettingsOpen
) {
685 UI
.toggleConnectPanel();
689 // Add mouse event click/focus/blur event handlers to the UI
690 UI
.addMouseHandlers();
692 if (typeof callback
=== "function") {
697 addMouseHandlers: function() {
698 // Setup interface handlers that can't be inline
699 $D("noVNC_view_drag_button").onclick
= UI
.setViewDrag
;
700 $D("noVNC_mouse_button0").onclick = function () { UI
.setMouseButton(1); };
701 $D("noVNC_mouse_button1").onclick = function () { UI
.setMouseButton(2); };
702 $D("noVNC_mouse_button2").onclick = function () { UI
.setMouseButton(4); };
703 $D("noVNC_mouse_button4").onclick = function () { UI
.setMouseButton(0); };
704 $D("showKeyboard").onclick
= UI
.showKeyboard
;
706 $D("keyboardinput").oninput
= UI
.keyInput
;
707 $D("keyboardinput").onblur
= UI
.keyInputBlur
;
709 $D("showExtraKeysButton").onclick
= UI
.showExtraKeys
;
710 $D("toggleCtrlButton").onclick
= UI
.toggleCtrl
;
711 $D("toggleAltButton").onclick
= UI
.toggleAlt
;
712 $D("sendTabButton").onclick
= UI
.sendTab
;
713 $D("sendEscButton").onclick
= UI
.sendEsc
;
715 $D("showSendKeysButton").onclick
= UI
.togglePVESendKeysPanel
;
717 $D("pveStartButton").onclick
= UI
.pveCmdStart
;
718 $D("pveShutdownButton").onclick
= UI
.pveCmdShutdown
;
719 $D("pveStopButton").onclick
= UI
.pveCmdStop
;
720 $D("pveResetButton").onclick
= UI
.pveCmdReset
;
721 $D("pveSuspendButton").onclick
= UI
.pveCmdSuspend
;
722 $D("pveResumeButton").onclick
= UI
.pveCmdResume
;
723 $D("pveReloadButton").onclick
= UI
.pveCmdReload
;
725 $D("sendCtrlAltDelButton").onclick
= UI
.sendCtrlAltDel
;
726 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
727 //$D("xvpRebootButton").onclick = UI.xvpReboot;
728 //$D("xvpResetButton").onclick = UI.xvpReset;
729 // disable popup, because it does not provide more info?
730 //$D("noVNC_status").onclick = UI.togglePopupStatusPanel;
731 $D("noVNC_popup_status_panel").onclick
= UI
.togglePopupStatusPanel
;
732 //$D("xvpButton").onclick = UI.toggleXvpPanel;
733 $D("clipboardButton").onclick
= UI
.toggleClipboardPanel
;
734 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
735 $D("pveCommandsButton").onclick
= UI
.togglePVECommandPanel
;
736 //$D("connectButton").onclick = UI.toggleConnectPanel;
737 //$D("disconnectButton").onclick = UI.disconnect;
738 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
740 $D("noVNC_clipboard_text").onfocus
= UI
.displayBlur
;
741 $D("noVNC_clipboard_text").onblur
= UI
.displayFocus
;
742 $D("noVNC_clipboard_text").onchange
= UI
.clipSend
;
743 $D("noVNC_clipboard_clear_button").onclick
= UI
.clipClear
;
745 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
746 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
747 //$D("noVNC_apply").onclick = UI.settingsApply;
749 //$D("noVNC_connect_button").onclick = UI.connect;
752 // Read form control compatible setting from cookie
753 getSetting: function(name
) {
754 var val
, ctrl
= $D('noVNC_' + name
);
755 val
= WebUtil
.readSetting(name
);
756 if (val
!== null && ctrl
.type
=== 'checkbox') {
757 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
766 // Update cookie and form control setting. If value is not set, then
767 // updates from control to current cookie setting.
768 updateSetting: function(name
, value
) {
770 var i
, ctrl
= $D('noVNC_' + name
);
771 // Save the cookie for this session
772 if (typeof value
!== 'undefined') {
773 WebUtil
.writeSetting(name
, value
);
776 // Update the settings control
777 value
= UI
.getSetting(name
);
779 if (ctrl
.type
=== 'checkbox') {
780 ctrl
.checked
= value
;
782 } else if (typeof ctrl
.options
!== 'undefined') {
783 for (i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
784 if (ctrl
.options
[i
].value
=== value
) {
785 ctrl
.selectedIndex
= i
;
790 /*Weird IE9 error leads to 'null' appearring
791 in textboxes instead of ''.*/
792 if (value
=== null) {
799 // Save control setting to cookie
800 saveSetting: function(name
) {
801 var val
, ctrl
= $D('noVNC_' + name
);
802 if (ctrl
.type
=== 'checkbox') {
804 } else if (typeof ctrl
.options
!== 'undefined') {
805 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
809 WebUtil
.writeSetting(name
, val
);
810 //Util.Debug("Setting saved '" + name + "=" + val + "'");
814 // Initial page load read/initialization of settings
815 initSetting: function(name
, defVal
) {
818 // Check Query string followed by cookie
819 val
= WebUtil
.getQueryVar(name
);
821 val
= WebUtil
.readSetting(name
, defVal
);
823 UI
.updateSetting(name
, val
);
824 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
828 // Force a setting to be a certain value
829 forceSetting: function(name
, val
) {
830 UI
.updateSetting(name
, val
);
835 // Show the popup status panel
836 togglePopupStatusPanel: function() {
837 var psp
= $D('noVNC_popup_status_panel');
838 if (UI
.popupStatusOpen
=== true) {
839 psp
.style
.display
= "none";
840 UI
.popupStatusOpen
= false;
842 psp
.innerHTML
= $D('noVNC_status').innerHTML
;
843 psp
.style
.display
= "block";
844 psp
.style
.left
= window
.innerWidth
/2 -
845 parseInt(window
.getComputedStyle(psp
, false).width
)/2 -30 + "px";
846 UI
.popupStatusOpen
= true;
850 // Show the XVP panel
851 toggleXvpPanel: function() {
852 // Close the description panel
853 $D('noVNC_description').style
.display
= "none";
854 if (UI
.pveCommandsOpen
=== true) {
855 UI
.togglePVECommandPanel();
857 // Close settings if open
858 if (UI
.settingsOpen
=== true) {
860 UI
.closeSettingsMenu();
862 // Close connection settings if open
863 if (UI
.connSettingsOpen
=== true) {
864 UI
.toggleConnectPanel();
866 // Close popup status panel if open
867 if (UI
.popupStatusOpen
=== true) {
868 UI
.togglePopupStatusPanel();
870 // Close clipboard panel if open
871 if (UI
.clipboardOpen
=== true) {
872 UI
.toggleClipboardPanel();
875 if (UI
.xvpOpen
=== true) {
876 //$D('noVNC_xvp').style.display = "none";
877 //$D('xvpButton').className = "noVNC_status_button";
880 //$D('noVNC_xvp').style.display = "block";
881 //$D('xvpButton').className = "noVNC_status_button_selected";
886 // Show the clipboard panel
887 toggleClipboardPanel: function() {
888 // Close the description panel
889 $D('noVNC_description').style
.display
= "none";
890 if (UI
.pveCommandsOpen
=== true) {
891 UI
.togglePVECommandPanel();
893 if (UI
.sendKeysVisible
=== true) {
894 UI
.togglePVESendKeysPanel();
896 // Close settings if open
897 if (UI
.settingsOpen
=== true) {
899 UI
.closeSettingsMenu();
901 // Close connection settings if open
902 if (UI
.connSettingsOpen
=== true) {
903 UI
.toggleConnectPanel();
905 // Close popup status panel if open
906 if (UI
.popupStatusOpen
=== true) {
907 UI
.togglePopupStatusPanel();
909 // Close XVP panel if open
910 if (UI
.xvpOpen
=== true) {
913 // Toggle Clipboard Panel
914 if (UI
.clipboardOpen
=== true) {
915 $D('noVNC_clipboard').style
.display
= "none";
916 $D('clipboardButton').className
= "noVNC_status_button";
917 UI
.clipboardOpen
= false;
919 $D('noVNC_clipboard').style
.display
= "block";
920 $D('clipboardButton').className
= "noVNC_status_button_selected";
921 UI
.clipboardOpen
= true;
925 // Show the connection settings panel/menu
926 toggleConnectPanel: function() {
927 // Close the description panel
928 $D('noVNC_description').style
.display
= "none";
929 if (UI
.pveCommandsOpen
=== true) {
930 UI
.togglePVECommandPanel();
932 // Close connection settings if open
933 if (UI
.settingsOpen
=== true) {
935 UI
.closeSettingsMenu();
936 //$D('connectButton').className = "noVNC_status_button";
938 // Close clipboard panel if open
939 if (UI
.clipboardOpen
=== true) {
940 UI
.toggleClipboardPanel();
942 // Close popup status panel if open
943 if (UI
.popupStatusOpen
=== true) {
944 UI
.togglePopupStatusPanel();
946 // Close XVP panel if open
947 if (UI
.xvpOpen
=== true) {
951 // Toggle Connection Panel
952 if (UI
.connSettingsOpen
=== true) {
953 $D('noVNC_controls').style
.display
= "none";
954 //$D('connectButton').className = "noVNC_status_button";
955 UI
.connSettingsOpen
= false;
956 UI
.saveSetting('host');
957 UI
.saveSetting('port');
958 //UI.saveSetting('password');
960 $D('noVNC_controls').style
.display
= "block";
961 //$D('connectButton').className = "noVNC_status_button_selected";
962 UI
.connSettingsOpen
= true;
963 $D('noVNC_host').focus();
967 // Toggle the settings menu:
968 // On open, settings are refreshed from saved cookies.
969 // On close, settings are applied
970 toggleSettingsPanel: function() {
971 // Close the description panel
972 $D('noVNC_description').style
.display
= "none";
973 if (UI
.settingsOpen
) {
975 UI
.closeSettingsMenu();
977 UI
.updateSetting('encrypt');
978 UI
.updateSetting('true_color');
979 if (UI
.rfb
.get_display().get_cursor_uri()) {
980 UI
.updateSetting('cursor');
982 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
983 $D('noVNC_cursor').disabled
= true;
985 UI
.updateSetting('clip');
986 UI
.updateSetting('shared');
987 UI
.updateSetting('view_only');
988 UI
.updateSetting('path');
989 UI
.updateSetting('repeaterID');
990 UI
.updateSetting('stylesheet');
991 UI
.updateSetting('logging');
993 UI
.openSettingsMenu();
998 openSettingsMenu: function() {
999 // Close the description panel
1000 $D('noVNC_description').style
.display
= "none";
1001 if (UI
.pveCommandsOpen
=== true) {
1002 UI
.togglePVECommandPanel();
1004 // Close clipboard panel if open
1005 if (UI
.clipboardOpen
=== true) {
1006 UI
.toggleClipboardPanel();
1008 // Close connection settings if open
1009 if (UI
.connSettingsOpen
=== true) {
1010 UI
.toggleConnectPanel();
1012 // Close popup status panel if open
1013 if (UI
.popupStatusOpen
=== true) {
1014 UI
.togglePopupStatusPanel();
1016 // Close XVP panel if open
1017 if (UI
.xvpOpen
=== true) {
1018 UI
.toggleXvpPanel();
1020 $D('noVNC_settings').style
.display
= "block";
1021 //$D('settingsButton').className = "noVNC_status_button_selected";
1022 UI
.settingsOpen
= true;
1025 // Close menu (without applying settings)
1026 closeSettingsMenu: function() {
1027 $D('noVNC_settings').style
.display
= "none";
1028 //$D('settingsButton').className = "noVNC_status_button";
1029 UI
.settingsOpen
= false;
1032 // Save/apply settings when 'Apply' button is pressed
1033 settingsApply: function() {
1034 //Util.Debug(">> settingsApply");
1035 UI
.saveSetting('encrypt');
1036 UI
.saveSetting('true_color');
1037 if (UI
.rfb
.get_display().get_cursor_uri()) {
1038 UI
.saveSetting('cursor');
1040 UI
.saveSetting('clip');
1041 UI
.saveSetting('shared');
1042 UI
.saveSetting('view_only');
1043 UI
.saveSetting('path');
1044 UI
.saveSetting('repeaterID');
1045 UI
.saveSetting('stylesheet');
1046 UI
.saveSetting('logging');
1048 // Settings with immediate (non-connected related) effect
1049 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
1050 WebUtil
.init_logging(UI
.getSetting('logging'));
1052 UI
.setViewDrag(UI
.rfb
.get_viewportDrag());
1053 //Util.Debug("<< settingsApply");
1058 setPassword: function() {
1059 UI
.rfb
.sendPassword($D('noVNC_password').value
);
1060 //Reset connect button.
1061 $D('noVNC_connect_button').value
= "Connect";
1062 $D('noVNC_connect_button').onclick
= UI
.Connect
;
1063 //Hide connection panel.
1064 UI
.toggleConnectPanel();
1068 sendCtrlAltDel: function() {
1069 UI
.rfb
.sendCtrlAltDel();
1072 xvpShutdown: function() {
1073 UI
.rfb
.xvpShutdown();
1076 xvpReboot: function() {
1080 xvpReset: function() {
1084 setMouseButton: function(num
) {
1085 var b
, blist
= [0, 1,2,4], button
;
1087 if (typeof num
=== 'undefined') {
1088 // Disable mouse buttons
1092 UI
.rfb
.get_mouse().set_touchButton(num
);
1095 for (b
= 0; b
< blist
.length
; b
++) {
1096 button
= $D('noVNC_mouse_button' + blist
[b
]);
1097 if (blist
[b
] === num
) {
1098 button
.style
.display
= "";
1100 button
.style
.display
= "none";
1102 button.style.backgroundColor = "black";
1103 button.style.color = "lightgray";
1104 button.style.backgroundColor = "";
1105 button.style.color = "";
1111 updateState: function(rfb
, state
, oldstate
, msg
) {
1112 var s
, sb
, c
, d
, cad
, vd
, klass
;
1113 UI
.rfb_state
= state
;
1117 klass
= "noVNC_status_error";
1120 klass
= "noVNC_status_normal";
1122 case 'disconnected':
1123 $D('noVNC_logo').style
.display
= "block";
1126 klass
= "noVNC_status_normal";
1129 UI
.toggleConnectPanel();
1131 $D('noVNC_connect_button').value
= "Send Password";
1132 $D('noVNC_connect_button').onclick
= UI
.setPassword
;
1133 $D('noVNC_password').focus();
1135 klass
= "noVNC_status_warn";
1138 klass
= "noVNC_status_warn";
1142 if (typeof(msg
) !== 'undefined') {
1143 $D('noVNC-control-bar').setAttribute("class", klass
);
1144 $D('noVNC_status').innerHTML
= msg
;
1147 UI
.updateVisualState();
1150 // Disable/enable controls depending on connection state
1151 updateVisualState: function() {
1152 var connected
= UI
.rfb_state
=== 'normal' ? true : false;
1154 //Util.Debug(">> updateVisualState");
1155 $D('noVNC_encrypt').disabled
= connected
;
1156 $D('noVNC_true_color').disabled
= connected
;
1157 if (UI
.rfb
&& UI
.rfb
.get_display() &&
1158 UI
.rfb
.get_display().get_cursor_uri()) {
1159 $D('noVNC_cursor').disabled
= connected
;
1161 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
1162 $D('noVNC_cursor').disabled
= true;
1164 $D('noVNC_shared').disabled
= connected
;
1165 $D('noVNC_view_only').disabled
= connected
;
1166 $D('noVNC_path').disabled
= connected
;
1167 $D('noVNC_repeaterID').disabled
= connected
;
1171 UI
.setMouseButton(1);
1172 $D('clipboardButton').style
.display
= (UI
.consoletype
!== 'kvm') ? "inline" : "none";
1173 $D('showKeyboard').style
.display
= "inline";
1174 $D('noVNC_extra_keys').style
.display
= "";
1176 UI
.setMouseButton();
1177 $D('clipboardButton').style
.display
= "none";
1178 $D('showKeyboard').style
.display
= "none";
1179 $D('noVNC_extra_keys').style
.display
= "none";
1180 UI
.updateXvpVisualState(0);
1183 // State change disables viewport dragging.
1184 // It is enabled (toggled) by direct click on the button
1185 UI
.setViewDrag(false);
1187 switch (UI
.rfb_state
) {
1191 case 'disconnected':
1192 //$D('connectButton').style.display = "";
1193 //$D('disconnectButton').style.display = "none";
1196 //$D('connectButton').style.display = "none";
1197 //$D('disconnectButton').style.display = "";
1201 //Util.Debug("<< updateVisualState");
1204 // Disable/enable XVP button
1205 updateXvpVisualState: function(ver
) {
1208 //$D('xvpButton').style.display = 'inline';
1210 //$D('xvpButton').style.display = 'none';
1211 // Close XVP panel if open
1212 if (UI
.xvpOpen
=== true) {
1213 UI
.toggleXvpPanel();
1219 // Display the desktop name in the document title
1220 updateDocumentTitle: function(rfb
, name
) {
1221 document
.title
= name
+ " - noVNC";
1225 clipReceive: function(rfb
, text
) {
1226 Util
.Debug(">> UI.clipReceive: " + text
.substr(0,40) + "...");
1227 $D('noVNC_clipboard_text').value
= text
;
1228 Util
.Debug("<< UI.clipReceive");
1232 connect: function() {
1233 var host
, port
, password
, path
;
1235 UI
.closeSettingsMenu();
1236 UI
.toggleConnectPanel();
1238 host
= $D('noVNC_host').value
;
1239 port
= $D('noVNC_port').value
;
1240 password
= $D('noVNC_password').value
;
1241 path
= $D('noVNC_path').value
;
1242 if ((!host
) || (!port
)) {
1243 throw("Must set host and port");
1246 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1247 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1248 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1249 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1250 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1251 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1253 UI
.rfb
.connect(host
, port
, password
, path
);
1256 setTimeout(UI
.setBarPosition
, 100);
1257 $D('noVNC_logo').style
.display
= "none";
1260 disconnect: function() {
1261 UI
.closeSettingsMenu();
1262 UI
.rfb
.disconnect();
1264 $D('noVNC_logo').style
.display
= "block";
1265 UI
.connSettingsOpen
= false;
1266 UI
.toggleConnectPanel();
1269 displayBlur: function() {
1270 UI
.rfb
.get_keyboard().set_focused(false);
1271 UI
.rfb
.get_mouse().set_focused(false);
1274 displayFocus: function() {
1275 UI
.rfb
.get_keyboard().set_focused(true);
1276 UI
.rfb
.get_mouse().set_focused(true);
1279 clipClear: function() {
1280 $D('noVNC_clipboard_text').value
= "";
1281 UI
.rfb
.clipboardPasteFrom("");
1284 clipSend: function() {
1285 var text
= $D('noVNC_clipboard_text').value
;
1286 Util
.Debug(">> UI.clipSend: " + text
.substr(0,40) + "...");
1287 UI
.rfb
.clipboardPasteFrom(text
);
1288 Util
.Debug("<< UI.clipSend");
1292 // Enable/disable and configure viewport clipping
1293 setViewClip: function(clip
) {
1294 var display
, cur_clip
, pos
, new_w
, new_h
;
1297 display
= UI
.rfb
.get_display();
1302 cur_clip
= display
.get_viewport();
1304 if (typeof(clip
) !== 'boolean') {
1305 // Use current setting
1306 clip
= UI
.getSetting('clip');
1309 if (clip
&& !cur_clip
) {
1311 UI
.updateSetting('clip', true);
1312 } else if (!clip
&& cur_clip
) {
1313 // Turn clipping off
1314 UI
.updateSetting('clip', false);
1315 display
.set_viewport(false);
1316 $D('noVNC_canvas').style
.position
= 'static';
1317 display
.viewportChange();
1319 if (UI
.getSetting('clip')) {
1320 // If clipping, update clipping settings
1321 $D('noVNC_canvas').style
.position
= 'absolute';
1322 pos
= Util
.getPosition($D('noVNC_canvas'));
1323 new_w
= window
.innerWidth
- pos
.x
;
1324 new_h
= window
.innerHeight
- pos
.y
;
1325 display
.set_viewport(true);
1326 display
.viewportChange(0, 0, new_w
, new_h
);
1330 // Toggle/set/unset the viewport drag/move button
1331 setViewDrag: function(drag
) {
1332 var vmb
= $D('noVNC_view_drag_button');
1333 if (!UI
.rfb
) { return; }
1335 if (UI
.rfb_state
=== 'normal' &&
1336 UI
.rfb
.get_display().get_viewport()) {
1337 vmb
.style
.display
= "inline";
1339 vmb
.style
.display
= "none";
1342 if (typeof(drag
) === "undefined" ||
1343 typeof(drag
) === "object") {
1344 // If not specified, then toggle
1345 drag
= !UI
.rfb
.get_viewportDrag();
1348 vmb
.className
= "noVNC_status_button_selected";
1349 UI
.rfb
.set_viewportDrag(true);
1351 vmb
.className
= "noVNC_status_button";
1352 UI
.rfb
.set_viewportDrag(false);
1356 // On touch devices, show the OS keyboard
1357 showKeyboard: function() {
1359 kbi
= $D('keyboardinput');
1360 skb
= $D('showKeyboard');
1361 l
= kbi
.value
.length
;
1362 if(UI
.keyboardVisible
=== false) {
1364 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1365 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1366 UI
.keyboardVisible
= true;
1367 skb
.className
= "noVNC_status_button_selected";
1368 } else if(UI
.keyboardVisible
=== true) {
1370 skb
.className
= "noVNC_status_button";
1371 UI
.keyboardVisible
= false;
1375 keepKeyboard: function() {
1376 clearTimeout(UI
.hideKeyboardTimeout
);
1377 if(UI
.keyboardVisible
=== true) {
1378 $D('keyboardinput').focus();
1379 $D('showKeyboard').className
= "noVNC_status_button_selected";
1380 } else if(UI
.keyboardVisible
=== false) {
1381 $D('keyboardinput').blur();
1382 $D('showKeyboard').className
= "noVNC_status_button";
1386 keyboardinputReset: function() {
1387 var kbi
= $D('keyboardinput');
1388 kbi
.value
= Array(UI
.defaultKeyboardinputLen
).join("_");
1389 UI
.lastKeyboardinput
= kbi
.value
;
1392 // When normal keyboard events are left uncought, use the input events from
1393 // the keyboardinput element instead and generate the corresponding key events.
1394 // This code is required since some browsers on Android are inconsistent in
1395 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1396 keyInput: function(event
) {
1397 var newValue
, oldValue
, newLen
, oldLen
;
1398 newValue
= event
.target
.value
;
1399 oldValue
= UI
.lastKeyboardinput
;
1402 // Try to check caret position since whitespace at the end
1403 // will not be considered by value.length in some browsers
1404 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1406 // selectionStart is undefined in Google Chrome
1407 newLen
= newValue
.length
;
1409 oldLen
= oldValue
.length
;
1412 var inputs
= newLen
- oldLen
;
1414 backspaces
= -inputs
;
1418 // Compare the old string with the new to account for
1419 // text-corrections or other input that modify existing text
1420 for (var i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1421 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1422 inputs
= newLen
- i
;
1423 backspaces
= oldLen
- i
;
1428 // Send the key events
1429 for (var i
= 0; i
< backspaces
; i
++)
1430 UI
.rfb
.sendKey(XK_BackSpace
);
1431 for (var i
= newLen
- inputs
; i
< newLen
; i
++)
1432 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1434 // Control the text content length in the keyboardinput element
1435 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1436 UI
.keyboardinputReset();
1437 } else if (newLen
< 1) {
1438 // There always have to be some text in the keyboardinput
1439 // element with which backspace can interact.
1440 UI
.keyboardinputReset();
1441 // This sometimes causes the keyboard to disappear for a second
1442 // but it is required for the android keyboard to recognize that
1443 // text has been added to the field
1444 event
.target
.blur();
1445 // This has to be ran outside of the input handler in order to work
1446 setTimeout(function() { UI
.keepKeyboard(); }, 0);
1449 UI
.lastKeyboardinput
= newValue
;
1453 keyInputBlur: function() {
1454 $D('showKeyboard').className
= "noVNC_status_button";
1455 //Weird bug in iOS if you change keyboardVisible
1456 //here it does not actually occur so next time
1457 //you click keyboard icon it doesnt work.
1458 UI
.hideKeyboardTimeout
= setTimeout(function() { UI
.setKeyboard(); },100);
1461 showExtraKeys: function() {
1463 if(UI
.extraKeysVisible
=== false) {
1464 $D('toggleCtrlButton').style
.display
= "inline";
1465 $D('toggleAltButton').style
.display
= "inline";
1466 $D('sendTabButton').style
.display
= "inline";
1467 $D('sendEscButton').style
.display
= "inline";
1468 $D('sendCtrlAltDelButton').style
.display
= (UI
.consoletype
=== 'kvm') ? "inline" : "none";
1469 $D('showExtraKeysButton').className
= "noVNC_status_button_selected";
1470 UI
.extraKeysVisible
= true;
1471 } else if(UI
.extraKeysVisible
=== true) {
1472 $D('toggleCtrlButton').style
.display
= "";
1473 $D('toggleAltButton').style
.display
= "";
1474 $D('sendTabButton').style
.display
= "";
1475 $D('sendEscButton').style
.display
= "";
1476 $D('sendCtrlAltDelButton').style
.display
= "";
1477 $D('showExtraKeysButton').className
= "noVNC_status_button";
1478 UI
.extraKeysVisible
= false;
1482 toggleCtrl: function() {
1484 if(UI
.ctrlOn
=== false) {
1485 UI
.rfb
.sendKey(XK_Control_L
, true);
1486 $D('toggleCtrlButton').className
= "noVNC_status_button_selected";
1488 } else if(UI
.ctrlOn
=== true) {
1489 UI
.rfb
.sendKey(XK_Control_L
, false);
1490 $D('toggleCtrlButton').className
= "noVNC_status_button";
1495 toggleAlt: function() {
1497 if(UI
.altOn
=== false) {
1498 UI
.rfb
.sendKey(XK_Alt_L
, true);
1499 $D('toggleAltButton').className
= "noVNC_status_button_selected";
1501 } else if(UI
.altOn
=== true) {
1502 UI
.rfb
.sendKey(XK_Alt_L
, false);
1503 $D('toggleAltButton').className
= "noVNC_status_button";
1508 sendTab: function() {
1510 UI
.rfb
.sendKey(XK_Tab
);
1513 sendEsc: function() {
1515 UI
.rfb
.sendKey(XK_Escape
);
1518 setKeyboard: function() {
1519 UI
.keyboardVisible
= false;
1522 // iOS < Version 5 does not support position fixed. Javascript workaround:
1523 setOnscroll: function() {
1524 window
.onscroll = function() {
1525 UI
.setBarPosition();
1529 setResize: function () {
1530 window
.onResize = function() {
1531 UI
.setBarPosition();
1535 //Helper to add options to dropdown.
1536 addOption: function(selectbox
,text
,value
)
1538 var optn
= document
.createElement("OPTION");
1541 selectbox
.options
.add(optn
);
1544 setBarPosition: function() {
1545 $D('noVNC-control-bar').style
.top
= (window
.pageYOffset
) + 'px';
1546 $D('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1548 var vncwidth
= $D('noVNC_screen').style
.offsetWidth
;
1549 $D('noVNC-control-bar').style
.width
= vncwidth
+ 'px';