use log::{debug, info, warn}; use acpi::aml::namespace::AmlName; use amlserde::AmlSerdeValue; use std::str::FromStr; use std::sync::RwLock; use crate::acpi::AcpiContext; const ACPI_WMI_EVENT: u8 = 1 << 3; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct GuidBlock { pub guid: [u8; 16], pub notify_id: u8, pub instance_count: u8, pub flags: u8, } impl GuidBlock { pub const SIZE: usize = 20; pub fn parse(data: &[u8]) -> Option { if data.len() < Self::SIZE { return None; } let mut guid = [0u8; 16]; guid.copy_from_slice(&data[0..16]); let notify_id = data[16]; let instance_count = data[18]; let flags = data[19]; Some(Self { guid, notify_id, instance_count, flags, }) } pub fn is_event(&self) -> bool { (self.flags & ACPI_WMI_EVENT) != 0 } } #[derive(Clone, Debug)] pub struct WmiEvent { pub notify_id: u8, pub eventcode: u32, pub key: &'static str, } pub struct WmiRegistry { device_path: RwLock>, event_notify_ids: RwLock>, } fn lg_key_for_eventcode(eventcode: u32) -> &'static str { match eventcode { 0x70 => "lg-control-panel", 0x74 => "touchpad-toggle", 0xf020000 => "read-mode", 0x10000000 => "kbd-backlight", _ => "unknown", } } impl WmiRegistry { pub fn new() -> Self { Self { device_path: RwLock::new(None), event_notify_ids: RwLock::new(Vec::new()), } } pub fn device_path(&self) -> Option { self.device_path.read().unwrap().clone() } pub fn has_notify_id(&self, notify_id: u8) -> bool { self.event_notify_ids.read().unwrap().contains(¬ify_id) } pub fn discover(context: &AcpiContext) -> Self { let registry = Self::new(); const CANDIDATES: &[&str] = &[ "\\_SB.PC00.WMID", "\\_SB.PCI0.WMID", "\\_SB.WMID", "\\_SB.PC00.LPCB.WMID", "\\_SB.PCI0.LPCB.WMID", ]; for path in CANDIDATES { let Ok(hid) = context.evaluate_acpi_method(path, "_HID", &[]) else { continue; }; if hid.first().copied() != Some(0x0C14) { continue; } info!("acpid: WMI device at {}", path); *registry.device_path.write().unwrap() = Some(path.to_string()); registry.enumerate_event_guids(context, path); break; } if registry.device_path().is_none() { debug!("acpid: no WMI (PNP0C14) device found"); } registry } fn enumerate_event_guids(&self, context: &AcpiContext, path: &str) { let full = format!("{path}._WDG"); let Ok(name) = AmlName::from_str(&full) else { warn!("acpid: invalid _WDG path {}", full); return; }; let value = match context.aml_eval(name, Vec::new()) { Ok(v) => v, Err(err) => { warn!("acpid: _WDG evaluation failed at {}: {:?}", path, err); return; } }; let buffer = match value { AmlSerdeValue::Buffer(b) => b, _ => { warn!("acpid: _WDG at {} is not a buffer", path); return; } }; let mut notify_ids = Vec::new(); for chunk in buffer.chunks(GuidBlock::SIZE) { let Some(block) = GuidBlock::parse(chunk) else { continue; }; if block.is_event() { notify_ids.push(block.notify_id); } } let count = notify_ids.len(); *self.event_notify_ids.write().unwrap() = notify_ids; info!( "acpid: WMI enumerated {} event GUID(s) from _WDG ({} total bytes)", count, buffer.len() ); } pub fn handle_notify(&self, context: &AcpiContext, notify_id: u8) -> Option { if !self.has_notify_id(notify_id) { return None; } let path = self.device_path()?; let full = format!("{path}._WED"); let Ok(name) = AmlName::from_str(&full) else { return None; }; let value = context .aml_eval(name, vec![AmlSerdeValue::Integer(notify_id as u64)]) .ok()?; let eventcode = match value { AmlSerdeValue::Integer(v) => v as u32, _ => return None, }; Some(WmiEvent { notify_id, eventcode, key: lg_key_for_eventcode(eventcode), }) } } #[cfg(test)] mod tests { use super::*; #[test] fn guid_block_parse_event() { let mut data = [0u8; 20]; data[0] = 0x48; data[16] = 0x2A; data[18] = 0x01; data[19] = ACPI_WMI_EVENT; let block = GuidBlock::parse(&data).unwrap(); assert_eq!(block.guid[0], 0x48); assert_eq!(block.notify_id, 0x2A); assert_eq!(block.instance_count, 1); assert!(block.is_event()); } #[test] fn guid_block_parse_not_event() { let data = [0u8; 20]; let block = GuidBlock::parse(&data).unwrap(); assert!(!block.is_event()); } #[test] fn guid_block_too_short() { assert!(GuidBlock::parse(&[0u8; 19]).is_none()); } #[test] fn keymap_covers_lg_codes() { assert_eq!(lg_key_for_eventcode(0x10000000), "kbd-backlight"); assert_eq!(lg_key_for_eventcode(0x74), "touchpad-toggle"); assert_eq!(lg_key_for_eventcode(0x70), "lg-control-panel"); assert_eq!(lg_key_for_eventcode(0xf020000), "read-mode"); assert_eq!(lg_key_for_eventcode(0xdead), "unknown"); } }