1010#include < cerrno>
1111#include < chrono>
1212#include < cmath>
13+ #include < condition_variable>
1314#include < cstddef>
1415#include < cstdint>
1516#include < cstring>
@@ -99,6 +100,7 @@ namespace lvh::detail {
99100#if defined(__linux__)
100101 namespace ps = playstation_feature_reports;
101102 constexpr auto playstation_periodic_report_ms = 10 ;
103+ constexpr auto uhid_start_timeout = std::chrono::seconds {5 };
102104#endif
103105
104106 int system_access (const char *path, int mode) {
@@ -303,6 +305,16 @@ namespace lvh::detail {
303305 }
304306 return to_uhid_bus (profile.bus_type );
305307 }
308+
309+ std::string_view uhid_gamepad_name (const DeviceProfile &profile) {
310+ // Steam's PlayStation HID path expects Sony's native product name. Keep
311+ // consumer branding out of the Linux transport identity while preserving
312+ // the requested descriptor, bus, and report framing.
313+ if (is_playstation_profile (profile.gamepad_kind )) {
314+ return " Wireless Controller" ;
315+ }
316+ return profile.name ;
317+ }
306318#endif
307319
308320 std::uint16_t to_uinput_bus (BusType bus_type) {
@@ -2787,7 +2799,8 @@ namespace lvh::detail {
27872799 }
27882800 physical_id_ = std::format (" libvirtualhid/uhid/{}" , id);
27892801
2790- copy_string (request.name , options.profile .name );
2802+ device_name_ = uhid_gamepad_name (options.profile );
2803+ copy_string (request.name , device_name_);
27912804 copy_string (request.phys , physical_id_);
27922805 copy_string (request.uniq , unique_id_);
27932806 request.rd_size = static_cast <std::uint16_t >(options.profile .report_descriptor .size ());
@@ -2797,20 +2810,31 @@ namespace lvh::detail {
27972810 request.version = options.profile .version ;
27982811 std::memcpy (request.rd_data , options.profile .report_descriptor .data (), options.profile .report_descriptor .size ());
27992812 profile_ = options.profile ;
2800- device_name_ = options.profile .name ;
28012813 {
28022814 std::lock_guard lock {report_mutex_};
28032815 last_report_ = reports::pack_input_report (profile_, {});
28042816 }
28052817
2806- if (const auto status = write_event (event); !status.ok ()) {
2807- return status;
2818+ {
2819+ std::lock_guard lock {lifecycle_mutex_};
2820+ started_ = false ;
2821+ reader_exited_ = false ;
28082822 }
2809-
28102823 running_ = true ;
28112824 reader_ = std::jthread {[this ](std::stop_token stop_token) {
28122825 read_loop (stop_token);
28132826 }};
2827+
2828+ if (const auto status = write_event (event); !status.ok ()) {
2829+ stop_reader ();
2830+ return status;
2831+ }
2832+
2833+ if (const auto status = wait_for_start (); !status.ok ()) {
2834+ stop_reader ();
2835+ return status;
2836+ }
2837+
28142838 if (is_playstation_profile (profile_.gamepad_kind )) {
28152839 periodic_reporter_ = std::jthread {[this ](std::stop_token stop_token) {
28162840 periodic_report_loop (stop_token);
@@ -2914,47 +2938,65 @@ namespace lvh::detail {
29142938 return OperationStatus::success ();
29152939 }
29162940
2917- void read_loop (std::stop_token stop_token) {
2918- while (!stop_token. stop_requested () && running_) {
2919- pollfd descriptor {};
2920- descriptor. fd = fd_;
2921- descriptor. events = POLLIN ;
2941+ enum class ReadEventResult {
2942+ event,
2943+ retry,
2944+ stop,
2945+ } ;
29222946
2923- const auto result = system_poll (&descriptor, 1 , poll_timeout_ms);
2924- if (result < 0 ) {
2925- if (errno == EINTR ) {
2926- continue ;
2927- }
2928- return ;
2929- }
2930- if (result == 0 ) {
2931- continue ;
2932- }
2933- if ((descriptor.revents & (POLLERR | POLLHUP | POLLNVAL )) != 0 ) {
2934- return ;
2935- }
2936- if ((descriptor.revents & POLLIN ) == 0 ) {
2937- continue ;
2938- }
2947+ ReadEventResult read_event (uhid_event &event) const {
2948+ pollfd descriptor {};
2949+ descriptor.fd = fd_;
2950+ descriptor.events = POLLIN ;
29392951
2952+ const auto result = system_poll (&descriptor, 1 , poll_timeout_ms);
2953+ if (result < 0 ) {
2954+ return errno == EINTR ? ReadEventResult::retry : ReadEventResult::stop;
2955+ }
2956+ if ((descriptor.revents & (POLLERR | POLLHUP | POLLNVAL )) != 0 ) {
2957+ return ReadEventResult::stop;
2958+ }
2959+ if (result == 0 || (descriptor.revents & POLLIN ) == 0 ) {
2960+ return ReadEventResult::retry;
2961+ }
2962+
2963+ const auto result_read = system_read (fd_, std::as_writable_bytes (std::span {&event, 1U }));
2964+ if (result_read < 0 ) {
2965+ return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ?
2966+ ReadEventResult::retry :
2967+ ReadEventResult::stop;
2968+ }
2969+ return result_read == 0 ? ReadEventResult::stop : ReadEventResult::event;
2970+ }
2971+
2972+ void read_loop (std::stop_token stop_token) {
2973+ while (!stop_token.stop_requested () && running_) {
29402974 uhid_event event {};
2941- const auto read_result = system_read (fd_, std::as_writable_bytes (std::span {&event, 1U }));
2942- if (read_result < 0 ) {
2943- if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR ) {
2944- continue ;
2945- }
2946- return ;
2975+ const auto result = read_event (event);
2976+ if (result == ReadEventResult::stop) {
2977+ break ;
29472978 }
2948- if (read_result == 0 ) {
2949- return ;
2979+ if (result == ReadEventResult::event ) {
2980+ handle_event (event) ;
29502981 }
2982+ }
29512983
2952- handle_event (event);
2984+ {
2985+ std::lock_guard lock {lifecycle_mutex_};
2986+ reader_exited_ = true ;
29532987 }
2988+ lifecycle_condition_.notify_all ();
29542989 }
29552990
29562991 void handle_event (const uhid_event &event) {
29572992 switch (event.type ) {
2993+ case UHID_START :
2994+ {
2995+ std::lock_guard lock {lifecycle_mutex_};
2996+ started_ = true ;
2997+ }
2998+ lifecycle_condition_.notify_all ();
2999+ break ;
29583000 case UHID_OUTPUT :
29593001 dispatch_output_report (event.u .output .data , event.u .output .size );
29603002 break ;
@@ -2970,6 +3012,25 @@ namespace lvh::detail {
29703012 }
29713013 }
29723014
3015+ void stop_reader () {
3016+ running_ = false ;
3017+ if (reader_.joinable ()) {
3018+ reader_.request_stop ();
3019+ reader_.join ();
3020+ }
3021+ }
3022+
3023+ OperationStatus wait_for_start () {
3024+ if (std::unique_lock lock {lifecycle_mutex_}; !lifecycle_condition_.wait_for (lock, uhid_start_timeout, [this ]() {
3025+ return started_ || reader_exited_;
3026+ })) {
3027+ return OperationStatus::failure (ErrorCode::backend_failure, " timed out waiting for UHID_START" );
3028+ } else if (!started_) {
3029+ return OperationStatus::failure (ErrorCode::backend_failure, " UHID reader stopped before UHID_START" );
3030+ }
3031+ return OperationStatus::success ();
3032+ }
3033+
29733034 void periodic_report_loop (std::stop_token stop_token) {
29743035 while (!stop_token.stop_requested () && running_) {
29753036 std::this_thread::sleep_for (std::chrono::milliseconds {playstation_periodic_report_ms});
@@ -3113,6 +3174,10 @@ namespace lvh::detail {
31133174 std::atomic_bool running_ = false ;
31143175 std::jthread reader_;
31153176 std::jthread periodic_reporter_;
3177+ std::mutex lifecycle_mutex_;
3178+ std::condition_variable lifecycle_condition_;
3179+ bool started_ = false ;
3180+ bool reader_exited_ = false ;
31163181 std::mutex write_mutex_;
31173182 std::mutex report_mutex_;
31183183 std::mutex callback_mutex_;
@@ -3185,7 +3250,7 @@ namespace lvh::detail {
31853250 }
31863251
31873252#if defined(__linux__)
3188- const auto fd = system_open (uhid_path, O_RDWR | O_CLOEXEC );
3253+ const auto fd = system_open (uhid_path, O_RDWR | O_CLOEXEC | O_NONBLOCK );
31893254 if (fd < 0 ) {
31903255 return {system_error_status (ErrorCode::backend_unavailable, " failed to open /dev/uhid" , errno), nullptr };
31913256 }
0 commit comments