From 716817c35b8009e09fa780605d1821c46cae1bdf Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Sat, 17 Oct 2026 06:13:51 -0700 Subject: [PATCH 1/2] Rebind a joystick when it is plugged back in When a joystick the game already knew about was reconnected, the device handler refreshed it and returned before the slot-binding code ran, so the stick stayed unassigned until the game restarted. Fall through to the binding code instead, which also restarts force feedback on Joy-0. Along with that, in the same handler: - Ignore the add events SDL sends at startup for sticks that init() already set up, so they are not bound a second time. - Only treat a stick that is currently unplugged as one coming back. Two sticks of the same model share a GUID, and plugging in the second one used to take over the first. When several known sticks share a GUID, a saved choice now picks the one that is plugged in. - Drop the shortcut that skipped binding when every slot was full; it skipped the rebind for a stick chosen again while unplugged. - Close the extra handle opened to check the new device, which leaked one handle per plug-in. - Keep the old handles open until the stick has its slot again, and log when a known stick reconnects. --- code/io/joy-sdl.cpp | 61 +++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/code/io/joy-sdl.cpp b/code/io/joy-sdl.cpp index 44921aa550b..4cd80ee0ae7 100644 --- a/code/io/joy-sdl.cpp +++ b/code/io/joy-sdl.cpp @@ -100,9 +100,13 @@ bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid) // Build a list of all joysticks with the right guid size_t num_sticks = 0; + size_t num_attached = 0; for (auto& stick : joysticks) { if (stick->getGUID() == guidStr) { ++num_sticks; + if (stick->isAttached()) { + ++num_attached; + } } } @@ -115,8 +119,8 @@ bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid) return true; } - // Multiple sticks -> check if the device id is the same - return false; + // Multiple sticks of the same model. If only one of them is plugged in right now, that's the one + return num_attached == 1 && testStick->isAttached(); } /** @@ -473,7 +477,17 @@ bool device_event_handler(const SDL_Event &evt) if (evtType == SDL_EVENT_JOYSTICK_ADDED) { // A new joystick has been added, add it to our list and check if it's the configured joystick - Joystick *addedStick; + if (getJoystickForID(joyDeviceEvent.which) != nullptr) + { + // SDL also sends this event for sticks that were already plugged in when it started, and init() has + // already set those up. Instance IDs are never reused, so a known ID is never a new connection. + return true; + } + + Joystick *addedStick = nullptr; + // When a known stick comes back, this keeps its old device handles open until the stick has been given its + // slot again below, so force feedback on the old handle is shut down before that handle is closed + JoystickPtr oldHandles; { auto device = SDL_OpenJoystick(joyDeviceEvent.which); @@ -485,36 +499,39 @@ bool device_event_handler(const SDL_Event &evt) // Add the new device to our list auto added = JoystickPtr(new Joystick(joyDeviceEvent.which)); + // The Joystick holds its own handle, this one was only needed to check that the device opens + SDL_CloseJoystick(device); for (auto iter = joysticks.begin(); iter != joysticks.end(); ++iter) { - // must use GUID here as joystick IDs are never reused - if ((*iter)->getGUID() == added->getGUID()) + // must use GUID here as joystick IDs are never reused. Only an unplugged entry can be this stick + // coming back: two sticks of the same model share a GUID, and taking over a stick that is still + // plugged in would leave that one dead. + if (!(*iter)->isAttached() && (*iter)->getGUID() == added->getGUID()) { - // Already have this stick + // A known stick that was unplugged and plugged back in: refresh it, then give it its slot back below. *(*iter) = std::move(*added); - return true; + oldHandles = std::move(added); + addedStick = iter->get(); + mprintf(("A joystick has been reconnected:\n")); + addedStick->printInfo(); + break; } } - addedStick = added.get(); - // This is a new stick so we can output it's information - mprintf(("A new joystick has been connected:\n")); - addedStick->printInfo(); + if (addedStick == nullptr) + { + addedStick = added.get(); + // This is a new stick so we can output it's information + mprintf(("A new joystick has been connected:\n")); + addedStick->printInfo(); - joysticks.push_back(std::move(added)); + joysticks.push_back(std::move(added)); + } } - // Is true if all of the pJoysticks are bound - bool all_bound = std::all_of(pJoystick.begin(), pJoystick.end(), [](Joystick* pJoy){ return pJoy != nullptr; }); - - if (all_bound) - { - // We already have all valid joystick devices, bail early - return true; - } // Else, at least one player stick is unbound - - // Bind the added stick, if we want it + // Bind the added stick, if we want it. This runs even when all slots are already filled: a stick that came + // back may still be in its slot, and binding it again restarts force feedback (Joy-0 only) on its new handle. if (Using_in_game_options) { // New menu system lets players select joysticks in-game From 7fac93bc22ad37cd63314f42c543f7abfbdc43f6 Mon Sep 17 00:00:00 2001 From: Dustinspace217 Date: Sat, 17 Oct 2026 11:52:29 -0700 Subject: [PATCH 2/2] Shorten comments in the joystick add handler --- code/io/joy-sdl.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/code/io/joy-sdl.cpp b/code/io/joy-sdl.cpp index 4cd80ee0ae7..4366460f80f 100644 --- a/code/io/joy-sdl.cpp +++ b/code/io/joy-sdl.cpp @@ -119,7 +119,7 @@ bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid) return true; } - // Multiple sticks of the same model. If only one of them is plugged in right now, that's the one + // Multiple sticks of the same model -> use the only one that's plugged in return num_attached == 1 && testStick->isAttached(); } @@ -479,14 +479,12 @@ bool device_event_handler(const SDL_Event &evt) // A new joystick has been added, add it to our list and check if it's the configured joystick if (getJoystickForID(joyDeviceEvent.which) != nullptr) { - // SDL also sends this event for sticks that were already plugged in when it started, and init() has - // already set those up. Instance IDs are never reused, so a known ID is never a new connection. + // SDL also sends this at startup for sticks init() already set up; instance IDs are never reused return true; } Joystick *addedStick = nullptr; - // When a known stick comes back, this keeps its old device handles open until the stick has been given its - // slot again below, so force feedback on the old handle is shut down before that handle is closed + // keeps a returning stick's old handles open until it is rebound, so force feedback is shut down first JoystickPtr oldHandles; { auto device = SDL_OpenJoystick(joyDeviceEvent.which); @@ -499,17 +497,15 @@ bool device_event_handler(const SDL_Event &evt) // Add the new device to our list auto added = JoystickPtr(new Joystick(joyDeviceEvent.which)); - // The Joystick holds its own handle, this one was only needed to check that the device opens + // the Joystick opened its own handle SDL_CloseJoystick(device); for (auto iter = joysticks.begin(); iter != joysticks.end(); ++iter) { - // must use GUID here as joystick IDs are never reused. Only an unplugged entry can be this stick - // coming back: two sticks of the same model share a GUID, and taking over a stick that is still - // plugged in would leave that one dead. + // must use GUID here as joystick IDs are never reused; unplugged only, same-model sticks share a GUID if (!(*iter)->isAttached() && (*iter)->getGUID() == added->getGUID()) { - // A known stick that was unplugged and plugged back in: refresh it, then give it its slot back below. + // Already have this stick *(*iter) = std::move(*added); oldHandles = std::move(added); addedStick = iter->get(); @@ -530,8 +526,7 @@ bool device_event_handler(const SDL_Event &evt) } } - // Bind the added stick, if we want it. This runs even when all slots are already filled: a stick that came - // back may still be in its slot, and binding it again restarts force feedback (Joy-0 only) on its new handle. + // Bind the added stick, if we want it (also when all slots are full: a returning stick may still hold its slot) if (Using_in_game_options) { // New menu system lets players select joysticks in-game