ui.js 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2012 Joel Martin
  4. * Copyright (C) 2016 Samuel Mannehed for Cendio AB
  5. * Copyright (C) 2016 Pierre Ossman for Cendio AB
  6. * Licensed under MPL 2.0 (see LICENSE.txt)
  7. *
  8. * See README.md for usage and integration instructions.
  9. */
  10. /* jslint white: false, browser: true */
  11. /* global window, document.getElementById, Util, WebUtil, RFB, Display */
  12. import * as Log from '../core/util/logging.js';
  13. import _, { l10n } from '../core/util/localization.js';
  14. import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js';
  15. import { setCapture, getPointerEvent } from '../core/util/events.js';
  16. import KeyTable from "../core/input/keysym.js";
  17. import keysyms from "../core/input/keysymdef.js";
  18. import RFB from "../core/rfb.js";
  19. import Display from "../core/display.js";
  20. import * as WebUtil from "./webutil.js";
  21. var UI = {
  22. connected: false,
  23. desktopName: "",
  24. resizeTimeout: null,
  25. statusTimeout: null,
  26. hideKeyboardTimeout: null,
  27. idleControlbarTimeout: null,
  28. closeControlbarTimeout: null,
  29. controlbarGrabbed: false,
  30. controlbarDrag: false,
  31. controlbarMouseDownClientY: 0,
  32. controlbarMouseDownOffsetY: 0,
  33. isSafari: false,
  34. rememberedClipSetting: null,
  35. lastKeyboardinput: null,
  36. defaultKeyboardinputLen: 100,
  37. inhibit_reconnect: true,
  38. reconnect_callback: null,
  39. reconnect_password: null,
  40. prime: function(callback) {
  41. if (document.readyState === "interactive" || document.readyState === "complete") {
  42. UI.load(callback);
  43. } else {
  44. document.addEventListener('DOMContentLoaded', UI.load.bind(UI, callback));
  45. }
  46. },
  47. // Setup rfb object, load settings from browser storage, then call
  48. // UI.init to setup the UI/menus
  49. load: function(callback) {
  50. WebUtil.initSettings(UI.start, callback);
  51. },
  52. // Render default UI and initialize settings menu
  53. start: function(callback) {
  54. // Setup global variables first
  55. UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
  56. navigator.userAgent.indexOf('Chrome') === -1);
  57. UI.initSettings();
  58. // Translate the DOM
  59. l10n.translateDOM();
  60. // Adapt the interface for touch screen devices
  61. if (isTouchDevice) {
  62. document.documentElement.classList.add("noVNC_touch");
  63. // Remove the address bar
  64. setTimeout(function() { window.scrollTo(0, 1); }, 100);
  65. }
  66. // Restore control bar position
  67. if (WebUtil.readSetting('controlbar_pos') === 'right') {
  68. UI.toggleControlbarSide();
  69. }
  70. UI.initFullscreen();
  71. // Setup event handlers
  72. UI.addResizeHandlers();
  73. UI.addControlbarHandlers();
  74. UI.addTouchSpecificHandlers();
  75. UI.addExtraKeysHandlers();
  76. UI.addXvpHandlers();
  77. UI.addConnectionControlHandlers();
  78. UI.addClipboardHandlers();
  79. UI.addSettingsHandlers();
  80. document.getElementById("noVNC_status")
  81. .addEventListener('click', UI.hideStatus);
  82. // Bootstrap fallback input handler
  83. UI.keyboardinputReset();
  84. UI.openControlbar();
  85. // Show the connect panel on first load unless autoconnecting
  86. if (!autoconnect) {
  87. UI.openConnectPanel();
  88. }
  89. UI.updateViewClip();
  90. UI.updateVisualState();
  91. document.getElementById('noVNC_setting_host').focus();
  92. document.documentElement.classList.remove("noVNC_loading");
  93. var autoconnect = WebUtil.getConfigVar('autoconnect', false);
  94. if (autoconnect === 'true' || autoconnect == '1') {
  95. autoconnect = true;
  96. UI.connect();
  97. } else {
  98. autoconnect = false;
  99. }
  100. if (typeof callback === "function") {
  101. callback(UI.rfb);
  102. }
  103. },
  104. initFullscreen: function() {
  105. // Only show the button if fullscreen is properly supported
  106. // * Safari doesn't support alphanumerical input while in fullscreen
  107. if (!UI.isSafari &&
  108. (document.documentElement.requestFullscreen ||
  109. document.documentElement.mozRequestFullScreen ||
  110. document.documentElement.webkitRequestFullscreen ||
  111. document.body.msRequestFullscreen)) {
  112. document.getElementById('noVNC_fullscreen_button')
  113. .classList.remove("noVNC_hidden");
  114. UI.addFullscreenHandlers();
  115. }
  116. },
  117. initSettings: function() {
  118. var i;
  119. // Logging selection dropdown
  120. var llevels = ['error', 'warn', 'info', 'debug'];
  121. for (i = 0; i < llevels.length; i += 1) {
  122. UI.addOption(document.getElementById('noVNC_setting_logging'),llevels[i], llevels[i]);
  123. }
  124. // Settings with immediate effects
  125. UI.initSetting('logging', 'warn');
  126. UI.updateLogging();
  127. // if port == 80 (or 443) then it won't be present and should be
  128. // set manually
  129. var port = window.location.port;
  130. if (!port) {
  131. if (window.location.protocol.substring(0,5) == 'https') {
  132. port = 443;
  133. }
  134. else if (window.location.protocol.substring(0,4) == 'http') {
  135. port = 80;
  136. }
  137. }
  138. /* Populate the controls if defaults are provided in the URL */
  139. UI.initSetting('host', window.location.hostname);
  140. UI.initSetting('port', port);
  141. UI.initSetting('encrypt', (window.location.protocol === "https:"));
  142. UI.initSetting('cursor', !isTouchDevice);
  143. UI.initSetting('clip', false);
  144. UI.initSetting('resize', 'off');
  145. UI.initSetting('shared', true);
  146. UI.initSetting('view_only', false);
  147. UI.initSetting('path', 'websockify');
  148. UI.initSetting('repeaterID', '');
  149. UI.initSetting('reconnect', false);
  150. UI.initSetting('reconnect_delay', 5000);
  151. UI.setupSettingLabels();
  152. },
  153. // Adds a link to the label elements on the corresponding input elements
  154. setupSettingLabels: function() {
  155. var labels = document.getElementsByTagName('LABEL');
  156. for (var i = 0; i < labels.length; i++) {
  157. var htmlFor = labels[i].htmlFor;
  158. if (htmlFor != '') {
  159. var elem = document.getElementById(htmlFor);
  160. if (elem) elem.label = labels[i];
  161. } else {
  162. // If 'for' isn't set, use the first input element child
  163. var children = labels[i].children;
  164. for (var j = 0; j < children.length; j++) {
  165. if (children[j].form !== undefined) {
  166. children[j].label = labels[i];
  167. break;
  168. }
  169. }
  170. }
  171. }
  172. },
  173. initRFB: function() {
  174. try {
  175. UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
  176. 'onNotification': UI.notification,
  177. 'onUpdateState': UI.updateState,
  178. 'onDisconnected': UI.disconnectFinished,
  179. 'onPasswordRequired': UI.passwordRequired,
  180. 'onXvpInit': UI.updateXvpButton,
  181. 'onClipboard': UI.clipboardReceive,
  182. 'onBell': UI.bell,
  183. 'onFBUComplete': UI.initialResize,
  184. 'onFBResize': UI.updateSessionSize,
  185. 'onDesktopName': UI.updateDesktopName});
  186. return true;
  187. } catch (exc) {
  188. var msg = "Unable to create RFB client -- " + exc;
  189. Log.Error(msg);
  190. UI.showStatus(msg, 'error');
  191. return false;
  192. }
  193. },
  194. /* ------^-------
  195. * /INIT
  196. * ==============
  197. * EVENT HANDLERS
  198. * ------v------*/
  199. addResizeHandlers: function() {
  200. window.addEventListener('resize', UI.applyResizeMode);
  201. window.addEventListener('resize', UI.updateViewClip);
  202. },
  203. addControlbarHandlers: function() {
  204. document.getElementById("noVNC_control_bar")
  205. .addEventListener('mousemove', UI.activateControlbar);
  206. document.getElementById("noVNC_control_bar")
  207. .addEventListener('mouseup', UI.activateControlbar);
  208. document.getElementById("noVNC_control_bar")
  209. .addEventListener('mousedown', UI.activateControlbar);
  210. document.getElementById("noVNC_control_bar")
  211. .addEventListener('keypress', UI.activateControlbar);
  212. document.getElementById("noVNC_control_bar")
  213. .addEventListener('mousedown', UI.keepControlbar);
  214. document.getElementById("noVNC_control_bar")
  215. .addEventListener('keypress', UI.keepControlbar);
  216. document.getElementById("noVNC_view_drag_button")
  217. .addEventListener('click', UI.toggleViewDrag);
  218. document.getElementById("noVNC_control_bar_handle")
  219. .addEventListener('mousedown', UI.controlbarHandleMouseDown);
  220. document.getElementById("noVNC_control_bar_handle")
  221. .addEventListener('mouseup', UI.controlbarHandleMouseUp);
  222. document.getElementById("noVNC_control_bar_handle")
  223. .addEventListener('mousemove', UI.dragControlbarHandle);
  224. // resize events aren't available for elements
  225. window.addEventListener('resize', UI.updateControlbarHandle);
  226. var exps = document.getElementsByClassName("noVNC_expander");
  227. for (var i = 0;i < exps.length;i++) {
  228. exps[i].addEventListener('click', UI.toggleExpander);
  229. }
  230. },
  231. addTouchSpecificHandlers: function() {
  232. document.getElementById("noVNC_mouse_button0")
  233. .addEventListener('click', function () { UI.setMouseButton(1); });
  234. document.getElementById("noVNC_mouse_button1")
  235. .addEventListener('click', function () { UI.setMouseButton(2); });
  236. document.getElementById("noVNC_mouse_button2")
  237. .addEventListener('click', function () { UI.setMouseButton(4); });
  238. document.getElementById("noVNC_mouse_button4")
  239. .addEventListener('click', function () { UI.setMouseButton(0); });
  240. document.getElementById("noVNC_keyboard_button")
  241. .addEventListener('click', UI.toggleVirtualKeyboard);
  242. document.getElementById("noVNC_keyboardinput")
  243. .addEventListener('input', UI.keyInput);
  244. document.getElementById("noVNC_keyboardinput")
  245. .addEventListener('focus', UI.onfocusVirtualKeyboard);
  246. document.getElementById("noVNC_keyboardinput")
  247. .addEventListener('blur', UI.onblurVirtualKeyboard);
  248. document.getElementById("noVNC_keyboardinput")
  249. .addEventListener('submit', function () { return false; });
  250. document.documentElement
  251. .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
  252. document.getElementById("noVNC_control_bar")
  253. .addEventListener('touchstart', UI.activateControlbar);
  254. document.getElementById("noVNC_control_bar")
  255. .addEventListener('touchmove', UI.activateControlbar);
  256. document.getElementById("noVNC_control_bar")
  257. .addEventListener('touchend', UI.activateControlbar);
  258. document.getElementById("noVNC_control_bar")
  259. .addEventListener('input', UI.activateControlbar);
  260. document.getElementById("noVNC_control_bar")
  261. .addEventListener('touchstart', UI.keepControlbar);
  262. document.getElementById("noVNC_control_bar")
  263. .addEventListener('input', UI.keepControlbar);
  264. document.getElementById("noVNC_control_bar_handle")
  265. .addEventListener('touchstart', UI.controlbarHandleMouseDown);
  266. document.getElementById("noVNC_control_bar_handle")
  267. .addEventListener('touchend', UI.controlbarHandleMouseUp);
  268. document.getElementById("noVNC_control_bar_handle")
  269. .addEventListener('touchmove', UI.dragControlbarHandle);
  270. },
  271. addExtraKeysHandlers: function() {
  272. document.getElementById("noVNC_toggle_extra_keys_button")
  273. .addEventListener('click', UI.toggleExtraKeys);
  274. document.getElementById("noVNC_toggle_ctrl_button")
  275. .addEventListener('click', UI.toggleCtrl);
  276. document.getElementById("noVNC_toggle_alt_button")
  277. .addEventListener('click', UI.toggleAlt);
  278. document.getElementById("noVNC_send_tab_button")
  279. .addEventListener('click', UI.sendTab);
  280. document.getElementById("noVNC_send_esc_button")
  281. .addEventListener('click', UI.sendEsc);
  282. document.getElementById("noVNC_send_ctrl_alt_del_button")
  283. .addEventListener('click', UI.sendCtrlAltDel);
  284. },
  285. addXvpHandlers: function() {
  286. document.getElementById("noVNC_xvp_shutdown_button")
  287. .addEventListener('click', function() { UI.rfb.xvpShutdown(); });
  288. document.getElementById("noVNC_xvp_reboot_button")
  289. .addEventListener('click', function() { UI.rfb.xvpReboot(); });
  290. document.getElementById("noVNC_xvp_reset_button")
  291. .addEventListener('click', function() { UI.rfb.xvpReset(); });
  292. document.getElementById("noVNC_xvp_button")
  293. .addEventListener('click', UI.toggleXvpPanel);
  294. },
  295. addConnectionControlHandlers: function() {
  296. document.getElementById("noVNC_disconnect_button")
  297. .addEventListener('click', UI.disconnect);
  298. document.getElementById("noVNC_connect_button")
  299. .addEventListener('click', UI.connect);
  300. document.getElementById("noVNC_cancel_reconnect_button")
  301. .addEventListener('click', UI.cancelReconnect);
  302. document.getElementById("noVNC_password_button")
  303. .addEventListener('click', UI.setPassword);
  304. },
  305. addClipboardHandlers: function() {
  306. document.getElementById("noVNC_clipboard_button")
  307. .addEventListener('click', UI.toggleClipboardPanel);
  308. document.getElementById("noVNC_clipboard_text")
  309. .addEventListener('focus', UI.displayBlur);
  310. document.getElementById("noVNC_clipboard_text")
  311. .addEventListener('blur', UI.displayFocus);
  312. document.getElementById("noVNC_clipboard_text")
  313. .addEventListener('change', UI.clipboardSend);
  314. document.getElementById("noVNC_clipboard_clear_button")
  315. .addEventListener('click', UI.clipboardClear);
  316. },
  317. // Add a call to save settings when the element changes,
  318. // unless the optional parameter changeFunc is used instead.
  319. addSettingChangeHandler: function(name, changeFunc) {
  320. var settingElem = document.getElementById("noVNC_setting_" + name);
  321. if (changeFunc === undefined) {
  322. changeFunc = function () { UI.saveSetting(name); };
  323. }
  324. settingElem.addEventListener('change', changeFunc);
  325. },
  326. addSettingsHandlers: function() {
  327. document.getElementById("noVNC_settings_button")
  328. .addEventListener('click', UI.toggleSettingsPanel);
  329. UI.addSettingChangeHandler('encrypt');
  330. UI.addSettingChangeHandler('cursor');
  331. UI.addSettingChangeHandler('cursor', UI.updateLocalCursor);
  332. UI.addSettingChangeHandler('resize');
  333. UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
  334. UI.addSettingChangeHandler('resize', UI.applyResizeMode);
  335. UI.addSettingChangeHandler('clip');
  336. UI.addSettingChangeHandler('clip', UI.updateViewClip);
  337. UI.addSettingChangeHandler('shared');
  338. UI.addSettingChangeHandler('view_only');
  339. UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
  340. UI.addSettingChangeHandler('host');
  341. UI.addSettingChangeHandler('port');
  342. UI.addSettingChangeHandler('path');
  343. UI.addSettingChangeHandler('repeaterID');
  344. UI.addSettingChangeHandler('logging');
  345. UI.addSettingChangeHandler('logging', UI.updateLogging);
  346. UI.addSettingChangeHandler('reconnect');
  347. UI.addSettingChangeHandler('reconnect_delay');
  348. },
  349. addFullscreenHandlers: function() {
  350. document.getElementById("noVNC_fullscreen_button")
  351. .addEventListener('click', UI.toggleFullscreen);
  352. window.addEventListener('fullscreenchange', UI.updateFullscreenButton);
  353. window.addEventListener('mozfullscreenchange', UI.updateFullscreenButton);
  354. window.addEventListener('webkitfullscreenchange', UI.updateFullscreenButton);
  355. window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
  356. },
  357. /* ------^-------
  358. * /EVENT HANDLERS
  359. * ==============
  360. * VISUAL
  361. * ------v------*/
  362. updateState: function(rfb, state, oldstate) {
  363. var msg;
  364. document.documentElement.classList.remove("noVNC_connecting");
  365. document.documentElement.classList.remove("noVNC_connected");
  366. document.documentElement.classList.remove("noVNC_disconnecting");
  367. document.documentElement.classList.remove("noVNC_reconnecting");
  368. switch (state) {
  369. case 'connecting':
  370. document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
  371. document.documentElement.classList.add("noVNC_connecting");
  372. break;
  373. case 'connected':
  374. UI.connected = true;
  375. UI.inhibit_reconnect = false;
  376. document.documentElement.classList.add("noVNC_connected");
  377. if (rfb && rfb.get_encrypt()) {
  378. msg = _("Connected (encrypted) to ") + UI.desktopName;
  379. } else {
  380. msg = _("Connected (unencrypted) to ") + UI.desktopName;
  381. }
  382. UI.showStatus(msg);
  383. break;
  384. case 'disconnecting':
  385. UI.connected = false;
  386. document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
  387. document.documentElement.classList.add("noVNC_disconnecting");
  388. break;
  389. case 'disconnected':
  390. UI.showStatus(_("Disconnected"));
  391. break;
  392. default:
  393. msg = "Invalid UI state";
  394. Log.Error(msg);
  395. UI.showStatus(msg, 'error');
  396. break;
  397. }
  398. UI.updateVisualState();
  399. },
  400. // Disable/enable controls depending on connection state
  401. updateVisualState: function() {
  402. //Log.Debug(">> updateVisualState");
  403. UI.enableDisableViewClip();
  404. if (cursorURIsSupported() && !isTouchDevice) {
  405. UI.enableSetting('cursor');
  406. } else {
  407. UI.disableSetting('cursor');
  408. }
  409. if (UI.connected) {
  410. UI.disableSetting('encrypt');
  411. UI.disableSetting('shared');
  412. UI.disableSetting('host');
  413. UI.disableSetting('port');
  414. UI.disableSetting('path');
  415. UI.disableSetting('repeaterID');
  416. UI.updateViewClip();
  417. UI.setMouseButton(1);
  418. // Hide the controlbar after 2 seconds
  419. UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
  420. } else {
  421. UI.enableSetting('encrypt');
  422. UI.enableSetting('shared');
  423. UI.enableSetting('host');
  424. UI.enableSetting('port');
  425. UI.enableSetting('path');
  426. UI.enableSetting('repeaterID');
  427. UI.updateXvpButton(0);
  428. UI.keepControlbar();
  429. }
  430. // Hide input related buttons in view only mode
  431. if (UI.rfb && UI.rfb.get_view_only()) {
  432. document.getElementById('noVNC_keyboard_button')
  433. .classList.add('noVNC_hidden');
  434. document.getElementById('noVNC_toggle_extra_keys_button')
  435. .classList.add('noVNC_hidden');
  436. } else {
  437. document.getElementById('noVNC_keyboard_button')
  438. .classList.remove('noVNC_hidden');
  439. document.getElementById('noVNC_toggle_extra_keys_button')
  440. .classList.remove('noVNC_hidden');
  441. }
  442. // State change disables viewport dragging.
  443. // It is enabled (toggled) by direct click on the button
  444. UI.setViewDrag(false);
  445. // State change also closes the password dialog
  446. document.getElementById('noVNC_password_dlg')
  447. .classList.remove('noVNC_open');
  448. //Log.Debug("<< updateVisualState");
  449. },
  450. showStatus: function(text, status_type, time) {
  451. var statusElem = document.getElementById('noVNC_status');
  452. clearTimeout(UI.statusTimeout);
  453. if (typeof status_type === 'undefined') {
  454. status_type = 'normal';
  455. }
  456. statusElem.classList.remove("noVNC_status_normal");
  457. statusElem.classList.remove("noVNC_status_warn");
  458. statusElem.classList.remove("noVNC_status_error");
  459. switch (status_type) {
  460. case 'warning':
  461. case 'warn':
  462. statusElem.classList.add("noVNC_status_warn");
  463. break;
  464. case 'error':
  465. statusElem.classList.add("noVNC_status_error");
  466. break;
  467. case 'normal':
  468. case 'info':
  469. default:
  470. statusElem.classList.add("noVNC_status_normal");
  471. break;
  472. }
  473. statusElem.textContent = text;
  474. statusElem.classList.add("noVNC_open");
  475. // If no time was specified, show the status for 1.5 seconds
  476. if (typeof time === 'undefined') {
  477. time = 1500;
  478. }
  479. // Error messages do not timeout
  480. if (status_type !== 'error') {
  481. UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
  482. }
  483. },
  484. hideStatus: function() {
  485. clearTimeout(UI.statusTimeout);
  486. document.getElementById('noVNC_status').classList.remove("noVNC_open");
  487. },
  488. notification: function (rfb, msg, level, options) {
  489. UI.showStatus(msg, level);
  490. },
  491. activateControlbar: function(event) {
  492. clearTimeout(UI.idleControlbarTimeout);
  493. // We manipulate the anchor instead of the actual control
  494. // bar in order to avoid creating new a stacking group
  495. document.getElementById('noVNC_control_bar_anchor')
  496. .classList.remove("noVNC_idle");
  497. UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
  498. },
  499. idleControlbar: function() {
  500. document.getElementById('noVNC_control_bar_anchor')
  501. .classList.add("noVNC_idle");
  502. },
  503. keepControlbar: function() {
  504. clearTimeout(UI.closeControlbarTimeout);
  505. },
  506. openControlbar: function() {
  507. document.getElementById('noVNC_control_bar')
  508. .classList.add("noVNC_open");
  509. },
  510. closeControlbar: function() {
  511. UI.closeAllPanels();
  512. document.getElementById('noVNC_control_bar')
  513. .classList.remove("noVNC_open");
  514. },
  515. toggleControlbar: function() {
  516. if (document.getElementById('noVNC_control_bar')
  517. .classList.contains("noVNC_open")) {
  518. UI.closeControlbar();
  519. } else {
  520. UI.openControlbar();
  521. }
  522. },
  523. toggleControlbarSide: function () {
  524. // Temporarily disable animation to avoid weird movement
  525. var bar = document.getElementById('noVNC_control_bar');
  526. bar.style.transitionDuration = '0s';
  527. bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
  528. var anchor = document.getElementById('noVNC_control_bar_anchor');
  529. if (anchor.classList.contains("noVNC_right")) {
  530. WebUtil.writeSetting('controlbar_pos', 'left');
  531. anchor.classList.remove("noVNC_right");
  532. } else {
  533. WebUtil.writeSetting('controlbar_pos', 'right');
  534. anchor.classList.add("noVNC_right");
  535. }
  536. // Consider this a movement of the handle
  537. UI.controlbarDrag = true;
  538. },
  539. showControlbarHint: function (show) {
  540. var hint = document.getElementById('noVNC_control_bar_hint');
  541. if (show) {
  542. hint.classList.add("noVNC_active");
  543. } else {
  544. hint.classList.remove("noVNC_active");
  545. }
  546. },
  547. dragControlbarHandle: function (e) {
  548. if (!UI.controlbarGrabbed) return;
  549. var ptr = getPointerEvent(e);
  550. var anchor = document.getElementById('noVNC_control_bar_anchor');
  551. if (ptr.clientX < (window.innerWidth * 0.1)) {
  552. if (anchor.classList.contains("noVNC_right")) {
  553. UI.toggleControlbarSide();
  554. }
  555. } else if (ptr.clientX > (window.innerWidth * 0.9)) {
  556. if (!anchor.classList.contains("noVNC_right")) {
  557. UI.toggleControlbarSide();
  558. }
  559. }
  560. if (!UI.controlbarDrag) {
  561. // The goal is to trigger on a certain physical width, the
  562. // devicePixelRatio brings us a bit closer but is not optimal.
  563. var dragThreshold = 10 * (window.devicePixelRatio || 1);
  564. var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
  565. if (dragDistance < dragThreshold) return;
  566. UI.controlbarDrag = true;
  567. }
  568. var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
  569. UI.moveControlbarHandle(eventY);
  570. e.preventDefault();
  571. e.stopPropagation();
  572. UI.keepControlbar();
  573. UI.activateControlbar();
  574. },
  575. // Move the handle but don't allow any position outside the bounds
  576. moveControlbarHandle: function (viewportRelativeY) {
  577. var handle = document.getElementById("noVNC_control_bar_handle");
  578. var handleHeight = handle.getBoundingClientRect().height;
  579. var controlbarBounds = document.getElementById("noVNC_control_bar")
  580. .getBoundingClientRect();
  581. var margin = 10;
  582. // These heights need to be non-zero for the below logic to work
  583. if (handleHeight === 0 || controlbarBounds.height === 0) {
  584. return;
  585. }
  586. var newY = viewportRelativeY;
  587. // Check if the coordinates are outside the control bar
  588. if (newY < controlbarBounds.top + margin) {
  589. // Force coordinates to be below the top of the control bar
  590. newY = controlbarBounds.top + margin;
  591. } else if (newY > controlbarBounds.top +
  592. controlbarBounds.height - handleHeight - margin) {
  593. // Force coordinates to be above the bottom of the control bar
  594. newY = controlbarBounds.top +
  595. controlbarBounds.height - handleHeight - margin;
  596. }
  597. // Corner case: control bar too small for stable position
  598. if (controlbarBounds.height < (handleHeight + margin * 2)) {
  599. newY = controlbarBounds.top +
  600. (controlbarBounds.height - handleHeight) / 2;
  601. }
  602. // The transform needs coordinates that are relative to the parent
  603. var parentRelativeY = newY - controlbarBounds.top;
  604. handle.style.transform = "translateY(" + parentRelativeY + "px)";
  605. },
  606. updateControlbarHandle: function () {
  607. // Since the control bar is fixed on the viewport and not the page,
  608. // the move function expects coordinates relative the the viewport.
  609. var handle = document.getElementById("noVNC_control_bar_handle");
  610. var handleBounds = handle.getBoundingClientRect();
  611. UI.moveControlbarHandle(handleBounds.top);
  612. },
  613. controlbarHandleMouseUp: function(e) {
  614. if ((e.type == "mouseup") && (e.button != 0)) return;
  615. // mouseup and mousedown on the same place toggles the controlbar
  616. if (UI.controlbarGrabbed && !UI.controlbarDrag) {
  617. UI.toggleControlbar();
  618. e.preventDefault();
  619. e.stopPropagation();
  620. UI.keepControlbar();
  621. UI.activateControlbar();
  622. }
  623. UI.controlbarGrabbed = false;
  624. UI.showControlbarHint(false);
  625. },
  626. controlbarHandleMouseDown: function(e) {
  627. if ((e.type == "mousedown") && (e.button != 0)) return;
  628. var ptr = getPointerEvent(e);
  629. var handle = document.getElementById("noVNC_control_bar_handle");
  630. var bounds = handle.getBoundingClientRect();
  631. // Touch events have implicit capture
  632. if (e.type === "mousedown") {
  633. setCapture(handle);
  634. }
  635. UI.controlbarGrabbed = true;
  636. UI.controlbarDrag = false;
  637. UI.showControlbarHint(true);
  638. UI.controlbarMouseDownClientY = ptr.clientY;
  639. UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
  640. e.preventDefault();
  641. e.stopPropagation();
  642. UI.keepControlbar();
  643. UI.activateControlbar();
  644. },
  645. toggleExpander: function(e) {
  646. if (this.classList.contains("noVNC_open")) {
  647. this.classList.remove("noVNC_open");
  648. } else {
  649. this.classList.add("noVNC_open");
  650. }
  651. },
  652. /* ------^-------
  653. * /VISUAL
  654. * ==============
  655. * SETTINGS
  656. * ------v------*/
  657. // Initial page load read/initialization of settings
  658. initSetting: function(name, defVal) {
  659. // Check Query string followed by cookie
  660. var val = WebUtil.getConfigVar(name);
  661. if (val === null) {
  662. val = WebUtil.readSetting(name, defVal);
  663. }
  664. UI.updateSetting(name, val);
  665. return val;
  666. },
  667. // Update cookie and form control setting. If value is not set, then
  668. // updates from control to current cookie setting.
  669. updateSetting: function(name, value) {
  670. // Save the cookie for this session
  671. if (typeof value !== 'undefined') {
  672. WebUtil.writeSetting(name, value);
  673. }
  674. // Update the settings control
  675. value = UI.getSetting(name);
  676. var ctrl = document.getElementById('noVNC_setting_' + name);
  677. if (ctrl.type === 'checkbox') {
  678. ctrl.checked = value;
  679. } else if (typeof ctrl.options !== 'undefined') {
  680. for (var i = 0; i < ctrl.options.length; i += 1) {
  681. if (ctrl.options[i].value === value) {
  682. ctrl.selectedIndex = i;
  683. break;
  684. }
  685. }
  686. } else {
  687. /*Weird IE9 error leads to 'null' appearring
  688. in textboxes instead of ''.*/
  689. if (value === null) {
  690. value = "";
  691. }
  692. ctrl.value = value;
  693. }
  694. },
  695. // Save control setting to cookie
  696. saveSetting: function(name) {
  697. var val, ctrl = document.getElementById('noVNC_setting_' + name);
  698. if (ctrl.type === 'checkbox') {
  699. val = ctrl.checked;
  700. } else if (typeof ctrl.options !== 'undefined') {
  701. val = ctrl.options[ctrl.selectedIndex].value;
  702. } else {
  703. val = ctrl.value;
  704. }
  705. WebUtil.writeSetting(name, val);
  706. //Log.Debug("Setting saved '" + name + "=" + val + "'");
  707. return val;
  708. },
  709. // Read form control compatible setting from cookie
  710. getSetting: function(name) {
  711. var ctrl = document.getElementById('noVNC_setting_' + name);
  712. var val = WebUtil.readSetting(name);
  713. if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
  714. if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
  715. val = false;
  716. } else {
  717. val = true;
  718. }
  719. }
  720. return val;
  721. },
  722. // These helpers compensate for the lack of parent-selectors and
  723. // previous-sibling-selectors in CSS which are needed when we want to
  724. // disable the labels that belong to disabled input elements.
  725. disableSetting: function(name) {
  726. var ctrl = document.getElementById('noVNC_setting_' + name);
  727. ctrl.disabled = true;
  728. ctrl.label.classList.add('noVNC_disabled');
  729. },
  730. enableSetting: function(name) {
  731. var ctrl = document.getElementById('noVNC_setting_' + name);
  732. ctrl.disabled = false;
  733. ctrl.label.classList.remove('noVNC_disabled');
  734. },
  735. /* ------^-------
  736. * /SETTINGS
  737. * ==============
  738. * PANELS
  739. * ------v------*/
  740. closeAllPanels: function() {
  741. UI.closeSettingsPanel();
  742. UI.closeXvpPanel();
  743. UI.closeClipboardPanel();
  744. UI.closeExtraKeys();
  745. },
  746. /* ------^-------
  747. * /PANELS
  748. * ==============
  749. * SETTINGS (panel)
  750. * ------v------*/
  751. openSettingsPanel: function() {
  752. UI.closeAllPanels();
  753. UI.openControlbar();
  754. // Refresh UI elements from saved cookies
  755. UI.updateSetting('encrypt');
  756. if (cursorURIsSupported()) {
  757. UI.updateSetting('cursor');
  758. } else {
  759. UI.updateSetting('cursor', !isTouchDevice);
  760. UI.disableSetting('cursor');
  761. }
  762. UI.updateSetting('clip');
  763. UI.updateSetting('resize');
  764. UI.updateSetting('shared');
  765. UI.updateSetting('view_only');
  766. UI.updateSetting('path');
  767. UI.updateSetting('repeaterID');
  768. UI.updateSetting('logging');
  769. UI.updateSetting('reconnect');
  770. UI.updateSetting('reconnect_delay');
  771. document.getElementById('noVNC_settings')
  772. .classList.add("noVNC_open");
  773. document.getElementById('noVNC_settings_button')
  774. .classList.add("noVNC_selected");
  775. },
  776. closeSettingsPanel: function() {
  777. document.getElementById('noVNC_settings')
  778. .classList.remove("noVNC_open");
  779. document.getElementById('noVNC_settings_button')
  780. .classList.remove("noVNC_selected");
  781. },
  782. toggleSettingsPanel: function() {
  783. if (document.getElementById('noVNC_settings')
  784. .classList.contains("noVNC_open")) {
  785. UI.closeSettingsPanel();
  786. } else {
  787. UI.openSettingsPanel();
  788. }
  789. },
  790. /* ------^-------
  791. * /SETTINGS
  792. * ==============
  793. * XVP
  794. * ------v------*/
  795. openXvpPanel: function() {
  796. UI.closeAllPanels();
  797. UI.openControlbar();
  798. document.getElementById('noVNC_xvp')
  799. .classList.add("noVNC_open");
  800. document.getElementById('noVNC_xvp_button')
  801. .classList.add("noVNC_selected");
  802. },
  803. closeXvpPanel: function() {
  804. document.getElementById('noVNC_xvp')
  805. .classList.remove("noVNC_open");
  806. document.getElementById('noVNC_xvp_button')
  807. .classList.remove("noVNC_selected");
  808. },
  809. toggleXvpPanel: function() {
  810. if (document.getElementById('noVNC_xvp')
  811. .classList.contains("noVNC_open")) {
  812. UI.closeXvpPanel();
  813. } else {
  814. UI.openXvpPanel();
  815. }
  816. },
  817. // Disable/enable XVP button
  818. updateXvpButton: function(ver) {
  819. if (ver >= 1 && !UI.rfb.get_view_only()) {
  820. document.getElementById('noVNC_xvp_button')
  821. .classList.remove("noVNC_hidden");
  822. } else {
  823. document.getElementById('noVNC_xvp_button')
  824. .classList.add("noVNC_hidden");
  825. // Close XVP panel if open
  826. UI.closeXvpPanel();
  827. }
  828. },
  829. /* ------^-------
  830. * /XVP
  831. * ==============
  832. * CLIPBOARD
  833. * ------v------*/
  834. openClipboardPanel: function() {
  835. UI.closeAllPanels();
  836. UI.openControlbar();
  837. document.getElementById('noVNC_clipboard')
  838. .classList.add("noVNC_open");
  839. document.getElementById('noVNC_clipboard_button')
  840. .classList.add("noVNC_selected");
  841. },
  842. closeClipboardPanel: function() {
  843. document.getElementById('noVNC_clipboard')
  844. .classList.remove("noVNC_open");
  845. document.getElementById('noVNC_clipboard_button')
  846. .classList.remove("noVNC_selected");
  847. },
  848. toggleClipboardPanel: function() {
  849. if (document.getElementById('noVNC_clipboard')
  850. .classList.contains("noVNC_open")) {
  851. UI.closeClipboardPanel();
  852. } else {
  853. UI.openClipboardPanel();
  854. }
  855. },
  856. clipboardReceive: function(rfb, text) {
  857. Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
  858. document.getElementById('noVNC_clipboard_text').value = text;
  859. Log.Debug("<< UI.clipboardReceive");
  860. },
  861. clipboardClear: function() {
  862. document.getElementById('noVNC_clipboard_text').value = "";
  863. UI.rfb.clipboardPasteFrom("");
  864. },
  865. clipboardSend: function() {
  866. var text = document.getElementById('noVNC_clipboard_text').value;
  867. Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
  868. UI.rfb.clipboardPasteFrom(text);
  869. Log.Debug("<< UI.clipboardSend");
  870. },
  871. /* ------^-------
  872. * /CLIPBOARD
  873. * ==============
  874. * CONNECTION
  875. * ------v------*/
  876. openConnectPanel: function() {
  877. document.getElementById('noVNC_connect_dlg')
  878. .classList.add("noVNC_open");
  879. },
  880. closeConnectPanel: function() {
  881. document.getElementById('noVNC_connect_dlg')
  882. .classList.remove("noVNC_open");
  883. },
  884. connect: function(event, password) {
  885. var host = UI.getSetting('host');
  886. var port = UI.getSetting('port');
  887. var path = UI.getSetting('path');
  888. if (typeof password === 'undefined') {
  889. password = WebUtil.getConfigVar('password');
  890. }
  891. if (password === null) {
  892. password = undefined;
  893. }
  894. if ((!host) || (!port)) {
  895. var msg = _("Must set host and port");
  896. Log.Error(msg);
  897. UI.showStatus(msg, 'error');
  898. return;
  899. }
  900. if (!UI.initRFB()) return;
  901. UI.closeAllPanels();
  902. UI.closeConnectPanel();
  903. UI.rfb.set_encrypt(UI.getSetting('encrypt'));
  904. UI.rfb.set_shared(UI.getSetting('shared'));
  905. UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
  906. UI.updateLocalCursor();
  907. UI.updateViewOnly();
  908. UI.rfb.connect(host, port, password, path);
  909. },
  910. disconnect: function() {
  911. UI.closeAllPanels();
  912. UI.rfb.disconnect();
  913. // Disable automatic reconnecting
  914. UI.inhibit_reconnect = true;
  915. // Restore the callback used for initial resize
  916. UI.rfb.set_onFBUComplete(UI.initialResize);
  917. // Don't display the connection settings until we're actually disconnected
  918. },
  919. reconnect: function() {
  920. UI.reconnect_callback = null;
  921. // if reconnect has been disabled in the meantime, do nothing.
  922. if (UI.inhibit_reconnect) {
  923. return;
  924. }
  925. UI.connect(null, UI.reconnect_password);
  926. },
  927. disconnectFinished: function (rfb, reason) {
  928. if (typeof reason !== 'undefined') {
  929. UI.showStatus(reason, 'error');
  930. } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
  931. document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
  932. document.documentElement.classList.add("noVNC_reconnecting");
  933. var delay = parseInt(UI.getSetting('reconnect_delay'));
  934. UI.reconnect_callback = setTimeout(UI.reconnect, delay);
  935. return;
  936. }
  937. UI.openControlbar();
  938. UI.openConnectPanel();
  939. },
  940. cancelReconnect: function() {
  941. if (UI.reconnect_callback !== null) {
  942. clearTimeout(UI.reconnect_callback);
  943. UI.reconnect_callback = null;
  944. }
  945. document.documentElement.classList.remove("noVNC_reconnecting");
  946. UI.openControlbar();
  947. UI.openConnectPanel();
  948. },
  949. /* ------^-------
  950. * /CONNECTION
  951. * ==============
  952. * PASSWORD
  953. * ------v------*/
  954. passwordRequired: function(rfb, msg) {
  955. document.getElementById('noVNC_password_dlg')
  956. .classList.add('noVNC_open');
  957. setTimeout(function () {
  958. document.getElementById('noVNC_password_input').focus();
  959. }, 100);
  960. if (typeof msg === 'undefined') {
  961. msg = _("Password is required");
  962. }
  963. Log.Warn(msg);
  964. UI.showStatus(msg, "warning");
  965. },
  966. setPassword: function(e) {
  967. var inputElem = document.getElementById('noVNC_password_input');
  968. var password = inputElem.value;
  969. // Clear the input after reading the password
  970. inputElem.value = "";
  971. UI.rfb.sendPassword(password);
  972. UI.reconnect_password = password;
  973. document.getElementById('noVNC_password_dlg')
  974. .classList.remove('noVNC_open');
  975. // Prevent actually submitting the form
  976. e.preventDefault();
  977. },
  978. /* ------^-------
  979. * /PASSWORD
  980. * ==============
  981. * FULLSCREEN
  982. * ------v------*/
  983. toggleFullscreen: function() {
  984. if (document.fullscreenElement || // alternative standard method
  985. document.mozFullScreenElement || // currently working methods
  986. document.webkitFullscreenElement ||
  987. document.msFullscreenElement) {
  988. if (document.exitFullscreen) {
  989. document.exitFullscreen();
  990. } else if (document.mozCancelFullScreen) {
  991. document.mozCancelFullScreen();
  992. } else if (document.webkitExitFullscreen) {
  993. document.webkitExitFullscreen();
  994. } else if (document.msExitFullscreen) {
  995. document.msExitFullscreen();
  996. }
  997. } else {
  998. if (document.documentElement.requestFullscreen) {
  999. document.documentElement.requestFullscreen();
  1000. } else if (document.documentElement.mozRequestFullScreen) {
  1001. document.documentElement.mozRequestFullScreen();
  1002. } else if (document.documentElement.webkitRequestFullscreen) {
  1003. document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  1004. } else if (document.body.msRequestFullscreen) {
  1005. document.body.msRequestFullscreen();
  1006. }
  1007. }
  1008. UI.enableDisableViewClip();
  1009. UI.updateFullscreenButton();
  1010. },
  1011. updateFullscreenButton: function() {
  1012. if (document.fullscreenElement || // alternative standard method
  1013. document.mozFullScreenElement || // currently working methods
  1014. document.webkitFullscreenElement ||
  1015. document.msFullscreenElement ) {
  1016. document.getElementById('noVNC_fullscreen_button')
  1017. .classList.add("noVNC_selected");
  1018. } else {
  1019. document.getElementById('noVNC_fullscreen_button')
  1020. .classList.remove("noVNC_selected");
  1021. }
  1022. },
  1023. /* ------^-------
  1024. * /FULLSCREEN
  1025. * ==============
  1026. * RESIZE
  1027. * ------v------*/
  1028. // Apply remote resizing or local scaling
  1029. applyResizeMode: function() {
  1030. if (!UI.rfb) return;
  1031. var screen = UI.screenSize();
  1032. if (screen && UI.connected && UI.rfb.get_display()) {
  1033. var display = UI.rfb.get_display();
  1034. var resizeMode = UI.getSetting('resize');
  1035. display.set_scale(1);
  1036. // Make sure the viewport is adjusted first
  1037. UI.updateViewClip();
  1038. if (resizeMode === 'remote') {
  1039. // Request changing the resolution of the remote display to
  1040. // the size of the local browser viewport.
  1041. // In order to not send multiple requests before the browser-resize
  1042. // is finished we wait 0.5 seconds before sending the request.
  1043. clearTimeout(UI.resizeTimeout);
  1044. UI.resizeTimeout = setTimeout(function(){
  1045. // Request a remote size covering the viewport
  1046. if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
  1047. Log.Debug('Requested new desktop size: ' +
  1048. screen.w + 'x' + screen.h);
  1049. }
  1050. }, 500);
  1051. } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
  1052. var downscaleOnly = resizeMode === 'downscale';
  1053. display.autoscale(screen.w, screen.h, downscaleOnly);
  1054. UI.fixScrollbars();
  1055. }
  1056. }
  1057. },
  1058. // Gets the the size of the available viewport in the browser window
  1059. screenSize: function() {
  1060. var screen = document.getElementById('noVNC_screen');
  1061. return {w: screen.offsetWidth, h: screen.offsetHeight};
  1062. },
  1063. // Normally we only apply the current resize mode after a window resize
  1064. // event. This means that when a new connection is opened, there is no
  1065. // resize mode active.
  1066. // We have to wait until the first FBU because this is where the client
  1067. // will find the supported encodings of the server. Some calls later in
  1068. // the chain is dependant on knowing the server-capabilities.
  1069. initialResize: function(rfb, fbu) {
  1070. UI.applyResizeMode();
  1071. // After doing this once, we remove the callback.
  1072. UI.rfb.set_onFBUComplete(function() { });
  1073. },
  1074. /* ------^-------
  1075. * /RESIZE
  1076. * ==============
  1077. * CLIPPING
  1078. * ------v------*/
  1079. // Set and configure viewport clipping
  1080. setViewClip: function(clip) {
  1081. UI.updateSetting('clip', clip);
  1082. UI.updateViewClip();
  1083. },
  1084. // Update parameters that depend on the clip setting
  1085. updateViewClip: function() {
  1086. if (!UI.rfb) return;
  1087. var display = UI.rfb.get_display();
  1088. var cur_clip = display.get_viewport();
  1089. var new_clip = UI.getSetting('clip');
  1090. var resizeSetting = UI.getSetting('resize');
  1091. if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
  1092. // Disable clipping if we are scaling
  1093. new_clip = false;
  1094. } else if (isTouchDevice) {
  1095. // Touch devices usually have shit scrollbars
  1096. new_clip = true;
  1097. }
  1098. if (cur_clip !== new_clip) {
  1099. display.set_viewport(new_clip);
  1100. }
  1101. var size = UI.screenSize();
  1102. if (new_clip && size) {
  1103. // When clipping is enabled, the screen is limited to
  1104. // the size of the browser window.
  1105. display.viewportChangeSize(size.w, size.h);
  1106. UI.fixScrollbars();
  1107. }
  1108. // Changing the viewport may change the state of
  1109. // the dragging button
  1110. UI.updateViewDrag();
  1111. },
  1112. // Handle special cases where clipping is forced on/off or locked
  1113. enableDisableViewClip: function() {
  1114. var resizeSetting = UI.getSetting('resize');
  1115. // Disable clipping if we are scaling, connected or on touch
  1116. if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
  1117. isTouchDevice) {
  1118. UI.disableSetting('clip');
  1119. } else {
  1120. UI.enableSetting('clip');
  1121. }
  1122. },
  1123. /* ------^-------
  1124. * /CLIPPING
  1125. * ==============
  1126. * VIEWDRAG
  1127. * ------v------*/
  1128. toggleViewDrag: function() {
  1129. if (!UI.rfb) return;
  1130. var drag = UI.rfb.get_viewportDrag();
  1131. UI.setViewDrag(!drag);
  1132. },
  1133. // Set the view drag mode which moves the viewport on mouse drags
  1134. setViewDrag: function(drag) {
  1135. if (!UI.rfb) return;
  1136. UI.rfb.set_viewportDrag(drag);
  1137. UI.updateViewDrag();
  1138. },
  1139. updateViewDrag: function() {
  1140. var clipping = false;
  1141. if (!UI.connected) return;
  1142. // Check if viewport drag is possible. It is only possible
  1143. // if the remote display is clipping the client display.
  1144. if (UI.rfb.get_display().get_viewport() &&
  1145. UI.rfb.get_display().clippingDisplay()) {
  1146. clipping = true;
  1147. }
  1148. var viewDragButton = document.getElementById('noVNC_view_drag_button');
  1149. if (!clipping &&
  1150. UI.rfb.get_viewportDrag()) {
  1151. // The size of the remote display is the same or smaller
  1152. // than the client display. Make sure viewport drag isn't
  1153. // active when it can't be used.
  1154. UI.rfb.set_viewportDrag(false);
  1155. }
  1156. if (UI.rfb.get_viewportDrag()) {
  1157. viewDragButton.classList.add("noVNC_selected");
  1158. } else {
  1159. viewDragButton.classList.remove("noVNC_selected");
  1160. }
  1161. // Different behaviour for touch vs non-touch
  1162. // The button is disabled instead of hidden on touch devices
  1163. if (isTouchDevice) {
  1164. viewDragButton.classList.remove("noVNC_hidden");
  1165. if (clipping) {
  1166. viewDragButton.disabled = false;
  1167. } else {
  1168. viewDragButton.disabled = true;
  1169. }
  1170. } else {
  1171. viewDragButton.disabled = false;
  1172. if (clipping) {
  1173. viewDragButton.classList.remove("noVNC_hidden");
  1174. } else {
  1175. viewDragButton.classList.add("noVNC_hidden");
  1176. }
  1177. }
  1178. },
  1179. /* ------^-------
  1180. * /VIEWDRAG
  1181. * ==============
  1182. * KEYBOARD
  1183. * ------v------*/
  1184. showVirtualKeyboard: function() {
  1185. if (!isTouchDevice) return;
  1186. var input = document.getElementById('noVNC_keyboardinput');
  1187. if (document.activeElement == input) return;
  1188. input.focus();
  1189. try {
  1190. var l = input.value.length;
  1191. // Move the caret to the end
  1192. input.setSelectionRange(l, l);
  1193. } catch (err) {} // setSelectionRange is undefined in Google Chrome
  1194. },
  1195. hideVirtualKeyboard: function() {
  1196. if (!isTouchDevice) return;
  1197. var input = document.getElementById('noVNC_keyboardinput');
  1198. if (document.activeElement != input) return;
  1199. input.blur();
  1200. },
  1201. toggleVirtualKeyboard: function () {
  1202. if (document.getElementById('noVNC_keyboard_button')
  1203. .classList.contains("noVNC_selected")) {
  1204. UI.hideVirtualKeyboard();
  1205. } else {
  1206. UI.showVirtualKeyboard();
  1207. }
  1208. },
  1209. onfocusVirtualKeyboard: function(event) {
  1210. document.getElementById('noVNC_keyboard_button')
  1211. .classList.add("noVNC_selected");
  1212. },
  1213. onblurVirtualKeyboard: function(event) {
  1214. document.getElementById('noVNC_keyboard_button')
  1215. .classList.remove("noVNC_selected");
  1216. },
  1217. keepVirtualKeyboard: function(event) {
  1218. var input = document.getElementById('noVNC_keyboardinput');
  1219. // Only prevent focus change if the virtual keyboard is active
  1220. if (document.activeElement != input) {
  1221. return;
  1222. }
  1223. // Only allow focus to move to other elements that need
  1224. // focus to function properly
  1225. if (event.target.form !== undefined) {
  1226. switch (event.target.type) {
  1227. case 'text':
  1228. case 'email':
  1229. case 'search':
  1230. case 'password':
  1231. case 'tel':
  1232. case 'url':
  1233. case 'textarea':
  1234. case 'select-one':
  1235. case 'select-multiple':
  1236. return;
  1237. }
  1238. }
  1239. event.preventDefault();
  1240. },
  1241. keyboardinputReset: function() {
  1242. var kbi = document.getElementById('noVNC_keyboardinput');
  1243. kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
  1244. UI.lastKeyboardinput = kbi.value;
  1245. },
  1246. // When normal keyboard events are left uncought, use the input events from
  1247. // the keyboardinput element instead and generate the corresponding key events.
  1248. // This code is required since some browsers on Android are inconsistent in
  1249. // sending keyCodes in the normal keyboard events when using on screen keyboards.
  1250. keyInput: function(event) {
  1251. if (!UI.rfb) return;
  1252. var newValue = event.target.value;
  1253. if (!UI.lastKeyboardinput) {
  1254. UI.keyboardinputReset();
  1255. }
  1256. var oldValue = UI.lastKeyboardinput;
  1257. var newLen;
  1258. try {
  1259. // Try to check caret position since whitespace at the end
  1260. // will not be considered by value.length in some browsers
  1261. newLen = Math.max(event.target.selectionStart, newValue.length);
  1262. } catch (err) {
  1263. // selectionStart is undefined in Google Chrome
  1264. newLen = newValue.length;
  1265. }
  1266. var oldLen = oldValue.length;
  1267. var backspaces;
  1268. var inputs = newLen - oldLen;
  1269. if (inputs < 0) {
  1270. backspaces = -inputs;
  1271. } else {
  1272. backspaces = 0;
  1273. }
  1274. // Compare the old string with the new to account for
  1275. // text-corrections or other input that modify existing text
  1276. var i;
  1277. for (i = 0; i < Math.min(oldLen, newLen); i++) {
  1278. if (newValue.charAt(i) != oldValue.charAt(i)) {
  1279. inputs = newLen - i;
  1280. backspaces = oldLen - i;
  1281. break;
  1282. }
  1283. }
  1284. // Send the key events
  1285. for (i = 0; i < backspaces; i++) {
  1286. UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
  1287. }
  1288. for (i = newLen - inputs; i < newLen; i++) {
  1289. UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
  1290. }
  1291. // Control the text content length in the keyboardinput element
  1292. if (newLen > 2 * UI.defaultKeyboardinputLen) {
  1293. UI.keyboardinputReset();
  1294. } else if (newLen < 1) {
  1295. // There always have to be some text in the keyboardinput
  1296. // element with which backspace can interact.
  1297. UI.keyboardinputReset();
  1298. // This sometimes causes the keyboard to disappear for a second
  1299. // but it is required for the android keyboard to recognize that
  1300. // text has been added to the field
  1301. event.target.blur();
  1302. // This has to be ran outside of the input handler in order to work
  1303. setTimeout(event.target.focus.bind(event.target), 0);
  1304. } else {
  1305. UI.lastKeyboardinput = newValue;
  1306. }
  1307. },
  1308. /* ------^-------
  1309. * /KEYBOARD
  1310. * ==============
  1311. * EXTRA KEYS
  1312. * ------v------*/
  1313. openExtraKeys: function() {
  1314. UI.closeAllPanels();
  1315. UI.openControlbar();
  1316. document.getElementById('noVNC_modifiers')
  1317. .classList.add("noVNC_open");
  1318. document.getElementById('noVNC_toggle_extra_keys_button')
  1319. .classList.add("noVNC_selected");
  1320. },
  1321. closeExtraKeys: function() {
  1322. document.getElementById('noVNC_modifiers')
  1323. .classList.remove("noVNC_open");
  1324. document.getElementById('noVNC_toggle_extra_keys_button')
  1325. .classList.remove("noVNC_selected");
  1326. },
  1327. toggleExtraKeys: function() {
  1328. if(document.getElementById('noVNC_modifiers')
  1329. .classList.contains("noVNC_open")) {
  1330. UI.closeExtraKeys();
  1331. } else {
  1332. UI.openExtraKeys();
  1333. }
  1334. },
  1335. sendEsc: function() {
  1336. UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
  1337. },
  1338. sendTab: function() {
  1339. UI.rfb.sendKey(KeyTable.XK_Tab);
  1340. },
  1341. toggleCtrl: function() {
  1342. var btn = document.getElementById('noVNC_toggle_ctrl_button');
  1343. if (btn.classList.contains("noVNC_selected")) {
  1344. UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
  1345. btn.classList.remove("noVNC_selected");
  1346. } else {
  1347. UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
  1348. btn.classList.add("noVNC_selected");
  1349. }
  1350. },
  1351. toggleAlt: function() {
  1352. var btn = document.getElementById('noVNC_toggle_alt_button');
  1353. if (btn.classList.contains("noVNC_selected")) {
  1354. UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
  1355. btn.classList.remove("noVNC_selected");
  1356. } else {
  1357. UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
  1358. btn.classList.add("noVNC_selected");
  1359. }
  1360. },
  1361. sendCtrlAltDel: function() {
  1362. UI.rfb.sendCtrlAltDel();
  1363. },
  1364. /* ------^-------
  1365. * /EXTRA KEYS
  1366. * ==============
  1367. * MISC
  1368. * ------v------*/
  1369. setMouseButton: function(num) {
  1370. var view_only = UI.rfb.get_view_only();
  1371. if (UI.rfb && !view_only) {
  1372. UI.rfb.get_mouse().set_touchButton(num);
  1373. }
  1374. var blist = [0, 1,2,4];
  1375. for (var b = 0; b < blist.length; b++) {
  1376. var button = document.getElementById('noVNC_mouse_button' +
  1377. blist[b]);
  1378. if (blist[b] === num && !view_only) {
  1379. button.classList.remove("noVNC_hidden");
  1380. } else {
  1381. button.classList.add("noVNC_hidden");
  1382. }
  1383. }
  1384. },
  1385. displayBlur: function() {
  1386. if (UI.rfb && !UI.rfb.get_view_only()) {
  1387. UI.rfb.get_keyboard().set_focused(false);
  1388. UI.rfb.get_mouse().set_focused(false);
  1389. }
  1390. },
  1391. displayFocus: function() {
  1392. if (UI.rfb && !UI.rfb.get_view_only()) {
  1393. UI.rfb.get_keyboard().set_focused(true);
  1394. UI.rfb.get_mouse().set_focused(true);
  1395. }
  1396. },
  1397. updateLocalCursor: function() {
  1398. if (!UI.rfb) return;
  1399. UI.rfb.set_local_cursor(UI.getSetting('cursor'));
  1400. },
  1401. updateViewOnly: function() {
  1402. if (!UI.rfb) return;
  1403. UI.rfb.set_view_only(UI.getSetting('view_only'));
  1404. },
  1405. updateLogging: function() {
  1406. WebUtil.init_logging(UI.getSetting('logging'));
  1407. },
  1408. updateSessionSize: function(rfb, width, height) {
  1409. UI.updateViewClip();
  1410. UI.fixScrollbars();
  1411. },
  1412. fixScrollbars: function() {
  1413. // This is a hack because Chrome screws up the calculation
  1414. // for when scrollbars are needed. So to fix it we temporarily
  1415. // toggle them off and on.
  1416. var screen = document.getElementById('noVNC_screen');
  1417. screen.style.overflow = 'hidden';
  1418. // Force Chrome to recalculate the layout by asking for
  1419. // an element's dimensions
  1420. screen.getBoundingClientRect();
  1421. screen.style.overflow = "";
  1422. },
  1423. updateDesktopName: function(rfb, name) {
  1424. UI.desktopName = name;
  1425. // Display the desktop name in the document title
  1426. document.title = name + " - noVNC";
  1427. },
  1428. bell: function(rfb) {
  1429. if (WebUtil.getConfigVar('bell', 'on') === 'on') {
  1430. document.getElementById('noVNC_bell').play()
  1431. .catch(function(e) {
  1432. if (e.name === "NotAllowedError") {
  1433. // Ignore when the browser doesn't let us play audio.
  1434. // It is common that the browsers require audio to be
  1435. // initiated from a user action.
  1436. } else {
  1437. Log.Error("Unable to play bell: " + e);
  1438. }
  1439. });
  1440. }
  1441. },
  1442. //Helper to add options to dropdown.
  1443. addOption: function(selectbox, text, value) {
  1444. var optn = document.createElement("OPTION");
  1445. optn.text = text;
  1446. optn.value = value;
  1447. selectbox.options.add(optn);
  1448. },
  1449. /* ------^-------
  1450. * /MISC
  1451. * ==============
  1452. */
  1453. };
  1454. // Set up translations
  1455. var LINGUAS = ["de", "el", "nl", "sv"];
  1456. l10n.setup(LINGUAS);
  1457. if (l10n.language !== "en" && l10n.dictionary === undefined) {
  1458. WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
  1459. l10n.dictionary = translations;
  1460. // wait for translations to load before loading the UI
  1461. UI.prime();
  1462. }, function (err) {
  1463. throw err;
  1464. });
  1465. } else {
  1466. UI.prime();
  1467. }
  1468. export default UI;