Skip to content

feat: parental control - a daily play time budget - #809

Draft
dalexanco wants to merge 3 commits into
LoveRetro:mainfrom
dalexanco:feat/parental-control
Draft

feat: parental control - a daily play time budget#809
dalexanco wants to merge 3 commits into
LoveRetro:mainfrom
dalexanco:feat/parental-control

Conversation

@dalexanco

@dalexanco dalexanco commented Aug 15, 2026

Copy link
Copy Markdown

Summary

Adds a Parental.pak — a daily play-time budget for the console, with a
button-code-locked settings screen so a child can't just turn it off.

screen-restricted screen-code screen-menu screen-dailylimite

feat(parental): daily play time budget

Description (for parents)

Parental.pak lets you cap how long the console can be played each day.

  • Open Tools → Parental to see time left today ("42m left today") and
    time already played.
  • Selecting Restricted Access and entering a button code opens the admin
    menu, where you can:
    • Daily limit — set an hours/minutes budget, or turn it off entirely.
    • Emulators — exempt specific emulator paks from the budget (e.g. keep
      a "study" emulator or app always available). Everything is blocked by
      default; only what you explicitly allow is exempt.
    • Change code — replace the unlock code with your own button sequence.
  • The default unlock code is UP, UP, DOWN, DOWN; it's meant to be changed.
  • Once the daily budget runs out, launching a game shows "No play time left
    today" and refuses to start. While playing, the console warns "Stopping in
    5 min" shortly before time runs out, then stops the game after a 3-second
    on-screen notice — so a child isn't cut off with zero warning mid-session.
  • Play time isn't tracked separately: it reuses whatever the console already
    logged as play activity, from midnight (local time) onward.

Technical description

