diff --git a/README.md b/README.md index 1d908cb..7f15bd2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Port Manager for the Steam Frame +# Frametanium for the Steam Frame Installs and updates Steam Frame apps and ports straight from their GitHub releases, like Obtainium: add any repo, -and Port Manager keeps it up to date. Works from the headset or a browser. +and Frametanium keeps it up to date. Works from the headset or a browser. - **Add any GitHub repo** by typing `owner/name` (or pasting a link) on the headset's on-screen keyboard, from the browser page, or by picking a suggestion made for the Frame. The built-in ports (Ship of Harkinian, 2 Ship 2 @@ -13,7 +13,7 @@ and Port Manager keeps it up to date. Works from the headset or a browser. - *Library app:* unpacked into `~/devkit-game/`, given a `run.sh` (its own, or one that starts the ARM64 program it finds), added to the Steam library with art (the repo's GitHub card and the owner's avatar). - *Its installer:* when the download has `install.sh` (or `contrib/install.sh`, `setup.sh`), it's unpacked into - `~/.local/share/port-manager/apps/` and that script runs, for tools such as SteamVR overlays. Removing the app + `~/.local/share/frametanium/apps/` and that script runs, for tools such as SteamVR overlays. Removing the app runs its uninstaller when it has one. - **Install, update, update all, or pick any version.** Pre-releases are skipped unless you turn them on for an app (they're on for apps that only have pre-releases). @@ -22,7 +22,7 @@ and Port Manager keeps it up to date. Works from the headset or a browser. - Won't touch an app that's running. Only add apps you trust: their programs and installers run as your user. ## Use it -- **In the headset:** open **Port Manager** in the library. D-pad to choose, **A** open an app (install/update, +- **In the headset:** open **Frametanium** in the library. D-pad to choose, **A** open an app (install/update, versions, previous version, pre-releases, remove), **Y** add an app, **Select** update all, **Start** check for updates, **B** back/close. - **From a phone or PC:** `http://:8643` (add apps, edit name/install style/program, remove). @@ -32,14 +32,14 @@ Updates are checked every 6 hours and on demand. ## Files - `catalog.json`: the built-in ports. - `suggestions.json`: apps offered on the Add screen. -- `~/.config/port-manager/catalog.json` on the Frame: the apps you added and changes to built-in ones +- `~/.config/frametanium/catalog.json` on the Frame: the apps you added and changes to built-in ones (`{"id": "soh", "hidden": true}` hides one; adding its repo again brings it back). -- `~/.config/port-manager/installed.json`: installed versions. +- `~/.config/frametanium/installed.json`: installed versions. ## Private repos GitHub only shows releases of private repos to signed-in users. Either make the port repos public (they contain no ROMs), or save a token in the web page's Settings: a fine-grained token limited to those repos with read-only -**Contents** access is enough. It's stored in `~/.config/port-manager/config.json` (mode 600) and never sent back +**Contents** access is enough. It's stored in `~/.config/frametanium/config.json` (mode 600) and never sent back to the page. ## Install @@ -51,12 +51,12 @@ app/build-arm64.sh # cross-compiles the headset app into dist/app ``` Service only, on the Frame: copy this folder over and run `sh install.sh`. -Uninstall: `systemctl --user disable --now frame-port-manager.service` and remove Port Manager from the library. +Uninstall: `systemctl --user disable --now frametanium.service` and remove Frametanium from the library. ## Notes -- The service listens on port 8643 on your network. Set `password` in `~/.config/port-manager/config.json` to require +- The service listens on port 8643 on your network. Set `password` in `~/.config/frametanium/config.json` to require one in the browser (the headset app connects locally and doesn't need it). - Standard-library Python only. The headset app is C + SDL2 with the public-domain [font8x8](https://github.com/dhepper/font8x8). -- Testing on another architecture: `PORT_MANAGER_EXEC_WRAPPER=qemu-aarch64-static` (with `QEMU_LD_PREFIX`) runs - ARM64 AppImages for unpacking; `PORT_MANAGER_GAMES` changes the install folder. +- Testing on another architecture: `FRAMETANIUM_EXEC_WRAPPER=qemu-aarch64-static` (with `QEMU_LD_PREFIX`) runs + ARM64 AppImages for unpacking; `FRAMETANIUM_GAMES` changes the install folder. diff --git a/app/app.c b/app/app.c index 82eac76..fd1ce5e 100644 --- a/app/app.c +++ b/app/app.c @@ -1,4 +1,4 @@ -/* Port Manager for the headset: a controller-driven front end for the Port Manager service (manager.py), which +/* Frametanium for the headset: a controller-driven front end for the Frametanium service (manager.py), which * does the downloading and installing. It talks to the service on 127.0.0.1 with plain HTTP and reads simple * tab-separated replies. * @@ -130,7 +130,7 @@ static void fill(int x, int y, int w, int h, Uint32 rgb) { static void read_port(void) { char path[512], buf[2048]; - snprintf(path, sizeof path, "%s/.config/port-manager/config.json", getenv("HOME") ? getenv("HOME") : ""); + snprintf(path, sizeof path, "%s/.config/frametanium/config.json", getenv("HOME") ? getenv("HOME") : ""); FILE* f = fopen(path, "r"); if (!f) return; size_t n = fread(buf, 1, sizeof buf - 1, f); @@ -256,7 +256,7 @@ static int load_releases(const char* id, Release* rels, char* err, size_t errsz) char* body = http("GET", path, 25); err[0] = '\0'; if (!body) { - snprintf(err, errsz, "Port Manager service isn't running."); + snprintf(err, errsz, "Frametanium service isn't running."); return 0; } int n = 0; @@ -306,7 +306,7 @@ static int inspect(const char* repo, Found* fd, char* err, size_t errsz) { memset(fd, 0, sizeof *fd); err[0] = '\0'; if (!body) { - snprintf(err, errsz, "The Port Manager service didn't answer."); + snprintf(err, errsz, "The Frametanium service didn't answer."); return 0; } char* save; @@ -348,7 +348,7 @@ static void post(const char* path, char* err, size_t errsz) { if (err) { err[0] = '\0'; if (!body) - snprintf(err, errsz, "The Port Manager service didn't answer."); + snprintf(err, errsz, "The Frametanium service didn't answer."); else if (body[0] == '!' && body[1] == '\t') snprintf(err, errsz, "%.*s", (int)strcspn(body + 2, "\n"), body + 2); else if (strstr(body, "\"error\": \"")) @@ -372,7 +372,7 @@ static void try_start_service(void) { dup2(devnull, 1); dup2(devnull, 2); } - execlp("systemctl", "systemctl", "--user", "start", "frame-port-manager.service", (char*)NULL); + execlp("systemctl", "systemctl", "--user", "start", "frametanium.service", (char*)NULL); _exit(127); } if (pid > 0) waitpid(pid, NULL, 0); @@ -435,13 +435,13 @@ static int scroll_first(int sel, int max_rows) { static void draw_list(App* apps, int n, int sel, const char* banner, Uint32 banner_rgb) { clear(); - int m = W / 20, y = title("Port Manager"); + int m = W / 20, y = title("Frametanium"); if (banner && *banner) { text_fit(banner, m, y, S, W - 2 * m, banner_rgb); y += 14 * S; } if (n < 0) { - text("Starting the Port Manager service...", m, y + 8 * S, S, MUTED); + text("Starting the Frametanium service...", m, y + 8 * S, S, MUTED); legend("B Close"); SDL_RenderPresent(ren); return; @@ -594,7 +594,7 @@ static void draw_found(const Found* fd, const char* err) { text(line, m, y, S, MUTED); y += 14 * S; } - text_wrap("Port Manager unpacks it and either adds it to your Steam library or, if it comes with an install.sh, " + text_wrap("Frametanium unpacks it and either adds it to your Steam library or, if it comes with an install.sh, " "runs that. Only add apps you trust.", m, y, S, W - 2 * m, 3, MUTED); y += 40 * S; if (err && *err) text_wrap(err, m, y, S, W - 2 * m, 3, BAD); @@ -661,7 +661,7 @@ int main(void) { return 1; } Uint32 flags = getenv("PM_WINDOWED") ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP; - SDL_Window* win = SDL_CreateWindow("Port Manager", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); + SDL_Window* win = SDL_CreateWindow("Frametanium", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, flags); if (!win) return 1; ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!ren) ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_SOFTWARE); @@ -745,7 +745,7 @@ int main(void) { if (sel > napps) sel = napps > 0 ? napps : 0; snprintf(banner, sizeof banner, "%s", napps < 0 && tried_start && SDL_GetTicks() > 8000 - ? "The Port Manager service isn't running. Restart the Frame and try again." + ? "The Frametanium service isn't running. Restart the Frame and try again." : notice); } App* cur = sel < napps ? &apps[sel] : NULL; @@ -950,7 +950,7 @@ int main(void) { draw_wait(fd.name, "Adding it..."); char* body = http("POST", path, 30); if (!body) { - snprintf(err, sizeof err, "The Port Manager service didn't answer."); + snprintf(err, sizeof err, "The Frametanium service didn't answer."); } else if (body[0] == '!') { snprintf(err, sizeof err, "%.*s", (int)strcspn(body + 2, "\n"), body + 2); } else { diff --git a/app/build-arm64.sh b/app/build-arm64.sh index d76b2f2..9874091 100755 --- a/app/build-arm64.sh +++ b/app/build-arm64.sh @@ -10,15 +10,15 @@ OUT=../dist/app mkdir -p "$OUT/lib" "$LLVM/clang" --target=aarch64-linux-gnu --sysroot="$SYSROOT" -fuse-ld=lld -O2 -Wall -march=armv8.2-a \ -I"$SDL/include/SDL2" -D_REENTRANT app.c -L"$SDL/lib" -lSDL2-2.0 -Wl,-rpath,'$ORIGIN/lib' \ - -o "$OUT/port-manager" -"$LLVM/llvm-strip" "$OUT/port-manager" + -o "$OUT/frametanium" +"$LLVM/llvm-strip" "$OUT/frametanium" cp -L "$SDL/lib/libSDL2-2.0.so.0" "$OUT/lib/" cat > "$OUT/run.sh" <<'RUN' #!/bin/sh # Steam launches this. FrameDrop-style installs lose the executable bit, so restore it first. cd "$(dirname "$0")" || exit 1 -chmod +x ./port-manager 2>/dev/null -exec ./port-manager "$@" +chmod +x ./frametanium 2>/dev/null +exec ./frametanium "$@" RUN chmod +x "$OUT/run.sh" echo "built $OUT" diff --git a/art/app/logo.png b/art/app/logo.png index 26a4c9d..eab9602 100644 Binary files a/art/app/logo.png and b/art/app/logo.png differ diff --git a/art/app/portrait.png b/art/app/portrait.png index 9650c64..3157a19 100644 Binary files a/art/app/portrait.png and b/art/app/portrait.png differ diff --git a/art/app/wide.png b/art/app/wide.png index 93f8f8d..bc9e112 100644 Binary files a/art/app/wide.png and b/art/app/wide.png differ diff --git a/catalog.json b/catalog.json index 1df9ba2..4fcef88 100644 --- a/catalog.json +++ b/catalog.json @@ -32,7 +32,7 @@ "rom_hint": "Send your Majora's Mask ROM with Frame Inbox, or put a .z64 in ~/.local/share/2ship.", "run_sh": [ "#!/bin/sh", - "# Steam Frame launcher for 2 Ship 2 Harkinian (written by Port Manager). Data, saves and the built mm.o2r", + "# Steam Frame launcher for 2 Ship 2 Harkinian (written by Frametanium). Data, saves and the built mm.o2r", "# live in ~/.local/share/2ship, so reinstalling this folder keeps them.", "D=\"$(cd \"$(dirname \"$0\")\" && pwd)\"", "export SHIP_HOME=\"${SHIP_HOME:-$HOME/.local/share/2ship}\"", diff --git a/deploy.sh b/deploy.sh index 077a773..9e72749 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Installs or updates Port Manager on a Steam Frame from this computer over SSH: the service, and the -# "Port Manager" app in the Steam library with its art. +# Installs or updates Frametanium on a Steam Frame from this computer over SSH: the service, and the +# "Frametanium" app in the Steam library with its art. # # Usage: ./deploy.sh [host] host defaults to frame.local # Env: SSH_KEY (default ~/.ssh/frame_devkit), SSH_USER (default steamos) @@ -11,18 +11,20 @@ HOST="${1:-frame.local}" KEY="${SSH_KEY:-$HOME/.ssh/frame_devkit}" USER_AT="${SSH_USER:-steamos}@$HOST" SSH="ssh -i $KEY -o ConnectTimeout=10" -GAMEID=Port_Manager +GAMEID=Frametanium -[ -x dist/app/port-manager ] || { echo "Build the app first: app/build-arm64.sh"; exit 1; } +[ -x dist/app/frametanium ] || { echo "Build the app first: app/build-arm64.sh"; exit 1; } $SSH "$USER_AT" true || { echo "Can't reach $HOST over SSH. Is the Frame on and on this network?"; exit 1; } echo "== Service" -$SSH "$USER_AT" 'rm -rf ~/.cache/frame-port-manager-src && mkdir -p ~/.cache/frame-port-manager-src' -rsync -a -e "$SSH" manager.py page.html catalog.json suggestions.json frame-port-manager.service install.sh art "$USER_AT:.cache/frame-port-manager-src/" -$SSH "$USER_AT" 'sh ~/.cache/frame-port-manager-src/install.sh' +$SSH "$USER_AT" 'rm -rf ~/.cache/frametanium-src && mkdir -p ~/.cache/frametanium-src' +rsync -a -e "$SSH" manager.py page.html catalog.json suggestions.json frametanium.service install.sh art "$USER_AT:.cache/frametanium-src/" +$SSH "$USER_AT" 'sh ~/.cache/frametanium-src/install.sh' echo "== Headset app" rsync -a --delete -e "$SSH" dist/app/ "$USER_AT:devkit-game/$GAMEID/" +# The app used to be called Port Manager. Its old folder goes; remove the old "Port_Manager" tile from the library by hand. +$SSH "$USER_AT" 'rm -rf ~/devkit-game/Port_Manager' $SSH "$USER_AT" "python3 ~/devkit-utils/steam-client-create-shortcut --parms '{\"gameid\": \"$GAMEID\", \"directory\": \"/home/steamos/devkit-game/$GAMEID\", \"argv\": [\"./run.sh\"], \"env\": {}, \"settings\": {\"compat_tool\": \"SteamLinuxRuntime_4-arm64\", \"steam_play\": \"0\"}}'" >/dev/null echo "== Library art" @@ -42,4 +44,4 @@ if [ -n "$APPID" ]; then else echo "Couldn't find the shortcut's app id yet; run this again later to add the art." fi -echo "Done. Open \"Port Manager\" from the library, or http://$HOST:8643 in a browser." +echo "Done. Open \"Frametanium\" from the library, or http://$HOST:8643 in a browser." diff --git a/frame-port-manager.service b/frame-port-manager.service deleted file mode 100644 index 70aabe1..0000000 --- a/frame-port-manager.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Port Manager (installs and updates Steam Frame apps and ports from GitHub releases) -After=network-online.target - -[Service] -ExecStart=/usr/bin/python3 %h/.local/share/frame-port-manager/manager.py -Restart=on-failure -RestartSec=5 - -[Install] -WantedBy=default.target diff --git a/frametanium.service b/frametanium.service new file mode 100644 index 0000000..b95b5cd --- /dev/null +++ b/frametanium.service @@ -0,0 +1,11 @@ +[Unit] +Description=Frametanium (installs and updates Steam Frame apps and ports from GitHub releases) +After=network-online.target + +[Service] +ExecStart=/usr/bin/python3 %h/.local/share/frametanium/service/manager.py +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target diff --git a/install.sh b/install.sh index bc45415..8d3a6a4 100755 --- a/install.sh +++ b/install.sh @@ -1,16 +1,31 @@ #!/bin/sh -# Installs the Port Manager service on a Steam Frame for the current user. Run it on the Frame from this folder. +# Installs the Frametanium service on a Steam Frame for the current user. Run it on the Frame from this folder. set -e here="$(cd "$(dirname "$0")" && pwd)" -dest="$HOME/.local/share/frame-port-manager" -mkdir -p "$dest" "$HOME/.config/systemd/user" +dest="$HOME/.local/share/frametanium/service" +mkdir -p "$HOME/.config/systemd/user" + +# Frametanium used to be called Port Manager: stop the old service and carry its settings, installed +# versions and added apps over to the new folders. +if [ -f "$HOME/.config/systemd/user/frame-port-manager.service" ]; then + systemctl --user disable --now frame-port-manager.service >/dev/null 2>&1 || true + rm -f "$HOME/.config/systemd/user/frame-port-manager.service" +fi +rm -rf "$HOME/.local/share/frame-port-manager" "$HOME/.cache/port-manager" +for base in "$HOME/.config" "$HOME/.local/share"; do + if [ -d "$base/port-manager" ] && [ ! -e "$base/frametanium" ]; then + mv "$base/port-manager" "$base/frametanium" + fi +done + +mkdir -p "$dest" cp "$here/manager.py" "$here/page.html" "$here/catalog.json" "$here/suggestions.json" "$dest/" rm -rf "$dest/art" && cp -r "$here/art" "$dest/art" -cp "$here/frame-port-manager.service" "$HOME/.config/systemd/user/" +cp "$here/frametanium.service" "$HOME/.config/systemd/user/" systemctl --user daemon-reload -systemctl --user enable frame-port-manager.service >/dev/null 2>&1 -systemctl --user restart frame-port-manager.service +systemctl --user enable frametanium.service >/dev/null 2>&1 +systemctl --user restart frametanium.service sleep 1 ip=$(python3 -c 'import socket;s=socket.socket(2,2);s.connect(("192.0.2.1",9));print(s.getsockname()[0])' 2>/dev/null || hostname) -echo "Port Manager is running: http://$ip:8643" -echo "Uninstall: systemctl --user disable --now frame-port-manager.service" +echo "Frametanium is running: http://$ip:8643" +echo "Uninstall: systemctl --user disable --now frametanium.service" diff --git a/manager.py b/manager.py index 13a3cf1..2ab397b 100755 --- a/manager.py +++ b/manager.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Port Manager: installs and updates Steam Frame apps and ports from their GitHub releases, like Obtainium. +"""Frametanium: installs and updates Steam Frame apps and ports from their GitHub releases, like Obtainium. A small web service (standard library only) that runs on the Frame. It tracks a list of GitHub repos: the built-in ports in catalog.json plus any repo you add. For each it reads the releases, picks the download meant for the Frame @@ -33,14 +33,14 @@ from pathlib import Path HOME = Path.home() APP_DIR = Path(__file__).resolve().parent -CONFIG_DIR = Path(os.environ.get("XDG_CONFIG_HOME", HOME / ".config")) / "port-manager" +CONFIG_DIR = Path(os.environ.get("XDG_CONFIG_HOME", HOME / ".config")) / "frametanium" CONFIG_FILE = CONFIG_DIR / "config.json" INSTALLED_FILE = CONFIG_DIR / "installed.json" USER_CATALOG = CONFIG_DIR / "catalog.json" -CACHE_DIR = Path(os.environ.get("XDG_CACHE_HOME", HOME / ".cache")) / "port-manager" -DATA_DIR = Path(os.environ.get("XDG_DATA_HOME", HOME / ".local/share")) / "port-manager" +CACHE_DIR = Path(os.environ.get("XDG_CACHE_HOME", HOME / ".cache")) / "frametanium" +DATA_DIR = Path(os.environ.get("XDG_DATA_HOME", HOME / ".local/share")) / "frametanium" APPS_DIR = DATA_DIR / "apps" # installs of "installer" apps (their installers may point back into them) -GAMES_DIR = Path(os.environ.get("PORT_MANAGER_GAMES", HOME / "devkit-game")) +GAMES_DIR = Path(os.environ.get("FRAMETANIUM_GAMES", HOME / "devkit-game")) CREATE_SHORTCUT = HOME / "devkit-utils" / "steam-client-create-shortcut" STEAM_USERDATA = HOME / ".local/share/Steam/userdata" @@ -123,7 +123,7 @@ def unhide_builtin(repo): def gh_request(url, accept="application/vnd.github+json"): - headers = {"Accept": accept, "User-Agent": "frame-port-manager", "X-GitHub-Api-Version": "2022-11-28"} + headers = {"Accept": accept, "User-Agent": "frametanium", "X-GitHub-Api-Version": "2022-11-28"} token = config().get("github_token") if token: headers["Authorization"] = f"Bearer {token}" @@ -402,7 +402,7 @@ def register_with_steam(port, folder): def fetch(url, dest, limit=8 * 1024 * 1024): - req = urllib.request.Request(url, headers={"User-Agent": "frame-port-manager"}) + req = urllib.request.Request(url, headers={"User-Agent": "frametanium"}) with urllib.request.urlopen(req, timeout=20) as r: data = r.read(limit) if not data.startswith(b"\x89PNG") and not data.startswith(b"\xff\xd8"): @@ -498,8 +498,8 @@ def extract_zip(archive, dest): def extract_appimage(archive, dest): os.chmod(archive, 0o755) - # PORT_MANAGER_EXEC_WRAPPER (e.g. qemu-aarch64-static) lets tests on another architecture run the AppImage. - wrapper = os.environ.get("PORT_MANAGER_EXEC_WRAPPER", "").split() + # FRAMETANIUM_EXEC_WRAPPER (e.g. qemu-aarch64-static) lets tests on another architecture run the AppImage. + wrapper = os.environ.get("FRAMETANIUM_EXEC_WRAPPER", "").split() try: r = subprocess.run(wrapper + [str(archive), "--appimage-extract"], cwd=dest, capture_output=True, timeout=600) except OSError as e: @@ -642,7 +642,7 @@ def write_launcher(staging, port): folder = "/" + str(rel.parent) if str(rel.parent) != "." else "" launch.write_text( "#!/bin/sh\n" - f"# Starts {port['name']} (written by Port Manager).\n" + f"# Starts {port['name']} (written by Frametanium).\n" 'D="$(cd "$(dirname "$0")" && pwd)"\n' f'chmod +x "$D/{rel}" 2>/dev/null\n' f'cd "$D{folder}" && exec "./{rel.name}" "$@"\n') @@ -996,7 +996,7 @@ def tsv(rows, keys): class Handler(BaseHTTPRequestHandler): - server_version = "PortManager/2.0" + server_version = "Frametanium/2.0" def log_message(self, fmt, *args): sys.stderr.write("%s %s\n" % (self.address_string(), fmt % args)) @@ -1169,7 +1169,7 @@ def main(): threading.Thread(target=checker, daemon=True).start() server = ThreadingHTTPServer(("0.0.0.0", int(cfg["port"])), Handler) server.daemon_threads = True - print(f"Port Manager on port {cfg['port']}", flush=True) + print(f"Frametanium on port {cfg['port']}", flush=True) server.serve_forever() diff --git a/page.html b/page.html index 308fa98..7325399 100644 --- a/page.html +++ b/page.html @@ -3,7 +3,7 @@ -Port Manager +Frametanium