Skip to content

Commit 108012f

Browse files
authored
Merge pull request #1 from nefarius/friendly-names
Added friendly product names for well-known hardware IDs
2 parents ac777b8 + 4461c2d commit 108012f

12 files changed

Lines changed: 399 additions & 12 deletions

src/dinput_backend.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "dinput_backend.h"
22
#include "sony_layout.h"
3+
#include "usb_names.h"
34
#include <algorithm>
45
#include <ranges>
56
#include <utility>
@@ -124,9 +125,39 @@ const GamepadState& DInputBackend::GetState(int slot) const
124125
return states_[slot];
125126
}
126127

128+
/**
129+
* Retrieve the backend's short name.
130+
*
131+
* @return Pointer to a null-terminated C-string with the backend name.
132+
*/
127133
const char* DInputBackend::GetName() const { return Name; }
128134

129-
// ── device enumeration ───────────────────────────────────────
135+
/**
136+
* @brief Retrieve a human-friendly display name for the device occupying a slot.
137+
*
138+
* @param slot Device slot index (valid range: 0..kMaxDevices-1).
139+
* @return const char* Null-terminated display name for the device, or `nullptr` if the slot is out of range or no device is present.
140+
*/
141+
const char* DInputBackend::GetSlotDisplayName(int slot) const
142+
{
143+
if (slot < 0 || slot >= kMaxDevices) return nullptr;
144+
const auto it = std::ranges::find_if(devices_, [slot](const DeviceInfo& d) { return d.slot == slot; });
145+
if (it == devices_.end()) return nullptr;
146+
return GetFriendlyName(it->vendorId, it->productId);
147+
}
148+
149+
/**
150+
* @brief Callback invoked for each DirectInput device during enumeration.
151+
*
152+
* Marks an already-known device as found or attempts to create and register a new
153+
* DeviceInfo for the discovered device. When SetupDevice succeeds, the new device
154+
* is appended to the backend's device list and marked as found.
155+
*
156+
* @param inst Pointer to the enumerated device instance information.
157+
* @param ctx User context passed to the enumerator; expected to be a pointer to
158+
* the DInputBackend instance.
159+
* @return BOOL Always returns DIENUM_CONTINUE to continue device enumeration.
160+
*/
130161

131162
BOOL CALLBACK DInputBackend::EnumCallback(
132163
const DIDEVICEINSTANCEW* inst, VOID* ctx)

src/dinput_backend.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,37 @@ class DInputBackend final : public IInputBackend
1717

1818
~DInputBackend() override;
1919
void Init(HWND hwnd) override;
20+
/**
21+
* Poll connected devices and update internal input states.
22+
*/
23+
24+
/**
25+
* Maximum number of input slots supported by this backend.
26+
* @returns The maximum slot count.
27+
*/
28+
29+
/**
30+
* Retrieve the current gamepad state for a given slot.
31+
* @param slot Index of the input slot to query.
32+
* @returns Reference to the `GamepadState` for the specified slot.
33+
*/
34+
35+
/**
36+
* Name identifying this input backend.
37+
* @returns Pointer to a null-terminated string with the backend name.
38+
*/
39+
40+
/**
41+
* Human-readable display name for a specific slot.
42+
* @param slot Index of the input slot to query.
43+
* @returns Pointer to a null-terminated string describing the slot or its assigned device.
44+
* May return nullptr when no slot/device mapping exists or when no name is available.
45+
*/
2046
void Poll() override;
2147
[[nodiscard]] int GetMaxSlots() const override;
2248
[[nodiscard]] const GamepadState& GetState(int slot) const override;
2349
[[nodiscard]] const char* GetName() const override;
50+
[[nodiscard]] const char* GetSlotDisplayName(int slot) const override;
2451

2552
private:
2653
struct DeviceInfo

src/gamepad_renderer.cpp

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,25 +149,53 @@ static void DrawSmallButton(ImDrawList* dl, const Layout& L,
149149
dl->AddText(ImVec2(center.x - ts.x * 0.5f, center.y - ts.y * 0.5f), tc, label);
150150
}
151151

