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