Skip to content

Commit d1bc01f

Browse files
fix(steam-deck): harden native discovery timing
1 parent 78b26b1 commit d1bc01f

8 files changed

Lines changed: 35 additions & 19 deletions

File tree

docs/platform-support.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ Game Pad while retaining the Valve-native report bytes, so Windows Game
8282
Controllers and generic HID clients can enumerate it even if the Valve-specific
8383
path is unavailable. SDL/HIDAPI can recognize it as a Steam Deck, disable its
8484
desktop mappings, and send native rumble without exposing Valve protocol details
85-
through the public C++ API. A neutral state is submitted continuously from the
86-
moment the backend is created, and each packet carries an advancing native
85+
through the public C++ API. The driver seeds a neutral report before exposing the
86+
device, the backend submits another synchronously during creation, and periodic
87+
updates continue afterward. Each backend packet carries an advancing native
8788
sequence number.
8889

8990
See [Windows driver package](windows-driver.md) for build, install, validation,
@@ -149,14 +150,14 @@ provides it separately on the UHID event.
149150

150151
Steam Deck retains Valve's native identity and emits the 64-byte Deck state
151152
packet periodically so SDL's direct HIDAPI path can initialize before the first
152-
client input arrives. Its UHID endpoint uses the virtual bus, preventing the
153-
hardware-specific `hid-steam` driver from suppressing input behind the physical
154-
Deck's lizard-mode gate; the Generic Desktop/Game Pad descriptor remains
155-
available as an evdev fallback. The backend answers the unit-serial feature
156-
query used during Linux registration, accepts the desktop-mapping/settings
157-
commands used by SDL, and forwards native `0xEB` rumble requests through the
158-
portable output callback. Each submitted native packet carries an advancing
159-
sequence number.
153+
client input arrives. Its UHID endpoint uses the Bluetooth HID transport tag:
154+
this remains visible to HIDAPI while avoiding the USB-only `hid-steam` match
155+
that otherwise suppresses the virtual endpoint's reports when a direct HID
156+
client opens it. The Generic Desktop/Game Pad descriptor remains available as
157+
an evdev fallback. The backend answers the unit-serial feature query used during
158+
Linux registration, accepts the desktop-mapping/settings commands used by SDL,
159+
and forwards native `0xEB` rumble requests through the portable output callback.
160+
Each submitted native packet carries an advancing sequence number.
160161

161162
The backend opens `/dev/uhid` in nonblocking mode, matching the original
162163
asynchronous gamepad registration path. Its event reader is active before

