diff --git a/src/input/composite_device/mod.rs b/src/input/composite_device/mod.rs index 7c9d66e..dd19a9d 100644 --- a/src/input/composite_device/mod.rs +++ b/src/input/composite_device/mod.rs @@ -17,10 +17,13 @@ use crate::{ }, input::{ capability::{Capability, Gamepad, GamepadButton, Mouse}, - event::{native::NativeEvent, value::InputValue, Event}, + event::{ + native::NativeEvent, + value::{InputValue, TranslationError}, + Event, + }, manager::SourceDeviceInfo, - source, - source::SourceDevice, + source::{self, SourceDevice}, target::TargetCommand, }, udev::{hide_device, unhide_device}, @@ -812,12 +815,43 @@ impl CompositeDevice { for target_event in mapping.target_events.iter() { // TODO: We can cache this conversion for faster translation let target_cap: Capability = target_event.clone().into(); - let value = event.get_value().translate( + let result = event.get_value().translate( &source_cap, &mapping.source_event, &target_cap, target_event, ); + let value = match result { + Ok(v) => v, + Err(err) => { + match err { + TranslationError::NotImplemented => { + log::trace!( + "Translation not implemented for profile mapping '{}': {:?} -> {:?}", + mapping.name, + source_cap, + target_cap, + ); + continue; + } + TranslationError::ImpossibleTranslation(msg) => { + log::warn!( + "Impossible translation for profile mapping '{}': {msg}", + mapping.name + ); + continue; + } + TranslationError::InvalidSourceConfig(msg) => { + log::warn!("Invalid source event config in profile mapping '{}': {msg}", mapping.name); + continue; + } + TranslationError::InvalidTargetConfig(msg) => { + log::warn!("Invalid target event config in profile mapping '{}': {msg}", mapping.name); + continue; + } + } + } + }; if matches!(value, InputValue::None) { continue; } diff --git a/src/input/event/value.rs b/src/input/event/value.rs index 1e29a5b..c5f7e66 100644 --- a/src/input/event/value.rs +++ b/src/input/event/value.rs @@ -3,6 +3,20 @@ use crate::{ input::capability::{Capability, Gamepad, Mouse}, }; +/// Possible errors while doing input value translation +pub enum TranslationError { + /// Translation not yet implemented + NotImplemented, + /// Impossible translation + ImpossibleTranslation(String), + /// Unable to translate value due to invalid or missing capability config + /// in source config. + InvalidSourceConfig(String), + /// Unable to translate value due to invalid or missing capability config + /// in target config. + InvalidTargetConfig(String), +} + /// InputValue represents different ways to represent a value from an input event. #[derive(Debug, Clone)] pub enum InputValue { @@ -39,232 +53,263 @@ impl InputValue { source_config: &CapabilityConfig, target_cap: &Capability, target_config: &CapabilityConfig, - ) -> InputValue { + ) -> Result { match source_cap { // None values cannot be translated - Capability::None => InputValue::None, + Capability::None => Err(TranslationError::ImpossibleTranslation( + "None events cannot be translated".to_string(), + )), // NotImplemented values cannot be translated - Capability::NotImplemented => InputValue::None, + Capability::NotImplemented => Ok(InputValue::None), // Sync values can only be translated to '0' - Capability::Sync => InputValue::Bool(false), + Capability::Sync => Ok(InputValue::Bool(false)), // Gamepad -> ... Capability::Gamepad(gamepad) => { match gamepad { // Gamepad Button -> ... - Gamepad::Button(_) => match target_cap { - // Gamepad Button -> None - Capability::None => InputValue::None, - // Gamepad Button -> NotImplemented - Capability::NotImplemented => InputValue::None, - // Gamepad Button -> Sync - Capability::Sync => InputValue::Bool(false), - // Gamepad Button -> Gamepad - Capability::Gamepad(gamepad) => match gamepad { - // Gamepad Button -> Gamepad Button - Gamepad::Button(_) => self.clone(), - // Gamepad Button -> Axis - Gamepad::Axis(_) => { - // Use provided mapping to determine axis values - if let Some(gamepad_config) = target_config.gamepad.as_ref() { - if let Some(axis) = gamepad_config.axis.as_ref() { - if let Some(direction) = axis.direction.as_ref() { - // Get the button value - let button_value = match self { - InputValue::Bool(v) => { - if *v { - 1.0 - } else { - 0.0 - } - } - InputValue::Float(v) => *v, - _ => 0.0, - }; - - // Create a vector2 value based on axis direction - match direction.as_str() { - // Left should be a negative value - "left" => InputValue::Vector2 { - x: Some(-button_value), - y: None, - }, - // Right should be a positive value - "right" => InputValue::Vector2 { - x: Some(button_value), - y: None, - }, - // Up should be a negative value - "up" => InputValue::Vector2 { - x: None, - y: Some(-button_value), - }, - // Down should be a positive value - "down" => InputValue::Vector2 { - x: None, - y: Some(button_value), - }, - _ => { - log::warn!( - "Invalid axis direction: {direction}" - ); - InputValue::None - } - } - } else { - log::warn!("No axis direction defined to translate button to axis"); - InputValue::None - } - } else { - log::warn!("No axis config to translate button to axis"); - InputValue::None - } - } else { - log::warn!("No gamepad config to translate button to axis"); - InputValue::None - } - } - // Gamepad Button -> Trigger - Gamepad::Trigger(_) => todo!(), - // Gamepad Button -> Accelerometer - Gamepad::Accelerometer => todo!(), - // Gamepad Button -> Gyro - Gamepad::Gyro => todo!(), - }, - // Gamepad Button -> Mouse - Capability::Mouse(mouse) => match mouse { - // Gamepad Button -> Mouse Motion - Mouse::Motion => todo!(), - // Gamepad Button -> Mouse Button - Mouse::Button(_) => self.clone(), - }, - // Gamepad Button -> Keyboard - Capability::Keyboard(_) => self.clone(), - }, + Gamepad::Button(_) => { + match target_cap { + // Gamepad Button -> None + Capability::None => Ok(InputValue::None), + // Gamepad Button -> NotImplemented + Capability::NotImplemented => Ok(InputValue::None), + // Gamepad Button -> Sync + Capability::Sync => Ok(InputValue::Bool(false)), + // Gamepad Button -> Gamepad + Capability::Gamepad(gamepad) => match gamepad { + // Gamepad Button -> Gamepad Button + Gamepad::Button(_) => Ok(self.clone()), + // Gamepad Button -> Axis + Gamepad::Axis(_) => self.translate_button_to_axis(target_config), + // Gamepad Button -> Trigger + Gamepad::Trigger(_) => Ok(self.translate_button_to_trigger()), + // Gamepad Button -> Accelerometer + Gamepad::Accelerometer => Err(TranslationError::NotImplemented), + // Gamepad Button -> Gyro + Gamepad::Gyro => Err(TranslationError::NotImplemented), + }, + // Gamepad Button -> Mouse + Capability::Mouse(mouse) => match mouse { + // Gamepad Button -> Mouse Motion + Mouse::Motion => Err(TranslationError::NotImplemented), + // Gamepad Button -> Mouse Button + Mouse::Button(_) => Ok(self.clone()), + }, + // Gamepad Button -> Keyboard + Capability::Keyboard(_) => Ok(self.clone()), + } + } // Axis -> ... Gamepad::Axis(_) => { match target_cap { // Axis -> None - Capability::None => InputValue::None, + Capability::None => Ok(InputValue::None), // Axis -> NotImplemented - Capability::NotImplemented => InputValue::None, + Capability::NotImplemented => Ok(InputValue::None), // Axis -> Sync - Capability::Sync => InputValue::None, + Capability::Sync => Ok(InputValue::None), // Axis -> Gamepad Capability::Gamepad(gamepad) => match gamepad { // Axis -> Button - Gamepad::Button(_) => { - if let Some(gamepad_config) = source_config.gamepad.as_ref() { - if let Some(axis) = gamepad_config.axis.as_ref() { - let threshold = axis.deadzone.unwrap_or(0.3); - if let Some(direction) = axis.direction.as_ref() { - // TODO: Axis input is a special case where we need - // to keep track of the state of the axis and only - // emit events whenever the axis passes or falls - // below the defined threshold - - // Get the axis value - let (x, y) = match self { - InputValue::Vector2 { x, y } => (*x, *y), - InputValue::Vector3 { x, y, z: _ } => (*x, *y), - _ => (None, None), - }; - - match direction.as_str() { - // Left should be a negative value - "left" => { - if let Some(x) = x { - if x <= -threshold { - InputValue::Bool(true) - } else { - InputValue::Bool(false) - } - } else { - InputValue::Bool(false) - } - } - // Right should be a positive value - "right" => { - if let Some(x) = x { - if x >= threshold { - InputValue::Bool(true) - } else { - InputValue::Bool(false) - } - } else { - InputValue::Bool(false) - } - } - // Up should be a negative value - "up" => { - if let Some(y) = y { - if y <= -threshold { - InputValue::Bool(true) - } else { - InputValue::Bool(false) - } - } else { - InputValue::Bool(false) - } - } - // Down should be a positive value - "down" => { - if let Some(y) = y { - if y >= threshold { - InputValue::Bool(true) - } else { - InputValue::Bool(false) - } - } else { - InputValue::Bool(false) - } - } - _ => { - log::warn!( - "Invalid axis direction: {direction}" - ); - InputValue::None - } - } - } else { - log::warn!("No axis direction defined to translate axis to button"); - InputValue::None - } - } else { - log::warn!( - "No axis config to translate axis to button" - ); - InputValue::None - } - } else { - log::warn!("No gamepad config to translate axis to button"); - InputValue::None - } - } + Gamepad::Button(_) => self.translate_axis_to_button(source_config), // Axis -> Axis - Gamepad::Axis(_) => self.clone(), + Gamepad::Axis(_) => Ok(self.clone()), // Axis -> Trigger - Gamepad::Trigger(_) => todo!(), + Gamepad::Trigger(_) => Err(TranslationError::NotImplemented), // Axis -> Accelerometer - Gamepad::Accelerometer => todo!(), + Gamepad::Accelerometer => Err(TranslationError::NotImplemented), // Axis -> Gyro - Gamepad::Gyro => todo!(), + Gamepad::Gyro => Err(TranslationError::NotImplemented), }, - Capability::Mouse(_) => todo!(), - Capability::Keyboard(_) => todo!(), + Capability::Mouse(_) => Err(TranslationError::NotImplemented), + Capability::Keyboard(_) => Err(TranslationError::NotImplemented), } } // Trigger -> ... - Gamepad::Trigger(_) => todo!(), + Gamepad::Trigger(_) => Err(TranslationError::NotImplemented), // Accelerometer -> ... - Gamepad::Accelerometer => todo!(), + Gamepad::Accelerometer => Err(TranslationError::NotImplemented), // Gyro -> ... - Gamepad::Gyro => todo!(), + Gamepad::Gyro => Err(TranslationError::NotImplemented), } } // Mouse -> ... - Capability::Mouse(_) => todo!(), + Capability::Mouse(_) => Err(TranslationError::NotImplemented), // Keyboard -> ... - Capability::Keyboard(_) => todo!(), + Capability::Keyboard(_) => Err(TranslationError::NotImplemented), + } + } + + /// Translate the button value into an axis value based on the given config + fn translate_button_to_axis( + &self, + target_config: &CapabilityConfig, + ) -> Result { + // Use provided mapping to determine axis values + if let Some(gamepad_config) = target_config.gamepad.as_ref() { + if let Some(axis) = gamepad_config.axis.as_ref() { + if let Some(direction) = axis.direction.as_ref() { + // Get the button value + let button_value = match self { + InputValue::Bool(v) => { + if *v { + 1.0 + } else { + 0.0 + } + } + InputValue::Float(v) => *v, + _ => 0.0, + }; + + // Create a vector2 value based on axis direction + match direction.as_str() { + // Left should be a negative value + "left" => Ok(InputValue::Vector2 { + x: Some(-button_value), + y: None, + }), + // Right should be a positive value + "right" => Ok(InputValue::Vector2 { + x: Some(button_value), + y: None, + }), + // Up should be a negative value + "up" => Ok(InputValue::Vector2 { + x: None, + y: Some(-button_value), + }), + // Down should be a positive value + "down" => Ok(InputValue::Vector2 { + x: None, + y: Some(button_value), + }), + _ => Err(TranslationError::InvalidTargetConfig(format!( + "Invalid axis direction: {direction}" + ))), + } + } else { + Err(TranslationError::InvalidTargetConfig( + "No axis direction defined to translate button to axis".to_string(), + )) + } + } else { + Err(TranslationError::InvalidTargetConfig( + "No axis config to translate button to axis".to_string(), + )) + } + } else { + Err(TranslationError::InvalidTargetConfig( + "No gamepad config to translate button to axis".to_string(), + )) + } + } + + /// Translate the button value into trigger value based on the given config + fn translate_button_to_trigger(&self) -> InputValue { + let button_value = match self { + InputValue::Bool(v) => { + if *v { + 1.0 + } else { + 0.0 + } + } + InputValue::Float(v) => *v, + _ => 0.0, + }; + InputValue::Float(button_value) + } + + /// Translate the axis value into a button value based on the given config. + fn translate_axis_to_button( + &self, + source_config: &CapabilityConfig, + ) -> Result { + if let Some(gamepad_config) = source_config.gamepad.as_ref() { + if let Some(axis) = gamepad_config.axis.as_ref() { + // Get the threshold to consider the axis as 'pressed' or not + let threshold = axis.deadzone.unwrap_or(0.3); + if let Some(direction) = axis.direction.as_ref() { + // TODO: Axis input is a special case where we need + // to keep track of the state of the axis and only + // emit events whenever the axis passes or falls + // below the defined threshold + + // Get the axis value + let (x, y) = match self { + InputValue::Vector2 { x, y } => (*x, *y), + InputValue::Vector3 { x, y, z: _ } => (*x, *y), + _ => (None, None), + }; + + match direction.as_str() { + // Left should be a negative value + "left" => { + if let Some(x) = x { + if x <= -threshold { + Ok(InputValue::Bool(true)) + } else { + Ok(InputValue::Bool(false)) + } + } else { + Ok(InputValue::Bool(false)) + } + } + // Right should be a positive value + "right" => { + if let Some(x) = x { + if x >= threshold { + Ok(InputValue::Bool(true)) + } else { + Ok(InputValue::Bool(false)) + } + } else { + Ok(InputValue::Bool(false)) + } + } + // Up should be a negative value + "up" => { + if let Some(y) = y { + if y <= -threshold { + Ok(InputValue::Bool(true)) + } else { + Ok(InputValue::Bool(false)) + } + } else { + Ok(InputValue::Bool(false)) + } + } + // Down should be a positive value + "down" => { + if let Some(y) = y { + if y >= threshold { + Ok(InputValue::Bool(true)) + } else { + Ok(InputValue::Bool(false)) + } + } else { + Ok(InputValue::Bool(false)) + } + } + _ => Err(TranslationError::InvalidSourceConfig(format!( + "Invalid axis direction: {direction}" + ))), + } + } else { + Err(TranslationError::InvalidSourceConfig( + "No axis direction defined to translate button to axis".to_string(), + )) + } + } else { + Err(TranslationError::InvalidSourceConfig( + "No axis config to translate button to axis".to_string(), + )) + } + } else { + Err(TranslationError::InvalidSourceConfig( + "No gamepad config to translate button to axis".to_string(), + )) } } }