ui.js 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2012 Joel Martin
  4. * Copyright (C) 2013 Samuel Mannehed for Cendio AB
  5. * Licensed under MPL 2.0 (see LICENSE.txt)
  6. *
  7. * See README.md for usage and integration instructions.
  8. */
  9. "use strict";
  10. /*jslint white: false, browser: true */
  11. /*global window, $D, Util, WebUtil, RFB, Display */
  12. // Load supporting scripts
  13. window.onscriptsload = function () { UI.load(); };
  14. window.onload = function () { UI.keyboardinputReset(); };
  15. Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
  16. "keysymdef.js", "keyboard.js", "input.js", "display.js",
  17. "jsunzip.js", "rfb.js", "keysym.js"]);
  18. var UI = {
  19. rfb_state : 'loaded',
  20. settingsOpen : false,
  21. connSettingsOpen : false,
  22. popupStatusOpen : false,
  23. clipboardOpen: false,
  24. keyboardVisible: false,
  25. hideKeyboardTimeout: null,
  26. lastKeyboardinput: null,
  27. defaultKeyboardinputLen: 100,
  28. extraKeysVisible: false,
  29. ctrlOn: false,
  30. altOn: false,
  31. isTouchDevice: false,
  32. // Setup rfb object, load settings from browser storage, then call
  33. // UI.init to setup the UI/menus
  34. load: function (callback) {
  35. WebUtil.initSettings(UI.start, callback);
  36. },
  37. // Render default UI and initialize settings menu
  38. start: function(callback) {
  39. var html = '', i, sheet, sheets, llevels, port, autoconnect;
  40. UI.isTouchDevice = 'ontouchstart' in document.documentElement;
  41. // Stylesheet selection dropdown
  42. sheet = WebUtil.selectStylesheet();
  43. sheets = WebUtil.getStylesheets();
  44. for (i = 0; i < sheets.length; i += 1) {
  45. UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
  46. }
  47. // Logging selection dropdown
  48. llevels = ['error', 'warn', 'info', 'debug'];
  49. for (i = 0; i < llevels.length; i += 1) {
  50. UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
  51. }
  52. // Settings with immediate effects
  53. UI.initSetting('logging', 'warn');
  54. WebUtil.init_logging(UI.getSetting('logging'));
  55. UI.initSetting('stylesheet', 'default');
  56. WebUtil.selectStylesheet(null);
  57. // call twice to get around webkit bug
  58. WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
  59. // if port == 80 (or 443) then it won't be present and should be
  60. // set manually
  61. port = window.location.port;
  62. if (!port) {
  63. if (window.location.protocol.substring(0,5) == 'https') {
  64. port = 443;
  65. }
  66. else if (window.location.protocol.substring(0,4) == 'http') {
  67. port = 80;
  68. }
  69. }
  70. /* Populate the controls if defaults are provided in the URL */
  71. UI.initSetting('host', window.location.hostname);
  72. UI.initSetting('port', port);
  73. UI.initSetting('password', '');
  74. UI.initSetting('encrypt', (window.location.protocol === "https:"));
  75. UI.initSetting('true_color', true);
  76. UI.initSetting('cursor', !UI.isTouchDevice);
  77. UI.initSetting('shared', true);
  78. UI.initSetting('view_only', false);
  79. UI.initSetting('path', 'websockify');
  80. UI.initSetting('repeaterID', '');
  81. UI.rfb = RFB({'target': $D('noVNC_canvas'),
  82. 'onUpdateState': UI.updateState,
  83. 'onXvpInit': UI.updateXvpVisualState,
  84. 'onClipboard': UI.clipReceive,
  85. 'onDesktopName': UI.updateDocumentTitle});
  86. autoconnect = WebUtil.getQueryVar('autoconnect', false);
  87. if (autoconnect === 'true' || autoconnect == '1') {
  88. autoconnect = true;
  89. UI.connect();
  90. } else {
  91. autoconnect = false;
  92. }
  93. UI.updateVisualState();
  94. // Unfocus clipboard when over the VNC area
  95. //$D('VNC_screen').onmousemove = function () {
  96. // var keyboard = UI.rfb.get_keyboard();
  97. // if ((! keyboard) || (! keyboard.get_focused())) {
  98. // $D('VNC_clipboard_text').blur();
  99. // }
  100. // };
  101. // Show mouse selector buttons on touch screen devices
  102. if (UI.isTouchDevice) {
  103. // Show mobile buttons
  104. $D('noVNC_mobile_buttons').style.display = "inline";
  105. UI.setMouseButton();
  106. // Remove the address bar
  107. setTimeout(function() { window.scrollTo(0, 1); }, 100);
  108. UI.forceSetting('clip', true);
  109. $D('noVNC_clip').disabled = true;
  110. } else {
  111. UI.initSetting('clip', false);
  112. }
  113. //iOS Safari does not support CSS position:fixed.
  114. //This detects iOS devices and enables javascript workaround.
  115. if ((navigator.userAgent.match(/iPhone/i)) ||
  116. (navigator.userAgent.match(/iPod/i)) ||
  117. (navigator.userAgent.match(/iPad/i))) {
  118. //UI.setOnscroll();
  119. //UI.setResize();
  120. }
  121. UI.setBarPosition();
  122. $D('noVNC_host').focus();
  123. UI.setViewClip();
  124. Util.addEvent(window, 'resize', UI.setViewClip);
  125. Util.addEvent(window, 'beforeunload', function () {
  126. if (UI.rfb_state === 'normal') {
  127. return "You are currently connected.";
  128. }
  129. } );
  130. // Show description by default when hosted at for kanaka.github.com
  131. if (location.host === "kanaka.github.io") {
  132. // Open the description dialog
  133. $D('noVNC_description').style.display = "block";
  134. } else {
  135. // Show the connect panel on first load unless autoconnecting
  136. if (autoconnect === UI.connSettingsOpen) {
  137. UI.toggleConnectPanel();
  138. }
  139. }
  140. // Add mouse event click/focus/blur event handlers to the UI
  141. UI.addMouseHandlers();
  142. if (typeof callback === "function") {
  143. callback(UI.rfb);
  144. }
  145. },
  146. addMouseHandlers: function() {
  147. // Setup interface handlers that can't be inline
  148. $D("noVNC_view_drag_button").onclick = UI.setViewDrag;
  149. $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
  150. $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
  151. $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
  152. $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
  153. $D("showKeyboard").onclick = UI.showKeyboard;
  154. $D("keyboardinput").oninput = UI.keyInput;
  155. $D("keyboardinput").onblur = UI.keyInputBlur;
  156. $D("showExtraKeysButton").onclick = UI.showExtraKeys;
  157. $D("toggleCtrlButton").onclick = UI.toggleCtrl;
  158. $D("toggleAltButton").onclick = UI.toggleAlt;
  159. $D("sendTabButton").onclick = UI.sendTab;
  160. $D("sendEscButton").onclick = UI.sendEsc;
  161. $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
  162. $D("xvpShutdownButton").onclick = UI.xvpShutdown;
  163. $D("xvpRebootButton").onclick = UI.xvpReboot;
  164. $D("xvpResetButton").onclick = UI.xvpReset;
  165. $D("noVNC_status").onclick = UI.togglePopupStatusPanel;
  166. $D("noVNC_popup_status_panel").onclick = UI.togglePopupStatusPanel;
  167. $D("xvpButton").onclick = UI.toggleXvpPanel;
  168. $D("clipboardButton").onclick = UI.toggleClipboardPanel;
  169. $D("settingsButton").onclick = UI.toggleSettingsPanel;
  170. $D("connectButton").onclick = UI.toggleConnectPanel;
  171. $D("disconnectButton").onclick = UI.disconnect;
  172. $D("descriptionButton").onclick = UI.toggleConnectPanel;
  173. $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
  174. $D("noVNC_clipboard_text").onblur = UI.displayFocus;
  175. $D("noVNC_clipboard_text").onchange = UI.clipSend;
  176. $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
  177. $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
  178. $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
  179. $D("noVNC_apply").onclick = UI.settingsApply;
  180. $D("noVNC_connect_button").onclick = UI.connect;
  181. },
  182. // Read form control compatible setting from cookie
  183. getSetting: function(name) {
  184. var val, ctrl = $D('noVNC_' + name);
  185. val = WebUtil.readSetting(name);
  186. if (val !== null && ctrl.type === 'checkbox') {
  187. if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
  188. val = false;
  189. } else {
  190. val = true;
  191. }
  192. }
  193. return val;
  194. },
  195. // Update cookie and form control setting. If value is not set, then
  196. // updates from control to current cookie setting.
  197. updateSetting: function(name, value) {
  198. var i, ctrl = $D('noVNC_' + name);
  199. // Save the cookie for this session
  200. if (typeof value !== 'undefined') {
  201. WebUtil.writeSetting(name, value);
  202. }
  203. // Update the settings control
  204. value = UI.getSetting(name);
  205. if (ctrl.type === 'checkbox') {
  206. ctrl.checked = value;
  207. } else if (typeof ctrl.options !== 'undefined') {
  208. for (i = 0; i < ctrl.options.length; i += 1) {
  209. if (ctrl.options[i].value === value) {
  210. ctrl.selectedIndex = i;
  211. break;
  212. }
  213. }
  214. } else {
  215. /*Weird IE9 error leads to 'null' appearring
  216. in textboxes instead of ''.*/
  217. if (value === null) {
  218. value = "";
  219. }
  220. ctrl.value = value;
  221. }
  222. },
  223. // Save control setting to cookie
  224. saveSetting: function(name) {
  225. var val, ctrl = $D('noVNC_' + name);
  226. if (ctrl.type === 'checkbox') {
  227. val = ctrl.checked;
  228. } else if (typeof ctrl.options !== 'undefined') {
  229. val = ctrl.options[ctrl.selectedIndex].value;
  230. } else {
  231. val = ctrl.value;
  232. }
  233. WebUtil.writeSetting(name, val);
  234. //Util.Debug("Setting saved '" + name + "=" + val + "'");
  235. return val;
  236. },
  237. // Initial page load read/initialization of settings
  238. initSetting: function(name, defVal) {
  239. var val;
  240. // Check Query string followed by cookie
  241. val = WebUtil.getQueryVar(name);
  242. if (val === null) {
  243. val = WebUtil.readSetting(name, defVal);
  244. }
  245. UI.updateSetting(name, val);
  246. //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
  247. return val;
  248. },
  249. // Force a setting to be a certain value
  250. forceSetting: function(name, val) {
  251. UI.updateSetting(name, val);
  252. return val;
  253. },
  254. // Show the popup status panel
  255. togglePopupStatusPanel: function() {
  256. var psp = $D('noVNC_popup_status_panel');
  257. if (UI.popupStatusOpen === true) {
  258. psp.style.display = "none";
  259. UI.popupStatusOpen = false;
  260. } else {
  261. psp.innerHTML = $D('noVNC_status').innerHTML;
  262. psp.style.display = "block";
  263. psp.style.left = window.innerWidth/2 -
  264. parseInt(window.getComputedStyle(psp, false).width)/2 -30 + "px";
  265. UI.popupStatusOpen = true;
  266. }
  267. },
  268. // Show the XVP panel
  269. toggleXvpPanel: function() {
  270. // Close the description panel
  271. $D('noVNC_description').style.display = "none";
  272. // Close settings if open
  273. if (UI.settingsOpen === true) {
  274. UI.settingsApply();
  275. UI.closeSettingsMenu();
  276. }
  277. // Close connection settings if open
  278. if (UI.connSettingsOpen === true) {
  279. UI.toggleConnectPanel();
  280. }
  281. // Close popup status panel if open
  282. if (UI.popupStatusOpen === true) {
  283. UI.togglePopupStatusPanel();
  284. }
  285. // Close clipboard panel if open
  286. if (UI.clipboardOpen === true) {
  287. UI.toggleClipboardPanel();
  288. }
  289. // Toggle XVP panel
  290. if (UI.xvpOpen === true) {
  291. $D('noVNC_xvp').style.display = "none";
  292. $D('xvpButton').className = "noVNC_status_button";
  293. UI.xvpOpen = false;
  294. } else {
  295. $D('noVNC_xvp').style.display = "block";
  296. $D('xvpButton').className = "noVNC_status_button_selected";
  297. UI.xvpOpen = true;
  298. }
  299. },
  300. // Show the clipboard panel
  301. toggleClipboardPanel: function() {
  302. // Close the description panel
  303. $D('noVNC_description').style.display = "none";
  304. // Close settings if open
  305. if (UI.settingsOpen === true) {
  306. UI.settingsApply();
  307. UI.closeSettingsMenu();
  308. }
  309. // Close connection settings if open
  310. if (UI.connSettingsOpen === true) {
  311. UI.toggleConnectPanel();
  312. }
  313. // Close popup status panel if open
  314. if (UI.popupStatusOpen === true) {
  315. UI.togglePopupStatusPanel();
  316. }
  317. // Close XVP panel if open
  318. if (UI.xvpOpen === true) {
  319. UI.toggleXvpPanel();
  320. }
  321. // Toggle Clipboard Panel
  322. if (UI.clipboardOpen === true) {
  323. $D('noVNC_clipboard').style.display = "none";
  324. $D('clipboardButton').className = "noVNC_status_button";
  325. UI.clipboardOpen = false;
  326. } else {
  327. $D('noVNC_clipboard').style.display = "block";
  328. $D('clipboardButton').className = "noVNC_status_button_selected";
  329. UI.clipboardOpen = true;
  330. }
  331. },
  332. // Show the connection settings panel/menu
  333. toggleConnectPanel: function() {
  334. // Close the description panel
  335. $D('noVNC_description').style.display = "none";
  336. // Close connection settings if open
  337. if (UI.settingsOpen === true) {
  338. UI.settingsApply();
  339. UI.closeSettingsMenu();
  340. $D('connectButton').className = "noVNC_status_button";
  341. }
  342. // Close clipboard panel if open
  343. if (UI.clipboardOpen === true) {
  344. UI.toggleClipboardPanel();
  345. }
  346. // Close popup status panel if open
  347. if (UI.popupStatusOpen === true) {
  348. UI.togglePopupStatusPanel();
  349. }
  350. // Close XVP panel if open
  351. if (UI.xvpOpen === true) {
  352. UI.toggleXvpPanel();
  353. }
  354. // Toggle Connection Panel
  355. if (UI.connSettingsOpen === true) {
  356. $D('noVNC_controls').style.display = "none";
  357. $D('connectButton').className = "noVNC_status_button";
  358. UI.connSettingsOpen = false;
  359. UI.saveSetting('host');
  360. UI.saveSetting('port');
  361. //UI.saveSetting('password');
  362. } else {
  363. $D('noVNC_controls').style.display = "block";
  364. $D('connectButton').className = "noVNC_status_button_selected";
  365. UI.connSettingsOpen = true;
  366. $D('noVNC_host').focus();
  367. }
  368. },
  369. // Toggle the settings menu:
  370. // On open, settings are refreshed from saved cookies.
  371. // On close, settings are applied
  372. toggleSettingsPanel: function() {
  373. // Close the description panel
  374. $D('noVNC_description').style.display = "none";
  375. if (UI.settingsOpen) {
  376. UI.settingsApply();
  377. UI.closeSettingsMenu();
  378. } else {
  379. UI.updateSetting('encrypt');
  380. UI.updateSetting('true_color');
  381. if (UI.rfb.get_display().get_cursor_uri()) {
  382. UI.updateSetting('cursor');
  383. } else {
  384. UI.updateSetting('cursor', !UI.isTouchDevice);
  385. $D('noVNC_cursor').disabled = true;
  386. }
  387. UI.updateSetting('clip');
  388. UI.updateSetting('shared');
  389. UI.updateSetting('view_only');
  390. UI.updateSetting('path');
  391. UI.updateSetting('repeaterID');
  392. UI.updateSetting('stylesheet');
  393. UI.updateSetting('logging');
  394. UI.openSettingsMenu();
  395. }
  396. },
  397. // Open menu
  398. openSettingsMenu: function() {
  399. // Close the description panel
  400. $D('noVNC_description').style.display = "none";
  401. // Close clipboard panel if open
  402. if (UI.clipboardOpen === true) {
  403. UI.toggleClipboardPanel();
  404. }
  405. // Close connection settings if open
  406. if (UI.connSettingsOpen === true) {
  407. UI.toggleConnectPanel();
  408. }
  409. // Close popup status panel if open
  410. if (UI.popupStatusOpen === true) {
  411. UI.togglePopupStatusPanel();
  412. }
  413. // Close XVP panel if open
  414. if (UI.xvpOpen === true) {
  415. UI.toggleXvpPanel();
  416. }
  417. $D('noVNC_settings').style.display = "block";
  418. $D('settingsButton').className = "noVNC_status_button_selected";
  419. UI.settingsOpen = true;
  420. },
  421. // Close menu (without applying settings)
  422. closeSettingsMenu: function() {
  423. $D('noVNC_settings').style.display = "none";
  424. $D('settingsButton').className = "noVNC_status_button";
  425. UI.settingsOpen = false;
  426. },
  427. // Save/apply settings when 'Apply' button is pressed
  428. settingsApply: function() {
  429. //Util.Debug(">> settingsApply");
  430. UI.saveSetting('encrypt');
  431. UI.saveSetting('true_color');
  432. if (UI.rfb.get_display().get_cursor_uri()) {
  433. UI.saveSetting('cursor');
  434. }
  435. UI.saveSetting('clip');
  436. UI.saveSetting('shared');
  437. UI.saveSetting('view_only');
  438. UI.saveSetting('path');
  439. UI.saveSetting('repeaterID');
  440. UI.saveSetting('stylesheet');
  441. UI.saveSetting('logging');
  442. // Settings with immediate (non-connected related) effect
  443. WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
  444. WebUtil.init_logging(UI.getSetting('logging'));
  445. UI.setViewClip();
  446. UI.setViewDrag(UI.rfb.get_viewportDrag());
  447. //Util.Debug("<< settingsApply");
  448. },
  449. setPassword: function() {
  450. UI.rfb.sendPassword($D('noVNC_password').value);
  451. //Reset connect button.
  452. $D('noVNC_connect_button').value = "Connect";
  453. $D('noVNC_connect_button').onclick = UI.Connect;
  454. //Hide connection panel.
  455. UI.toggleConnectPanel();
  456. return false;
  457. },
  458. sendCtrlAltDel: function() {
  459. UI.rfb.sendCtrlAltDel();
  460. },
  461. xvpShutdown: function() {
  462. UI.rfb.xvpShutdown();
  463. },
  464. xvpReboot: function() {
  465. UI.rfb.xvpReboot();
  466. },
  467. xvpReset: function() {
  468. UI.rfb.xvpReset();
  469. },
  470. setMouseButton: function(num) {
  471. var b, blist = [0, 1,2,4], button;
  472. if (typeof num === 'undefined') {
  473. // Disable mouse buttons
  474. num = -1;
  475. }
  476. if (UI.rfb) {
  477. UI.rfb.get_mouse().set_touchButton(num);
  478. }
  479. for (b = 0; b < blist.length; b++) {
  480. button = $D('noVNC_mouse_button' + blist[b]);
  481. if (blist[b] === num) {
  482. button.style.display = "";
  483. } else {
  484. button.style.display = "none";
  485. /*
  486. button.style.backgroundColor = "black";
  487. button.style.color = "lightgray";
  488. button.style.backgroundColor = "";
  489. button.style.color = "";
  490. */
  491. }
  492. }
  493. },
  494. updateState: function(rfb, state, oldstate, msg) {
  495. var s, sb, c, d, cad, vd, klass;
  496. UI.rfb_state = state;
  497. switch (state) {
  498. case 'failed':
  499. case 'fatal':
  500. klass = "noVNC_status_error";
  501. break;
  502. case 'normal':
  503. klass = "noVNC_status_normal";
  504. break;
  505. case 'disconnected':
  506. $D('noVNC_logo').style.display = "block";
  507. // Fall through
  508. case 'loaded':
  509. klass = "noVNC_status_normal";
  510. break;
  511. case 'password':
  512. UI.toggleConnectPanel();
  513. $D('noVNC_connect_button').value = "Send Password";
  514. $D('noVNC_connect_button').onclick = UI.setPassword;
  515. $D('noVNC_password').focus();
  516. klass = "noVNC_status_warn";
  517. break;
  518. default:
  519. klass = "noVNC_status_warn";
  520. break;
  521. }
  522. if (typeof(msg) !== 'undefined') {
  523. $D('noVNC-control-bar').setAttribute("class", klass);
  524. $D('noVNC_status').innerHTML = msg;
  525. }
  526. UI.updateVisualState();
  527. },
  528. // Disable/enable controls depending on connection state
  529. updateVisualState: function() {
  530. var connected = UI.rfb_state === 'normal' ? true : false;
  531. //Util.Debug(">> updateVisualState");
  532. $D('noVNC_encrypt').disabled = connected;
  533. $D('noVNC_true_color').disabled = connected;
  534. if (UI.rfb && UI.rfb.get_display() &&
  535. UI.rfb.get_display().get_cursor_uri()) {
  536. $D('noVNC_cursor').disabled = connected;
  537. } else {
  538. UI.updateSetting('cursor', !UI.isTouchDevice);
  539. $D('noVNC_cursor').disabled = true;
  540. }
  541. $D('noVNC_shared').disabled = connected;
  542. $D('noVNC_view_only').disabled = connected;
  543. $D('noVNC_path').disabled = connected;
  544. $D('noVNC_repeaterID').disabled = connected;
  545. if (connected) {
  546. UI.setViewClip();
  547. UI.setMouseButton(1);
  548. $D('clipboardButton').style.display = "inline";
  549. $D('showKeyboard').style.display = "inline";
  550. $D('noVNC_extra_keys').style.display = "";
  551. $D('sendCtrlAltDelButton').style.display = "inline";
  552. } else {
  553. UI.setMouseButton();
  554. $D('clipboardButton').style.display = "none";
  555. $D('showKeyboard').style.display = "none";
  556. $D('noVNC_extra_keys').style.display = "none";
  557. $D('sendCtrlAltDelButton').style.display = "none";
  558. UI.updateXvpVisualState(0);
  559. }
  560. // State change disables viewport dragging.
  561. // It is enabled (toggled) by direct click on the button
  562. UI.setViewDrag(false);
  563. switch (UI.rfb_state) {
  564. case 'fatal':
  565. case 'failed':
  566. case 'loaded':
  567. case 'disconnected':
  568. $D('connectButton').style.display = "";
  569. $D('disconnectButton').style.display = "none";
  570. break;
  571. default:
  572. $D('connectButton').style.display = "none";
  573. $D('disconnectButton').style.display = "";
  574. break;
  575. }
  576. //Util.Debug("<< updateVisualState");
  577. },
  578. // Disable/enable XVP button
  579. updateXvpVisualState: function(ver) {
  580. if (ver >= 1) {
  581. $D('xvpButton').style.display = 'inline';
  582. } else {
  583. $D('xvpButton').style.display = 'none';
  584. // Close XVP panel if open
  585. if (UI.xvpOpen === true) {
  586. UI.toggleXvpPanel();
  587. }
  588. }
  589. },
  590. // Display the desktop name in the document title
  591. updateDocumentTitle: function(rfb, name) {
  592. document.title = name + " - noVNC";
  593. },
  594. clipReceive: function(rfb, text) {
  595. Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
  596. $D('noVNC_clipboard_text').value = text;
  597. Util.Debug("<< UI.clipReceive");
  598. },
  599. connect: function() {
  600. var host, port, password, path;
  601. UI.closeSettingsMenu();
  602. UI.toggleConnectPanel();
  603. host = $D('noVNC_host').value;
  604. port = $D('noVNC_port').value;
  605. password = $D('noVNC_password').value;
  606. path = $D('noVNC_path').value;
  607. if ((!host) || (!port)) {
  608. throw("Must set host and port");
  609. }
  610. UI.rfb.set_encrypt(UI.getSetting('encrypt'));
  611. UI.rfb.set_true_color(UI.getSetting('true_color'));
  612. UI.rfb.set_local_cursor(UI.getSetting('cursor'));
  613. UI.rfb.set_shared(UI.getSetting('shared'));
  614. UI.rfb.set_view_only(UI.getSetting('view_only'));
  615. UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
  616. UI.rfb.connect(host, port, password, path);
  617. //Close dialog.
  618. setTimeout(UI.setBarPosition, 100);
  619. $D('noVNC_logo').style.display = "none";
  620. },
  621. disconnect: function() {
  622. UI.closeSettingsMenu();
  623. UI.rfb.disconnect();
  624. $D('noVNC_logo').style.display = "block";
  625. UI.connSettingsOpen = false;
  626. UI.toggleConnectPanel();
  627. },
  628. displayBlur: function() {
  629. UI.rfb.get_keyboard().set_focused(false);
  630. UI.rfb.get_mouse().set_focused(false);
  631. },
  632. displayFocus: function() {
  633. UI.rfb.get_keyboard().set_focused(true);
  634. UI.rfb.get_mouse().set_focused(true);
  635. },
  636. clipClear: function() {
  637. $D('noVNC_clipboard_text').value = "";
  638. UI.rfb.clipboardPasteFrom("");
  639. },
  640. clipSend: function() {
  641. var text = $D('noVNC_clipboard_text').value;
  642. Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
  643. UI.rfb.clipboardPasteFrom(text);
  644. Util.Debug("<< UI.clipSend");
  645. },
  646. // Enable/disable and configure viewport clipping
  647. setViewClip: function(clip) {
  648. var display, cur_clip, pos, new_w, new_h;
  649. if (UI.rfb) {
  650. display = UI.rfb.get_display();
  651. } else {
  652. return;
  653. }
  654. cur_clip = display.get_viewport();
  655. if (typeof(clip) !== 'boolean') {
  656. // Use current setting
  657. clip = UI.getSetting('clip');
  658. }
  659. if (clip && !cur_clip) {
  660. // Turn clipping on
  661. UI.updateSetting('clip', true);
  662. } else if (!clip && cur_clip) {
  663. // Turn clipping off
  664. UI.updateSetting('clip', false);
  665. display.set_viewport(false);
  666. $D('noVNC_canvas').style.position = 'static';
  667. display.viewportChange();
  668. }
  669. if (UI.getSetting('clip')) {
  670. // If clipping, update clipping settings
  671. $D('noVNC_canvas').style.position = 'absolute';
  672. pos = Util.getPosition($D('noVNC_canvas'));
  673. new_w = window.innerWidth - pos.x;
  674. new_h = window.innerHeight - pos.y;
  675. display.set_viewport(true);
  676. display.viewportChange(0, 0, new_w, new_h);
  677. }
  678. },
  679. // Toggle/set/unset the viewport drag/move button
  680. setViewDrag: function(drag) {
  681. var vmb = $D('noVNC_view_drag_button');
  682. if (!UI.rfb) { return; }
  683. if (UI.rfb_state === 'normal' &&
  684. UI.rfb.get_display().get_viewport()) {
  685. vmb.style.display = "inline";
  686. } else {
  687. vmb.style.display = "none";
  688. }
  689. if (typeof(drag) === "undefined" ||
  690. typeof(drag) === "object") {
  691. // If not specified, then toggle
  692. drag = !UI.rfb.get_viewportDrag();
  693. }
  694. if (drag) {
  695. vmb.className = "noVNC_status_button_selected";
  696. UI.rfb.set_viewportDrag(true);
  697. } else {
  698. vmb.className = "noVNC_status_button";
  699. UI.rfb.set_viewportDrag(false);
  700. }
  701. },
  702. // On touch devices, show the OS keyboard
  703. showKeyboard: function() {
  704. var kbi, skb, l;
  705. kbi = $D('keyboardinput');
  706. skb = $D('showKeyboard');
  707. l = kbi.value.length;
  708. if(UI.keyboardVisible === false) {
  709. kbi.focus();
  710. try { kbi.setSelectionRange(l, l); } // Move the caret to the end
  711. catch (err) {} // setSelectionRange is undefined in Google Chrome
  712. UI.keyboardVisible = true;
  713. skb.className = "noVNC_status_button_selected";
  714. } else if(UI.keyboardVisible === true) {
  715. kbi.blur();
  716. skb.className = "noVNC_status_button";
  717. UI.keyboardVisible = false;
  718. }
  719. },
  720. keepKeyboard: function() {
  721. clearTimeout(UI.hideKeyboardTimeout);
  722. if(UI.keyboardVisible === true) {
  723. $D('keyboardinput').focus();
  724. $D('showKeyboard').className = "noVNC_status_button_selected";
  725. } else if(UI.keyboardVisible === false) {
  726. $D('keyboardinput').blur();
  727. $D('showKeyboard').className = "noVNC_status_button";
  728. }
  729. },
  730. keyboardinputReset: function() {
  731. var kbi = $D('keyboardinput');
  732. kbi.value = Array(UI.defaultKeyboardinputLen).join("_");
  733. UI.lastKeyboardinput = kbi.value;
  734. },
  735. // When normal keyboard events are left uncought, use the input events from
  736. // the keyboardinput element instead and generate the corresponding key events.
  737. // This code is required since some browsers on Android are inconsistent in
  738. // sending keyCodes in the normal keyboard events when using on screen keyboards.
  739. keyInput: function(event) {
  740. var newValue, oldValue, newLen, oldLen;
  741. newValue = event.target.value;
  742. oldValue = UI.lastKeyboardinput;
  743. try {
  744. // Try to check caret position since whitespace at the end
  745. // will not be considered by value.length in some browsers
  746. newLen = Math.max(event.target.selectionStart, newValue.length);
  747. } catch (err) {
  748. // selectionStart is undefined in Google Chrome
  749. newLen = newValue.length;
  750. }
  751. oldLen = oldValue.length;
  752. var backspaces;
  753. var inputs = newLen - oldLen;
  754. if (inputs < 0)
  755. backspaces = -inputs;
  756. else
  757. backspaces = 0;
  758. // Compare the old string with the new to account for
  759. // text-corrections or other input that modify existing text
  760. for (var i = 0; i < Math.min(oldLen, newLen); i++) {
  761. if (newValue.charAt(i) != oldValue.charAt(i)) {
  762. inputs = newLen - i;
  763. backspaces = oldLen - i;
  764. break;
  765. }
  766. }
  767. // Send the key events
  768. for (var i = 0; i < backspaces; i++)
  769. UI.rfb.sendKey(XK_BackSpace);
  770. for (var i = newLen - inputs; i < newLen; i++)
  771. UI.rfb.sendKey(newValue.charCodeAt(i));
  772. // Control the text content length in the keyboardinput element
  773. if (newLen > 2 * UI.defaultKeyboardinputLen) {
  774. UI.keyboardinputReset();
  775. } else if (newLen < 1) {
  776. // There always have to be some text in the keyboardinput
  777. // element with which backspace can interact.
  778. UI.keyboardinputReset();
  779. // This sometimes causes the keyboard to disappear for a second
  780. // but it is required for the android keyboard to recognize that
  781. // text has been added to the field
  782. event.target.blur();
  783. // This has to be ran outside of the input handler in order to work
  784. setTimeout(function() { UI.keepKeyboard(); }, 0);
  785. } else {
  786. UI.lastKeyboardinput = newValue;
  787. }
  788. },
  789. keyInputBlur: function() {
  790. $D('showKeyboard').className = "noVNC_status_button";
  791. //Weird bug in iOS if you change keyboardVisible
  792. //here it does not actually occur so next time
  793. //you click keyboard icon it doesnt work.
  794. UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100);
  795. },
  796. showExtraKeys: function() {
  797. UI.keepKeyboard();
  798. if(UI.extraKeysVisible === false) {
  799. $D('toggleCtrlButton').style.display = "inline";
  800. $D('toggleAltButton').style.display = "inline";
  801. $D('sendTabButton').style.display = "inline";
  802. $D('sendEscButton').style.display = "inline";
  803. $D('showExtraKeysButton').className = "noVNC_status_button_selected";
  804. UI.extraKeysVisible = true;
  805. } else if(UI.extraKeysVisible === true) {
  806. $D('toggleCtrlButton').style.display = "";
  807. $D('toggleAltButton').style.display = "";
  808. $D('sendTabButton').style.display = "";
  809. $D('sendEscButton').style.display = "";
  810. $D('showExtraKeysButton').className = "noVNC_status_button";
  811. UI.extraKeysVisible = false;
  812. }
  813. },
  814. toggleCtrl: function() {
  815. UI.keepKeyboard();
  816. if(UI.ctrlOn === false) {
  817. UI.rfb.sendKey(XK_Control_L, true);
  818. $D('toggleCtrlButton').className = "noVNC_status_button_selected";
  819. UI.ctrlOn = true;
  820. } else if(UI.ctrlOn === true) {
  821. UI.rfb.sendKey(XK_Control_L, false);
  822. $D('toggleCtrlButton').className = "noVNC_status_button";
  823. UI.ctrlOn = false;
  824. }
  825. },
  826. toggleAlt: function() {
  827. UI.keepKeyboard();
  828. if(UI.altOn === false) {
  829. UI.rfb.sendKey(XK_Alt_L, true);
  830. $D('toggleAltButton').className = "noVNC_status_button_selected";
  831. UI.altOn = true;
  832. } else if(UI.altOn === true) {
  833. UI.rfb.sendKey(XK_Alt_L, false);
  834. $D('toggleAltButton').className = "noVNC_status_button";
  835. UI.altOn = false;
  836. }
  837. },
  838. sendTab: function() {
  839. UI.keepKeyboard();
  840. UI.rfb.sendKey(XK_Tab);
  841. },
  842. sendEsc: function() {
  843. UI.keepKeyboard();
  844. UI.rfb.sendKey(XK_Escape);
  845. },
  846. setKeyboard: function() {
  847. UI.keyboardVisible = false;
  848. },
  849. // iOS < Version 5 does not support position fixed. Javascript workaround:
  850. setOnscroll: function() {
  851. window.onscroll = function() {
  852. UI.setBarPosition();
  853. };
  854. },
  855. setResize: function () {
  856. window.onResize = function() {
  857. UI.setBarPosition();
  858. };
  859. },
  860. //Helper to add options to dropdown.
  861. addOption: function(selectbox,text,value )
  862. {
  863. var optn = document.createElement("OPTION");
  864. optn.text = text;
  865. optn.value = value;
  866. selectbox.options.add(optn);
  867. },
  868. setBarPosition: function() {
  869. $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
  870. $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
  871. var vncwidth = $D('noVNC_screen').style.offsetWidth;
  872. $D('noVNC-control-bar').style.width = vncwidth + 'px';
  873. }
  874. };