MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
TabletBackend.hpp
Go to the documentation of this file.
1#pragma once
2
5
6using hid_device = struct hid_device_;
7
8namespace MayaFlux::Core {
9
10/**
11 * @brief Extended information for a tablet tool.
12 */
13struct MAYAFLUX_API TabletToolInfo : InputDeviceInfo {
14 TabletToolType tool_type { TabletToolType::UNKNOWN };
15 TabletAxes axes { TabletAxes::NONE };
16 uint64_t hardware_serial {}; ///< Zero when the tool reports no serial
17 std::string tablet_name; ///< Parent device, shared by pen and eraser
18};
19
20/**
21 * @brief One field located in a report by descriptor parsing.
22 *
23 * Bit offsets are measured from the first byte after the report ID when the
24 * device uses numbered reports, and from byte zero when it does not.
25 */
27 uint8_t report_id {};
28 uint16_t usage_page {};
29 uint16_t usage {};
30 uint32_t bit_offset {};
31 uint32_t bit_size {};
32 int32_t logical_min {};
33 int32_t logical_max {};
34 size_t slot { TabletFrame::SLOT_COUNT }; ///< SLOT_COUNT means not a slot field
35};
36
37/**
38 * @brief Parsed layout of one device's input reports.
39 */
41 std::vector<TabletField> fields;
42 bool uses_report_ids { false };
44 bool has_invert { false };
45 bool has_serial { false };
46};
47
48/**
49 * @class TabletBackend
50 * @brief Cross-platform tablet and stylus backend over raw HID.
51 *
52 * Opens digitizer devices through HIDAPI on every supported platform,
53 * retrieves each device's HID report descriptor, parses it, and unpacks
54 * incoming reports into a fixed slot layout. There is no platform-specific
55 * code, no window, no message loop, no run loop and no thread affinity.
56 * Tablet data flows in a process that never creates a window.
57 *
58 * Devices are selected by HID usage page 0x0D, usages Digitizer (0x01) and
59 * Pen (0x02). Where HIDAPI does not report a usage page during enumeration,
60 * the descriptor is fetched and inspected instead.
61 *
62 * A device carrying an Invert usage presents two tools and therefore two
63 * device ids, one pen and one eraser. Reports route to whichever the Invert
64 * bit selects.
65 *
66 * Each input report produces one InputValue of Type::VECTOR laid out per
67 * TabletFrame. Fields absent from a report retain their previous value,
68 * since a report carries only what the device chose to send. Wheel clears
69 * after each emission because it is a delta.
70 *
71 * timestamp_ns is the time the report was read. HIDAPI carries no device
72 * timestamp on any platform, so poll jitter is present in the value.
73 *
74 * Threading follows HIDBackend: a dedicated thread polls open devices and
75 * invokes the input callback from that thread.
76 *
77 * Requires read access to the HID device nodes. On Linux that is a udev
78 * rule for /dev/hidraw*, on macOS the Input Monitoring permission.
79 */
80class MAYAFLUX_API TabletBackend : public IInputBackend {
81public:
82 /**
83 * @brief Configuration for the tablet backend.
84 */
85 struct Config {
86 size_t read_buffer_size { 256 }; ///< Per-device read buffer
87 int poll_timeout_ms { 2 }; ///< Timeout for hid_read_timeout
88 bool split_eraser { true }; ///< Report the eraser end as its own tool
89 bool probe_all_devices { true }; ///< Read every HID descriptor rather than trusting enumeration
90 };
91
93 explicit TabletBackend(Config config);
94 ~TabletBackend() override;
95
96 TabletBackend(const TabletBackend&) = delete;
100
101 // =========================================================================
102 // IInputBackend
103 // =========================================================================
104
105 bool initialize() override;
106 void start() override;
107 void stop() override;
108 void shutdown() override;
109
110 [[nodiscard]] bool is_initialized() const override { return m_initialized.load(); }
111 [[nodiscard]] bool is_running() const override { return m_running.load(); }
112
113 [[nodiscard]] std::vector<InputDeviceInfo> get_devices() const override;
114 size_t refresh_devices() override;
115
116 bool open_device(uint32_t device_id) override;
117 void close_device(uint32_t device_id) override;
118 [[nodiscard]] bool is_device_open(uint32_t device_id) const override;
119 [[nodiscard]] std::vector<uint32_t> get_open_devices() const override;
120
121 void set_input_callback(InputCallback callback) override;
122 void set_device_callback(DeviceCallback callback) override;
123
124 [[nodiscard]] InputType get_type() const override { return InputType::TABLET; }
125 [[nodiscard]] std::string get_name() const override { return "Tablet (HID descriptor)"; }
126 [[nodiscard]] std::string get_version() const override;
127
128 // =========================================================================
129 // Tablet specific
130 // =========================================================================
131
132 /**
133 * @brief Extended information for a tool.
134 * @param device_id Tool identifier from get_devices().
135 */
136 [[nodiscard]] std::optional<TabletToolInfo>
137 get_tool_info(uint32_t device_id) const;
138
139 /**
140 * @brief Parsed report layout for the device backing a tool.
141 *
142 * Exposed for inspection and for writing custom unpackers against
143 * hardware whose descriptor is unusual.
144 */
145 [[nodiscard]] std::optional<TabletLayout>
146 get_layout(uint32_t device_id) const;
147
148 /**
149 * @brief Parse a HID report descriptor into a tablet layout.
150 *
151 * Free of device state and of HIDAPI, so captured descriptor bytes can
152 * be parsed and asserted against with no hardware present.
153 *
154 * @param descriptor Raw report descriptor bytes.
155 * @return Parsed layout. No fields means no digitizer usages were found.
156 */
157 [[nodiscard]] static TabletLayout parse_descriptor(std::span<const uint8_t> descriptor);
158
159private:
160 /**
161 * @brief One physical HID device and the tools it presents.
162 */
164 hid_device* handle { nullptr };
165 std::string path;
166 std::string name;
167 uint16_t vendor_id {};
168 uint16_t product_id {};
170 std::vector<uint8_t> read_buffer;
171 std::atomic<bool> active { false };
172
173 uint32_t pen_id {};
174 uint32_t eraser_id {};
175 std::array<double, TabletFrame::SLOT_COUNT> slots {};
176 bool inverted { false };
177 };
178
180
181 std::atomic<bool> m_initialized { false };
182 std::atomic<bool> m_running { false };
183 std::atomic<bool> m_stop_requested { false };
184
185 mutable std::mutex m_devices_mutex;
186 std::unordered_map<std::string, std::shared_ptr<TabletDevice>> m_devices;
187 std::unordered_map<uint32_t, TabletToolInfo> m_tools;
188 std::unordered_map<uint32_t, std::string> m_tool_paths;
189 uint32_t m_next_device_id { 1 };
190
191 std::thread m_poll_thread;
194 mutable std::mutex m_callback_mutex;
195
196 void poll_thread_func();
197 void poll_device(TabletDevice& device);
198
199 void unpack_report(TabletDevice& device, std::span<const uint8_t> report);
200 void emit_frame(TabletDevice& device);
201
202 bool adopt_device(const std::string& path, uint16_t vid, uint16_t pid,
203 std::string name);
204
205 void notify_input(const InputValue& value);
206 void notify_device_change(const InputDeviceInfo& info, bool connected);
207};
208
209} // namespace MayaFlux::Core
hid_device_ hid_device
Definition HIDBackend.hpp:5
float value
Abstract interface for input device backends.
TabletBackend(TabletBackend &&)=delete
TabletBackend & operator=(const TabletBackend &)=delete
TabletBackend & operator=(TabletBackend &&)=delete
TabletBackend(const TabletBackend &)=delete
std::unordered_map< uint32_t, std::string > m_tool_paths
std::string get_name() const override
Get backend name/identifier string.
bool is_initialized() const override
Check if backend is initialized.
InputType get_type() const override
Get backend type.
std::unordered_map< std::string, std::shared_ptr< TabletDevice > > m_devices
bool is_running() const override
Check if backend is actively listening.
std::unordered_map< uint32_t, TabletToolInfo > m_tools
Cross-platform tablet and stylus backend over raw HID.
void initialize()
Definition main.cpp:11
InputType
Input backend type enumeration.
std::function< void(const InputValue &)> InputCallback
Callback signature for input events.
TabletToolType
Physical tool type.
std::function< void(const InputDeviceInfo &, bool connected)> DeviceCallback
Callback signature for device connection/disconnection events.
TabletAxes
Axes a tool actually reports.
Information about a connected input device.
Generic input value container.
Configuration for the tablet backend.
One physical HID device and the tools it presents.
size_t slot
SLOT_COUNT means not a slot field.
One field located in a report by descriptor parsing.
static constexpr size_t SLOT_COUNT
std::vector< TabletField > fields
Parsed layout of one device's input reports.
std::string tablet_name
Parent device, shared by pen and eraser.
Extended information for a tablet tool.