Skip to content

Commit

Permalink
fix(TargetDevice): add method for target devices to query composite d…
Browse files Browse the repository at this point in the history
…evice config
  • Loading branch information
ShadowApex committed Nov 22, 2024
1 parent b399900 commit 1ac2fd6
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 25 deletions.
32 changes: 32 additions & 0 deletions src/input/composite_device/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -97,6 +98,26 @@ impl CompositeDeviceClient {
Err(ClientError::ChannelClosed)
}

/// Get the [CompositeDeviceConfig] from the [CompositeDevice]
pub async fn get_config(&self) -> Result<CompositeDeviceConfig, ClientError> {
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<CompositeDeviceConfig, ClientError> {
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<HashSet<Capability>, ClientError> {
let (tx, mut rx) = channel(1);
Expand Down Expand Up @@ -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<Vec<String>, 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<Vec<String>, ClientError> {
let (tx, mut rx) = channel(1);
Expand Down
2 changes: 2 additions & 0 deletions src/input/composite_device/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
use tokio::sync::mpsc;

use crate::{
config::CompositeDeviceConfig,
input::{
capability::Capability,
event::{native::NativeEvent, Event},
Expand All @@ -20,6 +21,7 @@ use super::InterceptMode;
#[derive(Debug, Clone)]
pub enum CompositeCommand {
AttachTargetDevices(HashMap<String, TargetDeviceClient>),
GetConfig(mpsc::Sender<CompositeDeviceConfig>),
GetCapabilities(mpsc::Sender<HashSet<Capability>>),
GetDBusDevicePaths(mpsc::Sender<Vec<String>>),
GetInterceptMode(mpsc::Sender<InterceptMode>),
Expand Down
22 changes: 14 additions & 8 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1900,6 +1906,14 @@ impl CompositeDevice {
) -> Result<(), Box<dyn Error>> {
// 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(
Expand All @@ -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);
Expand Down
19 changes: 17 additions & 2 deletions src/input/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -87,6 +87,12 @@ impl From<io::Error> for InputError {
}
}

impl From<ClientError> 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 {
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -486,7 +500,8 @@ impl<T: TargetInputDevice + TargetOutputDevice + Send + 'static> TargetDriver<T>
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();
Expand Down
84 changes: 69 additions & 15 deletions src/input/target/steam_deck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<VirtualUSBDevice>,
state: PackedInputDataReport,
/// Steam will send 'SetReport' commands with a report type, so it can fetch
/// a particular result with 'GetReport'
Expand All @@ -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,
Expand All @@ -86,9 +82,9 @@ impl SteamDeckDevice {
}

/// Create the virtual device to emulate
fn create_virtual_device() -> Result<VirtualUSBDevice, Box<dyn Error>> {
fn create_virtual_device(product_id: u16) -> Result<VirtualUSBDevice, Box<dyn Error>> {
// 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")
Expand Down Expand Up @@ -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:?}");

Expand Down Expand Up @@ -768,18 +802,28 @@ 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 {
let reply = self.handle_xfer(xfer);

// 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)?;
}
}

Expand All @@ -797,15 +841,25 @@ 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 {
let reply = self.handle_xfer(xfer);

// 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)?;
}
}

Expand Down

0 comments on commit 1ac2fd6

Please sign in to comment.