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