Created
December 17, 2025 13:47
-
-
Save rbreaves/5872b20d68ab513520b4b7d258fbc26f to your computer and use it in GitHub Desktop.
Kasm enhancement
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Android Keyboard Keep-Alive + noVNC (url sessions) | |
| // @match https://url.co/* | |
| // @run-at document-end | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| /* ========================= | |
| URL GUARD (HASH-AWARE) | |
| ========================= */ | |
| function isValidSessionUrl() { | |
| return location.hostname === 'url.co' | |
| && location.hash.startsWith('#/session/'); | |
| } | |
| if (!isValidSessionUrl()) return; | |
| // If SPA navigation changes the hash later, we still want to activate | |
| window.addEventListener('hashchange', () => { | |
| if (!isValidSessionUrl()) return; | |
| }); | |
| /* ========================= | |
| CONFIG | |
| ========================= */ | |
| const REFRESH_MS = 250; | |
| const FORCE_NOVNC_BAR_VISIBLE = true; | |
| const NOVNC_ID = 'noVNC_control_bar_anchor'; | |
| const DONT_STEAL_FROM_REAL_INPUTS = true; | |
| const CLICK_TO_REFOCUS = true; | |
| /* ========================= | |
| KEYBOARD KEEP-ALIVE | |
| ========================= */ | |
| const catcher = document.createElement('input'); | |
| catcher.type = 'text'; | |
| catcher.autocomplete = 'off'; | |
| catcher.autocapitalize = 'off'; | |
| catcher.spellcheck = false; | |
| catcher.inputMode = 'text'; | |
| catcher.style.position = 'fixed'; | |
| catcher.style.left = '0'; | |
| catcher.style.bottom = '0'; | |
| catcher.style.width = '1px'; | |
| catcher.style.height = '1px'; | |
| catcher.style.opacity = '0.001'; | |
| catcher.style.zIndex = '2147483647'; | |
| catcher.style.pointerEvents = 'none'; | |
| document.documentElement.appendChild(catcher); | |
| function isEditable(el) { | |
| if (!el) return false; | |
| if (el.isContentEditable) return true; | |
| const tag = (el.tagName || '').toLowerCase(); | |
| if (tag === 'textarea') return true; | |
| if (tag === 'input') { | |
| const t = (el.type || '').toLowerCase(); | |
| return ![ | |
| 'button','submit','reset','checkbox', | |
| 'radio','file','image','range','color' | |
| ].includes(t); | |
| } | |
| return false; | |
| } | |
| function safeFocus() { | |
| if (DONT_STEAL_FROM_REAL_INPUTS && isEditable(document.activeElement)) return; | |
| try { catcher.focus({ preventScroll: true }); } | |
| catch { try { catcher.focus(); } catch {} } | |
| } | |
| window.addEventListener('blur', safeFocus, true); | |
| document.addEventListener('focusin', e => { | |
| if (DONT_STEAL_FROM_REAL_INPUTS && isEditable(e.target)) return; | |
| setTimeout(safeFocus, 0); | |
| }, true); | |
| if (CLICK_TO_REFOCUS) { | |
| document.addEventListener('touchend', e => { | |
| if (DONT_STEAL_FROM_REAL_INPUTS && isEditable(e.target)) return; | |
| safeFocus(); | |
| }, { capture: true, passive: true }); | |
| document.addEventListener('click', e => { | |
| if (DONT_STEAL_FROM_REAL_INPUTS && isEditable(e.target)) return; | |
| safeFocus(); | |
| }, true); | |
| } | |
| setInterval(safeFocus, REFRESH_MS); | |
| /* ========================= | |
| noVNC CONTROL BAR UNHIDE | |
| ========================= */ | |
| function forceNoVNCVisible() { | |
| if (!FORCE_NOVNC_BAR_VISIBLE) return; | |
| const bar = document.getElementById(NOVNC_ID); | |
| if (!bar) return; | |
| bar.style.display = 'block'; | |
| bar.style.visibility = 'visible'; | |
| bar.style.opacity = '1'; | |
| bar.style.pointerEvents = 'auto'; | |
| bar.style.zIndex = '2147483646'; | |
| } | |
| forceNoVNCVisible(); | |
| setInterval(forceNoVNCVisible, 500); | |
| const observer = new MutationObserver(forceNoVNCVisible); | |
| observer.observe(document.documentElement, { | |
| childList: true, | |
| subtree: true, | |
| attributes: true, | |
| attributeFilter: ['style', 'class'] | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment