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