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