]> git.proxmox.com Git - mirror_novnc.git/blame - include/ui.js
Renamed showExtraKeys to toggleExtraKeys
[mirror_novnc.git] / include / 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
1d728ace 5 * Licensed under MPL 2.0 (see LICENSE.txt)
15046f00
JM
6 *
7 * See README.md for usage and integration instructions.
8 */
43cf7bd8 9
bbbf42bb
SR
10/* jslint white: false, browser: true */
11/* global window, $D, Util, WebUtil, RFB, Display */
12
13var UI;
14
15(function () {
16 "use strict";
17
18 // Load supporting scripts
19 window.onscriptsload = function () { UI.load(); };
bbbf42bb
SR
20 Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
21 "keysymdef.js", "keyboard.js", "input.js", "display.js",
6940936f 22 "rfb.js", "keysym.js", "inflator.js"]);
bbbf42bb 23
bd6874e0 24 UI = {
bbbf42bb 25
045d9224 26 rfb_state: 'loaded',
529c64e1 27
045d9224 28 resizeTimeout: null,
529c64e1 29 popupStatusTimeout: null,
30 hideKeyboardTimeout: null,
31
045d9224 32 settingsOpen: false,
33 connSettingsOpen: false,
bbbf42bb
SR
34 clipboardOpen: false,
35 keyboardVisible: false,
529c64e1 36
bbbf42bb 37 isTouchDevice: false,
f620259b 38 isSafari: false,
a6357e82 39 rememberedClipSetting: null,
529c64e1 40 lastKeyboardinput: null,
41 defaultKeyboardinputLen: 100,
42
43 shiftDown: false,
44 ctrlDown: false,
45 altDown: false,
46 altGrDown: false,
bbbf42bb
SR
47
48 // Setup rfb object, load settings from browser storage, then call
49 // UI.init to setup the UI/menus
0bd2cbac 50 load: function(callback) {
bbbf42bb
SR
51 WebUtil.initSettings(UI.start, callback);
52 },
53
54 // Render default UI and initialize settings menu
55 start: function(callback) {
56 UI.isTouchDevice = 'ontouchstart' in document.documentElement;
57
58 // Stylesheet selection dropdown
59 var sheet = WebUtil.selectStylesheet();
60 var sheets = WebUtil.getStylesheets();
61 var i;
62 for (i = 0; i < sheets.length; i += 1) {
63 UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
64 }
65
66 // Logging selection dropdown
67 var llevels = ['error', 'warn', 'info', 'debug'];
68 for (i = 0; i < llevels.length; i += 1) {
69 UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
70 }
71
72 // Settings with immediate effects
73 UI.initSetting('logging', 'warn');
74 WebUtil.init_logging(UI.getSetting('logging'));
75
76 UI.initSetting('stylesheet', 'default');
77 WebUtil.selectStylesheet(null);
78 // call twice to get around webkit bug
79 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
80
81 // if port == 80 (or 443) then it won't be present and should be
82 // set manually
83 var port = window.location.port;
84 if (!port) {
85 if (window.location.protocol.substring(0,5) == 'https') {
86 port = 443;
87 }
88 else if (window.location.protocol.substring(0,4) == 'http') {
89 port = 80;
90 }
91 }
92
93 /* Populate the controls if defaults are provided in the URL */
94 UI.initSetting('host', window.location.hostname);
95 UI.initSetting('port', port);
96 UI.initSetting('password', '');
97 UI.initSetting('encrypt', (window.location.protocol === "https:"));
98 UI.initSetting('true_color', true);
99 UI.initSetting('cursor', !UI.isTouchDevice);
8b46c0de 100 UI.initSetting('resize', 'off');
bbbf42bb
SR
101 UI.initSetting('shared', true);
102 UI.initSetting('view_only', false);
103 UI.initSetting('path', 'websockify');
104 UI.initSetting('repeaterID', '');
c55f05f6 105 UI.initSetting('token', '');
bbbf42bb 106
494b407a 107 var autoconnect = WebUtil.getConfigVar('autoconnect', false);
bbbf42bb
SR
108 if (autoconnect === 'true' || autoconnect == '1') {
109 autoconnect = true;
110 UI.connect();
111 } else {
112 autoconnect = false;
113 }
114
115 UI.updateVisualState();
116
fdedbafb 117 $D('noVNC_host').focus();
118
bbbf42bb
SR
119 // Show mouse selector buttons on touch screen devices
120 if (UI.isTouchDevice) {
121 // Show mobile buttons
122 $D('noVNC_mobile_buttons').style.display = "inline";
123 UI.setMouseButton();
124 // Remove the address bar
125 setTimeout(function() { window.scrollTo(0, 1); }, 100);
126 UI.forceSetting('clip', true);
bbbf42bb
SR
127 } else {
128 UI.initSetting('clip', false);
129 }
130
bbbf42bb 131 UI.setViewClip();
fdedbafb 132 UI.setBarPosition();
f8b399d7 133
134 Util.addEvent(window, 'resize', function () {
777cb7a0 135 UI.applyResizeMode();
f8b399d7 136 UI.setViewClip();
31ddaa1c 137 UI.updateViewDrag();
fdedbafb 138 UI.setBarPosition();
f8b399d7 139 } );
bbbf42bb 140
f620259b 141 UI.isSafari = (navigator.userAgent.indexOf('Safari') != -1 &&
142 navigator.userAgent.indexOf('Chrome') == -1);
a6357e82 143
144 // Only show the button if fullscreen is properly supported
145 // * Safari doesn't support alphanumerical input while in fullscreen
f620259b 146 if (!UI.isSafari &&
a6357e82 147 (document.documentElement.requestFullscreen ||
148 document.documentElement.mozRequestFullScreen ||
149 document.documentElement.webkitRequestFullscreen ||
150 document.body.msRequestFullscreen)) {
151 $D('fullscreenButton').style.display = "inline";
7d1dc09a 152 Util.addEvent(window, 'fullscreenchange', UI.updateFullscreenButton);
153 Util.addEvent(window, 'mozfullscreenchange', UI.updateFullscreenButton);
154 Util.addEvent(window, 'webkitfullscreenchange', UI.updateFullscreenButton);
155 Util.addEvent(window, 'msfullscreenchange', UI.updateFullscreenButton);
156 }
157
5b7598ac 158 Util.addEvent(window, 'load', UI.keyboardinputReset);
159
bbbf42bb 160 Util.addEvent(window, 'beforeunload', function () {
58ded70d 161 if (UI.rfb && UI.rfb_state === 'normal') {
bbbf42bb
SR
162 return "You are currently connected.";
163 }
164 } );
165
166 // Show description by default when hosted at for kanaka.github.com
167 if (location.host === "kanaka.github.io") {
168 // Open the description dialog
169 $D('noVNC_description').style.display = "block";
170 } else {
171 // Show the connect panel on first load unless autoconnecting
172 if (autoconnect === UI.connSettingsOpen) {
173 UI.toggleConnectPanel();
174 }
175 }
176
177 // Add mouse event click/focus/blur event handlers to the UI
178 UI.addMouseHandlers();
179
180 if (typeof callback === "function") {
181 callback(UI.rfb);
182 }
183 },
184
0bd2cbac 185 initRFB: function() {
d9fc1c7b
SR
186 try {
187 UI.rfb = new RFB({'target': $D('noVNC_canvas'),
188 'onUpdateState': UI.updateState,
189 'onXvpInit': UI.updateXvpVisualState,
190 'onClipboard': UI.clipReceive,
777cb7a0 191 'onFBUComplete': UI.initialResize,
31ddaa1c 192 'onFBResize': UI.updateViewDrag,
d9fc1c7b
SR
193 'onDesktopName': UI.updateDocumentTitle});
194 return true;
195 } catch (exc) {
196 UI.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
197 return false;
198 }
e543525f
SR
199 },
200
bbbf42bb
SR
201 addMouseHandlers: function() {
202 // Setup interface handlers that can't be inline
31ddaa1c 203 $D("noVNC_view_drag_button").onclick = UI.toggleViewDrag;
bbbf42bb
SR
204 $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
205 $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
206 $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
207 $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
208 $D("showKeyboard").onclick = UI.showKeyboard;
209
210 $D("keyboardinput").oninput = UI.keyInput;
211 $D("keyboardinput").onblur = UI.keyInputBlur;
48d26b2d 212 $D("keyboardinput").onsubmit = function () { return false; };
bbbf42bb 213
cd611a53 214 $D("toggleExtraKeysButton").onclick = UI.toggleExtraKeys;
bbbf42bb
SR
215 $D("toggleCtrlButton").onclick = UI.toggleCtrl;
216 $D("toggleAltButton").onclick = UI.toggleAlt;
217 $D("sendTabButton").onclick = UI.sendTab;
218 $D("sendEscButton").onclick = UI.sendEsc;
219
220 $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
221 $D("xvpShutdownButton").onclick = UI.xvpShutdown;
222 $D("xvpRebootButton").onclick = UI.xvpReboot;
223 $D("xvpResetButton").onclick = UI.xvpReset;
4e471b5b 224 $D("noVNC_status").onclick = UI.popupStatus;
225 $D("noVNC_popup_status").onclick = UI.closePopup;
bbbf42bb
SR
226 $D("xvpButton").onclick = UI.toggleXvpPanel;
227 $D("clipboardButton").onclick = UI.toggleClipboardPanel;
7d1dc09a 228 $D("fullscreenButton").onclick = UI.toggleFullscreen;
bbbf42bb
SR
229 $D("settingsButton").onclick = UI.toggleSettingsPanel;
230 $D("connectButton").onclick = UI.toggleConnectPanel;
231 $D("disconnectButton").onclick = UI.disconnect;
232 $D("descriptionButton").onclick = UI.toggleConnectPanel;
233
234 $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
235 $D("noVNC_clipboard_text").onblur = UI.displayFocus;
236 $D("noVNC_clipboard_text").onchange = UI.clipSend;
237 $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
238
239 $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
240 $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
241 $D("noVNC_apply").onclick = UI.settingsApply;
242
243 $D("noVNC_connect_button").onclick = UI.connect;
72747869 244
a6357e82 245 $D("noVNC_resize").onchange = UI.enableDisableViewClip;
bbbf42bb
SR
246 },
247
29475d77 248 updateState: function(rfb, state, oldstate, msg) {
249 UI.rfb_state = state;
250 var klass;
251 switch (state) {
252 case 'failed':
253 case 'fatal':
254 klass = "noVNC_status_error";
255 break;
256 case 'normal':
257 klass = "noVNC_status_normal";
258 break;
259 case 'disconnected':
260 $D('noVNC_logo').style.display = "block";
261 $D('noVNC_screen').style.display = "none";
262 /* falls through */
263 case 'loaded':
264 klass = "noVNC_status_normal";
265 break;
266 case 'password':
267 UI.toggleConnectPanel();
268
269 $D('noVNC_connect_button').value = "Send Password";
270 $D('noVNC_connect_button').onclick = UI.setPassword;
271 $D('noVNC_password').focus();
272
273 klass = "noVNC_status_warn";
274 break;
275 default:
276 klass = "noVNC_status_warn";
277 break;
278 }
279
280 if (typeof(msg) !== 'undefined') {
281 $D('noVNC-control-bar').setAttribute("class", klass);
282 $D('noVNC_status').innerHTML = msg;
283 }
284
285 UI.updateVisualState();
286 },
287
288 // Disable/enable controls depending on connection state
289 updateVisualState: function() {
290 var connected = UI.rfb && UI.rfb_state === 'normal';
291
292 //Util.Debug(">> updateVisualState");
293 $D('noVNC_encrypt').disabled = connected;
294 $D('noVNC_true_color').disabled = connected;
295 if (Util.browserSupportsCursorURIs()) {
296 $D('noVNC_cursor').disabled = connected;
297 } else {
298 UI.updateSetting('cursor', !UI.isTouchDevice);
299 $D('noVNC_cursor').disabled = true;
300 }
301
302 UI.enableDisableViewClip();
303 $D('noVNC_resize').disabled = connected;
304 $D('noVNC_shared').disabled = connected;
305 $D('noVNC_view_only').disabled = connected;
306 $D('noVNC_path').disabled = connected;
307 $D('noVNC_repeaterID').disabled = connected;
308
309 if (connected) {
310 UI.setViewClip();
311 UI.setMouseButton(1);
312 $D('clipboardButton').style.display = "inline";
313 $D('showKeyboard').style.display = "inline";
314 $D('noVNC_extra_keys').style.display = "";
315 $D('sendCtrlAltDelButton').style.display = "inline";
316 } else {
317 UI.setMouseButton();
318 $D('clipboardButton').style.display = "none";
319 $D('showKeyboard').style.display = "none";
320 $D('noVNC_extra_keys').style.display = "none";
321 $D('sendCtrlAltDelButton').style.display = "none";
322 UI.updateXvpVisualState(0);
323 }
324
325 // State change disables viewport dragging.
326 // It is enabled (toggled) by direct click on the button
327 UI.updateViewDrag(false);
328
329 switch (UI.rfb_state) {
330 case 'fatal':
331 case 'failed':
332 case 'disconnected':
333 $D('connectButton').style.display = "";
334 $D('disconnectButton').style.display = "none";
335 UI.connSettingsOpen = false;
336 UI.toggleConnectPanel();
337 break;
338 case 'loaded':
339 $D('connectButton').style.display = "";
340 $D('disconnectButton').style.display = "none";
341 break;
342 default:
343 $D('connectButton').style.display = "none";
344 $D('disconnectButton').style.display = "";
345 break;
346 }
347
348 //Util.Debug("<< updateVisualState");
349 },
350
4e471b5b 351 popupStatus: function(text) {
352 var psp = $D('noVNC_popup_status');
353
354 clearTimeout(UI.popupStatusTimeout);
355
356 if (typeof text === 'string') {
357 psp.innerHTML = text;
358 } else {
359 psp.innerHTML = $D('noVNC_status').innerHTML;
360 }
361 psp.style.display = "block";
362 psp.style.left = window.innerWidth/2 -
363 parseInt(window.getComputedStyle(psp).width)/2 -30 + "px";
364
365 // Show the popup for a maximum of 1.5 seconds
366 UI.popupStatusTimeout = setTimeout(function() {
367 UI.closePopup();
368 }, 1500);
369 },
370
371 closePopup: function() {
372 clearTimeout(UI.popupStatusTimeout);
373 $D('noVNC_popup_status').style.display = "none";
374 },
375
45c70c9e 376 // Initial page load read/initialization of settings
377 initSetting: function(name, defVal) {
378 // Check Query string followed by cookie
379 var val = WebUtil.getConfigVar(name);
380 if (val === null) {
381 val = WebUtil.readSetting(name, defVal);
bbbf42bb 382 }
45c70c9e 383 UI.updateSetting(name, val);
bbbf42bb
SR
384 return val;
385 },
386
387 // Update cookie and form control setting. If value is not set, then
388 // updates from control to current cookie setting.
389 updateSetting: function(name, value) {
390
391 // Save the cookie for this session
392 if (typeof value !== 'undefined') {
393 WebUtil.writeSetting(name, value);
394 }
395
396 // Update the settings control
397 value = UI.getSetting(name);
398
399 var ctrl = $D('noVNC_' + name);
400 if (ctrl.type === 'checkbox') {
401 ctrl.checked = value;
402
403 } else if (typeof ctrl.options !== 'undefined') {
404 for (var i = 0; i < ctrl.options.length; i += 1) {
405 if (ctrl.options[i].value === value) {
406 ctrl.selectedIndex = i;
407 break;
408 }
409 }
410 } else {
411 /*Weird IE9 error leads to 'null' appearring
412 in textboxes instead of ''.*/
413 if (value === null) {
414 value = "";
415 }
416 ctrl.value = value;
417 }
418 },
419
420 // Save control setting to cookie
421 saveSetting: function(name) {
422 var val, ctrl = $D('noVNC_' + name);
423 if (ctrl.type === 'checkbox') {
424 val = ctrl.checked;
425 } else if (typeof ctrl.options !== 'undefined') {
426 val = ctrl.options[ctrl.selectedIndex].value;
427 } else {
428 val = ctrl.value;
429 }
430 WebUtil.writeSetting(name, val);
431 //Util.Debug("Setting saved '" + name + "=" + val + "'");
432 return val;
433 },
434
45c70c9e 435 // Force a setting to be a certain value
436 forceSetting: function(name, val) {
bbbf42bb
SR
437 UI.updateSetting(name, val);
438 return val;
439 },
440
45c70c9e 441 // Read form control compatible setting from cookie
442 getSetting: function(name) {
443 var ctrl = $D('noVNC_' + name);
444 var val = WebUtil.readSetting(name);
445 if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
446 if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
447 val = false;
448 } else {
449 val = true;
450 }
451 }
bbbf42bb
SR
452 return val;
453 },
454
45c70c9e 455 // Open menu
456 openSettingsMenu: function() {
457 // Close the description panel
458 $D('noVNC_description').style.display = "none";
459 // Close clipboard panel if open
460 if (UI.clipboardOpen === true) {
461 UI.toggleClipboardPanel();
462 }
463 // Close connection settings if open
464 if (UI.connSettingsOpen === true) {
465 UI.toggleConnectPanel();
466 }
467 // Close XVP panel if open
468 if (UI.xvpOpen === true) {
469 UI.toggleXvpPanel();
470 }
471 $D('noVNC_settings').style.display = "block";
472 $D('settingsButton').className = "noVNC_status_button_selected";
473 UI.settingsOpen = true;
474 },
475
476 // Close menu (without applying settings)
477 closeSettingsMenu: function() {
478 $D('noVNC_settings').style.display = "none";
479 $D('settingsButton').className = "noVNC_status_button";
480 UI.settingsOpen = false;
481 },
482
483 // Toggle the settings menu:
484 // On open, settings are refreshed from saved cookies.
485 // On close, settings are applied
486 toggleSettingsPanel: function() {
487 // Close the description panel
488 $D('noVNC_description').style.display = "none";
489 if (UI.settingsOpen) {
490 UI.settingsApply();
491 UI.closeSettingsMenu();
492 } else {
493 UI.updateSetting('encrypt');
494 UI.updateSetting('true_color');
495 if (Util.browserSupportsCursorURIs()) {
496 UI.updateSetting('cursor');
497 } else {
498 UI.updateSetting('cursor', !UI.isTouchDevice);
499 $D('noVNC_cursor').disabled = true;
500 }
501 UI.updateSetting('clip');
502 UI.updateSetting('resize');
503 UI.updateSetting('shared');
504 UI.updateSetting('view_only');
505 UI.updateSetting('path');
506 UI.updateSetting('repeaterID');
507 UI.updateSetting('stylesheet');
508 UI.updateSetting('logging');
509
510 UI.openSettingsMenu();
511 }
512 },
513
bbbf42bb
SR
514 // Show the XVP panel
515 toggleXvpPanel: function() {
516 // Close the description panel
517 $D('noVNC_description').style.display = "none";
518 // Close settings if open
519 if (UI.settingsOpen === true) {
520 UI.settingsApply();
521 UI.closeSettingsMenu();
522 }
523 // Close connection settings if open
524 if (UI.connSettingsOpen === true) {
525 UI.toggleConnectPanel();
526 }
bbbf42bb
SR
527 // Close clipboard panel if open
528 if (UI.clipboardOpen === true) {
529 UI.toggleClipboardPanel();
530 }
531 // Toggle XVP panel
532 if (UI.xvpOpen === true) {
533 $D('noVNC_xvp').style.display = "none";
534 $D('xvpButton').className = "noVNC_status_button";
535 UI.xvpOpen = false;
536 } else {
537 $D('noVNC_xvp').style.display = "block";
538 $D('xvpButton').className = "noVNC_status_button_selected";
539 UI.xvpOpen = true;
540 }
541 },
542
543 // Show the clipboard panel
544 toggleClipboardPanel: function() {
545 // Close the description panel
546 $D('noVNC_description').style.display = "none";
547 // Close settings if open
548 if (UI.settingsOpen === true) {
549 UI.settingsApply();
550 UI.closeSettingsMenu();
551 }
552 // Close connection settings if open
553 if (UI.connSettingsOpen === true) {
554 UI.toggleConnectPanel();
555 }
bbbf42bb
SR
556 // Close XVP panel if open
557 if (UI.xvpOpen === true) {
558 UI.toggleXvpPanel();
559 }
560 // Toggle Clipboard Panel
561 if (UI.clipboardOpen === true) {
562 $D('noVNC_clipboard').style.display = "none";
563 $D('clipboardButton').className = "noVNC_status_button";
564 UI.clipboardOpen = false;
565 } else {
566 $D('noVNC_clipboard').style.display = "block";
567 $D('clipboardButton').className = "noVNC_status_button_selected";
568 UI.clipboardOpen = true;
569 }
570 },
571
7d1dc09a 572 // Toggle fullscreen mode
573 toggleFullscreen: function() {
574 if (document.fullscreenElement || // alternative standard method
575 document.mozFullScreenElement || // currently working methods
576 document.webkitFullscreenElement ||
a6357e82 577 document.msFullscreenElement) {
7d1dc09a 578 if (document.exitFullscreen) {
579 document.exitFullscreen();
580 } else if (document.mozCancelFullScreen) {
581 document.mozCancelFullScreen();
582 } else if (document.webkitExitFullscreen) {
583 document.webkitExitFullscreen();
584 } else if (document.msExitFullscreen) {
585 document.msExitFullscreen();
586 }
587 } else {
588 if (document.documentElement.requestFullscreen) {
589 document.documentElement.requestFullscreen();
590 } else if (document.documentElement.mozRequestFullScreen) {
591 document.documentElement.mozRequestFullScreen();
592 } else if (document.documentElement.webkitRequestFullscreen) {
593 document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
594 } else if (document.body.msRequestFullscreen) {
595 document.body.msRequestFullscreen();
596 }
597 }
a6357e82 598 UI.enableDisableViewClip();
7d1dc09a 599 UI.updateFullscreenButton();
600 },
601
602 updateFullscreenButton: function() {
603 if (document.fullscreenElement || // alternative standard method
604 document.mozFullScreenElement || // currently working methods
605 document.webkitFullscreenElement ||
606 document.msFullscreenElement ) {
607 $D('fullscreenButton').className = "noVNC_status_button_selected";
608 } else {
609 $D('fullscreenButton').className = "noVNC_status_button";
610 }
611 },
612
bbbf42bb
SR
613 // Show the connection settings panel/menu
614 toggleConnectPanel: function() {
615 // Close the description panel
616 $D('noVNC_description').style.display = "none";
617 // Close connection settings if open
618 if (UI.settingsOpen === true) {
619 UI.settingsApply();
620 UI.closeSettingsMenu();
621 $D('connectButton').className = "noVNC_status_button";
622 }
623 // Close clipboard panel if open
624 if (UI.clipboardOpen === true) {
625 UI.toggleClipboardPanel();
626 }
bbbf42bb
SR
627 // Close XVP panel if open
628 if (UI.xvpOpen === true) {
629 UI.toggleXvpPanel();
630 }
631
632 // Toggle Connection Panel
633 if (UI.connSettingsOpen === true) {
634 $D('noVNC_controls').style.display = "none";
635 $D('connectButton').className = "noVNC_status_button";
636 UI.connSettingsOpen = false;
637 UI.saveSetting('host');
638 UI.saveSetting('port');
c55f05f6 639 UI.saveSetting('token');
bbbf42bb
SR
640 //UI.saveSetting('password');
641 } else {
642 $D('noVNC_controls').style.display = "block";
643 $D('connectButton').className = "noVNC_status_button_selected";
644 UI.connSettingsOpen = true;
645 $D('noVNC_host').focus();
646 }
647 },
648
bbbf42bb
SR
649 // Save/apply settings when 'Apply' button is pressed
650 settingsApply: function() {
651 //Util.Debug(">> settingsApply");
652 UI.saveSetting('encrypt');
653 UI.saveSetting('true_color');
58ded70d 654 if (Util.browserSupportsCursorURIs()) {
bbbf42bb
SR
655 UI.saveSetting('cursor');
656 }
72747869 657
f8b399d7 658 UI.saveSetting('resize');
72747869
SR
659
660 if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
661 UI.forceSetting('clip', false);
662 }
663
664 UI.saveSetting('clip');
bbbf42bb
SR
665 UI.saveSetting('shared');
666 UI.saveSetting('view_only');
667 UI.saveSetting('path');
668 UI.saveSetting('repeaterID');
669 UI.saveSetting('stylesheet');
670 UI.saveSetting('logging');
671
672 // Settings with immediate (non-connected related) effect
673 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
674 WebUtil.init_logging(UI.getSetting('logging'));
675 UI.setViewClip();
31ddaa1c 676 UI.updateViewDrag();
bbbf42bb
SR
677 //Util.Debug("<< settingsApply");
678 },
679
680
bbbf42bb
SR
681 setPassword: function() {
682 UI.rfb.sendPassword($D('noVNC_password').value);
683 //Reset connect button.
684 $D('noVNC_connect_button').value = "Connect";
340290fa 685 $D('noVNC_connect_button').onclick = UI.connect;
bbbf42bb 686 //Hide connection panel.
f8ddfc73 687 UI.toggleConnectPanel();
bbbf42bb
SR
688 return false;
689 },
da6dd893 690
bbbf42bb
SR
691 sendCtrlAltDel: function() {
692 UI.rfb.sendCtrlAltDel();
693 },
53fc7392 694
bbbf42bb
SR
695 xvpShutdown: function() {
696 UI.rfb.xvpShutdown();
697 },
01a9eee9 698
bbbf42bb
SR
699 xvpReboot: function() {
700 UI.rfb.xvpReboot();
701 },
53fc7392 702
bbbf42bb
SR
703 xvpReset: function() {
704 UI.rfb.xvpReset();
705 },
01a9eee9 706
bbbf42bb
SR
707 setMouseButton: function(num) {
708 if (typeof num === 'undefined') {
709 // Disable mouse buttons
710 num = -1;
da6dd893 711 }
bbbf42bb
SR
712 if (UI.rfb) {
713 UI.rfb.get_mouse().set_touchButton(num);
714 }
715
716 var blist = [0, 1,2,4];
717 for (var b = 0; b < blist.length; b++) {
718 var button = $D('noVNC_mouse_button' + blist[b]);
719 if (blist[b] === num) {
720 button.style.display = "";
721 } else {
722 button.style.display = "none";
723 }
724 }
725 },
726
bbbf42bb
SR
727 // Disable/enable XVP button
728 updateXvpVisualState: function(ver) {
729 if (ver >= 1) {
730 $D('xvpButton').style.display = 'inline';
731 } else {
732 $D('xvpButton').style.display = 'none';
733 // Close XVP panel if open
734 if (UI.xvpOpen === true) {
735 UI.toggleXvpPanel();
736 }
737 }
738 },
739
740 // Display the desktop name in the document title
741 updateDocumentTitle: function(rfb, name) {
742 document.title = name + " - noVNC";
743 },
744
745 clipReceive: function(rfb, text) {
746 Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
747 $D('noVNC_clipboard_text').value = text;
748 Util.Debug("<< UI.clipReceive");
749 },
750
751 connect: function() {
752 UI.closeSettingsMenu();
c506a481 753 UI.toggleConnectPanel();
f00b1e37 754
bbbf42bb
SR
755 var host = $D('noVNC_host').value;
756 var port = $D('noVNC_port').value;
757 var password = $D('noVNC_password').value;
c55f05f6 758 var token = $D('noVNC_token').value;
bbbf42bb 759 var path = $D('noVNC_path').value;
c55f05f6
MXPN
760
761 //if token is in path then ignore the new token variable
762 if (token) {
763 path = WebUtil.injectParamIfMissing(path, "token", token);
764 }
765
bbbf42bb
SR
766 if ((!host) || (!port)) {
767 throw new Error("Must set host and port");
768 }
53fc7392 769
d9fc1c7b 770 if (!UI.initRFB()) return;
58ded70d 771
bbbf42bb
SR
772 UI.rfb.set_encrypt(UI.getSetting('encrypt'));
773 UI.rfb.set_true_color(UI.getSetting('true_color'));
774 UI.rfb.set_local_cursor(UI.getSetting('cursor'));
775 UI.rfb.set_shared(UI.getSetting('shared'));
776 UI.rfb.set_view_only(UI.getSetting('view_only'));
777 UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
53fc7392 778
bbbf42bb 779 UI.rfb.connect(host, port, password, path);
8e0f0088 780
bbbf42bb
SR
781 //Close dialog.
782 setTimeout(UI.setBarPosition, 100);
783 $D('noVNC_logo').style.display = "none";
553864e8 784 $D('noVNC_screen').style.display = "inline";
bbbf42bb 785 },
5299db1a 786
bbbf42bb
SR
787 disconnect: function() {
788 UI.closeSettingsMenu();
789 UI.rfb.disconnect();
8e0f0088 790
f8b399d7 791 // Restore the callback used for initial resize
777cb7a0 792 UI.rfb.set_onFBUComplete(UI.initialResize);
f8b399d7 793
bbbf42bb 794 $D('noVNC_logo').style.display = "block";
553864e8 795 $D('noVNC_screen').style.display = "none";
fdedbafb 796
e543525f 797 // Don't display the connection settings until we're actually disconnected
bbbf42bb
SR
798 },
799
800 displayBlur: function() {
58ded70d
SR
801 if (!UI.rfb) return;
802
bbbf42bb
SR
803 UI.rfb.get_keyboard().set_focused(false);
804 UI.rfb.get_mouse().set_focused(false);
805 },
806
807 displayFocus: function() {
58ded70d
SR
808 if (!UI.rfb) return;
809
bbbf42bb
SR
810 UI.rfb.get_keyboard().set_focused(true);
811 UI.rfb.get_mouse().set_focused(true);
812 },
813
814 clipClear: function() {
815 $D('noVNC_clipboard_text').value = "";
816 UI.rfb.clipboardPasteFrom("");
817 },
818
819 clipSend: function() {
820 var text = $D('noVNC_clipboard_text').value;
821 Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
822 UI.rfb.clipboardPasteFrom(text);
823 Util.Debug("<< UI.clipSend");
824 },
825
777cb7a0 826
827 // Apply remote resizing or local scaling
0bd2cbac 828 applyResizeMode: function() {
777cb7a0 829 if (!UI.rfb) return;
830
831 var screen = UI.screenSize();
832
833 if (screen && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
834
835 var display = UI.rfb.get_display();
836 var resizeMode = UI.getSetting('resize');
837
838 if (resizeMode === 'remote') {
839
840 // Request changing the resolution of the remote display to
841 // the size of the local browser viewport.
842
843 // In order to not send multiple requests before the browser-resize
844 // is finished we wait 0.5 seconds before sending the request.
845 clearTimeout(UI.resizeTimeout);
846 UI.resizeTimeout = setTimeout(function(){
847
848 // Limit the viewport to the size of the browser window
849 display.set_maxWidth(screen.w);
850 display.set_maxHeight(screen.h);
851
852 Util.Debug('Attempting setDesktopSize(' +
853 screen.w + ', ' + screen.h + ')');
854
855 // Request a remote size covering the viewport
856 UI.rfb.setDesktopSize(screen.w, screen.h);
857 }, 500);
858
859 } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
860 var downscaleOnly = resizeMode === 'downscale';
861 var scaleRatio = display.autoscale(screen.w, screen.h, downscaleOnly);
862 UI.rfb.get_mouse().set_scale(scaleRatio);
863 Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
864 }
865 }
866 },
867
868 // The screen is always the same size as the available viewport
869 // in the browser window minus the height of the control bar
0bd2cbac 870 screenSize: function() {
777cb7a0 871 var screen = $D('noVNC_screen');
872
873 // Hide the scrollbars until the size is calculated
874 screen.style.overflow = "hidden";
875
876 var pos = Util.getPosition(screen);
877 var w = pos.width;
878 var h = pos.height;
879
880 screen.style.overflow = "visible";
881
882 if (isNaN(w) || isNaN(h)) {
883 return false;
884 } else {
885 return {w: w, h: h};
886 }
887 },
888
889 // Normally we only apply the current resize mode after a window resize
890 // event. This means that when a new connection is opened, there is no
891 // resize mode active.
892 // We have to wait until the first FBU because this is where the client
893 // will find the supported encodings of the server. Some calls later in
894 // the chain is dependant on knowing the server-capabilities.
895 initialResize: function(rfb, fbu) {
896 UI.applyResizeMode();
897 // After doing this once, we remove the callback.
898 UI.rfb.set_onFBUComplete(function() { });
899 },
900
30bfff81 901 // Set and configure viewport clipping
bbbf42bb
SR
902 setViewClip: function(clip) {
903 var display;
904 if (UI.rfb) {
905 display = UI.rfb.get_display();
906 } else {
a6357e82 907 UI.forceSetting('clip', clip);
bbbf42bb
SR
908 return;
909 }
8e0f0088 910
bbbf42bb
SR
911 var cur_clip = display.get_viewport();
912
913 if (typeof(clip) !== 'boolean') {
914 // Use current setting
915 clip = UI.getSetting('clip');
916 }
8e0f0088 917
bbbf42bb
SR
918 if (clip && !cur_clip) {
919 // Turn clipping on
920 UI.updateSetting('clip', true);
921 } else if (!clip && cur_clip) {
922 // Turn clipping off
923 UI.updateSetting('clip', false);
924 display.set_viewport(false);
31ddaa1c 925 // Disable max dimensions
fdedbafb 926 display.set_maxWidth(0);
927 display.set_maxHeight(0);
f8b399d7 928 display.viewportChangeSize();
bbbf42bb
SR
929 }
930 if (UI.getSetting('clip')) {
931 // If clipping, update clipping settings
bbbf42bb 932 display.set_viewport(true);
fdedbafb 933
553864e8 934 var size = UI.screenSize();
fdedbafb 935 if (size) {
936 display.set_maxWidth(size.w);
937 display.set_maxHeight(size.h);
938
939 // Hide potential scrollbars that can skew the position
553864e8 940 $D('noVNC_screen').style.overflow = "hidden";
fdedbafb 941
942 // The x position marks the left margin of the canvas,
943 // remove the margin from both sides to keep it centered
944 var new_w = size.w - (2 * Util.getPosition($D('noVNC_canvas')).x);
945
553864e8 946 $D('noVNC_screen').style.overflow = "visible";
fdedbafb 947
948 display.viewportChangeSize(new_w, size.h);
949 }
bbbf42bb
SR
950 }
951 },
952
30bfff81 953 // Handle special cases where clipping is forced on/off or locked
0bd2cbac 954 enableDisableViewClip: function() {
30bfff81 955 var resizeElem = $D('noVNC_resize');
a6357e82 956 var connected = UI.rfb && UI.rfb_state === 'normal';
957
f620259b 958 if (UI.isSafari) {
959 // Safari auto-hides the scrollbars which makes them
960 // impossible to use in most cases
961 UI.setViewClip(true);
962 $D('noVNC_clip').disabled = true;
963 } else if (resizeElem.value === 'downscale' || resizeElem.value === 'scale') {
a6357e82 964 // Disable clipping if we are scaling
965 UI.setViewClip(false);
966 $D('noVNC_clip').disabled = true;
967 } else if (document.msFullscreenElement) {
968 // The browser is IE and we are in fullscreen mode.
969 // - We need to force clipping while in fullscreen since
970 // scrollbars doesn't work.
4e471b5b 971 UI.popupStatus("Forcing clipping mode since scrollbars aren't supported by IE in fullscreen");
a6357e82 972 UI.rememberedClipSetting = UI.getSetting('clip');
973 UI.setViewClip(true);
30bfff81 974 $D('noVNC_clip').disabled = true;
a6357e82 975 } else if (document.body.msRequestFullscreen && UI.rememberedClip !== null) {
976 // Restore view clip to what it was before fullscreen on IE
977 UI.setViewClip(UI.rememberedClipSetting);
978 $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
30bfff81 979 } else {
980 $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
981 if (UI.isTouchDevice) {
a6357e82 982 UI.setViewClip(true);
30bfff81 983 }
984 }
985 },
986
31ddaa1c 987 // Update the viewport drag/move button
988 updateViewDrag: function(drag) {
58ded70d 989 if (!UI.rfb) return;
bbbf42bb 990
f8b399d7 991 var vmb = $D('noVNC_view_drag_button');
bbbf42bb 992
31ddaa1c 993 // Check if viewport drag is possible
f8b399d7 994 if (UI.rfb_state === 'normal' &&
995 UI.rfb.get_display().get_viewport() &&
fdedbafb 996 UI.rfb.get_display().clippingDisplay()) {
31ddaa1c 997
998 // Show and enable the drag button
f8b399d7 999 vmb.style.display = "inline";
29a0e6a8 1000 vmb.disabled = false;
1001
31ddaa1c 1002 } else {
1003 // The VNC content is the same size as
1004 // or smaller than the display
1005
1006 if (UI.rfb.get_viewportDrag) {
1007 // Turn off viewport drag when it's
1008 // active since it can't be used here
1009 vmb.className = "noVNC_status_button";
1010 UI.rfb.set_viewportDrag(false);
1011 }
1012
1013 // Disable or hide the drag button
1014 if (UI.rfb_state === 'normal' && UI.isTouchDevice) {
1015 vmb.style.display = "inline";
1016 vmb.disabled = true;
1017 } else {
1018 vmb.style.display = "none";
1019 }
1020 return;
1021 }
1022
1023 if (typeof(drag) !== "undefined" &&
1024 typeof(drag) !== "object") {
1025 if (drag) {
1026 vmb.className = "noVNC_status_button_selected";
1027 UI.rfb.set_viewportDrag(true);
1028 } else {
1029 vmb.className = "noVNC_status_button";
1030 UI.rfb.set_viewportDrag(false);
1031 }
1032 }
1033 },
29a0e6a8 1034
31ddaa1c 1035 toggleViewDrag: function() {
1036 if (!UI.rfb) return;
1037
1038 var vmb = $D('noVNC_view_drag_button');
1039 if (UI.rfb.get_viewportDrag()) {
1040 vmb.className = "noVNC_status_button";
1041 UI.rfb.set_viewportDrag(false);
f8b399d7 1042 } else {
31ddaa1c 1043 vmb.className = "noVNC_status_button_selected";
1044 UI.rfb.set_viewportDrag(true);
f8b399d7 1045 }
1046 },
1047
bbbf42bb
SR
1048 // On touch devices, show the OS keyboard
1049 showKeyboard: function() {
1050 var kbi = $D('keyboardinput');
1051 var skb = $D('showKeyboard');
1052 var l = kbi.value.length;
1053 if(UI.keyboardVisible === false) {
1054 kbi.focus();
1055 try { kbi.setSelectionRange(l, l); } // Move the caret to the end
1056 catch (err) {} // setSelectionRange is undefined in Google Chrome
1057 UI.keyboardVisible = true;
1058 skb.className = "noVNC_status_button_selected";
1059 } else if(UI.keyboardVisible === true) {
1060 kbi.blur();
1061 skb.className = "noVNC_status_button";
1062 UI.keyboardVisible = false;
1063 }
1064 },
1065
1066 keepKeyboard: function() {
1067 clearTimeout(UI.hideKeyboardTimeout);
1068 if(UI.keyboardVisible === true) {
1069 $D('keyboardinput').focus();
1070 $D('showKeyboard').className = "noVNC_status_button_selected";
1071 } else if(UI.keyboardVisible === false) {
1072 $D('keyboardinput').blur();
1073 $D('showKeyboard').className = "noVNC_status_button";
1074 }
1075 },
1076
1077 keyboardinputReset: function() {
1078 var kbi = $D('keyboardinput');
1079 kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
1080 UI.lastKeyboardinput = kbi.value;
1081 },
1082
1083 // When normal keyboard events are left uncought, use the input events from
1084 // the keyboardinput element instead and generate the corresponding key events.
1085 // This code is required since some browsers on Android are inconsistent in
1086 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1087 keyInput: function(event) {
3b8ec46f 1088
58ded70d 1089 if (!UI.rfb) return;
3b8ec46f 1090
bbbf42bb 1091 var newValue = event.target.value;
1138bdd4 1092
1093 if (!UI.lastKeyboardinput) {
1094 UI.keyboardinputReset();
1095 }
cb3e4deb 1096 var oldValue = UI.lastKeyboardinput;
bbbf42bb
SR
1097
1098 var newLen;
1099 try {
1100 // Try to check caret position since whitespace at the end
1101 // will not be considered by value.length in some browsers
1102 newLen = Math.max(event.target.selectionStart, newValue.length);
1103 } catch (err) {
1104 // selectionStart is undefined in Google Chrome
1105 newLen = newValue.length;
1106 }
1107 var oldLen = oldValue.length;
1108
1109 var backspaces;
1110 var inputs = newLen - oldLen;
1111 if (inputs < 0) {
1112 backspaces = -inputs;
1113 } else {
1114 backspaces = 0;
1115 }
8e0f0088 1116
bbbf42bb
SR
1117 // Compare the old string with the new to account for
1118 // text-corrections or other input that modify existing text
1119 var i;
1120 for (i = 0; i < Math.min(oldLen, newLen); i++) {
1121 if (newValue.charAt(i) != oldValue.charAt(i)) {
1122 inputs = newLen - i;
1123 backspaces = oldLen - i;
1124 break;
1125 }
1126 }
1127
1128 // Send the key events
1129 for (i = 0; i < backspaces; i++) {
1130 UI.rfb.sendKey(XK_BackSpace);
1131 }
1132 for (i = newLen - inputs; i < newLen; i++) {
1133 UI.rfb.sendKey(newValue.charCodeAt(i));
1134 }
1135
1136 // Control the text content length in the keyboardinput element
1137 if (newLen > 2 * UI.defaultKeyboardinputLen) {
1138 UI.keyboardinputReset();
1139 } else if (newLen < 1) {
1140 // There always have to be some text in the keyboardinput
1141 // element with which backspace can interact.
1142 UI.keyboardinputReset();
1143 // This sometimes causes the keyboard to disappear for a second
1144 // but it is required for the android keyboard to recognize that
1145 // text has been added to the field
1146 event.target.blur();
1147 // This has to be ran outside of the input handler in order to work
1148 setTimeout(function() { UI.keepKeyboard(); }, 0);
1149 } else {
1150 UI.lastKeyboardinput = newValue;
1151 }
1152 },
1153
1154 keyInputBlur: function() {
1155 $D('showKeyboard').className = "noVNC_status_button";
1156 //Weird bug in iOS if you change keyboardVisible
1157 //here it does not actually occur so next time
1158 //you click keyboard icon it doesnt work.
1159 UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100);
1160 },
1161
cd611a53 1162 toggleExtraKeys: function() {
bbbf42bb
SR
1163 UI.keepKeyboard();
1164 if(UI.extraKeysVisible === false) {
1165 $D('toggleCtrlButton').style.display = "inline";
1166 $D('toggleAltButton').style.display = "inline";
1167 $D('sendTabButton').style.display = "inline";
1168 $D('sendEscButton').style.display = "inline";
cd611a53 1169 $D('toggleExtraKeysButton').className = "noVNC_status_button_selected";
bbbf42bb
SR
1170 UI.extraKeysVisible = true;
1171 } else if(UI.extraKeysVisible === true) {
1172 $D('toggleCtrlButton').style.display = "";
1173 $D('toggleAltButton').style.display = "";
1174 $D('sendTabButton').style.display = "";
1175 $D('sendEscButton').style.display = "";
cd611a53 1176 $D('toggleExtraKeysButton').className = "noVNC_status_button";
bbbf42bb
SR
1177 UI.extraKeysVisible = false;
1178 }
1179 },
1180
1181 toggleCtrl: function() {
1182 UI.keepKeyboard();
1183 if(UI.ctrlOn === false) {
1184 UI.rfb.sendKey(XK_Control_L, true);
1185 $D('toggleCtrlButton').className = "noVNC_status_button_selected";
1186 UI.ctrlOn = true;
1187 } else if(UI.ctrlOn === true) {
1188 UI.rfb.sendKey(XK_Control_L, false);
1189 $D('toggleCtrlButton').className = "noVNC_status_button";
1190 UI.ctrlOn = false;
1191 }
1192 },
1193
1194 toggleAlt: function() {
1195 UI.keepKeyboard();
1196 if(UI.altOn === false) {
1197 UI.rfb.sendKey(XK_Alt_L, true);
1198 $D('toggleAltButton').className = "noVNC_status_button_selected";
1199 UI.altOn = true;
1200 } else if(UI.altOn === true) {
1201 UI.rfb.sendKey(XK_Alt_L, false);
1202 $D('toggleAltButton').className = "noVNC_status_button";
1203 UI.altOn = false;
1204 }
1205 },
1206
1207 sendTab: function() {
1208 UI.keepKeyboard();
1209 UI.rfb.sendKey(XK_Tab);
1210 },
1211
1212 sendEsc: function() {
1213 UI.keepKeyboard();
1214 UI.rfb.sendKey(XK_Escape);
1215 },
1216
1217 setKeyboard: function() {
1218 UI.keyboardVisible = false;
1219 },
1220
bbbf42bb
SR
1221 //Helper to add options to dropdown.
1222 addOption: function(selectbox, text, value) {
1223 var optn = document.createElement("OPTION");
1224 optn.text = text;
1225 optn.value = value;
1226 selectbox.options.add(optn);
1227 },
1228
1229 setBarPosition: function() {
1230 $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
1231 $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
1232
553864e8 1233 var vncwidth = $D('noVNC_container').style.offsetWidth;
bbbf42bb
SR
1234 $D('noVNC-control-bar').style.width = vncwidth + 'px';
1235 }
1236
1237 };
1238})();