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