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