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 */
14 import * as Log
from '../core/util/logging.js';
15 import _
, { l10n
} from '../core/util/localization.js'
16 import { isTouchDevice
, browserSupportsCursorURIs as cursorURIsSupported
} from '../core/util/browsers.js';
17 import { setCapture
, getPointerEvent
} from '../core/util/events.js';
18 import KeyTable
from "../core/input/keysym.js";
19 import keysyms
from "../core/input/keysymdef.js";
20 import RFB
from "../core/rfb.js";
21 import Display
from "../core/display.js";
22 import * as WebUtil
from "./webutil.js";
31 hideKeyboardTimeout
: null,
32 idleControlbarTimeout
: null,
33 closeControlbarTimeout
: null,
35 controlbarGrabbed
: false,
36 controlbarDrag
: false,
37 controlbarMouseDownClientY
: 0,
38 controlbarMouseDownOffsetY
: 0,
41 rememberedClipSetting
: null,
42 lastKeyboardinput
: null,
43 defaultKeyboardinputLen
: 100,
45 inhibit_reconnect
: true,
46 reconnect_callback
: null,
47 reconnect_password
: null,
49 prime: function(callback
) {
50 if (document
.readyState
=== "interactive" || document
.readyState
=== "complete") {
53 document
.addEventListener('DOMContentLoaded', UI
.load
.bind(UI
, callback
));
57 // Setup rfb object, load settings from browser storage, then call
58 // UI.init to setup the UI/menus
59 load: function(callback
) {
60 WebUtil
.initSettings(UI
.start
, callback
);
63 // Render default UI and initialize settings menu
64 start: function(callback
) {
66 // Setup global variables first
67 UI
.isSafari
= (navigator
.userAgent
.indexOf('Safari') !== -1 &&
68 navigator
.userAgent
.indexOf('Chrome') === -1);
75 // Adapt the interface for touch screen devices
77 document
.documentElement
.classList
.add("noVNC_touch");
78 // Remove the address bar
79 setTimeout(function() { window
.scrollTo(0, 1); }, 100);
82 // Restore control bar position
83 if (WebUtil
.readSetting('controlbar_pos') === 'right') {
84 UI
.toggleControlbarSide();
89 // Setup event handlers
90 UI
.addResizeHandlers();
91 UI
.addControlbarHandlers();
92 UI
.addTouchSpecificHandlers();
93 UI
.addExtraKeysHandlers();
95 UI
.addConnectionControlHandlers();
96 UI
.addClipboardHandlers();
97 UI
.addSettingsHandlers();
98 document
.getElementById("noVNC_status")
99 .addEventListener('click', UI
.hideStatus
);
103 // Show the connect panel on first load unless autoconnecting
105 UI
.openConnectPanel();
110 UI
.updateVisualState();
112 document
.getElementById('noVNC_setting_host').focus();
113 document
.documentElement
.classList
.remove("noVNC_loading");
115 var autoconnect
= WebUtil
.getConfigVar('autoconnect', false);
116 if (autoconnect
=== 'true' || autoconnect
== '1') {
123 if (typeof callback
=== "function") {
128 initFullscreen: function() {
129 // Only show the button if fullscreen is properly supported
130 // * Safari doesn't support alphanumerical input while in fullscreen
132 (document
.documentElement
.requestFullscreen
||
133 document
.documentElement
.mozRequestFullScreen
||
134 document
.documentElement
.webkitRequestFullscreen
||
135 document
.body
.msRequestFullscreen
)) {
136 document
.getElementById('noVNC_fullscreen_button')
137 .classList
.remove("noVNC_hidden");
138 UI
.addFullscreenHandlers();
142 initSettings: function() {
145 // Logging selection dropdown
146 var llevels
= ['error', 'warn', 'info', 'debug'];
147 for (i
= 0; i
< llevels
.length
; i
+= 1) {
148 UI
.addOption(document
.getElementById('noVNC_setting_logging'),llevels
[i
], llevels
[i
]);
151 // Settings with immediate effects
152 UI
.initSetting('logging', 'warn');
155 // if port == 80 (or 443) then it won't be present and should be
157 var port
= window
.location
.port
;
159 if (window
.location
.protocol
.substring(0,5) == 'https') {
162 else if (window
.location
.protocol
.substring(0,4) == 'http') {
167 /* Populate the controls if defaults are provided in the URL */
168 UI
.initSetting('host', window
.location
.hostname
);
169 UI
.initSetting('port', port
);
170 UI
.initSetting('encrypt', (window
.location
.protocol
=== "https:"));
171 UI
.initSetting('true_color', true);
172 UI
.initSetting('cursor', !isTouchDevice
);
173 UI
.initSetting('clip', false);
174 UI
.initSetting('resize', 'off');
175 UI
.initSetting('shared', true);
176 UI
.initSetting('view_only', false);
177 UI
.initSetting('path', 'websockify');
178 UI
.initSetting('repeaterID', '');
179 UI
.initSetting('reconnect', false);
180 UI
.initSetting('reconnect_delay', 5000);
182 UI
.setupSettingLabels();
184 // Adds a link to the label elements on the corresponding input elements
185 setupSettingLabels: function() {
186 var labels
= document
.getElementsByTagName('LABEL');
187 for (var i
= 0; i
< labels
.length
; i
++) {
188 var htmlFor
= labels
[i
].htmlFor
;
190 var elem
= document
.getElementById(htmlFor
);
191 if (elem
) elem
.label
= labels
[i
];
193 // If 'for' isn't set, use the first input element child
194 var children
= labels
[i
].children
;
195 for (var j
= 0; j
< children
.length
; j
++) {
196 if (children
[j
].form
!== undefined) {
197 children
[j
].label
= labels
[i
];
205 initRFB: function() {
207 UI
.rfb
= new RFB({'target': document
.getElementById('noVNC_canvas'),
208 'onNotification': UI
.notification
,
209 'onUpdateState': UI
.updateState
,
210 'onDisconnected': UI
.disconnectFinished
,
211 'onPasswordRequired': UI
.passwordRequired
,
212 'onXvpInit': UI
.updateXvpButton
,
213 'onClipboard': UI
.clipboardReceive
,
215 'onFBUComplete': UI
.initialResize
,
216 'onFBResize': UI
.updateSessionSize
,
217 'onDesktopName': UI
.updateDesktopName
});
220 var msg
= "Unable to create RFB client -- " + exc
;
222 UI
.showStatus(msg
, 'error');
233 addResizeHandlers: function() {
234 window
.addEventListener('resize', UI
.applyResizeMode
);
235 window
.addEventListener('resize', UI
.updateViewClip
);
238 addControlbarHandlers: function() {
239 document
.getElementById("noVNC_control_bar")
240 .addEventListener('mousemove', UI
.activateControlbar
);
241 document
.getElementById("noVNC_control_bar")
242 .addEventListener('mouseup', UI
.activateControlbar
);
243 document
.getElementById("noVNC_control_bar")
244 .addEventListener('mousedown', UI
.activateControlbar
);
245 document
.getElementById("noVNC_control_bar")
246 .addEventListener('keypress', UI
.activateControlbar
);
248 document
.getElementById("noVNC_control_bar")
249 .addEventListener('mousedown', UI
.keepControlbar
);
250 document
.getElementById("noVNC_control_bar")
251 .addEventListener('keypress', UI
.keepControlbar
);
253 document
.getElementById("noVNC_view_drag_button")
254 .addEventListener('click', UI
.toggleViewDrag
);
256 document
.getElementById("noVNC_control_bar_handle")
257 .addEventListener('mousedown', UI
.controlbarHandleMouseDown
);
258 document
.getElementById("noVNC_control_bar_handle")
259 .addEventListener('mouseup', UI
.controlbarHandleMouseUp
);
260 document
.getElementById("noVNC_control_bar_handle")
261 .addEventListener('mousemove', UI
.dragControlbarHandle
);
262 // resize events aren't available for elements
263 window
.addEventListener('resize', UI
.updateControlbarHandle
);
265 var exps
= document
.getElementsByClassName("noVNC_expander");
266 for (var i
= 0;i
< exps
.length
;i
++) {
267 exps
[i
].addEventListener('click', UI
.toggleExpander
);
271 addTouchSpecificHandlers: function() {
272 document
.getElementById("noVNC_mouse_button0")
273 .addEventListener('click', function () { UI
.setMouseButton(1); });
274 document
.getElementById("noVNC_mouse_button1")
275 .addEventListener('click', function () { UI
.setMouseButton(2); });
276 document
.getElementById("noVNC_mouse_button2")
277 .addEventListener('click', function () { UI
.setMouseButton(4); });
278 document
.getElementById("noVNC_mouse_button4")
279 .addEventListener('click', function () { UI
.setMouseButton(0); });
280 document
.getElementById("noVNC_keyboard_button")
281 .addEventListener('click', UI
.toggleVirtualKeyboard
);
283 document
.getElementById("noVNC_keyboardinput")
284 .addEventListener('input', UI
.keyInput
);
285 document
.getElementById("noVNC_keyboardinput")
286 .addEventListener('focus', UI
.onfocusVirtualKeyboard
);
287 document
.getElementById("noVNC_keyboardinput")
288 .addEventListener('blur', UI
.onblurVirtualKeyboard
);
289 document
.getElementById("noVNC_keyboardinput")
290 .addEventListener('submit', function () { return false; });
292 document
.documentElement
293 .addEventListener('mousedown', UI
.keepVirtualKeyboard
, true);
295 document
.getElementById("noVNC_control_bar")
296 .addEventListener('touchstart', UI
.activateControlbar
);
297 document
.getElementById("noVNC_control_bar")
298 .addEventListener('touchmove', UI
.activateControlbar
);
299 document
.getElementById("noVNC_control_bar")
300 .addEventListener('touchend', UI
.activateControlbar
);
301 document
.getElementById("noVNC_control_bar")
302 .addEventListener('input', UI
.activateControlbar
);
304 document
.getElementById("noVNC_control_bar")
305 .addEventListener('touchstart', UI
.keepControlbar
);
306 document
.getElementById("noVNC_control_bar")
307 .addEventListener('input', UI
.keepControlbar
);
309 document
.getElementById("noVNC_control_bar_handle")
310 .addEventListener('touchstart', UI
.controlbarHandleMouseDown
);
311 document
.getElementById("noVNC_control_bar_handle")
312 .addEventListener('touchend', UI
.controlbarHandleMouseUp
);
313 document
.getElementById("noVNC_control_bar_handle")
314 .addEventListener('touchmove', UI
.dragControlbarHandle
);
316 window
.addEventListener('load', UI
.keyboardinputReset
);
319 addExtraKeysHandlers: function() {
320 document
.getElementById("noVNC_toggle_extra_keys_button")
321 .addEventListener('click', UI
.toggleExtraKeys
);
322 document
.getElementById("noVNC_toggle_ctrl_button")
323 .addEventListener('click', UI
.toggleCtrl
);
324 document
.getElementById("noVNC_toggle_alt_button")
325 .addEventListener('click', UI
.toggleAlt
);
326 document
.getElementById("noVNC_send_tab_button")
327 .addEventListener('click', UI
.sendTab
);
328 document
.getElementById("noVNC_send_esc_button")
329 .addEventListener('click', UI
.sendEsc
);
330 document
.getElementById("noVNC_send_ctrl_alt_del_button")
331 .addEventListener('click', UI
.sendCtrlAltDel
);
334 addXvpHandlers: function() {
335 document
.getElementById("noVNC_xvp_shutdown_button")
336 .addEventListener('click', function() { UI
.rfb
.xvpShutdown(); });
337 document
.getElementById("noVNC_xvp_reboot_button")
338 .addEventListener('click', function() { UI
.rfb
.xvpReboot(); });
339 document
.getElementById("noVNC_xvp_reset_button")
340 .addEventListener('click', function() { UI
.rfb
.xvpReset(); });
341 document
.getElementById("noVNC_xvp_button")
342 .addEventListener('click', UI
.toggleXvpPanel
);
345 addConnectionControlHandlers: function() {
346 document
.getElementById("noVNC_disconnect_button")
347 .addEventListener('click', UI
.disconnect
);
348 document
.getElementById("noVNC_connect_button")
349 .addEventListener('click', UI
.connect
);
350 document
.getElementById("noVNC_cancel_reconnect_button")
351 .addEventListener('click', UI
.cancelReconnect
);
353 document
.getElementById("noVNC_password_button")
354 .addEventListener('click', UI
.setPassword
);
357 addClipboardHandlers: function() {
358 document
.getElementById("noVNC_clipboard_button")
359 .addEventListener('click', UI
.toggleClipboardPanel
);
360 document
.getElementById("noVNC_clipboard_text")
361 .addEventListener('focus', UI
.displayBlur
);
362 document
.getElementById("noVNC_clipboard_text")
363 .addEventListener('blur', UI
.displayFocus
);
364 document
.getElementById("noVNC_clipboard_text")
365 .addEventListener('change', UI
.clipboardSend
);
366 document
.getElementById("noVNC_clipboard_clear_button")
367 .addEventListener('click', UI
.clipboardClear
);
370 // Add a call to save settings when the element changes,
371 // unless the optional parameter changeFunc is used instead.
372 addSettingChangeHandler: function(name
, changeFunc
) {
373 var settingElem
= document
.getElementById("noVNC_setting_" + name
);
374 if (changeFunc
=== undefined) {
375 changeFunc = function () { UI
.saveSetting(name
); };
377 settingElem
.addEventListener('change', changeFunc
);
380 addSettingsHandlers: function() {
381 document
.getElementById("noVNC_settings_button")
382 .addEventListener('click', UI
.toggleSettingsPanel
);
384 UI
.addSettingChangeHandler('encrypt');
385 UI
.addSettingChangeHandler('true_color');
386 UI
.addSettingChangeHandler('cursor');
387 UI
.addSettingChangeHandler('cursor', UI
.updateLocalCursor
);
388 UI
.addSettingChangeHandler('resize');
389 UI
.addSettingChangeHandler('resize', UI
.enableDisableViewClip
);
390 UI
.addSettingChangeHandler('resize', UI
.applyResizeMode
);
391 UI
.addSettingChangeHandler('clip');
392 UI
.addSettingChangeHandler('clip', UI
.updateViewClip
);
393 UI
.addSettingChangeHandler('shared');
394 UI
.addSettingChangeHandler('view_only');
395 UI
.addSettingChangeHandler('view_only', UI
.updateViewOnly
);
396 UI
.addSettingChangeHandler('host');
397 UI
.addSettingChangeHandler('port');
398 UI
.addSettingChangeHandler('path');
399 UI
.addSettingChangeHandler('repeaterID');
400 UI
.addSettingChangeHandler('logging');
401 UI
.addSettingChangeHandler('logging', UI
.updateLogging
);
402 UI
.addSettingChangeHandler('reconnect');
403 UI
.addSettingChangeHandler('reconnect_delay');
406 addFullscreenHandlers: function() {
407 document
.getElementById("noVNC_fullscreen_button")
408 .addEventListener('click', UI
.toggleFullscreen
);
410 window
.addEventListener('fullscreenchange', UI
.updateFullscreenButton
);
411 window
.addEventListener('mozfullscreenchange', UI
.updateFullscreenButton
);
412 window
.addEventListener('webkitfullscreenchange', UI
.updateFullscreenButton
);
413 window
.addEventListener('msfullscreenchange', UI
.updateFullscreenButton
);
422 updateState: function(rfb
, state
, oldstate
) {
425 document
.documentElement
.classList
.remove("noVNC_connecting");
426 document
.documentElement
.classList
.remove("noVNC_connected");
427 document
.documentElement
.classList
.remove("noVNC_disconnecting");
428 document
.documentElement
.classList
.remove("noVNC_reconnecting");
432 document
.getElementById("noVNC_transition_text").textContent
= _("Connecting...");
433 document
.documentElement
.classList
.add("noVNC_connecting");
437 UI
.inhibit_reconnect
= false;
438 document
.documentElement
.classList
.add("noVNC_connected");
439 if (rfb
&& rfb
.get_encrypt()) {
440 msg
= _("Connected (encrypted) to ") + UI
.desktopName
;
442 msg
= _("Connected (unencrypted) to ") + UI
.desktopName
;
446 case 'disconnecting':
447 UI
.connected
= false;
448 document
.getElementById("noVNC_transition_text").textContent
= _("Disconnecting...");
449 document
.documentElement
.classList
.add("noVNC_disconnecting");
452 UI
.showStatus(_("Disconnected"));
455 msg
= "Invalid UI state";
457 UI
.showStatus(msg
, 'error');
461 UI
.updateVisualState();
464 // Disable/enable controls depending on connection state
465 updateVisualState: function() {
466 //Log.Debug(">> updateVisualState");
468 UI
.enableDisableViewClip();
470 if (cursorURIsSupported() && !isTouchDevice
) {
471 UI
.enableSetting('cursor');
473 UI
.disableSetting('cursor');
477 UI
.disableSetting('encrypt');
478 UI
.disableSetting('true_color');
479 UI
.disableSetting('shared');
480 UI
.disableSetting('host');
481 UI
.disableSetting('port');
482 UI
.disableSetting('path');
483 UI
.disableSetting('repeaterID');
485 UI
.setMouseButton(1);
487 // Hide the controlbar after 2 seconds
488 UI
.closeControlbarTimeout
= setTimeout(UI
.closeControlbar
, 2000);
490 UI
.enableSetting('encrypt');
491 UI
.enableSetting('true_color');
492 UI
.enableSetting('shared');
493 UI
.enableSetting('host');
494 UI
.enableSetting('port');
495 UI
.enableSetting('path');
496 UI
.enableSetting('repeaterID');
497 UI
.updateXvpButton(0);
501 // Hide input related buttons in view only mode
502 if (UI
.rfb
&& UI
.rfb
.get_view_only()) {
503 document
.getElementById('noVNC_keyboard_button')
504 .classList
.add('noVNC_hidden');
505 document
.getElementById('noVNC_toggle_extra_keys_button')
506 .classList
.add('noVNC_hidden');
508 document
.getElementById('noVNC_keyboard_button')
509 .classList
.remove('noVNC_hidden');
510 document
.getElementById('noVNC_toggle_extra_keys_button')
511 .classList
.remove('noVNC_hidden');
514 // State change disables viewport dragging.
515 // It is enabled (toggled) by direct click on the button
516 UI
.setViewDrag(false);
518 // State change also closes the password dialog
519 document
.getElementById('noVNC_password_dlg')
520 .classList
.remove('noVNC_open');
522 //Log.Debug("<< updateVisualState");
525 showStatus: function(text
, status_type
, time
) {
526 var statusElem
= document
.getElementById('noVNC_status');
528 clearTimeout(UI
.statusTimeout
);
530 if (typeof status_type
=== 'undefined') {
531 status_type
= 'normal';
534 statusElem
.classList
.remove("noVNC_status_normal");
535 statusElem
.classList
.remove("noVNC_status_warn");
536 statusElem
.classList
.remove("noVNC_status_error");
538 switch (status_type
) {
541 statusElem
.classList
.add("noVNC_status_warn");
544 statusElem
.classList
.add("noVNC_status_error");
549 statusElem
.classList
.add("noVNC_status_normal");
553 statusElem
.textContent
= text
;
554 statusElem
.classList
.add("noVNC_open");
556 // If no time was specified, show the status for 1.5 seconds
557 if (typeof time
=== 'undefined') {
561 // Error messages do not timeout
562 if (status_type
!== 'error') {
563 UI
.statusTimeout
= window
.setTimeout(UI
.hideStatus
, time
);
567 hideStatus: function() {
568 clearTimeout(UI
.statusTimeout
);
569 document
.getElementById('noVNC_status').classList
.remove("noVNC_open");
572 notification: function (rfb
, msg
, level
, options
) {
573 UI
.showStatus(msg
, level
);
576 activateControlbar: function(event
) {
577 clearTimeout(UI
.idleControlbarTimeout
);
578 // We manipulate the anchor instead of the actual control
579 // bar in order to avoid creating new a stacking group
580 document
.getElementById('noVNC_control_bar_anchor')
581 .classList
.remove("noVNC_idle");
582 UI
.idleControlbarTimeout
= window
.setTimeout(UI
.idleControlbar
, 2000);
585 idleControlbar: function() {
586 document
.getElementById('noVNC_control_bar_anchor')
587 .classList
.add("noVNC_idle");
590 keepControlbar: function() {
591 clearTimeout(UI
.closeControlbarTimeout
);
594 openControlbar: function() {
595 document
.getElementById('noVNC_control_bar')
596 .classList
.add("noVNC_open");
599 closeControlbar: function() {
601 document
.getElementById('noVNC_control_bar')
602 .classList
.remove("noVNC_open");
605 toggleControlbar: function() {
606 if (document
.getElementById('noVNC_control_bar')
607 .classList
.contains("noVNC_open")) {
608 UI
.closeControlbar();
614 toggleControlbarSide: function () {
615 // Temporarily disable animation to avoid weird movement
616 var bar
= document
.getElementById('noVNC_control_bar');
617 bar
.style
.transitionDuration
= '0s';
618 bar
.addEventListener('transitionend', function () { this.style
.transitionDuration
= ""; });
620 var anchor
= document
.getElementById('noVNC_control_bar_anchor');
621 if (anchor
.classList
.contains("noVNC_right")) {
622 WebUtil
.writeSetting('controlbar_pos', 'left');
623 anchor
.classList
.remove("noVNC_right");
625 WebUtil
.writeSetting('controlbar_pos', 'right');
626 anchor
.classList
.add("noVNC_right");
629 // Consider this a movement of the handle
630 UI
.controlbarDrag
= true;
633 dragControlbarHandle: function (e
) {
634 if (!UI
.controlbarGrabbed
) return;
636 var ptr
= getPointerEvent(e
);
638 var anchor
= document
.getElementById('noVNC_control_bar_anchor');
639 if (ptr
.clientX
< (window
.innerWidth
* 0.1)) {
640 if (anchor
.classList
.contains("noVNC_right")) {
641 UI
.toggleControlbarSide();
643 } else if (ptr
.clientX
> (window
.innerWidth
* 0.9)) {
644 if (!anchor
.classList
.contains("noVNC_right")) {
645 UI
.toggleControlbarSide();
649 if (!UI
.controlbarDrag
) {
650 // The goal is to trigger on a certain physical width, the
651 // devicePixelRatio brings us a bit closer but is not optimal.
652 var dragThreshold
= 10 * (window
.devicePixelRatio
|| 1);
653 var dragDistance
= Math
.abs(ptr
.clientY
- UI
.controlbarMouseDownClientY
);
655 if (dragDistance
< dragThreshold
) return;
657 UI
.controlbarDrag
= true;
660 var eventY
= ptr
.clientY
- UI
.controlbarMouseDownOffsetY
;
662 UI
.moveControlbarHandle(eventY
);
667 UI
.activateControlbar();
670 // Move the handle but don't allow any position outside the bounds
671 moveControlbarHandle: function (viewportRelativeY
) {
672 var handle
= document
.getElementById("noVNC_control_bar_handle");
673 var handleHeight
= handle
.getBoundingClientRect().height
;
674 var controlbarBounds
= document
.getElementById("noVNC_control_bar")
675 .getBoundingClientRect();
678 // These heights need to be non-zero for the below logic to work
679 if (handleHeight
=== 0 || controlbarBounds
.height
=== 0) {
683 var newY
= viewportRelativeY
;
685 // Check if the coordinates are outside the control bar
686 if (newY
< controlbarBounds
.top
+ margin
) {
687 // Force coordinates to be below the top of the control bar
688 newY
= controlbarBounds
.top
+ margin
;
690 } else if (newY
> controlbarBounds
.top
+
691 controlbarBounds
.height
- handleHeight
- margin
) {
692 // Force coordinates to be above the bottom of the control bar
693 newY
= controlbarBounds
.top
+
694 controlbarBounds
.height
- handleHeight
- margin
;
697 // Corner case: control bar too small for stable position
698 if (controlbarBounds
.height
< (handleHeight
+ margin
* 2)) {
699 newY
= controlbarBounds
.top
+
700 (controlbarBounds
.height
- handleHeight
) / 2;
703 // The transform needs coordinates that are relative to the parent
704 var parentRelativeY
= newY
- controlbarBounds
.top
;
705 handle
.style
.transform
= "translateY(" + parentRelativeY
+ "px)";
708 updateControlbarHandle: function () {
709 // Since the control bar is fixed on the viewport and not the page,
710 // the move function expects coordinates relative the the viewport.
711 var handle
= document
.getElementById("noVNC_control_bar_handle");
712 var handleBounds
= handle
.getBoundingClientRect();
713 UI
.moveControlbarHandle(handleBounds
.top
);
716 controlbarHandleMouseUp: function(e
) {
717 if ((e
.type
== "mouseup") && (e
.button
!= 0)) return;
719 // mouseup and mousedown on the same place toggles the controlbar
720 if (UI
.controlbarGrabbed
&& !UI
.controlbarDrag
) {
721 UI
.toggleControlbar();
725 UI
.activateControlbar();
727 UI
.controlbarGrabbed
= false;
730 controlbarHandleMouseDown: function(e
) {
731 if ((e
.type
== "mousedown") && (e
.button
!= 0)) return;
733 var ptr
= getPointerEvent(e
);
735 var handle
= document
.getElementById("noVNC_control_bar_handle");
736 var bounds
= handle
.getBoundingClientRect();
739 UI
.controlbarGrabbed
= true;
740 UI
.controlbarDrag
= false;
742 UI
.controlbarMouseDownClientY
= ptr
.clientY
;
743 UI
.controlbarMouseDownOffsetY
= ptr
.clientY
- bounds
.top
;
747 UI
.activateControlbar();
750 toggleExpander: function(e
) {
751 if (this.classList
.contains("noVNC_open")) {
752 this.classList
.remove("noVNC_open");
754 this.classList
.add("noVNC_open");
764 // Initial page load read/initialization of settings
765 initSetting: function(name
, defVal
) {
766 // Check Query string followed by cookie
767 var val
= WebUtil
.getConfigVar(name
);
769 val
= WebUtil
.readSetting(name
, defVal
);
771 UI
.updateSetting(name
, val
);
775 // Update cookie and form control setting. If value is not set, then
776 // updates from control to current cookie setting.
777 updateSetting: function(name
, value
) {
779 // Save the cookie for this session
780 if (typeof value
!== 'undefined') {
781 WebUtil
.writeSetting(name
, value
);
784 // Update the settings control
785 value
= UI
.getSetting(name
);
787 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
788 if (ctrl
.type
=== 'checkbox') {
789 ctrl
.checked
= value
;
791 } else if (typeof ctrl
.options
!== 'undefined') {
792 for (var i
= 0; i
< ctrl
.options
.length
; i
+= 1) {
793 if (ctrl
.options
[i
].value
=== value
) {
794 ctrl
.selectedIndex
= i
;
799 /*Weird IE9 error leads to 'null' appearring
800 in textboxes instead of ''.*/
801 if (value
=== null) {
808 // Save control setting to cookie
809 saveSetting: function(name
) {
810 var val
, ctrl
= document
.getElementById('noVNC_setting_' + name
);
811 if (ctrl
.type
=== 'checkbox') {
813 } else if (typeof ctrl
.options
!== 'undefined') {
814 val
= ctrl
.options
[ctrl
.selectedIndex
].value
;
818 WebUtil
.writeSetting(name
, val
);
819 //Log.Debug("Setting saved '" + name + "=" + val + "'");
823 // Read form control compatible setting from cookie
824 getSetting: function(name
) {
825 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
826 var val
= WebUtil
.readSetting(name
);
827 if (typeof val
!== 'undefined' && val
!== null && ctrl
.type
=== 'checkbox') {
828 if (val
.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
837 // These helpers compensate for the lack of parent-selectors and
838 // previous-sibling-selectors in CSS which are needed when we want to
839 // disable the labels that belong to disabled input elements.
840 disableSetting: function(name
) {
841 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
842 ctrl
.disabled
= true;
843 ctrl
.label
.classList
.add('noVNC_disabled');
846 enableSetting: function(name
) {
847 var ctrl
= document
.getElementById('noVNC_setting_' + name
);
848 ctrl
.disabled
= false;
849 ctrl
.label
.classList
.remove('noVNC_disabled');
858 closeAllPanels: function() {
859 UI
.closeSettingsPanel();
861 UI
.closeClipboardPanel();
871 openSettingsPanel: function() {
875 // Refresh UI elements from saved cookies
876 UI
.updateSetting('encrypt');
877 UI
.updateSetting('true_color');
878 if (cursorURIsSupported()) {
879 UI
.updateSetting('cursor');
881 UI
.updateSetting('cursor', !isTouchDevice
);
882 UI
.disableSetting('cursor');
884 UI
.updateSetting('clip');
885 UI
.updateSetting('resize');
886 UI
.updateSetting('shared');
887 UI
.updateSetting('view_only');
888 UI
.updateSetting('path');
889 UI
.updateSetting('repeaterID');
890 UI
.updateSetting('logging');
891 UI
.updateSetting('reconnect');
892 UI
.updateSetting('reconnect_delay');
894 document
.getElementById('noVNC_settings')
895 .classList
.add("noVNC_open");
896 document
.getElementById('noVNC_settings_button')
897 .classList
.add("noVNC_selected");
900 closeSettingsPanel: function() {
901 document
.getElementById('noVNC_settings')
902 .classList
.remove("noVNC_open");
903 document
.getElementById('noVNC_settings_button')
904 .classList
.remove("noVNC_selected");
907 toggleSettingsPanel: function() {
908 if (document
.getElementById('noVNC_settings')
909 .classList
.contains("noVNC_open")) {
910 UI
.closeSettingsPanel();
912 UI
.openSettingsPanel();
922 openXvpPanel: function() {
926 document
.getElementById('noVNC_xvp')
927 .classList
.add("noVNC_open");
928 document
.getElementById('noVNC_xvp_button')
929 .classList
.add("noVNC_selected");
932 closeXvpPanel: function() {
933 document
.getElementById('noVNC_xvp')
934 .classList
.remove("noVNC_open");
935 document
.getElementById('noVNC_xvp_button')
936 .classList
.remove("noVNC_selected");
939 toggleXvpPanel: function() {
940 if (document
.getElementById('noVNC_xvp')
941 .classList
.contains("noVNC_open")) {
948 // Disable/enable XVP button
949 updateXvpButton: function(ver
) {
950 if (ver
>= 1 && !UI
.rfb
.get_view_only()) {
951 document
.getElementById('noVNC_xvp_button')
952 .classList
.remove("noVNC_hidden");
954 document
.getElementById('noVNC_xvp_button')
955 .classList
.add("noVNC_hidden");
956 // Close XVP panel if open
967 openClipboardPanel: function() {
971 document
.getElementById('noVNC_clipboard')
972 .classList
.add("noVNC_open");
973 document
.getElementById('noVNC_clipboard_button')
974 .classList
.add("noVNC_selected");
977 closeClipboardPanel: function() {
978 document
.getElementById('noVNC_clipboard')
979 .classList
.remove("noVNC_open");
980 document
.getElementById('noVNC_clipboard_button')
981 .classList
.remove("noVNC_selected");
984 toggleClipboardPanel: function() {
985 if (document
.getElementById('noVNC_clipboard')
986 .classList
.contains("noVNC_open")) {
987 UI
.closeClipboardPanel();
989 UI
.openClipboardPanel();
993 clipboardReceive: function(rfb
, text
) {
994 Log
.Debug(">> UI.clipboardReceive: " + text
.substr(0,40) + "...");
995 document
.getElementById('noVNC_clipboard_text').value
= text
;
996 Log
.Debug("<< UI.clipboardReceive");
999 clipboardClear: function() {
1000 document
.getElementById('noVNC_clipboard_text').value
= "";
1001 UI
.rfb
.clipboardPasteFrom("");
1004 clipboardSend: function() {
1005 var text
= document
.getElementById('noVNC_clipboard_text').value
;
1006 Log
.Debug(">> UI.clipboardSend: " + text
.substr(0,40) + "...");
1007 UI
.rfb
.clipboardPasteFrom(text
);
1008 Log
.Debug("<< UI.clipboardSend");
1017 openConnectPanel: function() {
1018 document
.getElementById('noVNC_connect_dlg')
1019 .classList
.add("noVNC_open");
1022 closeConnectPanel: function() {
1023 document
.getElementById('noVNC_connect_dlg')
1024 .classList
.remove("noVNC_open");
1027 connect: function(event
, password
) {
1028 var host
= UI
.getSetting('host');
1029 var port
= UI
.getSetting('port');
1030 var path
= UI
.getSetting('path');
1032 if (typeof password
=== 'undefined') {
1033 password
= WebUtil
.getConfigVar('password');
1036 if (password
=== null) {
1037 password
= undefined;
1040 if ((!host
) || (!port
)) {
1041 var msg
= _("Must set host and port");
1043 UI
.showStatus(msg
, 'error');
1047 if (!UI
.initRFB()) return;
1049 UI
.closeAllPanels();
1050 UI
.closeConnectPanel();
1052 UI
.rfb
.set_encrypt(UI
.getSetting('encrypt'));
1053 UI
.rfb
.set_true_color(UI
.getSetting('true_color'));
1054 UI
.rfb
.set_shared(UI
.getSetting('shared'));
1055 UI
.rfb
.set_repeaterID(UI
.getSetting('repeaterID'));
1057 UI
.updateLocalCursor();
1058 UI
.updateViewOnly();
1060 UI
.rfb
.connect(host
, port
, password
, path
);
1063 disconnect: function() {
1064 UI
.closeAllPanels();
1065 UI
.rfb
.disconnect();
1067 // Disable automatic reconnecting
1068 UI
.inhibit_reconnect
= true;
1070 // Restore the callback used for initial resize
1071 UI
.rfb
.set_onFBUComplete(UI
.initialResize
);
1073 // Don't display the connection settings until we're actually disconnected
1076 reconnect: function() {
1077 UI
.reconnect_callback
= null;
1079 // if reconnect has been disabled in the meantime, do nothing.
1080 if (UI
.inhibit_reconnect
) {
1084 UI
.connect(null, UI
.reconnect_password
);
1087 disconnectFinished: function (rfb
, reason
) {
1088 if (typeof reason
!== 'undefined') {
1089 UI
.showStatus(reason
, 'error');
1090 } else if (UI
.getSetting('reconnect', false) === true && !UI
.inhibit_reconnect
) {
1091 document
.getElementById("noVNC_transition_text").textContent
= _("Reconnecting...");
1092 document
.documentElement
.classList
.add("noVNC_reconnecting");
1094 var delay
= parseInt(UI
.getSetting('reconnect_delay'));
1095 UI
.reconnect_callback
= setTimeout(UI
.reconnect
, delay
);
1099 UI
.openControlbar();
1100 UI
.openConnectPanel();
1103 cancelReconnect: function() {
1104 if (UI
.reconnect_callback
!== null) {
1105 clearTimeout(UI
.reconnect_callback
);
1106 UI
.reconnect_callback
= null;
1109 document
.documentElement
.classList
.remove("noVNC_reconnecting");
1110 UI
.openControlbar();
1111 UI
.openConnectPanel();
1120 passwordRequired: function(rfb
, msg
) {
1122 document
.getElementById('noVNC_password_dlg')
1123 .classList
.add('noVNC_open');
1125 setTimeout(function () {
1126 document
.getElementById('noVNC_password_input').focus();
1129 if (typeof msg
=== 'undefined') {
1130 msg
= _("Password is required");
1133 UI
.showStatus(msg
, "warning");
1136 setPassword: function(e
) {
1137 var password
= document
.getElementById('noVNC_password_input').value
;
1138 UI
.rfb
.sendPassword(password
);
1139 UI
.reconnect_password
= password
;
1140 document
.getElementById('noVNC_password_dlg')
1141 .classList
.remove('noVNC_open');
1142 // Prevent actually submitting the form
1152 toggleFullscreen: function() {
1153 if (document
.fullscreenElement
|| // alternative standard method
1154 document
.mozFullScreenElement
|| // currently working methods
1155 document
.webkitFullscreenElement
||
1156 document
.msFullscreenElement
) {
1157 if (document
.exitFullscreen
) {
1158 document
.exitFullscreen();
1159 } else if (document
.mozCancelFullScreen
) {
1160 document
.mozCancelFullScreen();
1161 } else if (document
.webkitExitFullscreen
) {
1162 document
.webkitExitFullscreen();
1163 } else if (document
.msExitFullscreen
) {
1164 document
.msExitFullscreen();
1167 if (document
.documentElement
.requestFullscreen
) {
1168 document
.documentElement
.requestFullscreen();
1169 } else if (document
.documentElement
.mozRequestFullScreen
) {
1170 document
.documentElement
.mozRequestFullScreen();
1171 } else if (document
.documentElement
.webkitRequestFullscreen
) {
1172 document
.documentElement
.webkitRequestFullscreen(Element
.ALLOW_KEYBOARD_INPUT
);
1173 } else if (document
.body
.msRequestFullscreen
) {
1174 document
.body
.msRequestFullscreen();
1177 UI
.enableDisableViewClip();
1178 UI
.updateFullscreenButton();
1181 updateFullscreenButton: function() {
1182 if (document
.fullscreenElement
|| // alternative standard method
1183 document
.mozFullScreenElement
|| // currently working methods
1184 document
.webkitFullscreenElement
||
1185 document
.msFullscreenElement
) {
1186 document
.getElementById('noVNC_fullscreen_button')
1187 .classList
.add("noVNC_selected");
1189 document
.getElementById('noVNC_fullscreen_button')
1190 .classList
.remove("noVNC_selected");
1200 // Apply remote resizing or local scaling
1201 applyResizeMode: function() {
1202 if (!UI
.rfb
) return;
1204 var screen
= UI
.screenSize();
1206 if (screen
&& UI
.connected
&& UI
.rfb
.get_display()) {
1208 var display
= UI
.rfb
.get_display();
1209 var resizeMode
= UI
.getSetting('resize');
1210 display
.set_scale(1);
1212 // Make sure the viewport is adjusted first
1213 UI
.updateViewClip();
1215 if (resizeMode
=== 'remote') {
1217 // Request changing the resolution of the remote display to
1218 // the size of the local browser viewport.
1220 // In order to not send multiple requests before the browser-resize
1221 // is finished we wait 0.5 seconds before sending the request.
1222 clearTimeout(UI
.resizeTimeout
);
1223 UI
.resizeTimeout
= setTimeout(function(){
1224 // Request a remote size covering the viewport
1225 if (UI
.rfb
.requestDesktopSize(screen
.w
, screen
.h
)) {
1226 Log
.Debug('Requested new desktop size: ' +
1227 screen
.w
+ 'x' + screen
.h
);
1231 } else if (resizeMode
=== 'scale' || resizeMode
=== 'downscale') {
1232 var downscaleOnly
= resizeMode
=== 'downscale';
1233 display
.autoscale(screen
.w
, screen
.h
, downscaleOnly
);
1239 // Gets the the size of the available viewport in the browser window
1240 screenSize: function() {
1241 var screen
= document
.getElementById('noVNC_screen');
1242 return {w
: screen
.offsetWidth
, h
: screen
.offsetHeight
};
1245 // Normally we only apply the current resize mode after a window resize
1246 // event. This means that when a new connection is opened, there is no
1247 // resize mode active.
1248 // We have to wait until the first FBU because this is where the client
1249 // will find the supported encodings of the server. Some calls later in
1250 // the chain is dependant on knowing the server-capabilities.
1251 initialResize: function(rfb
, fbu
) {
1252 UI
.applyResizeMode();
1253 // After doing this once, we remove the callback.
1254 UI
.rfb
.set_onFBUComplete(function() { });
1263 // Set and configure viewport clipping
1264 setViewClip: function(clip
) {
1265 UI
.updateSetting('clip', clip
);
1266 UI
.updateViewClip();
1269 // Update parameters that depend on the clip setting
1270 updateViewClip: function() {
1271 if (!UI
.rfb
) return;
1273 var display
= UI
.rfb
.get_display();
1274 var cur_clip
= display
.get_viewport();
1275 var new_clip
= UI
.getSetting('clip');
1277 var resizeSetting
= UI
.getSetting('resize');
1278 if (resizeSetting
=== 'downscale' || resizeSetting
=== 'scale') {
1279 // Disable clipping if we are scaling
1281 } else if (isTouchDevice
) {
1282 // Touch devices usually have shit scrollbars
1286 if (cur_clip
!== new_clip
) {
1287 display
.set_viewport(new_clip
);
1290 var size
= UI
.screenSize();
1292 if (new_clip
&& size
) {
1293 // When clipping is enabled, the screen is limited to
1294 // the size of the browser window.
1295 display
.viewportChangeSize(size
.w
, size
.h
);
1299 // Changing the viewport may change the state of
1300 // the dragging button
1301 UI
.updateViewDrag();
1304 // Handle special cases where clipping is forced on/off or locked
1305 enableDisableViewClip: function() {
1306 var resizeSetting
= UI
.getSetting('resize');
1307 // Disable clipping if we are scaling, connected or on touch
1308 if (resizeSetting
=== 'downscale' || resizeSetting
=== 'scale' ||
1310 UI
.disableSetting('clip');
1312 UI
.enableSetting('clip');
1322 toggleViewDrag: function() {
1323 if (!UI
.rfb
) return;
1325 var drag
= UI
.rfb
.get_viewportDrag();
1326 UI
.setViewDrag(!drag
);
1329 // Set the view drag mode which moves the viewport on mouse drags
1330 setViewDrag: function(drag
) {
1331 if (!UI
.rfb
) return;
1333 UI
.rfb
.set_viewportDrag(drag
);
1335 UI
.updateViewDrag();
1338 updateViewDrag: function() {
1339 var clipping
= false;
1341 if (!UI
.connected
) return;
1343 // Check if viewport drag is possible. It is only possible
1344 // if the remote display is clipping the client display.
1345 if (UI
.rfb
.get_display().get_viewport() &&
1346 UI
.rfb
.get_display().clippingDisplay()) {
1350 var viewDragButton
= document
.getElementById('noVNC_view_drag_button');
1353 UI
.rfb
.get_viewportDrag()) {
1354 // The size of the remote display is the same or smaller
1355 // than the client display. Make sure viewport drag isn't
1356 // active when it can't be used.
1357 UI
.rfb
.set_viewportDrag(false);
1360 if (UI
.rfb
.get_viewportDrag()) {
1361 viewDragButton
.classList
.add("noVNC_selected");
1363 viewDragButton
.classList
.remove("noVNC_selected");
1366 // Different behaviour for touch vs non-touch
1367 // The button is disabled instead of hidden on touch devices
1368 if (isTouchDevice
) {
1369 viewDragButton
.classList
.remove("noVNC_hidden");
1372 viewDragButton
.disabled
= false;
1374 viewDragButton
.disabled
= true;
1377 viewDragButton
.disabled
= false;
1380 viewDragButton
.classList
.remove("noVNC_hidden");
1382 viewDragButton
.classList
.add("noVNC_hidden");
1393 showVirtualKeyboard: function() {
1394 if (!isTouchDevice
) return;
1396 var input
= document
.getElementById('noVNC_keyboardinput');
1398 if (document
.activeElement
== input
) return;
1403 var l
= input
.value
.length
;
1404 // Move the caret to the end
1405 input
.setSelectionRange(l
, l
);
1406 } catch (err
) {} // setSelectionRange is undefined in Google Chrome
1409 hideVirtualKeyboard: function() {
1410 if (!isTouchDevice
) return;
1412 var input
= document
.getElementById('noVNC_keyboardinput');
1414 if (document
.activeElement
!= input
) return;
1419 toggleVirtualKeyboard: function () {
1420 if (document
.getElementById('noVNC_keyboard_button')
1421 .classList
.contains("noVNC_selected")) {
1422 UI
.hideVirtualKeyboard();
1424 UI
.showVirtualKeyboard();
1428 onfocusVirtualKeyboard: function(event
) {
1429 document
.getElementById('noVNC_keyboard_button')
1430 .classList
.add("noVNC_selected");
1433 onblurVirtualKeyboard: function(event
) {
1434 document
.getElementById('noVNC_keyboard_button')
1435 .classList
.remove("noVNC_selected");
1438 keepVirtualKeyboard: function(event
) {
1439 var input
= document
.getElementById('noVNC_keyboardinput');
1441 // Only prevent focus change if the virtual keyboard is active
1442 if (document
.activeElement
!= input
) {
1446 // Only allow focus to move to other elements that need
1447 // focus to function properly
1448 if (event
.target
.form
!== undefined) {
1449 switch (event
.target
.type
) {
1458 case 'select-multiple':
1463 event
.preventDefault();
1466 keyboardinputReset: function() {
1467 var kbi
= document
.getElementById('noVNC_keyboardinput');
1468 kbi
.value
= new Array(UI
.defaultKeyboardinputLen
).join("_");
1469 UI
.lastKeyboardinput
= kbi
.value
;
1472 // When normal keyboard events are left uncought, use the input events from
1473 // the keyboardinput element instead and generate the corresponding key events.
1474 // This code is required since some browsers on Android are inconsistent in
1475 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1476 keyInput: function(event
) {
1478 if (!UI
.rfb
) return;
1480 var newValue
= event
.target
.value
;
1482 if (!UI
.lastKeyboardinput
) {
1483 UI
.keyboardinputReset();
1485 var oldValue
= UI
.lastKeyboardinput
;
1489 // Try to check caret position since whitespace at the end
1490 // will not be considered by value.length in some browsers
1491 newLen
= Math
.max(event
.target
.selectionStart
, newValue
.length
);
1493 // selectionStart is undefined in Google Chrome
1494 newLen
= newValue
.length
;
1496 var oldLen
= oldValue
.length
;
1499 var inputs
= newLen
- oldLen
;
1501 backspaces
= -inputs
;
1506 // Compare the old string with the new to account for
1507 // text-corrections or other input that modify existing text
1509 for (i
= 0; i
< Math
.min(oldLen
, newLen
); i
++) {
1510 if (newValue
.charAt(i
) != oldValue
.charAt(i
)) {
1511 inputs
= newLen
- i
;
1512 backspaces
= oldLen
- i
;
1517 // Send the key events
1518 for (i
= 0; i
< backspaces
; i
++) {
1519 UI
.rfb
.sendKey(KeyTable
.XK_BackSpace
);
1521 for (i
= newLen
- inputs
; i
< newLen
; i
++) {
1522 UI
.rfb
.sendKey(keysyms
.fromUnicode(newValue
.charCodeAt(i
)).keysym
);
1525 // Control the text content length in the keyboardinput element
1526 if (newLen
> 2 * UI
.defaultKeyboardinputLen
) {
1527 UI
.keyboardinputReset();
1528 } else if (newLen
< 1) {
1529 // There always have to be some text in the keyboardinput
1530 // element with which backspace can interact.
1531 UI
.keyboardinputReset();
1532 // This sometimes causes the keyboard to disappear for a second
1533 // but it is required for the android keyboard to recognize that
1534 // text has been added to the field
1535 event
.target
.blur();
1536 // This has to be ran outside of the input handler in order to work
1537 setTimeout(event
.target
.focus
.bind(event
.target
), 0);
1539 UI
.lastKeyboardinput
= newValue
;
1549 openExtraKeys: function() {
1550 UI
.closeAllPanels();
1551 UI
.openControlbar();
1553 document
.getElementById('noVNC_modifiers')
1554 .classList
.add("noVNC_open");
1555 document
.getElementById('noVNC_toggle_extra_keys_button')
1556 .classList
.add("noVNC_selected");
1559 closeExtraKeys: function() {
1560 document
.getElementById('noVNC_modifiers')
1561 .classList
.remove("noVNC_open");
1562 document
.getElementById('noVNC_toggle_extra_keys_button')
1563 .classList
.remove("noVNC_selected");
1566 toggleExtraKeys: function() {
1567 if(document
.getElementById('noVNC_modifiers')
1568 .classList
.contains("noVNC_open")) {
1569 UI
.closeExtraKeys();
1575 sendEsc: function() {
1576 UI
.rfb
.sendKey(KeyTable
.XK_Escape
);
1579 sendTab: function() {
1580 UI
.rfb
.sendKey(KeyTable
.XK_Tab
);
1583 toggleCtrl: function() {
1584 var btn
= document
.getElementById('noVNC_toggle_ctrl_button');
1585 if (btn
.classList
.contains("noVNC_selected")) {
1586 UI
.rfb
.sendKey(KeyTable
.XK_Control_L
, false);
1587 btn
.classList
.remove("noVNC_selected");
1589 UI
.rfb
.sendKey(KeyTable
.XK_Control_L
, true);
1590 btn
.classList
.add("noVNC_selected");
1594 toggleAlt: function() {
1595 var btn
= document
.getElementById('noVNC_toggle_alt_button');
1596 if (btn
.classList
.contains("noVNC_selected")) {
1597 UI
.rfb
.sendKey(KeyTable
.XK_Alt_L
, false);
1598 btn
.classList
.remove("noVNC_selected");
1600 UI
.rfb
.sendKey(KeyTable
.XK_Alt_L
, true);
1601 btn
.classList
.add("noVNC_selected");
1605 sendCtrlAltDel: function() {
1606 UI
.rfb
.sendCtrlAltDel();
1615 setMouseButton: function(num
) {
1616 var view_only
= UI
.rfb
.get_view_only();
1617 if (UI
.rfb
&& !view_only
) {
1618 UI
.rfb
.get_mouse().set_touchButton(num
);
1621 var blist
= [0, 1,2,4];
1622 for (var b
= 0; b
< blist
.length
; b
++) {
1623 var button
= document
.getElementById('noVNC_mouse_button' +
1625 if (blist
[b
] === num
&& !view_only
) {
1626 button
.classList
.remove("noVNC_hidden");
1628 button
.classList
.add("noVNC_hidden");
1633 displayBlur: function() {
1634 if (UI
.rfb
&& !UI
.rfb
.get_view_only()) {
1635 UI
.rfb
.get_keyboard().set_focused(false);
1636 UI
.rfb
.get_mouse().set_focused(false);
1640 displayFocus: function() {
1641 if (UI
.rfb
&& !UI
.rfb
.get_view_only()) {
1642 UI
.rfb
.get_keyboard().set_focused(true);
1643 UI
.rfb
.get_mouse().set_focused(true);
1647 updateLocalCursor: function() {
1648 if (!UI
.rfb
) return;
1649 UI
.rfb
.set_local_cursor(UI
.getSetting('cursor'));
1652 updateViewOnly: function() {
1653 if (!UI
.rfb
) return;
1654 UI
.rfb
.set_view_only(UI
.getSetting('view_only'));
1657 updateLogging: function() {
1658 WebUtil
.init_logging(UI
.getSetting('logging'));
1661 updateSessionSize: function(rfb
, width
, height
) {
1662 UI
.updateViewClip();
1666 fixScrollbars: function() {
1667 // This is a hack because Chrome screws up the calculation
1668 // for when scrollbars are needed. So to fix it we temporarily
1669 // toggle them off and on.
1670 var screen
= document
.getElementById('noVNC_screen');
1671 screen
.style
.overflow
= 'hidden';
1672 // Force Chrome to recalculate the layout by asking for
1673 // an element's dimensions
1674 screen
.getBoundingClientRect();
1675 screen
.style
.overflow
= null;
1678 updateDesktopName: function(rfb
, name
) {
1679 UI
.desktopName
= name
;
1680 // Display the desktop name in the document title
1681 document
.title
= name
+ " - noVNC";
1684 bell: function(rfb
) {
1685 if (WebUtil
.getConfigVar('bell', 'on') === 'on') {
1686 document
.getElementById('noVNC_bell').play();
1690 //Helper to add options to dropdown.
1691 addOption: function(selectbox
, text
, value
) {
1692 var optn
= document
.createElement("OPTION");
1695 selectbox
.options
.add(optn
);
1704 // Set up translations
1705 var LINGUAS
= ["de", "el", "nl", "sv"];
1706 l10n
.setup(LINGUAS
);
1707 if (l10n
.language
!== "en" && l10n
.dictionary
=== undefined) {
1708 WebUtil
.fetchJSON('app/locale/' + l10n
.language
+ '.json', function (translations
) {
1709 l10n
.dictionary
= translations
;
1711 // wait for translations to load before loading the UI