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