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