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