mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Overlay input sink and UI capture gate (in-game overlay P3)
psx_host_set_input_sink: an overlay provider sees host SDL events first, in the event drain and the host pause loop, and may consume them. psx_host_set_ui_capture: while on, local game input reads released and centred (netplay included, so peers receive "nothing pressed") and the external-input gate stays closed. Debug builds gain host_key, which pushes a real SDL key event for scripted runs. Inert without a provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,19 @@ void psx_host_overlay_set_draw_cb(PsxHostOverlayDrawFn fn, void *ctx);
|
||||
int psx_host_overlay_has_draw(void);
|
||||
void psx_host_overlay_draw(int width, int height); /* renderer side */
|
||||
|
||||
/* ---- P3: input sink -----------------------------------------------------
|
||||
* The sink sees every host SDL event first (main thread), in the normal event
|
||||
* drain and in the host pause loop. Returning non-zero consumes the event.
|
||||
* While ui_capture is on, local game input reads as released/centred, in
|
||||
* netplay too (the peer then simply receives "nothing pressed"), so a menu
|
||||
* open over a running session never leaks presses into the game.
|
||||
* `event` is an SDL_Event*. NULL unregisters. */
|
||||
typedef int (*PsxHostInputSinkFn)(const void *event, void *ctx);
|
||||
void psx_host_set_input_sink(PsxHostInputSinkFn fn, void *ctx);
|
||||
int psx_host_input_sink_dispatch(const void *event);
|
||||
void psx_host_set_ui_capture(int on);
|
||||
int psx_host_ui_capture_active(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9258,6 +9258,18 @@ static void handle_host_pause(int id, const char *json)
|
||||
psx_host_pause_depth());
|
||||
}
|
||||
|
||||
/* host_key name=<SDL scancode name> down=<1|0> (both when omitted). */
|
||||
extern int psx_debug_push_host_key(const char *name, int down);
|
||||
static void handle_host_key(int id, const char *json)
|
||||
{
|
||||
char name[64] = "";
|
||||
json_get_str(json, "name", name, sizeof name);
|
||||
int down = json_get_int(json, "down", -1);
|
||||
int ok = down < 0 ? (psx_debug_push_host_key(name, 1) && psx_debug_push_host_key(name, 0))
|
||||
: psx_debug_push_host_key(name, down);
|
||||
send_fmt("{\"id\":%d,\"ok\":%s}", id, ok ? "true" : "false");
|
||||
}
|
||||
|
||||
static void handle_ws_nw(int id, const char *json)
|
||||
{
|
||||
int on = json_get_int(json, "on", -1);
|
||||
@@ -15920,6 +15932,7 @@ static const CmdEntry s_commands[] = {
|
||||
{ "ws_nw", handle_ws_nw },
|
||||
{ "debug_key", handle_debug_key },
|
||||
{ "host_pause", handle_host_pause },
|
||||
{ "host_key", handle_host_key },
|
||||
{ "scanline", handle_scanline },
|
||||
{ "ws_backdrop_ring", handle_ws_backdrop_ring },
|
||||
{ "ws_ui_groups", handle_ws_ui_groups },
|
||||
|
||||
@@ -101,3 +101,19 @@ void psx_host_overlay_draw(int width, int height) {
|
||||
atomic_fetch_sub(&s_draw_inflight, 1);
|
||||
}
|
||||
|
||||
/* ---- P3: input sink -------------------------------------------------------- */
|
||||
static PsxHostInputSinkFn s_sink_fn = NULL;
|
||||
static void *s_sink_ctx = NULL;
|
||||
static volatile int s_ui_capture = 0;
|
||||
|
||||
void psx_host_set_input_sink(PsxHostInputSinkFn fn, void *ctx) {
|
||||
s_sink_fn = fn;
|
||||
s_sink_ctx = ctx;
|
||||
}
|
||||
|
||||
int psx_host_input_sink_dispatch(const void *event) {
|
||||
return (s_sink_fn && event) ? (s_sink_fn(event, s_sink_ctx) != 0) : 0;
|
||||
}
|
||||
|
||||
void psx_host_set_ui_capture(int on) { s_ui_capture = on ? 1 : 0; }
|
||||
int psx_host_ui_capture_active(void) { return s_ui_capture; }
|
||||
|
||||
+36
-1
@@ -6176,6 +6176,8 @@ static int capture_host_layer_pad(PsxNetPad* out, uint16_t word,
|
||||
static int capture_pad_slot_view(int s, PsxNetPad* out, bool guarded,
|
||||
bool host_view) {
|
||||
if (!out) return 0;
|
||||
/* An open overlay menu owns the controls: the game sees released pads. */
|
||||
if (psx_host_ui_capture_active()) guarded = true;
|
||||
out->buttons = 0xFFFFu;
|
||||
out->lx = out->ly = out->rx = out->ry = 0x80u;
|
||||
out->analog = 0;
|
||||
@@ -6276,6 +6278,12 @@ static int capture_pad_slot_view(int s, PsxNetPad* out, bool guarded,
|
||||
|
||||
out->buttons = btn;
|
||||
out->lx = st[0]; out->ly = st[1]; out->rx = st[2]; out->ry = st[3];
|
||||
if (psx_host_ui_capture_active()) {
|
||||
/* Overlay open during netplay: publish "nothing pressed", which every
|
||||
* peer simulates identically. */
|
||||
out->buttons = 0xFFFFu;
|
||||
out->lx = out->ly = out->rx = out->ry = 0x80u;
|
||||
}
|
||||
out->analog = (uint8_t)frame_type;
|
||||
out->connected = 1;
|
||||
return 1;
|
||||
@@ -8030,7 +8038,9 @@ static void rewind_host_pause_loop(void) {
|
||||
static void host_pause_service(void); /* P6 fills this in; no-op until then */
|
||||
static void host_pause_service(void) {}
|
||||
static bool host_pause_event(const SDL_Event& ev); /* P3: input sink */
|
||||
static bool host_pause_event(const SDL_Event&) { return false; }
|
||||
static bool host_pause_event(const SDL_Event& ev) {
|
||||
return psx_host_input_sink_dispatch(&ev) != 0;
|
||||
}
|
||||
|
||||
static void host_pause_loop(void) {
|
||||
psx_local_mouse_begin(sdl_window, false);
|
||||
@@ -8075,6 +8085,28 @@ static void host_pause_loop(void) {
|
||||
savestate_input_guard_arm();
|
||||
}
|
||||
|
||||
/* Debug: inject a host key / gamepad button as an SDL event, so scripted
|
||||
* runs can drive host UI (the overlay) through the real event path. */
|
||||
extern "C" int psx_debug_push_host_key(const char *name, int down) {
|
||||
const SDL_Scancode sc = SDL_GetScancodeFromName(name ? name : "");
|
||||
if (sc == SDL_SCANCODE_UNKNOWN) return 0;
|
||||
SDL_Event ev;
|
||||
SDL_zero(ev);
|
||||
#if defined(PSX_SDL3)
|
||||
ev.type = down ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP;
|
||||
ev.key.scancode = sc;
|
||||
ev.key.key = SDL_GetKeyFromScancode(sc, SDL_KMOD_NONE, false);
|
||||
ev.key.down = down != 0;
|
||||
ev.key.windowID = sdl_window ? SDL_GetWindowID(sdl_window) : 0;
|
||||
#else
|
||||
ev.type = down ? SDL_KEYDOWN : SDL_KEYUP;
|
||||
ev.key.keysym.scancode = sc;
|
||||
ev.key.keysym.sym = SDL_GetKeyFromScancode(sc);
|
||||
ev.key.state = down ? SDL_PRESSED : SDL_RELEASED;
|
||||
#endif
|
||||
return SDL_PushEvent(&ev) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int host_pause_refuse_netplay(void) { return psx_netplay_active() ? 1 : 0; }
|
||||
|
||||
/* Freeze guest in vblank present while the save-state slot menu is open. */
|
||||
@@ -8220,6 +8252,8 @@ static bool drain_host_events() {
|
||||
SDL_Event ev;
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
if (psx_local_mouse_event(ev)) continue;
|
||||
/* In-game overlay provider sees events first (host_overlay.h P3). */
|
||||
if (psx_host_input_sink_dispatch(&ev)) continue;
|
||||
if (ev.type == SDL_QUIT) {
|
||||
if (psx_netplay_active()) {
|
||||
netplay_soft_exit("sdl_window_close");
|
||||
@@ -8366,6 +8400,7 @@ static bool local_mouse_live(int override_word) {
|
||||
g.savestate_menu_open = savestate_menu_open;
|
||||
g.rewind_open = psx_rewind_is_open();
|
||||
g.input_guard = savestate_input_guard_active();
|
||||
g.ui_capture = psx_host_ui_capture_active();
|
||||
return pad_ext_live(&g) != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ int pad_ext_live(const PadExtGate *g) {
|
||||
return g && !g->injected_input && !g->headless && !g->netplay_active &&
|
||||
!g->netplay_resim && !g->selfcheck_locked && !g->selfcheck_resim &&
|
||||
!g->render_pass && !g->savestate_menu_open && !g->rewind_open &&
|
||||
!g->input_guard;
|
||||
!g->input_guard && !g->ui_capture;
|
||||
}
|
||||
|
||||
/* Stage 2b: run the title transform over the resolved pad and pack its
|
||||
|
||||
@@ -85,6 +85,7 @@ typedef struct PadExtGate {
|
||||
int savestate_menu_open;
|
||||
int rewind_open;
|
||||
int input_guard; /* savestate input guard armed */
|
||||
int ui_capture; /* host overlay owns input (host_overlay.h P3) */
|
||||
} PadExtGate;
|
||||
int pad_ext_live(const PadExtGate *g);
|
||||
|
||||
|
||||
@@ -111,9 +111,28 @@ static void test_draw_reentrant(void) {
|
||||
CHECK(n == 1 && !psx_host_overlay_has_draw());
|
||||
}
|
||||
|
||||
static int sunk = 0;
|
||||
static int sink(const void *ev, void *ctx) { (void)ctx; sunk++; return *(const int *)ev == 1; }
|
||||
|
||||
static void test_sink(void) {
|
||||
int a = 1, b = 2;
|
||||
CHECK(!psx_host_input_sink_dispatch(&a)); /* none registered */
|
||||
psx_host_set_input_sink(sink, NULL);
|
||||
CHECK(psx_host_input_sink_dispatch(&a) && !psx_host_input_sink_dispatch(&b) && sunk == 2);
|
||||
CHECK(!psx_host_input_sink_dispatch(NULL) && sunk == 2);
|
||||
psx_host_set_input_sink(NULL, NULL);
|
||||
CHECK(!psx_host_input_sink_dispatch(&a));
|
||||
CHECK(!psx_host_ui_capture_active());
|
||||
psx_host_set_ui_capture(5);
|
||||
CHECK(psx_host_ui_capture_active() == 1);
|
||||
psx_host_set_ui_capture(0);
|
||||
CHECK(!psx_host_ui_capture_active());
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_draw_lifetime();
|
||||
test_draw_reentrant();
|
||||
test_sink();
|
||||
test_draw();
|
||||
test_pause();
|
||||
printf("host_overlay_test: %s\n", g_fail ? "FAIL" : "ok");
|
||||
|
||||
@@ -280,7 +280,7 @@ int main(void) {
|
||||
int *f[] = { &g.injected_input, &g.headless, &g.netplay_active,
|
||||
&g.netplay_resim, &g.selfcheck_locked, &g.selfcheck_resim,
|
||||
&g.render_pass, &g.savestate_menu_open, &g.rewind_open,
|
||||
&g.input_guard };
|
||||
&g.input_guard, &g.ui_capture };
|
||||
for (unsigned i = 0; i < sizeof f / sizeof f[0]; i++) {
|
||||
*f[i] = 1; CHECK(pad_ext_live(&g) == 0); *f[i] = 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user