Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,38 @@ echo "$(date): launched $HOOK_ROM_PATH" >> "$LOGS_PATH/launches.log"

## Rules

- Each script runs in a subshell. A crash or non-zero exit will not affect the launcher or other hooks.
- Each script runs in a subshell. A crash will not affect the launcher or other hooks.
- Script output (stdout/stderr) is suppressed. If you need logging, write to your own log file.
- Pre-launch hooks cannot cancel the launch. They are for observation and setup only.
- A **synchronous** pre-launch hook (`*.sync.sh`) that exits non-zero **cancels the launch**: the rom or pak is never started, and `post-launch.d` is skipped. See "Vetoing a launch" below.
- Background hooks cannot cancel anything — their exit status is not recoverable from `wait` in POSIX sh, so only `.sync.sh` hooks get a vote. A non-zero exit from a background hook is ignored, as is any exit status outside `pre-launch.d`.
- Keep hooks fast. A slow hook delays the launch or the return to the menu.
- Unlike auto.sh, each pak should manage their own hook and use a descriptive filename to avoid collisions.


## Vetoing a launch

Name the hook `*.sync.sh` and exit non-zero:

```sh
#!/bin/sh
# no-roms-before-noon.sync.sh

[ "$HOOK_TYPE" = "rom" ] || exit 0
[ "$(date +%H)" -ge 12 ] && exit 0

show2.elf --mode=simple --text="Not before noon" --timeout=3
exit 1
```

Two things to know when you cancel a rom launch:

- The launcher has already run `gametimectl.elf start` for that rom by the time the
hook runs, so a vetoing hook should call `gametimectl.elf stop_all` to avoid leaving
an open play session behind.
- Nothing is displayed for you. If the user should know why nothing happened, say so —
`show2.elf` is the usual way.


## Example: sync after ROM exit

