ui.js 18 KB

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