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