The Recalbox interface can run your scripts (or publish MQTT messages) on each of its events: startup, system selection, game launch, scraping… Enough to drive a secondary screen, a cabinet marquee, LEDs, or any external action.
It is mainly on Raspberry Pi and other GPIO-friendly machines that this feature reveals its full potential — but it works everywhere.
On every event, the interface also writes a complete state file that your scripts can use.
To run scripts on demand from a menu, see User scripts instead.
The list of events, when they fire, and the extra parameter possibly passed to the scripts:
| Event | When? | Parameter |
|---|---|---|
start |
Interface starts or restarts | Start count |
stop |
Interface stops | Start count |
shutdown |
Full system shutdown | fast or normal |
reboot |
System reboot | fast or normal |
quit |
Interface stops following an external request (GPi Case on/off button for instance) | quitrequested or fatalerror |
relaunch |
Interface restarts (gamelist.xml changed from outside, game list update…) | |
systembrowsing |
A new system was just selected in the systems list | System short name |
gamelistbrowsing |
A new game (or folder) was just selected in a game list | Rom path |
rungame |
A game is about to launch | Rom path |
rundemo |
A game is about to launch in demo mode | Rom path |
endgame |
A game just ended | Rom path |
enddemo |
A game demo just ended | Rom path |
sleep |
Screensaver starts | Idle time (ms) |
wakeup |
Screensaver ends | Idle time (ms) |
scrapstart |
A multi-game scraping session starts | |
scrapstop |
A multi-game scraping session ends | Number of scraped games |
scrapgame |
A game was just scraped | Rom path |
startgameclip |
A video clip is about to play | Rom path |
stopgameclip |
A video clip just ended | |
runkodi |
Kodi is about to launch | |
configurationchanged |
Something changed in the configuration | Modified configuration file |
Scripts go directly in /recalbox/share/userscripts (or \\RECALBOX\share\userscripts over the network) — subfolders are not scanned, and the manual/ subfolder is reserved for User scripts.
The interface picks the launcher itself based on the extension:
.sh: run with sh;.ash: run with ash (busybox's optimized shell);.py: run with python (python3 on Recalbox);.py3: explicitly run with python3.Files without an extension are treated as executables and run directly.
Each script is launched with the following arguments:
script -action <action> -statefile <statefile> [-param <parameter>]
<action>: the event name, lowercase (first column of the table above);<statefile>: the path of the state file (/tmp/es_state.inf);<parameter>: the event's parameter. If there is none, -param is absent.By default, a script runs on every event. To restrict it to specific events, list them in the filename, between square brackets and separated by commas (case does not matter):
marquee[start,stop].sh — only runs when the interface starts or stops;gamesinfo[gamelistbrowsing,rungame,rundemo,scrapgame].sh — only runs for game-related events (to show game information on a secondary screen, for instance).By default, scripts run asynchronously: the interface keeps running while the script executes in parallel.
Add (sync) to the filename for a synchronous launch: the interface waits for the script to finish before continuing. This is essential for scripts triggered on system shutdown or reboot, which must complete before the machine powers off:
backup[reboot,shutdown](sync).sh — runs on shutdown or reboot, blocking the procedure until the script ends.Add (permanent) to the filename so the script is launched once when the interface starts and keeps running — typically to listen for MQTT messages. If the interface restarts, permanent scripts that are still alive are not relaunched.
Recalbox ships an MQTT server (Mosquitto) for publish/subscribe. On every event, the interface publishes:
Recalbox/EmulationStation/Event topic;Recalbox/EmulationStation/EventJson topic.The broker listens on port 1883 (and MQTT over websockets on port 18833), including from your local network — handy to drive an external screen or automation.
Mosquitto provides two small executables, mosquitto_pub and mosquitto_sub, to publish or wait for a message. In a permanent script, you can wait for events in a loop:
event=$(mosquitto_sub -h 127.0.0.1 -p 1883 -q 0 -t Recalbox/EmulationStation/Event -C 1)
This command blocks until an event is published, then returns it in the event variable.
This is where permanent scripts shine: instead of spawning a process on every event, a single permanent script intercepts everything, at nearly zero CPU cost.
On every event, the interface writes /tmp/es_state.inf (in RAM), an ini-style file (key=value), before running the scripts and publishing the MQTT message.
The keys that are always present:
| Key | Value |
|---|---|
Version |
format version (2.0) |
Action |
the name of the event that triggered the write |
ActionData |
the event's parameter (may be empty) |
System / SystemId |
full name / short name of the involved system (may be empty) |
Game / GamePath |
name / full path of the involved game (may be empty) |
ImagePath |
path of the game's image (may be empty) |
State |
playing (a game is running), demo (a demo is running) or selected (all other cases) |
The extra keys when a game is involved (gamelistbrowsing, rungame, rundemo, endgame, enddemo, scrapgame):
| Key | Value |
|---|---|
IsFolder |
1 if a folder is selected, 0 for a game |
Emulator / Core |
emulator and core used to launch this game |
BoxPath / VideoPath |
paths of the game's box art and video (*) |
Developer / Publisher |
developer / publisher (*) |
Players |
number of players (*) |
Region / Genre / GenreId |
region, genre and genre identifier (*) |
Favorite / Hidden / Adult |
1 or 0 based on metadata (*) |
(*) This information comes from the game's metadata: it may be empty if the game has not been scraped.
And when a system is involved (systembrowsing):
| Key | Value |
|---|---|
DefaultEmulator / DefaultCore |
the system's default emulator and core |
Some events are very close together: the file may have been rewritten by a second event by the time your script reads it. So check the
Actionkey, and never assume an optional key will be present or a fixed key will have a value.
Running scripts on every event has a cost: slowdowns, in-game or audio lag, slower game launches… A few tips:
(sync) unless truly needed (shutdown phase);mosquitto_sub rather than one launch per event.