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