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