Skip to content

Commit

Permalink
fix(CompositeDevice): add dbus endpoints for setting profile
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Mar 30, 2024
1 parent 9e7c5e6 commit 1dc1cbc
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 19 deletions.
99 changes: 87 additions & 12 deletions src/input/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ impl FromStr for Capability {

impl From<CapabilityConfig> 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() {
Expand All @@ -68,6 +68,8 @@ impl From<CapabilityConfig> 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() {
Expand All @@ -81,8 +83,47 @@ impl From<CapabilityConfig> 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!();
}

Expand Down Expand Up @@ -170,6 +211,25 @@ impl fmt::Display for MouseButton {
}
}

impl FromStr for MouseButton {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
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 {
Expand Down Expand Up @@ -356,6 +416,21 @@ impl fmt::Display for GamepadAxis {
}
}

impl FromStr for GamepadAxis {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
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,
Expand Down
71 changes: 66 additions & 5 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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
Expand Down Expand Up @@ -59,6 +59,8 @@ pub enum Command {
SourceDeviceAdded(SourceDeviceInfo),
SourceDeviceStopped(String),
SourceDeviceRemoved(String),
GetProfileName(mpsc::Sender<String>),
LoadProfilePath(String, mpsc::Sender<Result<(), String>>),
Stop,
}

Expand All @@ -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<String> {
let (sender, mut receiver) = mpsc::channel::<String>(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::<Result<(), String>>(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<Vec<String>> {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -864,6 +919,7 @@ impl CompositeDevice {
}
}

log::trace!("No translation mapping found for event: {:?}", source_cap);
Ok(vec![event.clone()])
}

Expand Down Expand Up @@ -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<dyn Error>> {
// 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();
Expand All @@ -1014,6 +1074,7 @@ impl CompositeDevice {
config_map.push(mapping.clone());
}

log::debug!("Successfully loaded device profile: {}", profile.name);
Ok(())
}
}
9 changes: 7 additions & 2 deletions src/input/event/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 -> ...
Expand Down

0 comments on commit 1dc1cbc

Please sign in to comment.