From 1dc1cbc1f4a385084aa4b3c4ed8a46a98dd4d53e Mon Sep 17 00:00:00 2001 From: William Edwards Date: Sat, 30 Mar 2024 13:59:36 -0700 Subject: [PATCH] fix(CompositeDevice): add dbus endpoints for setting profile --- src/input/capability.rs | 99 +++++++++++++++++++++++++++---- src/input/composite_device/mod.rs | 71 ++++++++++++++++++++-- src/input/event/value.rs | 9 ++- 3 files changed, 160 insertions(+), 19 deletions(-) diff --git a/src/input/capability.rs b/src/input/capability.rs index 1687e9f..a2eb509 100644 --- a/src/input/capability.rs +++ b/src/input/capability.rs @@ -45,20 +45,20 @@ impl FromStr for Capability { impl From for Capability { fn from(value: CapabilityConfig) -> Self { - if let Some(keyboard_string) = value.keyboard.as_ref() { - let key = Keyboard::from_str(keyboard_string.as_str()); - if key.is_err() { - log::error!("Invalid keyboard string: {keyboard_string}"); - return Capability::NotImplemented; - } - let key = key.unwrap(); - return Capability::Keyboard(key); - } + // Gamepad if let Some(gamepad) = value.gamepad.as_ref() { - if let Some(axis_string) = gamepad.axis.clone() { - unimplemented!(); // We might need to look at the struct for this to track - // positive vs negative values. + // Axis + if let Some(axis_config) = gamepad.axis.clone() { + let axis = GamepadAxis::from_str(&axis_config.name); + if axis.is_err() { + log::error!("Invalid or unimplemented axis: {}", axis_config.name); + return Capability::NotImplemented; + } + let axis = axis.unwrap(); + return Capability::Gamepad(Gamepad::Axis(axis)); } + + // Button if let Some(button_string) = gamepad.button.clone() { let button = GamepadButton::from_str(&button_string); if button.is_err() { @@ -68,6 +68,8 @@ impl From for Capability { let button = button.unwrap(); return Capability::Gamepad(Gamepad::Button(button)); } + + // Trigger if let Some(trigger_capability) = gamepad.trigger.clone() { let trigger = GamepadTrigger::from_str(&trigger_capability.name); if trigger.is_err() { @@ -81,8 +83,47 @@ impl From for Capability { let trigger = trigger.unwrap(); return Capability::Gamepad(Gamepad::Trigger(trigger)); } + + // Gyro + if let Some(gyro_capability) = gamepad.gyro.clone() { + unimplemented!(); + } + + // TODO: Accelerometer + } + + // Keyboard + if let Some(keyboard_string) = value.keyboard.as_ref() { + let key = Keyboard::from_str(keyboard_string.as_str()); + if key.is_err() { + log::error!("Invalid keyboard string: {keyboard_string}"); + return Capability::NotImplemented; + } + let key = key.unwrap(); + return Capability::Keyboard(key); } + + // Mouse if let Some(mouse) = value.mouse.as_ref() { + // Motion + if mouse.motion.is_some() { + return Capability::Mouse(Mouse::Motion); + } + + // Button + if let Some(button_string) = mouse.button.clone() { + let button = MouseButton::from_str(&button_string); + if button.is_err() { + log::error!("Invalid or unimplemented button: {button_string}"); + return Capability::NotImplemented; + } + let button = button.unwrap(); + return Capability::Mouse(Mouse::Button(button)); + } + } + + // DBus + if let Some(dbus) = value.dbus.as_ref() { unimplemented!(); } @@ -170,6 +211,25 @@ impl fmt::Display for MouseButton { } } +impl FromStr for MouseButton { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "Left" => Ok(MouseButton::Left), + "Right" => Ok(MouseButton::Right), + "Middle" => Ok(MouseButton::Middle), + "WheelUp" => Ok(MouseButton::WheelUp), + "WheelDown" => Ok(MouseButton::WheelDown), + "WheelLeft" => Ok(MouseButton::WheelLeft), + "WheelRight" => Ok(MouseButton::WheelRight), + "Extra1" => Ok(MouseButton::Extra1), + "Extra2" => Ok(MouseButton::Extra2), + _ => Err(()), + } + } +} + /// Gamepad Buttons typically use binary input that represents button presses #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum GamepadButton { @@ -356,6 +416,21 @@ impl fmt::Display for GamepadAxis { } } +impl FromStr for GamepadAxis { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "LeftStick" => Ok(GamepadAxis::LeftStick), + "RightStick" => Ok(GamepadAxis::RightStick), + "Hat1" => Ok(GamepadAxis::Hat1), + "Hat2" => Ok(GamepadAxis::Hat2), + "Hat3" => Ok(GamepadAxis::Hat3), + _ => Err(()), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum GamepadTrigger { LeftTrigger, diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index dd19a9d..bc59a87 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -12,8 +12,7 @@ use zbus_macros::dbus_interface; use crate::{ config::{ - CapabilityConfig, CapabilityMap, CapabilityMapping, CompositeDeviceConfig, DeviceProfile, - ProfileMapping, + CapabilityMap, CapabilityMapping, CompositeDeviceConfig, DeviceProfile, ProfileMapping, }, input::{ capability::{Capability, Gamepad, GamepadButton, Mouse}, @@ -29,6 +28,7 @@ use crate::{ udev::{hide_device, unhide_device}, }; +/// Size of the command channel buffer for processing input events and commands. const BUFFER_SIZE: usize = 2048; /// The [InterceptMode] defines whether or not inputs should be routed over @@ -59,6 +59,8 @@ pub enum Command { SourceDeviceAdded(SourceDeviceInfo), SourceDeviceStopped(String), SourceDeviceRemoved(String), + GetProfileName(mpsc::Sender), + LoadProfilePath(String, mpsc::Sender>), Stop, } @@ -83,6 +85,43 @@ impl DBusInterface { Ok("CompositeDevice".into()) } + /// Name of the currently loaded profile + #[dbus_interface(property)] + async fn profile_name(&self) -> fdo::Result { + let (sender, mut receiver) = mpsc::channel::(1); + self.tx + .send(Command::GetProfileName(sender)) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + let Some(profile_name) = receiver.recv().await else { + return Ok("".to_string()); + }; + + Ok(profile_name) + } + + /// Load the device profile from the given path + async fn load_profile_path(&self, path: String) -> fdo::Result<()> { + let (sender, mut receiver) = mpsc::channel::>(1); + self.tx + .send(Command::LoadProfilePath(path, sender)) + .map_err(|e| fdo::Error::Failed(e.to_string()))?; + + let Some(result) = receiver.recv().await else { + return Err(fdo::Error::Failed( + "No response from CompositeDevice".to_string(), + )); + }; + + if let Err(e) = result { + return Err(fdo::Error::Failed(format!( + "Failed to load profile: {:?}", + e + ))); + } + + Ok(()) + } + /// List of capabilities that all source devices implement #[dbus_interface(property)] async fn capabilities(&self) -> fdo::Result> { @@ -411,6 +450,22 @@ impl CompositeDevice { break; } } + Command::GetProfileName(sender) => { + let profile_name = self.device_profile.clone().unwrap_or_default(); + if let Err(e) = sender.send(profile_name).await { + log::error!("Failed to send profile name: {:?}", e); + } + } + Command::LoadProfilePath(path, sender) => { + log::info!("Loading profile from path: {path}"); + let result = match self.load_device_profile_from_path(path.clone()) { + Ok(_) => Ok(()), + Err(e) => Err(e.to_string()), + }; + if let Err(e) = sender.send(result).await { + log::error!("Failed to send load profile result: {:?}", e); + } + } Command::Stop => { log::debug!("Stopping CompositeDevice"); break; @@ -826,7 +881,7 @@ impl CompositeDevice { Err(err) => { match err { TranslationError::NotImplemented => { - log::trace!( + log::warn!( "Translation not implemented for profile mapping '{}': {:?} -> {:?}", mapping.name, source_cap, @@ -864,6 +919,7 @@ impl CompositeDevice { } } + log::trace!("No translation mapping found for event: {:?}", source_cap); Ok(vec![event.clone()]) } @@ -978,17 +1034,21 @@ impl CompositeDevice { /// Load the given device profile from the given path pub fn load_device_profile_from_path(&mut self, path: String) -> Result<(), Box> { - // Remove all outdated capabily mappings. + log::debug!("Loading device profile from path: {path}"); + // Remove all outdated capability mappings. + log::debug!("Clearing old device profile mappings"); self.device_profile_map.clear(); self.device_profile_config_map.clear(); // Load and parse the device profile let profile = DeviceProfile::from_yaml_file(path.clone())?; - self.device_profile = Some(profile.name); + self.device_profile = Some(profile.name.clone()); // Loop through every mapping in the profile, extract the source and target events, // and map them into our profile map. for mapping in profile.mapping.iter() { + log::debug!("Loading mapping from profile: {}", mapping.name); + // Convert the source event configuration in the mapping into a // capability that can be easily matched on during event translation let source_event_cap: Capability = mapping.source_event.clone().into(); @@ -1014,6 +1074,7 @@ impl CompositeDevice { config_map.push(mapping.clone()); } + log::debug!("Successfully loaded device profile: {}", profile.name); Ok(()) } } diff --git a/src/input/event/value.rs b/src/input/event/value.rs index c5f7e66..b58b869 100644 --- a/src/input/event/value.rs +++ b/src/input/event/value.rs @@ -121,8 +121,13 @@ impl InputValue { // Axis -> Gyro Gamepad::Gyro => Err(TranslationError::NotImplemented), }, - Capability::Mouse(_) => Err(TranslationError::NotImplemented), - Capability::Keyboard(_) => Err(TranslationError::NotImplemented), + // Axis -> Mouse + Capability::Mouse(mouse) => match mouse { + Mouse::Motion => Err(TranslationError::NotImplemented), + Mouse::Button(_) => self.translate_axis_to_button(source_config), + }, + // Axis -> Keyboard + Capability::Keyboard(_) => self.translate_axis_to_button(source_config), } } // Trigger -> ...