From 35ae7ff847065b241aab971e2bfbda34e0ac9c16 Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 26 Mar 2026 21:16:07 +0000 Subject: [PATCH] fix: kiosk cursor, Esc dead-end, PWA prompt, password overlay, gamepad Enter - Kiosk: show cursor when active (removed -nocursor from Xorg), unclutter hides after 3s idle. X11 on VT7 for Ctrl+Alt+F1/F7 switching. - Kiosk: keep getty@tty1 running so MOTD is accessible via Ctrl+Alt+F1 - Kiosk: disable Chromium password save overlay (--password-store=basic) - Esc: don't navigate back from top-level pages (dashboard, login, kiosk) to prevent dead-end at root redirect - PWA: suppress install prompt in kiosk mode (/kiosk path) - Gamepad: Enter in text fields moves focus to next element (submit button) instead of submitting the form Co-Authored-By: Claude Opus 4.6 (1M context) --- image-recipe/build-auto-installer-iso.sh | 12 +++++++---- neode-ui/src/components/PWAInstallPrompt.vue | 3 ++- neode-ui/src/composables/useControllerNav.ts | 22 +++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index d4e1fa02..3ad95385 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -1599,10 +1599,13 @@ CLAUDESVC # Not enabled by default; toggle via: sudo archipelago-kiosk enable/disable cat > /mnt/target/usr/local/bin/archipelago-kiosk-launcher <<'KIOSKLAUNCHER' #!/bin/bash -# Start X server in the background -/usr/bin/Xorg :0 -nocursor vt1 -nolisten tcp -keeptty & +# Start X server on VT7 (VT1 stays on MOTD/console) +/usr/bin/Xorg :0 vt7 -nolisten tcp -keeptty & XPID=$! -sleep 2 +sleep 3 + +# Switch to kiosk display +chvt 7 2>/dev/null || true if ! kill -0 $XPID 2>/dev/null; then echo 'ERROR: Xorg failed to start' @@ -1639,6 +1642,8 @@ while true; do --disable-session-crashed-bubble \ --disable-save-password-bubble \ --disable-suggestions-service \ + --password-store=basic \ + --disable-features=TranslateUI,PasswordManagerOnboarding \ --disable-component-update \ --user-data-dir=/home/archipelago/.config/chromium-kiosk sleep 3 @@ -1654,7 +1659,6 @@ Description=Archipelago Kiosk (X11 + Chromium) After=archipelago.service Wants=archipelago.service ConditionPathExists=/usr/local/bin/archipelago-kiosk-launcher -Conflicts=getty@tty1.service [Service] Type=simple diff --git a/neode-ui/src/components/PWAInstallPrompt.vue b/neode-ui/src/components/PWAInstallPrompt.vue index d2598d33..c744c209 100644 --- a/neode-ui/src/components/PWAInstallPrompt.vue +++ b/neode-ui/src/components/PWAInstallPrompt.vue @@ -43,7 +43,8 @@ let deferredPrompt: { prompt: () => Promise<{ outcome: string }> } | null = null const DISMISS_KEY = 'archipelago_pwa_install_dismissed' onMounted(() => { - // Don't show if already dismissed this session or if already installed + // Don't show in kiosk mode, if already dismissed, or if already installed + if (window.location.pathname.startsWith('/kiosk')) return if (sessionStorage.getItem(DISMISS_KEY) === '1') return if (window.matchMedia('(display-mode: standalone)').matches) return if ((window.navigator as Navigator & { standalone?: boolean }).standalone) return diff --git a/neode-ui/src/composables/useControllerNav.ts b/neode-ui/src/composables/useControllerNav.ts index 76d1939f..1b6d05ab 100644 --- a/neode-ui/src/composables/useControllerNav.ts +++ b/neode-ui/src/composables/useControllerNav.ts @@ -149,6 +149,18 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) { const target = e.target as HTMLElement if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { + // Enter in text field: blur and move to next focusable element (e.g., submit button) + if (e.key === 'Enter' && target.tagName === 'INPUT' && (target as HTMLInputElement).type !== 'submit') { + e.preventDefault() + const root = containerRef?.value ?? document + const all = getFocusableElements(root) + const idx = all.indexOf(target as HTMLElement) + if (idx >= 0 && idx < all.length - 1) { + all[idx + 1].focus() + all[idx + 1].scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + } + return + } if (e.key !== 'Escape') return } @@ -207,9 +219,13 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) { return } - playNavSound('back') - window.history.back() - e.preventDefault() + // Don't navigate back from top-level pages — it leads to a dead end + const topLevel = ['/', '/dashboard', '/login', '/kiosk'] + if (!topLevel.some(p => route.path === p || route.path.startsWith('/dashboard'))) { + playNavSound('back') + window.history.back() + e.preventDefault() + } return }