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