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