src/platform/linux/uhid_backend.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,17 @@ namespace lvh::detail {
319319

320320
#if defined(__linux__)
321321
std::uint16_t to_uhid_bus(const DeviceProfile &profile) {
322-
if (
323-
profile.gamepad_kind == GamepadProfileKind::switch_pro ||
324-
profile.gamepad_kind == GamepadProfileKind::steam_deck
325-
) {
326-
// Prevent hardware-specific kernel drivers from taking over these
327-
// virtual endpoints. In particular, hid-steam suppresses Steam Deck
328-
// gamepad input while its hardware-only lizard-mode gate is active.
322+
if (profile.gamepad_kind == GamepadProfileKind::switch_pro) {
323+
// Prevent hardware-specific kernel drivers from taking over this
324+
// virtual endpoint.
329325
return BUS_VIRTUAL;
330326
}
327+
if (profile.gamepad_kind == GamepadProfileKind::steam_deck) {
328+
// hid-steam matches the Deck's USB identity and suppresses its input
329+
// while a hidraw client is open. BUS_VIRTUAL avoids that driver but is
330+
// filtered out by hidapi, so use its other supported HID transport.
331+
return BUS_BLUETOOTH;
332+
}
331333
return to_uhid_bus(profile.bus_type);
332334
}
333335

src/platform/windows/windows_backend.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,16 @@ namespace lvh::detail {
10481048
response.device_path[0] == '\0' ? command_channel_->path() : std::string {response.device_path.data()}
10491049
);
10501050

1051+
if (state->profile.gamepad_kind == GamepadProfileKind::steam_deck) {
1052+
static_cast<void>(
1053+
stamp_steam_deck_packet_number(state->last_input_report, ++state->steam_deck_packet_number)
1054+
);
1055+
if (const auto status = submit_gamepad_report(state, state->last_input_report); !status.ok()) {
1056+
static_cast<void>(command_channel_->destroy_device(state->driver_id, state->token));
1057+
return {status, nullptr};
1058+
}
1059+
}
1060+
10511061
{
10521062
std::lock_guard lock {devices_mutex_};
10531063
gamepads_[state->driver_id] = state;

tests/fixtures/include/fixtures/windows_backend_test_hooks.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace lvh::detail::test {
5555
OperationStatus create_status;
5656
OperationStatus submit_status;
5757
OperationStatus close_status;
58+
std::size_t reports_immediately_after_create = 0;
5859
std::size_t reports_before_submit = 0;
5960
std::size_t reports_before_close = 0;
6061
std::size_t reports_after_close = 0;

tests/fixtures/linux_backend_test_hooks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1695,7 +1695,7 @@ namespace lvh::detail::test {
16951695
options.metadata.stable_id = "linux-steam-deck";
16961696

16971697
UhidGamepad gamepad {descriptors[0]};
1698-
auto event = create_started_profile_uhid_gamepad(gamepad, 11, options, descriptors[1], BUS_VIRTUAL, result);
1698+
auto event = create_started_profile_uhid_gamepad(gamepad, 11, options, descriptors[1], BUS_BLUETOOTH, result);
16991699
gamepad.set_output_callback([&result](const GamepadOutput &output) {
17001700
if (output.kind == GamepadOutputKind::rumble) {
17011701
++result.output.callback_count;

tests/fixtures/windows_backend_test_hooks.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ namespace lvh::detail {
512512
if (!created) {
513513
return result;
514514
}
515+
result.reports_immediately_after_create = command_state->submit_report_count();
515516

516517
static_cast<void>(wait_until([&command_state] {
517518
return command_state->submit_report_count() >= 2U;

tests/unit/test_linux_backend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ TEST_F(LinuxBackendTest, TranslatesMouseButtonsAndBusTypes) {
124124
EXPECT_EQ(lvh::detail::test::linux_uhid_bus(lvh::BusType::bluetooth), BUS_BLUETOOTH);
125125
EXPECT_EQ(lvh::detail::test::linux_gamepad_uhid_bus(lvh::GamepadProfileKind::xbox_series), BUS_USB);
126126
EXPECT_EQ(lvh::detail::test::linux_gamepad_uhid_bus(lvh::GamepadProfileKind::switch_pro), BUS_VIRTUAL);
127-
EXPECT_EQ(lvh::detail::test::linux_gamepad_uhid_bus(lvh::GamepadProfileKind::steam_deck), BUS_VIRTUAL);
127+
EXPECT_EQ(lvh::detail::test::linux_gamepad_uhid_bus(lvh::GamepadProfileKind::steam_deck), BUS_BLUETOOTH);
128128
EXPECT_EQ(lvh::detail::test::linux_uinput_bus(lvh::BusType::bluetooth), BUS_BLUETOOTH);
129129

130130
EXPECT_EQ(lvh::detail::test::linux_pen_tool(lvh::PenToolType::pen), BTN_TOOL_PEN);

tests/unit/test_windows_backend.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ TEST_F(WindowsBackendTest, SteamDeckRepeatsNativeStateUntilClose) {
100100
ASSERT_TRUE(result.create_status.ok()) << result.create_status.message();
101101
ASSERT_TRUE(result.submit_status.ok()) << result.submit_status.message();
102102
ASSERT_TRUE(result.close_status.ok()) << result.close_status.message();
103+
EXPECT_GE(result.reports_immediately_after_create, 1U);
103104
EXPECT_GE(result.reports_before_submit, 2U);
104105
EXPECT_GT(result.reports_before_close, result.reports_before_submit);
105106
EXPECT_TRUE(result.packet_numbers_advance);

0 commit comments

Comments
 (0)