usehid_core/platform/
linux.rs1use super::HidBackend;
6use crate::error::{Error, Result};
7use crate::hid::{KEYBOARD_REPORT_DESCRIPTOR, MOUSE_REPORT_DESCRIPTOR, GAMEPAD_REPORT_DESCRIPTOR};
8use std::fs::{File, OpenOptions};
9use std::io::Write;
10use std::os::unix::io::AsRawFd;
11
12const UHID_CREATE2: u32 = 11;
14const UHID_DESTROY: u32 = 1;
15const UHID_INPUT2: u32 = 12;
16
17#[repr(C)]
19struct UhidCreate2 {
20 name: [u8; 128],
21 phys: [u8; 64],
22 uniq: [u8; 64],
23 rd_size: u16,
24 bus: u16,
25 vendor: u32,
26 product: u32,
27 version: u32,
28 country: u32,
29 rd_data: [u8; 4096],
30}
31
32#[repr(C)]
34struct UhidEvent {
35 event_type: u32,
36 data: [u8; 4380], }
38
39#[repr(C)]
41struct UhidInput2 {
42 size: u16,
43 data: [u8; 4096],
44}
45
46const BUS_VIRTUAL: u16 = 0x06;
47
48pub struct LinuxUhidBackend {
50 file: File,
51}
52
53impl LinuxUhidBackend {
54 fn new(name: &str, report_descriptor: &[u8], vendor_id: u32, product_id: u32) -> Result<Self> {
55 let file = OpenOptions::new()
56 .read(true)
57 .write(true)
58 .open("/dev/uhid")
59 .map_err(|e| Error::CreateFailed(format!("Failed to open /dev/uhid: {}. Try running as root or add user to 'input' group.", e)))?;
60
61 let mut create = UhidCreate2 {
63 name: [0; 128],
64 phys: [0; 64],
65 uniq: [0; 64],
66 rd_size: report_descriptor.len() as u16,
67 bus: BUS_VIRTUAL,
68 vendor: vendor_id,
69 product: product_id,
70 version: 0,
71 country: 0,
72 rd_data: [0; 4096],
73 };
74
75 let name_bytes = name.as_bytes();
77 let name_len = name_bytes.len().min(127);
78 create.name[..name_len].copy_from_slice(&name_bytes[..name_len]);
79
80 let rd_len = report_descriptor.len().min(4096);
82 create.rd_data[..rd_len].copy_from_slice(&report_descriptor[..rd_len]);
83
84 let mut event = UhidEvent {
86 event_type: UHID_CREATE2,
87 data: [0; 4380],
88 };
89
90 unsafe {
92 let create_ptr = &create as *const UhidCreate2 as *const u8;
93 let create_size = std::mem::size_of::<UhidCreate2>();
94 std::ptr::copy_nonoverlapping(
95 create_ptr,
96 event.data.as_mut_ptr(),
97 create_size.min(event.data.len()),
98 );
99 }
100
101 let event_bytes = unsafe {
103 std::slice::from_raw_parts(
104 &event as *const UhidEvent as *const u8,
105 std::mem::size_of::<UhidEvent>(),
106 )
107 };
108
109 let mut f = file.try_clone()?;
110 f.write_all(event_bytes)
111 .map_err(|e| Error::CreateFailed(format!("Failed to create UHID device: {}", e)))?;
112
113 Ok(Self { file })
114 }
115}
116
117impl HidBackend for LinuxUhidBackend {
118 fn send_report(&self, report: &[u8]) -> Result<()> {
119 let mut input = UhidInput2 {
120 size: report.len() as u16,
121 data: [0; 4096],
122 };
123
124 let len = report.len().min(4096);
125 input.data[..len].copy_from_slice(&report[..len]);
126
127 let mut event = UhidEvent {
128 event_type: UHID_INPUT2,
129 data: [0; 4380],
130 };
131
132 unsafe {
133 let input_ptr = &input as *const UhidInput2 as *const u8;
134 let input_size = std::mem::size_of::<UhidInput2>();
135 std::ptr::copy_nonoverlapping(
136 input_ptr,
137 event.data.as_mut_ptr(),
138 input_size.min(event.data.len()),
139 );
140 }
141
142 let event_bytes = unsafe {
143 std::slice::from_raw_parts(
144 &event as *const UhidEvent as *const u8,
145 std::mem::size_of::<UhidEvent>(),
146 )
147 };
148
149 let mut f = self.file.try_clone()?;
150 f.write_all(event_bytes)
151 .map_err(|e| Error::SendFailed(format!("Failed to send UHID report: {}", e)))?;
152
153 Ok(())
154 }
155
156 fn destroy(self: Box<Self>) -> Result<()> {
157 let event = UhidEvent {
158 event_type: UHID_DESTROY,
159 data: [0; 4380],
160 };
161
162 let event_bytes = unsafe {
163 std::slice::from_raw_parts(
164 &event as *const UhidEvent as *const u8,
165 std::mem::size_of::<UhidEvent>(),
166 )
167 };
168
169 let mut f = self.file.try_clone()?;
170 f.write_all(event_bytes).ok(); Ok(())
173 }
174}
175
176pub fn create_mouse_backend(name: &str) -> Result<Box<dyn HidBackend>> {
177 Ok(Box::new(LinuxUhidBackend::new(
178 name,
179 MOUSE_REPORT_DESCRIPTOR,
180 0x1234,
181 0x0001,
182 )?))
183}
184
185pub fn create_keyboard_backend(name: &str) -> Result<Box<dyn HidBackend>> {
186 Ok(Box::new(LinuxUhidBackend::new(
187 name,
188 KEYBOARD_REPORT_DESCRIPTOR,
189 0x1234,
190 0x0002,
191 )?))
192}
193
194pub fn create_gamepad_backend(name: &str) -> Result<Box<dyn HidBackend>> {
195 Ok(Box::new(LinuxUhidBackend::new(
196 name,
197 GAMEPAD_REPORT_DESCRIPTOR,
198 0x1234,
199 0x0003,
200 )?))
201}