ui.js 40 KB

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