152-
// ── main entry point ─────────────────────────────────────────
152+
/**
153+
* @brief Render a controller panel that visualizes a gamepad's layout and state.
154+
*
155+
* Renders a framed panel at the given position/size containing a header (built from the player
156+
* slot, optional display name, and backend name), a connection-status pill, and a scaled
157+
* representation of the controller showing body, D-pad, face buttons, thumbsticks, bumpers,
158+
* triggers, and small buttons. When the gamepad is not connected, displays a centered
159+
* "No controller detected" message instead of the controls.
160+
*
161+
* @param dl ImGui draw list to use for low-level drawing operations.
162+
* @param panelPos Top-left corner of the panel in screen coordinates.
163+
* @param panelSize Width and height of the panel in screen coordinates.
164+
* @param gs Current gamepad state (buttons, sticks, triggers, connection).
165+
* @param slotIndex Zero-based player slot index used in the header label.
166+
* @param backendName Null-terminated string identifying the input backend (shown in header).
167+
* @param displayName Optional null-terminated display name for the controller; when non-empty
168+
* it is included in the header as "Player N - DisplayName [backend]".
169+
*/
153170

154171
void DrawGamepad(ImDrawList* dl, ImVec2 panelPos, ImVec2 panelSize,
155-
const GamepadState& gs, int slotIndex, const char* backendName) {
172+
const GamepadState& gs, int slotIndex, const char* backendName,
173+
const char* displayName) {
156174
dl->AddRectFilled(panelPos,
157175
ImVec2(panelPos.x + panelSize.x, panelPos.y + panelSize.y),
158176
BgDim(), 8.0f);
159177

160-
auto header = std::format("Player {} [{}]", slotIndex + 1, backendName);
178+
std::string header;
179+
if (displayName && displayName[0] != '\0')
180+
header = std::format("Player {} - {} [{}]", slotIndex + 1, displayName, backendName);
181+
else
182+
header = std::format("Player {} [{}]", slotIndex + 1, backendName);
161183
ImVec2 hts = ImGui::CalcTextSize(header.c_str());
162184
float headerH = hts.y + 10.0f;
185+
186+
const char* status = gs.connected ? "Connected" : "Not Connected";
187+
ImVec2 sts = ImGui::CalcTextSize(status);
188+
const float pillLeft = panelPos.x + panelSize.x - sts.x - 18 - 4;
189+
const float headerClipRight = pillLeft;
190+
dl->PushClipRect(ImVec2(panelPos.x + 10, panelPos.y),
191+
ImVec2(headerClipRight, panelPos.y + headerH), true);
163192
dl->AddText(ImVec2(panelPos.x + 10, panelPos.y + 5),
164193
gs.connected ? IM_COL32(220, 220, 220, 255)
165194
: IM_COL32(100, 100, 100, 255),
166195
header.c_str());
196+
dl->PopClipRect();
167197

168198
{
169-
const char* status = gs.connected ? "Connected" : "Not Connected";
170-
ImVec2 sts = ImGui::CalcTextSize(status);
171199
float px = panelPos.x + panelSize.x - sts.x - 18;
172200
float py = panelPos.y + 5;
173201
ImU32 pillCol = gs.connected ? IM_COL32(50, 180, 80, 200)

src/gamepad_renderer.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,27 @@
33
#include "imgui.h"
44
#include <algorithm>
55

6-
namespace GamepadRenderer
6+
/**
7+
* Layout for mapping logical gamepad coordinates to screen space.
8+
*
9+
* Encapsulates an origin and separate X/Y scale factors used to transform
10+
* positions and sizes from the layout's logical coordinate system into
11+
* screen coordinates.
12+
*/
13+
14+
/**
15+
* Transform a logical point into screen coordinates by applying the layout origin and scaling.
16+
* @param x X coordinate in the layout's logical coordinate space.
17+
* @param y Y coordinate in the layout's logical coordinate space.
18+
* @returns The transformed point in screen coordinates.
19+
*/
20+
21+
/**
22+
* Scale a scalar value using the layout's uniform scale (the smaller of sx and sy).
23+
* @param v Value in the layout's logical units.
24+
* @returns The value scaled into screen units.
25+
*/
26+
namespace GamepadRenderer
727
{
828
struct Layout
929
{
@@ -19,5 +39,6 @@ namespace GamepadRenderer
1939
};
2040

2141
void DrawGamepad(ImDrawList* dl, ImVec2 panelPos, ImVec2 panelSize,
22-
const GamepadState& gs, int slotIndex, const char* backendName);
42+
const GamepadState& gs, int slotIndex, const char* backendName,
43+
const char* displayName = nullptr);
2344
} // namespace GamepadRenderer

src/hidapi_backend.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "hidapi_backend.h"
22
#include "sony_layout.h"
3+
#include "usb_names.h"
34
#include <algorithm>
45
#include <ranges>
56
#include <utility>
@@ -105,9 +106,36 @@ const GamepadState& HidApiBackend::GetState(const int slot) const
105106
return states_[slot];
106107
}
107108

