Skip to content

Commit ba360a5

Browse files
Restore nonblocking Linux UHID registration
1 parent 8dbde99 commit ba360a5

5 files changed

Lines changed: 41 additions & 6 deletions

File tree

docs/platform-support.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ and control channels. Numbered control-channel output is normalized before
138138
parsing, whether the kernel includes the report number in the payload or
139139
provides it separately on the UHID event.
140140

141-
The UHID event reader is active before device registration begins, and gamepad
142-
creation does not report success until the kernel returns `UHID_START`. This
143-
keeps control-channel initialization available throughout registration and
144-
prevents streaming hosts from publishing a controller before its kernel HID
145-
device has started.
141+
The backend opens `/dev/uhid` in nonblocking mode, matching the original
142+
asynchronous gamepad registration path. Its event reader is active before
143+
device registration begins, and creation does not report success until the
144+
kernel returns `UHID_START`. This keeps control-channel initialization
145+
available throughout registration and prevents streaming hosts from publishing
146+
a controller before its kernel HID device has started.
146147

147148
On Linux, DualShock 4 and DualSense emit Sony's native `Wireless Controller`
148149
product name for Steam HID discovery. The requested USB or Bluetooth bus,

src/platform/linux/uhid_backend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3245,7 +3245,7 @@ namespace lvh::detail {
32453245
}
32463246

32473247
#if defined(__linux__)
3248-
const auto fd = system_open(uhid_path, O_RDWR | O_CLOEXEC);
3248+
const auto fd = system_open(uhid_path, O_RDWR | O_CLOEXEC | O_NONBLOCK);
32493249
if (fd < 0) {
32503250
return {system_error_status(ErrorCode::backend_unavailable, "failed to open /dev/uhid", errno), nullptr};
32513251
}

tests/fixtures/include/fixtures/linux_backend_test_hooks.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,13 @@ namespace lvh::detail::test {
845845
*/
846846
OperationStatus linux_backend_gamepad_fake_open_failure();
847847

848+
/**
849+
* @brief Capture the flags used to open UHID for a descriptor-driven gamepad.
850+
*
851+
* @return Flags passed to `open()` for `/dev/uhid`.
852+
*/
853+
int linux_backend_gamepad_open_flags();
854+
848855
/**
849856
* @brief Try creating a Linux backend gamepad while fake UHID creation fails.
850857
*

tests/fixtures/linux_backend_test_hooks.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ namespace lvh::detail::test {
120120
int access_result = 0;
121121
bool override_open = false;
122122
int open_result = 100000;
123+
int last_open_flags = 0;
123124
bool override_write = false;
124125
std::atomic_int write_call_count = 0;
125126
int fail_write_call = -1;
@@ -207,6 +208,9 @@ int lvh_linux_test_access(const char *path, int mode) {
207208
}
208209

209210
int lvh_linux_test_open(const char *path, int flags) {
211+
if (lvh::detail::test::active_test_syscalls() != nullptr) {
212+
lvh::detail::test::active_test_syscalls()->last_open_flags = flags;
213+
}
210214
if (lvh::detail::test::active_test_syscalls() != nullptr && lvh::detail::test::active_test_syscalls()->override_open) {
211215
if (lvh::detail::test::active_test_syscalls()->open_result < 0) {
212216
errno = ENOENT;
@@ -1975,6 +1979,21 @@ namespace lvh::detail::test {
19751979
return backend.create_gamepad(1, options).status;
19761980
}
19771981

1982+
int linux_backend_gamepad_open_flags() {
1983+
LinuxTestSyscalls syscalls;
1984+
syscalls.override_access = true;
1985+
syscalls.override_open = true;
1986+
syscalls.open_result = -1;
1987+
ScopedLinuxTestSyscalls scoped_syscalls {syscalls};
1988+
1989+
LinuxUhidBackend backend;
1990+
1991+
CreateGamepadOptions options;
1992+
options.profile = profiles::dualshock4_usb();
1993+
static_cast<void>(backend.create_gamepad(1, options));
1994+
return syscalls.last_open_flags;
1995+
}
1996+
19781997
OperationStatus linux_backend_gamepad_fake_create_failure() {
19791998
LinuxTestSyscalls syscalls;
19801999
enable_fake_device_syscalls(syscalls);

tests/unit/test_linux_backend.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <vector>
1616

1717
// platform includes
18+
#include <fcntl.h>
1819
#include <linux/input.h>
1920
#if defined(LIBVIRTUALHID_HAVE_XTEST)
2021
#include <X11/keysym.h>
@@ -908,6 +909,13 @@ TEST_F(LinuxBackendTest, FakeLinuxBackendCreatesAllDeviceTypes) {
908909
EXPECT_TRUE(result.pen_tablet_close_status.ok()) << result.pen_tablet_close_status.message();
909910
}
910911

912+
TEST_F(LinuxBackendTest, OpensUhidGamepadsNonblocking) {
913+
const auto flags = lvh::detail::test::linux_backend_gamepad_open_flags();
914+
EXPECT_EQ(flags & O_ACCMODE, O_RDWR);
915+
EXPECT_NE(flags & O_CLOEXEC, 0);
916+
EXPECT_NE(flags & O_NONBLOCK, 0);
917+
}
918+
911919
TEST_F(LinuxBackendTest, FakeUhidSyscallsCoverFailureBranches) {
912920
EXPECT_EQ(lvh::detail::test::linux_uhid_submit_fake_write_failure().code(), lvh::ErrorCode::backend_failure);
913921
EXPECT_EQ(lvh::detail::test::linux_uhid_submit_fake_short_write().code(), lvh::ErrorCode::backend_failure);

0 commit comments

Comments
 (0)