') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Web backend: run ManaTools AUI apps in a web browser by xquiet · Pull Request #7 · manatools/python-manatools · GitHub
Skip to content

Web backend: run ManaTools AUI apps in a web browser - #7

Open
xquiet wants to merge 97 commits into
mainfrom
web_backend
Open

Web backend: run ManaTools AUI apps in a web browser#7
xquiet wants to merge 97 commits into
mainfrom
web_backend

Conversation

@xquiet

Copy link
Copy Markdown

This branch adds a fourth UI backend to the AUI layer: applications written
against manatools can now be served in a web browser with no code
changes
- the same source runs on Qt, GTK, ncurses, or web.

What's included

  • New web backend (manatools/aui/backends/web/): renders the widget
    tree as HTML and serves it over HTTP, with real-time updates and events over
    WebSocket. Pure Python standard library without extra dependencies;
  • All standard widgets supported: dialogs (main + popup modals), layouts,
    labels, buttons, input fields, check/radio boxes, combo/selection boxes,
    frames, progress bars, sliders, tables, trees, rich text, menu bars, images,
    int/date/time fields, multi-line edit, log view, dumb tabs, paned splitter,
    spacing, alignment, replace points;
  • Backend selection: MUI_BACKEND=web|qt|gtk|ncurses environment
    variable override; the web backend is opt-in only and it's never
    auto-selected;
  • Security hardening: CSP and standard security headers on
    every response, localhost-only binding, origin checks on WebSocket upgrades
    and the /event POST fallback, frame/body size caps, path-traversal guards
    on static file and icon serving;
  • Browser UX: Bootstrap 5.3 frontend, XDG icon lookup served from system
    themes, deferred skeleton rendering for heavy widgets (table/tree),
    coalesced server thus browser DOM updates;
  • Qt backend robustness fixes: widgets now initialize their attributes
    before super().__init__() so containers that trigger backend-widget
    creation mid-construction (e.g. YDumbTab) no longer crash; popup dialog
    lifecycle fixes; guarded _items access in table/tree rebuilds.

How to test

bash
MUI_BACKEND=web python3 test/test_hello_world.py

The application prints its URL (http://127.0.0.1:8080/) - open it in a
modern browser. The same test runs unchanged under MUI_BACKEND=qt,
gtk, or ncurses.

Notes

  • The web backend binds to 127.0.0.1 only; file dialogs and system tray are
    not supported in the browser but they seems to degrade gracefully;
  • This branch also carries the main fixes/features merged in (tooltips,
    paned multi-layout, backend detection improvements), so it's ready to
    fast-forward main, local testing show no conflicts.

xquiet added 30 commits March 28, 2026 23:44
Links inside YRichText now behave like Qt/GTK: browser navigation is
blocked, the URL is stored in widget._last_url, and a YMenuEvent(id=url)
is posted to the dialog queue when notify() is True.
xquiet added 30 commits April 6, 2026 20:24
…o build
Resolved yui.py conflict: keep MUI_BACKEND/XDG detection, take main's
DISPLAY/WAYLAND and PKEXEC_UID display-recovery steps, web backend stays
explicit-only. treeqt.py merged both the init-before-super ordering and
tooltip support.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@xquiet