Wireless file transfer for retro handhelds.
retsend (retro + sending) is a Rust LocalSend
client for retro handhelds: send and receive files with your phone
or PC over Wi-Fi, no cable or SSH. Compatible with the official LocalSend apps.
It targets PortMaster-compatible Linux handhelds (Knulli, muOS, ROCKNIX, and ArkOS) and the Miyoo Mini Plus and Miyoo Mini Flip running OnionOS, both of which are gamepad-only systems without a compositor, and runs on regular desktop Linux too.
| Receive | Send | Save | History |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Getting files onto a handheld usually means pulling the SD card or setting up SSH/SMB. Phones and desktops already have LocalSend — this is the missing end: a client built for a gamepad and screen.
- Discovery — a live radar of nearby devices (LocalSend protocol v2.1), with a manually typed IP address for the networks that block multicast.
- Receive — accept/decline dialog with countdown, speed/ETA, and cancel from either side; X picks a folder for that one transfer; quick-save mode auto-accepts.
- Send — gamepad file browser with multi-select and per-file progress; pin the folders and files you send often and they lead every listing.
- Save routes — received ROMs land in the console folder they belong to, detected from the card; per-extension overrides on top.
- Encryption — the protocol's HTTPS mode, on by default; works with the official app's default settings both ways.
- Settings on device — alias, save folder, and port, applied live.
- Headless —
retsend --receiveruns with no screen, for SSH and scripting. - Small and simple — no async runtime, a minimal HTTP server, and the platform's own SDL2 where there is one.
Grab retsend-portmaster.zip from
Releases and unpack it
into your ports folder (e.g. /roms/ports/).
Grab retsend-onionos.zip from
Releases and unzip it at the
root of the SD card, so the app lands in App/Retsend/. It shows up under Apps, and MENU quits it.
System SDL2 is the only native dependency. On Debian/Ubuntu:
sudo apt-get install -y build-essential pkg-config libsdl2-dev
cargo runFiles passed as arguments (cargo run -- file1 file2) come pre-selected in the
send browser.
Two instances on one machine discover each other (multicast loopback) — handy for trying both sides of a transfer without a second device:
RETSEND_DATA_DIR=/tmp/ls-a cargo run &
RETSEND_DATA_DIR=/tmp/ls-b cargo runTests are headless (no SDL, no network setup needed):
cargo test| Pad | Keyboard | Action |
|---|---|---|
| D-pad / stick | Arrows | Navigate · left/right switch tabs |
| A | Enter | Send to device · select file · accept · type |
| B | Esc | Back · decline · cancel · leave the keyboard |
| X | X / Bksp | Add a device by IP · pick where an incoming transfer lands · erase a character · take every file in the folder |
| Y | Y | Pin / unpin the row under the cursor |
| Start | F1 | Confirm send · OK (keyboard) |
| Select | Tab / F5 | Refresh radar · switch roots · layer (keyboard) |
| L1 / R1 | PgUp / PgDn | Switch tabs · page the file browser |
config.toml lives in the data dir (created with defaults on first run). The
Settings screen edits everything in it except:
[network] https = false— fall back to the protocol's plain-http mode[network] announce_interval_secs— multicast announce cadence[transfer] browser_roots— extra mount points for the file browser[transfer] history_limit— max transfers kept in the History tab (default 200)[transfer] pinned_paths,last_send_dir— written by Y and by sending
Environment variables override paths and control logging at launch:
RETSEND_DATA_DIR, RETSEND_CONFIG, RETSEND_SAVE_DIR, RETSEND_SCALE,
RETSEND_GLES=0|1, RETSEND_SOFTWARE=1, RETSEND_BLIT=1,
RETSEND_KEYMAP=miyoo|desktop, RETSEND_LOG_LEVEL, RETSEND_LOG_FILE,
RETSEND_PANIC_FILE.
RETSEND_BLIT=1 rasterizes the UI offscreen and presents it as a single texture
copy per frame, for drivers that show nothing else — the Miyoo Mini's mmiyoo
drops geometry and window-surface updates without reporting an error.
Received files land in save_dir, except ROMs: those are routed by extension
into the console folders already present in save_dir — .gba into gba,
.sfc into snes. Nothing is created, so a missing folder gets no route, and ambiguous
extensions (.zip, .iso, .bin, .md) never take part. Off via
Settings → Auto save routes or [transfer] auto_routes = false.
Per-extension overrides win over the detected routes — Settings → Save routes on the device, or:
[transfer.routes]
gbc = "gb" # relative → <save_dir>/gb
gba = "/roms/gba" # absolute → used as-is
png = "/roms/screenshots"Extensions match case-insensitively, folders are created on demand, and anything
unrouted lands in save_dir.