109+
/**
110+
* @brief Returns the backend's identifier string.
111+
*
112+
* @return const char* Null-terminated name of this HID backend.
113+
*/
108114
const char* HidApiBackend::GetName() const { return Name; }
109115

110-
// ── device enumeration ───────────────────────────────────────
116+
/**
117+
* @brief Retrieve the user-facing display name for a device assigned to a slot.
118+
*
119+
* @param slot Slot index to query (valid range: 0 to kMaxDevices - 1).
120+
* @return const char* Pointer to a friendly name derived from the device's vendor and product IDs,
121+
* or `nullptr` if the slot is out of range or no device is assigned to that slot.
122+
*/
123+
const char* HidApiBackend::GetSlotDisplayName(int slot) const
124+
{
125+
if (slot < 0 || slot >= kMaxDevices) return nullptr;
126+
const auto it = std::ranges::find_if(devices_, [slot](const std::unique_ptr<DeviceInfo>& d) { return d->slot == slot; });
127+
if (it == devices_.end()) return nullptr;
128+
return GetFriendlyName((*it)->vendorId, (*it)->productId);
129+
}
130+
131+
/**
132+
* @brief Enumerates connected HID devices and synchronizes the backend's device list.
133+
*
134+
* @details Scans the system for present HID device interfaces, marks previously-known
135+
* devices that remain present, attempts to open and initialize newly discovered devices,
136+
* and removes (and closes) devices that are no longer present. Updates the internal
137+
* devices_ container and associated per-slot state as devices are added or removed.
138+
*/
111139

