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