Skip to content

Instantly share code, notes, and snippets.

@rbreaves
Created December 17, 2025 13:47
Show Gist options
  • Select an option

  • Save rbreaves/5872b20d68ab513520b4b7d258fbc26f to your computer and use it in GitHub Desktop.

Select an option

Save rbreaves/5872b20d68ab513520b4b7d258fbc26f to your computer and use it in GitHub Desktop.
Kasm enhancement
// ==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