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