```sh
Expand Down
3 changes: 3 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ ifneq ($(PLATFORM), desktop)
cp ./workspace/all/libgametimedb/build/$(PLATFORM)/libgametimedb.so ./build/SYSTEM/$(PLATFORM)/lib
cp ./workspace/all/gametimectl/build/$(PLATFORM)/gametimectl.elf ./build/SYSTEM/$(PLATFORM)/bin/
cp ./workspace/all/gametime/build/$(PLATFORM)/gametime.elf ./build/EXTRAS/Tools/$(PLATFORM)/Game\ Tracker.pak/

# daily play time budget (reads the same db as game time tracking)
cp ./workspace/all/parental/build/$(PLATFORM)/parental.elf ./build/EXTRAS/Tools/$(PLATFORM)/Parental.pak/
endif
cp ./workspace/$(PLATFORM)/libmsettings/libmsettings.so ./build/SYSTEM/$(PLATFORM)/lib
cp ./workspace/all/nextui/build/$(PLATFORM)/nextui.elf ./build/SYSTEM/$(PLATFORM)/bin/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh
# parental-gate.sync.sh -- refuse a rom launch once the daily budget is spent.
#
# Must stay named *.sync.sh: only synchronous pre-launch hooks get a veto,
# and a non-zero exit is what cancels the launch.

[ "$HOOK_TYPE" = "rom" ] || exit 0

# Emulators the owner exempted in the settings screen. HOOK_EMU_PATH points at
# the emulator's launch.sh, so the pak name is its parent directory -- basename
# alone would be "launch.sh" for every emulator alike.
#
# This is what lets tool shortcuts through: they are launched by the bridge
# emulator of the Shortcuts pak, which makes them look like roms to the launcher.
# Deny by default, so a pak that is not named here stays blocked.
EMU_PAK=$(basename "$(dirname "$HOOK_EMU_PATH")")
EXEMPT=$(sed -n 's/^exempt=//p' "$SHARED_USERDATA_PATH/parental.txt" 2>/dev/null)
case ",$EXEMPT," in
*",$EMU_PAK,"*) exit 0 ;;
esac

PAK="$SDCARD_PATH/Tools/$PLATFORM/Parental.pak"
[ -x "$PAK/parental.elf" ] || exit 0

if "$PAK/parental.elf" --gate; then
# budget left: watch the session so it can be stopped on time
"$PAK/parental.elf" --watch &
exit 0
fi

# nextui already opened a play_activity row before handing off to us, close it
# so the refused launch doesn't count as time played
gametimectl.elf stop_all

# --image is mandatory, show2.elf prints its usage and draws nothing without it
show2.elf --mode=simple --image="$SDCARD_PATH/.system/res/logo.png" --text="No play time left today" --timeout=3

exit 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# parental-stop.sh -- tear down the watcher started by parental-gate.sync.sh

[ "$HOOK_TYPE" = "rom" ] || exit 0

PAK="$SDCARD_PATH/Tools/$PLATFORM/Parental.pak"
[ -x "$PAK/parental.elf" ] || exit 0

"$PAK/parental.elf" --stop
12 changes: 12 additions & 0 deletions skeleton/EXTRAS/Tools/tg5040/Parental.pak/launch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

cd "$(dirname "$0")"

# (Re)install the hooks that actually enforce the budget. Opening the pak once
# is what arms it -- there is no other moment a Tools pak gets to run.
mkdir -p "$HOOKS_PATH/pre-launch.d" "$HOOKS_PATH/post-launch.d"
cp -f ./hooks/parental-gate.sync.sh "$HOOKS_PATH/pre-launch.d/"
cp -f ./hooks/parental-stop.sh "$HOOKS_PATH/post-launch.d/"
chmod +x "$HOOKS_PATH/pre-launch.d/parental-gate.sync.sh" "$HOOKS_PATH/post-launch.d/parental-stop.sh"

./parental.elf # &> ./log.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/sh
# parental-gate.sync.sh -- refuse a rom launch once the daily budget is spent.
#
# Must stay named *.sync.sh: only synchronous pre-launch hooks get a veto,
# and a non-zero exit is what cancels the launch.

[ "$HOOK_TYPE" = "rom" ] || exit 0

# Emulators the owner exempted in the settings screen. HOOK_EMU_PATH points at
# the emulator's launch.sh, so the pak name is its parent directory -- basename
# alone would be "launch.sh" for every emulator alike.
#
# This is what lets tool shortcuts through: they are launched by the bridge
# emulator of the Shortcuts pak, which makes them look like roms to the launcher.
# Deny by default, so a pak that is not named here stays blocked.
EMU_PAK=$(basename "$(dirname "$HOOK_EMU_PATH")")
EXEMPT=$(sed -n 's/^exempt=//p' "$SHARED_USERDATA_PATH/parental.txt" 2>/dev/null)
case ",$EXEMPT," in
*",$EMU_PAK,"*) exit 0 ;;
esac

PAK="$SDCARD_PATH/Tools/$PLATFORM/Parental.pak"
[ -x "$PAK/parental.elf" ] || exit 0

if "$PAK/parental.elf" --gate; then
# budget left: watch the session so it can be stopped on time
"$PAK/parental.elf" --watch &
exit 0
fi

# nextui already opened a play_activity row before handing off to us, close it
# so the refused launch doesn't count as time played
gametimectl.elf stop_all

# --image is mandatory, show2.elf prints its usage and draws nothing without it
show2.elf --mode=simple --image="$SDCARD_PATH/.system/res/logo.png" --text="No play time left today" --timeout=3

exit 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
# parental-stop.sh -- tear down the watcher started by parental-gate.sync.sh

[ "$HOOK_TYPE" = "rom" ] || exit 0

PAK="$SDCARD_PATH/Tools/$PLATFORM/Parental.pak"
[ -x "$PAK/parental.elf" ] || exit 0

"$PAK/parental.elf" --stop
12 changes: 12 additions & 0 deletions skeleton/EXTRAS/Tools/tg5050/Parental.pak/launch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/sh

cd "$(dirname "$0")"

# (Re)install the hooks that actually enforce the budget. Opening the pak once
# is what arms it -- there is no other moment a Tools pak gets to run.
mkdir -p "$HOOKS_PATH/pre-launch.d" "$HOOKS_PATH/post-launch.d"
cp -f ./hooks/parental-gate.sync.sh "$HOOKS_PATH/pre-launch.d/"
cp -f ./hooks/parental-stop.sh "$HOOKS_PATH/post-launch.d/"
chmod +x "$HOOKS_PATH/pre-launch.d/parental-gate.sync.sh" "$HOOKS_PATH/post-launch.d/parental-stop.sh"

./parental.elf # &> ./log.txt
14 changes: 13 additions & 1 deletion skeleton/SYSTEM/desktop/bin/run_hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
#
# By default, scripts run in the background. Scripts ending in .sync.sh
# always run synchronously. All background scripts are waited on before exit.
#
# Veto: a synchronous hook that exits non-zero makes run_hooks.sh itself exit
# non-zero, letting the caller cancel whatever the hooks were run ahead of
# (see pre-launch.d in MinUI.pak/launch.sh). Background hooks cannot veto --
# their exit status is not recoverable from `wait` in POSIX sh -- so only
# .sync.sh hooks (or any hook under --sync-only) get a vote.

DIR_NAME="$1"
SYNC_ONLY="${2:-}"
Expand All @@ -25,12 +31,18 @@ case "$DIR_NAME" in
esac
export HOOK_CATEGORY="$DIR_NAME"

VETOED=0

for script in "$HOOK_DIR"/*.sh; do
[ -f "$script" ] || continue
if [ "$SYNC_ONLY" = "--sync-only" ] || echo "$script" | grep -q '\.sync\.sh$'; then
( "$script" ) > /dev/null 2>&1 || true
if ! ( "$script" ) > /dev/null 2>&1; then
VETOED=1
fi
else
( "$script" ) > /dev/null 2>&1 &
fi
done
wait

exit $VETOED
8 changes: 5 additions & 3 deletions skeleton/SYSTEM/desktop/paks/MinUI.pak/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ touch "$EXEC_PATH" && sync
if [ -f $NEXT_PATH ]; then
CMD=`cat $NEXT_PATH`
parse_hook_cmd "$CMD"
"$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
# a synchronous pre-launch hook that exits non-zero cancels the launch
if "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d; then
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
fi
rm -f $NEXT_PATH
fi
#done
14 changes: 13 additions & 1 deletion skeleton/SYSTEM/tg5040/bin/run_hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
#
# By default, scripts run in the background. Scripts ending in .sync.sh
# always run synchronously. All background scripts are waited on before exit.
#
# Veto: a synchronous hook that exits non-zero makes run_hooks.sh itself exit
# non-zero, letting the caller cancel whatever the hooks were run ahead of
# (see pre-launch.d in MinUI.pak/launch.sh). Background hooks cannot veto --
# their exit status is not recoverable from `wait` in POSIX sh -- so only
# .sync.sh hooks (or any hook under --sync-only) get a vote.

DIR_NAME="$1"
SYNC_ONLY="${2:-}"
Expand All @@ -25,12 +31,18 @@ case "$DIR_NAME" in
esac
export HOOK_CATEGORY="$DIR_NAME"

VETOED=0

for script in "$HOOK_DIR"/*.sh; do
[ -f "$script" ] || continue
if [ "$SYNC_ONLY" = "--sync-only" ] || echo "$script" | grep -q '\.sync\.sh$'; then
( "$script" ) > /dev/null 2>&1 || true
if ! ( "$script" ) > /dev/null 2>&1; then
VETOED=1
fi
else
( "$script" ) > /dev/null 2>&1 &
fi
done
wait

exit $VETOED
8 changes: 5 additions & 3 deletions skeleton/SYSTEM/tg5040/paks/MinUI.pak/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,11 @@ while [ -f $EXEC_PATH ]; do
if [ -f $NEXT_PATH ]; then
CMD=`cat $NEXT_PATH`
parse_hook_cmd "$CMD"
"$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
# a synchronous pre-launch hook that exits non-zero cancels the launch
if "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d; then
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
fi
rm -f $NEXT_PATH
# reset to performance when exiting, UI will reset to auto if needed
sh "$SYSTEM_PATH/bin/governor.sh" "performance"
Expand Down
14 changes: 13 additions & 1 deletion skeleton/SYSTEM/tg5050/bin/run_hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
#
# By default, scripts run in the background. Scripts ending in .sync.sh
# always run synchronously. All background scripts are waited on before exit.
#
# Veto: a synchronous hook that exits non-zero makes run_hooks.sh itself exit
# non-zero, letting the caller cancel whatever the hooks were run ahead of
# (see pre-launch.d in MinUI.pak/launch.sh). Background hooks cannot veto --
# their exit status is not recoverable from `wait` in POSIX sh -- so only
# .sync.sh hooks (or any hook under --sync-only) get a vote.

DIR_NAME="$1"
SYNC_ONLY="${2:-}"
Expand All @@ -25,12 +31,18 @@ case "$DIR_NAME" in
esac
export HOOK_CATEGORY="$DIR_NAME"

VETOED=0

for script in "$HOOK_DIR"/*.sh; do
[ -f "$script" ] || continue
if [ "$SYNC_ONLY" = "--sync-only" ] || echo "$script" | grep -q '\.sync\.sh$'; then
( "$script" ) > /dev/null 2>&1 || true
if ! ( "$script" ) > /dev/null 2>&1; then
VETOED=1
fi
else
( "$script" ) > /dev/null 2>&1 &
fi
done
wait

exit $VETOED
8 changes: 5 additions & 3 deletions skeleton/SYSTEM/tg5050/paks/MinUI.pak/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,11 @@ while [ -f $EXEC_PATH ]; do
if [ -f $NEXT_PATH ]; then
CMD=`cat $NEXT_PATH`
parse_hook_cmd "$CMD"
"$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
# a synchronous pre-launch hook that exits non-zero cancels the launch
if "$SYSTEM_PATH/bin/run_hooks.sh" pre-launch.d; then
eval $CMD
"$SYSTEM_PATH/bin/run_hooks.sh" post-launch.d
fi
rm -f $NEXT_PATH
# reset to performance when exiting, UI will reset to auto if needed
sh "$SYSTEM_PATH/bin/governor.sh" "performance"
Expand Down
10 changes: 10 additions & 0 deletions workspace/all/common/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ LightSettings lightsLowBattery[MAX_LIGHTS];
LightSettings lightsCriticalBattery[MAX_LIGHTS];
LightSettings lightsSleep[MAX_LIGHTS];
LightSettings lightsAmbient[MAX_LIGHTS];
LightSettings lightsAppWarning[MAX_LIGHTS];
LightSettings (*lights)[MAX_LIGHTS] = NULL;

#define PROFILE_OVERRIDE_SIZE 4
Expand Down Expand Up @@ -4478,6 +4479,9 @@ void LEDS_setProfile(int profile)
new_lights = lightsAmbient;
indicator = false;
break;
case LIGHT_PROFILE_APP_WARNING:
new_lights = lightsAppWarning;
break;
default:
return;
}
Expand Down Expand Up @@ -4609,6 +4613,12 @@ void LEDS_initLeds()
// LIGHT_PROFILE_AMBIENT
// just to have sensible defaults, will be updated by GFX_setAmbientColor
lightsAmbient[i] = lightsDefault[i];

// LIGHT_PROFILE_APP_WARNING
lightsAppWarning[i] = lightsDefault[i];
lightsAppWarning[i].effect = 3; // blink
lightsAppWarning[i].color1 = CFG_getColor(COLOR_MAIN);
lightsAppWarning[i].cycles = -1; // infinite
}

// this might be called more than once (for reasons), so reset to consistent state
Expand Down
1 change: 1 addition & 0 deletions workspace/all/common/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ enum LightProfile {
LIGHT_PROFILE_CHARGING = 4, // derived from default
LIGHT_PROFILE_SLEEP = 5, // sleep mode
LIGHT_PROFILE_AMBIENT = 6, // ambient mode
LIGHT_PROFILE_APP_WARNING = 7, // any app warning the user of an imminent, forced action (blinks the main color)
LIGHT_PROFILE_COUNT
};

Expand Down
2 changes: 2 additions & 0 deletions workspace/all/common/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#define CHANGE_DISC_PATH "/tmp/change_disc.txt"
#define RESUME_SLOT_PATH "/tmp/resume_slot.txt"
#define NOUI_PATH "/tmp/noui"
// message an external process wants shown over the running game, see SIGUSR2 in minarch
#define NOTIFY_PATH "/tmp/notify.txt"

#define TRIAD_WHITE 0xff,0xff,0xff
#define TRIAD_BLACK 0x00,0x00,0x00
Expand Down
1 change: 1 addition & 0 deletions workspace/all/common/notification.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ typedef enum {
NOTIFICATION_SETTING, // volume/brightness/colortemp adjustments
NOTIFICATION_ACHIEVEMENT, // RetroAchievements unlocks
NOTIFICATION_OFFLINE_ACHIEVEMENT, // Offline RA unlocks (shows wifi-off icon)
NOTIFICATION_SYSTEM, // pushed by an external process, see SIGUSR2 in minarch
} NotificationType;

typedef enum {
Expand Down
Loading