From eb93c26c6ce457e2b692753f5f01943139b40612 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Tue, 20 Jun 2023 15:28:23 +0200 Subject: [PATCH] all: Modularize HID code even further * Move IPTS interface logic to ipts namespace * core::linux only contains the linux specific hidraw code * Report entries in the HID descriptor get deduplicated --- src/apps/check-device/main.cpp | 24 ++- src/core/linux/device-runner.hpp | 27 +++- src/core/linux/hidraw-device.hpp | 248 +++++-------------------------- src/hid/descriptor.hpp | 92 ------------ src/hid/device.hpp | 32 ++++ src/hid/parser.hpp | 45 +++++- src/hid/report.hpp | 63 +++++++- src/hid/state.hpp | 9 +- src/hid/usage.hpp | 28 ++++ src/ipts/descriptor.hpp | 140 +++++++++++++++++ src/ipts/device.hpp | 140 +++++++++++++++++ 11 files changed, 510 insertions(+), 338 deletions(-) delete mode 100644 src/hid/descriptor.hpp create mode 100644 src/hid/device.hpp create mode 100644 src/ipts/descriptor.hpp create mode 100644 src/ipts/device.hpp diff --git a/src/apps/check-device/main.cpp b/src/apps/check-device/main.cpp index 825986cd..dddbb4bf 100644 --- a/src/apps/check-device/main.cpp +++ b/src/apps/check-device/main.cpp @@ -5,7 +5,10 @@ #include #include #include +#include #include +#include +#include #include #include @@ -14,6 +17,7 @@ #include #include #include +#include #include #include @@ -39,21 +43,29 @@ int run(const int argc, const char **argv) spdlog::set_level(spdlog::level::off); // Open the device - const core::linux::HidrawDevice device {path}; + const auto device = std::make_shared(path); - const core::DeviceInfo info = device.info(); - const std::optional metadata = device.get_metadata(); + core::DeviceInfo info {}; + info.vendor = device->vendor(); + info.product = device->product(); - spdlog::info("Opened device {:04X}:{:04X}", info.vendor, info.product); + // Create IPTS interface + const ipts::Device ipts {device}; + const ipts::Descriptor &descriptor = ipts.descriptor(); + const std::optional metadata = ipts.metadata(); + + info.buffer_size = ipts.buffer_size(); + + spdlog::info("Opened device {:04X}:{:04X}", device->vendor(), device->product()); // Check if the device can switch modes - if (!device.has_set_mode()) { + if (!descriptor.find_modesetting_report().has_value()) { spdlog::error("{} is not an IPTS device!", path.string()); return EXIT_FAILURE; } // Check if the device can send touch data. - if (!device.has_touch_data()) { + if (descriptor.find_touch_data_reports().empty()) { spdlog::error("{} is not an IPTS device!", path.string()); return EXIT_FAILURE; } diff --git a/src/core/linux/device-runner.hpp b/src/core/linux/device-runner.hpp index cc2b67df..0acd6842 100644 --- a/src/core/linux/device-runner.hpp +++ b/src/core/linux/device-runner.hpp @@ -10,11 +10,13 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -28,7 +30,10 @@ class DeviceRunner { private: // The hidraw device serving as the source of data. - HidrawDevice m_device; + std::shared_ptr m_device; + + // The IPTS touchscreen interface + ipts::Device m_ipts; // Whether the loop for reading from the device should stop. std::atomic_bool m_should_stop = false; @@ -45,10 +50,16 @@ class DeviceRunner { public: template - DeviceRunner(const std::filesystem::path &path, Args... args) : m_device {path} + DeviceRunner(const std::filesystem::path &path, Args... args) + : m_device {std::make_shared(path)} + , m_ipts {m_device} { - const DeviceInfo info = m_device.info(); - const std::optional meta = m_device.get_metadata(); + DeviceInfo info {}; + info.vendor = m_device->vendor(); + info.product = m_device->product(); + info.buffer_size = m_ipts.buffer_size(); + + const std::optional meta = m_ipts.metadata(); const ConfigLoader loader {info, meta}; m_application.emplace(loader.config(), info, meta, args...); @@ -94,7 +105,7 @@ class DeviceRunner { throw std::runtime_error("Init error: Application is null"); // Enable multitouch mode - m_device.set_mode(true); + m_ipts.set_mode(ipts::Mode::Multitouch); // Signal the application that the data flow has started. m_application->on_start(); @@ -108,10 +119,10 @@ class DeviceRunner { } try { - const isize size = m_device.read(m_buffer); + const isize size = m_device->read(m_buffer); // Does this report contain touch data? - if (!m_device.is_touch_data(m_buffer[0])) + if (!m_ipts.is_touch_data(m_buffer)) continue; m_application->process( @@ -137,7 +148,7 @@ class DeviceRunner { try { // Disable multitouch mode - m_device.set_mode(false); + m_ipts.set_mode(ipts::Mode::Singletouch); } catch (std::exception &e) { spdlog::error(e.what()); } diff --git a/src/core/linux/hidraw-device.hpp b/src/core/linux/hidraw-device.hpp index 030a360c..18c2aac7 100644 --- a/src/core/linux/hidraw-device.hpp +++ b/src/core/linux/hidraw-device.hpp @@ -7,16 +7,13 @@ #include #include -#include -#include +#include #include #include -#include #include #include #include -#include #include @@ -28,13 +25,14 @@ namespace iptsd::core::linux { -class HidrawDevice { +class HidrawDevice : public hid::Device { private: int m_fd = -1; + struct hidraw_devinfo m_devinfo {}; + struct hidraw_report_descriptor m_desc {}; - // The HID descriptor of the device. - hid::Descriptor m_desc {}; + std::vector m_reports {}; public: HidrawDevice(const std::filesystem::path &path) : m_fd {syscalls::open(path, O_RDWR)} @@ -44,43 +42,51 @@ class HidrawDevice { syscalls::ioctl(m_fd, HIDIOCGRAWINFO, &m_devinfo); syscalls::ioctl(m_fd, HIDIOCGRDESCSIZE, &desc_size); - struct hidraw_report_descriptor hidraw_desc {}; - hidraw_desc.size = desc_size; + m_desc.size = desc_size; + syscalls::ioctl(m_fd, HIDIOCGRDESC, &m_desc); - syscalls::ioctl(m_fd, HIDIOCGRDESC, &hidraw_desc); + hid::parse(gsl::span {&m_desc.value[0], desc_size}, m_reports); + } - m_desc = hid::parse(gsl::span {&hidraw_desc.value[0], desc_size}); + ~HidrawDevice() override + { + try { + syscalls::close(m_fd); + } catch (std::exception &) { + // ignored + } } /*! - * Returns information about the device (vendor, product and buffer size). - * - * @return An object describing the device. + * The vendor ID of the device. */ - [[nodiscard]] DeviceInfo info() const + u16 vendor() override { - DeviceInfo info {}; - /* * The value is just an ID stored in a signed value. * A negative device ID doesn't make sense, so cast it away. */ - info.vendor = gsl::narrow_cast(m_devinfo.vendor); - info.product = gsl::narrow_cast(m_devinfo.product); - - info.buffer_size = casts::to(this->buffer_size()); + return gsl::narrow_cast(m_devinfo.vendor); + } - return info; + /*! + * The product ID of the device. + */ + u16 product() override + { + /* + * The value is just an ID stored in a signed value. + * A negative device ID doesn't make sense, so cast it away. + */ + return gsl::narrow_cast(m_devinfo.product); } /*! * The HID descriptor of the device. - * - * @return A reference to the wrapped HID descriptor of the device. */ - [[nodiscard]] const hid::Descriptor &descriptor() const + const std::vector &descriptor() override { - return m_desc; + return m_reports; } /*! @@ -89,128 +95,17 @@ class HidrawDevice { * @param[in] buffer The target storage for the report. * @return The size of the report that was read in bytes. */ - [[nodiscard]] isize read(gsl::span buffer) const + isize read(gsl::span buffer) override { return syscalls::read(m_fd, buffer); } - /*! - * Changes the mode of the IPTS device. - * - * If the device is already in the requested mode, nothing will happen. - * - * @param[in] multitouch Whether multitouch mode should be enabled. - */ - void set_mode(const bool multitouch) const - { - std::array report { - this->get_set_mode().value_or(0), - multitouch ? casts::to(0x1) : casts::to(0x0), - }; - - this->set_feature(report); - } - - /*! - * Checks whether a HID report matches the properties for an IPTS touch data report. - * - * @param[in] report The ID of the HID report to check. - * @return Whether the given report contains touchscreen data. - */ - [[nodiscard]] bool is_touch_data(const std::optional report) const - { - const hid::Descriptor &desc = this->descriptor(); - const std::vector usage = desc.usage(report); - - if (usage.size() != 2) - return false; - - return usage[0].page == IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER && - usage[0].value == IPTS_HID_REPORT_USAGE_SCAN_TIME && - usage[1].page == IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER && - usage[1].value == IPTS_HID_REPORT_USAGE_GESTURE_DATA; - } - - /*! - * Checks if a feature report is responsible for modesetting. - * - * @return Whether the device has a modesetting report. - */ - [[nodiscard]] bool has_set_mode() const - { - const hid::Descriptor &desc = this->descriptor(); - const std::set> reports = desc.reports(hid::ReportType::Feature); - - return std::any_of( - reports.cbegin(), reports.cend(), - [&](const std::optional report) { return this->is_set_mode(report); }); - } - - /*! - * Checks if an input report contains touch data. - * - * @return Whether the device has a report containing touch data. - */ - [[nodiscard]] bool has_touch_data() const - { - const hid::Descriptor &desc = this->descriptor(); - const std::set> reports = desc.reports(hid::ReportType::Input); - - return std::any_of(reports.cbegin(), reports.cend(), - [&](const std::optional report) { - return this->is_touch_data(report); - }); - } - - /*! - * Reads the IPTS device metadata from the metadata feature report. - * - * @return The metadata of the current device, or null if the report is not supported. - */ - [[nodiscard]] std::optional get_metadata() const - { - std::optional metadata = std::nullopt; - const hid::Descriptor &desc = this->descriptor(); - - const std::optional id = this->get_metadata_report_id(); - if (!id.has_value()) - return std::nullopt; - - std::vector report(desc.size(id) + 1); - report.at(0) = id.value(); - - this->get_feature(report); - - ipts::Parser parser; - parser.on_metadata = [&](const ipts::Metadata &m) { metadata = m; }; - parser.parse(report); - - return metadata; - } - -private: - /*! - * Determines the required size for a buffer holding IPTS touch data. - * - * @return The size of the largest touch data report that IPTS can send. - */ - [[nodiscard]] usize buffer_size() const - { - usize size = 0; - const hid::Descriptor &desc = this->descriptor(); - - for (const std::optional report : desc.reports(hid::ReportType::Input)) - size = std::max(size, desc.size(report)); - - return size; - } - /*! * Gets the data of a HID feature report. * * @param[in] report The report ID to get, followed by enough space to fit the data. */ - void get_feature(gsl::span report) const + void get_feature(gsl::span report) override { syscalls::ioctl(m_fd, HIDIOCGFEATURE(report.size()), report.data()); } @@ -220,83 +115,10 @@ class HidrawDevice { * * @param[in] report The report ID to set, followed by the new data. */ - void set_feature(const gsl::span report) const + void set_feature(const gsl::span report) override { syscalls::ioctl(m_fd, HIDIOCSFEATURE(report.size()), report.data()); } - - /*! - * Checks whether a HID report matches the properties for the modesetting report. - * - * @param[in] report The ID of the HID report to check. - * @return Whether the given report is a modesetting report. - */ - [[nodiscard]] bool is_set_mode(const std::optional report) const - { - const hid::Descriptor &desc = this->descriptor(); - const std::vector usage = desc.usage(report); - - if (usage.size() != 1) - return false; - - if (desc.size(report) != 1) - return false; - - return usage[0].page == IPTS_HID_REPORT_USAGE_PAGE_VENDOR && - usage[0].value == IPTS_HID_REPORT_USAGE_SET_MODE; - } - - /*! - * Tries to find the report for modesetting in the HID descriptor. - * - * @return The report ID of the report for modesetting if it exists, 0 otherwise. - */ - [[nodiscard]] std::optional get_set_mode() const - { - const hid::Descriptor &desc = this->descriptor(); - - for (const std::optional report : desc.reports(hid::ReportType::Feature)) { - if (this->is_set_mode(report)) - return report; - } - - return std::nullopt; - } - - /*! - * Checks whether a HID report matches the properties of the metadata report. - * - * @param[in] report The ID of the HID report to check. - * @return Whether the given report is a metadata report. - */ - [[nodiscard]] bool is_metadata_report(const std::optional report) const - { - const hid::Descriptor &desc = this->descriptor(); - const std::vector usage = desc.usage(report); - - if (usage.size() != 1) - return false; - - return usage[0].page == IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER && - usage[0].value == IPTS_HID_REPORT_USAGE_METADATA; - } - - /*! - * Tries to find the metadata report in the HID descriptor. - * - * @return The report ID of the metadata report if it exists, 0 otherwise. - */ - [[nodiscard]] std::optional get_metadata_report_id() const - { - const hid::Descriptor &desc = this->descriptor(); - - for (const std::optional report : desc.reports(hid::ReportType::Feature)) { - if (this->is_metadata_report(report)) - return report; - } - - return std::nullopt; - } }; } // namespace iptsd::core::linux diff --git a/src/hid/descriptor.hpp b/src/hid/descriptor.hpp deleted file mode 100644 index d14e736d..00000000 --- a/src/hid/descriptor.hpp +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifndef IPTSD_HID_DESCRIPTOR_HPP -#define IPTSD_HID_DESCRIPTOR_HPP - -#include "report.hpp" -#include "spec.hpp" - -#include -#include - -#include - -#include -#include -#include - -namespace iptsd::hid { - -class Descriptor { -private: - std::vector m_reports {}; - -public: - Descriptor() = default; - Descriptor(std::vector reports) : m_reports {std::move(reports)} {}; - - /*! - * Returns all reports of a certain type. - * - * @param[in] type The type of report to search for. - * @return A list of all report IDs of the given type. - */ - [[nodiscard]] std::set> reports(const ReportType type) const - { - std::set> reports {}; - - for (const Report &report : m_reports) { - if (report.type() != type) - continue; - - reports.insert(report.id()); - } - - return reports; - } - - /*! - * Returns the usage tags of a HID report. - * - * @param[in] report The report ID for which to get the usage tags. - * @return All usage tags that apply to the report. - */ - [[nodiscard]] std::vector usage(const std::optional id) const - { - std::vector usages {}; - - for (const Report &report : m_reports) { - if (report.id() != id) - continue; - - for (const Usage &usage : report.usages()) - usages.push_back(usage); - } - - return usages; - } - - /*! - * Calculates the size of a HID report. - * - * @param[in] report The report ID for which to calculate the size. - * @return The combined size of the HID report in bytes. - */ - [[nodiscard]] usize size(const std::optional id) const - { - f64 total_size = 0; - - for (const Report &report : m_reports) { - if (report.id() != id) - continue; - - total_size += casts::to(report.size()) / 8.0; - } - - return casts::to(std::ceil(total_size)); - } -}; - -} // namespace iptsd::hid - -#endif // IPTSD_HID_DESCRIPTOR_HPP diff --git a/src/hid/device.hpp b/src/hid/device.hpp new file mode 100644 index 00000000..2c523982 --- /dev/null +++ b/src/hid/device.hpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef IPTSD_HID_DEVICE_HPP +#define IPTSD_HID_DEVICE_HPP + +#include "report.hpp" + +#include + +#include + +#include +#include + +namespace iptsd::hid { + +class Device { +public: + virtual ~Device() = default; + + virtual u16 vendor() = 0; + virtual u16 product() = 0; + virtual const std::vector &descriptor() = 0; + + virtual isize read(gsl::span buffer) = 0; + virtual void get_feature(gsl::span report) = 0; + virtual void set_feature(gsl::span report) = 0; +}; + +} // namespace iptsd::hid + +#endif // IPTSD_HID_DEVICE_HPP diff --git a/src/hid/parser.hpp b/src/hid/parser.hpp index 1db257e2..78fb0583 100644 --- a/src/hid/parser.hpp +++ b/src/hid/parser.hpp @@ -3,7 +3,6 @@ #ifndef IPTSD_HID_PARSER_HPP #define IPTSD_HID_PARSER_HPP -#include "descriptor.hpp" #include "report.hpp" #include "spec.hpp" #include "state.hpp" @@ -24,16 +23,17 @@ namespace iptsd::hid { * * This function will parse the report ID, size and usage tags. * - * @param[in] data The buffer containing the descriptor. + * @param[in] buffer The buffer containing the descriptor. + * @param[in] reports A reference to the vector where a representation of the reports will be saved. * @return A representation of the HID reports defined by the descriptor. */ -inline Descriptor parse(const gsl::span buffer) +inline void parse(const gsl::span buffer, std::vector &reports) { ParserState state {}; - std::vector reports {}; - Reader reader {buffer}; + reports.clear(); + while (reader.size() > 0) { const u8 header = reader.read(); @@ -61,7 +61,25 @@ inline Descriptor parse(const gsl::span buffer) continue; } - reports.push_back(state.get_report(tag)); + bool found = false; + const Report nr = state.get_report(tag); + + // Try to find an existing report that we can update + for (Report &report : reports) { + if (nr.id() != report.id()) + continue; + + if (nr.type() != report.type()) + continue; + + report.merge(nr); + + found = true; + break; + } + + if (!found) + reports.push_back(nr); } else if (type == ItemType::Global) { const auto tag = gsl::narrow((header & BITS_TAG) >> SHIFT_TAG); @@ -95,8 +113,21 @@ inline Descriptor parse(const gsl::span buffer) } } } +} - return Descriptor {std::move(reports)}; +/*! + * Loads a HID descriptor from a its binary representation. + * + * This function will parse the report ID, size and usage tags. + * + * @param[in] buffer The buffer containing the descriptor. + * @return A representation of the HID reports defined by the descriptor. + */ +inline std::vector parse(const gsl::span buffer) +{ + std::vector reports {}; + parse(buffer, reports); + return reports; } } // namespace iptsd::hid diff --git a/src/hid/report.hpp b/src/hid/report.hpp index f7cb9d20..178b1ae5 100644 --- a/src/hid/report.hpp +++ b/src/hid/report.hpp @@ -9,7 +9,11 @@ #include #include +#include +#include #include +#include +#include #include namespace iptsd::hid { @@ -40,22 +44,20 @@ class Report { std::optional m_report_id; // The size of the report - u32 m_report_size; - u32 m_report_count; + u64 m_report_size; // The usage values describing the report - std::vector m_usages; + std::unordered_set m_usages; public: Report(ReportType type, std::optional report_id, u32 report_count, u32 report_size, - const std::vector &usages) + const std::unordered_set &usages) : m_type {type} , m_report_id {report_id} - , m_report_size {report_size} - , m_report_count {report_count} + , m_report_size {casts::to(report_count) * report_size} , m_usages {usages} {}; /*! @@ -79,16 +81,61 @@ class Report { */ [[nodiscard]] u64 size() const { - return casts::to(m_report_size) * m_report_count; + return m_report_size; } /* * The usage tags of the HID report. */ - [[nodiscard]] const std::vector &usages() const + [[nodiscard]] const std::unordered_set &usages() const { return m_usages; } + + /*! + * Checks if the report contains a certain usage tag. + * + * @param[in] value The usage tag to search for. + * @return Whether the combination of Usage / Usage Page applies to this report. + */ + [[nodiscard]] bool find_usage(Usage value) const + { + return this->find_usage(value.page, value.value); + } + + /*! + * Checks if the report contains a certain usage tag. + * + * @param[in] page The usage page tag to search for. + * @param[in] value The usage tag to search for. + * @return Whether the combination of Usage / Usage Page applies to this report. + */ + [[nodiscard]] bool find_usage(u16 page, u16 value) const + { + return std::any_of(m_usages.cbegin(), m_usages.cend(), + [&](const Usage &usage) -> bool { + return usage.page == page && usage.value == value; + }); + } + + /*! + * Combines two reports sharing the same ID and type. + * + * @param[in] other The report to combine with this one. + */ + void merge(const Report &other) + { + if (m_type != other.type()) + throw std::runtime_error {"Cannot merge two reports of different types"}; + + if (m_report_id != other.id()) + throw std::runtime_error {"Cannot merge two reports with different IDs"}; + + m_report_size += other.size(); + + for (const Usage &usage : other.usages()) + m_usages.insert(usage); + } }; } // namespace iptsd::hid diff --git a/src/hid/state.hpp b/src/hid/state.hpp index e9857d0e..9dc3aeba 100644 --- a/src/hid/state.hpp +++ b/src/hid/state.hpp @@ -11,6 +11,7 @@ #include #include +#include namespace iptsd::hid { @@ -25,7 +26,7 @@ class ParserState { std::optional m_usage_min = std::nullopt; std::optional m_usage_max = std::nullopt; - std::vector m_usages {}; + std::unordered_set m_usages {}; public: /*! @@ -101,7 +102,7 @@ class ParserState { if (!m_usage_page.has_value()) throw std::runtime_error {"HID Descriptor: Usage before Usage Page"}; - m_usages.push_back(Usage {m_usage_page.value(), usage}); + m_usages.insert(Usage {m_usage_page.value(), usage}); } /*! @@ -126,7 +127,7 @@ class ParserState { if (m_usage_max.has_value()) { for (u16 i = usage_min; i < (m_usage_max.value() + 1); i++) - m_usages.push_back(Usage {m_usage_page.value(), i}); + m_usages.insert(Usage {m_usage_page.value(), i}); m_usage_max.reset(); } else { @@ -146,7 +147,7 @@ class ParserState { if (m_usage_min.has_value()) { for (u16 i = m_usage_min.value(); i < (usage_max + 1); i++) - m_usages.push_back(Usage {m_usage_page.value(), i}); + m_usages.insert(Usage {m_usage_page.value(), i}); m_usage_min.reset(); } else { diff --git a/src/hid/usage.hpp b/src/hid/usage.hpp index 7a205d0c..58be3e53 100644 --- a/src/hid/usage.hpp +++ b/src/hid/usage.hpp @@ -3,15 +3,43 @@ #ifndef IPTSD_HID_USAGE_HPP #define IPTSD_HID_USAGE_HPP +#include #include +#include + namespace iptsd::hid { struct Usage { u16 page; u16 value; + + bool operator==(const Usage &other) const + { + return this->page == other.page && this->value == other.value; + } + + bool operator!=(const Usage &other) const + { + return !(*this == other); + } }; } // namespace iptsd::hid +/* + * Implement std::hash for usage so it can be added to unordered_set. + */ +template <> +struct std::hash { +public: + usize operator()(const iptsd::hid::Usage &usage) const + { + using namespace iptsd; + + const u32 value = (casts::to(usage.page) << 16) + usage.value; + return std::hash {}(value); + } +}; + #endif // IPTSD_HID_USAGE_HPP diff --git a/src/ipts/descriptor.hpp b/src/ipts/descriptor.hpp new file mode 100644 index 00000000..90430151 --- /dev/null +++ b/src/ipts/descriptor.hpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef IPTSD_IPTS_DESCRIPTOR_HPP +#define IPTSD_IPTS_DESCRIPTOR_HPP + +#include "protocol.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace iptsd::ipts { + +class Descriptor { +private: + std::vector m_reports {}; + +public: + Descriptor(const std::vector &reports) : m_reports {reports} {}; + + /*! + * Tries to find all reports that contain touch data in the HID descriptor. + * + * @return A list of reports containing IPTS touch data. + */ + [[nodiscard]] std::vector find_touch_data_reports() const + { + std::vector out {}; + + for (const hid::Report &report : m_reports) { + if (is_touch_data_report(report)) + out.push_back(report); + } + + return out; + } + + /*! + * Tries to find the report for modesetting in the HID descriptor. + * + * @return The HID report for modesetting if it exists, null otherwise. + */ + [[nodiscard]] std::optional find_modesetting_report() const + { + for (const hid::Report &report : m_reports) { + if (is_modesetting_report(report)) + return report; + } + + return std::nullopt; + } + + /*! + * Tries to find the metadata report in the HID descriptor. + * + * @return The HID report for fetching metadata if it exists, null otherwise. + */ + [[nodiscard]] std::optional find_metadata_report() const + { + for (const hid::Report &report : m_reports) { + if (is_metadata_report(report)) + return report; + } + + return std::nullopt; + } + + /*! + * Checks whether a HID report matches the properties for an IPTS touch data report. + * + * @param[in] report The HID report to check. + * @return Whether the given report contains touch data. + */ + static bool is_touch_data_report(const hid::Report &report) + { + const std::unordered_set &usages = report.usages(); + + if (report.type() != hid::ReportType::Input) + return false; + + if (usages.size() != 2) + return false; + + return report.find_usage(IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER, + IPTS_HID_REPORT_USAGE_SCAN_TIME) && + report.find_usage(IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER, + IPTS_HID_REPORT_USAGE_GESTURE_DATA); + } + + /*! + * Checks whether a HID report matches the properties for the modesetting report. + * + * @param[in] report The HID report to check. + * @return Whether the given report is a modesetting report. + */ + static bool is_modesetting_report(const hid::Report &report) + { + const std::unordered_set &usages = report.usages(); + + if (report.type() != hid::ReportType::Feature) + return false; + + if (usages.size() != 1) + return false; + + if (report.size() != 8) + return false; + + return report.find_usage(IPTS_HID_REPORT_USAGE_PAGE_VENDOR, + IPTS_HID_REPORT_USAGE_SET_MODE); + } + + /*! + * Checks whether a HID report matches the properties of the metadata report. + * + * @param[in] report The HID report to check. + * @return Whether the given report is a metadata report. + */ + static bool is_metadata_report(const hid::Report &report) + { + const std::unordered_set &usages = report.usages(); + + if (report.type() != hid::ReportType::Feature) + return false; + + if (usages.size() != 1) + return false; + + return report.find_usage(IPTS_HID_REPORT_USAGE_PAGE_DIGITIZER, + IPTS_HID_REPORT_USAGE_METADATA); + } +}; + +} // namespace iptsd::ipts + +#endif // IPTSD_IPTS_DESCRIPTOR_HPP diff --git a/src/ipts/device.hpp b/src/ipts/device.hpp new file mode 100644 index 00000000..4a9947e3 --- /dev/null +++ b/src/ipts/device.hpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef IPTSD_IPTS_DEVICE_HPP +#define IPTSD_IPTS_DEVICE_HPP + +#include "data.hpp" +#include "descriptor.hpp" +#include "parser.hpp" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace iptsd::ipts { + +enum class Mode { + Singletouch = 0, + Multitouch = 1, +}; + +class Device { +private: + // The (platform specific) HID device interface + std::shared_ptr m_hid; + + // Support code for interfacing with the device through the HID descriptor + Descriptor m_descriptor; + + // All reports of the HID descriptor that contain touch data + std::vector m_touch_data_reports; + +public: + Device(std::shared_ptr hid) + : m_hid {std::move(hid)} + , m_descriptor {m_hid->descriptor()} + , m_touch_data_reports {m_descriptor.find_touch_data_reports()} {}; + + /* + * + */ + [[nodiscard]] const Descriptor &descriptor() const + { + return m_descriptor; + } + + /*! + * Determines the required size for a buffer holding IPTS touch data. + * + * @return The size of the largest touch data report that IPTS can send. + */ + [[nodiscard]] usize buffer_size() const + { + u64 size = 0; + + for (const hid::Report &report : m_touch_data_reports) + size = std::max(size, report.size()); + + return casts::to(size / 8); + } + + /*! + * Reads the IPTS device metadata from the metadata feature report. + * + * @return The metadata of the device, or null if the report is not supported. + */ + [[nodiscard]] std::optional metadata() const + { + std::optional metadata = std::nullopt; + + const std::optional report = m_descriptor.find_metadata_report(); + if (!report.has_value()) + return std::nullopt; + + const std::optional id = report->id(); + if (!id.has_value()) + return std::nullopt; + + std::vector buffer((report->size() / 8) + 1); + buffer[0] = id.value(); + + m_hid->get_feature(buffer); + + Parser parser {}; + parser.on_metadata = [&](const Metadata &m) { metadata = m; }; + parser.parse(buffer); + + return metadata; + } + + /*! + * Changes the mode of the IPTS device. + * + * If the device is already in the requested mode, nothing will happen. + * + * @param[in] mode The new mode of operation. + */ + void set_mode(const Mode mode) const + { + const std::optional report = m_descriptor.find_modesetting_report(); + if (!report.has_value()) + throw std::runtime_error {"Could not find IPTS modesetting report"}; + + const std::optional id = report->id(); + if (!id.has_value()) + throw std::runtime_error {"Found modesetting report, but no report ID"}; + + std::array buffer {id.value(), gsl::narrow(mode)}; + m_hid->set_feature(buffer); + } + + /*! + * Checks whether a buffer contains IPTS touch data. + * + * @param[in] buffer The buffer that was read from the device. + * @return Whether the buffer contains touchscreen data. + */ + [[nodiscard]] bool is_touch_data(const gsl::span buffer) const + { + if (buffer.empty()) + return false; + + return std::any_of( + m_touch_data_reports.cbegin(), m_touch_data_reports.cend(), + [&](const hid::Report &report) { return report.id() == buffer[0]; }); + } +}; + +} // namespace iptsd::ipts + +#endif // IPTSD_IPTS_DEVICE_HPP