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