mirror of
https://github.com/Cogential/frametanium.git
synced 2026-10-28 00:00:26 +01:00
Discover new Steam Frame apps on GitHub
Searches GitHub (steam-frame topic, "steam frame" in names/descriptions) and the awesome-steam-frame list, checks each project's releases for a download the Frame can install, and lists them with New badges, in the headset app (X / Discover row) and on the web page. Cached, rechecked only when a repo changes, and limited per run so update checks keep enough of GitHub's unauthenticated rate limit. 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
c6b3edf252
commit
dca76585bc
4 files changed
+434
-19
No files matched your search
@@ -3,11 +3,11 @@
|
||||
* tab-separated replies.
|
||||
*
|
||||
* Screens: the app list; an app's menu (install/update, versions, previous version, pre-releases, remove); adding
|
||||
* an app (type a GitHub repo on an on-screen keyboard, paste a link, or pick a suggestion); and the found repo,
|
||||
* where you pick its download and add it.
|
||||
* an app (type a GitHub repo on an on-screen keyboard, paste a link, or pick a suggestion); discover (new Steam Frame
|
||||
* projects on GitHub with a release to install); and the found repo, where you pick its download and add it.
|
||||
*
|
||||
* Controls: D-pad/left stick to choose, A select, B back/close, Y add an app, Select update all, Start check for
|
||||
* updates. Keyboard: arrows, Enter, Esc, and typing on the on-screen keyboard screen. */
|
||||
* Controls: D-pad/left stick to choose, A select, B back/close, Y add an app, X discover, Select update all, Start
|
||||
* check for updates. Keyboard: arrows, Enter, Esc, and typing on the on-screen keyboard screen. */
|
||||
|
||||
#include <SDL.h>
|
||||
#include <arpa/inet.h>
|
||||
@@ -28,6 +28,7 @@
|
||||
#define MAX_RELEASES 40
|
||||
#define MAX_SUGGEST 32
|
||||
#define MAX_ASSETS 16
|
||||
#define MAX_DISC 64
|
||||
|
||||
typedef struct {
|
||||
char id[64], name[128], installed[64], latest[64], status[32], message[256], repo[128], mode[16], job[16];
|
||||
@@ -44,6 +45,11 @@ typedef struct {
|
||||
char repo[128], name[128], note[160];
|
||||
} Suggestion;
|
||||
|
||||
typedef struct {
|
||||
char repo[128], name[128], description[300], tag[64], date[16], asset[128];
|
||||
int stars, arm64, prerelease, is_new;
|
||||
} Disc;
|
||||
|
||||
typedef struct {
|
||||
char repo[128], name[128], description[300], tag[64], date[16], existing[64];
|
||||
int prereleases, nassets, asset;
|
||||
@@ -297,6 +303,41 @@ static int load_suggestions(Suggestion* sug) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/* New Steam Frame projects found on GitHub; running says a search is going, msg is its progress. */
|
||||
static int load_discover(Disc* d, int* running, char* msg, size_t msz, int* nnew) {
|
||||
char* body = http("GET", "/api/discover?fmt=tsv", 5);
|
||||
*running = 0;
|
||||
*nnew = 0;
|
||||
msg[0] = '\0';
|
||||
if (!body) return -1;
|
||||
int n = 0;
|
||||
char* save;
|
||||
EACH_LINE(body, line, save) {
|
||||
char* f[11];
|
||||
int k = split(line, f, 11);
|
||||
if (k >= 4 && !strcmp(f[0], "#")) {
|
||||
*running = atoi(f[1]);
|
||||
copy(msg, msz, f[2]);
|
||||
*nnew = atoi(f[3]);
|
||||
continue;
|
||||
}
|
||||
if (k < 10 || n >= MAX_DISC) continue;
|
||||
Disc* x = &d[n++];
|
||||
copy(x->repo, sizeof x->repo, f[0]);
|
||||
copy(x->name, sizeof x->name, f[1]);
|
||||
copy(x->description, sizeof x->description, f[2]);
|
||||
x->stars = atoi(f[3]);
|
||||
copy(x->tag, sizeof x->tag, f[4]);
|
||||
copy(x->date, sizeof x->date, f[5]);
|
||||
copy(x->asset, sizeof x->asset, f[6]);
|
||||
x->arm64 = atoi(f[7]);
|
||||
x->prerelease = atoi(f[8]);
|
||||
x->is_new = atoi(f[9]);
|
||||
}
|
||||
free(body);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Looks a repo up; fills fd and returns 1, or writes the problem to err and returns 0. */
|
||||
static int inspect(const char* repo, Found* fd, char* err, size_t errsz) {
|
||||
char enc[600], path[700];
|
||||
@@ -433,7 +474,7 @@ static int scroll_first(int sel, int max_rows) {
|
||||
return sel >= max_rows ? sel - max_rows + 1 : 0;
|
||||
}
|
||||
|
||||
static void draw_list(App* apps, int n, int sel, const char* banner, Uint32 banner_rgb) {
|
||||
static void draw_list(App* apps, int n, int sel, const char* banner, Uint32 banner_rgb, int disc_new) {
|
||||
clear();
|
||||
int m = W / 20, y = title("Frametanium");
|
||||
if (banner && *banner) {
|
||||
@@ -450,13 +491,19 @@ static void draw_list(App* apps, int n, int sel, const char* banner, Uint32 bann
|
||||
int max_rows = (H - y - 40 * S) / row_h;
|
||||
if (max_rows < 1) max_rows = 1;
|
||||
int first = scroll_first(sel, max_rows);
|
||||
for (int i = first; i <= n && i < first + max_rows; i++) {
|
||||
for (int i = first; i <= n + 1 && i < first + max_rows; i++) {
|
||||
int x = m, w = W - 2 * m;
|
||||
if (i == n) { /* the "add" row */
|
||||
if (i >= n) { /* the "discover" and "add" rows */
|
||||
fill(x, y, w, 26 * S, i == sel ? ROW_SEL : ROW);
|
||||
if (i == sel) fill(x, y, 3 * S, 26 * S, ACCENT);
|
||||
text("+ Add an app from GitHub", x + 10 * S, y + 9 * S, S, ACCENT);
|
||||
break;
|
||||
char label[100];
|
||||
if (i == n && disc_new > 0)
|
||||
snprintf(label, sizeof label, "* Discover new apps for the Frame (%d new)", disc_new);
|
||||
else
|
||||
snprintf(label, sizeof label, "%s", i == n ? "* Discover new apps for the Frame" : "+ Add an app from GitHub");
|
||||
text(label, x + 10 * S, y + 9 * S, S, ACCENT);
|
||||
y += 32 * S;
|
||||
continue;
|
||||
}
|
||||
App* p = &apps[i];
|
||||
fill(x, y, w, row_h - 6 * S, i == sel ? ROW_SEL : ROW);
|
||||
@@ -481,7 +528,7 @@ static void draw_list(App* apps, int n, int sel, const char* banner, Uint32 bann
|
||||
}
|
||||
y += row_h;
|
||||
}
|
||||
legend("A Open Y Add app Select Update all Start Check B Close");
|
||||
legend("A Open Y Add X Discover Select Update all Start Check B Close");
|
||||
SDL_RenderPresent(ren);
|
||||
}
|
||||
|
||||
@@ -520,6 +567,49 @@ static void draw_wait(const char* heading, const char* what) {
|
||||
SDL_RenderPresent(ren);
|
||||
}
|
||||
|
||||
static void draw_discover(const Disc* d, int n, int sel, int running, const char* msg, const char* err) {
|
||||
clear();
|
||||
int m = W / 20, y = title("Discover new apps for the Frame");
|
||||
const char* sub = running ? (msg[0] ? msg : "Searching GitHub for Steam Frame projects...")
|
||||
: msg[0] ? msg : "Steam Frame projects on GitHub with a release you can install.";
|
||||
text_fit(sub, m, y, S, W - 2 * m, running ? ACCENT : MUTED);
|
||||
y += 16 * S;
|
||||
if (err && *err) {
|
||||
text_fit(err, m, y, S, W - 2 * m, BAD);
|
||||
y += 14 * S;
|
||||
}
|
||||
if (n <= 0) {
|
||||
text(n < 0 ? "The Frametanium service didn't answer." : running ? "" : "Nothing new right now. Start searches again.",
|
||||
m, y + 8 * S, S, MUTED);
|
||||
}
|
||||
int row_h = 50 * S;
|
||||
int max_rows = (H - y - 40 * S) / row_h;
|
||||
if (max_rows < 1) max_rows = 1;
|
||||
int first = scroll_first(sel, max_rows);
|
||||
for (int i = first; i < n && i < first + max_rows; i++) {
|
||||
const Disc* x = &d[i];
|
||||
int w = W - 2 * m, tx = m + 10 * S, ty = y + 8 * S;
|
||||
fill(m, y, w, row_h - 6 * S, i == sel ? ROW_SEL : ROW);
|
||||
if (i == sel) fill(m, y, 3 * S, row_h - 6 * S, ACCENT);
|
||||
int nw = 0;
|
||||
if (x->is_new) {
|
||||
nw = 6 * 8 * S;
|
||||
text("NEW", m + w - 10 * S - 3 * 8 * S, ty, S, ACCENT);
|
||||
}
|
||||
text_fit(x->name, tx, ty, S, w - 20 * S - nw, TXT);
|
||||
ty += 13 * S;
|
||||
if (x->description[0]) text_fit(x->description, tx, ty, S, w - 20 * S, MUTED);
|
||||
ty += 12 * S;
|
||||
char line[400];
|
||||
snprintf(line, sizeof line, "%s %s%s %s %d stars%s", x->repo, x->tag, x->prerelease ? " (pre)" : "", x->date,
|
||||
x->stars, x->arm64 ? "" : " not marked ARM64");
|
||||
text_fit(line, tx, ty, S, w - 20 * S, x->arm64 ? MUTED : WARN);
|
||||
y += row_h;
|
||||
}
|
||||
legend("A Look at it X Not interested Start Search again B Back");
|
||||
SDL_RenderPresent(ren);
|
||||
}
|
||||
|
||||
/* ---- on-screen keyboard */
|
||||
|
||||
static const char* KB_ROWS[] = { "1234567890", "qwertyuiop", "asdfghjkl-", "zxcvbnm_./" };
|
||||
@@ -605,7 +695,7 @@ static void draw_found(const Found* fd, const char* err) {
|
||||
/* ---------------------------------------------------------------- main */
|
||||
|
||||
enum { NONE, UP, DOWN, LEFT, RIGHT, OK, BACK, ADD, UPDATE_ALL, REFRESH, DELETE };
|
||||
enum { LIST, MENU, VERSIONS, CONFIRM, ADDING, KEYBOARD, FOUND };
|
||||
enum { LIST, MENU, VERSIONS, CONFIRM, ADDING, KEYBOARD, FOUND, DISCOVER };
|
||||
|
||||
static int map_button(int b) {
|
||||
switch (b) {
|
||||
@@ -639,13 +729,14 @@ static int map_key(SDL_Keycode k, int typing) {
|
||||
case SDLK_a: return ADD;
|
||||
case SDLK_u: return UPDATE_ALL;
|
||||
case SDLK_r: return REFRESH;
|
||||
case SDLK_x: return DELETE;
|
||||
default: return NONE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* PM_SCREENSHOT=<file.bmp> draws one frame into memory, saves it and exits (testing without a display);
|
||||
* PM_SIZE=WxH sets its size, PM_SCREEN=menu|versions|add|keyboard|found picks the screen (for the first app;
|
||||
* PM_SIZE=WxH sets its size, PM_SCREEN=menu|versions|add|keyboard|found|discover picks the screen (for the first app;
|
||||
* PM_REPO is the repo for "found", PM_TYPED the text for "keyboard"). */
|
||||
const char* shot = getenv("PM_SCREENSHOT");
|
||||
SDL_Surface* shot_surface = NULL;
|
||||
@@ -675,7 +766,11 @@ int main(void) {
|
||||
static Release rels[MAX_RELEASES];
|
||||
static Suggestion sug[MAX_SUGGEST];
|
||||
static Found fd;
|
||||
static Disc disc[MAX_DISC];
|
||||
int napps = -1, sel = 0, nrels = 0, rsel = 0, msel = 0, asel = 0, nsug = 0, tried_start = 0;
|
||||
int ndisc = 0, dsel = 0, disc_running = 0, disc_new = 0, found_from = ADDING;
|
||||
char disc_msg[300] = "";
|
||||
Uint32 next_disc_poll = 0;
|
||||
int screen = LIST, kb_row = 1, kb_col = 0, notice_busy = 0;
|
||||
char err[300] = "", banner[300] = "", notice[300] = "", entry[256] = "", want_id[64] = "";
|
||||
char hint_name[128] = "";
|
||||
@@ -707,6 +802,8 @@ int main(void) {
|
||||
screen = FOUND;
|
||||
}
|
||||
if (!strcmp(sc, "confirm")) screen = CONFIRM;
|
||||
if (!strcmp(sc, "discover")) screen = DISCOVER;
|
||||
ndisc = load_discover(disc, &disc_running, disc_msg, sizeof disc_msg, &disc_new);
|
||||
next_poll = 0xffffffff;
|
||||
}
|
||||
|
||||
@@ -742,24 +839,34 @@ int main(void) {
|
||||
sel = i;
|
||||
want_id[0] = '\0';
|
||||
}
|
||||
if (sel > napps) sel = napps > 0 ? napps : 0;
|
||||
if (sel > napps + 1) sel = napps > 0 ? napps : 0;
|
||||
snprintf(banner, sizeof banner, "%s",
|
||||
napps < 0 && tried_start && SDL_GetTicks() > 8000
|
||||
? "The Frametanium service isn't running. Restart the Frame and try again."
|
||||
: notice);
|
||||
}
|
||||
if (SDL_GetTicks() >= next_disc_poll) {
|
||||
ndisc = load_discover(disc, &disc_running, disc_msg, sizeof disc_msg, &disc_new);
|
||||
next_disc_poll = SDL_GetTicks() + (screen == DISCOVER && disc_running ? 1500 : 30000);
|
||||
if (dsel >= ndisc) dsel = ndisc > 0 ? ndisc - 1 : 0;
|
||||
}
|
||||
App* cur = sel < napps ? &apps[sel] : NULL;
|
||||
|
||||
switch (screen) {
|
||||
case LIST:
|
||||
if (act == UP && sel > 0) sel--;
|
||||
if (act == DOWN && sel < napps) sel++;
|
||||
if (act == DOWN && sel < napps + 1) sel++;
|
||||
if (act == BACK) goto done;
|
||||
if (napps < 0) break;
|
||||
if (act == OK && cur) {
|
||||
screen = MENU;
|
||||
msel = 0;
|
||||
err[0] = '\0';
|
||||
} else if (act == DELETE || (act == OK && sel == napps)) {
|
||||
screen = DISCOVER;
|
||||
dsel = 0;
|
||||
err[0] = '\0';
|
||||
next_disc_poll = 0;
|
||||
} else if (act == ADD || (act == OK && !cur)) {
|
||||
draw_wait("Add an app from GitHub", "Loading suggestions...");
|
||||
nsug = load_suggestions(sug);
|
||||
@@ -877,6 +984,7 @@ int main(void) {
|
||||
if (inspect(repo, &fd, err, sizeof err)) {
|
||||
if (hint_name[0]) snprintf(fd.name, sizeof fd.name, "%s", hint_name);
|
||||
err[0] = '\0';
|
||||
found_from = ADDING;
|
||||
screen = FOUND;
|
||||
}
|
||||
}
|
||||
@@ -885,6 +993,36 @@ int main(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
case DISCOVER:
|
||||
if (act == UP && dsel > 0) dsel--;
|
||||
if (act == DOWN && dsel < ndisc - 1) dsel++;
|
||||
if (act == BACK) {
|
||||
if (disc_new > 0) post("/api/discover/seen", NULL, 0); /* everything listed has now been seen */
|
||||
screen = LIST;
|
||||
next_disc_poll = SDL_GetTicks() + 300;
|
||||
}
|
||||
if (act == REFRESH && !disc_running) {
|
||||
post("/api/discover/refresh", NULL, 0);
|
||||
next_disc_poll = SDL_GetTicks() + 400;
|
||||
}
|
||||
if (act == DELETE && dsel < ndisc) {
|
||||
char enc[300], path[400];
|
||||
urlenc(enc, sizeof enc, disc[dsel].repo);
|
||||
snprintf(path, sizeof path, "/api/discover/hide?repo=%s", enc);
|
||||
post(path, NULL, 0);
|
||||
next_disc_poll = 0;
|
||||
}
|
||||
if (act == OK && dsel < ndisc) {
|
||||
draw_wait(disc[dsel].name, "Looking it up on GitHub...");
|
||||
if (inspect(disc[dsel].repo, &fd, err, sizeof err)) {
|
||||
snprintf(fd.name, sizeof fd.name, "%s", disc[dsel].name);
|
||||
err[0] = '\0';
|
||||
found_from = DISCOVER;
|
||||
screen = FOUND;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KEYBOARD: {
|
||||
size_t len = strlen(entry);
|
||||
if (typed && len + strlen(typed) < sizeof entry - 1) strcat(entry, typed);
|
||||
@@ -923,6 +1061,7 @@ int main(void) {
|
||||
if (inspect(entry, &fd, err, sizeof err)) {
|
||||
err[0] = '\0';
|
||||
SDL_StopTextInput();
|
||||
found_from = ADDING;
|
||||
screen = FOUND;
|
||||
}
|
||||
}
|
||||
@@ -930,7 +1069,10 @@ int main(void) {
|
||||
}
|
||||
|
||||
case FOUND:
|
||||
if (act == BACK) screen = ADDING;
|
||||
if (act == BACK) {
|
||||
screen = found_from;
|
||||
err[0] = '\0';
|
||||
}
|
||||
if (fd.existing[0]) {
|
||||
if (act == OK) {
|
||||
snprintf(want_id, sizeof want_id, "%s", fd.existing);
|
||||
@@ -958,6 +1100,7 @@ int main(void) {
|
||||
screen = LIST;
|
||||
entry[0] = '\0';
|
||||
next_poll = 0;
|
||||
next_disc_poll = 0;
|
||||
}
|
||||
free(body);
|
||||
}
|
||||
@@ -1006,10 +1149,12 @@ int main(void) {
|
||||
draw_keyboard(entry, kb_row, kb_col, err);
|
||||
} else if (screen == FOUND) {
|
||||
draw_found(&fd, err);
|
||||
} else if (screen == DISCOVER) {
|
||||
draw_discover(disc, ndisc, dsel, disc_running, disc_msg, err);
|
||||
} else {
|
||||
if (screen != LIST) screen = LIST;
|
||||
int is_err = strstr(banner, "isn't running") != NULL;
|
||||
draw_list(apps, napps, sel, banner, is_err ? WARN : OK_C);
|
||||
draw_list(apps, napps, sel, banner, is_err ? WARN : OK_C, disc_new);
|
||||
}
|
||||
if (shot) {
|
||||
SDL_SaveBMP(shot_surface, shot);
|
||||
|
||||
Reference in new issue
Block a user