ui.js 18 KB

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