22#include < winrt/Windows.Foundation.h>
33#include < winrt/Windows.Foundation.Collections.h>
44#include < winrt/Windows.Gaming.Input.h>
5- #include < array>
65#include < cstdint>
76#include < mutex>
87#include < optional>
98#include < string>
109#include < utility>
10+ #include < vector>
1111
1212namespace
1313{
1414 using namespace winrt ;
1515 using namespace Windows ::Gaming::Input;
1616 using namespace Windows ::Foundation;
1717
18- constexpr int kMaxSlots = 4 ;
19-
2018 /* *
2119 * @brief Converts a WinRT GamepadButtons bitmask into the internal 16-bit button mask.
2220 *
@@ -109,59 +107,64 @@ namespace
109107struct WgiBackend ::Impl
110108{
111109 mutable std::mutex mutex;
112- std::array <std::optional<Gamepad>, kMaxSlots > slotGamepads{} ;
113- GamepadState states[ kMaxSlots ]{} ;
114- std::array <std::string, kMaxSlots > slotDisplayNames;
115- std::array <uint16_t , kMaxSlots > slotVendorIds{} ;
116- std::array <uint16_t , kMaxSlots > slotProductIds{} ;
110+ std::vector <std::optional<Gamepad>> slotGamepads;
111+ std::vector< GamepadState> states;
112+ std::vector <std::string> slotDisplayNames;
113+ std::vector <uint16_t > slotVendorIds;
114+ std::vector <uint16_t > slotProductIds;
117115
118116 event_token addedToken;
119117 event_token removedToken;
120118
121119 /* *
122- * @brief Assigns a newly connected gamepad to the first available backend slot and records its metadata.
123- *
124- * If a free slot exists, stores the provided gamepad handle in that slot and populates the slot's
125- * display name and vendor/product IDs. If all slots are occupied, the added gamepad is ignored.
120+ * @brief Re-enumerate the system gamepad list and rebuild the slot arrays.
126121 *
127- * @param pad The gamepad that was added .
122+ * Must be called with mutex held .
128123 */
129- void OnGamepadAdded ( const IInspectable&, const Gamepad& pad )
124+ void ResyncFromSystemLocked ( )
130125 {
131- std::lock_guard lock (mutex);
132- for (int i = 0 ; i < kMaxSlots ; ++i)
126+ auto gamepads = Gamepad::Gamepads ();
127+ const size_t count = static_cast <size_t >(gamepads.Size ());
128+
129+ slotGamepads.assign (count, std::nullopt );
130+ states.assign (count, GamepadState{});
131+ slotDisplayNames.assign (count, std::string{});
132+ slotVendorIds.assign (count, 0 );
133+ slotProductIds.assign (count, 0 );
134+
135+ for (size_t i = 0 ; i < count; ++i)
133136 {
134- if (!slotGamepads[i])
137+ try
135138 {
139+ auto pad = gamepads.GetAt (static_cast <uint32_t >(i));
136140 slotGamepads[i] = pad;
137- slotDisplayNames[i] = GetDisplayNameForGamepad (pad, i );
141+ slotDisplayNames[i] = GetDisplayNameForGamepad (pad, static_cast < int >(i) );
138142 GetDeviceIdsForGamepad (pad, &slotVendorIds[i], &slotProductIds[i]);
143+ }
144+ catch (...)
145+ {
146+ // If enumeration fails mid-way, leave remaining entries as empty/default.
139147 break ;
140148 }
141149 }
142150 }
143151
144152 /* *
145- * @brief Handles a gamepad removal event by clearing the corresponding slot.
146- *
147- * Searches the tracked slots for the given gamepad handle; when a match is found,
148- * removes the stored handle and resets the slot's vendor and product IDs to zero.
149- *
150- * @param pad The gamepad that was removed.
153+ * @brief Handles a gamepad add event by re-syncing the system list.
151154 */
152- void OnGamepadRemoved (const IInspectable&, const Gamepad& pad )
155+ void OnGamepadAdded (const IInspectable&, const Gamepad&)
153156 {
154157 std::lock_guard lock (mutex);
155- for ( int i = 0 ; i < kMaxSlots ; ++i)
156- {
157- if (slotGamepads[i] && slotGamepads[i] == pad)
158- {
159- slotGamepads[i]. reset ();
160- slotVendorIds[i] = 0 ;
161- slotProductIds[i] = 0 ;
162- break ;
163- }
164- }
158+ ResyncFromSystemLocked ();
159+ }
160+
161+ /* *
162+ * @brief Handles a gamepad remove event by re-syncing the system list.
163+ */
164+ void OnGamepadRemoved ( const IInspectable&, const Gamepad&)
165+ {
166+ std::lock_guard lock (mutex);
167+ ResyncFromSystemLocked ();
165168 }
166169};
167170
@@ -181,7 +184,7 @@ WgiBackend::~WgiBackend()
181184/* *
182185 * @brief Initialize the WinRT gamepad backend, enumerate currently connected gamepads, and subscribe to add/remove events.
183186 *
184- * Initializes the WinRT apartment once per thread, acquires the internal mutex, populates up to kMaxSlots with already-connected gamepads (storing each slot's handle, display name, and vendor/product IDs), and registers handlers for future GamepadAdded and GamepadRemoved events, saving their subscription tokens.
187+ * Initializes the WinRT apartment once per thread, acquires the internal mutex, populates slots with already-connected gamepads (storing each slot's handle, display name, and vendor/product IDs), and registers handlers for future GamepadAdded and GamepadRemoved events, saving their subscription tokens.
185188 *
186189 * @param hwnd Window handle provided for initialization context; currently accepted for API compatibility and not otherwise used.
187190 */
@@ -195,22 +198,7 @@ void WgiBackend::Init(HWND)
195198 }
196199
197200 std::lock_guard lock (impl_->mutex );
198- // Enumerate already-connected gamepads (GetAt in loop avoids IVectorView::Size() auto return type issue with MSVC)
199- auto gamepads = Gamepad::Gamepads ();
200- for (int i = 0 ; i < kMaxSlots ; ++i)
201- {
202- try
203- {
204- auto pad = gamepads.GetAt (static_cast <uint32_t >(i));
205- impl_->slotGamepads [i] = pad;
206- impl_->slotDisplayNames [i] = GetDisplayNameForGamepad (pad, i);
207- GetDeviceIdsForGamepad (pad, &impl_->slotVendorIds [i], &impl_->slotProductIds [i]);
208- }
209- catch (const hresult_out_of_bounds&)
210- {
211- break ;
212- }
213- }
201+ impl_->ResyncFromSystemLocked ();
214202 impl_->addedToken = Gamepad::GamepadAdded ([this ](const IInspectable& s, const Gamepad& p)
215203 {
216204 impl_->OnGamepadAdded (s, p);
@@ -224,7 +212,8 @@ void WgiBackend::Init(HWND)
224212void WgiBackend::Poll ()
225213{
226214 std::lock_guard lock (impl_->mutex );
227- for (int i = 0 ; i < kMaxSlots ; ++i)
215+ const size_t count = impl_->states .size ();
216+ for (size_t i = 0 ; i < count; ++i)
228217 {
229218 auto & gs = impl_->states [i];
230219 if (!impl_->slotGamepads [i])
@@ -251,14 +240,28 @@ void WgiBackend::Poll()
251240 }
252241}
253242
254- int WgiBackend::GetMaxSlots () const { return kMaxSlots ; }
243+ int WgiBackend::GetMaxSlots () const
244+ {
245+ std::lock_guard lock (impl_->mutex );
246+ return static_cast <int >(impl_->states .size ());
247+ }
255248
256249const GamepadState& WgiBackend::GetState (int slot) const
257250{
258251 static constexpr GamepadState empty{};
259- if (slot < 0 || slot >= kMaxSlots )
252+ if (slot < 0 )
260253 return empty;
261- return impl_->states [slot];
254+
255+ std::lock_guard lock (impl_->mutex );
256+ if (static_cast <size_t >(slot) >= impl_->states .size ())
257+ return empty;
258+
259+ // Return a stable reference even if slots resize on another thread.
260+ thread_local std::vector<GamepadState> slotResultStates;
261+ if (slotResultStates.size () < impl_->states .size ())
262+ slotResultStates.resize (impl_->states .size ());
263+ slotResultStates[static_cast <size_t >(slot)] = impl_->states [static_cast <size_t >(slot)];
264+ return slotResultStates[static_cast <size_t >(slot)];
262265}
263266
264267const char * WgiBackend::GetName () const { return Name; }
@@ -271,34 +274,43 @@ const char* WgiBackend::GetName() const { return Name; }
271274 */
272275const char * WgiBackend::GetSlotDisplayName (int slot) const
273276{
274- if (slot < 0 || slot >= kMaxSlots ) return nullptr ;
277+ if (slot < 0 ) return nullptr ;
275278 std::lock_guard lock (impl_->mutex );
276- if (!impl_->slotGamepads [slot]) return nullptr ;
277- thread_local std::array<std::string, kMaxSlots > slotResultBuffers;
278- slotResultBuffers[slot] = impl_->slotDisplayNames [slot];
279- return slotResultBuffers[slot].c_str ();
279+ if (static_cast <size_t >(slot) >= impl_->slotGamepads .size ()) return nullptr ;
280+ if (!impl_->slotGamepads [static_cast <size_t >(slot)]) return nullptr ;
281+ thread_local std::vector<std::string> slotResultBuffers;
282+ if (slotResultBuffers.size () < impl_->slotDisplayNames .size ())
283+ slotResultBuffers.resize (impl_->slotDisplayNames .size ());
284+ slotResultBuffers[static_cast <size_t >(slot)] = impl_->slotDisplayNames [static_cast <size_t >(slot)];
285+ return slotResultBuffers[static_cast <size_t >(slot)].c_str ();
280286}
281287
282288/* *
283289 * @brief Retrieve the vendor and product identifiers for a slot's gamepad.
284290 *
285291 * Writes the 16-bit vendor and product IDs for the gamepad in the given slot into the provided output pointers. If the slot index is out of range or no gamepad is present in that slot, `0` is written for each ID. Passing a `nullptr` for either output pointer suppresses writing that value.
286292 *
287- * @param slot Slot index (0 .. kMaxSlots - 1) to query.
293+ * @param slot Slot index to query.
288294 * @param vendorId Pointer that receives the vendor ID, or `nullptr` to ignore.
289295 * @param productId Pointer that receives the product ID, or `nullptr` to ignore.
290296 */
291297void WgiBackend::GetSlotDeviceIds (int slot, uint16_t * vendorId, uint16_t * productId) const
292298{
293- if (slot < 0 || slot >= kMaxSlots )
299+ if (slot < 0 )
294300 {
295301 if (vendorId) *vendorId = 0 ;
296302 if (productId) *productId = 0 ;
297303 return ;
298304 }
299305 std::lock_guard lock (impl_->mutex );
300- uint16_t vid = impl_->slotGamepads [slot] ? impl_->slotVendorIds [slot] : 0 ;
301- uint16_t pid = impl_->slotGamepads [slot] ? impl_->slotProductIds [slot] : 0 ;
306+ if (static_cast <size_t >(slot) >= impl_->slotGamepads .size ())
307+ {
308+ if (vendorId) *vendorId = 0 ;
309+ if (productId) *productId = 0 ;
310+ return ;
311+ }
312+ uint16_t vid = impl_->slotGamepads [static_cast <size_t >(slot)] ? impl_->slotVendorIds [static_cast <size_t >(slot)] : 0 ;
313+ uint16_t pid = impl_->slotGamepads [static_cast <size_t >(slot)] ? impl_->slotProductIds [static_cast <size_t >(slot)] : 0 ;
302314 if (vendorId) *vendorId = vid;
303315 if (productId) *productId = pid;
304316}
0 commit comments