mirror of
https://github.com/Cogential/frametanium.git
synced 2026-10-17 15:00:28 +02:00
Rename Port Manager to Frametanium
Avoids confusion with the other Steam Frame project called FramePort. New service frametanium.service, folders ~/.config/frametanium and ~/.local/share/frametanium (service code in its service/ subfolder), headset app Frametanium. install.sh moves an existing Port Manager install's settings and installed versions over and stops the old service. Library art regenerated by the new tools/make_art.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHDaz86aayqMULHsjByEmT
This commit is contained in:
1 parent
94865cb34f
commit
c6b3edf252
14 files changed
+136
-70
No files matched your search
@@ -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/<name>`, 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/<id>` and that script runs, for tools such as SteamVR overlays. Removing the app
|
||||
`~/.local/share/frametanium/apps/<id>` 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://<frame-ip>: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.
|
||||
@@ -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 {
|
||||
|
||||
+4
-4
@@ -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"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
+1
-1
@@ -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}\"",
|
||||
|
||||
@@ -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."
|
||||
@@ -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
|
||||
@@ -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
|
||||
+23
-8
@@ -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"
|
||||
+12
-12
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Port Manager</title>
|
||||
<title>Frametanium</title>
|
||||
<style>
|
||||
:root { --bg: #0f1216; --panel: #181d23; --line: #2a313a; --text: #e8edf2; --muted: #93a0ad;
|
||||
--accent: #4fa3ff; --ok: #4cc38a; --warn: #f0b429; --bad: #f2555a; }
|
||||
@@ -49,7 +49,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="top"><h1>Port Manager</h1>
|
||||
<div class="top"><h1>Frametanium</h1>
|
||||
<div class="row"><button id="updateall">Update all</button><button id="refresh">Check for updates</button></div></div>
|
||||
<p class="sub">Install and update Steam Frame apps and ports from their GitHub releases. Saves and ROMs are kept.</p>
|
||||
<p class="notice" id="notice" hidden></p>
|
||||
@@ -88,7 +88,7 @@ const open = {}; // app ids whose Edit section is open, kep
|
||||
async function get(path) {
|
||||
const r = await fetch(path, { headers: hdr() });
|
||||
if (r.status === 401) {
|
||||
key = prompt("Port Manager password") || "";
|
||||
key = prompt("Frametanium password") || "";
|
||||
try { localStorage.setItem("pmKey", key); } catch (e) {}
|
||||
return get(path);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Draws Frametanium's library art into art/app (needs Pillow and the DejaVu fonts). Run from the repo root."""
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import subprocess, math
|
||||
|
||||
NAME = "Frametanium"
|
||||
bold = subprocess.run(["fc-match", "-f", "%{file}", "DejaVu Sans:bold"], capture_output=True, text=True).stdout
|
||||
reg = subprocess.run(["fc-match", "-f", "%{file}", "DejaVu Sans"], capture_output=True, text=True).stdout
|
||||
BG = (15, 18, 22); ACC = (79, 163, 255); TXT = (232, 237, 242); MUT = (147, 160, 173)
|
||||
|
||||
|
||||
def glow(W, H, c):
|
||||
im = Image.new("RGB", (W, H), BG); d = ImageDraw.Draw(im); R = int(max(W, H) * 0.6)
|
||||
for r in range(R, 0, -8):
|
||||
t = r / R; col = tuple(int(BG[i] * t + (25, 45, 75)[i] * (1 - t)) for i in range(3))
|
||||
d.ellipse([c[0] - r, c[1] - r, c[0] + r, c[1] + r], fill=col)
|
||||
return im
|
||||
|
||||
|
||||
def icon(d, cx, cy, s):
|
||||
"""A cartridge with a circular update arrow."""
|
||||
lw = max(4, s // 16)
|
||||
w = int(s * 0.78); h = int(s * 0.9); x0 = cx - w // 2; y0 = cy - h // 2
|
||||
d.rounded_rectangle([x0, y0, x0 + w, y0 + h], radius=s // 10, outline=TXT, width=lw)
|
||||
d.rectangle([x0 + w * 0.2, y0 + h * 0.12, x0 + w * 0.8, y0 + h * 0.42], outline=TXT, width=lw)
|
||||
for k in range(5):
|
||||
x = x0 + w * 0.18 + k * w * 0.16; d.line([(x, y0 + h * 0.84), (x, y0 + h * 0.96)], fill=TXT, width=lw)
|
||||
r = s * 0.2; ax, ay = cx, cy + h * 0.18
|
||||
d.arc([ax - r, ay - r, ax + r, ay + r], start=200, end=500, fill=ACC, width=lw)
|
||||
ang = math.radians(140); ex, ey = ax + r * math.cos(ang), ay + r * math.sin(ang)
|
||||
d.polygon([(ex - s * 0.08, ey - s * 0.02), (ex + s * 0.05, ey - s * 0.06), (ex + s * 0.01, ey + s * 0.08)], fill=ACC)
|
||||
|
||||
|
||||
def make(W, H, c, s, title=None, txy=None, ts=0, sub=None):
|
||||
im = glow(W, H, c); d = ImageDraw.Draw(im); icon(d, c[0], c[1], s)
|
||||
if title:
|
||||
d.text(txy, title, font=ImageFont.truetype(bold, ts), fill=TXT, anchor="mm")
|
||||
if sub:
|
||||
d.text((txy[0], txy[1] + int(ts * 0.95)), sub, font=ImageFont.truetype(reg, int(ts * 0.38)), fill=MUT, anchor="mm")
|
||||
return im
|
||||
|
||||
|
||||
make(600, 900, (300, 320), 280, NAME, (300, 620), 70, "Apps & ports from GitHub").save("art/app/portrait.png")
|
||||
make(920, 430, (170, 215), 230, NAME, (600, 190), 64, "Updates from GitHub releases").save("art/app/wide.png")
|
||||
make(1920, 620, (1500, 310), 330).save("art/app/hero.png")
|
||||
im = Image.new("RGB", (256, 256), BG); icon(ImageDraw.Draw(im), 128, 128, 190); im.save("art/app/icon.png")
|
||||
lg = Image.new("RGBA", (1000, 220), (0, 0, 0, 0))
|
||||
ImageDraw.Draw(lg).text((500, 110), NAME, font=ImageFont.truetype(bold, 120), fill=TXT + (255,), anchor="mm")
|
||||
lg.save("art/app/logo.png")
|
||||
Reference in new issue
Block a user