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