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