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
53 changes: 53 additions & 0 deletions src/bluez/hid_error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2026 Joel Winarske
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef SRC_BLUEZ_HID_ERROR_H
#define SRC_BLUEZ_HID_ERROR_H

#include <string>
#include <system_error>

/// Domain error category for HID feature-report handling. Failures that are not
/// errno (a well-formed ioctl that returns a response failing a protocol check)
/// carry a std::error_code in this category, so they compose with the errno
/// codes from sys:: through the same std::expected<T, std::error_code> channel.
namespace hid {

enum class Error {
kInvalidResponse = 1, // ioctl succeeded but the report failed validation
};

inline const std::error_category& error_category() noexcept {
static const struct Category final : std::error_category {
[[nodiscard]] const char* name() const noexcept override { return "hid"; }
[[nodiscard]] std::string message(const int condition) const override {
if (static_cast<Error>(condition) == Error::kInvalidResponse) {
return "invalid device response";
}
return "unknown hid error";
}
} category;
return category;
}

inline std::error_code make_error_code(const Error e) noexcept {
return {static_cast<int>(e), error_category()};
}

} // namespace hid

template <>
struct std::is_error_code_enum<hid::Error> : std::true_type {};

#endif // SRC_BLUEZ_HID_ERROR_H
55 changes: 30 additions & 25 deletions src/bluez/ps5_dual_sense/input_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

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

Expand Down Expand Up @@ -98,11 +99,19 @@ void InputReader::open_and_init() {
os << CustomHexdump<400, false>(std::data(rpt_desc.value), rpt_desc.size);
LOG_INFO(os.str());

// Get Features
GetControllerCalibrationData(fd.get(),
hw_cal_data_); // enables extended report for BT
GetControllerMacAll(fd.get(), controller_and_host_mac_);
GetControllerVersion(fd.get(), version_);
// Feature reports are best-effort: the controller still streams input if they
// fail, so log and continue. GetControllerCalibrationData also enables the
// extended BT report as a side effect.
if (auto r = GetControllerCalibrationData(fd.get(), hw_cal_data_); !r) {
LOG_WARN("controller calibration data unavailable: {}",
r.error().message());
}
if (auto r = GetControllerMacAll(fd.get(), controller_and_host_mac_); !r) {
LOG_WARN("controller MAC unavailable: {}", r.error().message());
}
if (auto r = GetControllerVersion(fd.get(), version_); !r) {
LOG_WARN("controller version unavailable: {}", r.error().message());
}

// Initialisation succeeded: keep the fd so the loop can poll it.
fd_ = std::move(fd);
Expand Down Expand Up @@ -154,40 +163,38 @@ void InputReader::dispatch(const short revents) {
}
}

int InputReader::GetControllerMacAll(const int fd,
ReportFeatureInMacAll& mac_all) {
std::expected<void, std::error_code> InputReader::GetControllerMacAll(
const int fd,
ReportFeatureInMacAll& mac_all) {
mac_all.ReportID = 0x09;
if (auto r = sys::ioctl(fd, HIDIOCGFEATURE(sizeof(ReportFeatureInMacAll)),
&mac_all);
!r) {
LOG_ERROR("GetControllerMacAll failed: {}", r.error().message());
return 1;
return std::unexpected(r.error());
}
if (mac_all.ReportID != 0x09 || mac_all.Hard08 != 0x08 ||
mac_all.Hard25 != 0x25 || mac_all.Hard00 != 0x00) {
LOG_ERROR("GetControllerMacAll invalid response");
return 1;
return std::unexpected(hid::make_error_code(hid::Error::kInvalidResponse));
}
return 0;
return {};
}

int InputReader::GetControllerVersion(const int fd,
ReportFeatureInVersion& version) {
std::expected<void, std::error_code> InputReader::GetControllerVersion(
const int fd,
ReportFeatureInVersion& version) {
version.Data.ReportID = 0x20;
if (auto r = sys::ioctl(fd, HIDIOCGFEATURE(sizeof(ReportFeatureInVersion)),
&version);
!r) {
LOG_ERROR("GetControllerVersion failed: {}", r.error().message());
return 1;
return std::unexpected(r.error());
}
if (version.Data.ReportID != 0x20) {
LOG_ERROR("GetControllerVersion invalid response");
return 1;
return std::unexpected(hid::make_error_code(hid::Error::kInvalidResponse));
}
return 0;
return {};
}

int InputReader::GetControllerCalibrationData(
std::expected<void, std::error_code> InputReader::GetControllerCalibrationData(
const int fd,
HardwareCalibrationData& hw_cal_data) {
static constexpr auto ACC_RES_PER_G = 8192;
Expand All @@ -200,12 +207,10 @@ int InputReader::GetControllerCalibrationData(
if (auto r = sys::ioctl(
fd, HIDIOCGFEATURE(sizeof(ReportFeatureCalibrationData)), &cal_data);
!r) {
LOG_ERROR("GetControllerCalibrationData failed: {}", r.error().message());
return 1;
return std::unexpected(r.error());
}
if (cal_data.Data.ReportID != 0x05) {
LOG_ERROR("GetControllerCalibrationData invalid response");
return 1;
return std::unexpected(hid::make_error_code(hid::Error::kInvalidResponse));
}

// Set gyroscope calibration and normalization parameters.
Expand Down Expand Up @@ -285,7 +290,7 @@ int InputReader::GetControllerCalibrationData(
i++;
}

return 0;
return {};
}

std::string InputReader::dpad_to_string(const Direction dpad) {
Expand Down
13 changes: 9 additions & 4 deletions src/bluez/ps5_dual_sense/input_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

#include <array>
#include <cstdint>
#include <expected>
#include <string>
#include <system_error>

#include "../../utils/event_loop.h"
#include "../../utils/unique_fd.h"
Expand Down Expand Up @@ -68,12 +70,15 @@ class InputReader final : public EventSource {
static std::string light_brightness_to_string(LightBrightness state);
static std::string light_fade_animation_to_string(LightFadeAnimation state);

static int GetControllerMacAll(int fd, ReportFeatureInMacAll& mac_all);
[[nodiscard]] static std::expected<void, std::error_code> GetControllerMacAll(
int fd,
ReportFeatureInMacAll& mac_all);

static int GetControllerVersion(int fd, ReportFeatureInVersion& version);
[[nodiscard]] static std::expected<void, std::error_code>
GetControllerVersion(int fd, ReportFeatureInVersion& version);

static int GetControllerCalibrationData(int fd,
HardwareCalibrationData& hw_cal_data);
[[nodiscard]] static std::expected<void, std::error_code>
GetControllerCalibrationData(int fd, HardwareCalibrationData& hw_cal_data);

static void PrintCalibrationData(HardwareCalibrationData const& hw_cal_data);
static void PrintControllerMacAll(
Expand Down
Loading