ui.js 19 KB

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