feat(tv-input): gamepad->keyboard bridge daemon + controller-navigable modals

- archipelago-gamepad-keys: evdev->uinput daemon (pure python3 stdlib) that
  mirrors any attached controller as a virtual keyboard — D-pad/stick to
  arrows, A/B to Enter/Escape, X/Y to Space/f, shoulders to Tab/Shift+Tab.
  The browser sees real keys, so controllers work inside EVERY app iframe
  (IndeeHub, Jellyfin…) with zero per-app code. Ships via the ISO splice and
  bootstrap self-heal (kiosk nodes only), hotplug via 5s rescans.
  Implements docs/tv-input-iframe-apps.md.
- useControllerNav: an open [role=dialog][aria-modal] owns navigation —
  focus is pulled inside, arrows move spatially between its controls, Enter
  activates, Escape stays with the modal's own close handling. One standard
  mapping for every modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-23 16:17:11 -04:00
co-authored by Claude Fable 5
parent 402183c0ae
commit 54ddd043bd
6 changed files with 355 additions and 0 deletions
@@ -2913,6 +2913,33 @@ fi
echo "AUDIOSVC"
} >> "$ARCH_DIR/auto-install.sh"
# Gamepad->keyboard bridge: same pattern (configs/ single source of truth,
# also self-healed via the binary). TV input for every app iframe.
GAMEPAD_SRC="$SCRIPT_DIR/../configs/archipelago-gamepad-keys.py"
GAMEPAD_SERVICE_SRC="$SCRIPT_DIR/../configs/archipelago-gamepad-keys.service"
for _pad_src in "$GAMEPAD_SRC" "$GAMEPAD_SERVICE_SRC"; do
if [ ! -f "$_pad_src" ]; then
echo "ERROR: gamepad config file missing: $_pad_src" >&2
exit 1
fi
done
if grep -qx 'GAMEPADPY' "$GAMEPAD_SRC" || grep -qx 'GAMEPADSVC' "$GAMEPAD_SERVICE_SRC"; then
echo "ERROR: gamepad config contains a reserved heredoc terminator line (GAMEPADPY/GAMEPADSVC)" >&2
exit 1
fi
{
echo ""
echo "# Gamepad->keyboard bridge — controller input inside every app iframe on the TV."
echo "cat > /mnt/target/usr/local/bin/archipelago-gamepad-keys <<'GAMEPADPY'"
cat "$GAMEPAD_SRC"
echo "GAMEPADPY"
echo "chmod +x /mnt/target/usr/local/bin/archipelago-gamepad-keys"
echo ""
echo "cat > /mnt/target/etc/systemd/system/archipelago-gamepad-keys.service <<'GAMEPADSVC'"
cat "$GAMEPAD_SERVICE_SRC"
echo "GAMEPADSVC"
} >> "$ARCH_DIR/auto-install.sh"
cat >> "$ARCH_DIR/auto-install.sh" <<'INSTALLER_SCRIPT'
# Toggle script: sudo archipelago-kiosk enable|disable|status
@@ -3293,6 +3320,7 @@ chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null ||
chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true
chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true
chroot /mnt/target systemctl enable archipelago-audio-router.service 2>/dev/null || true
chroot /mnt/target systemctl enable archipelago-gamepad-keys.service 2>/dev/null || true
chroot /mnt/target systemctl enable nostr-vpn.service 2>/dev/null || true
# Enable claude-api-proxy (create symlink manually — chroot systemctl can fail)
chroot /mnt/target systemctl enable claude-api-proxy.service 2>/dev/null || \