]> git.proxmox.com Git - mirror_novnc.git/blame - app/ui.js
Merge pull request #1011 from juanjoDiaz/remove_console_statements
[mirror_novnc.git] / app / ui.js
CommitLineData
15046f00
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
777cb7a0 4 * Copyright (C) 2016 Samuel Mannehed for Cendio AB
6cba147d 5 * Copyright (C) 2016 Pierre Ossman for Cendio AB
1d728ace 6 * Licensed under MPL 2.0 (see LICENSE.txt)
15046f00
JM
7 *
8 * See README.md for usage and integration instructions.
9 */
43cf7bd8 10
bbbf42bb 11/* jslint white: false, browser: true */
ae510306
SR
12/* global window, document.getElementById, Util, WebUtil, RFB, Display */
13
6d6f0db0 14import * as Log from '../core/util/logging.js';
7279364c 15import _, { l10n } from './localization.js';
8d1f0a3d 16import { isTouchDevice } from '../core/util/browsers.js';
6d6f0db0 17import { setCapture, getPointerEvent } from '../core/util/events.js';
3ae0bb09
SR
18import KeyTable from "../core/input/keysym.js";
19import keysyms from "../core/input/keysymdef.js";
867daa98 20import Keyboard from "../core/input/keyboard.js";
3ae0bb09
SR
21import RFB from "../core/rfb.js";
22import Display from "../core/display.js";
6d6f0db0 23import * as WebUtil from "./webutil.js";
bbbf42bb 24
858ea4a7 25var UI = {
6d6f0db0
SR
26
27 connected: false,
28 desktopName: "",
29
6d6f0db0
SR
30 statusTimeout: null,
31 hideKeyboardTimeout: null,
32 idleControlbarTimeout: null,
33 closeControlbarTimeout: null,
34
35 controlbarGrabbed: false,
36 controlbarDrag: false,
37 controlbarMouseDownClientY: 0,
38 controlbarMouseDownOffsetY: 0,
39
40 isSafari: false,
6d6f0db0
SR
41 lastKeyboardinput: null,
42 defaultKeyboardinputLen: 100,
43
44 inhibit_reconnect: true,
45 reconnect_callback: null,
46 reconnect_password: null,
47
48 prime: function(callback) {
49 if (document.readyState === "interactive" || document.readyState === "complete") {
50 UI.load(callback);
51 } else {
52 document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
53 }
54 },
529c64e1 55
6d6f0db0
SR
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);
60 },
529c64e1 61
6d6f0db0
SR
62 // Render default UI and initialize settings menu
63 start: function(callback) {
044d54ed 64
6d6f0db0
SR
65 // Setup global variables first
66 UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
67 navigator.userAgent.indexOf('Chrome') === -1);
bbbf42bb 68
6d6f0db0 69 UI.initSettings();
0f6af1e3 70
6d6f0db0
SR
71 // Translate the DOM
72 l10n.translateDOM();
0f6af1e3 73
6d6f0db0
SR
74 // Adapt the interface for touch screen devices
75 if (isTouchDevice) {
76 document.documentElement.classList.add("noVNC_touch");
77 // Remove the address bar
78 setTimeout(function() { window.scrollTo(0, 1); }, 100);
79 }
0f6af1e3 80
6d6f0db0
SR
81 // Restore control bar position
82 if (WebUtil.readSetting('controlbar_pos') === 'right') {
83 UI.toggleControlbarSide();
84 }
edffd9e2 85
6d6f0db0 86 UI.initFullscreen();
0f6af1e3 87
6d6f0db0 88 // Setup event handlers
6d6f0db0
SR
89 UI.addControlbarHandlers();
90 UI.addTouchSpecificHandlers();
91 UI.addExtraKeysHandlers();
cd523e8f 92 UI.addMachineHandlers();
6d6f0db0
SR
93 UI.addConnectionControlHandlers();
94 UI.addClipboardHandlers();
95 UI.addSettingsHandlers();
96 document.getElementById("noVNC_status")
97 .addEventListener('click', UI.hideStatus);
cf348b78 98
55988e7a
PO
99 // Bootstrap fallback input handler
100 UI.keyboardinputReset();
101
6d6f0db0 102 UI.openControlbar();
b3d91b78 103
ee5cae9f 104 UI.updateVisualState('init');
b3c932c3 105
e25f9c40 106 document.documentElement.classList.remove("noVNC_loading");
0f6af1e3 107
6d6f0db0
SR
108 var autoconnect = WebUtil.getConfigVar('autoconnect', false);
109 if (autoconnect === 'true' || autoconnect == '1') {
110 autoconnect = true;
111 UI.connect();
112 } else {
113 autoconnect = false;
067accb8
GK
114 // Show the connect panel on first load unless autoconnecting
115 UI.openConnectPanel();
6d6f0db0 116 }
0f6af1e3 117
6d6f0db0
SR
118 if (typeof callback === "function") {
119 callback(UI.rfb);
120 }
121 },
122
123 initFullscreen: function() {
124 // Only show the button if fullscreen is properly supported
125 // * Safari doesn't support alphanumerical input while in fullscreen
126 if (!UI.isSafari &&
127 (document.documentElement.requestFullscreen ||
128 document.documentElement.mozRequestFullScreen ||
129 document.documentElement.webkitRequestFullscreen ||
130 document.body.msRequestFullscreen)) {
131 document.getElementById('noVNC_fullscreen_button')
132 .classList.remove("noVNC_hidden");
133 UI.addFullscreenHandlers();
134 }
135 },
0f6af1e3 136
6d6f0db0
SR
137 initSettings: function() {
138 var i;
0f6af1e3 139
6d6f0db0
SR
140 // Logging selection dropdown
141 var llevels = ['error', 'warn', 'info', 'debug'];
142 for (i = 0; i < llevels.length; i += 1) {
143 UI.addOption(document.getElementById('noVNC_setting_logging'),llevels[i], llevels[i]);
144 }
b3d91b78 145
6d6f0db0
SR
146 // Settings with immediate effects
147 UI.initSetting('logging', 'warn');
148 UI.updateLogging();
bbbf42bb 149
6d6f0db0
SR
150 // if port == 80 (or 443) then it won't be present and should be
151 // set manually
152 var port = window.location.port;
153 if (!port) {
154 if (window.location.protocol.substring(0,5) == 'https') {
155 port = 443;
bbbf42bb 156 }
6d6f0db0
SR
157 else if (window.location.protocol.substring(0,4) == 'http') {
158 port = 80;
bbbf42bb 159 }
6d6f0db0 160 }
bbbf42bb 161
6d6f0db0
SR
162 /* Populate the controls if defaults are provided in the URL */
163 UI.initSetting('host', window.location.hostname);
164 UI.initSetting('port', port);
165 UI.initSetting('encrypt', (window.location.protocol === "https:"));
a49ade5f 166 UI.initSetting('view_clip', false);
6d6f0db0
SR
167 UI.initSetting('resize', 'off');
168 UI.initSetting('shared', true);
169 UI.initSetting('view_only', false);
170 UI.initSetting('path', 'websockify');
171 UI.initSetting('repeaterID', '');
172 UI.initSetting('reconnect', false);
173 UI.initSetting('reconnect_delay', 5000);
174
175 UI.setupSettingLabels();
176 },
177 // Adds a link to the label elements on the corresponding input elements
178 setupSettingLabels: function() {
179 var labels = document.getElementsByTagName('LABEL');
180 for (var i = 0; i < labels.length; i++) {
181 var htmlFor = labels[i].htmlFor;
182 if (htmlFor != '') {
183 var elem = document.getElementById(htmlFor);
184 if (elem) elem.label = labels[i];
185 } else {
186 // If 'for' isn't set, use the first input element child
187 var children = labels[i].children;
188 for (var j = 0; j < children.length; j++) {
189 if (children[j].form !== undefined) {
190 children[j].label = labels[i];
191 break;
24584cca
SM
192 }
193 }
194 }
6d6f0db0
SR
195 }
196 },
59387b34 197
6d6f0db0
SR
198/* ------^-------
199* /INIT
200* ==============
201* EVENT HANDLERS
202* ------v------*/
203
6d6f0db0
SR
204 addControlbarHandlers: function() {
205 document.getElementById("noVNC_control_bar")
206 .addEventListener('mousemove', UI.activateControlbar);
207 document.getElementById("noVNC_control_bar")
208 .addEventListener('mouseup', UI.activateControlbar);
209 document.getElementById("noVNC_control_bar")
210 .addEventListener('mousedown', UI.activateControlbar);
211 document.getElementById("noVNC_control_bar")
06fe4a3e 212 .addEventListener('keydown', UI.activateControlbar);
6d6f0db0
SR
213
214 document.getElementById("noVNC_control_bar")
215 .addEventListener('mousedown', UI.keepControlbar);
216 document.getElementById("noVNC_control_bar")
06fe4a3e 217 .addEventListener('keydown', UI.keepControlbar);
6d6f0db0
SR
218
219 document.getElementById("noVNC_view_drag_button")
220 .addEventListener('click', UI.toggleViewDrag);
221
222 document.getElementById("noVNC_control_bar_handle")
223 .addEventListener('mousedown', UI.controlbarHandleMouseDown);
224 document.getElementById("noVNC_control_bar_handle")
225 .addEventListener('mouseup', UI.controlbarHandleMouseUp);
226 document.getElementById("noVNC_control_bar_handle")
227 .addEventListener('mousemove', UI.dragControlbarHandle);
228 // resize events aren't available for elements
229 window.addEventListener('resize', UI.updateControlbarHandle);
230
231 var exps = document.getElementsByClassName("noVNC_expander");
232 for (var i = 0;i < exps.length;i++) {
233 exps[i].addEventListener('click', UI.toggleExpander);
234 }
235 },
236
237 addTouchSpecificHandlers: function() {
238 document.getElementById("noVNC_mouse_button0")
239 .addEventListener('click', function () { UI.setMouseButton(1); });
240 document.getElementById("noVNC_mouse_button1")
241 .addEventListener('click', function () { UI.setMouseButton(2); });
242 document.getElementById("noVNC_mouse_button2")
243 .addEventListener('click', function () { UI.setMouseButton(4); });
244 document.getElementById("noVNC_mouse_button4")
245 .addEventListener('click', function () { UI.setMouseButton(0); });
246 document.getElementById("noVNC_keyboard_button")
247 .addEventListener('click', UI.toggleVirtualKeyboard);
248
747b4623
PO
249 UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
250 UI.touchKeyboard.onkeyevent = UI.keyEvent;
867daa98 251 UI.touchKeyboard.grab();
6d6f0db0
SR
252 document.getElementById("noVNC_keyboardinput")
253 .addEventListener('input', UI.keyInput);
254 document.getElementById("noVNC_keyboardinput")
255 .addEventListener('focus', UI.onfocusVirtualKeyboard);
256 document.getElementById("noVNC_keyboardinput")
257 .addEventListener('blur', UI.onblurVirtualKeyboard);
258 document.getElementById("noVNC_keyboardinput")
259 .addEventListener('submit', function () { return false; });
260
261 document.documentElement
262 .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
263
264 document.getElementById("noVNC_control_bar")
265 .addEventListener('touchstart', UI.activateControlbar);
266 document.getElementById("noVNC_control_bar")
267 .addEventListener('touchmove', UI.activateControlbar);
268 document.getElementById("noVNC_control_bar")
269 .addEventListener('touchend', UI.activateControlbar);
270 document.getElementById("noVNC_control_bar")
271 .addEventListener('input', UI.activateControlbar);
272
273 document.getElementById("noVNC_control_bar")
274 .addEventListener('touchstart', UI.keepControlbar);
275 document.getElementById("noVNC_control_bar")
276 .addEventListener('input', UI.keepControlbar);
277
278 document.getElementById("noVNC_control_bar_handle")
279 .addEventListener('touchstart', UI.controlbarHandleMouseDown);
280 document.getElementById("noVNC_control_bar_handle")
281 .addEventListener('touchend', UI.controlbarHandleMouseUp);
282 document.getElementById("noVNC_control_bar_handle")
283 .addEventListener('touchmove', UI.dragControlbarHandle);
6d6f0db0
SR
284 },
285
286 addExtraKeysHandlers: function() {
287 document.getElementById("noVNC_toggle_extra_keys_button")
288 .addEventListener('click', UI.toggleExtraKeys);
289 document.getElementById("noVNC_toggle_ctrl_button")
290 .addEventListener('click', UI.toggleCtrl);
291 document.getElementById("noVNC_toggle_alt_button")
292 .addEventListener('click', UI.toggleAlt);
293 document.getElementById("noVNC_send_tab_button")
294 .addEventListener('click', UI.sendTab);
295 document.getElementById("noVNC_send_esc_button")
296 .addEventListener('click', UI.sendEsc);
297 document.getElementById("noVNC_send_ctrl_alt_del_button")
298 .addEventListener('click', UI.sendCtrlAltDel);
299 },
300
cd523e8f
PO
301 addMachineHandlers: function() {
302 document.getElementById("noVNC_shutdown_button")
303 .addEventListener('click', function() { UI.rfb.machineShutdown(); });
304 document.getElementById("noVNC_reboot_button")
305 .addEventListener('click', function() { UI.rfb.machineReboot(); });
306 document.getElementById("noVNC_reset_button")
307 .addEventListener('click', function() { UI.rfb.machineReset(); });
308 document.getElementById("noVNC_power_button")
309 .addEventListener('click', UI.togglePowerPanel);
6d6f0db0
SR
310 },
311
312 addConnectionControlHandlers: function() {
313 document.getElementById("noVNC_disconnect_button")
314 .addEventListener('click', UI.disconnect);
315 document.getElementById("noVNC_connect_button")
316 .addEventListener('click', UI.connect);
317 document.getElementById("noVNC_cancel_reconnect_button")
318 .addEventListener('click', UI.cancelReconnect);
319
320 document.getElementById("noVNC_password_button")
321 .addEventListener('click', UI.setPassword);
322 },
323
324 addClipboardHandlers: function() {
325 document.getElementById("noVNC_clipboard_button")
326 .addEventListener('click', UI.toggleClipboardPanel);
6d6f0db0
SR
327 document.getElementById("noVNC_clipboard_text")
328 .addEventListener('change', UI.clipboardSend);
329 document.getElementById("noVNC_clipboard_clear_button")
330 .addEventListener('click', UI.clipboardClear);
331 },
332
333 // Add a call to save settings when the element changes,
334 // unless the optional parameter changeFunc is used instead.
335 addSettingChangeHandler: function(name, changeFunc) {
336 var settingElem = document.getElementById("noVNC_setting_" + name);
337 if (changeFunc === undefined) {
338 changeFunc = function () { UI.saveSetting(name); };
339 }
340 settingElem.addEventListener('change', changeFunc);
341 },
342
343 addSettingsHandlers: function() {
344 document.getElementById("noVNC_settings_button")
345 .addEventListener('click', UI.toggleSettingsPanel);
346
347 UI.addSettingChangeHandler('encrypt');
6d6f0db0
SR
348 UI.addSettingChangeHandler('resize');
349 UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
350 UI.addSettingChangeHandler('resize', UI.applyResizeMode);
a49ade5f
SM
351 UI.addSettingChangeHandler('view_clip');
352 UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
6d6f0db0
SR
353 UI.addSettingChangeHandler('shared');
354 UI.addSettingChangeHandler('view_only');
355 UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
356 UI.addSettingChangeHandler('host');
357 UI.addSettingChangeHandler('port');
358 UI.addSettingChangeHandler('path');
359 UI.addSettingChangeHandler('repeaterID');
360 UI.addSettingChangeHandler('logging');
361 UI.addSettingChangeHandler('logging', UI.updateLogging);
362 UI.addSettingChangeHandler('reconnect');
363 UI.addSettingChangeHandler('reconnect_delay');
364 },
365
366 addFullscreenHandlers: function() {
367 document.getElementById("noVNC_fullscreen_button")
368 .addEventListener('click', UI.toggleFullscreen);
369
370 window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
371 window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
372 window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
373 window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
374 },
0f6af1e3 375
95dd6001 376/* ------^-------
59387b34 377 * /EVENT HANDLERS
95dd6001 378 * ==============
379 * VISUAL
380 * ------v------*/
58ded70d 381
ee5cae9f
SM
382 // Disable/enable controls depending on connection state
383 updateVisualState: function(state) {
6d6f0db0
SR
384
385 document.documentElement.classList.remove("noVNC_connecting");
386 document.documentElement.classList.remove("noVNC_connected");
387 document.documentElement.classList.remove("noVNC_disconnecting");
388 document.documentElement.classList.remove("noVNC_reconnecting");
389
ee5cae9f
SM
390 let transition_elem = document.getElementById("noVNC_transition_text");
391 switch (state) {
392 case 'init':
393 break;
6d6f0db0 394 case 'connecting':
ee5cae9f 395 transition_elem.textContent = _("Connecting...");
6d6f0db0
SR
396 document.documentElement.classList.add("noVNC_connecting");
397 break;
398 case 'connected':
6d6f0db0 399 document.documentElement.classList.add("noVNC_connected");
6d6f0db0
SR
400 break;
401 case 'disconnecting':
ee5cae9f 402 transition_elem.textContent = _("Disconnecting...");
6d6f0db0
SR
403 document.documentElement.classList.add("noVNC_disconnecting");
404 break;
405 case 'disconnected':
6d6f0db0 406 break;
ee5cae9f
SM
407 case 'reconnecting':
408 transition_elem.textContent = _("Reconnecting...");
409 document.documentElement.classList.add("noVNC_reconnecting");
6d6f0db0 410 break;
6d6f0db0 411 default:
ee5cae9f 412 Log.Error("Invalid visual state: " + state);
2592c243 413 UI.showStatus(_("Internal error"), 'error');
ee5cae9f 414 return;
6d6f0db0 415 }
8d710e8b 416
6d6f0db0 417 UI.enableDisableViewClip();
fdedbafb 418
6d6f0db0
SR
419 if (UI.connected) {
420 UI.disableSetting('encrypt');
6d6f0db0
SR
421 UI.disableSetting('shared');
422 UI.disableSetting('host');
423 UI.disableSetting('port');
424 UI.disableSetting('path');
425 UI.disableSetting('repeaterID');
6d6f0db0
SR
426 UI.setMouseButton(1);
427
428 // Hide the controlbar after 2 seconds
429 UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
430 } else {
431 UI.enableSetting('encrypt');
6d6f0db0
SR
432 UI.enableSetting('shared');
433 UI.enableSetting('host');
434 UI.enableSetting('port');
435 UI.enableSetting('path');
436 UI.enableSetting('repeaterID');
cd523e8f 437 UI.updatePowerButton();
6d6f0db0
SR
438 UI.keepControlbar();
439 }
fdedbafb 440
6d6f0db0
SR
441 // State change disables viewport dragging.
442 // It is enabled (toggled) by direct click on the button
443 UI.setViewDrag(false);
fdedbafb 444
6d6f0db0
SR
445 // State change also closes the password dialog
446 document.getElementById('noVNC_password_dlg')
447 .classList.remove('noVNC_open');
6d6f0db0 448 },
29475d77 449
6d6f0db0
SR
450 showStatus: function(text, status_type, time) {
451 var statusElem = document.getElementById('noVNC_status');
8a7ec6ea 452
6d6f0db0 453 clearTimeout(UI.statusTimeout);
29475d77 454
6d6f0db0
SR
455 if (typeof status_type === 'undefined') {
456 status_type = 'normal';
457 }
4e471b5b 458
d623a029
SM
459 // Don't overwrite more severe visible statuses and never
460 // errors. Only shows the first error.
461 let visible_status_type = 'none';
462 if (statusElem.classList.contains("noVNC_open")) {
463 if (statusElem.classList.contains("noVNC_status_error")) {
464 visible_status_type = 'error';
465 } else if (statusElem.classList.contains("noVNC_status_warn")) {
466 visible_status_type = 'warn';
467 } else {
468 visible_status_type = 'normal';
469 }
470 }
471 if (visible_status_type === 'error' ||
472 (visible_status_type === 'warn' && status_type === 'normal')) {
473 return;
474 }
6d6f0db0
SR
475
476 switch (status_type) {
d623a029
SM
477 case 'error':
478 statusElem.classList.remove("noVNC_status_warn");
479 statusElem.classList.remove("noVNC_status_normal");
480 statusElem.classList.add("noVNC_status_error");
481 break;
6d6f0db0
SR
482 case 'warning':
483 case 'warn':
d623a029
SM
484 statusElem.classList.remove("noVNC_status_error");
485 statusElem.classList.remove("noVNC_status_normal");
6d6f0db0
SR
486 statusElem.classList.add("noVNC_status_warn");
487 break;
6d6f0db0
SR
488 case 'normal':
489 case 'info':
490 default:
d623a029
SM
491 statusElem.classList.remove("noVNC_status_error");
492 statusElem.classList.remove("noVNC_status_warn");
6d6f0db0
SR
493 statusElem.classList.add("noVNC_status_normal");
494 break;
495 }
8d7708c8 496
6d6f0db0
SR
497 statusElem.textContent = text;
498 statusElem.classList.add("noVNC_open");
8d7708c8 499
6d6f0db0
SR
500 // If no time was specified, show the status for 1.5 seconds
501 if (typeof time === 'undefined') {
502 time = 1500;
503 }
8d7708c8 504
6d6f0db0
SR
505 // Error messages do not timeout
506 if (status_type !== 'error') {
507 UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
508 }
509 },
510
511 hideStatus: function() {
512 clearTimeout(UI.statusTimeout);
513 document.getElementById('noVNC_status').classList.remove("noVNC_open");
514 },
515
6d6f0db0
SR
516 activateControlbar: function(event) {
517 clearTimeout(UI.idleControlbarTimeout);
518 // We manipulate the anchor instead of the actual control
519 // bar in order to avoid creating new a stacking group
520 document.getElementById('noVNC_control_bar_anchor')
521 .classList.remove("noVNC_idle");
522 UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
523 },
524
525 idleControlbar: function() {
526 document.getElementById('noVNC_control_bar_anchor')
527 .classList.add("noVNC_idle");
528 },
529
530 keepControlbar: function() {
531 clearTimeout(UI.closeControlbarTimeout);
532 },
533
534 openControlbar: function() {
535 document.getElementById('noVNC_control_bar')
536 .classList.add("noVNC_open");
537 },
538
539 closeControlbar: function() {
540 UI.closeAllPanels();
541 document.getElementById('noVNC_control_bar')
542 .classList.remove("noVNC_open");
543 },
544
545 toggleControlbar: function() {
546 if (document.getElementById('noVNC_control_bar')
547 .classList.contains("noVNC_open")) {
548 UI.closeControlbar();
549 } else {
550 UI.openControlbar();
551 }
552 },
553
554 toggleControlbarSide: function () {
555 // Temporarily disable animation to avoid weird movement
556 var bar = document.getElementById('noVNC_control_bar');
557 bar.style.transitionDuration = '0s';
558 bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
559
560 var anchor = document.getElementById('noVNC_control_bar_anchor');
561 if (anchor.classList.contains("noVNC_right")) {
562 WebUtil.writeSetting('controlbar_pos', 'left');
563 anchor.classList.remove("noVNC_right");
564 } else {
565 WebUtil.writeSetting('controlbar_pos', 'right');
566 anchor.classList.add("noVNC_right");
567 }
ca5c74ad 568
6d6f0db0
SR
569 // Consider this a movement of the handle
570 UI.controlbarDrag = true;
571 },
4e471b5b 572
bbc1648c
SM
573 showControlbarHint: function (show) {
574 var hint = document.getElementById('noVNC_control_bar_hint');
575 if (show) {
576 hint.classList.add("noVNC_active");
577 } else {
578 hint.classList.remove("noVNC_active");
579 }
580 },
581
6d6f0db0
SR
582 dragControlbarHandle: function (e) {
583 if (!UI.controlbarGrabbed) return;
38323d4d 584
6d6f0db0 585 var ptr = getPointerEvent(e);
8ee432f1 586
6d6f0db0
SR
587 var anchor = document.getElementById('noVNC_control_bar_anchor');
588 if (ptr.clientX < (window.innerWidth * 0.1)) {
cf348b78 589 if (anchor.classList.contains("noVNC_right")) {
6d6f0db0 590 UI.toggleControlbarSide();
8ee432f1 591 }
6d6f0db0
SR
592 } else if (ptr.clientX > (window.innerWidth * 0.9)) {
593 if (!anchor.classList.contains("noVNC_right")) {
594 UI.toggleControlbarSide();
04b399e2 595 }
6d6f0db0 596 }
04b399e2 597
6d6f0db0
SR
598 if (!UI.controlbarDrag) {
599 // The goal is to trigger on a certain physical width, the
600 // devicePixelRatio brings us a bit closer but is not optimal.
601 var dragThreshold = 10 * (window.devicePixelRatio || 1);
602 var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
04b399e2 603
6d6f0db0 604 if (dragDistance < dragThreshold) return;
04b399e2 605
6d6f0db0
SR
606 UI.controlbarDrag = true;
607 }
f75e4d3c 608
6d6f0db0 609 var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
04b399e2 610
6d6f0db0 611 UI.moveControlbarHandle(eventY);
59cd99bc 612
6d6f0db0
SR
613 e.preventDefault();
614 e.stopPropagation();
615 UI.keepControlbar();
616 UI.activateControlbar();
617 },
04b399e2 618
6d6f0db0
SR
619 // Move the handle but don't allow any position outside the bounds
620 moveControlbarHandle: function (viewportRelativeY) {
621 var handle = document.getElementById("noVNC_control_bar_handle");
622 var handleHeight = handle.getBoundingClientRect().height;
623 var controlbarBounds = document.getElementById("noVNC_control_bar")
624 .getBoundingClientRect();
625 var margin = 10;
04b399e2 626
6d6f0db0
SR
627 // These heights need to be non-zero for the below logic to work
628 if (handleHeight === 0 || controlbarBounds.height === 0) {
629 return;
630 }
04b399e2 631
6d6f0db0 632 var newY = viewportRelativeY;
04b399e2 633
6d6f0db0
SR
634 // Check if the coordinates are outside the control bar
635 if (newY < controlbarBounds.top + margin) {
636 // Force coordinates to be below the top of the control bar
637 newY = controlbarBounds.top + margin;
04b399e2 638
6d6f0db0
SR
639 } else if (newY > controlbarBounds.top +
640 controlbarBounds.height - handleHeight - margin) {
641 // Force coordinates to be above the bottom of the control bar
642 newY = controlbarBounds.top +
643 controlbarBounds.height - handleHeight - margin;
644 }
04b399e2 645
6d6f0db0
SR
646 // Corner case: control bar too small for stable position
647 if (controlbarBounds.height < (handleHeight + margin * 2)) {
648 newY = controlbarBounds.top +
649 (controlbarBounds.height - handleHeight) / 2;
650 }
04b399e2 651
6d6f0db0
SR
652 // The transform needs coordinates that are relative to the parent
653 var parentRelativeY = newY - controlbarBounds.top;
654 handle.style.transform = "translateY(" + parentRelativeY + "px)";
655 },
656
657 updateControlbarHandle: function () {
658 // Since the control bar is fixed on the viewport and not the page,
659 // the move function expects coordinates relative the the viewport.
660 var handle = document.getElementById("noVNC_control_bar_handle");
661 var handleBounds = handle.getBoundingClientRect();
662 UI.moveControlbarHandle(handleBounds.top);
663 },
664
665 controlbarHandleMouseUp: function(e) {
666 if ((e.type == "mouseup") && (e.button != 0)) return;
667
668 // mouseup and mousedown on the same place toggles the controlbar
669 if (UI.controlbarGrabbed && !UI.controlbarDrag) {
670 UI.toggleControlbar();
04b399e2
SM
671 e.preventDefault();
672 e.stopPropagation();
de315d62
PO
673 UI.keepControlbar();
674 UI.activateControlbar();
6d6f0db0
SR
675 }
676 UI.controlbarGrabbed = false;
bbc1648c 677 UI.showControlbarHint(false);
6d6f0db0
SR
678 },
679
680 controlbarHandleMouseDown: function(e) {
681 if ((e.type == "mousedown") && (e.button != 0)) return;
682
683 var ptr = getPointerEvent(e);
684
685 var handle = document.getElementById("noVNC_control_bar_handle");
686 var bounds = handle.getBoundingClientRect();
687
333ad45c
SM
688 // Touch events have implicit capture
689 if (e.type === "mousedown") {
690 setCapture(handle);
691 }
692
6d6f0db0
SR
693 UI.controlbarGrabbed = true;
694 UI.controlbarDrag = false;
695
bbc1648c
SM
696 UI.showControlbarHint(true);
697
6d6f0db0
SR
698 UI.controlbarMouseDownClientY = ptr.clientY;
699 UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
700 e.preventDefault();
701 e.stopPropagation();
702 UI.keepControlbar();
703 UI.activateControlbar();
704 },
705
706 toggleExpander: function(e) {
707 if (this.classList.contains("noVNC_open")) {
708 this.classList.remove("noVNC_open");
709 } else {
710 this.classList.add("noVNC_open");
711 }
712 },
575f6983 713
95dd6001 714/* ------^-------
715 * /VISUAL
716 * ==============
717 * SETTINGS
718 * ------v------*/
719
6d6f0db0
SR
720 // Initial page load read/initialization of settings
721 initSetting: function(name, defVal) {
722 // Check Query string followed by cookie
723 var val = WebUtil.getConfigVar(name);
724 if (val === null) {
725 val = WebUtil.readSetting(name, defVal);
726 }
727 UI.updateSetting(name, val);
728 return val;
729 },
bbbf42bb 730
6d6f0db0
SR
731 // Update cookie and form control setting. If value is not set, then
732 // updates from control to current cookie setting.
733 updateSetting: function(name, value) {
bbbf42bb 734
6d6f0db0
SR
735 // Save the cookie for this session
736 if (typeof value !== 'undefined') {
737 WebUtil.writeSetting(name, value);
738 }
bbbf42bb 739
6d6f0db0
SR
740 // Update the settings control
741 value = UI.getSetting(name);
bbbf42bb 742
6d6f0db0
SR
743 var ctrl = document.getElementById('noVNC_setting_' + name);
744 if (ctrl.type === 'checkbox') {
745 ctrl.checked = value;
bbbf42bb 746
6d6f0db0
SR
747 } else if (typeof ctrl.options !== 'undefined') {
748 for (var i = 0; i < ctrl.options.length; i += 1) {
749 if (ctrl.options[i].value === value) {
750 ctrl.selectedIndex = i;
751 break;
bbbf42bb 752 }
bbbf42bb 753 }
6d6f0db0
SR
754 } else {
755 /*Weird IE9 error leads to 'null' appearring
756 in textboxes instead of ''.*/
757 if (value === null) {
758 value = "";
bbbf42bb 759 }
6d6f0db0
SR
760 ctrl.value = value;
761 }
762 },
763
764 // Save control setting to cookie
765 saveSetting: function(name) {
766 var val, ctrl = document.getElementById('noVNC_setting_' + name);
767 if (ctrl.type === 'checkbox') {
768 val = ctrl.checked;
769 } else if (typeof ctrl.options !== 'undefined') {
770 val = ctrl.options[ctrl.selectedIndex].value;
771 } else {
772 val = ctrl.value;
773 }
774 WebUtil.writeSetting(name, val);
775 //Log.Debug("Setting saved '" + name + "=" + val + "'");
776 return val;
777 },
778
779 // Read form control compatible setting from cookie
780 getSetting: function(name) {
781 var ctrl = document.getElementById('noVNC_setting_' + name);
782 var val = WebUtil.readSetting(name);
783 if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
784 if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
785 val = false;
786 } else {
787 val = true;
bbbf42bb 788 }
6d6f0db0
SR
789 }
790 return val;
791 },
792
793 // These helpers compensate for the lack of parent-selectors and
794 // previous-sibling-selectors in CSS which are needed when we want to
795 // disable the labels that belong to disabled input elements.
796 disableSetting: function(name) {
797 var ctrl = document.getElementById('noVNC_setting_' + name);
798 ctrl.disabled = true;
799 ctrl.label.classList.add('noVNC_disabled');
800 },
801
802 enableSetting: function(name) {
803 var ctrl = document.getElementById('noVNC_setting_' + name);
804 ctrl.disabled = false;
805 ctrl.label.classList.remove('noVNC_disabled');
806 },
24584cca 807
ed8cbe4e
PO
808/* ------^-------
809 * /SETTINGS
810 * ==============
811 * PANELS
812 * ------v------*/
813
6d6f0db0
SR
814 closeAllPanels: function() {
815 UI.closeSettingsPanel();
cd523e8f 816 UI.closePowerPanel();
6d6f0db0
SR
817 UI.closeClipboardPanel();
818 UI.closeExtraKeys();
819 },
ed8cbe4e
PO
820
821/* ------^-------
822 * /PANELS
823 * ==============
824 * SETTINGS (panel)
825 * ------v------*/
826
6d6f0db0
SR
827 openSettingsPanel: function() {
828 UI.closeAllPanels();
829 UI.openControlbar();
830
831 // Refresh UI elements from saved cookies
832 UI.updateSetting('encrypt');
a49ade5f 833 UI.updateSetting('view_clip');
6d6f0db0
SR
834 UI.updateSetting('resize');
835 UI.updateSetting('shared');
836 UI.updateSetting('view_only');
837 UI.updateSetting('path');
838 UI.updateSetting('repeaterID');
839 UI.updateSetting('logging');
840 UI.updateSetting('reconnect');
841 UI.updateSetting('reconnect_delay');
842
843 document.getElementById('noVNC_settings')
844 .classList.add("noVNC_open");
845 document.getElementById('noVNC_settings_button')
846 .classList.add("noVNC_selected");
847 },
848
849 closeSettingsPanel: function() {
850 document.getElementById('noVNC_settings')
851 .classList.remove("noVNC_open");
852 document.getElementById('noVNC_settings_button')
853 .classList.remove("noVNC_selected");
854 },
855
856 toggleSettingsPanel: function() {
857 if (document.getElementById('noVNC_settings')
858 .classList.contains("noVNC_open")) {
859 UI.closeSettingsPanel();
860 } else {
861 UI.openSettingsPanel();
862 }
863 },
bbbf42bb 864
95dd6001 865/* ------^-------
866 * /SETTINGS
867 * ==============
cd523e8f 868 * POWER
95dd6001 869 * ------v------*/
870
cd523e8f 871 openPowerPanel: function() {
6d6f0db0
SR
872 UI.closeAllPanels();
873 UI.openControlbar();
874
cd523e8f 875 document.getElementById('noVNC_power')
6d6f0db0 876 .classList.add("noVNC_open");
cd523e8f 877 document.getElementById('noVNC_power_button')
6d6f0db0
SR
878 .classList.add("noVNC_selected");
879 },
880
cd523e8f
PO
881 closePowerPanel: function() {
882 document.getElementById('noVNC_power')
6d6f0db0 883 .classList.remove("noVNC_open");
cd523e8f 884 document.getElementById('noVNC_power_button')
6d6f0db0
SR
885 .classList.remove("noVNC_selected");
886 },
887
cd523e8f
PO
888 togglePowerPanel: function() {
889 if (document.getElementById('noVNC_power')
6d6f0db0 890 .classList.contains("noVNC_open")) {
cd523e8f 891 UI.closePowerPanel();
6d6f0db0 892 } else {
cd523e8f 893 UI.openPowerPanel();
6d6f0db0
SR
894 }
895 },
ed8cbe4e 896
cd523e8f
PO
897 // Disable/enable power button
898 updatePowerButton: function() {
899 if (UI.connected &&
747b4623
PO
900 UI.rfb.capabilities.power &&
901 !UI.rfb.viewOnly) {
cd523e8f 902 document.getElementById('noVNC_power_button')
6d6f0db0
SR
903 .classList.remove("noVNC_hidden");
904 } else {
cd523e8f 905 document.getElementById('noVNC_power_button')
6d6f0db0 906 .classList.add("noVNC_hidden");
cd523e8f
PO
907 // Close power panel if open
908 UI.closePowerPanel();
6d6f0db0
SR
909 }
910 },
bbbf42bb 911
95dd6001 912/* ------^-------
cd523e8f 913 * /POWER
95dd6001 914 * ==============
915 * CLIPBOARD
916 * ------v------*/
f8b399d7 917
6d6f0db0
SR
918 openClipboardPanel: function() {
919 UI.closeAllPanels();
920 UI.openControlbar();
921
922 document.getElementById('noVNC_clipboard')
923 .classList.add("noVNC_open");
924 document.getElementById('noVNC_clipboard_button')
925 .classList.add("noVNC_selected");
926 },
927
928 closeClipboardPanel: function() {
929 document.getElementById('noVNC_clipboard')
930 .classList.remove("noVNC_open");
931 document.getElementById('noVNC_clipboard_button')
932 .classList.remove("noVNC_selected");
933 },
934
935 toggleClipboardPanel: function() {
936 if (document.getElementById('noVNC_clipboard')
937 .classList.contains("noVNC_open")) {
938 UI.closeClipboardPanel();
939 } else {
940 UI.openClipboardPanel();
941 }
942 },
943
e89eef94
PO
944 clipboardReceive: function(e) {
945 Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0,40) + "...");
946 document.getElementById('noVNC_clipboard_text').value = e.detail.text;
6d6f0db0
SR
947 Log.Debug("<< UI.clipboardReceive");
948 },
949
950 clipboardClear: function() {
951 document.getElementById('noVNC_clipboard_text').value = "";
952 UI.rfb.clipboardPasteFrom("");
953 },
954
955 clipboardSend: function() {
956 var text = document.getElementById('noVNC_clipboard_text').value;
957 Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
958 UI.rfb.clipboardPasteFrom(text);
959 Log.Debug("<< UI.clipboardSend");
960 },
95dd6001 961
962/* ------^-------
963 * /CLIPBOARD
964 * ==============
965 * CONNECTION
966 * ------v------*/
967
6d6f0db0
SR
968 openConnectPanel: function() {
969 document.getElementById('noVNC_connect_dlg')
970 .classList.add("noVNC_open");
971 },
b3c932c3 972
6d6f0db0
SR
973 closeConnectPanel: function() {
974 document.getElementById('noVNC_connect_dlg')
975 .classList.remove("noVNC_open");
976 },
b3c932c3 977
6d6f0db0 978 connect: function(event, password) {
a4822c3a
SM
979
980 // Ignore when rfb already exists
981 if (typeof UI.rfb !== 'undefined') {
982 return;
983 }
984
6d6f0db0
SR
985 var host = UI.getSetting('host');
986 var port = UI.getSetting('port');
987 var path = UI.getSetting('path');
c55f05f6 988
6d6f0db0
SR
989 if (typeof password === 'undefined') {
990 password = WebUtil.getConfigVar('password');
2e735160 991 UI.reconnect_password = password;
6d6f0db0 992 }
044d54ed 993
6d6f0db0
SR
994 if (password === null) {
995 password = undefined;
996 }
512d3605 997
d623a029
SM
998 UI.hideStatus();
999
d593483e 1000 if (!host) {
8317524c
SM
1001 Log.Error("Can't connect when host is: " + host);
1002 UI.showStatus(_("Must set host"), 'error');
6d6f0db0
SR
1003 return;
1004 }
53fc7392 1005
6d6f0db0
SR
1006 UI.closeAllPanels();
1007 UI.closeConnectPanel();
4102b71c 1008
f3763a01
SM
1009 UI.updateVisualState('connecting');
1010
5b4e5d01
PO
1011 var url;
1012
1013 url = UI.getSetting('encrypt') ? 'wss' : 'ws';
1014
1015 url += '://' + host;
1016 if(port) {
1017 url += ':' + port;
1018 }
1019 url += '/' + path;
1020
9b84f516 1021 UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
2f4516f2
PO
1022 { shared: UI.getSetting('shared'),
1023 repeaterID: UI.getSetting('repeaterID'),
1024 credentials: { password: password } });
ee5cae9f 1025 UI.rfb.addEventListener("connect", UI.connectFinished);
e89eef94
PO
1026 UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
1027 UI.rfb.addEventListener("credentialsrequired", UI.credentials);
d472f3f1 1028 UI.rfb.addEventListener("securityfailure", UI.securityFailed);
9b84f516 1029 UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); });
e89eef94
PO
1030 UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
1031 UI.rfb.addEventListener("bell", UI.bell);
e89eef94 1032 UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
9b84f516
PO
1033 UI.rfb.clipViewport = UI.getSetting('view_clip');
1034 UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
1035 UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
37c60935 1036
f3763a01 1037 UI.updateViewOnly(); // requires UI.rfb
6d6f0db0 1038 },
5299db1a 1039
6d6f0db0
SR
1040 disconnect: function() {
1041 UI.closeAllPanels();
1042 UI.rfb.disconnect();
8e0f0088 1043
ee5cae9f
SM
1044 UI.connected = false;
1045
6d6f0db0
SR
1046 // Disable automatic reconnecting
1047 UI.inhibit_reconnect = true;
044d54ed 1048
ee5cae9f
SM
1049 UI.updateVisualState('disconnecting');
1050
6d6f0db0
SR
1051 // Don't display the connection settings until we're actually disconnected
1052 },
bbbf42bb 1053
6d6f0db0
SR
1054 reconnect: function() {
1055 UI.reconnect_callback = null;
044d54ed 1056
6d6f0db0
SR
1057 // if reconnect has been disabled in the meantime, do nothing.
1058 if (UI.inhibit_reconnect) {
1059 return;
1060 }
044d54ed 1061
6d6f0db0
SR
1062 UI.connect(null, UI.reconnect_password);
1063 },
044d54ed 1064
68958038
SM
1065 cancelReconnect: function() {
1066 if (UI.reconnect_callback !== null) {
1067 clearTimeout(UI.reconnect_callback);
1068 UI.reconnect_callback = null;
1069 }
1070
1071 UI.updateVisualState('disconnected');
1072
1073 UI.openControlbar();
1074 UI.openConnectPanel();
1075 },
1076
ee5cae9f
SM
1077 connectFinished: function (e) {
1078 UI.connected = true;
1079 UI.inhibit_reconnect = false;
ee5cae9f
SM
1080
1081 let msg;
1082 if (UI.getSetting('encrypt')) {
1083 msg = _("Connected (encrypted) to ") + UI.desktopName;
1084 } else {
1085 msg = _("Connected (unencrypted) to ") + UI.desktopName;
1086 }
1087 UI.showStatus(msg);
1088 UI.updateVisualState('connected');
1089
1090 // Do this last because it can only be used on rendered elements
9b84f516 1091 UI.rfb.focus();
ee5cae9f
SM
1092 },
1093
e89eef94 1094 disconnectFinished: function (e) {
7f1049c0
SM
1095 let wasConnected = UI.connected;
1096
ee5cae9f
SM
1097 // This variable is ideally set when disconnection starts, but
1098 // when the disconnection isn't clean or if it is initiated by
1099 // the server, we need to do it here as well since
1100 // UI.disconnect() won't be used in those cases.
1101 UI.connected = false;
1102
d1aeb435
PO
1103 UI.rfb = undefined;
1104
d472f3f1 1105 if (!e.detail.clean) {
ee5cae9f 1106 UI.updateVisualState('disconnected');
7f1049c0
SM
1107 if (wasConnected) {
1108 UI.showStatus(_("Something went wrong, connection is closed"),
1109 'error');
1110 } else {
1111 UI.showStatus(_("Failed to connect to server"), 'error');
1112 }
6d6f0db0 1113 } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
ee5cae9f 1114 UI.updateVisualState('reconnecting');
044d54ed 1115
6d6f0db0
SR
1116 var delay = parseInt(UI.getSetting('reconnect_delay'));
1117 UI.reconnect_callback = setTimeout(UI.reconnect, delay);
1118 return;
ee5cae9f
SM
1119 } else {
1120 UI.updateVisualState('disconnected');
1121 UI.showStatus(_("Disconnected"), 'normal');
6d6f0db0 1122 }
044d54ed 1123
6d6f0db0
SR
1124 UI.openControlbar();
1125 UI.openConnectPanel();
1126 },
044d54ed 1127
d472f3f1
SM
1128 securityFailed: function (e) {
1129 let msg = "";
1130 // On security failures we might get a string with a reason
1131 // directly from the server. Note that we can't control if
1132 // this string is translated or not.
1133 if ('reason' in e.detail) {
1134 msg = _("New connection has been rejected with reason: ") +
1135 e.detail.reason;
1136 } else {
1137 msg = _("New connection has been rejected");
6d6f0db0 1138 }
d472f3f1 1139 UI.showStatus(msg, 'error');
6d6f0db0 1140 },
3bb12056 1141
7d714b15
SM
1142/* ------^-------
1143 * /CONNECTION
1144 * ==============
1145 * PASSWORD
1146 * ------v------*/
1147
e89eef94 1148 credentials: function(e) {
430f00d6 1149 // FIXME: handle more types
6d6f0db0
SR
1150 document.getElementById('noVNC_password_dlg')
1151 .classList.add('noVNC_open');
7d714b15 1152
6d6f0db0
SR
1153 setTimeout(function () {
1154 document.getElementById('noVNC_password_input').focus();
1155 }, 100);
7d714b15 1156
8317524c
SM
1157 Log.Warn("Server asked for a password");
1158 UI.showStatus(_("Password is required"), "warning");
6d6f0db0
SR
1159 },
1160
1161 setPassword: function(e) {
a80b5fda
PO
1162 // Prevent actually submitting the form
1163 e.preventDefault();
1164
c23665dd
SM
1165 var inputElem = document.getElementById('noVNC_password_input');
1166 var password = inputElem.value;
1167 // Clear the input after reading the password
1168 inputElem.value = "";
430f00d6 1169 UI.rfb.sendCredentials({ password: password });
6d6f0db0
SR
1170 UI.reconnect_password = password;
1171 document.getElementById('noVNC_password_dlg')
1172 .classList.remove('noVNC_open');
6d6f0db0 1173 },
58ded70d 1174
95dd6001 1175/* ------^-------
7d714b15 1176 * /PASSWORD
95dd6001 1177 * ==============
1178 * FULLSCREEN
1179 * ------v------*/
1180
6d6f0db0
SR
1181 toggleFullscreen: function() {
1182 if (document.fullscreenElement || // alternative standard method
1183 document.mozFullScreenElement || // currently working methods
1184 document.webkitFullscreenElement ||
1185 document.msFullscreenElement) {
1186 if (document.exitFullscreen) {
1187 document.exitFullscreen();
1188 } else if (document.mozCancelFullScreen) {
1189 document.mozCancelFullScreen();
1190 } else if (document.webkitExitFullscreen) {
1191 document.webkitExitFullscreen();
1192 } else if (document.msExitFullscreen) {
1193 document.msExitFullscreen();
1194 }
1195 } else {
1196 if (document.documentElement.requestFullscreen) {
1197 document.documentElement.requestFullscreen();
1198 } else if (document.documentElement.mozRequestFullScreen) {
1199 document.documentElement.mozRequestFullScreen();
1200 } else if (document.documentElement.webkitRequestFullscreen) {
1201 document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
1202 } else if (document.body.msRequestFullscreen) {
1203 document.body.msRequestFullscreen();
7d1dc09a 1204 }
6d6f0db0
SR
1205 }
1206 UI.enableDisableViewClip();
1207 UI.updateFullscreenButton();
1208 },
1209
1210 updateFullscreenButton: function() {
1211 if (document.fullscreenElement || // alternative standard method
1212 document.mozFullScreenElement || // currently working methods
1213 document.webkitFullscreenElement ||
1214 document.msFullscreenElement ) {
1215 document.getElementById('noVNC_fullscreen_button')
1216 .classList.add("noVNC_selected");
1217 } else {
1218 document.getElementById('noVNC_fullscreen_button')
1219 .classList.remove("noVNC_selected");
1220 }
1221 },
7d1dc09a 1222
95dd6001 1223/* ------^-------
1224 * /FULLSCREEN
1225 * ==============
1226 * RESIZE
1227 * ------v------*/
777cb7a0 1228
6d6f0db0
SR
1229 // Apply remote resizing or local scaling
1230 applyResizeMode: function() {
1231 if (!UI.rfb) return;
58ded70d 1232
9b84f516
PO
1233 UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
1234 UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
6d6f0db0 1235 },
bbbf42bb 1236
95dd6001 1237/* ------^-------
1238 * /RESIZE
1239 * ==============
a49ade5f 1240 * VIEW CLIPPING
95dd6001 1241 * ------v------*/
1242
a49ade5f 1243 // Update parameters that depend on the viewport clip setting
6d6f0db0
SR
1244 updateViewClip: function() {
1245 if (!UI.rfb) return;
1246
0460e5fd 1247 var cur_clip = UI.rfb.clipViewport;
a49ade5f 1248 var new_clip = UI.getSetting('view_clip');
6d6f0db0 1249
9b84f516 1250 if (isTouchDevice) {
6d6f0db0
SR
1251 // Touch devices usually have shit scrollbars
1252 new_clip = true;
1253 }
5f38376a 1254
6d6f0db0 1255 if (cur_clip !== new_clip) {
0460e5fd 1256 UI.rfb.clipViewport = new_clip;
6d6f0db0 1257 }
8e0f0088 1258
6d6f0db0
SR
1259 // Changing the viewport may change the state of
1260 // the dragging button
1261 UI.updateViewDrag();
1262 },
1263
a49ade5f 1264 // Handle special cases where viewport clipping is forced on/off or locked
6d6f0db0
SR
1265 enableDisableViewClip: function() {
1266 var resizeSetting = UI.getSetting('resize');
1267 // Disable clipping if we are scaling, connected or on touch
002907d2 1268 if (resizeSetting === 'scale' ||
6d6f0db0 1269 isTouchDevice) {
a49ade5f 1270 UI.disableSetting('view_clip');
6d6f0db0 1271 } else {
a49ade5f 1272 UI.enableSetting('view_clip');
6d6f0db0
SR
1273 }
1274 },
30bfff81 1275
95dd6001 1276/* ------^-------
a49ade5f 1277 * /VIEW CLIPPING
95dd6001 1278 * ==============
1279 * VIEWDRAG
1280 * ------v------*/
1281
6d6f0db0
SR
1282 toggleViewDrag: function() {
1283 if (!UI.rfb) return;
bbbf42bb 1284
0460e5fd 1285 var drag = UI.rfb.dragViewport;
6d6f0db0
SR
1286 UI.setViewDrag(!drag);
1287 },
f0d9ab96 1288
6d6f0db0
SR
1289 // Set the view drag mode which moves the viewport on mouse drags
1290 setViewDrag: function(drag) {
1291 if (!UI.rfb) return;
f0d9ab96 1292
0460e5fd 1293 UI.rfb.dragViewport = drag;
f0d9ab96 1294
6d6f0db0
SR
1295 UI.updateViewDrag();
1296 },
f0d9ab96 1297
6d6f0db0 1298 updateViewDrag: function() {
6d6f0db0 1299 if (!UI.connected) return;
6244e383 1300
6d6f0db0 1301 var viewDragButton = document.getElementById('noVNC_view_drag_button');
29a0e6a8 1302
9b84f516
PO
1303 if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
1304 // We are no longer clipping the viewport. Make sure
1305 // viewport drag isn't active when it can't be used.
0460e5fd 1306 UI.rfb.dragViewport = false;
6d6f0db0
SR
1307 }
1308
0460e5fd 1309 if (UI.rfb.dragViewport) {
6d6f0db0
SR
1310 viewDragButton.classList.add("noVNC_selected");
1311 } else {
1312 viewDragButton.classList.remove("noVNC_selected");
1313 }
1314
1315 // Different behaviour for touch vs non-touch
1316 // The button is disabled instead of hidden on touch devices
1317 if (isTouchDevice) {
1318 viewDragButton.classList.remove("noVNC_hidden");
6244e383 1319
9b84f516 1320 if (UI.rfb.clipViewport) {
6d6f0db0 1321 viewDragButton.disabled = false;
31ddaa1c 1322 } else {
6d6f0db0 1323 viewDragButton.disabled = true;
6244e383 1324 }
6d6f0db0
SR
1325 } else {
1326 viewDragButton.disabled = false;
31ddaa1c 1327
9b84f516 1328 if (UI.rfb.clipViewport) {
6244e383 1329 viewDragButton.classList.remove("noVNC_hidden");
6244e383 1330 } else {
6d6f0db0 1331 viewDragButton.classList.add("noVNC_hidden");
f8b399d7 1332 }
6d6f0db0
SR
1333 }
1334 },
f8b399d7 1335
95dd6001 1336/* ------^-------
1337 * /VIEWDRAG
1338 * ==============
1339 * KEYBOARD
1340 * ------v------*/
fdf21468 1341
6d6f0db0
SR
1342 showVirtualKeyboard: function() {
1343 if (!isTouchDevice) return;
4b30f9ce 1344
6d6f0db0 1345 var input = document.getElementById('noVNC_keyboardinput');
4b30f9ce 1346
6d6f0db0 1347 if (document.activeElement == input) return;
4b30f9ce 1348
6d6f0db0 1349 input.focus();
4b30f9ce 1350
6d6f0db0
SR
1351 try {
1352 var l = input.value.length;
1353 // Move the caret to the end
1354 input.setSelectionRange(l, l);
1355 } catch (err) {} // setSelectionRange is undefined in Google Chrome
1356 },
4b30f9ce 1357
6d6f0db0
SR
1358 hideVirtualKeyboard: function() {
1359 if (!isTouchDevice) return;
4b30f9ce 1360
6d6f0db0 1361 var input = document.getElementById('noVNC_keyboardinput');
4b30f9ce 1362
6d6f0db0 1363 if (document.activeElement != input) return;
4b30f9ce 1364
6d6f0db0
SR
1365 input.blur();
1366 },
4b30f9ce 1367
6d6f0db0
SR
1368 toggleVirtualKeyboard: function () {
1369 if (document.getElementById('noVNC_keyboard_button')
1370 .classList.contains("noVNC_selected")) {
1371 UI.hideVirtualKeyboard();
1372 } else {
1373 UI.showVirtualKeyboard();
1374 }
1375 },
bbbf42bb 1376
6d6f0db0
SR
1377 onfocusVirtualKeyboard: function(event) {
1378 document.getElementById('noVNC_keyboard_button')
1379 .classList.add("noVNC_selected");
1d6ff4a3 1380 if (UI.rfb) {
b8dfb983 1381 UI.rfb.focusOnClick = false;
1d6ff4a3 1382 }
6d6f0db0 1383 },
fdf21468 1384
6d6f0db0
SR
1385 onblurVirtualKeyboard: function(event) {
1386 document.getElementById('noVNC_keyboard_button')
1387 .classList.remove("noVNC_selected");
1d6ff4a3 1388 if (UI.rfb) {
b8dfb983 1389 UI.rfb.focusOnClick = true;
1d6ff4a3 1390 }
6d6f0db0 1391 },
ffcadf95 1392
6d6f0db0
SR
1393 keepVirtualKeyboard: function(event) {
1394 var input = document.getElementById('noVNC_keyboardinput');
ffcadf95 1395
6d6f0db0
SR
1396 // Only prevent focus change if the virtual keyboard is active
1397 if (document.activeElement != input) {
1398 return;
1399 }
ffcadf95 1400
6d6f0db0
SR
1401 // Only allow focus to move to other elements that need
1402 // focus to function properly
1403 if (event.target.form !== undefined) {
1404 switch (event.target.type) {
1405 case 'text':
1406 case 'email':
1407 case 'search':
1408 case 'password':
1409 case 'tel':
1410 case 'url':
1411 case 'textarea':
1412 case 'select-one':
1413 case 'select-multiple':
1414 return;
ffcadf95 1415 }
6d6f0db0 1416 }
ffcadf95 1417
1d6ff4a3 1418 event.preventDefault();
6d6f0db0 1419 },
bbbf42bb 1420
6d6f0db0
SR
1421 keyboardinputReset: function() {
1422 var kbi = document.getElementById('noVNC_keyboardinput');
1423 kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
1424 UI.lastKeyboardinput = kbi.value;
1425 },
bbbf42bb 1426
867daa98
PO
1427 keyEvent: function (keysym, code, down) {
1428 if (!UI.rfb) return;
1429
1430 UI.rfb.sendKey(keysym, code, down);
1431 },
1432
6d6f0db0
SR
1433 // When normal keyboard events are left uncought, use the input events from
1434 // the keyboardinput element instead and generate the corresponding key events.
1435 // This code is required since some browsers on Android are inconsistent in
1436 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1437 keyInput: function(event) {
3b8ec46f 1438
6d6f0db0 1439 if (!UI.rfb) return;
3b8ec46f 1440
6d6f0db0 1441 var newValue = event.target.value;
1138bdd4 1442
6d6f0db0
SR
1443 if (!UI.lastKeyboardinput) {
1444 UI.keyboardinputReset();
1445 }
1446 var oldValue = UI.lastKeyboardinput;
bbbf42bb 1447
6d6f0db0
SR
1448 var newLen;
1449 try {
1450 // Try to check caret position since whitespace at the end
1451 // will not be considered by value.length in some browsers
1452 newLen = Math.max(event.target.selectionStart, newValue.length);
1453 } catch (err) {
1454 // selectionStart is undefined in Google Chrome
1455 newLen = newValue.length;
1456 }
1457 var oldLen = oldValue.length;
1458
1459 var backspaces;
1460 var inputs = newLen - oldLen;
1461 if (inputs < 0) {
1462 backspaces = -inputs;
1463 } else {
1464 backspaces = 0;
1465 }
8e0f0088 1466
6d6f0db0
SR
1467 // Compare the old string with the new to account for
1468 // text-corrections or other input that modify existing text
1469 var i;
1470 for (i = 0; i < Math.min(oldLen, newLen); i++) {
1471 if (newValue.charAt(i) != oldValue.charAt(i)) {
1472 inputs = newLen - i;
1473 backspaces = oldLen - i;
1474 break;
bbbf42bb 1475 }
6d6f0db0 1476 }
bbbf42bb 1477
6d6f0db0
SR
1478 // Send the key events
1479 for (i = 0; i < backspaces; i++) {
94f5cf05 1480 UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
6d6f0db0
SR
1481 }
1482 for (i = newLen - inputs; i < newLen; i++) {
524d67f2 1483 UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
6d6f0db0 1484 }
bbbf42bb 1485
6d6f0db0
SR
1486 // Control the text content length in the keyboardinput element
1487 if (newLen > 2 * UI.defaultKeyboardinputLen) {
1488 UI.keyboardinputReset();
1489 } else if (newLen < 1) {
1490 // There always have to be some text in the keyboardinput
1491 // element with which backspace can interact.
1492 UI.keyboardinputReset();
1493 // This sometimes causes the keyboard to disappear for a second
1494 // but it is required for the android keyboard to recognize that
1495 // text has been added to the field
1496 event.target.blur();
1497 // This has to be ran outside of the input handler in order to work
1498 setTimeout(event.target.focus.bind(event.target), 0);
1499 } else {
1500 UI.lastKeyboardinput = newValue;
1501 }
1502 },
bbbf42bb 1503
4b30f9ce
SM
1504/* ------^-------
1505 * /KEYBOARD
1506 * ==============
1507 * EXTRA KEYS
1508 * ------v------*/
1509
6d6f0db0
SR
1510 openExtraKeys: function() {
1511 UI.closeAllPanels();
1512 UI.openControlbar();
1513
1514 document.getElementById('noVNC_modifiers')
1515 .classList.add("noVNC_open");
1516 document.getElementById('noVNC_toggle_extra_keys_button')
1517 .classList.add("noVNC_selected");
1518 },
1519
1520 closeExtraKeys: function() {
1521 document.getElementById('noVNC_modifiers')
1522 .classList.remove("noVNC_open");
1523 document.getElementById('noVNC_toggle_extra_keys_button')
1524 .classList.remove("noVNC_selected");
1525 },
1526
1527 toggleExtraKeys: function() {
1528 if(document.getElementById('noVNC_modifiers')
1529 .classList.contains("noVNC_open")) {
1530 UI.closeExtraKeys();
1531 } else {
1532 UI.openExtraKeys();
1533 }
1534 },
1535
1536 sendEsc: function() {
94f5cf05 1537 UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
6d6f0db0
SR
1538 },
1539
1540 sendTab: function() {
1541 UI.rfb.sendKey(KeyTable.XK_Tab);
1542 },
1543
1544 toggleCtrl: function() {
1545 var btn = document.getElementById('noVNC_toggle_ctrl_button');
1546 if (btn.classList.contains("noVNC_selected")) {
94f5cf05 1547 UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
6d6f0db0
SR
1548 btn.classList.remove("noVNC_selected");
1549 } else {
94f5cf05 1550 UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
6d6f0db0
SR
1551 btn.classList.add("noVNC_selected");
1552 }
1553 },
1554
1555 toggleAlt: function() {
1556 var btn = document.getElementById('noVNC_toggle_alt_button');
1557 if (btn.classList.contains("noVNC_selected")) {
94f5cf05 1558 UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
6d6f0db0
SR
1559 btn.classList.remove("noVNC_selected");
1560 } else {
94f5cf05 1561 UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
6d6f0db0
SR
1562 btn.classList.add("noVNC_selected");
1563 }
1564 },
bbbf42bb 1565
6d6f0db0
SR
1566 sendCtrlAltDel: function() {
1567 UI.rfb.sendCtrlAltDel();
1568 },
bbbf42bb 1569
95dd6001 1570/* ------^-------
4b30f9ce 1571 * /EXTRA KEYS
95dd6001 1572 * ==============
1573 * MISC
1574 * ------v------*/
1575
6d6f0db0 1576 setMouseButton: function(num) {
747b4623 1577 var view_only = UI.rfb.viewOnly;
6d6f0db0 1578 if (UI.rfb && !view_only) {
747b4623 1579 UI.rfb.touchButton = num;
6d6f0db0 1580 }
95dd6001 1581
6d6f0db0
SR
1582 var blist = [0, 1,2,4];
1583 for (var b = 0; b < blist.length; b++) {
1584 var button = document.getElementById('noVNC_mouse_button' +
1585 blist[b]);
1586 if (blist[b] === num && !view_only) {
1587 button.classList.remove("noVNC_hidden");
1588 } else {
1589 button.classList.add("noVNC_hidden");
ceb847b0 1590 }
6d6f0db0
SR
1591 }
1592 },
ef1e8bab 1593
6d6f0db0 1594 updateViewOnly: function() {
e6a8eb15 1595 if (!UI.rfb) return;
747b4623 1596 UI.rfb.viewOnly = UI.getSetting('view_only');
f3763a01
SM
1597
1598 // Hide input related buttons in view only mode
1599 if (UI.rfb.viewOnly) {
1600 document.getElementById('noVNC_keyboard_button')
1601 .classList.add('noVNC_hidden');
1602 document.getElementById('noVNC_toggle_extra_keys_button')
1603 .classList.add('noVNC_hidden');
1604 } else {
1605 document.getElementById('noVNC_keyboard_button')
1606 .classList.remove('noVNC_hidden');
1607 document.getElementById('noVNC_toggle_extra_keys_button')
1608 .classList.remove('noVNC_hidden');
1609 }
61f93180 1610 UI.setMouseButton(1); //has it's own logic for hiding/showing
6d6f0db0
SR
1611 },
1612
1613 updateLogging: function() {
1614 WebUtil.init_logging(UI.getSetting('logging'));
1615 },
1616
e89eef94
PO
1617 updateDesktopName: function(e) {
1618 UI.desktopName = e.detail.name;
6d6f0db0 1619 // Display the desktop name in the document title
e89eef94 1620 document.title = e.detail.name + " - noVNC";
6d6f0db0
SR
1621 },
1622
e89eef94 1623 bell: function(e) {
6d6f0db0 1624 if (WebUtil.getConfigVar('bell', 'on') === 'on') {
a342ed70
SM
1625 var promise = document.getElementById('noVNC_bell').play();
1626 // The standards disagree on the return value here
1627 if (promise) {
1628 promise.catch(function(e) {
d4fc89d8
SM
1629 if (e.name === "NotAllowedError") {
1630 // Ignore when the browser doesn't let us play audio.
1631 // It is common that the browsers require audio to be
1632 // initiated from a user action.
1633 } else {
1634 Log.Error("Unable to play bell: " + e);
1635 }
1636 });
a342ed70 1637 }
6d6f0db0
SR
1638 }
1639 },
63bf2ba5 1640
6d6f0db0
SR
1641 //Helper to add options to dropdown.
1642 addOption: function(selectbox, text, value) {
1643 var optn = document.createElement("OPTION");
1644 optn.text = text;
1645 optn.value = value;
1646 selectbox.options.add(optn);
1647 },
bbbf42bb 1648
95dd6001 1649/* ------^-------
1650 * /MISC
1651 * ==============
1652 */
6d6f0db0
SR
1653};
1654
1655// Set up translations
03d829f1 1656var LINGUAS = ["de", "el", "nl", "pl", "sv", "zh"];
6d6f0db0
SR
1657l10n.setup(LINGUAS);
1658if (l10n.language !== "en" && l10n.dictionary === undefined) {
1659 WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
1660 l10n.dictionary = translations;
1661
1662 // wait for translations to load before loading the UI
1663 UI.prime();
1664 }, function (err) {
adfc9d3f 1665 throw err;
6d6f0db0
SR
1666 });
1667} else {
1668 UI.prime();
1669}
1670
3ae0bb09 1671export default UI;