112140
void HidApiBackend::EnumerateDevices()
113141
{

src/hidapi_backend.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,44 @@ class HidApiBackend final : public IInputBackend
1818
static constexpr int kMaxDevices = 16;
1919

2020
~HidApiBackend() override;
21+
/**
22+
* Poll HID devices, process incoming reports, and update internal gamepad states.
23+
*/
24+
25+
/**
26+
* Return the maximum number of device slots supported by this backend.
27+
* @returns The maximum number of slots.
28+
*/
29+
30+
/**
31+
* Retrieve the current gamepad state for a given slot.
32+
* @param slot Index of the slot to query; valid range is 0 .. GetMaxSlots()-1.
33+
* @returns Reference to the GamepadState for the specified slot.
34+
*/
35+
36+
/**
37+
* Get the backend name identifier.
38+
* @returns Null-terminated string identifying this backend.
39+
*/
40+
41+
/**
42+
* Get a human-readable display name for the device assigned to a slot.
43+
* @param slot Index of the slot to query; valid range is 0 .. GetMaxSlots()-1.
44+
* @returns A null-terminated display name for the slot, or nullptr when no name is available.
45+
*/
46+
47+
/**
48+
* Per-device runtime information and resources used to manage a HID device.
49+
*
50+
* Contains device path, OS handle and overlapped I/O state, read buffer,
51+
* HID preparsed data and capability descriptors, assigned slot index, flags
52+
* tracking discovery and read state, and the device's vendor/product IDs.
53+
*/
2154
void Poll() override;
2255
[[nodiscard]] int GetMaxSlots() const override;
2356
[[nodiscard]] const GamepadState& GetState(int slot) const override;
2457
[[nodiscard]] const char* GetName() const override;
58+
[[nodiscard]] const char* GetSlotDisplayName(int slot) const override;
2559

2660
private:
2761
struct DeviceInfo

src/input_backend.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,52 @@
22
#include "gamepad_state.h"
33
#include <Windows.h>
44

5+
/**
6+
* Abstract interface for input backends that provide per-slot gamepad state.
7+
*/
8+
9+
/**
10+
* Virtual destructor to allow proper cleanup of derived implementations.
11+
*/
12+
13+
/**
14+
* Initialize the backend with an optional window handle.
15+
* @param hwnd Window handle used for backend initialization (may be ignored).
16+
*/
17+
18+
/**
19+
* Handle a window message.
20+
* @param msg The window message identifier.
21+
* @param wParam The message wParam.
22+
* @param lParam The message lParam.
23+
* @returns `true` if the message was handled, `false` otherwise.
24+
*/
25+
26+
/**
27+
* Update the backend's input state for all slots.
28+
*/
29+
30+
/**
31+
* Get the maximum number of input slots supported by this backend.
32+
* @returns The maximum number of supported input slots.
33+
*/
34+
35+
/**
36+
* Get the current gamepad state for a specific slot.
37+
* @param slot Index of the input slot.
38+
* @returns A const reference to the GamepadState for the specified slot.
39+
*/
40+
41+
/**
42+
* Get the name of this backend.
43+
* @returns A null-terminated C-string identifying the backend.
44+
*/
45+
46+
/**
47+
* Get a display name for a specific slot, if available.
48+
* @param slot Index of the input slot.
49+
* @returns A null-terminated C-string containing the slot display name, or `nullptr` if none is available.
50+
*/
551
class IInputBackend
652
{
753
public:
@@ -16,4 +62,5 @@ class IInputBackend
1662
[[nodiscard]] virtual int GetMaxSlots() const = 0;
1763
[[nodiscard]] virtual const GamepadState& GetState(int slot) const = 0;
1864
[[nodiscard]] virtual const char* GetName() const = 0;
65+
[[nodiscard]] virtual const char* GetSlotDisplayName([[maybe_unused]] int slot) const { return nullptr; }
1966
};

src/main.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,17 @@ static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
214214
return DefWindowProc(hWnd, msg, wParam, lParam);
215215
}
216216

217+
/**
218+
* @brief Application entry point that initializes subsystems, runs the main event and render loop, and performs cleanup on exit.
219+
*
220+
* Initializes DPI awareness, registers the main window class, loads persisted preferences, creates the main window and Direct3D/ImGui backends, constructs input backends, and enters the primary message/poll/render loop that drives UI and input visualization. On exit it shuts down ImGui and Direct3D, saves state as needed, and unregisters the window class.
221+
*
222+
* @param hInstance Handle to the current application instance.
223+
* @param hPrevInstance Reserved; typically unused.
224+
* @param lpCmdLine Command line for the application as a Unicode string.
225+
* @param nCmdShow Controls how the window is to be shown.
226+
* @return int `0` on normal exit, non-zero on initialization failure (e.g., Direct3D creation failure).
227+
*/
217228
int APIENTRY wWinMain(
218229
_In_ HINSTANCE hInstance,
219230
_In_opt_ HINSTANCE,
@@ -354,11 +365,12 @@ int APIENTRY wWinMain(
354365
const GamepadState* state;
355366
int slotIndex;
356367
const char* backendName;
368+
const char* displayName;
357369
};
358370
std::vector<SlotInfo> slots;
359371
for (int i = 0; i < b->GetMaxSlots(); ++i)
360372
if (b->GetState(i).connected)
361-
slots.push_back({&b->GetState(i), i, b->GetName()});
373+
slots.push_back({&b->GetState(i), i, b->GetName(), b->GetSlotDisplayName(i)});
362374

363375
ImDrawList* dl = ImGui::GetWindowDrawList();
364376
ImVec2 origin = ImGui::GetCursorScreenPos();
@@ -393,7 +405,7 @@ int APIENTRY wWinMain(
393405

394406
GamepadRenderer::DrawGamepad(dl, pos, size,
395407
*slots[i].state, slots[i].slotIndex,
396-
slots[i].backendName);
408+
slots[i].backendName, slots[i].displayName);
397409
}
398410
}
399411

0 commit comments

Comments
 (0)