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