Skip to main content

usehid_core/platform/
linux.rs

1//! Linux HID backend using uhid (/dev/uhid)
2//!
3//! This requires access to /dev/uhid, typically needs root or uinput group.
4
5use 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
12// UHID event types
13const UHID_CREATE2: u32 = 11;
14const UHID_DESTROY: u32 = 1;
15const UHID_INPUT2: u32 = 12;
16
17// UHID_CREATE2 structure
18#[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// UHID event structure
33#[repr(C)]
34struct UhidEvent {
35    event_type: u32,
36    data: [u8; 4380], // Union of all event types
37}
38
39// UHID_INPUT2 structure
40#[repr(C)]
41struct UhidInput2 {
42    size: u16,
43    data: [u8; 4096],
44}
45
46const BUS_VIRTUAL: u16 = 0x06;
47
48/// Linux UHID backend
49pub 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        // Create UHID device
62        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        // Copy name
76        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        // Copy report descriptor
81        let rd_len = report_descriptor.len().min(4096);
82        create.rd_data[..rd_len].copy_from_slice(&report_descriptor[..rd_len]);
83        
84        // Create event
85        let mut event = UhidEvent {
86            event_type: UHID_CREATE2,
87            data: [0; 4380],
88        };
89        
90        // Copy create struct to event data
91        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        // Write event
102        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(); // Best effort
171        
172        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}