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