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