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