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