From 678e9c6b3082d03c6f805ee340608562201cff58 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Sun, 31 Mar 2024 11:23:29 -0700 Subject: [PATCH] fix(SourceDevice): Allow blocking of input events from specific source devices. --- .../inputplumber/devices/50-legion_go.yaml | 2 + .../schema/composite_device_v1.json | 5 ++ src/config/mod.rs | 31 ++++++++++- src/drivers/lego/driver.rs | 4 +- src/input/composite_device/mod.rs | 51 +++++++++++++++---- src/input/source/evdev.rs | 7 +-- src/input/source/hidraw.rs | 4 +- src/input/source/hidraw/lego.rs | 19 +++++-- src/input/source/hidraw/steam_deck.rs | 19 +++++-- 9 files changed, 119 insertions(+), 23 deletions(-) diff --git a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml index 6e5abf53..f6d957b7 100644 --- a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml @@ -21,6 +21,7 @@ matches: # from these devices will be watched and translated according to the key map. source_devices: - group: keyboard # Block data + blocked: true hidraw: vendor_id: 0x17ef product_id: 0x6182 @@ -56,6 +57,7 @@ source_devices: product_id: "6182" name: "Generic X-Box pad" - group: keyboard + blocked: true unique: false evdev: vendor_id: "17ef" diff --git a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json index 821eb19b..c95624f0 100644 --- a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json +++ b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json @@ -130,6 +130,11 @@ "gamepad" ] }, + "blocked": { + "description": "If true, device will be grabbed but no events from this device will reach target devices. Defaults to false.", + "type": "boolean", + "default": false + }, "evdev": { "$ref": "#/definitions/Evdev" }, diff --git a/src/config/mod.rs b/src/config/mod.rs index 29724869..b627da52 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,7 +7,10 @@ use thiserror::Error; use crate::{ dmi::data::DMIData, - input::event::{native::NativeEvent, value::InputValue}, + input::{ + event::{native::NativeEvent, value::InputValue}, + manager::SourceDeviceInfo, + }, procfs, }; @@ -254,6 +257,7 @@ pub struct SourceDevice { pub evdev: Option, pub hidraw: Option, pub unique: Option, + pub blocked: Option, } #[derive(Debug, Deserialize, Clone, PartialEq)] @@ -318,6 +322,31 @@ impl CompositeDeviceConfig { .collect() } + /// Returns a [SourceDevice] if it matches the given [SourceDeviceInfo]. + pub fn get_matching_device(&self, device: &SourceDeviceInfo) -> Option { + match device { + SourceDeviceInfo::EvdevDeviceInfo(evdev) => { + for config in self.source_devices.iter() { + if let Some(evdev_config) = config.evdev.as_ref() { + if self.has_matching_evdev(evdev, evdev_config) { + return Some(config.clone()); + } + } + } + } + SourceDeviceInfo::HIDRawDeviceInfo(hidraw) => { + for config in self.source_devices.iter() { + if let Some(hidraw_config) = config.hidraw.as_ref() { + if self.has_matching_hidraw(hidraw, hidraw_config) { + return Some(config.clone()); + } + } + } + } + } + None + } + /// Returns true if a given hidraw device is within a list of hidraw configs. pub fn has_matching_hidraw(&self, device: &DeviceInfo, hidraw_config: &Hidraw) -> bool { log::debug!("Checking hidraw config: {:?}", hidraw_config); diff --git a/src/drivers/lego/driver.rs b/src/drivers/lego/driver.rs index de2a85bc..4e4e9677 100644 --- a/src/drivers/lego/driver.rs +++ b/src/drivers/lego/driver.rs @@ -109,8 +109,8 @@ impl Driver { let report_id = buf[0]; let slice = &buf[..bytes_read]; - log::trace!("Got Report ID: {report_id}"); - log::trace!("Got Report Size: {bytes_read}"); + //log::trace!("Got Report ID: {report_id}"); + //log::trace!("Got Report Size: {bytes_read}"); match report_id { DINPUTLEFT_DATA => { diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index a97df290..c4d39388 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -49,7 +49,7 @@ pub enum InterceptMode { /// dispatched as they come in. #[derive(Debug, Clone)] pub enum Command { - ProcessEvent(Event), + ProcessEvent(String, Event), GetCapabilities(mpsc::Sender>), SetInterceptMode(InterceptMode), GetInterceptMode(mpsc::Sender), @@ -280,6 +280,9 @@ pub struct CompositeDevice { rx: broadcast::Receiver, /// Source devices that this composite device will consume. source_devices: Vec, + /// HashSet of source devices that are blocked from passing their input events to target + /// events. + source_devices_blocked: HashSet, /// Physical device path for source devices. E.g. ["/dev/input/event0"] source_device_paths: Vec, /// All currently running source device threads @@ -318,6 +321,7 @@ impl CompositeDevice { tx, rx, source_devices: Vec::new(), + source_devices_blocked: HashSet::new(), source_device_paths: Vec::new(), source_device_tasks: JoinSet::new(), source_devices_used: Vec::new(), @@ -391,8 +395,8 @@ impl CompositeDevice { while let Ok(cmd) = self.rx.recv().await { //log::debug!("Received command: {:?}", cmd); match cmd { - Command::ProcessEvent(event) => { - if let Err(e) = self.process_event(event).await { + Command::ProcessEvent(device_id, event) => { + if let Err(e) = self.process_event(device_id, event).await { log::error!("Failed to process event: {:?}", e); } } @@ -599,8 +603,17 @@ impl CompositeDevice { /// Process a single event from a source device. Events are piped through /// a translation layer, then dispatched to the appropriate target device(s) - async fn process_event(&mut self, raw_event: Event) -> Result<(), Box> { - log::trace!("Received event: {:?}", raw_event); + async fn process_event( + &mut self, + device_id: String, + raw_event: Event, + ) -> Result<(), Box> { + log::trace!("Received event: {:?} from {device_id}", raw_event); + + if self.source_devices_blocked.contains(&device_id) { + log::trace!("Blocking event! {:?}", raw_event); + return Ok(()); + } // Convert the event into a NativeEvent let event: NativeEvent = match raw_event { @@ -958,6 +971,7 @@ impl CompositeDevice { if let Some(idx) = self.source_devices_used.iter().position(|str| str == &id) { self.source_devices_used.remove(idx); }; + self.source_devices_blocked.remove(&id); } if id.starts_with("hidraw://") { let name = id.strip_prefix("hidraw://").unwrap(); @@ -970,6 +984,7 @@ impl CompositeDevice { if let Some(idx) = self.source_devices_used.iter().position(|str| str == &id) { self.source_devices_used.remove(idx); }; + self.source_devices_blocked.remove(&id); } log::debug!( @@ -987,11 +1002,11 @@ impl CompositeDevice { /// Creates and adds a source device using the given [SourceDeviceInfo] fn add_source_device(&mut self, device_info: SourceDeviceInfo) -> Result<(), Box> { let device_info = device_info.clone(); - match device_info { + match device_info.clone() { SourceDeviceInfo::EvdevDeviceInfo(info) => { // Create an instance of the device log::debug!("Adding source device: {:?}", info); - let device = source::evdev::EventDevice::new(info, self.tx.clone()); + let device = source::evdev::EventDevice::new(info.clone(), self.tx.clone()); // Get the capabilities of the source device. // TODO: When we *remove* a source device, we also need to remove @@ -1012,7 +1027,16 @@ impl CompositeDevice { let source_device = source::SourceDevice::EventDevice(device); self.source_devices.push(source_device); self.source_device_paths.push(device_path); - self.source_devices_used.push(id); + self.source_devices_used.push(id.clone()); + + // Check if this device should be blocked from sending events to target devices. + if let Some(device_config) = self.config.get_matching_device(&device_info) { + if let Some(blocked) = device_config.blocked { + if blocked { + self.source_devices_blocked.insert(id); + } + } + }; } SourceDeviceInfo::HIDRawDeviceInfo(info) => { @@ -1033,7 +1057,16 @@ impl CompositeDevice { let source_device = source::SourceDevice::HIDRawDevice(device); self.source_devices.push(source_device); self.source_device_paths.push(device_path); - self.source_devices_used.push(id); + self.source_devices_used.push(id.clone()); + + // Check if this device should be blocked from sending events to target devices. + if let Some(device_config) = self.config.get_matching_device(&device_info) { + if let Some(blocked) = device_config.blocked { + if blocked { + self.source_devices_blocked.insert(id); + } + } + }; } } diff --git a/src/input/source/evdev.rs b/src/input/source/evdev.rs index 29e6a7bb..a8bf55e8 100644 --- a/src/input/source/evdev.rs +++ b/src/input/source/evdev.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, error::Error}; -use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent, KeyCode}; +use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent}; use tokio::sync::broadcast; use zbus::{fdo, Connection}; use zbus_macros::dbus_interface; @@ -8,7 +8,7 @@ use zbus_macros::dbus_interface; use crate::{ constants::BUS_PREFIX, input::{ - capability::{Capability, Gamepad, GamepadButton}, + capability::Capability, composite_device::Command, event::{evdev::EvdevEvent, Event}, }, @@ -132,7 +132,8 @@ impl EventDevice { // Send the event to the composite device let event = Event::Evdev(evdev_event); - self.composite_tx.send(Command::ProcessEvent(event))?; + self.composite_tx + .send(Command::ProcessEvent(self.get_id(), event))?; } log::debug!("Stopped reading device events"); diff --git a/src/input/source/hidraw.rs b/src/input/source/hidraw.rs index dc887041..12414b5b 100644 --- a/src/input/source/hidraw.rs +++ b/src/input/source/hidraw.rs @@ -105,7 +105,7 @@ impl HIDRawDevice { if self.info.vendor_id() == steam_deck::VID && self.info.product_id() == steam_deck::PID { log::info!("Detected Steam Deck"); let tx = self.composite_tx.clone(); - let driver = steam_deck::DeckController::new(self.info.clone(), tx); + let driver = steam_deck::DeckController::new(self.info.clone(), tx, self.get_id()); driver.run().await?; } else if self.info.vendor_id() == drivers::lego::driver::VID && (self.info.product_id() == drivers::lego::driver::PID @@ -113,7 +113,7 @@ impl HIDRawDevice { { log::info!("Detected Legion Go"); let tx = self.composite_tx.clone(); - let driver = lego::LegionController::new(self.info.clone(), tx); + let driver = lego::LegionController::new(self.info.clone(), tx, self.get_id()); driver.run().await?; } else { return Err(format!( diff --git a/src/input/source/hidraw/lego.rs b/src/input/source/hidraw/lego.rs index 17a110f0..0e3bcdde 100644 --- a/src/input/source/hidraw/lego.rs +++ b/src/input/source/hidraw/lego.rs @@ -19,11 +19,20 @@ use crate::{ pub struct LegionController { info: DeviceInfo, composite_tx: broadcast::Sender, + device_id: String, } impl LegionController { - pub fn new(info: DeviceInfo, composite_tx: broadcast::Sender) -> Self { - Self { info, composite_tx } + pub fn new( + info: DeviceInfo, + composite_tx: broadcast::Sender, + device_id: String, + ) -> Self { + Self { + info, + composite_tx, + device_id, + } } pub async fn run(&self) -> Result<(), Box> { @@ -33,6 +42,7 @@ impl LegionController { // Spawn a blocking task to read the events let device_path = path.clone(); + let device_id = self.device_id.clone(); let task = tokio::task::spawn_blocking(move || -> Result<(), Box> { let mut driver = Driver::new(device_path.clone())?; @@ -44,7 +54,10 @@ impl LegionController { if matches!(event.as_capability(), Capability::NotImplemented) { continue; } - tx.send(Command::ProcessEvent(Event::Native(event)))?; + tx.send(Command::ProcessEvent( + device_id.clone(), + Event::Native(event), + ))?; } // Polling interval is about 4ms so we can sleep a little diff --git a/src/input/source/hidraw/steam_deck.rs b/src/input/source/hidraw/steam_deck.rs index a8228a9c..3976149c 100644 --- a/src/input/source/hidraw/steam_deck.rs +++ b/src/input/source/hidraw/steam_deck.rs @@ -22,11 +22,20 @@ pub const PID: u16 = 0x1205; pub struct DeckController { info: DeviceInfo, composite_tx: broadcast::Sender, + device_id: String, } impl DeckController { - pub fn new(info: DeviceInfo, composite_tx: broadcast::Sender) -> Self { - Self { info, composite_tx } + pub fn new( + info: DeviceInfo, + composite_tx: broadcast::Sender, + device_id: String, + ) -> Self { + Self { + info, + composite_tx, + device_id, + } } pub async fn run(&self) -> Result<(), Box> { @@ -36,6 +45,7 @@ impl DeckController { // Spawn a blocking task to read the events let device_path = path.clone(); + let device_id = self.device_id.clone(); let task = tokio::task::spawn_blocking(move || -> Result<(), Box> { let mut driver = Driver::new(device_path.clone())?; @@ -47,7 +57,10 @@ impl DeckController { if matches!(event.as_capability(), Capability::NotImplemented) { continue; } - tx.send(Command::ProcessEvent(Event::Native(event)))?; + tx.send(Command::ProcessEvent( + device_id.clone(), + Event::Native(event), + ))?; } // Polling interval is about 4ms so we can sleep a little