Skip to content

Add Kid Mode launcher for Onion OS - #1

Open
daverad wants to merge 38 commits into
mainfrom
claude/onion-kid-mode-launcher-psewag
Open

Add Kid Mode launcher for Onion OS#1
daverad wants to merge 38 commits into
mainfrom
claude/onion-kid-mode-launcher-psewag

Conversation

@daverad

@daverad daverad commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Introduces Kid Mode, a fullscreen favorites-only launcher that locks a Miyoo Mini/Mini+ so a young child can use it unsupervised. Integrates with Onion OS 4.3+ via a startup hook (no MainUI patching, no SD-card reorganization) and adds parental controls: a 4-digit PIN, a per-session play timer, and a RetroArch kiosk lock.

How it works

  • Boot hook (kidmode_boot.sh, self-installed to .tmp_update/startup/ on first arm): runs before MainUI launches. While /mnt/SDCARD/.kidmode exists, it blocks in the kid launcher loop — MainUI only starts after a PIN unlock removes the flag. Deleting the flag file from a computer always restores normal Onion.

  • Kid UI (src/kidsMode/kidui.c, SDL): fullscreen carousel of Onion's own favorites (Roms/favourite.json) with box art, big labels, and left/right browsing; A launches. Holding SELECT+START 3 s opens the PIN pad; a correct PIN opens the parent menu (Exit Kid Mode / +5 minutes / Timer / Back). Accent colors are read from the active Onion theme's config. Box art is scaled by an in-house bilinear scaler (the shipped libSDL_rotozoom flips zoomed surfaces on-device). Results are passed to the shell via /tmp/kidmode_ui_result, not stdout, which carries SDL/driver noise on hardware.

  • Orchestration (kid_mode_loop.sh, ash): arming asks to set a PIN (salted SHA-256 in kidmode.json, plaintext-reset escape hatch) and shows a session timer picker (OFF / 5–50 min in 5-min steps, default OFF; each arm starts a fresh budget). Games launch exactly as runtime.sh does — same command format, per-game core overrides, playActivity tracking, V4 560p handling — so auto-save/resume is unchanged and game exits return to the kid UI. Handles /tmp/.offOrder for clean power-off while the runtime loop is blocked.

  • Play timer: a background ticker counts consumed seconds (sleep pauses it; reboot doesn't reset it), persists state in timer_state.txt, overlays a persistent remaining-time chip in the top-left corner during gameplay plus 3/2/1-minute warning badges (imgpop), and at zero sends RetroArch a network QUIT so Onion's auto-save snapshots the exact spot before a friendly "Time's up!" screen. Non-RetroArch games get a best-effort TERM.

  • RetroArch kiosk lock while armed (settings/menus hidden; config backed up and restored on unlock).

  • Build workflow (.github/workflows/build-kidmode.yml): cross-compiles kidui with the miyoomini toolchain container and commits the ARM binary to kidmode-sd/, which holds the ready-to-copy SD payload (kidmode-install.zip at the repo root).

Safety properties

  • No SD-card reorganization: only the flag file, RetroArch config (with backup), timer state, and an optional favorites entry are touched; roms, saves, and folder structure are untouched.
  • Fail-open: a missing or repeatedly crashing kid UI disarms Kid Mode instead of brick-looping; everything is recoverable by deleting files from a computer.
  • Favorites hygiene: an earlier revision could glue its optional Favorites-tab shortcut onto a final line lacking a newline, corrupting favourite.json and breaking MainUI search results — the loop now repairs glued lines automatically, appends newline-safely, and the shortcut is opt-in ("fav_shortcut": true), default off.
  • Threat model: a curious ~6-year-old, not an adversary. A long power press still force-shuts-down; v2 hardening (keymon patch gated on the flag file) is designed and documented but out of scope.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc

claude and others added 30 commits July 12, 2026 15:59
A fullscreen kid-safe launcher for Onion OS that locks the device to
favorites only:

- src/kidsMode/kidui.c: SDL carousel (box art, d-pad nav, A to play),
  SELECT+START 3s hold opens a 4-digit PIN pad; reads Onion's own
  Roms/favourite.json
- kidmode-sd/App/KidsMode: arm app for the Apps tab + ash play loop that
  launches games the same way runtime.sh does (core overrides,
  playActivity, V4 560p, save/resume) and returns to the kid UI on exit
- kidmode-sd/.tmp_update/startup/kidmode_boot.sh: boot hook that blocks
  before MainUI while /mnt/SDCARD/.kidmode exists
- RetroArch kiosk lock while armed (backup/restore), salted-SHA256 PIN
  with plaintext-reset escape hatch, fail-open recovery, no directory
  moves on the SD card
- .github/workflows/build-kidmode.yml: cross-compiles kidui with the
  miyoomini toolchain and commits the ARM binary to kidmode-sd

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
The startup hook now ships inside App/KidsMode/ and is copied into
.tmp_update/startup/ automatically on arm, so installing is just
dropping one visible folder onto the card (no manual edits inside the
hidden .tmp_update folder, which is easy to clobber from a computer).
Adds kidmode-install.zip with the ready-to-copy SD payload. [skip ci]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
- Optional daily play budget in 5-minute steps (timer counts consumed
  play time, not wall clock: sleep pauses it, reboot doesn't reset it,
  resets per day). Warning badges overlay the running game at 3/2/1
  minutes left; at zero RetroArch gets a graceful network QUIT so
  Onion's auto-save preserves the exact spot, then kidui shows a
  friendly Time's-up screen
- Parent menu behind the PIN: Exit Kid Mode / +5 minutes today /
  Timer per day (5-min steps) / Back; remaining-time chip on the
  carousel
- 'Kid Mode' entry in Onion's Favorites tab for one-tap arming
  (filtered out of the kid carousel; fav_shortcut config to disable)
- Fix config_get swallowing boolean false (jq // empty pitfall) and
  store_pin overwriting non-PIN config keys

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
On device, the SDL/driver stack prints messages to stdout before
anything kidui writes, so the shell's first-line protocol parsing
failed and every PIN confirmation looked like a cancel. kidui now
writes results to /tmp/kidmode_ui_result and the loop reads that;
kidui stdout goes to /tmp/kidmode_ui_log for debugging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
…ent, unlock handoff, favorites corruption

- Arming now asks for a session timer (OFF / 5-50 min in 5-min steps,
  default OFF) and starts a fresh budget each time
- Persistent remaining-time chip overlays the top-left corner during
  gameplay (per-minute PNGs via imgpop), plus existing 3/2/1 badges
- Box art rendered upside down on device: the shipped libSDL_rotozoom
  flips zoomed surfaces, so scale artwork with an in-house bilinear
  scaler and drop the rotozoom dependency (orientation covered by test)
- PIN pad and highlights now use the active Onion theme's accent color
  (currentpage/grid/title color from the theme config, amber fallback)
- After unlock, MainUI could paint an invisible framebuffer page; drop
  kidui's final flip and reset the display with 'bootScreen clear' on
  every hand-back path
- favourite.json shortcut could glue onto a final line lacking a
  newline, corrupting the list and breaking MainUI search results:
  repair glued lines automatically, append newline-safely, and make
  fav_shortcut opt-in (default off)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
- Box art was still upside down on device: images load 180-degree
  rotated relative to text rendering on this hardware, which Onion
  corrects by rotating every loaded image (theme_backgroundLoad).
  Do the same for artwork; rects/text stay unrotated
- PIN entry: A now advances to the next digit instead of submitting
  (too easy to submit a half-entered PIN); only START confirms
- Wrong PIN now shows a brief 'Wrong PIN' message instead of silently
  returning to the carousel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
The Search results section needs App/Search/data/ with its anchor
.miyoocmd file present; recreating it repairs an 'Empty' results
screen after the folder was lost or damaged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
imgpop overlays are erased by RetroArch's per-frame redraw, so the
in-game countdown was invisible. Use RA's SHOW_MSG network command
instead: budget announcement at game start, '<N> minutes left' every
10 minutes and each minute from 5 down, and a save notice at zero.
The corner chip remains for non-RetroArch content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
imgpop overlays draw in panel-native coordinates (180 degrees rotated
from the viewed image) and are erased by RetroArch's per-frame redraw,
producing only a brief flipped flash in the corner. Drop all in-game
imgpop overlays and their PNG assets; the countdown now uses RA's OSD
exclusively: budget announcement at game start, every 5 minutes, and
every minute from 5 down. Force video_font_enable while armed so the
messages always render.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
The previous format run raced with a concurrent push and tried to
auto-commit a stale-checkout diff; code is clang-format clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
RA OSD messages last ~3s; re-pushing the same text every 2s renders as
one continuous message. The last minute now stays pinned ('1 minute
left!') until the graceful save-and-quit; earlier warnings remain
once-per-minute flashes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
- Parent menu 'Add play time' now opens the same horizontal minutes
  picker as arming (5-50, B cancels), shows 'Added N minutes! Time
  left: M minutes', and returns straight to the kid launcher
- While armed, override keymap ingame_single_press to 'exit to menu'
  (backup/restore + keymon restart) so the MENU button saves and quits
  to the kid launcher instead of opening the GameSwitcher overlay,
  which could expose the parent's recent games

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
X on the selected game asks 'Start over?'; A launches from the
beginning by skipping the auto-resume snapshot (savestate_auto_load
off via appendconfig, the same mechanism runtime.sh uses for its
reset-game flag; in-game battery saves untouched). Injection into the
emulator launch script is cleaned up after the run and defensively on
every build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
claude and others added 8 commits July 15, 2026 04:20
A default-title if statement inserted mid-chain in ec07d54 detached
the final else branch that loads favourite.json, so the carousel
started with zero games: a lone fallback '?' tile, all input ignored,
no exit and nothing logged (a hang, not a crash, so fail-open never
fired). Restore the chain and add a stderr marker asserting favorites
load in carousel mode.

Also move the RetroArch/keymap backups and timer state from
App/KidsMode (where a folder-replace update deletes them, orphaning a
kiosk-locked config) to Saves/kidmode/, with legacy-path fallback on
restore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
…een intro

- Remove all daily-timer semantics: no day-based reset (a new arm or
  added time is the only refill), parent menu header says 'Time left'
- Parent menu is now Exit / Add play time / Back (the Timer row was
  redundant with the arm-time picker)
- Add play time shows a full receipt: added (new total), played so
  far, and time remaining, then returns straight to the kid screen
- App tile renamed to 'Kids Mode' with subtitle 'Curate Games, Simple
  UI, Timer...'; user-visible strings updated to match
- PIN-creation screen now notes that Kids Mode shows only favorited
  games, lists the feature set, and explains the SELECT+START parent
  menu gesture

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
…[skip ci]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtJHyTgJ1TdmqhZiW5qotc
Sign up for free to 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.

2 participants