mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-19 02:20:02 -05:00
helper web UI: free seen jobs, hide the tile from the PC library, games-only Close
- /api/jobs releases the page's finished jobs after replying (ava1_jobs_release_seen), so batches cannot fill the shared table. - list_registered_titles_json leaves out WEBUI_TILE_TITLE_ID; a PC's APP_UNREGISTER of the tile is Settings -> tile off (saved tile:false, tile thread removes it) so the next start does not re-register it. - Now running: the focused app is never the tile; the fallback names only a game title id (PPSA/CUSA/... prefixes), never the browser, the tile or homebrew. Codec selftest covers it. - /api/settings carries tile_busy/tile_failed (webui_tile_state); the tile request is made before the reply. - put_str/r_status clamp `at` after every escape/printf; send_error and r_fs_delete clamp too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
This commit is contained in:
1 parent
9d2625850e
commit
284d22c6e5
9 files changed
+178
-32
No files matched your search
@@ -21,6 +21,13 @@
|
||||
* /api/settings answers them and /api/settings/set changes them. Call once, before webui_start. */
|
||||
void webui_api_settings_init(const webui_settings_t *s);
|
||||
|
||||
/* A paired PC unregistered the tile (APP_UNREGISTER of WEBUI_TILE_TITLE_ID): the same as Settings ->
|
||||
* tile off. Saves tile:false to WEBUI_SETTINGS_PATH (so the next start does not register it again)
|
||||
* and asks the tile thread to remove it (webui_tile_request(0): unregister, then the tile folder and
|
||||
* the staged /user/app copy are deleted). Returns at once; 0, or -1 when the save failed (the
|
||||
* removal is requested either way). */
|
||||
int webui_api_tile_off(void);
|
||||
|
||||
int webui_api_route(const webui_req_t *r, const uint8_t *body, size_t body_len, int fd);
|
||||
|
||||
#endif
|
||||
@@ -67,11 +67,17 @@ const char *webui_mime_for(const char *path);
|
||||
int webui_kv_str(const char *text, size_t n, const char *key, char *out, size_t cap);
|
||||
int webui_kv_int(const char *text, size_t n, const char *key, long *out);
|
||||
|
||||
/* 1 when `tid` is a game's title id: four letters of a game prefix (PPSA, CUSA, PCJS, PLJS, ...:
|
||||
* Sony's disc and digital game ranges for every region) and five digits. Homebrew (FAKE..., PLDM...,
|
||||
* WKAL...), system apps (NPXS...) and the page's own tile (WEBUI_TILE_TITLE_ID) are not games. */
|
||||
int webui_is_game_title_id(const char *tid);
|
||||
|
||||
/* From a PROC_FOCUS reply ({"apps":[{"title_id":..,"app_id":..},...],"focus_app_id":N,
|
||||
* "big_app_id":N,...}): the running game. Candidates are the apps whose title id is set and is
|
||||
* not a system app (NPXS...); the one that owns the screen (focus_app_id, else big_app_id) wins,
|
||||
* else the first candidate. Returns 1 with title_id / *app_id set, 0 when no game runs, -1 when
|
||||
* the reply does not parse. */
|
||||
* "big_app_id":N,...}): what Home shows as running (and may close). First the app that owns the
|
||||
* screen (focus_app_id, else big_app_id) when its title id is set, not a system app (NPXS...) and
|
||||
* not the page's own tile; else the first app whose title id is a game's (webui_is_game_title_id),
|
||||
* so the fallback never names the browser, the tile or a homebrew app. Returns 1 with title_id /
|
||||
* *app_id set, 0 when no game runs, -1 when the reply does not parse. */
|
||||
int webui_focus_running(const char *json, size_t n, char *title_id, size_t cap, uint32_t *app_id);
|
||||
|
||||
#endif
|
||||
@@ -65,6 +65,10 @@ int webui_tile_remove(void);
|
||||
* applies the LATEST request and repeats while new ones arrive, so ensure and remove never run
|
||||
* at the same time and a fast on/off/on ends in the last state asked for. Failures are logged. */
|
||||
void webui_tile_request(int tile, uint16_t port);
|
||||
|
||||
/* What the page shows after a tile switch: *busy 1 while the worker still applies a request,
|
||||
* *failed 1 when the last ensure/remove it applied failed (the tile is not in the state asked for). */
|
||||
void webui_tile_state(int *busy, int *failed);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "authid.h"
|
||||
#include "config.h"
|
||||
#include "register.h"
|
||||
#include "sony_api_lock.h"
|
||||
#include "proc_list.h"
|
||||
@@ -1116,6 +1117,9 @@ int list_registered_titles_json(char *out_json,
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
const char *tid = entry->d_name;
|
||||
if (!looks_like_title_id(tid)) continue;
|
||||
/* The helper's own home-screen tile (webui_tile.c) is not a game: the PC app's library must
|
||||
* not list it (or offer to uninstall it as one). */
|
||||
if (strcmp(tid, WEBUI_TILE_TITLE_ID) == 0) continue;
|
||||
|
||||
char lnk[REG_MAX_PATH];
|
||||
char lnk_img[REG_MAX_PATH];
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <fnmatch.h>
|
||||
#include <regex.h>
|
||||
#include "config.h"
|
||||
#include "webui_api.h"
|
||||
#include "runtime.h"
|
||||
#include "sandbox_unmount.h"
|
||||
#include "instance_verdict.h"
|
||||
@@ -3472,6 +3473,15 @@ static int handle_app_unregister(runtime_state_t *state, const char *request_bod
|
||||
if (title_id[0] == '\0') {
|
||||
return mgmt_reply(MGMT_FRAME_ERROR, "unregister_title_id_missing", 27);
|
||||
}
|
||||
if (strcmp(title_id, WEBUI_TILE_TITLE_ID) == 0) {
|
||||
/* The helper's own home-screen tile: a PC removing it means "tile off", as in the page's
|
||||
* Settings. Saved, so the next helper start does not register it again; the tile thread
|
||||
* unregisters it and deletes its folders (webui_tile_remove). */
|
||||
static const char ack[] = "{\"sony_uninstall_rc\":0,\"tile_off\":true}";
|
||||
(void)webui_api_tile_off();
|
||||
pop_notification("PS5Upload tile removed");
|
||||
return mgmt_reply(MGMT_FRAME_APP_UNREGISTER_ACK, ack, (uint64_t)(sizeof ack - 1));
|
||||
}
|
||||
unsigned sony_rc = 0u;
|
||||
if (unregister_title(title_id, &err, &sony_rc) != 0) {
|
||||
const char *reason = err ? err : "unregister_failed";
|
||||
|
||||
+80
-24
@@ -8,6 +8,7 @@
|
||||
#include "webui_api.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -52,7 +53,9 @@ static void send_error(int fd, int http, const char *token, size_t len) {
|
||||
if (len > API_CAUSE_MAX) len = API_CAUSE_MAX;
|
||||
at = (size_t)snprintf(buf, sizeof buf, "{\"error\":\"");
|
||||
at += webui_json_escape(buf + at, sizeof buf - at, token, len);
|
||||
if (at + 3 > sizeof buf) at = sizeof buf - 3; /* cannot happen: 6 bytes per input byte at most */
|
||||
/* escape returns the full length: clamp before any `sizeof buf - at` (cannot trigger: 6 bytes
|
||||
* per input byte at most, and the buffer holds that) */
|
||||
if (at + 3 > sizeof buf) at = sizeof buf - 3;
|
||||
memcpy(buf + at, "\"}", 3);
|
||||
send_json(fd, http, buf, at + 2);
|
||||
}
|
||||
@@ -195,18 +198,37 @@ static const char *kind_name(uint8_t k) {
|
||||
|
||||
typedef void (*api_fn)(const char *b, size_t n, uint8_t *out, int fd);
|
||||
|
||||
/* snprintf and webui_json_escape return the length they WANTED to write: after a truncation *at
|
||||
* would point past the buffer and `cap - *at` would wrap. Keep it on the NUL instead. */
|
||||
static void clamp_at(size_t *at, size_t cap) {
|
||||
if (cap && *at > cap - 1) *at = cap - 1;
|
||||
}
|
||||
|
||||
/* Appends printf output to buf at *at, never past cap (a truncated append is cut short). */
|
||||
static void put_fmt(char *buf, size_t cap, size_t *at, const char *fmt, ...) __attribute__((format(printf, 4, 5)));
|
||||
static void put_fmt(char *buf, size_t cap, size_t *at, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int k;
|
||||
clamp_at(at, cap);
|
||||
if (*at + 1 >= cap) return;
|
||||
va_start(ap, fmt);
|
||||
k = vsnprintf(buf + *at, cap - *at, fmt, ap);
|
||||
va_end(ap);
|
||||
if (k > 0) *at += (size_t)k;
|
||||
clamp_at(at, cap);
|
||||
}
|
||||
|
||||
/* Appends `"key":"<escaped s>"` or `"key":null` (s NULL) to buf at *at. */
|
||||
static void put_str(char *buf, size_t cap, size_t *at, const char *key, const char *s) {
|
||||
if (*at >= cap) return;
|
||||
if (!s) {
|
||||
*at += (size_t)snprintf(buf + *at, cap - *at, "\"%s\":null", key);
|
||||
put_fmt(buf, cap, at, "\"%s\":null", key);
|
||||
return;
|
||||
}
|
||||
*at += (size_t)snprintf(buf + *at, cap - *at, "\"%s\":\"", key);
|
||||
if (*at >= cap) return;
|
||||
put_fmt(buf, cap, at, "\"%s\":\"", key);
|
||||
if (*at + 1 >= cap) return;
|
||||
*at += webui_json_escape(buf + *at, cap - *at, s, strlen(s));
|
||||
if (*at >= cap) return;
|
||||
*at += (size_t)snprintf(buf + *at, cap - *at, "\"");
|
||||
clamp_at(at, cap);
|
||||
put_fmt(buf, cap, at, "\"");
|
||||
}
|
||||
|
||||
/* NODE_STATUS + HW_INFO (model) + HW_TEMPS + PROC_FOCUS. Each call is in-process and bounded by
|
||||
@@ -248,22 +270,23 @@ static void r_status(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
{
|
||||
char *j = (char *)out;
|
||||
size_t cap = API_OUT_MAX;
|
||||
at = (size_t)snprintf(j, cap, "{");
|
||||
at = 0;
|
||||
put_fmt(j, cap, &at, "{");
|
||||
put_str(j, cap, &at, "version", have_ver ? version : NULL);
|
||||
at += (size_t)snprintf(j + at, cap - at, ",");
|
||||
put_fmt(j, cap, &at, ",");
|
||||
put_str(j, cap, &at, "firmware", have_ver ? firmware : NULL);
|
||||
at += (size_t)snprintf(j + at, cap - at, ",");
|
||||
put_fmt(j, cap, &at, ",");
|
||||
put_str(j, cap, &at, "model", have_model ? model : NULL);
|
||||
if (have_temp) at += (size_t)snprintf(j + at, cap - at, ",\"temp_c\":%ld,\"running\":", temp);
|
||||
else at += (size_t)snprintf(j + at, cap - at, ",\"temp_c\":null,\"running\":");
|
||||
if (have_temp) put_fmt(j, cap, &at, ",\"temp_c\":%ld,\"running\":", temp);
|
||||
else put_fmt(j, cap, &at, ",\"temp_c\":null,\"running\":");
|
||||
if (running) {
|
||||
at += (size_t)snprintf(j + at, cap - at, "{");
|
||||
put_fmt(j, cap, &at, "{");
|
||||
put_str(j, cap, &at, "title_id", title);
|
||||
at += (size_t)snprintf(j + at, cap - at, ",\"app_id\":%lu,\"name\":null}", (unsigned long)app_id);
|
||||
put_fmt(j, cap, &at, ",\"app_id\":%lu,\"name\":null}", (unsigned long)app_id);
|
||||
} else {
|
||||
at += (size_t)snprintf(j + at, cap - at, "null");
|
||||
put_fmt(j, cap, &at, "null");
|
||||
}
|
||||
at += (size_t)snprintf(j + at, cap - at, "}");
|
||||
put_fmt(j, cap, &at, "}");
|
||||
send_json(fd, 200, j, at);
|
||||
}
|
||||
}
|
||||
@@ -512,6 +535,7 @@ static void r_fs_delete(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
/* {"path":...}: fs_jobs.c's op_delete reads it with fsj_json_str (\" \\ \uXXXX understood) */
|
||||
at = (size_t)snprintf(args, args_cap, "{\"path\":\"");
|
||||
at += webui_json_escape(args + at, args_cap - at, path, strlen(path));
|
||||
if (at + 3 > args_cap) at = args_cap - 3; /* escape returns the full length (cannot trigger: 6x fits) */
|
||||
memcpy(args + at, "\"}", 3);
|
||||
at += 2;
|
||||
memset(&q, 0, sizeof q);
|
||||
@@ -554,8 +578,15 @@ static void r_jobs(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
send_error(fd, 500, "out_of_memory", 13);
|
||||
return;
|
||||
}
|
||||
if (webui_jobs_to_json(out, ol, is_mine, NULL, json, API_JOBS_JSON) < 0) send_error(fd, 500, "bad_jobs_reply", 14);
|
||||
else send_json(fd, 200, json, strlen(json));
|
||||
if (webui_jobs_to_json(out, ol, is_mine, NULL, json, API_JOBS_JSON) < 0) {
|
||||
send_error(fd, 500, "bad_jobs_reply", 14);
|
||||
} else {
|
||||
/* The page has now seen how its finished jobs ended: release them (after a short grace, so
|
||||
* a second open page still sees them) instead of letting each hold one of the 32 job slots
|
||||
* every owner shares for the full park age. A paired PC's jobs are left alone. */
|
||||
ava1_jobs_release_seen(k_console_id);
|
||||
send_json(fd, 200, json, strlen(json));
|
||||
}
|
||||
free(json);
|
||||
}
|
||||
|
||||
@@ -633,10 +664,17 @@ void webui_api_settings_init(const webui_settings_t *s) {
|
||||
pthread_mutex_unlock(&g_set_mu);
|
||||
}
|
||||
|
||||
/* tile_busy / tile_failed: the tile switch is applied on the tile thread after this reply (Sony calls
|
||||
* take seconds), so the page re-reads /api/settings until tile_busy is false and then says so when
|
||||
* the last ensure/remove failed (webui_tile_state). */
|
||||
static void send_settings(int fd, const webui_settings_t *s) {
|
||||
char json[160];
|
||||
int k = snprintf(json, sizeof json, "{\"lan\":%s,\"tile\":%s,\"version\":\"%s\"}", s->lan ? "true" : "false",
|
||||
s->tile ? "true" : "false", PS5UPLOAD2_VERSION);
|
||||
char json[200];
|
||||
int busy, failed, k;
|
||||
webui_tile_state(&busy, &failed);
|
||||
k = snprintf(json, sizeof json,
|
||||
"{\"lan\":%s,\"tile\":%s,\"tile_busy\":%s,\"tile_failed\":%s,\"version\":\"%s\"}",
|
||||
s->lan ? "true" : "false", s->tile ? "true" : "false", busy ? "true" : "false",
|
||||
failed ? "true" : "false", PS5UPLOAD2_VERSION);
|
||||
send_json(fd, 200, json, (size_t)k);
|
||||
}
|
||||
|
||||
@@ -710,11 +748,29 @@ static void r_settings_set(const char *b, size_t n, uint8_t *out, int fd) {
|
||||
}
|
||||
g_set = s;
|
||||
pthread_mutex_unlock(&g_set_mu);
|
||||
send_settings(fd, &s); /* written in full (or the deadline passed) before anything below runs */
|
||||
if (s.lan != old.lan) spawn_lan_apply();
|
||||
/* tile:true is applied even when unchanged: it retries an ensure that failed (and costs one statfs
|
||||
* plus three file compares when the tile is already in place). A remove only on a change. */
|
||||
* plus three file compares when the tile is already in place). A remove only on a change. Requested
|
||||
* before the reply (it only starts the tile thread), so the reply already says tile_busy. */
|
||||
if (has_tile && (s.tile || s.tile != old.tile)) webui_tile_request(s.tile, WEBUI_PORT);
|
||||
send_settings(fd, &s); /* written in full (or the deadline passed) before the rebind below runs */
|
||||
if (s.lan != old.lan) spawn_lan_apply();
|
||||
}
|
||||
|
||||
int webui_api_tile_off(void) {
|
||||
webui_settings_t s;
|
||||
int rc = 0;
|
||||
pthread_mutex_lock(&g_set_mu);
|
||||
s = g_set;
|
||||
s.tile = 0;
|
||||
if (g_set.tile && webui_settings_save(WEBUI_SETTINGS_PATH, &s) != 0) {
|
||||
fprintf(stderr, "webui: cannot save tile:false after a PC removed the tile\n");
|
||||
rc = -1;
|
||||
} else {
|
||||
g_set = s;
|
||||
}
|
||||
pthread_mutex_unlock(&g_set_mu);
|
||||
webui_tile_request(0, WEBUI_PORT);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* The allowlist: nothing else is reachable. */
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "ava1_gen.h"
|
||||
#include "ava1_wire.h"
|
||||
#include "config.h"
|
||||
|
||||
/* ---- a bounded JSON scanner (never reads past `e`, never recurses) ---- */
|
||||
|
||||
@@ -544,6 +545,20 @@ int webui_kv_int(const char *text, size_t n, const char *key, long *out) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int webui_is_game_title_id(const char *tid) {
|
||||
/* PS5 (PPSA, and PCSx/PLxS... for older regional ranges), PS4 (CUSA, PCJS/PLJS Japan, PCAS/PLAS
|
||||
* Asia, PCKS/PLKS Korea, ECAS/ELAS Asia) and the PS Vita/PS3-era PCSA..PCSH ranges. */
|
||||
static const char *const k_prefixes[] = { "PPSA", "CUSA", "PCJS", "PCAS", "PLJS", "PLAS", "ECAS", "ELAS", "PCKS",
|
||||
"PLKS", "PCSA", "PCSB", "PCSC", "PCSD", "PCSE", "PCSF", "PCSG", "PCSH" };
|
||||
size_t i;
|
||||
if (!tid || strlen(tid) != 9 || strcmp(tid, WEBUI_TILE_TITLE_ID) == 0) return 0;
|
||||
for (i = 4; i < 9; i++)
|
||||
if (tid[i] < '0' || tid[i] > '9') return 0;
|
||||
for (i = 0; i < sizeof k_prefixes / sizeof k_prefixes[0]; i++)
|
||||
if (memcmp(tid, k_prefixes[i], 4) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webui_focus_running(const char *json, size_t n, char *title_id, size_t cap, uint32_t *app_id) {
|
||||
const char *e = json + n, *p = top_value(json, n, "apps");
|
||||
long long focus = -1, big = -1;
|
||||
@@ -552,7 +567,8 @@ int webui_focus_running(const char *json, size_t n, char *title_id, size_t cap,
|
||||
if (!p || *p != '[') return -1;
|
||||
(void)json_i64(json, n, "focus_app_id", &focus);
|
||||
(void)json_i64(json, n, "big_app_id", &big);
|
||||
/* pass 0: the app on screen (focus flag, else big app); pass 1: the first game at all */
|
||||
/* pass 0: the app on screen (focus flag, else big app); pass 1: the first GAME (never the tile,
|
||||
* the browser or a homebrew app: Close would kill it) */
|
||||
for (pass = 0; pass < 2; pass++) {
|
||||
long long want = pass == 0 ? (focus > 0 ? focus : big) : -1;
|
||||
const char *q = p + 1;
|
||||
@@ -568,7 +584,9 @@ int webui_focus_running(const char *json, size_t n, char *title_id, size_t cap,
|
||||
end = skip_val(q, e);
|
||||
if (!end) return -1;
|
||||
if (*el == '{' && webui_json_str(el, (size_t)(end - el), "title_id", tid, sizeof tid) == 1 && tid[0] &&
|
||||
strncmp(tid, "NPXS", 4) != 0 && webui_json_u64(el, (size_t)(end - el), "app_id", &aid) == 1 &&
|
||||
strncmp(tid, "NPXS", 4) != 0 && strcmp(tid, WEBUI_TILE_TITLE_ID) != 0 &&
|
||||
(pass == 0 || webui_is_game_title_id(tid)) &&
|
||||
webui_json_u64(el, (size_t)(end - el), "app_id", &aid) == 1 &&
|
||||
(pass == 1 || (long long)aid == want) && strlen(tid) < cap) {
|
||||
memcpy(title_id, tid, strlen(tid) + 1);
|
||||
*app_id = (uint32_t)aid;
|
||||
|
||||
@@ -282,6 +282,7 @@ static unsigned g_req_gen;
|
||||
static int g_req_tile;
|
||||
static uint16_t g_req_port;
|
||||
static int g_worker_running;
|
||||
static int g_last_failed; /* the last ensure/remove the worker applied returned -1 */
|
||||
|
||||
static void *tile_worker(void *arg) {
|
||||
unsigned done = 0;
|
||||
@@ -294,16 +295,23 @@ static void *tile_worker(void *arg) {
|
||||
uint16_t port = g_req_port;
|
||||
if (done == gen) break;
|
||||
pthread_mutex_unlock(&g_req_mu);
|
||||
if (tile) (void)webui_tile_ensure(port);
|
||||
else (void)webui_tile_remove();
|
||||
int rc = tile ? webui_tile_ensure(port) : webui_tile_remove();
|
||||
done = gen;
|
||||
pthread_mutex_lock(&g_req_mu);
|
||||
g_last_failed = rc != 0;
|
||||
}
|
||||
g_worker_running = 0;
|
||||
pthread_mutex_unlock(&g_req_mu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void webui_tile_state(int *busy, int *failed) {
|
||||
pthread_mutex_lock(&g_req_mu);
|
||||
*busy = g_worker_running;
|
||||
*failed = g_last_failed;
|
||||
pthread_mutex_unlock(&g_req_mu);
|
||||
}
|
||||
|
||||
void webui_tile_request(int tile, uint16_t port) {
|
||||
pthread_attr_t attr;
|
||||
pthread_t t;
|
||||
|
||||
@@ -493,6 +493,38 @@ static void focus_running(void) {
|
||||
CHECK(webui_focus_running("nope", 4, tid, sizeof tid, &app) == -1);
|
||||
}
|
||||
|
||||
/* The fallback (nothing game-like on screen) names only a game: never the page's own tile, the
|
||||
* browser or a homebrew app, which Close would kill. The focused app never names the tile either. */
|
||||
static void focus_running_fallback_games_only(void) {
|
||||
char tid[16];
|
||||
uint32_t app = 0;
|
||||
const char *j = "{\"apps\":[{\"title_id\":\"PSUP00001\",\"app_id\":3},{\"title_id\":\"FAKE10101\",\"app_id\":4},"
|
||||
"{\"title_id\":\"PLDM00001\",\"app_id\":5},{\"title_id\":\"WKAL00001\",\"app_id\":6},"
|
||||
"{\"title_id\":\"PPSA01650\",\"app_id\":7}],\"focus_app_id\":-1,\"big_app_id\":0}";
|
||||
CHECK(webui_focus_running(j, strlen(j), tid, sizeof tid, &app) == 1);
|
||||
CHECK(strcmp(tid, "PPSA01650") == 0 && app == 7);
|
||||
/* only non-games run: nothing to close */
|
||||
j = "{\"apps\":[{\"title_id\":\"PSUP00001\",\"app_id\":3},{\"title_id\":\"FAKE10101\",\"app_id\":4}],"
|
||||
"\"focus_app_id\":-1}";
|
||||
CHECK(webui_focus_running(j, strlen(j), tid, sizeof tid, &app) == 0);
|
||||
/* the tile focused (the browser it opened owns the screen): not "running", the game behind it is */
|
||||
j = "{\"apps\":[{\"title_id\":\"PSUP00001\",\"app_id\":3},{\"title_id\":\"CUSA07842\",\"app_id\":9}],"
|
||||
"\"focus_app_id\":3}";
|
||||
CHECK(webui_focus_running(j, strlen(j), tid, sizeof tid, &app) == 1);
|
||||
CHECK(strcmp(tid, "CUSA07842") == 0 && app == 9);
|
||||
/* a focused homebrew app is still what is on screen (the user sees it; pass 0 keeps it) */
|
||||
j = "{\"apps\":[{\"title_id\":\"FAKE10101\",\"app_id\":4},{\"title_id\":\"PPSA01650\",\"app_id\":7}],"
|
||||
"\"focus_app_id\":4}";
|
||||
CHECK(webui_focus_running(j, strlen(j), tid, sizeof tid, &app) == 1);
|
||||
CHECK(strcmp(tid, "FAKE10101") == 0 && app == 4);
|
||||
CHECK(webui_is_game_title_id("PPSA01650") && webui_is_game_title_id("CUSA07842") &&
|
||||
webui_is_game_title_id("PLJS00001") && webui_is_game_title_id("PCSH00001"));
|
||||
CHECK(!webui_is_game_title_id("PSUP00001") && !webui_is_game_title_id("NPXS40087") &&
|
||||
!webui_is_game_title_id("FAKE10101") && !webui_is_game_title_id("PPSA0165") &&
|
||||
!webui_is_game_title_id("PPSA0165X") && !webui_is_game_title_id("PPSA016500") &&
|
||||
!webui_is_game_title_id(""));
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
unicode_path_round_trips();
|
||||
surrogate_pair_decodes();
|
||||
@@ -506,6 +538,7 @@ int main(void) {
|
||||
mime_types();
|
||||
kv_lines();
|
||||
focus_running();
|
||||
focus_running_fallback_games_only();
|
||||
if (failures) {
|
||||
fprintf(stderr, "webui_codec_selftest: %d failure(s)\n", failures);
|
||||
return 1;
|
||||
|
||||
Reference in new issue
Block a user