Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/bluez/horipad_steam/horipad_steam.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const std::vector<std::pair<std::string, std::string>> input_match_params_usb =
{"ID_MODEL_ID", "01ab"},
{"TAGS", ":seat:"}};

HoripadSteam::HoripadSteam(sdbus::IConnection& connection)
HoripadSteam::HoripadSteam(sdbus::IConnection& connection, EventLoop& loop)
: ProxyInterfaces(connection,
sdbus::ServiceName(INTERFACE_NAME),
sdbus::ObjectPath("/")),
Expand All @@ -46,15 +46,17 @@ HoripadSteam::HoripadSteam(sdbus::IConnection& connection)
if (std::strcmp(sub_system, "hidraw") == 0) {
if (std::strcmp(action, "remove") == 0) {
if (input_reader_) {
input_reader_->stop();
input_reader_.reset();
// Retire (not reset) so the reader outlives this
// dispatch pass; the loop destroys it safely.
loop_.retire(std::move(input_reader_));
}
}
if (!get_hidraw_devices(input_match_params_bt)) {
get_hidraw_devices(input_match_params_usb);
}
}
}) {
}),
loop_(loop) {
if (!get_hidraw_devices(input_match_params_bt)) {
get_hidraw_devices(input_match_params_usb);
}
Expand Down Expand Up @@ -153,8 +155,14 @@ void HoripadSteam::onInterfacesAdded(
!hidraw_device.empty()) {
LOG_INFO("Adding hidraw device: {}", hidraw_device_key);
if (!input_reader_) {
input_reader_ = std::make_unique<InputReader>(hidraw_device);
input_reader_->start();
auto reader = std::make_unique<InputReader>(hidraw_device);
if (reader->valid()) {
loop_.add(reader.get());
input_reader_ = std::move(reader);
} else {
LOG_ERROR("Failed to initialize hidraw reader: {}",
hidraw_device);
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/bluez/horipad_steam/horipad_steam.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class HoripadSteam final
public Hidraw,
public UdevMonitor {
public:
explicit HoripadSteam(sdbus::IConnection& connection);
HoripadSteam(sdbus::IConnection& connection, EventLoop& loop);

~HoripadSteam() override;

Expand All @@ -53,6 +53,10 @@ class HoripadSteam final
std::map<sdbus::ObjectPath, std::unique_ptr<Input1>> input1_;
std::unique_ptr<InputReader> input_reader_;

// The loop that polls the InputReader source; used to add it on device
// arrival and retire it on removal.
EventLoop& loop_;

void onInterfacesAdded(
const sdbus::ObjectPath& objectPath,
const std::map<sdbus::InterfaceName,
Expand Down
300 changes: 109 additions & 191 deletions src/bluez/horipad_steam/input_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,221 +14,139 @@

#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <thread>

#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <poll.h>
#include <unistd.h>

#include "../../utils/logging.h"
#include "../hidraw.hpp"
#include "input_reader.h"

InputReader::InputReader(std::string device)
: device_(std::move(device)),
stop_flag_(false),
stop_event_fd_(::eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)) {
if (!stop_event_fd_.valid()) {
LOG_ERROR("Failed to create eventfd: {}", strerror(errno));
}
InputReader::InputReader(std::string device) : device_(std::move(device)) {
open_and_init();
}

void InputReader::start() {
LOG_DEBUG("InputReader start: {}", device_);
if (thread_.joinable()) {
return; // already running
}
stop_flag_ = false;
thread_ = std::thread([this] { read_input(); });
}
void InputReader::open_and_init() {
LOG_DEBUG("hidraw device: {}", device_);

void InputReader::stop() {
LOG_DEBUG("InputReader stop: {}", device_);
stop_flag_ = true;
// Wake the blocking epoll_wait so the loop observes stop_flag_ immediately.
if (stop_event_fd_.valid()) {
constexpr std::uint64_t one = 1;
if (::write(stop_event_fd_.get(), &one, sizeof(one)) < 0) {
LOG_ERROR("Failed to signal stop eventfd: {}", strerror(errno));
}
// Non-blocking so dispatch()'s read() never stalls the event loop.
UniqueFd fd(open(device_.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC));
if (!fd.valid()) {
LOG_ERROR("unable to open device");
return;
}
}

InputReader::~InputReader() {
stop();
if (thread_.joinable()) {
thread_.join();
// Raw Info
hidraw_devinfo raw_dev_info{};
if (const auto res = ioctl(fd.get(), HIDIOCGRAWINFO, &raw_dev_info);
res < 0) {
LOG_ERROR("HIDIOCGRAWINFO");
return;
}
}

void InputReader::read_input() {
LOG_DEBUG("hidraw device: {}", device_);

const UniqueFd fd(open(device_.c_str(), O_RDWR));

while (true) {
if (!fd.valid()) {
LOG_ERROR("unable to open device");
break;
}

// Raw Info
hidraw_devinfo raw_dev_info{};
if (const auto res = ioctl(fd.get(), HIDIOCGRAWINFO, &raw_dev_info);
res < 0) {
LOG_ERROR("HIDIOCGRAWINFO");
break;
}
LOG_INFO("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype));
LOG_INFO("Vendor ID: {:04X}", raw_dev_info.vendor);
LOG_INFO("Product ID: {:04X}", raw_dev_info.product);

// Raw Name
std::array<char, 256> buf{};
auto res = ioctl(fd.get(), HIDIOCGRAWNAME(buf.size()), buf.data());
if (res < 0) {
LOG_ERROR("HIDIOCGRAWNAME");
break;
}
buf.back() = '\0'; // guarantee null-termination
LOG_INFO("HID Name: {}", buf.data());
product_ = raw_dev_info.product;
LOG_INFO("bustype: {}", Hidraw::bus_str(raw_dev_info.bustype));
LOG_INFO("Vendor ID: {:04X}", raw_dev_info.vendor);
LOG_INFO("Product ID: {:04X}", raw_dev_info.product);

// Raw Name
std::array<char, 256> buf{};
auto res = ioctl(fd.get(), HIDIOCGRAWNAME(buf.size()), buf.data());
if (res < 0) {
LOG_ERROR("HIDIOCGRAWNAME");
return;
}
buf.back() = '\0'; // guarantee null-termination
LOG_INFO("HID Name: {}", buf.data());

// Raw Physical Location
res = ioctl(fd.get(), HIDIOCGRAWPHYS(buf.size()), buf.data());
if (res < 0) {
LOG_ERROR("HIDIOCGRAWPHYS");
return;
}
buf.back() = '\0'; // guarantee null-termination
LOG_INFO("HID Physical Location: {}", buf.data());

// Report Descriptor Size
int desc_size = 0;
res = ioctl(fd.get(), HIDIOCGRDESCSIZE, &desc_size);
if (res < 0) {
LOG_ERROR("HIDIOCGRDESCSIZE");
return;
}
LOG_INFO("Report Descriptor Size: {}", desc_size);

// Raw Physical Location
res = ioctl(fd.get(), HIDIOCGRAWPHYS(buf.size()), buf.data());
if (res < 0) {
LOG_ERROR("HIDIOCGRAWPHYS");
break;
}
buf.back() = '\0'; // guarantee null-termination
LOG_INFO("HID Physical Location: {}", buf.data());
if (desc_size < 0 ||
static_cast<unsigned>(desc_size) > HID_MAX_DESCRIPTOR_SIZE) {
LOG_ERROR("Invalid report descriptor size: {}", desc_size);
return;
}

// Report Descriptor Size
int desc_size = 0;
res = ioctl(fd.get(), HIDIOCGRDESCSIZE, &desc_size);
if (res < 0) {
LOG_ERROR("HIDIOCGRDESCSIZE");
break;
}
LOG_INFO("Report Descriptor Size: {}", desc_size);
// Report Descriptor
hidraw_report_descriptor rpt_desc{};
rpt_desc.size = desc_size;
res = ioctl(fd.get(), HIDIOCGRDESC, &rpt_desc);
if (res < 0) {
LOG_ERROR("HIDIOCGRDESC");
return;
}

if (desc_size < 0 ||
static_cast<unsigned>(desc_size) > HID_MAX_DESCRIPTOR_SIZE) {
LOG_ERROR("Invalid report descriptor size: {}", desc_size);
break;
}
std::ostringstream os;
os << "Report Descriptor\n";
os << CustomHexdump<400, false>(std::data(rpt_desc.value), rpt_desc.size);
LOG_INFO(os.str());

// Report Descriptor
hidraw_report_descriptor rpt_desc{};
rpt_desc.size = desc_size;
res = ioctl(fd.get(), HIDIOCGRDESC, &rpt_desc);
if (res < 0) {
LOG_ERROR("HIDIOCGRDESC");
break;
}
// Initialisation succeeded: keep the fd so the loop can poll it.
fd_ = std::move(fd);
}

std::ostringstream os;
os << "Report Descriptor\n";
os << CustomHexdump<400, false>(std::data(rpt_desc.value), rpt_desc.size);
LOG_INFO(os.str());
void InputReader::dispatch(const short revents) {
if ((revents & (POLLHUP | POLLERR)) != 0) {
// Device went away; the udev "remove" handler will retire this source.
return;
}

// Wait on both the hidraw fd and the stop eventfd so a blocking read can
// be interrupted immediately when stop() is called from another thread.
const UniqueFd epoll_fd(epoll_create1(EPOLL_CLOEXEC));
if (!epoll_fd.valid()) {
LOG_ERROR("epoll_create1 failed: {}", strerror(errno));
break;
std::array<std::uint8_t, sizeof(inputReport12_t)> buffer{};
const ssize_t result = read(fd_.get(), buffer.data(), buffer.size());
if (result < 0) {
if (errno == EINTR || errno == EAGAIN) {
return;
}
epoll_event ev{};
ev.events = EPOLLIN;
ev.data.fd = fd.get();
if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, fd.get(), &ev) == -1) {
LOG_ERROR("epoll_ctl(hidraw) failed: {}", strerror(errno));
break;
}
if (stop_event_fd_.valid()) {
ev.data.fd = stop_event_fd_.get();
if (epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, stop_event_fd_.get(), &ev) ==
-1) {
LOG_ERROR("epoll_ctl(stop) failed: {}", strerror(errno));
break;
}
}

while (!stop_flag_) {
std::array<epoll_event, 2> poll_events{};
const int nfds = epoll_wait(epoll_fd.get(), poll_events.data(),
poll_events.size(), -1);
if (nfds == -1) {
if (errno == EINTR) {
continue;
}
LOG_ERROR("epoll_wait failed: {}", strerror(errno));
break;
}

bool stop_requested = false;
bool data_ready = false;
for (int i = 0; i < nfds; ++i) {
if (stop_event_fd_.valid() &&
poll_events.at(i).data.fd == stop_event_fd_.get()) {
stop_requested = true;
} else if (poll_events.at(i).data.fd == fd.get()) {
data_ready = true;
}
}
if (stop_requested) {
break;
}
if (!data_ready) {
continue;
}

std::array<std::uint8_t, sizeof(inputReport12_t)> buffer{};
const ssize_t result = read(fd.get(), buffer.data(), buffer.size());
if (result < 0) {
if (errno == EINTR || errno == EAGAIN) {
continue;
}
LOG_ERROR("read failed: {}", strerror(errno));
break;
}
if (result == 0) {
continue;
}

if (raw_dev_info.product == 0x01ab || raw_dev_info.product == 0x0196) {
if (const auto report_id = buffer.at(0); report_id == 7) {
inputReport07_t input_report07{};
std::memcpy(&input_report07, buffer.data(),
std::min(sizeof(inputReport07_t), buffer.size()));
PrintInputReport7(input_report07);
} else if (report_id == 10) {
inputReport10_t input_report10{};
std::memcpy(&input_report10, buffer.data(),
std::min(sizeof(inputReport10_t), buffer.size()));
PrintInputReport10(input_report10);
} else if (report_id == 12) {
inputReport12_t input_report12{};
std::memcpy(&input_report12, buffer.data(),
std::min(sizeof(inputReport12_t), buffer.size()));
PrintInputReport12(input_report12);
} else if (report_id == 14) {
inputReport14_t input_report14{};
std::memcpy(&input_report14, buffer.data(),
std::min(sizeof(inputReport14_t), buffer.size()));
PrintInputReport14(input_report14);
} else {
LOG_ERROR("Unknown report id: {}", report_id);
}
}
LOG_ERROR("read failed: {}", strerror(errno));
return;
}
if (result == 0) {
return;
}
const auto bytes_read = static_cast<size_t>(result);

if (product_ == 0x01ab || product_ == 0x0196) {
if (const auto report_id = buffer.at(0); report_id == 7) {
inputReport07_t input_report07{};
std::memcpy(&input_report07, buffer.data(),
std::min(sizeof(inputReport07_t), bytes_read));
PrintInputReport7(input_report07);
} else if (report_id == 10) {
inputReport10_t input_report10{};
std::memcpy(&input_report10, buffer.data(),
std::min(sizeof(inputReport10_t), bytes_read));
PrintInputReport10(input_report10);
} else if (report_id == 12) {
inputReport12_t input_report12{};
std::memcpy(&input_report12, buffer.data(),
std::min(sizeof(inputReport12_t), bytes_read));
PrintInputReport12(input_report12);
} else if (report_id == 14) {
inputReport14_t input_report14{};
std::memcpy(&input_report14, buffer.data(),
std::min(sizeof(inputReport14_t), bytes_read));
PrintInputReport14(input_report14);
} else {
LOG_ERROR("Unknown report id: {}", report_id);
}
break;
}

// fd is automatically closed by UniqueFd destructor.
}

std::string InputReader::dpad_to_string(const Direction dpad) {
Expand Down
Loading
Loading