]>
git.proxmox.com Git - mirror_novnc.git/blob - app/ui.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2016 Samuel Mannehed for Cendio AB
5 * Copyright (C) 2016 Pierre Ossman for Cendio AB
6 * Licensed under MPL 2.0 (see LICENSE.txt)
8 * See README.md for usage and integration instructions.
11 /* jslint white: false, browser: true */
12 /* global window, document.getElementById, Util, WebUtil, RFB, Display */
15 * import Util from "../core/util";
16 * import KeyTable from "../core/input/keysym";
17 * import RFB from "../core/rfb";
18 * import Display from "../core/display";
19 * import WebUtil from "./webutil";
27 /* [begin skip-as-module] */
28 // Load supporting scripts
30 {'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
31 "input/xtscancodes.js", "input/util.js", "input/devices.js",
32 "display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
34 window
.onscriptsload = function () { UI
.load(); };
35 /* [end skip-as-module] */
43 hideKeyboardTimeout
: null,
45 keyboardVisible
: false,
49 rememberedClipSetting
: null,
50 lastKeyboardinput
: null,
51 defaultKeyboardinputLen
: 100,
56 // Setup rfb object, load settings from browser storage, then call
57 // UI.init to setup the UI/menus
58 load: function(callback
) {
59 WebUtil
.initSettings(UI
.start
, callback
);
62 // Render default UI and initialize settings menu
63 start: function(callback
) {
65 // Setup global variables first
66 UI
.isTouchDevice
= 'ontouchstart' in document
.documentElement
;
67 UI
.isSafari
= (navigator
.userAgent
.indexOf('Safari') !== -1 &&
68 navigator
.userAgent
.indexOf('Chrome') === -1);
72 // Show mouse selector buttons on touch screen devices
73 if (UI
.isTouchDevice
) {
74 // Show mobile buttons
75 document
.getElementById('noVNC_mobile_buttons')
76 .classList
.remove("noVNC_hidden");
78 // Remove the address bar
79 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
80 UI
.forceSetting('clip', true);
82 UI
.initSetting('clip', false);
85 // Setup and initialize event handlers
86 UI
.setupWindowEvents();
88 UI
.addControlbarHandlers();
89 UI
.addTouchSpecificHandlers();
91 UI
.addConnectionControlHandlers();
92 UI
.addClipboardHandlers();
93 UI
.addSettingsHandlers();
95 // Show the connect panel on first load unless autoconnecting
97 UI
.openConnectPanel();
103 UI
.updateVisualState();
105 document
.getElementById('noVNC_setting_host').focus();
107 var autoconnect
= WebUtil
.getConfigVar('autoconnect', false);
108 if (autoconnect
=== 'true' || autoconnect
== '1') {
115 if (typeof callback
=== "function") {
120 initSettings: function() {
121 // Stylesheet selection dropdown
122 var sheet
= WebUtil
.selectStylesheet();
123 var sheets
= WebUtil
.getStylesheets();
125 for (i
= 0; i
< sheets
.length
; i
+= 1) {
126 UI
.addOption(document
.getElementById('noVNC_setting_stylesheet'),sheets
[i
].title
, sheets
[i
].title
);
129 // Logging selection dropdown
130 var llevels
= ['error', 'warn', 'info', 'debug'];
131 for (i
= 0; i
< llevels
.length
; i
+= 1) {
132 UI
.addOption(document
.getElementById('noVNC_setting_logging'),llevels
[i
], llevels
[i
]);
135 // Settings with immediate effects
136 UI
.initSetting('logging', 'warn');
137 WebUtil
.init_logging(UI
.getSetting('logging'));
139 UI
.initSetting('stylesheet', 'default');
140 WebUtil
.selectStylesheet(null);
141 // call twice to get around webkit bug
142 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
144 // if port == 80 (or 443) then it won't be present and should be
146 var port
= window
.location
.port
;
148 if (window
.location
.protocol
.substring(0,5) == 'https') {
151 else if (window
.location
.protocol
.substring(0,4) == 'http') {
156 /* Populate the controls if defaults are provided in the URL */
157 UI
.initSetting('host', window
.location
.hostname
);
158 UI
.initSetting('port', port
);
159 UI
.initSetting('password', '');
160 UI
.initSetting('encrypt', (window
.location
.protocol
=== "https:"));
161 UI
.initSetting('true_color', true);
162 UI
.initSetting('cursor', !UI
.isTouchDevice
);
163 UI
.initSetting('resize', 'off');
164 UI
.initSetting('shared', true);
165 UI
.initSetting('view_only', false);
166 UI
.initSetting('path', 'websockify');
167 UI
.initSetting('repeaterID', '');
168 UI
.initSetting('token', '');
171 setupWindowEvents: function() {
172 window
.addEventListener( 'resize', function () {
173 UI
.applyResizeMode();
179 document
.getElementById("noVNC_status")
180 .addEventListener('click', UI
.hideStatus
);
183 setupFullscreen: function() {
184 // Only show the button if fullscreen is properly supported
185 // * Safari doesn't support alphanumerical input while in fullscreen
187 (document
.documentElement
.requestFullscreen
||
188 document
.documentElement
.mozRequestFullScreen
||
189 document
.documentElement
.webkitRequestFullscreen
||
190 document
.body
.msRequestFullscreen
)) {
191 document
.getElementById('noVNC_fullscreen_button')
192 .classList
.remove("noVNC_hidden");
193 UI
.addFullscreenHandlers();
197 addControlbarHandlers: function() {
198 document
.getElementById("noVNC_view_drag_button")
199 .addEventListener('click', UI
.toggleViewDrag
);
200 document
.getElementById("noVNC_send_ctrl_alt_del_button")
201 .addEventListener('click', UI
.sendCtrlAltDel
);
204 addTouchSpecificHandlers: function() {
205 document
.getElementById("noVNC_mouse_button0")
206 .addEventListener('click', function () { UI
.setMouseButton(1); });
207 document
.getElementById("noVNC_mouse_button1")
208 .addEventListener('click', function () { UI
.setMouseButton(2); });
209 document
.getElementById("noVNC_mouse_button2")
210 .addEventListener('click', function () { UI
.setMouseButton(4); });
211 document
.getElementById("noVNC_mouse_button4")
212 .addEventListener('click', function () { UI
.setMouseButton(0); });
213 document
.getElementById("noVNC_keyboard_button")
214 .addEventListener('click', UI
.showKeyboard
);
216 document
.getElementById("noVNC_keyboardinput")
217 .addEventListener('input', UI
.keyInput
);
218 document
.getElementById("noVNC_keyboardinput")
219 .addEventListener('blur', UI
.hideKeyboard
);
220 document
.getElementById("noVNC_keyboardinput")
221 .addEventListener('submit', function () { return false; });
223 window
.addEventListener('load', UI
.keyboardinputReset
);
225 document
.getElementById("noVNC_toggle_extra_keys_button")
226 .addEventListener('click', UI
.toggleExtraKeys
);
227 document
.getElementById("noVNC_toggle_ctrl_button")
228 .addEventListener('click', UI
.toggleCtrl
);
229 document
.getElementById("noVNC_toggle_alt_button")
230 .addEventListener('click', UI
.toggleAlt
);
231 document
.getElementById("noVNC_send_tab_button")
232 .addEventListener('click', UI
.sendTab
);
233 document
.getElementById("noVNC_send_esc_button")
234 .addEventListener('click', UI
.sendEsc
);
237 addXvpHandlers: function() {
238 document
.getElementById("noVNC_xvp_shutdown_button")
239 .addEventListener('click', function() { UI
.rfb
.xvpShutdown(); });
240 document
.getElementById("noVNC_xvp_reboot_button")
241 .addEventListener('click', function() { UI
.rfb
.xvpReboot(); });
242 document
.getElementById("noVNC_xvp_reset_button")
243 .addEventListener('click', function() { UI
.rfb
.xvpReset(); });
244 document
.getElementById("noVNC_xvp_button")
245 .addEventListener('click', UI
.toggleXvpPanel
);
248 addConnectionControlHandlers: function() {
249 document
.getElementById("noVNC_connect_controls_button")
250 .addEventListener('click', UI
.toggleConnectPanel
);
251 document
.getElementById("noVNC_disconnect_button")
252 .addEventListener('click', UI
.disconnect
);
253 document
.getElementById("noVNC_connect_button")
254 .addEventListener('click', UI
.connect
);
257 addClipboardHandlers: function() {
258 document
.getElementById("noVNC_clipboard_button")
259 .addEventListener('click', UI
.toggleClipboardPanel
);
260 document
.getElementById("noVNC_clipboard_text")
261 .addEventListener('focus', UI
.displayBlur
);
262 document
.getElementById("noVNC_clipboard_text")
263 .addEventListener('blur', UI
.displayFocus
);
264 document
.getElementById("noVNC_clipboard_text")
265 .addEventListener('change', UI
.clipboardSend
);
266 document
.getElementById("noVNC_clipboard_clear_button")
267 .addEventListener('click', UI
.clipboardClear
);
270 addSettingsHandlers: function() {
271 document
.getElementById("noVNC_settings_button")
272 .addEventListener('click', UI
.toggleSettingsPanel
);
273 document
.getElementById("noVNC_settings_apply")
274 .addEventListener('click', UI
.settingsApply
);
276 document
.getElementById("noVNC_setting_resize")
277 .addEventListener('change', UI
.enableDisableViewClip
);
280 addFullscreenHandlers: function() {
281 document
.getElementById("noVNC_fullscreen_button")
282 .addEventListener('click', UI
.toggleFullscreen
);
284 window
.addEventListener('fullscreenchange', UI
.updateFullscreenButton
);
285 window
.addEventListener('mozfullscreenchange', UI
.updateFullscreenButton
);
286 window
.addEventListener('webkitfullscreenchange', UI
.updateFullscreenButton
);
287 window
.addEventListener('msfullscreenchange', UI
.updateFullscreenButton
);
290 initRFB: function() {
292 UI
.rfb
= new RFB({'target': document
.getElementById('noVNC_canvas'),
293 'onUpdateState': UI
.updateState
,
294 'onXvpInit': UI
.updateXvpButton
,
295 'onClipboard': UI
.clipboardReceive
,
296 'onFBUComplete': UI
.initialResize
,
297 'onFBResize': UI
.updateViewDrag
,
298 'onDesktopName': UI
.updateDocumentTitle
});
301 UI
.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc
);
312 updateState: function(rfb
, state
, oldstate
, msg
) {
313 UI
.rfb_state
= state
;
319 klass
= "noVNC_status_error";
320 timeout
= 0; // zero means no timeout
323 klass
= "noVNC_status_normal";
328 klass
= "noVNC_status_normal";
331 UI
.toggleConnectPanel();
333 document
.getElementById('noVNC_connect_button').value
= "Send Password";
334 document
.getElementById('noVNC_connect_button').onclick
= UI
.setPassword
;
335 document
.getElementById('noVNC_setting_password').focus();
337 klass
= "noVNC_status_warn";
340 klass
= "noVNC_status_warn";
344 if (typeof(msg
) !== 'undefined') {
345 document
.getElementById('noVNC_status')
346 .classList
.remove("noVNC_status_normal",
348 "noVNC_status_error");
349 document
.getElementById('noVNC_status').classList
.add(klass
);
350 UI
.showStatus(msg
, timeout
);
353 UI
.updateVisualState();
356 // Disable/enable controls depending on connection state
357 updateVisualState: function() {
358 var connected
= UI
.rfb
&& UI
.rfb_state
=== 'normal';
360 //Util.Debug(">> updateVisualState");
361 document
.getElementById('noVNC_setting_encrypt').disabled
= connected
;
362 document
.getElementById('noVNC_setting_true_color').disabled
= connected
;
363 if (Util
.browserSupportsCursorURIs()) {
364 document
.getElementById('noVNC_setting_cursor').disabled
= connected
;
366 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
367 document
.getElementById('noVNC_setting_cursor').disabled
= true;
370 UI
.enableDisableViewClip();
371 document
.getElementById('noVNC_setting_resize').disabled
= connected
;
372 document
.getElementById('noVNC_setting_shared').disabled
= connected
;
373 document
.getElementById('noVNC_setting_view_only').disabled
= connected
;
374 document
.getElementById('noVNC_setting_path').disabled
= connected
;
375 document
.getElementById('noVNC_setting_repeaterID').disabled
= connected
;
378 document
.getElementById('noVNC_logo')
379 .classList
.add("noVNC_hidden");
380 document
.getElementById('noVNC_screen')
381 .classList
.remove("noVNC_hidden");
383 UI
.setMouseButton(1);
384 document
.getElementById('noVNC_clipboard_button')
385 .classList
.remove("noVNC_hidden");
386 document
.getElementById('noVNC_keyboard_button')
387 .classList
.remove("noVNC_hidden");
388 document
.getElementById('noVNC_extra_keys')
389 .classList
.remove("noVNC_hidden");
390 document
.getElementById('noVNC_send_ctrl_alt_del_button')
391 .classList
.remove("noVNC_hidden");
393 document
.getElementById('noVNC_logo')
394 .classList
.remove("noVNC_hidden");
395 document
.getElementById('noVNC_screen')
396 .classList
.add("noVNC_hidden");
397 UI
.hideMouseButton();
398 document
.getElementById('noVNC_clipboard_button')
399 .classList
.add("noVNC_hidden");
400 document
.getElementById('noVNC_keyboard_button')
401 .classList
.add("noVNC_hidden");
402 document
.getElementById('noVNC_extra_keys')
403 .classList
.add("noVNC_hidden");
404 document
.getElementById('noVNC_send_ctrl_alt_del_button')
405 .classList
.add("noVNC_hidden");
406 UI
.updateXvpButton(0);
409 // State change disables viewport dragging.
410 // It is enabled (toggled) by direct click on the button
411 UI
.setViewDrag(false);
413 switch (UI
.rfb_state
) {
417 document
.getElementById('noVNC_connect_controls_button')
418 .classList
.remove("noVNC_hidden");
419 document
.getElementById('noVNC_disconnect_button')
420 .classList
.add("noVNC_hidden");
421 UI
.openConnectPanel();
424 document
.getElementById('noVNC_connect_controls_button')
425 .classList
.remove("noVNC_hidden");
426 document
.getElementById('noVNC_disconnect_button')
427 .classList
.add("noVNC_hidden");
430 document
.getElementById('noVNC_connect_controls_button')
431 .classList
.add("noVNC_hidden");
432 document
.getElementById('noVNC_disconnect_button')
433 .classList
.remove("noVNC_hidden");
437 //Util.Debug("<< updateVisualState");
440 showStatus: function(text
, time
) {
441 var statusElem
= document
.getElementById('noVNC_status');
443 clearTimeout(UI
.statusTimeout
);
445 statusElem
.innerHTML
= text
;
446 statusElem
.classList
.add("noVNC_open");
448 // If no time was specified, show the status for 1.5 seconds
449 if (typeof time
=== 'undefined') {
453 // A specified time of zero means no timeout
455 UI
.statusTimeout
= window
.setTimeout(UI
.hideStatus
, time
);
459 hideStatus: function() {
460 clearTimeout(UI
.statusTimeout
);
461 document
.getElementById('noVNC_status').classList
.remove("noVNC_open");
470 // Initial page load read/initialization of settings
471 initSetting: function(name
, defVal
) {
472 // Check Query string followed by cookie
473 var val
= WebUtil
.getConfigVar(name
);
475 val
= WebUtil
.readSetting(name
, defVal
);
477 UI
.updateSetting(name
, val
);
481 // Update cookie and form control setting. If value is not set, then
482 // updates from control to current cookie setting.
483 updateSetting: function(name
, value
) {
485 // Save the cookie for this session
486 if (typeof value
!== 'undefined') {
487 WebUtil
.writeSetting(name
, value
);
490 // Update the settings control
491 value
= UI
.getSetting(name
);
493 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
494 if (ctrl
.type
=== 'checkbox') {
495 ctrl
.checked
= value
;
497 } else if (typeof ctrl
.options
!== 'undefined') {
498 for (var i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
499 if (ctrl
.options
[i
].value
=== value
) {
500 ctrl
.selectedIndex
= i
;
505 /*Weird IE9 error leads to 'null' appearring
506 in textboxes instead of ''.*/
507 if (value
=== null) {
514 // Save control setting to cookie
515 saveSetting: function(name
) {
516 var val
, ctrl
= document
.getElementById('noVNC_setting_' + name
);
517 if (ctrl
.type
=== 'checkbox') {
519 } else if (typeof ctrl
.options
!== 'undefined') {
520 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
524 WebUtil
.writeSetting(name
, val
);
525 //Util.Debug("Setting saved '" + name + "=" + val + "'");
529 // Force a setting to be a certain value
530 forceSetting: function(name
, val
) {
531 UI
.updateSetting(name
, val
);
535 // Read form control compatible setting from cookie
536 getSetting: function(name
) {
537 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
538 var val
= WebUtil
.readSetting(name
);
539 if (typeof val
!== 'undefined' && val
!== null && ctrl
.type
=== 'checkbox') {
540 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
549 // Save/apply settings when 'Apply' button is pressed
550 settingsApply: function() {
551 //Util.Debug(">> settingsApply");
552 UI
.saveSetting('encrypt');
553 UI
.saveSetting('true_color');
554 if (Util
.browserSupportsCursorURIs()) {
555 UI
.saveSetting('cursor');
558 UI
.saveSetting('resize');
560 if (UI
.getSetting('resize') === 'downscale' || UI
.getSetting('resize') === 'scale') {
561 UI
.forceSetting('clip', false);
564 UI
.saveSetting('clip');
565 UI
.saveSetting('shared');
566 UI
.saveSetting('view_only');
567 UI
.saveSetting('path');
568 UI
.saveSetting('repeaterID');
569 UI
.saveSetting('stylesheet');
570 UI
.saveSetting('logging');
572 // Settings with immediate (non-connected related) effect
573 WebUtil
.selectStylesheet(UI
.getSetting('stylesheet'));
574 WebUtil
.init_logging(UI
.getSetting('logging'));
577 //Util.Debug("<< settingsApply");
586 closeAllPanels: function() {
587 UI
.closeSettingsPanel();
589 UI
.closeClipboardPanel();
590 UI
.closeConnectPanel();
599 openSettingsPanel: function() {
602 UI
.updateSetting('encrypt');
603 UI
.updateSetting('true_color');
604 if (Util
.browserSupportsCursorURIs()) {
605 UI
.updateSetting('cursor');
607 UI
.updateSetting('cursor', !UI
.isTouchDevice
);
608 document
.getElementById('noVNC_setting_cursor').disabled
= true;
610 UI
.updateSetting('clip');
611 UI
.updateSetting('resize');
612 UI
.updateSetting('shared');
613 UI
.updateSetting('view_only');
614 UI
.updateSetting('path');
615 UI
.updateSetting('repeaterID');
616 UI
.updateSetting('stylesheet');
617 UI
.updateSetting('logging');
619 document
.getElementById('noVNC_settings')
620 .classList
.add("noVNC_open");
621 document
.getElementById('noVNC_settings_button')
622 .classList
.add("noVNC_selected");
625 closeSettingsPanel: function() {
626 document
.getElementById('noVNC_settings')
627 .classList
.remove("noVNC_open");
628 document
.getElementById('noVNC_settings_button')
629 .classList
.remove("noVNC_selected");
632 // Toggle the settings menu:
633 // On open, settings are refreshed from saved cookies.
634 // On close, settings are applied
635 toggleSettingsPanel: function() {
636 if (document
.getElementById('noVNC_settings')
637 .classList
.contains("noVNC_open")) {
639 UI
.closeSettingsPanel();
641 UI
.openSettingsPanel();
651 openXvpPanel: function() {
654 document
.getElementById('noVNC_xvp')
655 .classList
.add("noVNC_open");
656 document
.getElementById('noVNC_xvp_button')
657 .classList
.add("noVNC_selected");
660 closeXvpPanel: function() {
661 document
.getElementById('noVNC_xvp')
662 .classList
.remove("noVNC_open");
663 document
.getElementById('noVNC_xvp_button')
664 .classList
.remove("noVNC_selected");
667 toggleXvpPanel: function() {
668 if (document
.getElementById('noVNC_xvp')
669 .classList
.contains("noVNC_open")) {
676 // Disable/enable XVP button
677 updateXvpButton: function(ver
) {
679 document
.getElementById('noVNC_xvp_button')
680 .classList
.remove("noVNC_hidden");
682 document
.getElementById('noVNC_xvp_button')
683 .classList
.add("noVNC_hidden");
684 // Close XVP panel if open
695 openClipboardPanel: function() {
698 document
.getElementById('noVNC_clipboard')
699 .classList
.add("noVNC_open");
700 document
.getElementById('noVNC_clipboard_button')
701 .classList
.add("noVNC_selected");
704 closeClipboardPanel: function() {
705 document
.getElementById('noVNC_clipboard')
706 .classList
.remove("noVNC_open");
707 document
.getElementById('noVNC_clipboard_button')
708 .classList
.remove("noVNC_selected");
711 toggleClipboardPanel: function() {
712 if (document
.getElementById('noVNC_clipboard')
713 .classList
.contains("noVNC_open")) {
714 UI
.closeClipboardPanel();
716 UI
.openClipboardPanel();
720 clipboardReceive: function(rfb
, text
) {
721 Util
.Debug(">> UI.clipboardReceive: " + text
.substr(0,40) + "...");
722 document
.getElementById('noVNC_clipboard_text').value
= text
;
723 Util
.Debug("<< UI.clipboardReceive");
726 clipboardClear: function() {
727 document
.getElementById('noVNC_clipboard_text').value
= "";
728 UI
.rfb
.clipboardPasteFrom("");
731 clipboardSend: function() {
732 var text
= document
.getElementById('noVNC_clipboard_text').value
;
733 Util
.Debug(">> UI.clipboardSend: " + text
.substr(0,40) + "...");
734 UI
.rfb
.clipboardPasteFrom(text
);
735 Util
.Debug("<< UI.clipboardSend");
744 openConnectPanel: function() {
747 document
.getElementById('noVNC_connect_controls')
748 .classList
.add("noVNC_open");
749 document
.getElementById('noVNC_connect_controls_button')
750 .classList
.add("noVNC_selected");
752 document
.getElementById('noVNC_setting_host').focus();
755 closeConnectPanel: function() {
756 document
.getElementById('noVNC_connect_controls')
757 .classList
.remove("noVNC_open");
758 document
.getElementById('noVNC_connect_controls_button')
759 .classList
.remove("noVNC_selected");
761 UI
.saveSetting('host');
762 UI
.saveSetting('port');
763 UI
.saveSetting('token');
764 //UI.saveSetting('password');
767 toggleConnectPanel: function() {
768 if (document
.getElementById('noVNC_connect_controls')
769 .classList
.contains("noVNC_open")) {
770 UI
.closeConnectPanel();
772 UI
.openConnectPanel();
776 connect: function() {
779 var host
= document
.getElementById('noVNC_setting_host').value
;
780 var port
= document
.getElementById('noVNC_setting_port').value
;
781 var password
= document
.getElementById('noVNC_setting_password').value
;
782 var token
= document
.getElementById('noVNC_setting_token').value
;
783 var path
= document
.getElementById('noVNC_setting_path').value
;
785 //if token is in path then ignore the new token variable
787 path
= WebUtil
.injectParamIfMissing(path
, "token", token
);
790 if ((!host
) || (!port
)) {
791 throw new Error("Must set host and port");
794 if (!UI
.initRFB()) return;
796 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
797 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
798 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
799 UI
.rfb
.set_shared(UI
.getSetting('shared'));
800 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
801 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
803 UI
.rfb
.connect(host
, port
, password
, path
);
806 setTimeout(UI
.setBarPosition
, 100);
809 disconnect: function() {
813 // Restore the callback used for initial resize
814 UI
.rfb
.set_onFBUComplete(UI
.initialResize
);
816 // Don't display the connection settings until we're actually disconnected
819 setPassword: function() {
820 UI
.rfb
.sendPassword(document
.getElementById('noVNC_setting_password').value
);
821 //Reset connect button.
822 document
.getElementById('noVNC_connect_button').value
= "Connect";
823 document
.getElementById('noVNC_connect_button').onclick
= UI
.connect
;
824 //Hide connection panel.
825 UI
.toggleConnectPanel();
835 toggleFullscreen: function() {
836 if (document
.fullscreenElement
|| // alternative standard method
837 document
.mozFullScreenElement
|| // currently working methods
838 document
.webkitFullscreenElement
||
839 document
.msFullscreenElement
) {
840 if (document
.exitFullscreen
) {
841 document
.exitFullscreen();
842 } else if (document
.mozCancelFullScreen
) {
843 document
.mozCancelFullScreen();
844 } else if (document
.webkitExitFullscreen
) {
845 document
.webkitExitFullscreen();
846 } else if (document
.msExitFullscreen
) {
847 document
.msExitFullscreen();
850 if (document
.documentElement
.requestFullscreen
) {
851 document
.documentElement
.requestFullscreen();
852 } else if (document
.documentElement
.mozRequestFullScreen
) {
853 document
.documentElement
.mozRequestFullScreen();
854 } else if (document
.documentElement
.webkitRequestFullscreen
) {
855 document
.documentElement
.webkitRequestFullscreen(Element
.ALLOW_KEYBOARD_INPUT
);
856 } else if (document
.body
.msRequestFullscreen
) {
857 document
.body
.msRequestFullscreen();
860 UI
.enableDisableViewClip();
861 UI
.updateFullscreenButton();
864 updateFullscreenButton: function() {
865 if (document
.fullscreenElement
|| // alternative standard method
866 document
.mozFullScreenElement
|| // currently working methods
867 document
.webkitFullscreenElement
||
868 document
.msFullscreenElement
) {
869 document
.getElementById('noVNC_fullscreen_button')
870 .classList
.add("noVNC_selected");
872 document
.getElementById('noVNC_fullscreen_button')
873 .classList
.remove("noVNC_selected");
883 // Apply remote resizing or local scaling
884 applyResizeMode: function() {
887 var screen
= UI
.screenSize();
889 if (screen
&& UI
.rfb_state
=== 'normal' && UI
.rfb
.get_display()) {
891 var display
= UI
.rfb
.get_display();
892 var resizeMode
= UI
.getSetting('resize');
894 if (resizeMode
=== 'remote') {
896 // Request changing the resolution of the remote display to
897 // the size of the local browser viewport.
899 // In order to not send multiple requests before the browser-resize
900 // is finished we wait 0.5 seconds before sending the request.
901 clearTimeout(UI
.resizeTimeout
);
902 UI
.resizeTimeout
= setTimeout(function(){
904 // Limit the viewport to the size of the browser window
905 display
.set_maxWidth(screen
.w
);
906 display
.set_maxHeight(screen
.h
);
908 Util
.Debug('Attempting requestDesktopSize(' +
909 screen
.w
+ ', ' + screen
.h
+ ')');
911 // Request a remote size covering the viewport
912 UI
.rfb
.requestDesktopSize(screen
.w
, screen
.h
);
915 } else if (resizeMode
=== 'scale' || resizeMode
=== 'downscale') {
916 var downscaleOnly
= resizeMode
=== 'downscale';
917 var scaleRatio
= display
.autoscale(screen
.w
, screen
.h
, downscaleOnly
);
918 UI
.rfb
.get_mouse().set_scale(scaleRatio
);
919 Util
.Debug('Scaling by ' + UI
.rfb
.get_mouse().get_scale());
924 // The screen is always the same size as the available viewport
925 // in the browser window minus the height of the control bar
926 screenSize: function() {
927 var screen
= document
.getElementById('noVNC_screen');
929 // Hide the scrollbars until the size is calculated
930 screen
.style
.overflow
= "hidden";
932 var pos
= Util
.getPosition(screen
);
936 screen
.style
.overflow
= "visible";
938 if (isNaN(w
) || isNaN(h
)) {
945 // Normally we only apply the current resize mode after a window resize
946 // event. This means that when a new connection is opened, there is no
947 // resize mode active.
948 // We have to wait until the first FBU because this is where the client
949 // will find the supported encodings of the server. Some calls later in
950 // the chain is dependant on knowing the server-capabilities.
951 initialResize: function(rfb
, fbu
) {
952 UI
.applyResizeMode();
953 // After doing this once, we remove the callback.
954 UI
.rfb
.set_onFBUComplete(function() { });
963 // Set and configure viewport clipping
964 setViewClip: function(clip
) {
965 UI
.updateSetting('clip', clip
);
969 // Update parameters that depend on the clip setting
970 updateViewClip: function() {
976 var display
= UI
.rfb
.get_display();
977 var cur_clip
= display
.get_viewport();
978 var new_clip
= UI
.getSetting('clip');
980 if (cur_clip
!== new_clip
) {
981 display
.set_viewport(new_clip
);
984 var size
= UI
.screenSize();
986 if (new_clip
&& size
) {
987 // When clipping is enabled, the screen is limited to
988 // the size of the browser window.
989 display
.set_maxWidth(size
.w
);
990 display
.set_maxHeight(size
.h
);
992 var screen
= document
.getElementById('noVNC_screen');
993 var canvas
= document
.getElementById('noVNC_canvas');
995 // Hide potential scrollbars that can skew the position
996 screen
.style
.overflow
= "hidden";
998 // The x position marks the left margin of the canvas,
999 // remove the margin from both sides to keep it centered.
1000 var new_w
= size
.w
- (2 * Util
.getPosition(canvas
).x
);
1002 screen
.style
.overflow
= "visible";
1004 display
.viewportChangeSize(new_w
, size
.h
);
1006 // Disable max dimensions
1007 display
.set_maxWidth(0);
1008 display
.set_maxHeight(0);
1009 display
.viewportChangeSize();
1013 // Handle special cases where clipping is forced on/off or locked
1014 enableDisableViewClip: function() {
1015 var resizeSetting
= document
.getElementById('noVNC_setting_resize');
1016 var connected
= UI
.rfb
&& UI
.rfb_state
=== 'normal';
1019 // Safari auto-hides the scrollbars which makes them
1020 // impossible to use in most cases
1021 UI
.setViewClip(true);
1022 document
.getElementById('noVNC_setting_clip').disabled
= true;
1023 } else if (resizeSetting
.value
=== 'downscale' || resizeSetting
.value
=== 'scale') {
1024 // Disable clipping if we are scaling
1025 UI
.setViewClip(false);
1026 document
.getElementById('noVNC_setting_clip').disabled
= true;
1027 } else if (document
.msFullscreenElement
) {
1028 // The browser is IE and we are in fullscreen mode.
1029 // - We need to force clipping while in fullscreen since
1030 // scrollbars doesn't work.
1031 UI
.showStatus("Forcing clipping mode since scrollbars aren't supported by IE in fullscreen");
1032 UI
.rememberedClipSetting
= UI
.getSetting('clip');
1033 UI
.setViewClip(true);
1034 document
.getElementById('noVNC_setting_clip').disabled
= true;
1035 } else if (document
.body
.msRequestFullscreen
&& UI
.rememberedClip
!== null) {
1036 // Restore view clip to what it was before fullscreen on IE
1037 UI
.setViewClip(UI
.rememberedClipSetting
);
1038 document
.getElementById('noVNC_setting_clip').disabled
= connected
|| UI
.isTouchDevice
;
1040 document
.getElementById('noVNC_setting_clip').disabled
= connected
|| UI
.isTouchDevice
;
1041 if (UI
.isTouchDevice
) {
1042 UI
.setViewClip(true);
1053 toggleViewDrag: function() {
1054 if (!UI
.rfb
) return;
1056 var drag
= UI
.rfb
.get_viewportDrag();
1057 UI
.setViewDrag(!drag
);
1060 // Set the view drag mode which moves the viewport on mouse drags
1061 setViewDrag: function(drag
) {
1062 if (!UI
.rfb
) return;
1064 UI
.rfb
.set_viewportDrag(drag
);
1066 UI
.updateViewDrag();
1069 updateViewDrag: function() {
1070 var clipping
= false;
1072 // Check if viewport drag is possible. It is only possible
1073 // if the remote display is clipping the client display.
1074 if (UI
.rfb_state
=== 'normal' &&
1075 UI
.rfb
.get_display().get_viewport() &&
1076 UI
.rfb
.get_display().clippingDisplay()) {
1080 var viewDragButton
= document
.getElementById('noVNC_view_drag_button');
1082 if (UI
.rfb_state
!== 'normal') {
1083 // Always hide when not connected
1084 viewDragButton
.classList
.add("noVNC_hidden");
1087 UI
.rfb
.get_viewportDrag()) {
1088 // The size of the remote display is the same or smaller
1089 // than the client display. Make sure viewport drag isn't
1090 // active when it can't be used.
1091 UI
.rfb
.set_viewportDrag(false);
1094 if (UI
.rfb
.get_viewportDrag()) {
1095 viewDragButton
.classList
.add("noVNC_selected");
1097 viewDragButton
.classList
.remove("noVNC_selected");
1100 // Different behaviour for touch vs non-touch
1101 // The button is disabled instead of hidden on touch devices
1102 if (UI
.isTouchDevice
) {
1103 viewDragButton
.classList
.remove("noVNC_hidden");
1106 viewDragButton
.disabled
= false;
1108 viewDragButton
.disabled
= true;
1111 viewDragButton
.disabled
= false;
1114 viewDragButton
.classList
.remove("noVNC_hidden");
1116 viewDragButton
.classList
.add("noVNC_hidden");
1128 // On touch devices, show the OS keyboard
1129 showKeyboard: function() {
1130 var kbi
= document
.getElementById('noVNC_keyboardinput');
1131 var skb
= document
.getElementById('noVNC_keyboard_button');
1132 var l
= kbi
.value
.length
;
1133 if(UI
.keyboardVisible
=== false) {
1135 try { kbi
.setSelectionRange(l
, l
); } // Move the caret to the end
1136 catch (err
) {} // setSelectionRange is undefined in Google Chrome
1137 UI
.keyboardVisible
= true;
1138 skb
.classList
.add("noVNC_selected");
1139 } else if(UI
.keyboardVisible
=== true) {
1141 skb
.classList
.remove("noVNC_selected");
1142 UI
.keyboardVisible
= false;
1146 hideKeyboard: function() {
1147 document
.getElementById('noVNC_keyboard_button')
1148 .classList
.remove("noVNC_selected");
1149 //Weird bug in iOS if you change keyboardVisible
1150 //here it does not actually occur so next time
1151 //you click keyboard icon it doesnt work.
1152 UI
.hideKeyboardTimeout
= setTimeout(function() {
1153 UI
.keyboardVisible
= false;
1157 keepKeyboard: function() {
1158 clearTimeout(UI
.hideKeyboardTimeout
);
1159 if(UI
.keyboardVisible
=== true) {
1160 document
.getElementById('noVNC_keyboardinput').focus();
1161 document
.getElementById('noVNC_keyboard_button')
1162 .classList
.add("noVNC_selected");
1163 } else if(UI
.keyboardVisible
=== false) {
1164 document
.getElementById('noVNC_keyboardinput').blur();
1165 document
.getElementById('noVNC_keyboard_button')
1166 .classList
.remove("noVNC_selected");
1170 keyboardinputReset: function() {
1171 var kbi
= document
.getElementById('noVNC_keyboardinput');
1172 kbi
.value
= new Array(UI
.defaultKeyboardinputLen
).join("_");
1173 UI
.lastKeyboardinput
= kbi
.value
;
1176 // When normal keyboard events are left uncought, use the input events from
1177 // the keyboardinput element instead and generate the corresponding key events.
1178 // This code is required since some browsers on Android are inconsistent in
1179 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1180 keyInput: function(event
) {
1182 if (!UI
.rfb
) return;
1184 var newValue
= event
.target
.value
;
1186 if (!UI
.lastKeyboardinput
) {
1187 UI
.keyboardinputReset();
1189 var oldValue
= UI
.lastKeyboardinput
;
1193 // Try to check caret position since whitespace at the end
1194 // will not be considered by value.length in some browsers
1195 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1197 // selectionStart is undefined in Google Chrome
1198 newLen
= newValue
.length
;
1200 var oldLen
= oldValue
.length
;
1203 var inputs
= newLen
- oldLen
;
1205 backspaces
= -inputs
;
1210 // Compare the old string with the new to account for
1211 // text-corrections or other input that modify existing text
1213 for (i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1214 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1215 inputs
= newLen
- i
;
1216 backspaces
= oldLen
- i
;
1221 // Send the key events
1222 for (i
= 0; i
< backspaces
; i
++) {
1223 UI
.rfb
.sendKey(KeyTable
.XK_BackSpace
);
1225 for (i
= newLen
- inputs
; i
< newLen
; i
++) {
1226 UI
.rfb
.sendKey(newValue
.charCodeAt(i
));
1229 // Control the text content length in the keyboardinput element
1230 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1231 UI
.keyboardinputReset();
1232 } else if (newLen
< 1) {
1233 // There always have to be some text in the keyboardinput
1234 // element with which backspace can interact.
1235 UI
.keyboardinputReset();
1236 // This sometimes causes the keyboard to disappear for a second
1237 // but it is required for the android keyboard to recognize that
1238 // text has been added to the field
1239 event
.target
.blur();
1240 // This has to be ran outside of the input handler in order to work
1241 setTimeout(UI
.keepKeyboard
, 0);
1243 UI
.lastKeyboardinput
= newValue
;
1247 openExtraKeys: function() {
1248 document
.getElementById('noVNC_modifiers')
1249 .classList
.add("noVNC_open");
1250 document
.getElementById('noVNC_toggle_extra_keys_button')
1251 .classList
.add("noVNC_selected");
1254 closeExtraKeys: function() {
1255 document
.getElementById('noVNC_modifiers')
1256 .classList
.remove("noVNC_open");
1257 document
.getElementById('noVNC_toggle_extra_keys_button')
1258 .classList
.remove("noVNC_selected");
1261 toggleExtraKeys: function() {
1263 if(document
.getElementById('noVNC_modifiers')
1264 .classList
.contains("noVNC_open")) {
1265 UI
.closeExtraKeys();
1271 sendEsc: function() {
1273 UI
.rfb
.sendKey(KeyTable
.XK_Escape
);
1276 sendTab: function() {
1278 UI
.rfb
.sendKey(KeyTable
.XK_Tab
);
1281 toggleCtrl: function() {
1283 if(UI
.ctrlOn
=== false) {
1284 UI
.rfb
.sendKey(KeyTable
.XK_Control_L
, true);
1285 document
.getElementById('noVNC_toggle_ctrl_button')
1286 .classList
.add("noVNC_selected");
1288 } else if(UI
.ctrlOn
=== true) {
1289 UI
.rfb
.sendKey(KeyTable
.XK_Control_L
, false);
1290 document
.getElementById('noVNC_toggle_ctrl_button')
1291 .classList
.remove("noVNC_selected");
1296 toggleAlt: function() {
1298 if(UI
.altOn
=== false) {
1299 UI
.rfb
.sendKey(KeyTable
.XK_Alt_L
, true);
1300 document
.getElementById('noVNC_toggle_alt_button')
1301 .classList
.add("noVNC_selected");
1303 } else if(UI
.altOn
=== true) {
1304 UI
.rfb
.sendKey(KeyTable
.XK_Alt_L
, false);
1305 document
.getElementById('noVNC_toggle_alt_button')
1306 .classList
.remove("noVNC_selected");
1311 sendCtrlAltDel: function() {
1312 UI
.rfb
.sendCtrlAltDel();
1321 hideMouseButton: function() {
1322 UI
.setMouseButton(-1);
1325 setMouseButton: function(num
) {
1327 UI
.rfb
.get_mouse().set_touchButton(num
);
1330 var blist
= [0, 1,2,4];
1331 for (var b
= 0; b
< blist
.length
; b
++) {
1332 var button
= document
.getElementById('noVNC_mouse_button' + blist
[b
]);
1333 if (blist
[b
] === num
) {
1334 button
.classList
.remove("noVNC_hidden");
1336 button
.classList
.add("noVNC_hidden");
1341 displayBlur: function() {
1342 if (!UI
.rfb
) return;
1344 UI
.rfb
.get_keyboard().set_focused(false);
1345 UI
.rfb
.get_mouse().set_focused(false);
1348 displayFocus: function() {
1349 if (!UI
.rfb
) return;
1351 UI
.rfb
.get_keyboard().set_focused(true);
1352 UI
.rfb
.get_mouse().set_focused(true);
1355 // Display the desktop name in the document title
1356 updateDocumentTitle: function(rfb
, name
) {
1357 document
.title
= name
+ " - noVNC";
1360 //Helper to add options to dropdown.
1361 addOption: function(selectbox
, text
, value
) {
1362 var optn
= document
.createElement("OPTION");
1365 selectbox
.options
.add(optn
);
1368 setBarPosition: function() {
1369 document
.getElementById('noVNC_control_bar').style
.top
= (window
.pageYOffset
) + 'px';
1370 document
.getElementById('noVNC_mobile_buttons').style
.left
= (window
.pageXOffset
) + 'px';
1372 var vncwidth
= document
.getElementById('noVNC_container').style
.offsetWidth
;
1373 document
.getElementById('noVNC_control_bar').style
.width
= vncwidth
+ 'px';
1382 /* [module] UI.load(); */
1385 /* [module] export default UI; */