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