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