New files

  • workspace/all/parental/parental.c (+ makefile) — single binary,
    built for tg5040/tg5050, four modes selected by argv:
    • (no args) — SDL2 UI: home screen, PIN/code entry, admin menu (limit,
      exemptions, change code), limit editor, exemption list, new-code capture.
    • --gate — invoked synchronously from the pre-launch hook; exits 1 to
      veto the launch when the daily budget is exhausted, 0 otherwise.
    • --watch — backgrounded by the gate hook after a launch is allowed;
      polls every WATCH_INTERVAL_SEC (10s), warns via NOTIFY_PATH +
      SIGUSR2 to minarch.elf at warn_minutes remaining, and force-stops
      the game (SIGUSR2 notice, 3s pause, SIGUSR1) when the budget hits 0.
      Also self-terminates if minarch.elf isn't running anymore (safety net
      in case the post-launch hook didn't fire).
    • --stop — invoked from the post-launch hook; kills the --watch
      process via its PID file (/tmp/parental.pid).
  • skeleton/EXTRAS/Tools/{tg5040,tg5050}/Parental.pak/launch.sh — on open,
    (re)installs the two hook scripts below into
    $HOOKS_PATH/{pre-launch,post-launch}.d/, then launches the settings UI.
    This is the only moment a Tools pak runs, so opening it once is what arms
    enforcement.
  • .../Parental.pak/hooks/parental-gate.sync.sh — pre-launch hook (must
    keep the .sync.sh suffix: only synchronous pre-launch hooks can veto a
    launch via non-zero exit). No-ops for non-ROM hook invocations. Resolves
    the launching pak's name from HOOK_EMU_PATH's parent directory (not
    basename, which would just be launch.sh for every emulator) and skips
    enforcement if that pak is in the exemption list. Otherwise calls
    parental.elf --gate; on refusal it closes the already-opened
    play_activity row via gametimectl.elf stop_all (so a refused launch
    doesn't count as played time) and shows a show2.elf toast.
  • .../Parental.pak/hooks/parental-stop.sh — post-launch hook, tears down
    the watcher via parental.elf --stop.

Config storage$SHARED_USERDATA_PATH/parental.txt, plain
key=value lines:

limit_minutes=90
warn_minutes=5
code_hash=<djb2 hash of the button sequence>
code_len=4
exempt=GBA.pak,SNES.pak
  • limit_minutes <= 0 disables the whole feature (gate always allows).
  • warn_minutes isn't exposed in the UI, only editable by hand.
  • The exemption list stores only what's explicitly allowed, never a
    snapshot of installed emulators — anything installed later stays blocked
    by default, matched as a whole comma-separated token (so GB.pak can't
    match inside GBA.pak).
  • The unlock code is hashed with djb2 (code_hash/code_len), not stored
    in the clear. This is explicitly not a security boundary against a
    motivated attacker — the threat model is a child poking around with
    Files.pak, not an adversary — it just keeps the code from being readable
    at a glance. Default code is UP, UP, DOWN, DOWN.

Play time accounting — no new tracking is introduced. Time played is
aggregated per-day from the existing gametimedb/play_activity sqlite
table via play_activity_get_play_time_since(start_of_today), so it stays
consistent with whatever the console already records as a play session.

UI — plain SDL2 + the shared GFX_*/pill-list widgets used elsewhere
in NextUI tools, consistent with the rest of the launcher's look
(GFX_blitPill, GFX_blitButtonGroup, GFX_blitHardwareHints, etc.). Code
entry is a configurable-length sequence over a fixed button set (UP DOWN LEFT RIGHT A X Y L1 R1 SELECT); B always means "back" and is deliberately
excluded from the code alphabet so no confirm key is needed, and START
terminates capture when setting a new code.

@dalexanco
dalexanco force-pushed the feat/parental-control branch 2 times, most recently from 606d10c to 7c7466a Compare August 16, 2026 23:22
@dalexanco

Copy link
Copy Markdown
Author

note : exempt is usefull for shortcut pak to avoid making it blocked since it use rom pak type

Adds Parental.pak, an optional cap on how long the device can be played
each day. Set a limit (90 minutes by default); while time is left nothing
changes. Once it is spent, a running game is warned, then stopped, and
further rom launches are refused until the next day. The settings screen
sits behind a button-sequence code (UP UP DOWN DOWN by default).

This is a household guardrail, not a lock. Anyone who can edit the SD card
can lift it, and every path fails open: a missing binary, a zero limit or
an unreadable db all let the game start.

Implementation
--------------

All of the policy lives in the pak. The firmware only gained three small
generic capabilities, each useful on its own:

- Hooks. A synchronous pre-launch hook (*.sync.sh) that exits non-zero now
  cancels the launch: run_hooks.sh propagates the failure and MinUI.pak
  guards the launch with it. Background hooks keep the old behaviour --
  their exit status is not recoverable from `wait` in POSIX sh, so only
  .sync.sh hooks get a vote.

- minarch. SIGUSR1 saves and quits; SIGUSR2 shows the message left in
  NOTIFY_PATH over the running game as a NOTIFICATION_SYSTEM toast. The
  handlers only raise a flag -- the work happens on the main thread once
  per frame, since neither Menu_beforeSleep() nor Notification_push() is
  async-signal-safe.

- gametimedb. play_activity_get_play_time_since() sums the seconds played
  across every rom since a given epoch, the session in progress included.

Parental.pak composes the three. `parental.elf --gate` answers the
pre-launch hook, exit 1 refusing the launch; `--watch` is backgrounded
alongside the game and polls every 10s to warn, then stop it; `--stop`
tears the watcher down from the post-launch hook. The budget is recomputed
from gametimedb on every check rather than stored, so the daily reset is
implicit and there is no counter to keep in sync. Opening the pak once is
what arms it -- that is when its hooks are installed into .hooks/.

Only rom launches are gated. Paks are left alone, both because they open
no play session to measure and because blocking them would lock the owner
out of the settings screen.

Some launchers make a pak look like a rom, though -- a bridge emulator that
execs a tool is indistinguishable from a real emulator at this level, since
HOOK_TYPE describes the shape of the launch and not the nature of what is
launched. The settings screen therefore lists the installed emulators and
lets the owner exempt any of them from the budget.

Only the exemptions are persisted, never the list of emulators found. A
stored list would be a snapshot, and anything installed afterwards would
fall outside it and run unmetered; asking instead whether a pak is
explicitly exempted keeps the unknown case closed.
Adds a track+fill bar under "X left today" on the home screen, filled
proportionally to remaining/limit. Confirmed and documented that the
bar doesn't move just from having the screen open: opening a Tools
pak never writes to the play-time DB the remaining-time calc reads
from (only real rom launches do, via gametimectl.elf start).
Adds an "Extra time" entry next to Daily limit: same hours/minutes
editor, but the amount only counts for the calendar day it was
granted on (extra_day tracks which day, so it stops applying once the
day rolls over, no reset job needed). Folded into remainingFrom() so
the gate/watch enforcement and the home screen (including the
progress bar's fraction) all pick it up automatically.
@dalexanco
dalexanco force-pushed the feat/parental-control branch from 7c7466a to 00f9fad Compare August 17, 2026 20:56
@frysee

frysee commented Aug 18, 2026

Copy link
Copy Markdown
Member

A couple of comments here. It is still marked as draft so I'm assuming you are still working on it, but allow be to leave a few notes:

  • This feels like it needs to be split up into a pak that lives in the Pak Store and the core changes you are proposing to enable the functionality in the first place. I won't vendor any paks that I feel arent useful to the majority of people, we have the Pak Store for exactly those more niche offerings.
  • The question whether to expand the hooks to allow synchronous execution and actually blocking the launch is probably a discussion of its own. It severely increases the chance of weird side effects (launch hooks having their opposite not called due to the synchronous one cutting off the loop early, hook name suddenly matters due to its influence on call order, just blocking the main thread in general, etc.). Thats not a no, just something we would need to establish some rules for before we move this ahead. Generally I'd have to say this is exactly the kind of thing that motivated the hooks in the first place, because it goes beyond a single hook interaction.
  • The same for the SIGUSR stuff, I'm impressed with you (or your LLM) coming up with an interesting approach of expanding whats already there, without completely going off the rails in terms of complexity. I'm wondering if that is a pattern we should even expand on, which could enable extensible toast notifications system-wide (if enabled by the user, of course).

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