diff --git a/src/input/composite_device/client.rs b/src/input/composite_device/client.rs index daca66d..a04e099 100644 --- a/src/input/composite_device/client.rs +++ b/src/input/composite_device/client.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use thiserror::Error; use tokio::sync::mpsc::{channel, error::SendError, Sender}; +use crate::config::CompositeDeviceConfig; use crate::input::event::native::NativeEvent; use crate::input::target::client::TargetDeviceClient; use crate::input::{capability::Capability, event::Event, output_event::OutputEvent}; @@ -97,6 +98,26 @@ impl CompositeDeviceClient { Err(ClientError::ChannelClosed) } + /// Get the [CompositeDeviceConfig] from the [CompositeDevice] + pub async fn get_config(&self) -> Result { + let (tx, mut rx) = channel(1); + self.tx.send(CompositeCommand::GetConfig(tx)).await?; + if let Some(config) = rx.recv().await { + return Ok(config); + } + Err(ClientError::ChannelClosed) + } + + /// Get the [CompositeDeviceConfig] from the [CompositeDevice] (blocking) + pub fn blocking_get_config(&self) -> Result { + let (tx, mut rx) = channel(1); + self.tx.blocking_send(CompositeCommand::GetConfig(tx))?; + if let Some(config) = rx.blocking_recv() { + return Ok(config); + } + Err(ClientError::ChannelClosed) + } + /// Get capabilities from all target devices pub async fn get_target_capabilities(&self) -> Result, ClientError> { let (tx, mut rx) = channel(1); @@ -139,6 +160,17 @@ impl CompositeDeviceClient { Err(ClientError::ChannelClosed) } + /// Get the source device paths of the composite device (blocking) + pub fn blocking_get_source_device_paths(&self) -> Result, ClientError> { + let (tx, mut rx) = channel(1); + self.tx + .blocking_send(CompositeCommand::GetSourceDevicePaths(tx))?; + if let Some(paths) = rx.blocking_recv() { + return Ok(paths); + } + Err(ClientError::ChannelClosed) + } + /// Get the target device paths of the composite device pub async fn get_target_device_paths(&self) -> Result, ClientError> { let (tx, mut rx) = channel(1); diff --git a/src/input/composite_device/command.rs b/src/input/composite_device/command.rs index f073031..4b41663 100644 --- a/src/input/composite_device/command.rs +++ b/src/input/composite_device/command.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use tokio::sync::mpsc; use crate::{ + config::CompositeDeviceConfig, input::{ capability::Capability, event::{native::NativeEvent, Event}, @@ -20,6 +21,7 @@ use super::InterceptMode; #[derive(Debug, Clone)] pub enum CompositeCommand { AttachTargetDevices(HashMap), + GetConfig(mpsc::Sender), GetCapabilities(mpsc::Sender>), GetDBusDevicePaths(mpsc::Sender>), GetInterceptMode(mpsc::Sender), diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index 855ba07..19b733e 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -342,6 +342,12 @@ impl CompositeDevice { log::error!("Failed to send intercept mode: {:?}", e); } } + CompositeCommand::GetConfig(sender) => { + log::error!("sendin config"); + if let Err(e) = sender.send(self.config.clone()).await { + log::error!("Failed to send config: {e:?}"); + } + } CompositeCommand::GetSourceDevicePaths(sender) => { if let Err(e) = sender.send(self.get_source_device_paths()).await { log::error!("Failed to send source device paths: {:?}", e); @@ -1900,6 +1906,14 @@ impl CompositeDevice { ) -> Result<(), Box> { // Keep track of all target devices for (path, target) in targets.into_iter() { + // Query the target device for its capabilities + let caps = match target.get_capabilities().await { + Ok(caps) => caps, + Err(e) => { + return Err(format!("Failed to get target capabilities: {e:?}").into()); + } + }; + log::debug!("Attaching target device: {path}"); if let Err(e) = target.set_composite_device(self.client()).await { return Err( @@ -1911,14 +1925,6 @@ impl CompositeDevice { self.dbus_path.as_ref().unwrap_or(&"".to_string()) ); - // Query the target device for its capabilities - let caps = match target.get_capabilities().await { - Ok(caps) => caps, - Err(e) => { - return Err(format!("Failed to get target capabilities: {e:?}").into()); - } - }; - // Add the target device self.target_devices_queued.remove(&path); self.target_devices.insert(path.clone(), target); diff --git a/src/input/target/mod.rs b/src/input/target/mod.rs index 8b9aefd..fd883f5 100644 --- a/src/input/target/mod.rs +++ b/src/input/target/mod.rs @@ -13,7 +13,7 @@ use crate::dbus::interface::target::gamepad::TargetGamepadInterface; use super::{ capability::Capability, - composite_device::client::CompositeDeviceClient, + composite_device::client::{ClientError, CompositeDeviceClient}, event::native::{NativeEvent, ScheduledNativeEvent}, output_capability::OutputCapability, output_event::OutputEvent, @@ -87,6 +87,12 @@ impl From for InputError { } } +impl From for InputError { + fn from(value: ClientError) -> Self { + InputError::DeviceError(value.to_string()) + } +} + /// Possible errors for a target device client #[derive(Error, Debug)] pub enum OutputError { @@ -285,6 +291,14 @@ pub trait TargetInputDevice { /// that the target device should stop sending input. fn clear_state(&mut self) {} + /// Called when the target device has been attached to a composite device. + fn on_composite_device_attached( + &mut self, + _device: CompositeDeviceClient, + ) -> Result<(), InputError> { + Ok(()) + } + /// Stop the target device fn stop(&mut self) -> Result<(), InputError> { Ok(()) @@ -486,7 +500,8 @@ impl TargetDriver implementation.write_event(event)?; } TargetCommand::SetCompositeDevice(device) => { - *composite_device = Some(device); + *composite_device = Some(device.clone()); + implementation.on_composite_device_attached(device)?; } TargetCommand::GetCapabilities(sender) => { let capabilities = implementation.get_capabilities().unwrap_or_default(); diff --git a/src/input/target/steam_deck.rs b/src/input/target/steam_deck.rs index 5cabaa5..82b8eb3 100644 --- a/src/input/target/steam_deck.rs +++ b/src/input/target/steam_deck.rs @@ -51,7 +51,7 @@ use super::{InputError, OutputError, TargetInputDevice, TargetOutputDevice}; const MIN_FRAME_TIME: Duration = Duration::from_millis(80); pub struct SteamDeckDevice { - device: VirtualUSBDevice, + device: Option, state: PackedInputDataReport, /// Steam will send 'SetReport' commands with a report type, so it can fetch /// a particular result with 'GetReport' @@ -70,12 +70,8 @@ impl SteamDeckDevice { return Err(e.to_string().into()); } - // Create and start the virtual USB device - let mut device = SteamDeckDevice::create_virtual_device()?; - device.start()?; - Ok(Self { - device, + device: None, state: PackedInputDataReport::default(), current_report: ReportType::InputData, lizard_mode_enabled: false, @@ -86,9 +82,9 @@ impl SteamDeckDevice { } /// Create the virtual device to emulate - fn create_virtual_device() -> Result> { + fn create_virtual_device(product_id: u16) -> Result> { // Configuration values can be obtained from a real device with "sudo lsusb -v" - let virtual_device = VirtualUSBDeviceBuilder::new(VID, PID) + let virtual_device = VirtualUSBDeviceBuilder::new(VID, product_id) .class(DeviceClass::UseInterface) .supported_langs(vec![LangId::EnglishUnitedStates]) .manufacturer("Valve Software") @@ -673,6 +669,44 @@ impl SteamDeckDevice { } impl TargetInputDevice for SteamDeckDevice { + /// Start the driver when attached to a composite device. + fn on_composite_device_attached( + &mut self, + composite_device: CompositeDeviceClient, + ) -> Result<(), InputError> { + // Get the configuration from the composite device + let config = composite_device.blocking_get_config()?; + + // Set the product id based on the composite device configuration + let pid = match config.name.as_str() { + "Lenovo Legion Go" => PID, + "ASUS ROG Ally" => PID, + "ASUS ROG Ally X" => PID, + _ => PID, + }; + + // TODO: + // Get the device node paths to the source devices to query for additional + // information like serial number, etc. + //let source_device_paths = composite_device.blocking_get_source_device_paths()?; + //let mut source_device_info = Vec::with_capacity(source_device_paths.len()); + //for source_path in source_device_paths { + // let path = Path::new(source_path.as_str()); + // let name = path.file_name().unwrap_or_default(); + // let base_path = path.parent().unwrap().as_os_str(); + // let device = + // UdevDevice::from_devnode(base_path.to_str().unwrap(), name.to_str().unwrap()); + // source_device_info.push(device); + //} + + // Create and start the virtual USB device + let mut device = SteamDeckDevice::create_virtual_device(pid)?; + device.start()?; + self.device = Some(device); + + Ok(()) + } + fn write_event(&mut self, event: NativeEvent) -> Result<(), InputError> { log::trace!("Received event: {event:?}"); @@ -768,10 +802,16 @@ impl TargetInputDevice for SteamDeckDevice { /// Stop the virtual USB read/write threads fn stop(&mut self) -> Result<(), InputError> { log::debug!("Stopping virtual Deck controller"); - self.device.stop(); - - // Read from the device - let xfer = self.device.blocking_read()?; + let xfer = { + let Some(device) = self.device.as_mut() else { + log::trace!("Deck controller was never started"); + return Ok(()); + }; + device.stop(); + + // Read from the device + device.blocking_read()? + }; // Handle any non-standard transfers if let Some(xfer) = xfer { @@ -779,7 +819,11 @@ impl TargetInputDevice for SteamDeckDevice { // Write to the device if a reply is necessary if let Some(reply) = reply { - self.device.write(reply)?; + let Some(device) = self.device.as_mut() else { + log::trace!("Deck controller was never started"); + return Ok(()); + }; + device.write(reply)?; } } @@ -797,7 +841,13 @@ impl TargetOutputDevice for SteamDeckDevice { self.state.frame = Integer::from_primitive(frame.wrapping_add(1)); // Read from the device - let xfer = self.device.blocking_read()?; + let xfer = { + let Some(device) = self.device.as_mut() else { + log::trace!("Device not started yet"); + return Ok(vec![]); + }; + device.blocking_read()? + }; // Handle any non-standard transfers if let Some(xfer) = xfer { @@ -805,7 +855,11 @@ impl TargetOutputDevice for SteamDeckDevice { // Write to the device if a reply is necessary if let Some(reply) = reply { - self.device.write(reply)?; + let Some(device) = self.device.as_mut() else { + log::trace!("Device not started yet"); + return Ok(vec![]); + }; + device.write(reply)?; } }