5.1 KiB
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:
-
Cache at connect. Add
FString CachedDisplayNametoFGameInputDeviceContainer. In its constructor / device-info init path (GameInputDeviceContainer.cpp:44, whereInfois in scope), set:CachedDisplayName = FString(Info->displayName);TCHAR == wchar_ton Windows, so the conversion is direct. Lifetime matches the container — cleared automatically on disconnect. -
Make the container reachable from
FGameInputEventParams. The processor already gets aDevicepointer; add aContainerpointer soGetHardwareDeviceIdentifierNamecan read the cached string. -
Return it from
GetHardwareDeviceIdentifierName(GameInputDeviceProcessor.cpp:61). After the existingbOverrideHardwareDeviceIdStringblock, before the family-bucket switch: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:
-
Store the name. Add
FString DeviceNametoSDLControllerState. InAddGameController(~line 2175), assign:ControllerState.DeviceName = UTF8_TO_TCHAR(SDL_GameControllerName(Controller)); -
Wrap controller-event dispatch in a scope. Around the
MessageHandler->OnControllerButton*/OnControllerAnalogblock (starting ~line 518):FInputDeviceScope Scope( nullptr, FName("LinuxApplication"), ControllerState.DeviceId.GetId(), ControllerState.DeviceName); // ... existing dispatch ...Use
"LinuxApplication"forInputDeviceNameto 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
XInputDeviceplugin, events continue to come throughXInputInterface.cppwith 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 theIG_xxtoken in the Raw Input device path, then callingHidD_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.
-
displayNameis 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.