f315a9be3b
Phase 5 — Laptop ACPI events (LG Gram 16Z90TP compatibility plan):
- Vendor acpi-rs crate (rev 90cbe88) into base fork with a real
Opcode::Notify executor; upstream panicked on every Notify opcode,
which is the backbone of ACPI event flow on real laptop firmware.
Handler::handle_notify hook added; acpid + amlserde pointed at the
path dep so every consumer sees the same fork.
- gpe.rs: FADT-driven GPE block register map (status half + enable
half), write-1-to-clear status bits, read-modify-write enable
preserving every other GPE's firmware state. PM1 fixed-event bits
(PWRBTN/SLPBTN/RTC) per ACPI 6.4 §4.8.3.1.
- power_events.rs: init builds the GPE map, discovers the EC via ECDT
(ACPI 6.4 §5.2.16) with a _HID=PNP0C09 probe fallback over conven-
tional paths, enables the EC GPE + PM1 fixed events, discovers lid
and ACPI 4.0 fan devices. handle_sci dispatches PM1 → EC query loop
(bounded at 32) → AML notification drain, following Linux 7.1
evgpe.c / ec.c / button.c.
- notifications.rs: shared AmlNotifications queue (parking_lot::Mutex)
populated by the vendored acpi-rs handle_notify hook, drained by the
SCI handler and redbear-upower.
- ec.rs: sci_evt_set() and query() exposed on Ec for the SCI handler.
- scheme.rs: new handle kinds Lid, LidState, ButtonDir, Button,
Notifications, Fan, FanState, FanSpeed with proper dir/file
separation. Fan _FST on read, _FSL on write (percent clamped 0-100).
- main.rs: subscribes /scheme/irq/{sci_irq} (default 9) on the event
queue, dispatches SCI events through handle_sci. PowerButton incre-
ments edge counter and triggers the shutdown path; SleepButton incre-
ments counter and calls enter_s2idle.
- acpi.rs: AcpiContext gained gpe, ec_device, lid_device, lid_state,
power_button_events, sleep_button_events, fan_devices fields (all
RwLock-guarded). evaluate_acpi_method and enter_s2idle changed from
&mut self to &self (AML mutation goes through RwLock inner state).
- aml_physmem.rs: handle_notify impl pushes onto the shared queue.
Phase 4.4 — HID report descriptor parser (i2c-hidd):
- report_desc.rs: HID 1.11 §6.2.2 descriptor parser + decoder. Global-
state stack (Push/Pop), usage-min/max expansion, sign-extended
fields, contact reconstruction (id, tip, x, y). 3 unit tests.
- input.rs: forward_layout_report dispatches descriptor-driven reports
through forward_decoded: first touching contact → absolute
MouseEvent; two simultaneous contacts → vertical ScrollEvent (two-
finger scroll); buttons → ButtonEvent; keyboard-page reports fall
back to the existing boot-protocol path.
- hid.rs: stream_input_reports parses the descriptor once and uses
forward_layout_report when layouts are non-empty, otherwise keeps the
boot-protocol summary path.
Reference: Linux 7.1 drivers/acpi/{evgpeblk.c,evgpe.c,ec.c,button.c}
and drivers/hid/hid-core.c.
All affected crates compile for x86_64-unknown-redox; i2c-hidd 3/3
unit tests pass on host; acpid host-side tests still can't link
(pre-existing libredox limitation — must run via redoxer).
249 lines
7.4 KiB
Rust
249 lines
7.4 KiB
Rust
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<u8>,
|
|
last_buttons: u8,
|
|
}
|
|
|
|
impl InputForwarder {
|
|
pub fn new() -> Result<Self> {
|
|
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::<BTreeSet<_>>();
|
|
|
|
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<u8> {
|
|
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,
|
|
})
|
|
}
|