diff --git a/Cargo.lock b/Cargo.lock index 888e0f34..1c096e4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,7 +604,7 @@ dependencies = [ [[package]] name = "inputplumber" -version = "0.1.0" +version = "0.1.2" dependencies = [ "evdev", "glob-match", diff --git a/rootfs/usr/share/inputplumber/devices/gamepads.yaml b/rootfs/usr/share/inputplumber/devices/gamepads.yaml index b017a951..0fdfe0b5 100644 --- a/rootfs/usr/share/inputplumber/devices/gamepads.yaml +++ b/rootfs/usr/share/inputplumber/devices/gamepads.yaml @@ -20,9 +20,11 @@ source_devices: evdev: handler: js* +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: xb360 - -# The default output device that the virtual device profile should use if -# 'auto' is selected. -output_device: gamepad diff --git a/rootfs/usr/share/inputplumber/devices/onexplayer_amd.yaml b/rootfs/usr/share/inputplumber/devices/onexplayer_amd.yaml index 0f03a7bc..10443b55 100644 --- a/rootfs/usr/share/inputplumber/devices/onexplayer_amd.yaml +++ b/rootfs/usr/share/inputplumber/devices/onexplayer_amd.yaml @@ -45,9 +45,11 @@ source_devices: name: AT Translated Set 2 keyboard phys_path: isa0060/serio0/input0 +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: oxp2 - -# The default output device that the virtual device profile should use if -# 'auto' is selected. -output_device: gamepad diff --git a/rootfs/usr/share/inputplumber/devices/onexplayer_intel.yaml b/rootfs/usr/share/inputplumber/devices/onexplayer_intel.yaml index 2404fa7a..9a1c61ce 100644 --- a/rootfs/usr/share/inputplumber/devices/onexplayer_intel.yaml +++ b/rootfs/usr/share/inputplumber/devices/onexplayer_intel.yaml @@ -33,8 +33,11 @@ source_devices: name: AT Translated Set 2 keyboard phys_path: isa0060/serio0/input0 +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: oxp1 - -# Default output device -output_device: gamepad diff --git a/rootfs/usr/share/inputplumber/devices/onexplayer_mini_a07.yaml b/rootfs/usr/share/inputplumber/devices/onexplayer_mini_a07.yaml index e5b61af8..d111f007 100644 --- a/rootfs/usr/share/inputplumber/devices/onexplayer_mini_a07.yaml +++ b/rootfs/usr/share/inputplumber/devices/onexplayer_mini_a07.yaml @@ -29,5 +29,11 @@ source_devices: name: AT Translated Set 2 keyboard phys_path: isa0060/serio0/input0 +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: oxp3 diff --git a/rootfs/usr/share/inputplumber/devices/steam_deck.yaml b/rootfs/usr/share/inputplumber/devices/steam_deck.yaml index 258c27e8..ad0a00ba 100644 --- a/rootfs/usr/share/inputplumber/devices/steam_deck.yaml +++ b/rootfs/usr/share/inputplumber/devices/steam_deck.yaml @@ -26,9 +26,11 @@ source_devices: product_id: 0x1205 interface_num: 2 +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: deck - -# The default output device that the virtual device profile should use if -# 'auto' is selected. -output_device: gamepad diff --git a/rootfs/usr/share/inputplumber/devices/xbox360.yaml b/rootfs/usr/share/inputplumber/devices/xbox360.yaml index c71b6632..ef5c1eeb 100644 --- a/rootfs/usr/share/inputplumber/devices/xbox360.yaml +++ b/rootfs/usr/share/inputplumber/devices/xbox360.yaml @@ -20,9 +20,11 @@ source_devices: evdev: name: Xbox 360 Wireless Receiver (XBOX) +# The target input device(s) that the virtual device profile can use +target_devices: + - gamepad + - mouse + - keyboard + # The ID of a device event mapping in the 'event_maps' folder event_map_id: xb360 - -# The default output device that the virtual device profile should use if -# 'auto' is selected. -output_device: gamepad diff --git a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json index 09680060..e811c54d 100644 --- a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json +++ b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json @@ -37,13 +37,18 @@ "description": "The ID of a device event mapping in the 'event_maps' directory", "type": "string" }, - "output_device": { - "description": "Output device to emulate", - "type": "string", - "enum": [ - "gamepad", - "xb360" - ] + "target_devices": { + "description": "Target input device(s) to emulate. Can be one of ['mouse', 'keyboard', 'gamepad', 'xb360'].", + "type": "array", + "items": { + "type": "string", + "enum": [ + "mouse", + "keyboard", + "gamepad", + "xb360" + ] + } } }, "required": [ diff --git a/src/config/mod.rs b/src/config/mod.rs index cf302cb5..a62a698a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -89,9 +89,9 @@ pub struct CompositeDeviceConfig { pub kind: String, pub name: String, pub matches: Vec, - pub source_devices: Vec, pub event_map_id: String, - pub output_device: Option, + pub source_devices: Vec, + pub target_devices: Option>, } impl CompositeDeviceConfig { diff --git a/src/constants.rs b/src/constants.rs index 78c5f544..c8db0ef1 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,2 +1,4 @@ pub const BUS_NAME: &str = "org.shadowblip.InputPlumber"; pub const BUS_PREFIX: &str = "/org/shadowblip/InputPlumber"; +pub const BUS_SOURCES_PREFIX: &str = "/org/shadowblip/InputPlumber/devices/source"; +pub const BUS_TARGETS_PREFIX: &str = "/org/shadowblip/InputPlumber/devices/target"; diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index 3764134e..806e3071 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{collections::HashMap, error::Error}; use tokio::{ sync::{broadcast, mpsc}, @@ -9,23 +9,11 @@ use zbus_macros::dbus_interface; use crate::{ config::CompositeDeviceConfig, - input::{ - event::native::NativeEvent, - source, - target::{gamepad::GenericGamepad, xb360::XBox360Controller}, - }, + input::{event::native::NativeEvent, source}, udev::{hide_device, unhide_device}, }; -use super::{ - capability, - event::{ - dbus::{Action, DBusEvent}, - Event, - }, - source::SourceDevice, - target::TargetDevice, -}; +use super::{capability, event::Event, source::SourceDevice, target::TargetDevice}; const BUFFER_SIZE: usize = 2048; @@ -145,17 +133,30 @@ impl Handle { /// can translate input to any target devices #[derive(Debug)] pub struct CompositeDevice { + /// Connection to DBus conn: Connection, + /// The DBus path this [CompositeDevice] is listening on dbus_path: Option, + /// Mode defining how inputs should be routed intercept_mode: InterceptMode, - config: CompositeDeviceConfig, + /// Transmit channel for sending commands to this composite device tx: broadcast::Sender, + /// Receiver channel for listening for commands rx: broadcast::Receiver, + /// Source devices that this composite device will consume. source_devices: Vec, + /// Physical device path for source devices. E.g. ["/dev/input/event0"] source_device_paths: Vec, + /// Unique identifiers for source devices. E.g. ["evdev://event0"] source_device_ids: Vec, + /// Unique identifiers for running source devices. E.g. ["evdev://event0"] source_devices_used: Vec, - target_devices: Vec>, + /// Map of DBus paths to their respective transmitter channel. + /// E.g. {"/org/shadowblip/InputPlumber/devices/target/gamepad0": } + target_devices: HashMap>, + /// List of paths to [DBusDevice] targets + /// E.g. ["/org/shadowblip/InputPlumber/devices/target/dbus0"] + target_dbus_devices: Vec, } impl CompositeDevice { @@ -218,14 +219,14 @@ impl CompositeDevice { conn, dbus_path: None, intercept_mode: InterceptMode::None, - config, tx, rx, source_devices, source_device_paths, source_device_ids, source_devices_used: Vec::new(), - target_devices: Vec::new(), + target_devices: HashMap::new(), + target_dbus_devices: Vec::new(), }) } @@ -245,61 +246,14 @@ impl CompositeDevice { /// Starts the [CompositeDevice] and listens for events from all source /// devices to translate the events and send them to the appropriate target. - pub async fn run(&mut self) -> Result<(), Box> { + pub async fn run(&mut self, targets: Vec) -> Result<(), Box> { log::debug!("Starting composite device"); - // Keep a list of all the tasks - let mut tasks = JoinSet::new(); - - // Hide all source devices - // TODO: Make this configurable - for source_path in self.source_device_paths.clone() { - log::debug!("Hiding device: {}", source_path); - hide_device(source_path).await?; - } - - // Start listening for events from all source devices - let sources = self.source_devices.drain(..); - for source in sources { - match source { - // If the source device is an event device (i.e. from /dev/input/eventXX), - // then start listening for inputs from that device. - SourceDevice::EventDevice(device) => { - let device_id = device.get_id(); - self.source_devices_used.push(device_id.clone()); - let tx = self.tx.clone(); - tasks.spawn(async move { - if let Err(e) = device.run().await { - log::error!("Failed running event device: {:?}", e); - } - log::debug!("Event device closed"); - if let Err(e) = tx.send(Command::SourceDeviceStopped(device_id)) { - log::error!("Failed to send device stop command: {:?}", e); - } - }); - } - // If the source device is a hidraw device (i.e. /dev/hidraw0), - // then start listening for inputs from that device. - SourceDevice::HIDRawDevice(device) => { - let device_id = device.get_id(); - self.source_devices_used.push(device_id.clone()); - let tx = self.tx.clone(); - tasks.spawn(async move { - if let Err(e) = device.run().await { - log::error!("Failed running hidraw device: {:?}", e); - } - log::debug!("HIDRaw device closed"); - if let Err(e) = tx.send(Command::SourceDeviceStopped(device_id)) { - log::error!("Failed to send device stop command: {:?}", e); - } - }); - } - } - } + // Start all source devices + let mut tasks = self.run_source_devices().await?; - // Create and start all target devices - let targets = self.create_target_devices()?; - self.run_target_devices(targets); + // Start all target devices + self.run_target_devices(targets)?; // Loop and listen for command events log::debug!("CompositeDevice started"); @@ -391,44 +345,74 @@ impl CompositeDevice { self.source_device_paths.clone() } - /// Create target (output) devices to emulate. Returns the created devices - /// as an array. - fn create_target_devices(&self) -> Result, Box> { - log::debug!("Creating target devices"); - let mut target_devices: Vec = Vec::new(); - - // Create a transmitter channel that target devices can use to communitcate - // with the composite device - let tx = self.transmitter(); + /// Start and run the source devices that this composite device will + /// consume. + async fn run_source_devices(&mut self) -> Result, Box> { + // Keep a list of all the tasks + let mut tasks = JoinSet::new(); - // Create the target devices to emulate based on the config - let config = &self.config; - let output_device = &config.output_device; - let device_id = output_device.clone().unwrap_or("null".into()); - let gamepad_device = match device_id.as_str() { - "xb360" => TargetDevice::XBox360(XBox360Controller::new(tx)), - "null" | "none" => TargetDevice::Null, - _ => TargetDevice::GenericGamepad(GenericGamepad::new(tx)), - }; - target_devices.push(gamepad_device); - log::debug!("Created target gamepad"); + // Hide all source devices + // TODO: Make this configurable + for source_path in self.source_device_paths.clone() { + log::debug!("Hiding device: {}", source_path); + hide_device(source_path).await?; + } - // TODO: Create a keyboard device to emulate + // Start listening for events from all source devices + let sources = self.source_devices.drain(..); + for source in sources { + match source { + // If the source device is an event device (i.e. from /dev/input/eventXX), + // then start listening for inputs from that device. + SourceDevice::EventDevice(device) => { + let device_id = device.get_id(); + self.source_devices_used.push(device_id.clone()); + let tx = self.tx.clone(); + tasks.spawn(async move { + if let Err(e) = device.run().await { + log::error!("Failed running event device: {:?}", e); + } + log::debug!("Event device closed"); + if let Err(e) = tx.send(Command::SourceDeviceStopped(device_id)) { + log::error!("Failed to send device stop command: {:?}", e); + } + }); + } - // TODO: Create a mouse device to emulate + // If the source device is a hidraw device (i.e. /dev/hidraw0), + // then start listening for inputs from that device. + SourceDevice::HIDRawDevice(device) => { + let device_id = device.get_id(); + self.source_devices_used.push(device_id.clone()); + let tx = self.tx.clone(); + tasks.spawn(async move { + if let Err(e) = device.run().await { + log::error!("Failed running hidraw device: {:?}", e); + } + log::debug!("HIDRaw device closed"); + if let Err(e) = tx.send(Command::SourceDeviceStopped(device_id)) { + log::error!("Failed to send device stop command: {:?}", e); + } + }); + } + } + } - Ok(target_devices) + Ok(tasks) } /// Start and run the given target devices - fn run_target_devices(&mut self, targets: Vec) { + fn run_target_devices(&mut self, targets: Vec) -> Result<(), Box> { for target in targets { match target { TargetDevice::Null => (), TargetDevice::Keyboard(_) => todo!(), TargetDevice::Mouse(mut mouse) => { let event_tx = mouse.transmitter(); - self.target_devices.push(event_tx); + let Some(path) = mouse.get_dbus_path() else { + return Err("No DBus path found for target device".into()); + }; + self.target_devices.insert(path, event_tx); tokio::spawn(async move { if let Err(e) = mouse.run().await { log::error!("Failed to run target mouse: {:?}", e); @@ -438,7 +422,10 @@ impl CompositeDevice { } TargetDevice::GenericGamepad(mut gamepad) => { let event_tx = gamepad.transmitter(); - self.target_devices.push(event_tx); + let Some(path) = gamepad.get_dbus_path() else { + return Err("No DBus path found for target device".into()); + }; + self.target_devices.insert(path, event_tx); tokio::spawn(async move { if let Err(e) = gamepad.run().await { log::error!("Failed to run target gamepad: {:?}", e); @@ -447,8 +434,24 @@ impl CompositeDevice { }); } TargetDevice::XBox360(_) => todo!(), + TargetDevice::DBus(mut device) => { + let event_tx = device.transmitter(); + let Some(path) = device.get_dbus_path() else { + return Err("No DBus path found for target device".into()); + }; + self.target_devices.insert(path.clone(), event_tx); + self.target_dbus_devices.push(path); + tokio::spawn(async move { + if let Err(e) = device.run().await { + log::error!("Failed to run target dbus device: {:?}", e); + } + log::debug!("Target dbus device closed"); + }); + } } } + + Ok(()) } /// Process a single event from a source device. Events are piped through @@ -471,84 +474,50 @@ impl CompositeDevice { // Process the event depending on the intercept mode let mode = self.intercept_mode.clone(); - match mode { - // Intercept mode NONE will pass all input to the target device(s) - InterceptMode::None => { - self.write_event(event).await?; - } - // Intrecept mode PASS will pass all input to the target device(s) - // EXCEPT for GUIDE button presses - InterceptMode::Pass => { - let capability = event.as_capability(); - match capability { - capability::Capability::Gamepad(gamepad) => match gamepad { - capability::Gamepad::Button(btn) => match btn { - capability::GamepadButton::Guide => { - // Set the intercept mode while the button is pressed - if event.pressed() { - log::debug!("Intercepted guide button press"); - self.set_intercept_mode(InterceptMode::Always); - } - - // Send DBus event - log::debug!("Writing DBus event"); - self.write_dbus_event(event.into()).await?; - } - _ => self.write_event(event).await?, - }, - _ => self.write_event(event).await?, - }, - _ => self.write_event(event).await?, + if matches!(mode, InterceptMode::Pass) { + let capability = event.as_capability(); + if let capability::Capability::Gamepad(gamepad) = capability { + if let capability::Gamepad::Button(btn) = gamepad { + if let capability::GamepadButton::Guide = btn { + // Set the intercept mode while the button is pressed + if event.pressed() { + log::debug!("Intercepted guide button press"); + self.set_intercept_mode(InterceptMode::Always); + } + } } } - // Intercept mode ALWAYS will not send any input to the target - // devices and instead send them as DBus events. - InterceptMode::Always => { - self.write_dbus_event(event.into()).await?; - } } + // Write the event + self.write_event(event).await?; + Ok(()) } /// Writes the given event to the appropriate target device. async fn write_event(&self, event: NativeEvent) -> Result<(), Box> { - for target in &self.target_devices { + for (path, target) in &self.target_devices { + // If the device is in intercept mode, only send events to DBus + // target devices. + let is_dbus_device = self.is_dbus_device(path); + if matches!(self.intercept_mode, InterceptMode::Always) { + if is_dbus_device { + target.send(event.clone()).await?; + } + continue; + } + if is_dbus_device { + continue; + } target.send(event.clone()).await?; } Ok(()) } - /// Writes the given event to DBus - async fn write_dbus_event(&self, event: DBusEvent) -> Result<(), Box> { - // Only send valid events - let valid = !matches!(event.action, Action::None); - if !valid { - return Ok(()); - } - - // DBus events can only be written if there is a DBus path reference. - let Some(path) = self.dbus_path.clone() else { - return Err("No dbus path exists to send events to".into()); - }; - - // Get the object instance at the given path so we can send DBus signal - // updates - let iface_ref = self - .conn - .object_server() - .interface::<_, DBusInterface>(path) - .await?; - - // Send the input event signal - DBusInterface::input_event( - iface_ref.signal_context(), - event.action.as_string(), - event.value, - ) - .await?; - - Ok(()) + /// Returns true if the given DBus path belongs to a [DBusDevice]. + fn is_dbus_device(&self, path: &String) -> bool { + self.target_dbus_devices.contains(path) } /// Sets the intercept mode to the given value diff --git a/src/input/manager.rs b/src/input/manager.rs index b31b8ace..2cb0fc9a 100644 --- a/src/input/manager.rs +++ b/src/input/manager.rs @@ -11,15 +11,22 @@ use zbus_macros::dbus_interface; use crate::config::CompositeDeviceConfig; use crate::constants::BUS_PREFIX; +use crate::constants::BUS_TARGETS_PREFIX; use crate::dmi::data::DMIData; use crate::dmi::get_dmi_data; use crate::input::composite_device; use crate::input::composite_device::CompositeDevice; use crate::input::source; use crate::input::source::hidraw; +use crate::input::target::dbus::DBusDevice; +use crate::input::target::gamepad::GenericGamepad; +use crate::input::target::xb360::XBox360Controller; +use crate::input::target::TargetDevice; use crate::procfs; use crate::watcher; +use super::event::native::NativeEvent; + const DEV_PATH: &str = "/dev"; const INPUT_PATH: &str = "/dev/input"; const BUFFER_SIZE: usize = 1024; @@ -104,6 +111,12 @@ pub struct Manager { /// Mapping of DBus path to its corresponding [CompositeDevice] handle /// E.g. {"/org/shadowblip/InputPlumber/CompositeDevice0": } composite_devices: HashMap, + /// Mapping of target devices to their respective handles + /// E.g. {"/org/shadowblip/InputPlumber/devices/target/dbus0": } + target_devices: HashMap>, + /// Mapping of target devices without a [CompositeDevice] + /// E.g. {"/org/shadowblip/InputPlumber/devices/target/dbus0": } + orphan_target_devices: HashMap, } impl Manager { @@ -123,6 +136,8 @@ impl Manager { source_devices: HashMap::new(), source_devices_used: HashMap::new(), composite_devices: HashMap::new(), + target_devices: HashMap::new(), + orphan_target_devices: HashMap::new(), } } @@ -193,6 +208,76 @@ impl Manager { Ok(()) } + /// Create a [CompositeDevice] from the given configuration + async fn create_composite_device_from_config( + &mut self, + config: CompositeDeviceConfig, + ) -> Result> { + // TODO: Check to see if there's already a composite device running + // but without all its source devices + + // Create a composite device to manage these devices + log::info!("Found matching source devices: {:?}", config.name); + let device = CompositeDevice::new(self.dbus.clone(), config)?; + + // Check to see if there's already a CompositeDevice for + // these source devices. + // TODO: Should we allow multiple composite devices with the same source? + let mut devices_in_use = false; + let source_device_ids = device.get_source_device_ids(); + for (id, path) in self.source_devices_used.iter() { + if !source_device_ids.contains(id) { + continue; + } + log::debug!("Source device '{}' already in use by: {}", id, path); + devices_in_use = true; + break; + } + if devices_in_use { + return Err("Source device(s) are already in use".into()); + } + + Ok(device) + } + + /// Create target input device to emulate and adds it to the DBus object tree. + /// Returns the created device. + async fn create_target_device(&mut self, kind: &str) -> Result> { + log::debug!("Creating target device"); + // Create the target device to emulate based on the kind + let device = match kind { + "gamepad" => TargetDevice::GenericGamepad(GenericGamepad::new(self.dbus.clone())), + "xb360" => TargetDevice::XBox360(XBox360Controller::new()), + "dbus" => TargetDevice::DBus(DBusDevice::new(self.dbus.clone())), + _ => TargetDevice::Null, + }; + log::debug!("Created target input device {kind}"); + + // Add the device to the DBus object tree + let device = match device { + TargetDevice::Null => TargetDevice::Null, + TargetDevice::DBus(mut device) => { + let path = self.next_target_path("dbus")?; + let tx = device.transmitter(); + self.target_devices.insert(path.clone(), tx); + device.listen_on_dbus(path).await?; + TargetDevice::DBus(device) + } + TargetDevice::Keyboard(_) => todo!(), + TargetDevice::Mouse(_) => todo!(), + TargetDevice::GenericGamepad(mut device) => { + let path = self.next_target_path("gamepad")?; + let tx = device.transmitter(); + self.target_devices.insert(path.clone(), tx); + device.listen_on_dbus(path).await?; + TargetDevice::GenericGamepad(device) + } + TargetDevice::XBox360(_) => todo!(), + }; + + Ok(device) + } + /// Called when a composite device stops running async fn on_composite_device_stopped(&mut self, path: String) -> Result<(), Box> { log::debug!("Removing composite device: {}", path); @@ -246,51 +331,47 @@ impl Manager { continue; } - // TODO: Check to see if there's already a composite device running - // but without all its source devices + // Get the target input devices from the config + let target_devices_config = config.target_devices.clone(); - // Create a composite device to manage these devices - log::info!("Found matching source devices: {:?}", config.name); - let mut device = CompositeDevice::new(self.dbus.clone(), config)?; - - // Check to see if there's already a CompositeDevice for - // these source devices. - // TODO: Should we allow multiple composite devices with the same source? - let mut devices_in_use = false; - let source_device_ids = device.get_source_device_ids(); - for (id, path) in self.source_devices_used.iter() { - if !source_device_ids.contains(id) { - continue; - } - log::debug!("Source device '{}' already in use by: {}", id, path); - devices_in_use = true; - break; - } - if devices_in_use { - continue; - } + // Create the composite deivce + log::info!("Creating composite device"); + let mut device = self.create_composite_device_from_config(config).await?; // Generate the DBus tree path for this composite device let path = self.next_composite_dbus_path(); // Keep track of the source devices that this composite device is // using. + let source_device_ids = device.get_source_device_ids(); for id in source_device_ids { self.source_devices_used.insert(id, path.clone()); } // Create a DBus interface for the device - log::info!("Creating composite device"); device.listen_on_dbus(path.clone()).await?; // Get a handle to the device let handle = device.handle(); + // Create a DBus target device + let mut targets = Vec::new(); + let dbus_device = self.create_target_device("dbus").await?; + targets.push(dbus_device); + + // Create target devices based on the configuration + if let Some(target_devices_config) = target_devices_config { + for kind in target_devices_config { + let device = self.create_target_device(kind.as_str()).await?; + targets.push(device); + } + } + // Run the device let dbus_path = path.clone(); let tx = self.tx.clone(); tokio::spawn(async move { - if let Err(e) = device.run().await { + if let Err(e) = device.run(targets).await { log::error!("Error running device: {:?}", e); } log::debug!("Composite device stopped running: {:?}", dbus_path); @@ -415,6 +496,23 @@ impl Manager { Ok(()) } + /// Returns the next available target device dbus path + fn next_target_path(&self, kind: &str) -> Result> { + let max = 2048; + let mut i = 0; + loop { + if i > max { + return Err("Devices exceeded maximum of 2048".into()); + } + let path = format!("{BUS_TARGETS_PREFIX}/{kind}{i}"); + if self.target_devices.get(&path).is_some() { + i += 1; + continue; + } + return Ok(path); + } + } + /// Returns the next available composite device dbus path fn next_composite_dbus_path(&self) -> String { let max = 2048; diff --git a/src/input/target/dbus.rs b/src/input/target/dbus.rs new file mode 100644 index 00000000..ba3fef32 --- /dev/null +++ b/src/input/target/dbus.rs @@ -0,0 +1,139 @@ +use std::error::Error; + +use tokio::sync::{broadcast, mpsc}; +use zbus::{fdo, Connection, SignalContext}; +use zbus_macros::dbus_interface; + +use crate::input::{ + composite_device, + event::{ + dbus::{Action, DBusEvent}, + native::NativeEvent, + }, +}; + +const BUFFER_SIZE: usize = 2048; + +/// The [DBusInterface] provides a DBus interface that can be exposed for managing +/// a [DBusDevice]. +pub struct DBusInterface {} + +impl DBusInterface { + fn new() -> DBusInterface { + DBusInterface {} + } +} + +#[dbus_interface(name = "org.shadowblip.Input.DBusDevice")] +impl DBusInterface { + /// Name of the DBus device + #[dbus_interface(property)] + async fn name(&self) -> fdo::Result { + Ok("DBusDevice".into()) + } + + /// Emitted when an input event occurs + #[dbus_interface(signal)] + async fn input_event(ctxt: &SignalContext<'_>, event: String, value: f64) -> zbus::Result<()>; +} + +/// The [DBusDevice] is a virtual input device that can emit input events. It +/// is primarily used when a [CompositeDevice] is using input interception to +/// divert inputs to an overlay over DBus. +#[derive(Debug)] +pub struct DBusDevice { + conn: Connection, + dbus_path: Option, + tx: mpsc::Sender, + rx: mpsc::Receiver, + _composite_tx: Option>, +} + +impl DBusDevice { + // Create a new [DBusDevice] instance. + pub fn new(conn: Connection) -> Self { + let (tx, rx) = mpsc::channel(BUFFER_SIZE); + Self { + conn, + dbus_path: None, + _composite_tx: None, + tx, + rx, + } + } + + /// Returns the DBus path of this device + pub fn get_dbus_path(&self) -> Option { + self.dbus_path.clone() + } + + /// Returns a transmitter channel that can be used to send events to this device + pub fn transmitter(&self) -> mpsc::Sender { + self.tx.clone() + } + + /// Creates a new instance of the dbus device interface on DBus. + pub async fn listen_on_dbus(&mut self, path: String) -> Result<(), Box> { + let conn = self.conn.clone(); + self.dbus_path = Some(path.clone()); + tokio::spawn(async move { + let iface = DBusInterface::new(); + if let Err(e) = conn.object_server().at(path, iface).await { + log::error!("Failed to setup DBus interface for DBus device: {:?}", e); + } + }); + Ok(()) + } + + /// Creates and runs the target device + pub async fn run(&mut self) -> Result<(), Box> { + log::debug!("Creating virtual dbus device"); + + // Listen for send events + log::debug!("Started listening for events to send"); + while let Some(event) = self.rx.recv().await { + //log::debug!("Got event to emit: {:?}", event); + let dbus_event = self.translate_event(event); + self.write_dbus_event(dbus_event).await?; + } + + Ok(()) + } + + /// Translate the given native event into a dbus event + fn translate_event(&self, event: NativeEvent) -> DBusEvent { + event.into() + } + + /// Writes the given event to DBus + async fn write_dbus_event(&self, event: DBusEvent) -> Result<(), Box> { + // Only send valid events + let valid = !matches!(event.action, Action::None); + if !valid { + return Ok(()); + } + + // DBus events can only be written if there is a DBus path reference. + let Some(path) = self.dbus_path.clone() else { + return Err("No dbus path exists to send events to".into()); + }; + + // Get the object instance at the given path so we can send DBus signal + // updates + let iface_ref = self + .conn + .object_server() + .interface::<_, DBusInterface>(path) + .await?; + + // Send the input event signal + DBusInterface::input_event( + iface_ref.signal_context(), + event.action.as_string(), + event.value, + ) + .await?; + + Ok(()) + } +} diff --git a/src/input/target/gamepad.rs b/src/input/target/gamepad.rs index bb1193e8..41de5678 100644 --- a/src/input/target/gamepad.rs +++ b/src/input/target/gamepad.rs @@ -8,6 +8,8 @@ use evdev::{ SynchronizationEvent, UinputAbsSetup, }; use tokio::sync::{broadcast, mpsc}; +use zbus::{fdo, Connection}; +use zbus_macros::dbus_interface; use crate::input::{ capability::Capability, @@ -15,20 +17,43 @@ use crate::input::{ event::{evdev::EvdevEvent, native::NativeEvent}, }; +/// The [DBusInterface] provides a DBus interface that can be exposed for managing +/// a [GenericGamepad]. +pub struct DBusInterface {} + +impl DBusInterface { + fn new() -> DBusInterface { + DBusInterface {} + } +} + +#[dbus_interface(name = "org.shadowblip.Input.Gamepad")] +impl DBusInterface { + /// Name of the DBus device + #[dbus_interface(property)] + async fn name(&self) -> fdo::Result { + Ok("Gamepad".into()) + } +} + #[derive(Debug)] pub struct GenericGamepad { + conn: Connection, + dbus_path: Option, tx: mpsc::Sender, rx: mpsc::Receiver, - _composite_tx: broadcast::Sender, + _composite_tx: Option>, } impl GenericGamepad { - pub fn new(composite_tx: broadcast::Sender) -> Self { + pub fn new(conn: Connection) -> Self { let (tx, rx) = mpsc::channel(1024); Self { - _composite_tx: composite_tx, + conn, + dbus_path: None, tx, rx, + _composite_tx: None, } } @@ -43,11 +68,29 @@ impl GenericGamepad { ] } + /// Returns the DBus path of this device + pub fn get_dbus_path(&self) -> Option { + self.dbus_path.clone() + } + /// Returns a transmitter channel that can be used to send events to this device pub fn transmitter(&self) -> mpsc::Sender { self.tx.clone() } + /// Creates a new instance of the dbus device interface on DBus. + pub async fn listen_on_dbus(&mut self, path: String) -> Result<(), Box> { + let conn = self.conn.clone(); + self.dbus_path = Some(path.clone()); + tokio::spawn(async move { + let iface = DBusInterface::new(); + if let Err(e) = conn.object_server().at(path, iface).await { + log::error!("Failed to setup DBus interface for Gamepad device: {:?}", e); + } + }); + Ok(()) + } + /// Creates and runs the target device pub async fn run(&mut self) -> Result<(), Box> { log::debug!("Creating virtual gamepad"); diff --git a/src/input/target/keyboard.rs b/src/input/target/keyboard.rs index fb30493e..f1061f57 100644 --- a/src/input/target/keyboard.rs +++ b/src/input/target/keyboard.rs @@ -14,6 +14,7 @@ use crate::input::{ #[derive(Debug)] pub struct KeyboardDevice { + dbus_path: Option, tx: mpsc::Sender, rx: mpsc::Receiver, _composite_tx: broadcast::Sender, @@ -23,12 +24,18 @@ impl KeyboardDevice { pub fn new(composite_tx: broadcast::Sender) -> Self { let (tx, rx) = mpsc::channel(1024); Self { + dbus_path: None, _composite_tx: composite_tx, tx, rx, } } + /// Returns the DBus path of this device + pub fn get_dbus_path(&self) -> Option { + self.dbus_path.clone() + } + /// Creates and runs the target device pub async fn run(&mut self) -> Result<(), Box> { log::debug!("Creating virtual keyboard"); diff --git a/src/input/target/mod.rs b/src/input/target/mod.rs index 072af6dc..d643fe40 100644 --- a/src/input/target/mod.rs +++ b/src/input/target/mod.rs @@ -1,3 +1,4 @@ +pub mod dbus; pub mod gamepad; pub mod keyboard; pub mod mouse; @@ -7,6 +8,7 @@ pub mod xb360; #[derive(Debug)] pub enum TargetDevice { Null, + DBus(dbus::DBusDevice), Keyboard(keyboard::KeyboardDevice), Mouse(mouse::MouseDevice), GenericGamepad(gamepad::GenericGamepad), diff --git a/src/input/target/mouse.rs b/src/input/target/mouse.rs index 255dcc49..b798c83e 100644 --- a/src/input/target/mouse.rs +++ b/src/input/target/mouse.rs @@ -46,6 +46,7 @@ impl DBusInterface { #[derive(Debug)] pub struct MouseDevice { + dbus_path: Option, tx: mpsc::Sender, rx: mpsc::Receiver, _composite_tx: broadcast::Sender, @@ -55,12 +56,18 @@ impl MouseDevice { pub fn new(composite_tx: broadcast::Sender) -> Self { let (tx, rx) = mpsc::channel(1024); Self { + dbus_path: None, _composite_tx: composite_tx, tx, rx, } } + /// Returns the DBus path of this device + pub fn get_dbus_path(&self) -> Option { + self.dbus_path.clone() + } + /// Returns a transmitter channel that can be used to send events to this device pub fn transmitter(&self) -> mpsc::Sender { self.tx.clone() diff --git a/src/input/target/xb360.rs b/src/input/target/xb360.rs index e1dce9db..89c32237 100644 --- a/src/input/target/xb360.rs +++ b/src/input/target/xb360.rs @@ -10,13 +10,13 @@ use crate::input::composite_device; #[derive(Debug)] pub struct XBox360Controller { - _composite_tx: broadcast::Sender, + _composite_tx: Option>, } impl XBox360Controller { - pub fn new(composite_tx: broadcast::Sender) -> Self { + pub fn new() -> Self { Self { - _composite_tx: composite_tx, + _composite_tx: None, } }