]> git.proxmox.com Git - novnc-pve.git/blob - pveui.js
bump version to 0.4-4
[novnc-pve.git] / pveui.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
5 * Licensed under MPL 2.0 (see LICENSE.txt)
6 *
7 * See README.md for usage and integration instructions.
8 */
9
10 "use strict";
11 /*jslint white: false, browser: true */
12 /*global window, $D, Util, WebUtil, RFB, Display */
13
14 // Load supporting scripts
15 window.onscriptsload = function () { UI.load(); };
16 window.onload = function () { UI.keyboardinputReset(); };
17 Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
18 "keysymdef.js", "keyboard.js", "input.js", "display.js",
19 "jsunzip.js", "rfb.js", "keysym.js"]);
20
21 var UI = {
22
23 rfb_state : 'loaded',
24 pveCommandsOpen: false,
25 settingsOpen : false,
26 connSettingsOpen : false,
27 popupStatusOpen : false,
28 clipboardOpen: false,
29 keyboardVisible: false,
30 hideKeyboardTimeout: null,
31 lastKeyboardinput: null,
32 defaultKeyboardinputLen: 100,
33 extraKeysVisible: false,
34 ctrlOn: false,
35 altOn: false,
36 isTouchDevice: false,
37
38 consoletype: undefined,
39 vmid: undefined,
40 vmname: undefined,
41 nodename: undefined,
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.pve_start, callback);
47 },
48
49 // Proxmox VE related code
50
51 urlEncode: function(object) {
52 var i,value, params = [];
53
54 for (i in object) {
55 if (object.hasOwnProperty(i)) {
56 value = object[i];
57 if (value === undefined) value = '';
58 params.push(encodeURIComponent(i) + '=' + encodeURIComponent(String(value)));
59 }
60 }
61
62 return params.join('&');
63 },
64
65 API2Request: function(reqOpts) {
66
67 reqOpts.method = reqOpts.method || 'GET';
68
69 var xhr = new XMLHttpRequest();
70
71 xhr.onload = function() {
72 var scope = reqOpts.scope || this;
73 var result;
74 var errmsg;
75
76 if (xhr.readyState === 4) {
77 var ctype = xhr.getResponseHeader('Content-Type');
78 if (xhr.status === 200) {
79 if (ctype.match(/application\/json;/)) {
80 result = JSON.parse(xhr.responseText);
81 } else {
82 errmsg = 'got unexpected content type ' + ctype;
83 }
84 } else {
85 errmsg = 'Error ' + xhr.status + ': ' + xhr.statusText;
86 }
87 } else {
88 errmsg = 'Connection error - server offline?';
89 }
90
91 if (errmsg !== undefined) {
92 if (reqOpts.failure) {
93 reqOpts.failure.call(scope, errmsg);
94 }
95 } else {
96 if (reqOpts.success) {
97 reqOpts.success.call(scope, result);
98 }
99 }
100 if (reqOpts.callback) {
101 reqOpts.callback.call(scope, errmsg === undefined);
102 }
103 }
104
105 var data = UI.urlEncode(reqOpts.params || {});
106
107 if (reqOpts.method === 'GET') {
108 xhr.open(reqOpts.method, "/api2/json" + reqOpts.url + '?' + data);
109 } else {
110 xhr.open(reqOpts.method, "/api2/json" + reqOpts.url);
111 }
112 xhr.setRequestHeader('Cache-Control', 'no-cache');
113 if (reqOpts.method === 'POST' || reqOpts.method === 'PUT') {
114 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
115 console.dir(PVE);
116 xhr.setRequestHeader('CSRFPreventionToken', PVE.CSRFPreventionToken);
117 xhr.send(data);
118 } else if (reqOpts.method === 'GET') {
119 xhr.send();
120 } else {
121 throw "unknown method";
122 }
123
124
125 },
126
127 pve_vm_command: function(cmd, params, reload) {
128 var baseUrl;
129
130 if (UI.consoletype === 'kvm') {
131 baseUrl = '/nodes/' + UI.nodename + '/qemu/' + UI.vmid;
132 } else if (UI.consoletype === 'openvz') {
133 baseUrl = '/nodes/' + UI.nodename + '/openvz/' + UI.vmid;
134 } else {
135 throw "unknown VM type";
136 }
137
138 var show_msg = function(klass, msg) {
139 // show msg for 5 seconds
140 var oldklass = $D('noVNC-control-bar').getAttribute("class");
141 $D('noVNC-control-bar').setAttribute("class", klass);
142 var oldmsg = $D('noVNC_status').innerHTML;
143 $D('noVNC_status').innerHTML = msg;
144 setTimeout(function() {
145 var curmsg = $D('noVNC_status').innerHTML;
146 if (curmsg === msg) {
147 $D('noVNC_status').innerHTML = oldmsg;
148 }
149 var curklass = $D('noVNC-control-bar').getAttribute("class");
150 if (curklass === klass) {
151 $D('noVNC-control-bar').setAttribute("class", oldklass);
152 }
153 }, 5000);
154 };
155
156 UI.API2Request({
157 params: params,
158 url: baseUrl + "/status/" + cmd,
159 method: 'POST',
160 failure: function(msg) {
161 show_msg('noVNC_status_warn', msg);
162 },
163 success: function() {
164 show_msg('noVNC_status_normall', "VM command '" + cmd +"' successful");
165 if (reload) {
166 setTimeout(function() {
167 UI.pveReload();
168 }, 1000);
169 };
170 }
171 });
172 },
173
174 pveCmdStart: function() {
175 if (UI.pveCommandsOpen === true) {
176 UI.togglePVECommandPanel();
177 }
178 UI.pve_vm_command('start', {}, true);
179 },
180
181 pveCmdShutdown: function() {
182 if (UI.pveCommandsOpen === true) {
183 UI.togglePVECommandPanel();
184 }
185 var msg = gettext("Do you really want to shutdown VM {0}?");
186 msg = msg.replace(/\{0\}/, UI.vmid);
187
188 if (confirm(msg) === true) {
189 UI.pve_vm_command('shutdown');
190 }
191 },
192
193 pveCmdStop: function() {
194 if (UI.pveCommandsOpen === true) {
195 UI.togglePVECommandPanel();
196 }
197
198 var msg = gettext("Do you really want to stop VM {0}?");
199 msg = msg.replace(/\{0\}/, UI.vmid);
200
201 if (confirm(msg) === true) {
202 UI.pve_vm_command('stop');
203 }
204 },
205
206 pveCmdReset: function() {
207 if (UI.pveCommandsOpen === true) {
208 UI.togglePVECommandPanel();
209 }
210 var msg = gettext("Do you really want to reset VM {0}?");
211 msg = msg.replace(/\{0\}/, UI.vmid);
212
213 if (confirm(msg) === true) {
214 UI.pve_vm_command('reset');
215 }
216 },
217
218 pveCmdSuspend: function() {
219 if (UI.pveCommandsOpen === true) {
220 UI.togglePVECommandPanel();
221 }
222 var msg = gettext("Do you really want to suspend VM {0}?");
223 msg = msg.replace(/\{0\}/, UI.vmid);
224
225 if (confirm(msg) === true) {
226 UI.pve_vm_command('suspend');
227 }
228 },
229
230 pveCmdResume: function() {
231 if (UI.pveCommandsOpen === true) {
232 UI.togglePVECommandPanel();
233 }
234 UI.pve_vm_command('resume');
235 },
236
237 pveCmdReload: function() {
238 if (UI.pveCommandsOpen === true) {
239 UI.togglePVECommandPanel();
240 }
241 UI.pveReload();
242 },
243
244 pveReload: function() {
245 location.reload();
246 },
247
248 pve_start: function(callback) {
249 UI.consoletype = WebUtil.getQueryVar('console');
250 UI.vmid = WebUtil.getQueryVar('vmid');
251 UI.vmname = WebUtil.getQueryVar('vmname');
252 UI.nodename = WebUtil.getQueryVar('node');
253
254 var url;
255 var wsurl;
256 var params = { websocket: 1 };
257 var btn;
258
259 var vmcmd_btns = ['pveStartButton', 'pveShutdownButton', 'pveStopButton', 'pveResetButton', 'pveSuspendButton', 'pveResumeButton'];
260 vmcmd_btns.forEach(function(btn) {
261 var el = $D(btn);
262 el.style.display = "none";
263 el.value = gettext(el.value);
264 });
265
266
267 var title;
268
269 if (UI.consoletype === 'kvm') {
270 var baseUrl = '/nodes/' + UI.nodename + '/qemu/' + UI.vmid;
271 url = baseUrl + '/vncproxy';
272 wsurl = baseUrl + '/vncwebsocket';
273 vmcmd_btns.forEach(function(btn) {
274 $D(btn).style.display = "";
275 });
276 title = "VM " + UI.vmid;
277 if (UI.vmname) {
278 title += " ('" + UI.vmname + "')";
279 }
280 } else if (UI.consoletype === 'openvz') {
281 var baseUrl = '/nodes/' + UI.nodename + '/openvz/' + UI.vmid;
282 url = baseUrl + '/vncproxy';
283 wsurl = baseUrl + '/vncwebsocket';
284 ['pveStartButton', 'pveShutdownButton', 'pveStopButton'].forEach(function(btn) {
285 $D(btn).style.display = "";
286 });
287 title = "CT " + UI.vmid;
288 if (UI.vmname) {
289 title += " ('" + UI.vmname + "')";
290 }
291 } else if (UI.consoletype === 'shell') {
292 var baseUrl = '/nodes/' + UI.nodename;
293 url = baseUrl + '/vncshell';
294 wsurl = baseUrl + '/vncwebsocket';
295 title = "node '" + UI.nodename + "'";
296 } else if (UI.consoletype === 'upgrade') {
297 var baseUrl = '/nodes/' + UI.nodename;
298 url = baseUrl + '/vncshell';
299 wsurl = baseUrl + '/vncwebsocket';
300 params.upgrade = 1;
301 title = gettext('System upgrade on node {0}');
302 title = title.replace(/\{0\}/, UI.nodename);
303 } else {
304 throw "implement me";
305 }
306
307 document.title = title;
308
309 var start_vnc_viewer = function(param) {
310 console.dir(param);
311
312 var wsparams = UI.urlEncode({
313 port: param.port,
314 vncticket: param.ticket
315 });
316
317 UI.updateSetting('host', window.location.hostname);
318 UI.updateSetting('port', window.location.port);
319 UI.updateSetting('password', param.ticket);
320 UI.updateSetting('encrypt', true);
321 UI.updateSetting('true_color', true);
322 UI.updateSetting('cursor', !UI.isTouchDevice);
323 UI.updateSetting('shared', true);
324 UI.updateSetting('view_only', false);
325
326 UI.updateSetting('path', 'api2/json' + wsurl + "?" + wsparams);
327
328 UI.start(callback);
329 };
330
331 UI.API2Request({
332 url: url,
333 method: 'POST',
334 params: params,
335 success: function(result) {
336 start_vnc_viewer(result.data);
337 },
338 failure: function(msg) {
339 console.log("ERROR: " + msg);
340 }
341 });
342 },
343
344 lastFBWidth: undefined,
345 lastFBHeight: undefined,
346 sizeUpdateTimer: undefined,
347
348 updateFBSize: function(rfb, width, height) {
349 try {
350 UI.lastFBWidth = width + 1;
351 UI.lastFBHeight = height + 5;
352
353 if (UI.sizeUpdateTimer !== undefined) {
354 clearInterval(UI.sizeUpdateTimer);
355 }
356 if (UI.getSetting('clip')) return;
357
358 var update_size = function() {
359 var oh;
360 var ow;
361
362 if (window.innerHeight) {
363 oh = window.innerHeight;
364 ow = window.innerWidth;
365 } else if (document.documentElement &&
366 document.documentElement.clientHeight) {
367 oh = document.documentElement.clientHeight;
368 ow = document.documentElement.clientWidth;
369 } else if (document.body) {
370 oh = document.body.clientHeight;
371 ow = document.body.clientWidth;
372 } else {
373 throw "can't get window size";
374 }
375
376 // see base.css/noVNC_screen_pad
377 var toolbar_height = 36;
378
379 var offsetw = UI.lastFBWidth - ow;
380 var offseth = UI.lastFBHeight + toolbar_height - oh;
381 if (offsetw !== 0 || offseth !== 0) {
382 //console.log("try resize by " + offsetw + " " + offseth);
383 window.resizeBy(offsetw, offseth);
384 }
385 };
386
387 update_size();
388 UI.sizeUpdateTimer = setInterval(update_size, 1000);
389
390 } catch(e) {
391 console.log(e);
392 }
393 },
394
395 // Open/close PVE connand menu
396 togglePVECommandPanel: function() {
397 // Close the description panel
398 $D('noVNC_description').style.display = "none";
399 // Close clipboard panel if open
400 if (UI.clipboardOpen === true) {
401 UI.toggleClipboardPanel();
402 }
403 // Close connection settings if open
404 if (UI.connSettingsOpen === true) {
405 UI.toggleConnectPanel();
406 }
407 // Close popup status panel if open
408 if (UI.popupStatusOpen === true) {
409 UI.togglePopupStatusPanel();
410 }
411 // Close XVP panel if open
412 if (UI.xvpOpen === true) {
413 UI.toggleXvpPanel();
414 }
415 if (UI.pveCommandsOpen) {
416 $D('noVNC_pve_commands').style.display = "none";
417 $D('pveCommandsButton').className = "noVNC_status_button";
418 UI.pveCommandsOpen = false;
419 } else {
420 $D('noVNC_pve_commands').style.display = "block";
421 $D('pveCommandsButton').className = "noVNC_status_button_selected";
422 UI.pveCommandsOpen = true;
423 }
424 },
425
426 // Render default UI and initialize settings menu
427 start: function(callback) {
428 var html = '', i, sheet, sheets, llevels, port, autoconnect;
429
430 UI.isTouchDevice = 'ontouchstart' in document.documentElement;
431
432 // Stylesheet selection dropdown
433 sheet = WebUtil.selectStylesheet();
434 sheets = WebUtil.getStylesheets();
435 for (i = 0; i < sheets.length; i += 1) {
436 //UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
437 }
438
439 // Logging selection dropdown
440 llevels = ['error', 'warn', 'info', 'debug'];
441 for (i = 0; i < llevels.length; i += 1) {
442 //UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
443 }
444
445 // Settings with immediate effects
446 UI.initSetting('logging', 'warn');
447 WebUtil.init_logging(UI.getSetting('logging'));
448
449 UI.initSetting('stylesheet', 'default');
450 WebUtil.selectStylesheet(null);
451 // call twice to get around webkit bug
452 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
453
454 UI.initSetting('repeaterID', '');
455
456 UI.rfb = RFB({'target': $D('noVNC_canvas'),
457 'onUpdateState': UI.updateState,
458 'onXvpInit': UI.updateXvpVisualState,
459 'onClipboard': UI.clipReceive,
460 //'onDesktopName': UI.updateDocumentTitle,
461 'onFBResize': UI.updateFBSize});
462
463 autoconnect = true;
464 if (autoconnect === 'true' || autoconnect == '1') {
465 autoconnect = true;
466 UI.connect();
467 } else {
468 autoconnect = false;
469 }
470
471 UI.updateVisualState();
472
473 // Unfocus clipboard when over the VNC area
474 //$D('VNC_screen').onmousemove = function () {
475 // var keyboard = UI.rfb.get_keyboard();
476 // if ((! keyboard) || (! keyboard.get_focused())) {
477 // $D('VNC_clipboard_text').blur();
478 // }
479 // };
480
481 // Show mouse selector buttons on touch screen devices
482 if (UI.isTouchDevice) {
483 // Show mobile buttons
484 $D('noVNC_mobile_buttons').style.display = "inline";
485 UI.setMouseButton();
486 // Remove the address bar
487 setTimeout(function() { window.scrollTo(0, 1); }, 100);
488 UI.forceSetting('clip', true);
489 $D('noVNC_clip').disabled = true;
490 } else {
491 UI.initSetting('clip', false);
492 }
493
494 //iOS Safari does not support CSS position:fixed.
495 //This detects iOS devices and enables javascript workaround.
496 if ((navigator.userAgent.match(/iPhone/i)) ||
497 (navigator.userAgent.match(/iPod/i)) ||
498 (navigator.userAgent.match(/iPad/i))) {
499 //UI.setOnscroll();
500 //UI.setResize();
501 }
502 UI.setBarPosition();
503
504 $D('noVNC_host').focus();
505
506 UI.setViewClip();
507 Util.addEvent(window, 'resize', UI.setViewClip);
508
509 Util.addEvent(window, 'beforeunload', function () {
510 if (UI.rfb_state === 'normal') {
511 return "You are currently connected.";
512 }
513 } );
514
515 // Show description by default when hosted at for kanaka.github.com
516 if (location.host === "kanaka.github.io") {
517 // Open the description dialog
518 $D('noVNC_description').style.display = "block";
519 } else {
520 // Show the connect panel on first load unless autoconnecting
521 if (autoconnect === UI.connSettingsOpen) {
522 UI.toggleConnectPanel();
523 }
524 }
525
526 // Add mouse event click/focus/blur event handlers to the UI
527 UI.addMouseHandlers();
528
529 if (typeof callback === "function") {
530 callback(UI.rfb);
531 }
532 },
533
534 addMouseHandlers: function() {
535 // Setup interface handlers that can't be inline
536 $D("noVNC_view_drag_button").onclick = UI.setViewDrag;
537 $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
538 $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
539 $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
540 $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
541 $D("showKeyboard").onclick = UI.showKeyboard;
542
543 $D("keyboardinput").oninput = UI.keyInput;
544 $D("keyboardinput").onblur = UI.keyInputBlur;
545
546 $D("showExtraKeysButton").onclick = UI.showExtraKeys;
547 $D("toggleCtrlButton").onclick = UI.toggleCtrl;
548 $D("toggleAltButton").onclick = UI.toggleAlt;
549 $D("sendTabButton").onclick = UI.sendTab;
550 $D("sendEscButton").onclick = UI.sendEsc;
551
552 $D("pveStartButton").onclick = UI.pveCmdStart;
553 $D("pveShutdownButton").onclick = UI.pveCmdShutdown;
554 $D("pveStopButton").onclick = UI.pveCmdStop;
555 $D("pveResetButton").onclick = UI.pveCmdReset;
556 $D("pveSuspendButton").onclick = UI.pveCmdSuspend;
557 $D("pveResumeButton").onclick = UI.pveCmdResume;
558 $D("pveReloadButton").onclick = UI.pveCmdReload;
559
560 $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
561 //$D("xvpShutdownButton").onclick = UI.xvpShutdown;
562 //$D("xvpRebootButton").onclick = UI.xvpReboot;
563 //$D("xvpResetButton").onclick = UI.xvpReset;
564 $D("noVNC_status").onclick = UI.togglePopupStatusPanel;
565 $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel;
566 //$D("xvpButton").onclick = UI.toggleXvpPanel;
567 $D("clipboardButton").onclick = UI.toggleClipboardPanel;
568 //$D("settingsButton").onclick = UI.toggleSettingsPanel;
569 $D("pveCommandsButton").onclick = UI.togglePVECommandPanel;
570 //$D("connectButton").onclick = UI.toggleConnectPanel;
571 //$D("disconnectButton").onclick = UI.disconnect;
572 //$D("descriptionButton").onclick = UI.toggleConnectPanel;
573
574 $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
575 $D("noVNC_clipboard_text").onblur = UI.displayFocus;
576 $D("noVNC_clipboard_text").onchange = UI.clipSend;
577 $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
578
579 //$D("noVNC_settings_menu").onmouseover = UI.displayBlur;
580 //$D("noVNC_settings_menu").onmouseover = UI.displayFocus;
581 //$D("noVNC_apply").onclick = UI.settingsApply;
582
583 //$D("noVNC_connect_button").onclick = UI.connect;
584 },
585
586 // Read form control compatible setting from cookie
587 getSetting: function(name) {
588 console.log("GET: " + name);
589 var val, ctrl = $D('noVNC_' + name);
590 val = WebUtil.readSetting(name);
591 if (val !== null && ctrl.type === 'checkbox') {
592 if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
593 val = false;
594 } else {
595 val = true;
596 }
597 }
598 return val;
599 },
600
601 // Update cookie and form control setting. If value is not set, then
602 // updates from control to current cookie setting.
603 updateSetting: function(name, value) {
604
605 var i, ctrl = $D('noVNC_' + name);
606 // Save the cookie for this session
607 if (typeof value !== 'undefined') {
608 WebUtil.writeSetting(name, value);
609 }
610
611 // Update the settings control
612 value = UI.getSetting(name);
613
614 if (ctrl.type === 'checkbox') {
615 ctrl.checked = value;
616
617 } else if (typeof ctrl.options !== 'undefined') {
618 for (i = 0; i < ctrl.options.length; i += 1) {
619 if (ctrl.options[i].value === value) {
620 ctrl.selectedIndex = i;
621 break;
622 }
623 }
624 } else {
625 /*Weird IE9 error leads to 'null' appearring
626 in textboxes instead of ''.*/
627 if (value === null) {
628 value = "";
629 }
630 ctrl.value = value;
631 }
632 },
633
634 // Save control setting to cookie
635 saveSetting: function(name) {
636 var val, ctrl = $D('noVNC_' + name);
637 if (ctrl.type === 'checkbox') {
638 val = ctrl.checked;
639 } else if (typeof ctrl.options !== 'undefined') {
640 val = ctrl.options[ctrl.selectedIndex].value;
641 } else {
642 val = ctrl.value;
643 }
644 WebUtil.writeSetting(name, val);
645 //Util.Debug("Setting saved '" + name + "=" + val + "'");
646 return val;
647 },
648
649 // Initial page load read/initialization of settings
650 initSetting: function(name, defVal) {
651 var val;
652
653 // Check Query string followed by cookie
654 val = WebUtil.getQueryVar(name);
655 if (val === null) {
656 val = WebUtil.readSetting(name, defVal);
657 }
658 UI.updateSetting(name, val);
659 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
660 return val;
661 },
662
663 // Force a setting to be a certain value
664 forceSetting: function(name, val) {
665 UI.updateSetting(name, val);
666 return val;
667 },
668
669
670 // Show the popup status panel
671 togglePopupStatusPanel: function() {
672 var psp = $D('noVNC_popup_status_panel');
673 if (UI.popupStatusOpen === true) {
674 psp.style.display = "none";
675 UI.popupStatusOpen = false;
676 } else {
677 psp.innerHTML = $D('noVNC_status').innerHTML;
678 psp.style.display = "block";
679 psp.style.left = window.innerWidth/2 -
680 parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px";
681 UI.popupStatusOpen = true;
682 }
683 },
684
685 // Show the XVP panel
686 toggleXvpPanel: function() {
687 // Close the description panel
688 $D('noVNC_description').style.display = "none";
689 if (UI.pveCommandsOpen === true) {
690 UI.togglePVECommandPanel();
691 }
692 // Close settings if open
693 if (UI.settingsOpen === true) {
694 UI.settingsApply();
695 UI.closeSettingsMenu();
696 }
697 // Close connection settings if open
698 if (UI.connSettingsOpen === true) {
699 UI.toggleConnectPanel();
700 }
701 // Close popup status panel if open
702 if (UI.popupStatusOpen === true) {
703 UI.togglePopupStatusPanel();
704 }
705 // Close clipboard panel if open
706 if (UI.clipboardOpen === true) {
707 UI.toggleClipboardPanel();
708 }
709 // Toggle XVP panel
710 if (UI.xvpOpen === true) {
711 //$D('noVNC_xvp').style.display = "none";
712 //$D('xvpButton').className = "noVNC_status_button";
713 UI.xvpOpen = false;
714 } else {
715 //$D('noVNC_xvp').style.display = "block";
716 //$D('xvpButton').className = "noVNC_status_button_selected";
717 UI.xvpOpen = true;
718 }
719 },
720
721 // Show the clipboard panel
722 toggleClipboardPanel: function() {
723 // Close the description panel
724 $D('noVNC_description').style.display = "none";
725 if (UI.pveCommandsOpen === true) {
726 UI.togglePVECommandPanel();
727 }
728 // Close settings if open
729 if (UI.settingsOpen === true) {
730 UI.settingsApply();
731 UI.closeSettingsMenu();
732 }
733 // Close connection settings if open
734 if (UI.connSettingsOpen === true) {
735 UI.toggleConnectPanel();
736 }
737 // Close popup status panel if open
738 if (UI.popupStatusOpen === true) {
739 UI.togglePopupStatusPanel();
740 }
741 // Close XVP panel if open
742 if (UI.xvpOpen === true) {
743 UI.toggleXvpPanel();
744 }
745 // Toggle Clipboard Panel
746 if (UI.clipboardOpen === true) {
747 $D('noVNC_clipboard').style.display = "none";
748 $D('clipboardButton').className = "noVNC_status_button";
749 UI.clipboardOpen = false;
750 } else {
751 $D('noVNC_clipboard').style.display = "block";
752 $D('clipboardButton').className = "noVNC_status_button_selected";
753 UI.clipboardOpen = true;
754 }
755 },
756
757 // Show the connection settings panel/menu
758 toggleConnectPanel: function() {
759 // Close the description panel
760 $D('noVNC_description').style.display = "none";
761 if (UI.pveCommandsOpen === true) {
762 UI.togglePVECommandPanel();
763 }
764 // Close connection settings if open
765 if (UI.settingsOpen === true) {
766 UI.settingsApply();
767 UI.closeSettingsMenu();
768 //$D('connectButton').className = "noVNC_status_button";
769 }
770 // Close clipboard panel if open
771 if (UI.clipboardOpen === true) {
772 UI.toggleClipboardPanel();
773 }
774 // Close popup status panel if open
775 if (UI.popupStatusOpen === true) {
776 UI.togglePopupStatusPanel();
777 }
778 // Close XVP panel if open
779 if (UI.xvpOpen === true) {
780 UI.toggleXvpPanel();
781 }
782
783 // Toggle Connection Panel
784 if (UI.connSettingsOpen === true) {
785 $D('noVNC_controls').style.display = "none";
786 //$D('connectButton').className = "noVNC_status_button";
787 UI.connSettingsOpen = false;
788 UI.saveSetting('host');
789 UI.saveSetting('port');
790 //UI.saveSetting('password');
791 } else {
792 $D('noVNC_controls').style.display = "block";
793 //$D('connectButton').className = "noVNC_status_button_selected";
794 UI.connSettingsOpen = true;
795 $D('noVNC_host').focus();
796 }
797 },
798
799 // Toggle the settings menu:
800 // On open, settings are refreshed from saved cookies.
801 // On close, settings are applied
802 toggleSettingsPanel: function() {
803 // Close the description panel
804 $D('noVNC_description').style.display = "none";
805 if (UI.settingsOpen) {
806 UI.settingsApply();
807 UI.closeSettingsMenu();
808 } else {
809 UI.updateSetting('encrypt');
810 UI.updateSetting('true_color');
811 if (UI.rfb.get_display().get_cursor_uri()) {
812 UI.updateSetting('cursor');
813 } else {
814 UI.updateSetting('cursor', !UI.isTouchDevice);
815 $D('noVNC_cursor').disabled = true;
816 }
817 UI.updateSetting('clip');
818 UI.updateSetting('shared');
819 UI.updateSetting('view_only');
820 UI.updateSetting('path');
821 UI.updateSetting('repeaterID');
822 UI.updateSetting('stylesheet');
823 UI.updateSetting('logging');
824
825 UI.openSettingsMenu();
826 }
827 },
828
829 // Open menu
830 openSettingsMenu: function() {
831 // Close the description panel
832 $D('noVNC_description').style.display = "none";
833 if (UI.pveCommandsOpen === true) {
834 UI.togglePVECommandPanel();
835 }
836 // Close clipboard panel if open
837 if (UI.clipboardOpen === true) {
838 UI.toggleClipboardPanel();
839 }
840 // Close connection settings if open
841 if (UI.connSettingsOpen === true) {
842 UI.toggleConnectPanel();
843 }
844 // Close popup status panel if open
845 if (UI.popupStatusOpen === true) {
846 UI.togglePopupStatusPanel();
847 }
848 // Close XVP panel if open
849 if (UI.xvpOpen === true) {
850 UI.toggleXvpPanel();
851 }
852 $D('noVNC_settings').style.display = "block";
853 //$D('settingsButton').className = "noVNC_status_button_selected";
854 UI.settingsOpen = true;
855 },
856
857 // Close menu (without applying settings)
858 closeSettingsMenu: function() {
859 $D('noVNC_settings').style.display = "none";
860 //$D('settingsButton').className = "noVNC_status_button";
861 UI.settingsOpen = false;
862 },
863
864 // Save/apply settings when 'Apply' button is pressed
865 settingsApply: function() {
866 //Util.Debug(">> settingsApply");
867 UI.saveSetting('encrypt');
868 UI.saveSetting('true_color');
869 if (UI.rfb.get_display().get_cursor_uri()) {
870 UI.saveSetting('cursor');
871 }
872 UI.saveSetting('clip');
873 UI.saveSetting('shared');
874 UI.saveSetting('view_only');
875 UI.saveSetting('path');
876 UI.saveSetting('repeaterID');
877 UI.saveSetting('stylesheet');
878 UI.saveSetting('logging');
879
880 // Settings with immediate (non-connected related) effect
881 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
882 WebUtil.init_logging(UI.getSetting('logging'));
883 UI.setViewClip();
884 UI.setViewDrag(UI.rfb.get_viewportDrag());
885 //Util.Debug("<< settingsApply");
886 },
887
888
889
890 setPassword: function() {
891 UI.rfb.sendPassword($D('noVNC_password').value);
892 //Reset connect button.
893 $D('noVNC_connect_button').value = "Connect";
894 $D('noVNC_connect_button').onclick = UI.Connect;
895 //Hide connection panel.
896 UI.toggleConnectPanel();
897 return false;
898 },
899
900 sendCtrlAltDel: function() {
901 UI.rfb.sendCtrlAltDel();
902 },
903
904 xvpShutdown: function() {
905 UI.rfb.xvpShutdown();
906 },
907
908 xvpReboot: function() {
909 UI.rfb.xvpReboot();
910 },
911
912 xvpReset: function() {
913 UI.rfb.xvpReset();
914 },
915
916 setMouseButton: function(num) {
917 var b, blist = [0, 1,2,4], button;
918
919 if (typeof num === 'undefined') {
920 // Disable mouse buttons
921 num = -1;
922 }
923 if (UI.rfb) {
924 UI.rfb.get_mouse().set_touchButton(num);
925 }
926
927 for (b = 0; b < blist.length; b++) {
928 button = $D('noVNC_mouse_button' + blist[b]);
929 if (blist[b] === num) {
930 button.style.display = "";
931 } else {
932 button.style.display = "none";
933 /*
934 button.style.backgroundColor = "black";
935 button.style.color = "lightgray";
936 button.style.backgroundColor = "";
937 button.style.color = "";
938 */
939 }
940 }
941 },
942
943 updateState: function(rfb, state, oldstate, msg) {
944 var s, sb, c, d, cad, vd, klass;
945 UI.rfb_state = state;
946 switch (state) {
947 case 'failed':
948 case 'fatal':
949 klass = "noVNC_status_error";
950 break;
951 case 'normal':
952 klass = "noVNC_status_normal";
953 break;
954 case 'disconnected':
955 $D('noVNC_logo').style.display = "block";
956 // Fall through
957 case 'loaded':
958 klass = "noVNC_status_normal";
959 break;
960 case 'password':
961 UI.toggleConnectPanel();
962
963 $D('noVNC_connect_button').value = "Send Password";
964 $D('noVNC_connect_button').onclick = UI.setPassword;
965 $D('noVNC_password').focus();
966
967 klass = "noVNC_status_warn";
968 break;
969 default:
970 klass = "noVNC_status_warn";
971 break;
972 }
973
974 if (typeof(msg) !== 'undefined') {
975 $D('noVNC-control-bar').setAttribute("class", klass);
976 $D('noVNC_status').innerHTML = msg;
977 }
978
979 UI.updateVisualState();
980 },
981
982 // Disable/enable controls depending on connection state
983 updateVisualState: function() {
984 var connected = UI.rfb_state === 'normal' ? true : false;
985
986 //Util.Debug(">> updateVisualState");
987 $D('noVNC_encrypt').disabled = connected;
988 $D('noVNC_true_color').disabled = connected;
989 if (UI.rfb && UI.rfb.get_display() &&
990 UI.rfb.get_display().get_cursor_uri()) {
991 $D('noVNC_cursor').disabled = connected;
992 } else {
993 UI.updateSetting('cursor', !UI.isTouchDevice);
994 $D('noVNC_cursor').disabled = true;
995 }
996 $D('noVNC_shared').disabled = connected;
997 $D('noVNC_view_only').disabled = connected;
998 $D('noVNC_path').disabled = connected;
999 $D('noVNC_repeaterID').disabled = connected;
1000
1001 if (connected) {
1002 UI.setViewClip();
1003 UI.setMouseButton(1);
1004 $D('clipboardButton').style.display = (UI.consoletype !== 'kvm') ? "inline" : "none";
1005 $D('showKeyboard').style.display = "inline";
1006 $D('noVNC_extra_keys').style.display = "";
1007 $D('sendCtrlAltDelButton').style.display = (UI.consoletype === 'kvm') ? "inline" : "none";
1008 } else {
1009 UI.setMouseButton();
1010 $D('clipboardButton').style.display = "none";
1011 $D('showKeyboard').style.display = "none";
1012 $D('noVNC_extra_keys').style.display = "none";
1013 $D('sendCtrlAltDelButton').style.display = "none";
1014 UI.updateXvpVisualState(0);
1015 }
1016
1017 // State change disables viewport dragging.
1018 // It is enabled (toggled) by direct click on the button
1019 UI.setViewDrag(false);
1020
1021 switch (UI.rfb_state) {
1022 case 'fatal':
1023 case 'failed':
1024 case 'loaded':
1025 case 'disconnected':
1026 //$D('connectButton').style.display = "";
1027 //$D('disconnectButton').style.display = "none";
1028 break;
1029 default:
1030 //$D('connectButton').style.display = "none";
1031 //$D('disconnectButton').style.display = "";
1032 break;
1033 }
1034
1035 //Util.Debug("<< updateVisualState");
1036 },
1037
1038 // Disable/enable XVP button
1039 updateXvpVisualState: function(ver) {
1040 return;
1041 if (ver >= 1) {
1042 //$D('xvpButton').style.display = 'inline';
1043 } else {
1044 //$D('xvpButton').style.display = 'none';
1045 // Close XVP panel if open
1046 if (UI.xvpOpen === true) {
1047 UI.toggleXvpPanel();
1048 }
1049 }
1050 },
1051
1052
1053 // Display the desktop name in the document title
1054 updateDocumentTitle: function(rfb, name) {
1055 document.title = name + " - noVNC";
1056 },
1057
1058
1059 clipReceive: function(rfb, text) {
1060 Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
1061 $D('noVNC_clipboard_text').value = text;
1062 Util.Debug("<< UI.clipReceive");
1063 },
1064
1065
1066 connect: function() {
1067 var host, port, password, path;
1068
1069 UI.closeSettingsMenu();
1070 UI.toggleConnectPanel();
1071
1072 host = $D('noVNC_host').value;
1073 port = $D('noVNC_port').value;
1074 password = $D('noVNC_password').value;
1075 path = $D('noVNC_path').value;
1076 if ((!host) || (!port)) {
1077 throw("Must set host and port");
1078 }
1079
1080 UI.rfb.set_encrypt(UI.getSetting('encrypt'));
1081 UI.rfb.set_true_color(UI.getSetting('true_color'));
1082 UI.rfb.set_local_cursor(UI.getSetting('cursor'));
1083 UI.rfb.set_shared(UI.getSetting('shared'));
1084 UI.rfb.set_view_only(UI.getSetting('view_only'));
1085 UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
1086
1087 UI.rfb.connect(host, port, password, path);
1088
1089 //Close dialog.
1090 setTimeout(UI.setBarPosition, 100);
1091 $D('noVNC_logo').style.display = "none";
1092 },
1093
1094 disconnect: function() {
1095 UI.closeSettingsMenu();
1096 UI.rfb.disconnect();
1097
1098 $D('noVNC_logo').style.display = "block";
1099 UI.connSettingsOpen = false;
1100 UI.toggleConnectPanel();
1101 },
1102
1103 displayBlur: function() {
1104 UI.rfb.get_keyboard().set_focused(false);
1105 UI.rfb.get_mouse().set_focused(false);
1106 },
1107
1108 displayFocus: function() {
1109 UI.rfb.get_keyboard().set_focused(true);
1110 UI.rfb.get_mouse().set_focused(true);
1111 },
1112
1113 clipClear: function() {
1114 $D('noVNC_clipboard_text').value = "";
1115 UI.rfb.clipboardPasteFrom("");
1116 },
1117
1118 clipSend: function() {
1119 var text = $D('noVNC_clipboard_text').value;
1120 Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
1121 UI.rfb.clipboardPasteFrom(text);
1122 Util.Debug("<< UI.clipSend");
1123 },
1124
1125
1126 // Enable/disable and configure viewport clipping
1127 setViewClip: function(clip) {
1128 var display, cur_clip, pos, new_w, new_h;
1129
1130 if (UI.rfb) {
1131 display = UI.rfb.get_display();
1132 } else {
1133 return;
1134 }
1135
1136 cur_clip = display.get_viewport();
1137
1138 if (typeof(clip) !== 'boolean') {
1139 // Use current setting
1140 clip = UI.getSetting('clip');
1141 }
1142
1143 if (clip && !cur_clip) {
1144 // Turn clipping on
1145 UI.updateSetting('clip', true);
1146 } else if (!clip && cur_clip) {
1147 // Turn clipping off
1148 UI.updateSetting('clip', false);
1149 display.set_viewport(false);
1150 $D('noVNC_canvas').style.position = 'static';
1151 display.viewportChange();
1152 }
1153 if (UI.getSetting('clip')) {
1154 // If clipping, update clipping settings
1155 $D('noVNC_canvas').style.position = 'absolute';
1156 pos = Util.getPosition($D('noVNC_canvas'));
1157 new_w = window.innerWidth - pos.x;
1158 new_h = window.innerHeight - pos.y;
1159 display.set_viewport(true);
1160 display.viewportChange(0, 0, new_w, new_h);
1161 }
1162 },
1163
1164 // Toggle/set/unset the viewport drag/move button
1165 setViewDrag: function(drag) {
1166 var vmb = $D('noVNC_view_drag_button');
1167 if (!UI.rfb) { return; }
1168
1169 if (UI.rfb_state === 'normal' &&
1170 UI.rfb.get_display().get_viewport()) {
1171 vmb.style.display = "inline";
1172 } else {
1173 vmb.style.display = "none";
1174 }
1175
1176 if (typeof(drag) === "undefined" ||
1177 typeof(drag) === "object") {
1178 // If not specified, then toggle
1179 drag = !UI.rfb.get_viewportDrag();
1180 }
1181 if (drag) {
1182 vmb.className = "noVNC_status_button_selected";
1183 UI.rfb.set_viewportDrag(true);
1184 } else {
1185 vmb.className = "noVNC_status_button";
1186 UI.rfb.set_viewportDrag(false);
1187 }
1188 },
1189
1190 // On touch devices, show the OS keyboard
1191 showKeyboard: function() {
1192 var kbi, skb, l;
1193 kbi = $D('keyboardinput');
1194 skb = $D('showKeyboard');
1195 l = kbi.value.length;
1196 if(UI.keyboardVisible === false) {
1197 kbi.focus();
1198 try { kbi.setSelectionRange(l, l); } // Move the caret to the end
1199 catch (err) {} // setSelectionRange is undefined in Google Chrome
1200 UI.keyboardVisible = true;
1201 skb.className = "noVNC_status_button_selected";
1202 } else if(UI.keyboardVisible === true) {
1203 kbi.blur();
1204 skb.className = "noVNC_status_button";
1205 UI.keyboardVisible = false;
1206 }
1207 },
1208
1209 keepKeyboard: function() {
1210 clearTimeout(UI.hideKeyboardTimeout);
1211 if(UI.keyboardVisible === true) {
1212 $D('keyboardinput').focus();
1213 $D('showKeyboard').className = "noVNC_status_button_selected";
1214 } else if(UI.keyboardVisible === false) {
1215 $D('keyboardinput').blur();
1216 $D('showKeyboard').className = "noVNC_status_button";
1217 }
1218 },
1219
1220 keyboardinputReset: function() {
1221 var kbi = $D('keyboardinput');
1222 kbi.value = Array(UI.defaultKeyboardinputLen).join("_");
1223 UI.lastKeyboardinput = kbi.value;
1224 },
1225
1226 // When normal keyboard events are left uncought, use the input events from
1227 // the keyboardinput element instead and generate the corresponding key events.
1228 // This code is required since some browsers on Android are inconsistent in
1229 // sending keyCodes in the normal keyboard events when using on screen keyboards.
1230 keyInput: function(event) {
1231 var newValue, oldValue, newLen, oldLen;
1232 newValue = event.target.value;
1233 oldValue = UI.lastKeyboardinput;
1234
1235 try {
1236 // Try to check caret position since whitespace at the end
1237 // will not be considered by value.length in some browsers
1238 newLen = Math.max(event.target.selectionStart, newValue.length);
1239 } catch (err) {
1240 // selectionStart is undefined in Google Chrome
1241 newLen = newValue.length;
1242 }
1243 oldLen = oldValue.length;
1244
1245 var backspaces;
1246 var inputs = newLen - oldLen;
1247 if (inputs < 0)
1248 backspaces = -inputs;
1249 else
1250 backspaces = 0;
1251
1252 // Compare the old string with the new to account for
1253 // text-corrections or other input that modify existing text
1254 for (var i = 0; i < Math.min(oldLen, newLen); i++) {
1255 if (newValue.charAt(i) != oldValue.charAt(i)) {
1256 inputs = newLen - i;
1257 backspaces = oldLen - i;
1258 break;
1259 }
1260 }
1261
1262 // Send the key events
1263 for (var i = 0; i < backspaces; i++)
1264 UI.rfb.sendKey(XK_BackSpace);
1265 for (var i = newLen - inputs; i < newLen; i++)
1266 UI.rfb.sendKey(newValue.charCodeAt(i));
1267
1268 // Control the text content length in the keyboardinput element
1269 if (newLen > 2 * UI.defaultKeyboardinputLen) {
1270 UI.keyboardinputReset();
1271 } else if (newLen < 1) {
1272 // There always have to be some text in the keyboardinput
1273 // element with which backspace can interact.
1274 UI.keyboardinputReset();
1275 // This sometimes causes the keyboard to disappear for a second
1276 // but it is required for the android keyboard to recognize that
1277 // text has been added to the field
1278 event.target.blur();
1279 // This has to be ran outside of the input handler in order to work
1280 setTimeout(function() { UI.keepKeyboard(); }, 0);
1281
1282 } else {
1283 UI.lastKeyboardinput = newValue;
1284 }
1285 },
1286
1287 keyInputBlur: function() {
1288 $D('showKeyboard').className = "noVNC_status_button";
1289 //Weird bug in iOS if you change keyboardVisible
1290 //here it does not actually occur so next time
1291 //you click keyboard icon it doesnt work.
1292 UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100);
1293 },
1294
1295 showExtraKeys: function() {
1296 UI.keepKeyboard();
1297 if(UI.extraKeysVisible === false) {
1298 $D('toggleCtrlButton').style.display = "inline";
1299 $D('toggleAltButton').style.display = "inline";
1300 $D('sendTabButton').style.display = "inline";
1301 $D('sendEscButton').style.display = "inline";
1302 $D('showExtraKeysButton').className = "noVNC_status_button_selected";
1303 UI.extraKeysVisible = true;
1304 } else if(UI.extraKeysVisible === true) {
1305 $D('toggleCtrlButton').style.display = "";
1306 $D('toggleAltButton').style.display = "";
1307 $D('sendTabButton').style.display = "";
1308 $D('sendEscButton').style.display = "";
1309 $D('showExtraKeysButton').className = "noVNC_status_button";
1310 UI.extraKeysVisible = false;
1311 }
1312 },
1313
1314 toggleCtrl: function() {
1315 UI.keepKeyboard();
1316 if(UI.ctrlOn === false) {
1317 UI.rfb.sendKey(XK_Control_L, true);
1318 $D('toggleCtrlButton').className = "noVNC_status_button_selected";
1319 UI.ctrlOn = true;
1320 } else if(UI.ctrlOn === true) {
1321 UI.rfb.sendKey(XK_Control_L, false);
1322 $D('toggleCtrlButton').className = "noVNC_status_button";
1323 UI.ctrlOn = false;
1324 }
1325 },
1326
1327 toggleAlt: function() {
1328 UI.keepKeyboard();
1329 if(UI.altOn === false) {
1330 UI.rfb.sendKey(XK_Alt_L, true);
1331 $D('toggleAltButton').className = "noVNC_status_button_selected";
1332 UI.altOn = true;
1333 } else if(UI.altOn === true) {
1334 UI.rfb.sendKey(XK_Alt_L, false);
1335 $D('toggleAltButton').className = "noVNC_status_button";
1336 UI.altOn = false;
1337 }
1338 },
1339
1340 sendTab: function() {
1341 UI.keepKeyboard();
1342 UI.rfb.sendKey(XK_Tab);
1343 },
1344
1345 sendEsc: function() {
1346 UI.keepKeyboard();
1347 UI.rfb.sendKey(XK_Escape);
1348 },
1349
1350 setKeyboard: function() {
1351 UI.keyboardVisible = false;
1352 },
1353
1354 // iOS < Version 5 does not support position fixed. Javascript workaround:
1355 setOnscroll: function() {
1356 window.onscroll = function() {
1357 UI.setBarPosition();
1358 };
1359 },
1360
1361 setResize: function () {
1362 window.onResize = function() {
1363 UI.setBarPosition();
1364 };
1365 },
1366
1367 //Helper to add options to dropdown.
1368 addOption: function(selectbox,text,value )
1369 {
1370 var optn = document.createElement("OPTION");
1371 optn.text = text;
1372 optn.value = value;
1373 selectbox.options.add(optn);
1374 },
1375
1376 setBarPosition: function() {
1377 $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
1378 $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
1379
1380 var vncwidth = $D('noVNC_screen').style.offsetWidth;
1381 $D('noVNC-control-bar').style.width = vncwidth + 'px';
1382 }
1383
1384 };
1385
1386
1387
1388