Keep a late HD rumble report from opening a Joy-Con pair again after the stop.

hd_tick picks a report under the locks and writes it to the pair after letting them go. When input_rumble_hd_stop
wrote the neutral value and closed the pair in between, that write opened the two devices again and left the
Joy-Con vibrating after the exit. The stop now ends the pair (hd_pair_end), and hd_pair_write checks that under the
lock it holds for the write, so nothing is written after the stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 06:02:58 -05:00
parent d59488d308
commit b399d8f15c
3 changed files with 21 additions and 4 deletions

View File

@@ -667,9 +667,11 @@ static int hd_tick(void) {
plat_mutex_unlock(&lock);
if (close_pair) hd_pair_close(1, 0);
if (due && kind == HD_PAIR) {
int ok = hd_pair_write(d); // waits for the two writes, so not under lock
// waits for the two writes, so not under lock; after input_rumble_hd_stop it writes nothing (hd_pair_end)
int ok = hd_pair_write(d);
plat_mutex_lock(&lock);
if (pad && pad_hd == HD_PAIR) hd_wrote(ok, d, now, ok ? "" : SDL_GetError()); // unless the pair left meanwhile
// unless the pair left or the stop came meanwhile (hd_ended is written under lock as well)
if (pad && pad_hd == HD_PAIR && !hd_ended) hd_wrote(ok, d, now, ok ? "" : SDL_GetError());
plat_mutex_unlock(&lock);
}
return idle;
@@ -732,7 +734,7 @@ void input_rumble_hd_stop(void) {
}
}
plat_mutex_unlock(&lock);
if (pair) hd_pair_close(1, 100);
if (pair) hd_pair_end(100);
if (wait) plat_wait_ns(30000000); // SDL writes the report on a thread of its own: time for it before the process ends
}

View File

@@ -31,6 +31,7 @@ static char *path[2]; // the HID paths hd_pair_find sa
static PlatMutex dev_lock = PLAT_MUTEX_INIT; // guards dev[] and number[], and is held across a write
static SDL_hid_device *dev[2];
static unsigned number[2]; // the next report's number, per device
static volatile int32_t ended; // hd_pair_end ran: hd_pair_write, under dev_lock, writes nothing
int hd_pair_find(void) {
char *found[2] = {NULL, NULL};
@@ -71,6 +72,11 @@ static int write_side(int s, const uint8_t v[RUMBLE_HD_SIDE]) {
int hd_pair_write(const uint8_t data[RUMBLE_HD_DATA]) {
int ok = 1;
plat_mutex_lock(&dev_lock);
if (__atomic_load_n(&ended, __ATOMIC_ACQUIRE)) { // under the write's lock: no stop between this and the write
plat_mutex_unlock(&dev_lock);
SDL_SetError("HD rumble has ended");
return 0;
}
if (!dev[0] || !dev[1]) {
close_devices();
char *p[2];
@@ -111,3 +117,8 @@ void hd_pair_close(int neutral, unsigned wait_ms) {
close_devices();
plat_mutex_unlock(&dev_lock);
}
void hd_pair_end(unsigned wait_ms) {
__atomic_store_n(&ended, 1, __ATOMIC_RELEASE); // before the lock: a write that holds it now is the last one
hd_pair_close(1, wait_ms);
}

View File

@@ -10,10 +10,14 @@
int hd_pair_find(void);
// Writes one rumble report to each Joy-Con: the left half of data to the left one, the right half to the right one.
// The first call after hd_pair_find opens the two devices. 0 when a device could not be opened or written (both are
// closed then, and SDL's error text says why). Blocks for as long as the writes take: the HD rumble thread only.
// closed then, and SDL's error text says why), and after hd_pair_end. Blocks for as long as the writes take: the HD
// rumble thread only.
int hd_pair_write(const uint8_t data[RUMBLE_HD_DATA]);
// 1 while the two devices are open.
int hd_pair_is_open(void);
// Writes the neutral value to both Joy-Con, when neutral is set, and closes them. wait_ms: how long to wait for a
// write that is still running on another thread before giving up (the process is ending); 0 waits for it.
void hd_pair_close(int neutral, unsigned wait_ms);
// The process is ending (input.c input_rumble_hd_stop): hd_pair_close(1, wait_ms), and hd_pair_write writes nothing
// from here on, so a report the HD thread chose before the stop cannot open the two devices again after it.
void hd_pair_end(unsigned wait_ms);