# Getting the Gamepad USB Device Name Unreal exposes `UInputDeviceSubsystem::GetMostRecentlyUsedHardwareDevice()`, which returns an `FHardwareDeviceIdentifier`. In stock Unreal this is nearly useless: on Windows it toggles between `"WindowsApplication"` (KBM) and `"XInputController"` (any XInput pad); on Linux it returns nothing meaningful at all. It cannot distinguish a DualSense from an Xbox pad. The USB-reported device name (e.g. `"Sony Interactive Entertainment Wireless Controller"`) is what we want to surface. This document describes the changes required on each platform to put that name into `FHardwareDeviceIdentifier`. ## How `FHardwareDeviceIdentifier` Gets Populated Input drivers wrap their `MessageHandler->OnControllerButton/Analog` calls in an `FInputDeviceScope` (stack-allocated, thread-local stack of pointers). The scope carries `InputDeviceName` (driver class) and `HardwareDeviceIdentifier` (physical device string). When the input event reaches `UInputDeviceSubsystem`, those two fields are copied into the `FHardwareDeviceIdentifier` record for that user. So the fix on each platform is the same shape: **at device-connect time, cache the USB device-name string; in the per-event scope, pass it as the `HardwareDeviceIdentifier`.** ## Windows: Patch the GameInput Plugin Location: `Engine/Plugins/Runtime/GameInput/Source/GameInputBase/`. The Microsoft GameInput SDK already exposes the data we need on every device via `GameInputDeviceInfo::displayName` (a `wchar_t*`). The plugin reads it for log lines (`GameInputUtils.cpp:18-23`) but does not propagate it. Steps: 1. **Cache at connect.** Add `FString CachedDisplayName` to `FGameInputDeviceContainer`. In its constructor / device-info init path (`GameInputDeviceContainer.cpp:44`, where `Info` is in scope), set: ```cpp CachedDisplayName = FString(Info->displayName); ``` `TCHAR == wchar_t` on Windows, so the conversion is direct. Lifetime matches the container — cleared automatically on disconnect. 2. **Make the container reachable from `FGameInputEventParams`.** The processor already gets a `Device` pointer; add a `Container` pointer so `GetHardwareDeviceIdentifierName` can read the cached string. 3. **Return it from `GetHardwareDeviceIdentifierName`** (`GameInputDeviceProcessor.cpp:61`). After the existing `bOverrideHardwareDeviceIdString` block, before the family-bucket switch: ```cpp if (Params.Container && !Params.Container->CachedDisplayName.IsEmpty()) { return Params.Container->CachedDisplayName; } ``` The override path still wins (explicit dev intent); the family-bucket fallback handles devices with empty `displayName`. Total: ~10 lines plus the field. All inside `#if GAME_INPUT_SUPPORT`. The user must enable the GameInput plugin and disable the default `XInputDevice` plugin (otherwise both drivers will dispatch events for the same pad). ## Linux: Patch `LinuxApplication.cpp` Location: `Engine/Source/Runtime/ApplicationCore/Private/Linux/LinuxApplication.cpp`. SDL already provides the device name via `SDL_GameControllerName()`. The Linux path calls it once for a log line in `AddGameController` (line 2175) and then discards it. The Linux path also does not push an `FInputDeviceScope` at all around its controller events — that's the second half of why `GetMostRecentlyUsedHardwareDevice` is useless on Linux. Steps: 1. **Store the name.** Add `FString DeviceName` to `SDLControllerState`. In `AddGameController` (~line 2175), assign: ```cpp ControllerState.DeviceName = UTF8_TO_TCHAR(SDL_GameControllerName(Controller)); ``` 2. **Wrap controller-event dispatch in a scope.** Around the `MessageHandler->OnControllerButton*/OnControllerAnalog` block (starting ~line 518): ```cpp FInputDeviceScope Scope( nullptr, FName("LinuxApplication"), ControllerState.DeviceId.GetId(), ControllerState.DeviceName); // ... existing dispatch ... ``` Use `"LinuxApplication"` for `InputDeviceName` to mirror the Windows convention. Total: ~12 lines. No third-party dependencies, no engine plugins, no per-device config tables. ## What This Does Not Solve - **Stock XInput on Windows.** If we keep the default `XInputDevice` plugin, events continue to come through `XInputInterface.cpp` with a fixed `"XInputController"` identifier. XInput itself does not expose VID/PID or device strings. Recovering the name there requires correlating the XInput slot with a Raw Input HID via the `IG_xx` token in the Raw Input device path, then calling `HidD_GetProductString` — a separate, more involved patch. GameInput is the cleaner answer on Windows. - **XInput-wrapped pads under GameInput.** A DualSense routed through Steam Input or DS4Windows will surface as the wrapper's display name (e.g. ViGEm), not as the underlying physical device. This is a property of the wrapper, not fixable from our side. - **`displayName` is not a stable identifier.** It is a human-readable name whose exact text varies by OS version, driver, and USB descriptor. Use it for prompt-set selection and logging. Do not use it as a save-game key.