Skip to content

Commit c9166a7

Browse files
authored
Merge pull request #9 from nefarius/wgi-display-name
Add vendor/product ID storage and gamepad device identification for WGI backend
2 parents 0d2d99e + 2aef98e commit c9166a7

2 files changed

Lines changed: 132 additions & 6 deletions

File tree

src/wgi_backend.cpp

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
#include <winrt/Windows.Foundation.Collections.h>
44
#include <winrt/Windows.Gaming.Input.h>
55
#include <array>
6+
#include <cstdint>
67
#include <mutex>
78
#include <optional>
89
#include <string>
10+
#include <utility>
911

1012
namespace
1113
{
@@ -15,6 +17,12 @@ namespace
1517

1618
constexpr int kMaxSlots = 4;
1719

20+
/**
21+
* @brief Converts a WinRT GamepadButtons bitmask into the internal 16-bit button mask.
22+
*
23+
* @param wgi Bitfield of WinRT GamepadButtons flags to map.
24+
* @return uint16_t Bitmask where each set bit corresponds to the internal Button enum value for a pressed button.
25+
*/
1826
uint16_t MapGamepadButtons(GamepadButtons wgi)
1927
{
2028
uint16_t b = 0;
@@ -35,6 +43,63 @@ namespace
3543
if ((wgi & GamepadButtons::RightThumbstick) != GamepadButtons::None) b |= std::to_underlying(RightThumb);
3644
return b;
3745
}
46+
47+
/**
48+
* @brief Obtains a human-friendly display name for a gamepad, falling back to a numbered default.
49+
*
50+
* Attempts to retrieve the controller's DisplayName from its RawGameController representation.
51+
* If a display name is unavailable or retrieval fails, returns "Gamepad N" where N is
52+
* slotIndex + 1.
53+
*
54+
* @param pad WinRT Gamepad instance to query.
55+
* @param slotIndex Zero-based slot index used to generate the fallback name.
56+
* @return std::string The resolved display name or a fallback like "Gamepad 1".
57+
*/
58+
std::string GetDisplayNameForGamepad(Gamepad const& pad, int slotIndex)
59+
{
60+
try
61+
{
62+
auto raw = RawGameController::FromGameController(pad);
63+
if (raw)
64+
{
65+
auto name = raw.DisplayName();
66+
if (!name.empty())
67+
return winrt::to_string(name);
68+
}
69+
}
70+
catch (...) {}
71+
return "Gamepad " + std::to_string(slotIndex + 1);
72+
}
73+
74+
/**
75+
* @brief Retrieve the hardware vendor and product IDs for a gamepad.
76+
*
77+
* Attempts to obtain the controller's hardware vendor and product identifiers
78+
* and writes them to the provided pointers. If retrieval fails or the
79+
* identifiers are unavailable, writes 0 to the corresponding outputs.
80+
* If both output pointers are null, the function does nothing.
81+
*
82+
* @param pad The gamepad to query.
83+
* @param vendorId Pointer to receive the vendor ID, or nullptr to skip.
84+
* @param productId Pointer to receive the product ID, or nullptr to skip.
85+
*/
86+
void GetDeviceIdsForGamepad(Gamepad const& pad, uint16_t* vendorId, uint16_t* productId)
87+
{
88+
if (!vendorId && !productId) return;
89+
try
90+
{
91+
auto raw = RawGameController::FromGameController(pad);
92+
if (raw)
93+
{
94+
if (vendorId) *vendorId = raw.HardwareVendorId();
95+
if (productId) *productId = raw.HardwareProductId();
96+
return;
97+
}
98+
}
99+
catch (...) {}
100+
if (vendorId) *vendorId = 0;
101+
if (productId) *productId = 0;
102+
}
38103
}
39104

40105
struct WgiBackend::Impl
@@ -43,10 +108,20 @@ struct WgiBackend::Impl
43108
std::array<std::optional<Gamepad>, kMaxSlots> slotGamepads{};
44109
GamepadState states[kMaxSlots]{};
45110
std::array<std::string, kMaxSlots> slotDisplayNames;
111+
std::array<uint16_t, kMaxSlots> slotVendorIds{};
112+
std::array<uint16_t, kMaxSlots> slotProductIds{};
46113

47114
winrt::event_token addedToken;
48115
winrt::event_token removedToken;
49116

117+
/**
118+
* @brief Assigns a newly connected gamepad to the first available backend slot and records its metadata.
119+
*
120+
* If a free slot exists, stores the provided gamepad handle in that slot and populates the slot's
121+
* display name and vendor/product IDs. If all slots are occupied, the added gamepad is ignored.
122+
*
123+
* @param pad The gamepad that was added.
124+
*/
50125
void OnGamepadAdded(IInspectable const&, Gamepad const& pad)
51126
{
52127
std::lock_guard lock(mutex);
@@ -55,12 +130,21 @@ struct WgiBackend::Impl
55130
if (!slotGamepads[i])
56131
{
57132
slotGamepads[i] = pad;
58-
slotDisplayNames[i] = "Gamepad " + std::to_string(i);
133+
slotDisplayNames[i] = GetDisplayNameForGamepad(pad, i);
134+
GetDeviceIdsForGamepad(pad, &slotVendorIds[i], &slotProductIds[i]);
59135
break;
60136
}
61137
}
62138
}
63139

