use std::collections::BTreeSet; use anyhow::Result; use inputd::ProducerHandle; use orbclient::{ ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE, K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT, K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER, K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK, }; use crate::hid::ReportDescriptorSummary; use crate::report_desc::{decode_report, DecodedReport, ReportLayout}; pub struct InputForwarder { producer: ProducerHandle, keyboard_state: BTreeSet, last_buttons: u8, } impl InputForwarder { pub fn new() -> Result { Ok(Self { producer: ProducerHandle::new()?, keyboard_state: BTreeSet::new(), last_buttons: 0, }) } pub fn forward_report( &mut self, summary: &ReportDescriptorSummary, report: &[u8], ) -> Result<()> { if report.is_empty() { return Ok(()); } if summary.has_keyboard_page && report.len() >= 8 { self.forward_boot_keyboard(report)?; return Ok(()); } if summary.has_pointer_page && report.len() >= 3 { self.forward_boot_pointer(report)?; return Ok(()); } Ok(()) } /// Descriptor-driven report path (multitouch digitizers, absolute /// touchpads, button/wheel mice). Boot-protocol fields keep working /// through `forward_report` for devices whose reports are boot-shaped. pub fn forward_layout_report(&mut self, layouts: &[ReportLayout], report: &[u8]) -> Result<()> { let Some(decoded) = decode_report(layouts, report) else { return Ok(()); }; if decoded.keyboard_page && report.len() >= 8 { self.forward_boot_keyboard(report)?; return Ok(()); } self.forward_decoded(decoded) } fn forward_decoded(&mut self, decoded: DecodedReport) -> Result<()> { // Contacts drive absolute pointer position: the first touching // contact is the pointer; a second contact converts to two-finger // scroll (the common touchpad gesture). let touching: Vec<&(u32, bool, i32, i32)> = decoded.contacts.iter().filter(|contact| contact.1).collect(); match touching.as_slice() { [] => {} [first] => { self.producer.write_event(MouseEvent { x: first.2, y: first.3, } .to_event())?; } [first, second, ..] => { let dy = (second.3 - first.3) / 8; if dy != 0 { self.producer .write_event(ScrollEvent { x: 0, y: dy }.to_event())?; } self.producer.write_event(MouseEvent { x: first.2, y: first.3, } .to_event())?; } } if let (Some(x), Some(y)) = (decoded.x, decoded.y) { self.producer .write_event(MouseEvent { x, y }.to_event())?; } if let Some(wheel) = decoded.wheel { if wheel != 0 { self.producer .write_event(ScrollEvent { x: 0, y: wheel }.to_event())?; } } let buttons = (decoded.buttons & 0x07) as u8; if buttons != self.last_buttons { self.producer.write_event( ButtonEvent { left: buttons & 0x01 != 0, middle: buttons & 0x04 != 0, right: buttons & 0x02 != 0, } .to_event(), )?; } self.last_buttons = buttons; Ok(()) } fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> { let modifiers = report[0]; for (bit, scancode) in [ (0_u8, K_LEFT_CTRL), (1, K_LEFT_SHIFT), (2, K_ALT), (3, K_LEFT_SUPER), (4, K_RIGHT_CTRL), (5, K_RIGHT_SHIFT), (6, K_ALT_GR), (7, K_RIGHT_SUPER), ] { self.producer.write_event( KeyEvent { character: '\0', scancode, pressed: modifiers & (1 << bit) != 0, } .to_event(), )?; } let current = report[2..8] .iter() .copied() .filter(|code| *code != 0) .collect::>(); for code in current.difference(&self.keyboard_state) { if let Some(scancode) = map_boot_keyboard_usage(*code) { self.producer.write_event( KeyEvent { character: '\0', scancode, pressed: true, } .to_event(), )?; } } for code in self.keyboard_state.difference(¤t) { if let Some(scancode) = map_boot_keyboard_usage(*code) { self.producer.write_event( KeyEvent { character: '\0', scancode, pressed: false, } .to_event(), )?; } } self.keyboard_state = current; Ok(()) } fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> { let dx = i8::from_ne_bytes([report[1]]) as i32; let dy = i8::from_ne_bytes([report[2]]) as i32; if dx != 0 || dy != 0 { self.producer .write_event(MouseRelativeEvent { dx, dy }.to_event())?; } if let Some(scroll) = report.get(3).copied() { let scroll = i8::from_ne_bytes([scroll]) as i32; if scroll != 0 { self.producer .write_event(ScrollEvent { x: 0, y: scroll }.to_event())?; } } let buttons = report[0] & 0x07; for index in 0..3 { let mask = 1 << index; if (buttons & mask) != (self.last_buttons & mask) { self.producer.write_event( ButtonEvent { left: buttons & 0x01 != 0, middle: buttons & 0x04 != 0, right: buttons & 0x02 != 0, } .to_event(), )?; break; } } self.last_buttons = buttons; Ok(()) } } fn map_boot_keyboard_usage(usage: u8) -> Option { Some(match usage { 0x04..=0x1D => b'a' + (usage - 0x04), 0x1E => b'1', 0x1F => b'2', 0x20 => b'3', 0x21 => b'4', 0x22 => b'5', 0x23 => b'6', 0x24 => b'7', 0x25 => b'8', 0x26 => b'9', 0x27 => b'0', 0x28 => K_ENTER, 0x29 => K_ESC, 0x2A => K_BKSP, 0x2B => K_TAB, 0x2C => K_SPACE, 0x2D => K_MINUS, 0x2E => K_EQUALS, 0x2F => K_BRACE_OPEN, 0x30 => K_BRACE_CLOSE, 0x33 => K_SEMICOLON, 0x34 => K_QUOTE, 0x35 => K_TICK, 0x36 => K_COMMA, 0x37 => K_PERIOD, 0x38 => K_SLASH, 0x39 => K_CAPS, _ => return None, }) }