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