diff --git a/.github/workflows/build-linux-arm.yml b/.github/workflows/build-linux-arm.yml index 071731d..dd0b274 100644 --- a/.github/workflows/build-linux-arm.yml +++ b/.github/workflows/build-linux-arm.yml @@ -328,3 +328,54 @@ jobs: files: | retsend-allium.zip retsend-allium.zip.sha256 + + muos: + needs: build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: retsend-linux-aarch64 + path: bin + + - name: Assemble the application folder + run: | + set -eux + pkg=muos-dist + app=$pkg/Retsend + rm -rf "$pkg" + mkdir -p "$app" + # Archive Manager unpacks a .muxapp into MUOS/application/, so the + # folder at the archive root is the installed app. + cp muos/mux_launch.sh "$app/" + cp muos/README.md "$app/" + # The glyph the app list falls back to, named after the ICON header. + # 24x24, the size themes cut theirs to: muOS draws it at native size. + cp -r muos/glyph "$app/" + install -m 755 bin/retsend "$app/retsend" + chmod 755 "$app/mux_launch.sh" + ls -laR "$pkg" + + - name: Upload package artifact + uses: actions/upload-artifact@v4 + with: + name: retsend-muos + path: muos-dist + + - name: Pack for release + if: github.event_name == 'release' + run: | + set -eux + # .muxapp is a zip; the extension is what Archive Manager dispatches on. + (cd muos-dist && zip -r ../retsend-muos.muxapp Retsend) + sha256sum retsend-muos.muxapp > retsend-muos.muxapp.sha256 + + - name: Upload to GitHub Release + if: github.event_name == 'release' + uses: softprops/action-gh-release@v2 + with: + files: | + retsend-muos.muxapp + retsend-muos.muxapp.sha256 diff --git a/CHANGELOG.md b/CHANGELOG.md index e22db89..db2c719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **A muOS package**, `retsend-muos.muxapp`, installed from Archive Manager and + listed under Applications rather than under Ports as the PortMaster build is. + The launcher hands muOS's own `SETUP_APP` the pad and the governor, holds the + device awake for the length of a transfer, and points saves at `ROMS/` on the + card that booted. **B on the home screen quits**, which muOS needs: its only + exit is a `kill -9` that would cut a transfer where it stands, so the app + answers the key itself and closes the sockets on the way out. Other ports leave + B inert — there the launcher owns quitting — and the launcher opts in with + `RETSEND_BACK_QUIT`. The home tabs name the key in their hints only where it is + on, so nowhere else grows a hint for a button that does nothing. + - **A "UI scale" setting**, 60% to 160% of what the screen itself asks for, stepped with ◂ ▸ on its row (L1/R1 still switch tabs, and the hints name both arrows so a screen with no pad can reach them). Persisted as diff --git a/README.md b/README.md index d4cf2a5..1c90fa7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ 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](https://portmaster.games/supported-devices.html) Linux handhelds and the Miyoo Mini Plus and Flip running [OnionOS](https://github.com/OnionUI/Onion), [spruceOS](https://github.com/spruceUI/spruceOS) or [Allium](https://github.com/goweiwen/Allium), all of which are gamepad-only systems without a compositor. It also runs on regular desktop Linux and on Android. +It targets [PortMaster-compatible](https://portmaster.games/supported-devices.html) Linux handhelds, the handhelds running [muOS](https://muos.dev), and the Miyoo Mini Plus and Flip running [OnionOS](https://github.com/OnionUI/Onion), [spruceOS](https://github.com/spruceUI/spruceOS) or [Allium](https://github.com/goweiwen/Allium), all of which are gamepad-only systems without a compositor. It also runs on regular desktop Linux and on Android.
@@ -57,6 +57,14 @@ Grab `retsend-portmaster.zip` from
[Releases](https://github.com/mxmgorin/retsend/releases) and unpack it
into your ports folder (e.g. `/roms/ports/`).
+## Install (muOS)
+
+Copy `retsend-muos.muxapp` from
+[Releases](https://github.com/mxmgorin/retsend/releases) to `ARCHIVE/` on the SD
+card and install it from **Applications → Archive Manager**. It then sits under
+**Applications**, and **B on the home screen quits** — muOS keeps no way out of
+its own. See [muos/README.md](muos/README.md).
+
## Install (Miyoo Mini Plus / Flip)
Download the appropriate zip from
diff --git a/muos/README.md b/muos/README.md
new file mode 100644
index 0000000..e6d3c35
--- /dev/null
+++ b/muos/README.md
@@ -0,0 +1,76 @@
+# retsend for muOS
+
+Transfer files between a muOS handheld and your phone or PC over wifi, using
+[LocalSend](https://localsend.org).
+
+Every device muOS runs on is aarch64 and carries its own SDL2, so this package is
+the binary and a launcher — no bundled libraries. muOS also ships PortMaster, and
+`retsend-portmaster.zip` installs the same app under Ports; this one puts it in
+the **Applications** menu instead, where it belongs on a device with no ROM to
+launch.
+
+## Install
+
+Copy `retsend-muos.muxapp` to `ARCHIVE/` on the SD card, then run
+**Applications → Archive Manager** and pick it. It unpacks into
+`MUOS/application/Retsend/` and appears under **Applications**.
+
+## Setup
+
+1. Install LocalSend on your phone or PC (localsend.org).
+2. Connect both to the same network — wifi in muOS's own settings.
+3. Launch Retsend — nearby devices appear on the radar.
+
+Received files land in `ROMS/` on the card muOS boots from; change the folder in
+Settings. The config lives in `MUOS/application/Retsend/data/config.toml`, and
+the last run's log in `MUOS/application/Retsend/log.txt`.
+
+## Controls
+
+| Button | Action |
+|---------|---------------------------------------------------------|
+| D-pad | Navigate |
+| A | Send to device / select file / accept / type (keyboard) |
+| B | Back / decline / cancel / erase (keyboard) · **quit** |
+| Start | Settings · confirm send · OK (keyboard) |
+| Select | Refresh radar · switch roots · layer (keyboard) |
+| L1/R1 | Page through lists |
+
+## Notes for this device
+
+**B on the home screen is the way out**, and here it has to be: muOS expects an
+app to end itself, and the only exit it offers is `R2+SELECT+B`, which `kill -9`s
+the foreground process — mid-transfer that leaves a peer holding a connection
+that stopped answering. Leaving through B stops announcing and closes the sockets
+first, so the peer sees a transfer end. The launcher sets `RETSEND_BACK_QUIT`;
+every other port leaves the key inert, because there the launcher owns quitting.
+
+Transfers are minutes with no button pressed, which is exactly what the device
+reads as idle. The launcher holds `CAFFEINE` on for the run so it does not sleep
+part-way through a file.
+
+`SETUP_APP` from muOS's own `func.sh` does the work a launcher here would
+otherwise repeat — the action file, the CPU governor, and the SDL controller
+database that makes the pad arrive as a gamepad rather than as keys. It also
+points `HOME` at the board's, which is read-only rootfs, so the launcher moves it
+back to the app folder afterwards; the config, the log and the panic file all sit
+on the card. Older muOS has no `SETUP_APP`, and the launcher spells the same
+steps out when it is missing.
+
+The app glyph is the package's own, at `glyph/retsend.png`. muOS looks in the
+active theme first and falls back to this, so a theme carrying a `retsend` glyph
+wins and one without still shows an icon.
+
+It is 24×24 because that is the size themes cut their own glyphs to, and at the
+usual "native" glyph setting muOS draws a raster at whatever pixels it has —
+there is no fitting to the row. A vector would be fitted, but the `.svg` the
+frontend reaches for did not render on device, so the package ships the raster
+that does. `resources/retsend-glyph.svg` is what it is cut from.
+
+## Credits
+
+- Developed and ported by [mxmgorin](https://github.com/mxmgorin/)
+- Implements the [LocalSend](https://localsend.org) protocol
+- [muOS](https://muos.dev) is the MustardOS team's; this package only follows its
+ application layout
+- Source and issues: https://github.com/mxmgorin/retsend
diff --git a/muos/glyph/retsend.png b/muos/glyph/retsend.png
new file mode 100644
index 0000000..88ab5ea
Binary files /dev/null and b/muos/glyph/retsend.png differ
diff --git a/muos/mux_launch.sh b/muos/mux_launch.sh
new file mode 100755
index 0000000..406d52d
--- /dev/null
+++ b/muos/mux_launch.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+# HELP: Send and receive files over wifi with LocalSend - no cable, no SSH
+# ICON: retsend
+# GRID: Retsend
+
+. /opt/muos/script/var/func.sh
+
+APP_BIN="retsend"
+# frontend.sh passes the app folder, which is SD1 or SD2 depending on where the
+# archive was installed. The fallback is for a run by hand over SSH.
+APP_DIR="${1:-$(cd "$(dirname "$0")" && pwd)}"
+LOG_FILE="$APP_DIR/log.txt"
+
+# Jacaranda and newer: one call does the action file, the governor, HOME and the
+# SDL controller database. Older muOS spells the same out.
+if command -v SETUP_APP >/dev/null 2>&1; then
+ SETUP_STAGE_OVERLAY
+ SETUP_APP "$APP_BIN" ""
+else
+ echo app >/tmp/act_go
+ SETUP_SDL_ENVIRONMENT
+ SET_VAR "system" "foreground_process" "$APP_BIN"
+fi
+
+# RK3576 (Vita Pro and kin) ships an SDL that finds no EGL driver on its own.
+if grep -q "rk3576" /proc/device-tree/compatible 2>/dev/null; then
+ for MALI_LIBRARY in /usr/lib/libmali.so /usr/lib/aarch64-linux-gnu/libmali.so; do
+ if [ -e "$MALI_LIBRARY" ]; then
+ export SDL_VIDEO_EGL_DRIVER="$MALI_LIBRARY"
+ break
+ fi
+ done
+ export SDL_OPENGL_ES_DRIVER=1
+fi
+
+cd "$APP_DIR" || exit 1
+
+# SETUP_APP points HOME at the board's, which is rootfs; keep writable paths on
+# the card next to the app.
+export HOME="$APP_DIR"
+export XDG_DATA_HOME="$APP_DIR"
+export RETSEND_DATA_DIR="$APP_DIR/data"
+export RETSEND_PANIC_FILE="$APP_DIR/retsend-panic.log"
+ROM_MOUNT="$(GET_VAR "device" "storage/rom/mount")"
+export RETSEND_SAVE_DIR="$ROM_MOUNT/ROMS"
+# muOS expects an app to end itself — its own way out is the R2+SELECT+B kill —
+# so B on the home screen is ours, and the sockets close on the way.
+export RETSEND_BACK_QUIT=1
+#export RETSEND_LOG_LEVEL=debug
+
+: >"$LOG_FILE"
+
+# An empty radar reads as a broken app rather than an answer, so say which it is.
+NET_STATE="$(GET_VAR "device" "network/state")"
+if [ -r "$NET_STATE" ] && [ "$(cat "$NET_STATE")" != "up" ]; then
+ echo "wifi is down; connect it in muOS settings or nothing will be found" >>"$LOG_FILE"
+fi
+
+# A transfer is minutes with no button pressed, which is the device's cue to
+# sleep. Absent before Jacaranda, hence the guard.
+HAS_CAFFEINE=0
+command -v CAFFEINE >/dev/null 2>&1 && HAS_CAFFEINE=1
+
+[ "$HAS_CAFFEINE" -eq 1 ] && CAFFEINE on
+./"$APP_BIN" >>"$LOG_FILE" 2>&1
+[ "$HAS_CAFFEINE" -eq 1 ] && CAFFEINE off
+
+exit 0
diff --git a/resources/retsend-glyph.svg b/resources/retsend-glyph.svg
new file mode 100644
index 0000000..0387ae2
--- /dev/null
+++ b/resources/retsend-glyph.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/app/mod.rs b/src/app/mod.rs
index b3f55ae..77c9c31 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -20,7 +20,7 @@ use crate::transfer::outbound::{self, OutboundSession};
use crate::ui::AppUi;
use std::path::PathBuf;
use std::sync::atomic::Ordering;
-use std::sync::Arc;
+use std::sync::{Arc, OnceLock};
/// Rows a shoulder-button page jump moves in a list.
const PAGE_JUMP: i32 = 8;
@@ -45,6 +45,17 @@ pub struct App {
running: bool,
}
+/// Whether Back on the home screen leaves the app. Android's Back is a system
+/// button that has to lead somewhere; on a handheld only a launcher keeping no
+/// kill helper of its own hands the key over.
+pub fn back_quits() -> bool {
+ static BACK_QUITS: OnceLock