Files
psxrecomp/runtime/src/mod_controller_source.c
Matthew Stanley fedb5319d2 Support scoped modern controls and replay controller presence
Keep temporary keyboard profiles separate from saved bindings. Add optional persistent mouse capture with ordered release/focus/reset handling, and let offline sources represent an unplugged port without falling back to physical input. Existing capture and source defaults remain intact. Beads: beads-9wg8 and beads-eio.19.18.
2026-10-17 09:19:31 -07:00

91 lines
3.8 KiB
C

#include "mod_controller_source.h"
#include "sio.h"
#include <stdio.h>
#include <string.h>
static PSXModControllerSource s_source[PSX_MAX_PLAYERS];
static uint8_t s_connected[PSX_MAX_PLAYERS];
static uint8_t s_release[PSX_MAX_PLAYERS];
/* One sample per player per frame: the early and the low-latency re-sample in
* main.cpp share the result so a stateful callback runs once. */
static PSXModControllerState s_cache[PSX_MAX_PLAYERS];
static uint8_t s_cache_valid[PSX_MAX_PLAYERS];
static uint8_t s_cache_released[PSX_MAX_PLAYERS];
static uint32_t s_bad_count[PSX_MAX_PLAYERS];
#define BAD_LOG_INTERVAL 600u
int psx_mod_set_controller_source(uint32_t player, PSXModControllerSource source) {
if (player >= PSX_MAX_PLAYERS) return 0;
if (s_source[player] && !source) s_release[player] = 1;
else if (source) s_release[player] = 0;
s_source[player] = source;
s_connected[player] = source ? 1 : 0;
s_cache_valid[player] = 0;
s_bad_count[player] = 0;
return 1;
}
int psx_mod_set_controller_source_connected(uint32_t player, int connected) {
if (player >= PSX_MAX_PLAYERS || !s_source[player] ||
(connected != 0 && connected != 1)) return 0;
s_connected[player] = (uint8_t)connected;
return 1;
}
int mod_controller_source_connected(uint32_t player) {
return player < PSX_MAX_PLAYERS && s_source[player] && s_connected[player];
}
/* Detach every source but keep a neutral release pending for each port that
* had one, so held buttons/sticks are cleared even on a port with no device. */
void mod_controller_source_reset(void) {
for (uint32_t i = 0; i < PSX_MAX_PLAYERS; i++) {
if (s_source[i]) s_release[i] = 1;
s_source[i] = NULL;
s_connected[i] = 0;
}
memset(s_cache_valid, 0, sizeof s_cache_valid);
memset(s_bad_count, 0, sizeof s_bad_count);
}
void mod_controller_source_begin_frame(void) {
memset(s_cache_valid, 0, sizeof s_cache_valid);
}
int mod_controller_source_present(uint32_t player) {
return player < PSX_MAX_PLAYERS && s_source[player] != NULL;
}
static void note_bad(uint32_t player, const PSXModControllerState *s) {
if (s_bad_count[player]++ % BAD_LOG_INTERVAL) return;
fprintf(stderr,
"psxrecomp: mod controller source (player %u) returned an invalid "
"state (struct_size %u, expected %u; buttons 0x%x lx %u ly %u rx %u "
"ry %u analog %u); delivering neutral (%u so far)\n",
(unsigned)player, (unsigned)s->struct_size,
(unsigned)sizeof *s, (unsigned)s->buttons, (unsigned)s->lx,
(unsigned)s->ly, (unsigned)s->rx, (unsigned)s->ry,
(unsigned)s->analog, (unsigned)s_bad_count[player]);
}
int mod_controller_source_sample(uint32_t player, PSXModControllerState *state,
int *released) {
if (released) *released = 0;
if (!state || player>=PSX_MAX_PLAYERS || (!s_source[player] && !s_release[player] && !s_cache_valid[player])) return 0;
if (s_cache_valid[player]) {
*state = s_cache[player];
if (released) *released = s_cache_released[player];
return 1;
}
PSXModControllerState neutral = {sizeof neutral, 0xffff, 128,128,128,128,1};
int rel = 0;
*state = neutral;
if (s_release[player]) { s_release[player] = 0; rel = 1; }
else {
if (!s_source[player](state)) *state = neutral; /* declined: neutral */
else if (state->struct_size != sizeof *state ||
state->buttons > 0xffff || state->lx > 255 || state->ly > 255 ||
state->rx > 255 || state->ry > 255 || state->analog > 1) {
note_bad(player, state);
*state = neutral;
}
if (!state->analog) state->lx = state->ly = state->rx = state->ry = 128;
}
s_cache[player] = *state;
s_cache_released[player] = (uint8_t)rel;
s_cache_valid[player] = 1;
if (released) *released = rel;
return 1;
}