140+
/**
141+
* @brief Handles a gamepad removal event by clearing the corresponding slot.
142+
*
143+
* Searches the tracked slots for the given gamepad handle; when a match is found,
144+
* removes the stored handle and resets the slot's vendor and product IDs to zero.
145+
*
146+
* @param pad The gamepad that was removed.
147+
*/
64148
void OnGamepadRemoved(IInspectable const&, Gamepad const& pad)
65149
{
66150
std::lock_guard lock(mutex);
@@ -69,6 +153,8 @@ struct WgiBackend::Impl
69153
if (slotGamepads[i] && slotGamepads[i] == pad)
70154
{
71155
slotGamepads[i].reset();
156+
slotVendorIds[i] = 0;
157+
slotProductIds[i] = 0;
72158
break;
73159
}
74160
}
@@ -86,6 +172,13 @@ WgiBackend::~WgiBackend()
86172
}
87173
}
88174

175+
/**
176+
* @brief Initialize the WinRT gamepad backend, enumerate currently connected gamepads, and subscribe to add/remove events.
177+
*
178+
* 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.
179+
*
180+
* @param hwnd Window handle provided for initialization context; currently accepted for API compatibility and not otherwise used.
181+
*/
89182
void WgiBackend::Init(HWND)
90183
{
91184
thread_local static bool winrtInitialized = false;
@@ -102,8 +195,10 @@ void WgiBackend::Init(HWND)
102195
{
103196
try
104197
{
105-
impl_->slotGamepads[i] = gamepads.GetAt(static_cast<uint32_t>(i));
106-
impl_->slotDisplayNames[i] = "Gamepad " + std::to_string(i);
198+
auto pad = gamepads.GetAt(static_cast<uint32_t>(i));
199+
impl_->slotGamepads[i] = pad;
200+
impl_->slotDisplayNames[i] = GetDisplayNameForGamepad(pad, i);
201+
GetDeviceIdsForGamepad(pad, &impl_->slotVendorIds[i], &impl_->slotProductIds[i]);
107202
}
108203
catch (winrt::hresult_out_of_bounds const&)
109204
{
@@ -156,12 +251,42 @@ const GamepadState& WgiBackend::GetState(int slot) const
156251

157252
const char* WgiBackend::GetName() const { return Name; }
158253

254+
/**
255+
* @brief Retrieves the human-readable display name for a gamepad slot.
256+
*
257+
* @param slot Zero-based slot index.
258+
* @return const char* Pointer to a null-terminated C string containing the display name for the given slot, or `nullptr` if the slot index is out of range or no gamepad is present in that slot. The returned pointer is valid until the next call to GetSlotDisplayName on the same thread.
259+
*/
159260
const char* WgiBackend::GetSlotDisplayName(int slot) const
160261
{
161262
if (slot < 0 || slot >= kMaxSlots) return nullptr;
162263
std::lock_guard lock(impl_->mutex);
163264
if (!impl_->slotGamepads[slot]) return nullptr;
164-
thread_local static std::string result;
165-
result = impl_->slotDisplayNames[slot];
166-
return result.c_str();
265+
thread_local static std::array<std::string, kMaxSlots> slotResultBuffers;
266+
slotResultBuffers[slot] = impl_->slotDisplayNames[slot];
267+
return slotResultBuffers[slot].c_str();
268+
}
269+
270+
/**
271+
* @brief Retrieve the vendor and product identifiers for a slot's gamepad.
272+
*
273+
* 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.
274+
*
275+
* @param slot Slot index (0 .. kMaxSlots - 1) to query.
276+
* @param vendorId Pointer that receives the vendor ID, or `nullptr` to ignore.
277+
* @param productId Pointer that receives the product ID, or `nullptr` to ignore.
278+
*/
279+
void WgiBackend::GetSlotDeviceIds(int slot, uint16_t* vendorId, uint16_t* productId) const
280+
{
281+
if (slot < 0 || slot >= kMaxSlots)
282+
{
283+
if (vendorId) *vendorId = 0;
284+
if (productId) *productId = 0;
285+
return;
286+
}
287+
std::lock_guard lock(impl_->mutex);
288+
uint16_t vid = impl_->slotGamepads[slot] ? impl_->slotVendorIds[slot] : 0;
289+
uint16_t pid = impl_->slotGamepads[slot] ? impl_->slotProductIds[slot] : 0;
290+
if (vendorId) *vendorId = vid;
291+
if (productId) *productId = pid;
167292
}

src/wgi_backend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class WgiBackend final : public IInputBackend
1717
[[nodiscard]] const GamepadState& GetState(int slot) const override;
1818
[[nodiscard]] const char* GetName() const override;
1919
[[nodiscard]] const char* GetSlotDisplayName(int slot) const override;
20+
void GetSlotDeviceIds(int slot, uint16_t* vendorId, uint16_t* productId) const override;
2021

2122
private:
2223
struct Impl;

0 commit comments

Comments
 (0)