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