]> git.proxmox.com Git - mirror_novnc.git/blob - include/ui.js
ui.js: fix drag button so it can be toggled off.
[mirror_novnc.git] / include / ui.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Licensed under MPL 2.0 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 "use strict";
10 /*jslint white: false, browser: true */
11 /*global window, $D, Util, WebUtil, RFB, Display */
12
13 // Load supporting scripts
14 window.onscriptsload = function () { UI.load(); };
15 Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
16 "input.js", "display.js", "jsunzip.js", "rfb.js"]);
17
18 var UI = {
19
20 rfb_state : 'loaded',
21 settingsOpen : false,
22 connSettingsOpen : false,
23 clipboardOpen: false,
24 keyboardVisible: false,
25
26 // Setup rfb object, load settings from browser storage, then call
27 // UI.init to setup the UI/menus
28 load: function (callback) {
29 WebUtil.initSettings(UI.start, callback);
30 },
31
32 // Render default UI and initialize settings menu
33 start: function(callback) {
34 var html = '', i, sheet, sheets, llevels;
35
36 // Stylesheet selection dropdown
37 sheet = WebUtil.selectStylesheet();
38 sheets = WebUtil.getStylesheets();
39 for (i = 0; i < sheets.length; i += 1) {
40 UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
41 }
42
43 // Logging selection dropdown
44 llevels = ['error', 'warn', 'info', 'debug'];
45 for (i = 0; i < llevels.length; i += 1) {
46 UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
47 }
48
49 // Settings with immediate effects
50 UI.initSetting('logging', 'warn');
51 WebUtil.init_logging(UI.getSetting('logging'));
52
53 UI.initSetting('stylesheet', 'default');
54 WebUtil.selectStylesheet(null);
55 // call twice to get around webkit bug
56 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
57
58 /* Populate the controls if defaults are provided in the URL */
59 UI.initSetting('host', window.location.hostname);
60 UI.initSetting('port', window.location.port);
61 UI.initSetting('password', '');
62 UI.initSetting('encrypt', (window.location.protocol === "https:"));
63 UI.initSetting('true_color', true);
64 UI.initSetting('cursor', false);
65 UI.initSetting('shared', true);
66 UI.initSetting('view_only', false);
67 UI.initSetting('connectTimeout', 2);
68 UI.initSetting('path', 'websockify');
69 UI.initSetting('repeaterID', '');
70
71 UI.rfb = RFB({'target': $D('noVNC_canvas'),
72 'onUpdateState': UI.updateState,
73 'onClipboard': UI.clipReceive});
74 UI.updateVisualState();
75
76 // Unfocus clipboard when over the VNC area
77 //$D('VNC_screen').onmousemove = function () {
78 // var keyboard = UI.rfb.get_keyboard();
79 // if ((! keyboard) || (! keyboard.get_focused())) {
80 // $D('VNC_clipboard_text').blur();
81 // }
82 // };
83
84 // Show mouse selector buttons on touch screen devices
85 if ('ontouchstart' in document.documentElement) {
86 // Show mobile buttons
87 $D('noVNC_mobile_buttons').style.display = "inline";
88 UI.setMouseButton();
89 // Remove the address bar
90 setTimeout(function() { window.scrollTo(0, 1); }, 100);
91 UI.forceSetting('clip', true);
92 $D('noVNC_clip').disabled = true;
93 } else {
94 UI.initSetting('clip', false);
95 }
96
97 //iOS Safari does not support CSS position:fixed.
98 //This detects iOS devices and enables javascript workaround.
99 if ((navigator.userAgent.match(/iPhone/i)) ||
100 (navigator.userAgent.match(/iPod/i)) ||
101 (navigator.userAgent.match(/iPad/i))) {
102 //UI.setOnscroll();
103 //UI.setResize();
104 }
105 UI.setBarPosition();
106
107 $D('noVNC_host').focus();
108
109 UI.setViewClip();
110 Util.addEvent(window, 'resize', UI.setViewClip);
111
112 Util.addEvent(window, 'beforeunload', function () {
113 if (UI.rfb_state === 'normal') {
114 return "You are currently connected.";
115 }
116 } );
117
118 // Show description by default when hosted at for kanaka.github.com
119 if (location.host === "kanaka.github.com") {
120 // Open the description dialog
121 $D('noVNC_description').style.display = "block";
122 } else {
123 // Open the connect panel on first load
124 UI.toggleConnectPanel();
125 }
126
127 // Add mouse event click/focus/blur event handlers to the UI
128 UI.addMouseHandlers();
129
130 if (typeof callback === "function") {
131 callback(UI.rfb);
132 }
133 },
134
135 addMouseHandlers: function() {
136 // Setup interface handlers that can't be inline
137 $D("noVNC_view_drag_button").onclick = UI.setViewDrag;
138 $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
139 $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
140 $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
141 $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
142 $D("showKeyboard").onclick = UI.showKeyboard;
143 //$D("keyboardinput").onkeydown = function (event) { onKeyDown(event); };
144 $D("keyboardinput").onblur = UI.keyInputBlur;
145
146 $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
147 $D("clipboardButton").onclick = UI.toggleClipboardPanel;
148 $D("settingsButton").onclick = UI.toggleSettingsPanel;
149 $D("connectButton").onclick = UI.toggleConnectPanel;
150 $D("disconnectButton").onclick = UI.disconnect;
151 $D("descriptionButton").onclick = UI.toggleConnectPanel;
152
153 $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
154 $D("noVNC_clipboard_text").onblur = UI.displayFocus;
155 $D("noVNC_clipboard_text").onchange = UI.clipSend;
156 $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
157
158 $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
159 $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
160 $D("noVNC_apply").onclick = UI.settingsApply;
161
162 $D("noVNC_connect_button").onclick = UI.connect;
163 },
164
165 // Read form control compatible setting from cookie
166 getSetting: function(name) {
167 var val, ctrl = $D('noVNC_' + name);
168 val = WebUtil.readSetting(name);
169 if (val !== null && ctrl.type === 'checkbox') {
170 if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
171 val = false;
172 } else {
173 val = true;
174 }
175 }
176 return val;
177 },
178
179 // Update cookie and form control setting. If value is not set, then
180 // updates from control to current cookie setting.
181 updateSetting: function(name, value) {
182
183 var i, ctrl = $D('noVNC_' + name);
184 // Save the cookie for this session
185 if (typeof value !== 'undefined') {
186 WebUtil.writeSetting(name, value);
187 }
188
189 // Update the settings control
190 value = UI.getSetting(name);
191
192 if (ctrl.type === 'checkbox') {
193 ctrl.checked = value;
194
195 } else if (typeof ctrl.options !== 'undefined') {
196 for (i = 0; i < ctrl.options.length; i += 1) {
197 if (ctrl.options[i].value === value) {
198 ctrl.selectedIndex = i;
199 break;
200 }
201 }
202 } else {
203 /*Weird IE9 error leads to 'null' appearring
204 in textboxes instead of ''.*/
205 if (value === null) {
206 value = "";
207 }
208 ctrl.value = value;
209 }
210 },
211
212 // Save control setting to cookie
213 saveSetting: function(name) {
214 var val, ctrl = $D('noVNC_' + name);
215 if (ctrl.type === 'checkbox') {
216 val = ctrl.checked;
217 } else if (typeof ctrl.options !== 'undefined') {
218 val = ctrl.options[ctrl.selectedIndex].value;
219 } else {
220 val = ctrl.value;
221 }
222 WebUtil.writeSetting(name, val);
223 //Util.Debug("Setting saved '" + name + "=" + val + "'");
224 return val;
225 },
226
227 // Initial page load read/initialization of settings
228 initSetting: function(name, defVal) {
229 var val;
230
231 // Check Query string followed by cookie
232 val = WebUtil.getQueryVar(name);
233 if (val === null) {
234 val = WebUtil.readSetting(name, defVal);
235 }
236 UI.updateSetting(name, val);
237 //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
238 return val;
239 },
240
241 // Force a setting to be a certain value
242 forceSetting: function(name, val) {
243 UI.updateSetting(name, val);
244 return val;
245 },
246
247
248 // Show the clipboard panel
249 toggleClipboardPanel: function() {
250 // Close the description panel
251 $D('noVNC_description').style.display = "none";
252 //Close settings if open
253 if (UI.settingsOpen === true) {
254 UI.settingsApply();
255 UI.closeSettingsMenu();
256 }
257 //Close connection settings if open
258 if (UI.connSettingsOpen === true) {
259 UI.toggleConnectPanel();
260 }
261 //Toggle Clipboard Panel
262 if (UI.clipboardOpen === true) {
263 $D('noVNC_clipboard').style.display = "none";
264 $D('clipboardButton').className = "noVNC_status_button";
265 UI.clipboardOpen = false;
266 } else {
267 $D('noVNC_clipboard').style.display = "block";
268 $D('clipboardButton').className = "noVNC_status_button_selected";
269 UI.clipboardOpen = true;
270 }
271 },
272
273 // Show the connection settings panel/menu
274 toggleConnectPanel: function() {
275 // Close the description panel
276 $D('noVNC_description').style.display = "none";
277 //Close connection settings if open
278 if (UI.settingsOpen === true) {
279 UI.settingsApply();
280 UI.closeSettingsMenu();
281 $D('connectButton').className = "noVNC_status_button";
282 }
283 if (UI.clipboardOpen === true) {
284 UI.toggleClipboardPanel();
285 }
286
287 //Toggle Connection Panel
288 if (UI.connSettingsOpen === true) {
289 $D('noVNC_controls').style.display = "none";
290 $D('connectButton').className = "noVNC_status_button";
291 UI.connSettingsOpen = false;
292 UI.saveSetting('host');
293 UI.saveSetting('port');
294 //UI.saveSetting('password');
295 } else {
296 $D('noVNC_controls').style.display = "block";
297 $D('connectButton').className = "noVNC_status_button_selected";
298 UI.connSettingsOpen = true;
299 $D('noVNC_host').focus();
300 }
301 },
302
303 // Toggle the settings menu:
304 // On open, settings are refreshed from saved cookies.
305 // On close, settings are applied
306 toggleSettingsPanel: function() {
307 // Close the description panel
308 $D('noVNC_description').style.display = "none";
309 if (UI.settingsOpen) {
310 UI.settingsApply();
311 UI.closeSettingsMenu();
312 } else {
313 UI.updateSetting('encrypt');
314 UI.updateSetting('true_color');
315 if (UI.rfb.get_display().get_cursor_uri()) {
316 UI.updateSetting('cursor');
317 } else {
318 UI.updateSetting('cursor', false);
319 $D('noVNC_cursor').disabled = true;
320 }
321 UI.updateSetting('clip');
322 UI.updateSetting('shared');
323 UI.updateSetting('view_only');
324 UI.updateSetting('connectTimeout');
325 UI.updateSetting('path');
326 UI.updateSetting('repeaterID');
327 UI.updateSetting('stylesheet');
328 UI.updateSetting('logging');
329
330 UI.openSettingsMenu();
331 }
332 },
333
334 // Open menu
335 openSettingsMenu: function() {
336 // Close the description panel
337 $D('noVNC_description').style.display = "none";
338 if (UI.clipboardOpen === true) {
339 UI.toggleClipboardPanel();
340 }
341 //Close connection settings if open
342 if (UI.connSettingsOpen === true) {
343 UI.toggleConnectPanel();
344 }
345 $D('noVNC_settings').style.display = "block";
346 $D('settingsButton').className = "noVNC_status_button_selected";
347 UI.settingsOpen = true;
348 },
349
350 // Close menu (without applying settings)
351 closeSettingsMenu: function() {
352 $D('noVNC_settings').style.display = "none";
353 $D('settingsButton').className = "noVNC_status_button";
354 UI.settingsOpen = false;
355 },
356
357 // Save/apply settings when 'Apply' button is pressed
358 settingsApply: function() {
359 //Util.Debug(">> settingsApply");
360 UI.saveSetting('encrypt');
361 UI.saveSetting('true_color');
362 if (UI.rfb.get_display().get_cursor_uri()) {
363 UI.saveSetting('cursor');
364 }
365 UI.saveSetting('clip');
366 UI.saveSetting('shared');
367 UI.saveSetting('view_only');
368 UI.saveSetting('connectTimeout');
369 UI.saveSetting('path');
370 UI.saveSetting('repeaterID');
371 UI.saveSetting('stylesheet');
372 UI.saveSetting('logging');
373
374 // Settings with immediate (non-connected related) effect
375 WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
376 WebUtil.init_logging(UI.getSetting('logging'));
377 UI.setViewClip();
378 UI.setViewDrag(UI.rfb.get_viewportDrag());
379 //Util.Debug("<< settingsApply");
380 },
381
382
383
384 setPassword: function() {
385 UI.rfb.sendPassword($D('noVNC_password').value);
386 //Reset connect button.
387 $D('noVNC_connect_button').value = "Connect";
388 $D('noVNC_connect_button').onclick = UI.Connect;
389 //Hide connection panel.
390 UI.toggleConnectPanel();
391 return false;
392 },
393
394 sendCtrlAltDel: function() {
395 UI.rfb.sendCtrlAltDel();
396 },
397
398 setMouseButton: function(num) {
399 var b, blist = [0, 1,2,4], button;
400
401 if (typeof num === 'undefined') {
402 // Disable mouse buttons
403 num = -1;
404 }
405 if (UI.rfb) {
406 UI.rfb.get_mouse().set_touchButton(num);
407 }
408
409 for (b = 0; b < blist.length; b++) {
410 button = $D('noVNC_mouse_button' + blist[b]);
411 if (blist[b] === num) {
412 button.style.display = "";
413 } else {
414 button.style.display = "none";
415 /*
416 button.style.backgroundColor = "black";
417 button.style.color = "lightgray";
418 button.style.backgroundColor = "";
419 button.style.color = "";
420 */
421 }
422 }
423 },
424
425 updateState: function(rfb, state, oldstate, msg) {
426 var s, sb, c, d, cad, vd, klass;
427 UI.rfb_state = state;
428 s = $D('noVNC_status');
429 sb = $D('noVNC_status_bar');
430 switch (state) {
431 case 'failed':
432 case 'fatal':
433 klass = "noVNC_status_error";
434 break;
435 case 'normal':
436 klass = "noVNC_status_normal";
437 break;
438 case 'disconnected':
439 $D('noVNC_logo').style.display = "block";
440 // Fall through
441 case 'loaded':
442 klass = "noVNC_status_normal";
443 break;
444 case 'password':
445 UI.toggleConnectPanel();
446
447 $D('noVNC_connect_button').value = "Send Password";
448 $D('noVNC_connect_button').onclick = UI.setPassword;
449 $D('noVNC_password').focus();
450
451 klass = "noVNC_status_warn";
452 break;
453 default:
454 klass = "noVNC_status_warn";
455 break;
456 }
457
458 if (typeof(msg) !== 'undefined') {
459 s.setAttribute("class", klass);
460 sb.setAttribute("class", klass);
461 s.innerHTML = msg;
462 }
463
464 UI.updateVisualState();
465 },
466
467 // Disable/enable controls depending on connection state
468 updateVisualState: function() {
469 var connected = UI.rfb_state === 'normal' ? true : false;
470
471 //Util.Debug(">> updateVisualState");
472 $D('noVNC_encrypt').disabled = connected;
473 $D('noVNC_true_color').disabled = connected;
474 if (UI.rfb && UI.rfb.get_display() &&
475 UI.rfb.get_display().get_cursor_uri()) {
476 $D('noVNC_cursor').disabled = connected;
477 } else {
478 UI.updateSetting('cursor', false);
479 $D('noVNC_cursor').disabled = true;
480 }
481 $D('noVNC_shared').disabled = connected;
482 $D('noVNC_view_only').disabled = connected;
483 $D('noVNC_connectTimeout').disabled = connected;
484 $D('noVNC_path').disabled = connected;
485 $D('noVNC_repeaterID').disabled = connected;
486
487 if (connected) {
488 UI.setViewClip();
489 UI.setMouseButton(1);
490 $D('clipboardButton').style.display = "inline";
491 $D('showKeyboard').style.display = "inline";
492 $D('sendCtrlAltDelButton').style.display = "inline";
493 } else {
494 UI.setMouseButton();
495 $D('clipboardButton').style.display = "none";
496 $D('showKeyboard').style.display = "none";
497 $D('sendCtrlAltDelButton').style.display = "none";
498 }
499 // State change disables viewport dragging.
500 // It is enabled (toggled) by direct click on the button
501 UI.setViewDrag(false);
502
503 switch (UI.rfb_state) {
504 case 'fatal':
505 case 'failed':
506 case 'loaded':
507 case 'disconnected':
508 $D('connectButton').style.display = "";
509 $D('disconnectButton').style.display = "none";
510 break;
511 default:
512 $D('connectButton').style.display = "none";
513 $D('disconnectButton').style.display = "";
514 break;
515 }
516
517 //Util.Debug("<< updateVisualState");
518 },
519
520
521 clipReceive: function(rfb, text) {
522 Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
523 $D('noVNC_clipboard_text').value = text;
524 Util.Debug("<< UI.clipReceive");
525 },
526
527
528 connect: function() {
529 var host, port, password, path;
530
531 UI.closeSettingsMenu();
532 UI.toggleConnectPanel();
533
534 host = $D('noVNC_host').value;
535 port = $D('noVNC_port').value;
536 password = $D('noVNC_password').value;
537 path = $D('noVNC_path').value;
538 if ((!host) || (!port)) {
539 throw("Must set host and port");
540 }
541
542 UI.rfb.set_encrypt(UI.getSetting('encrypt'));
543 UI.rfb.set_true_color(UI.getSetting('true_color'));
544 UI.rfb.set_local_cursor(UI.getSetting('cursor'));
545 UI.rfb.set_shared(UI.getSetting('shared'));
546 UI.rfb.set_view_only(UI.getSetting('view_only'));
547 UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
548 UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
549
550 UI.rfb.connect(host, port, password, path);
551
552 //Close dialog.
553 setTimeout(UI.setBarPosition, 100);
554 $D('noVNC_logo').style.display = "none";
555 },
556
557 disconnect: function() {
558 UI.closeSettingsMenu();
559 UI.rfb.disconnect();
560
561 $D('noVNC_logo').style.display = "block";
562 UI.connSettingsOpen = false;
563 UI.toggleConnectPanel();
564 },
565
566 displayBlur: function() {
567 UI.rfb.get_keyboard().set_focused(false);
568 UI.rfb.get_mouse().set_focused(false);
569 },
570
571 displayFocus: function() {
572 UI.rfb.get_keyboard().set_focused(true);
573 UI.rfb.get_mouse().set_focused(true);
574 },
575
576 clipClear: function() {
577 $D('noVNC_clipboard_text').value = "";
578 UI.rfb.clipboardPasteFrom("");
579 },
580
581 clipSend: function() {
582 var text = $D('noVNC_clipboard_text').value;
583 Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
584 UI.rfb.clipboardPasteFrom(text);
585 Util.Debug("<< UI.clipSend");
586 },
587
588
589 // Enable/disable and configure viewport clipping
590 setViewClip: function(clip) {
591 var display, cur_clip, pos, new_w, new_h;
592
593 if (UI.rfb) {
594 display = UI.rfb.get_display();
595 } else {
596 return;
597 }
598
599 cur_clip = display.get_viewport();
600
601 if (typeof(clip) !== 'boolean') {
602 // Use current setting
603 clip = UI.getSetting('clip');
604 }
605
606 if (clip && !cur_clip) {
607 // Turn clipping on
608 UI.updateSetting('clip', true);
609 } else if (!clip && cur_clip) {
610 // Turn clipping off
611 UI.updateSetting('clip', false);
612 display.set_viewport(false);
613 $D('noVNC_canvas').style.position = 'static';
614 display.viewportChange();
615 }
616 if (UI.getSetting('clip')) {
617 // If clipping, update clipping settings
618 $D('noVNC_canvas').style.position = 'absolute';
619 pos = Util.getPosition($D('noVNC_canvas'));
620 new_w = window.innerWidth - pos.x;
621 new_h = window.innerHeight - pos.y;
622 display.set_viewport(true);
623 display.viewportChange(0, 0, new_w, new_h);
624 }
625 },
626
627 // Toggle/set/unset the viewport drag/move button
628 setViewDrag: function(drag) {
629 var vmb = $D('noVNC_view_drag_button');
630 if (!UI.rfb) { return; }
631
632 if (UI.rfb_state === 'normal' &&
633 UI.rfb.get_display().get_viewport()) {
634 vmb.style.display = "inline";
635 } else {
636 vmb.style.display = "none";
637 }
638
639 if (typeof(drag) === "undefined" ||
640 typeof(drag) === "object") {
641 // If not specified, then toggle
642 drag = !UI.rfb.get_viewportDrag();
643 }
644 if (drag) {
645 vmb.className = "noVNC_status_button_selected";
646 UI.rfb.set_viewportDrag(true);
647 } else {
648 vmb.className = "noVNC_status_button";
649 UI.rfb.set_viewportDrag(false);
650 }
651 },
652
653 // On touch devices, show the OS keyboard
654 showKeyboard: function() {
655 if(UI.keyboardVisible === false) {
656 $D('keyboardinput').focus();
657 UI.keyboardVisible = true;
658 $D('showKeyboard').className = "noVNC_status_button_selected";
659 } else if(UI.keyboardVisible === true) {
660 $D('keyboardinput').blur();
661 $D('showKeyboard').className = "noVNC_status_button";
662 UI.keyboardVisible = false;
663 }
664 },
665
666 keyInputBlur: function() {
667 $D('showKeyboard').className = "noVNC_status_button";
668 //Weird bug in iOS if you change keyboardVisible
669 //here it does not actually occur so next time
670 //you click keyboard icon it doesnt work.
671 setTimeout(function() { UI.setKeyboard(); },100);
672 },
673
674 setKeyboard: function() {
675 UI.keyboardVisible = false;
676 },
677
678 // iOS < Version 5 does not support position fixed. Javascript workaround:
679 setOnscroll: function() {
680 window.onscroll = function() {
681 UI.setBarPosition();
682 };
683 },
684
685 setResize: function () {
686 window.onResize = function() {
687 UI.setBarPosition();
688 };
689 },
690
691 //Helper to add options to dropdown.
692 addOption: function(selectbox,text,value )
693 {
694 var optn = document.createElement("OPTION");
695 optn.text = text;
696 optn.value = value;
697 selectbox.options.add(optn);
698 },
699
700 setBarPosition: function() {
701 $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
702 $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
703
704 var vncwidth = $D('noVNC_screen').style.offsetWidth;
705 $D('noVNC-control-bar').style.width = vncwidth + 'px';
706 }
707
708 };
709
710
711
712