diff --git a/app/styles/base.css b/app/styles/base.css index 5e71ae73..b0f6c7ad 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -791,7 +791,6 @@ html { list-style: none; padding: 0px; } -#noVNC_settings button, #noVNC_settings select, #noVNC_settings textarea, #noVNC_settings input:not([type=checkbox]):not([type=radio]) { @@ -799,6 +798,10 @@ html { /* Prevent inputs in settings from being too wide */ max-width: calc(100% - 6px - var(--input-xpadding) * 2); } +#noVNC_settings button:not(#noVNC_ignore_keys_help_button) { + margin-left: 6px; + max-width: calc(100% - 6px - var(--input-xpadding) * 2); +} #noVNC_setting_port { width: 80px; @@ -822,6 +825,83 @@ html { display: none; } +/* Help tooltips */ +.noVNC_setting_with_help { + position: relative; + display: flex; + align-items: center; + gap: 0.4rem; +} + +.noVNC_setting_with_help label { + flex: 0 1 auto; +} + +#noVNC_settings .noVNC_tooltip_list { + margin: 0.4rem 0 0 0; + padding-left: 0.8rem; + list-style: disc; +} + +.noVNC_tooltip_list li { + margin: 0.15rem 0; +} + +#noVNC_setting_ignore_keys::placeholder { + color: var(--novnc-grey); + opacity: 0.7; +} + +#noVNC_setting_ignore_keys.noVNC_invalid, +#noVNC_setting_ignore_keys.noVNC_invalid:focus { + border-color: var(--novnc-red); + box-shadow: 0 0 2px rgba(var(--novnc-red-rgb), 0.5); +} + +#noVNC_ignore_keys_help_button { + flex: 0 0 auto; + + width: 1.6rem; + height: 1.6rem; + min-width: 1.6rem; + max-width: 1.6rem; + + padding: 0; + margin: 0; + + border-radius: 50%; + background: transparent; + border: 1px solid rgba(0,0,0,0.25); + cursor: pointer; + + display: inline-flex; + align-items: center; + justify-content: center; + + line-height: 1; +} + +#noVNC_ignore_keys_tooltip { + display: none; + position: absolute; + z-index: 1000; + left: 1rem; + padding: 0.5rem; + margin: 0; + border-radius: 0.4rem; + background: rgba(20, 20, 20, 0.96); + color: #fff; + box-shadow: 0 0.4rem 1rem rgba(0, 0, 0, 0.35); + font-size: 0.75rem; + line-height: .9rem; +} + +#noVNC_ignore_keys_tooltip.noVNC_open { + display: flex !important; + flex-direction: column; + align-items: flex-start; +} + /* ---------------------------------------- * Status dialog * ---------------------------------------- diff --git a/app/styles/constants.css b/app/styles/constants.css index 1123a3ef..42239b2f 100644 --- a/app/styles/constants.css +++ b/app/styles/constants.css @@ -21,6 +21,8 @@ --novnc-green: rgb(0, 128, 0); --novnc-yellow: rgb(255, 255, 0); + --novnc-red-rgb: 229, 57, 53; + --novnc-red: rgb(var(--novnc-red-rgb)); } /* ------ MISC PROPERTIES ------ */ diff --git a/app/ui.js b/app/ui.js index 6e4af294..b594f997 100644 --- a/app/ui.js +++ b/app/ui.js @@ -129,6 +129,7 @@ const UI = { UI.addConnectionControlHandlers(); UI.addClipboardHandlers(); UI.addSettingsHandlers(); + UI.initIgnoreKeysTooltip(); document.getElementById("noVNC_status") .addEventListener('click', UI.hideStatus); @@ -196,6 +197,7 @@ const UI = { UI.initSetting('reconnect', false); UI.initSetting('reconnect_delay', 5000); UI.initSetting('keep_device_awake', false); + UI.initSetting('ignore_keys', ''); }, // Adds a link to the label elements on the corresponding input elements setupSettingLabels() { @@ -388,6 +390,13 @@ const UI = { UI.addSettingChangeHandler('logging', UI.updateLogging); UI.addSettingChangeHandler('reconnect'); UI.addSettingChangeHandler('reconnect_delay'); + UI.addSettingChangeHandler('ignore_keys'); + const input = document.getElementById('noVNC_setting_ignore_keys'); + if (input && !input.dataset.validationBound) { + input.addEventListener('input', UI.validateIgnoreKeysInput); + input.addEventListener('blur', UI.validateIgnoreKeysInput); + input.dataset.validationBound = 'true'; + } }, addFullscreenHandlers() { @@ -875,13 +884,28 @@ const UI = { } } } else { - ctrl.value = value; + ctrl.value = value ?? ''; } }, - // Save control setting to cookie - saveSetting(name) { - const ctrl = document.getElementById('noVNC_setting_' + name); + // Update cookie and form control setting. If value is not set, then + // updates from control to current cookie setting. + saveSetting(nameOrEvent) { + let ctrl = null; + + if (typeof nameOrEvent === 'string') { + ctrl = document.getElementById('noVNC_setting_' + nameOrEvent); + } else if (nameOrEvent && nameOrEvent.target) { + ctrl = nameOrEvent.target; + } else if (this instanceof Element) { + ctrl = this; + } + + if (!ctrl) { + Log.Warn("saveSetting called without valid control"); + return null; + } + let val; if (ctrl.type === 'checkbox') { val = ctrl.checked; @@ -890,6 +914,17 @@ const UI = { } else { val = ctrl.value; } + + let name = ctrl.id; + if (name && name.startsWith('noVNC_setting_')) { + name = name.slice('noVNC_setting_'.length); + } + + if (!name) { + Log.Warn("saveSetting could not determine setting name"); + return val; + } + WebUtil.writeSetting(name, val); //Log.Debug("Setting saved '" + name + "=" + val + "'"); return val; @@ -969,6 +1004,7 @@ const UI = { UI.updateSetting('logging'); UI.updateSetting('reconnect'); UI.updateSetting('reconnect_delay'); + UI.updateSetting('ignore_keys'); document.getElementById('noVNC_settings') .classList.add("noVNC_open"); @@ -1166,6 +1202,8 @@ const UI = { return; } + UI.wrapRfbSendKey(); + UI.rfb.addEventListener("connect", UI.connectFinished); UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("serververification", UI.serverVerify); @@ -1666,6 +1704,25 @@ const UI = { UI.rfb.sendKey(keysym, code, down); }, + wrapRfbSendKey() { + if (!UI.rfb || !UI.rfb.sendKey || UI.rfb._ignoreKeysWrapped) { + return; + } + + const originalSendKey = UI.rfb.sendKey.bind(UI.rfb); + + UI.rfb.sendKey = function (keysym, code, down) { + if (UI.shouldIgnoreKey(code)) { + Log.Debug("Key ignored: " + code); + return; + } + + return originalSendKey(keysym, code, down); + }; + + UI.rfb._ignoreKeysWrapped = true; + }, + // When normal keyboard events are left uncought, use the input events from // the keyboardinput element instead and generate the corresponding key events. // This code is required since some browsers on Android are inconsistent in @@ -1937,6 +1994,170 @@ const UI = { selectbox.options.add(optn); }, + supportedIgnoreKeys: [ + { label: 'Escape', aliases: ['esc', 'escape'] }, + { label: 'Tab', aliases: ['tab'] }, + { label: 'Enter', aliases: ['enter', 'return'] }, + { label: 'Delete', aliases: ['del', 'delete'] }, + { label: 'Backspace', aliases: ['bs', 'backspace'] }, + { label: 'ControlLeft', aliases: ['ctrl', 'ctl', 'controlleft'] }, + { label: 'AltLeft', aliases: ['alt', 'altleft'] }, + { label: 'MetaLeft', aliases: ['win', 'cmd', 'super', 'metaleft'] }, + ], + + buildIgnoreKeysTooltipText() { + return `