Skip to content

Commit

Permalink
feat(MouseDevice): add event translation support for target mouse dev…
Browse files Browse the repository at this point in the history
…ices
  • Loading branch information
ShadowApex committed Apr 2, 2024
1 parent 0b424aa commit 7bfd57a
Show file tree
Hide file tree
Showing 7 changed files with 472 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ mapping:
- mouse:
motion:
speed_pps: 800 # default to 800pps
direction: left

# Buttons
- name: Menu
Expand Down
2 changes: 2 additions & 0 deletions rootfs/usr/share/inputplumber/schema/device_profile_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@
"direction": {
"type": "string",
"enum": [
"horizontal",
"vertical",
"left",
"right",
"up",
Expand Down
2 changes: 1 addition & 1 deletion src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ impl CompositeDevice {
continue;
}

let event = NativeEvent::new(target_cap, value);
let event = NativeEvent::new_translated(source_cap.clone(), target_cap, value);
events.push(event);
}

Expand Down
35 changes: 30 additions & 5 deletions src/input/event/evdev.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::collections::HashMap;

use evdev::{AbsInfo, AbsoluteAxisCode, EventType, InputEvent, KeyCode};
use evdev::{AbsInfo, AbsoluteAxisCode, EventType, InputEvent, KeyCode, RelativeAxisCode};

use crate::input::capability::{
Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Keyboard,
Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger, Keyboard, Mouse, MouseButton,
};

use super::{native::NativeEvent, value::InputValue};
Expand Down Expand Up @@ -506,6 +506,10 @@ fn event_type_from_capability(capability: Capability) -> Option<EventType> {
match capability {
Capability::Sync => Some(EventType::SYNCHRONIZATION),
Capability::Keyboard(_) => Some(EventType::KEY),
Capability::Mouse(mouse) => match mouse {
Mouse::Motion => Some(EventType::RELATIVE),
Mouse::Button(_) => Some(EventType::KEY),
},
Capability::Gamepad(gamepad) => match gamepad {
Gamepad::Button(button) => match button {
GamepadButton::DPadUp => Some(EventType::ABSOLUTE),
Expand Down Expand Up @@ -625,7 +629,20 @@ fn event_codes_from_capability(capability: Capability) -> Vec<u16> {
Gamepad::Accelerometer => vec![],
Gamepad::Gyro => vec![],
},
Capability::Mouse(_) => vec![],
Capability::Mouse(mouse) => match mouse {
Mouse::Motion => vec![RelativeAxisCode::REL_X.0, RelativeAxisCode::REL_Y.0],
Mouse::Button(button) => match button {
MouseButton::Left => vec![KeyCode::BTN_LEFT.0],
MouseButton::Right => vec![KeyCode::BTN_RIGHT.0],
MouseButton::Middle => vec![KeyCode::BTN_MIDDLE.0],
MouseButton::WheelUp => vec![],
MouseButton::WheelDown => vec![],
MouseButton::WheelLeft => vec![],
MouseButton::WheelRight => vec![],
MouseButton::Extra1 => vec![KeyCode::BTN_EXTRA.0],
MouseButton::Extra2 => vec![],
},
},
Capability::Keyboard(key) => match key {
Keyboard::KeyEsc => vec![KeyCode::KEY_ESC.0],
Keyboard::Key1 => vec![KeyCode::KEY_1.0],
Expand Down Expand Up @@ -841,8 +858,16 @@ fn input_event_from_value(
_ => todo!(),
}
} else {
// Cannot convert axis input without axis info
None
match event_type {
// Cannot denormalize value without axis info
EventType::ABSOLUTE => None,
EventType::RELATIVE => match RelativeAxisCode(code) {
RelativeAxisCode::REL_X => Some(x? as i32),
RelativeAxisCode::REL_Y => Some(y? as i32),
_ => None,
},
_ => None,
}
}
}
InputValue::Vector3 { x: _, y: _, z: _ } => todo!(),
Expand Down
51 changes: 49 additions & 2 deletions src/input/event/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,40 @@ use super::{evdev::EvdevEvent, value::InputValue};
/// A native event represents an InputPlumber event
#[derive(Debug, Clone)]
pub struct NativeEvent {
/// The capability of the input event. Target input devices that implement
/// this capability will be able to emit this event.
capability: Capability,
/// Optional source capability of the input event if this event was translated
/// from one type to another. This can allow downstream target input devices
/// to have different behavior for events that have been translated from one
/// type to another.
source_capability: Option<Capability>,
/// The value of the input event.
value: InputValue,
}

impl NativeEvent {
/// Returns a new [NativeEvent] with the given capability and value
pub fn new(capability: Capability, value: InputValue) -> NativeEvent {
NativeEvent { capability, value }
NativeEvent {
capability,
value,
source_capability: None,
}
}

/// Returns a new [NativeEvent] with the original un-translated source
/// capability, the translated capability, and value.
pub fn new_translated(
source_capability: Capability,
capability: Capability,
value: InputValue,
) -> NativeEvent {
NativeEvent {
capability,
source_capability: Some(source_capability),
value,
}
}

/// Returns the capability that this event implements
Expand All @@ -24,6 +51,22 @@ impl NativeEvent {
self.value.clone()
}

/// Returns true if this event is a translated event and has a source
/// capability defined.
pub fn is_translated(&self) -> bool {
self.source_capability.is_some()
}

/// Set the source capability of the event if this is a translated event
pub fn set_source_capability(&mut self, cap: Capability) {
self.source_capability = Some(cap);
}

/// Returns the source capability that this event was translated from
pub fn get_source_capability(&self) -> Option<Capability> {
self.source_capability.clone()
}

/// Returns whether or not the event is "pressed"
pub fn pressed(&self) -> bool {
self.value.pressed()
Expand All @@ -35,6 +78,10 @@ impl From<EvdevEvent> for NativeEvent {
fn from(item: EvdevEvent) -> Self {
let capability = item.as_capability();
let value = item.get_value();
NativeEvent { capability, value }
NativeEvent {
capability,
value,
source_capability: None,
}
}
}
202 changes: 200 additions & 2 deletions src/input/event/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,49 @@ impl InputValue {
},
// Axis -> Mouse
Capability::Mouse(mouse) => match mouse {
Mouse::Motion => Err(TranslationError::NotImplemented),
// Axis -> Mouse Motion
Mouse::Motion => self
.translate_axis_to_mouse_motion(source_config, target_config),
// Axis -> Mouse Button
Mouse::Button(_) => self.translate_axis_to_button(source_config),
},
// Axis -> Keyboard
Capability::Keyboard(_) => self.translate_axis_to_button(source_config),
}
}
// Trigger -> ...
Gamepad::Trigger(_) => Err(TranslationError::NotImplemented),
Gamepad::Trigger(_) => match target_cap {
// Trigger -> None
Capability::None => Ok(InputValue::None),
// Trigger -> NotImplemented
Capability::NotImplemented => Ok(InputValue::None),
// Trigger -> Sync
Capability::Sync => Ok(InputValue::None),
// Trigger -> DBus
Capability::DBus(_) => Ok(self.clone()),
// Trigger -> Gamepad
Capability::Gamepad(gamepad) => match gamepad {
// Trigger -> Button
Gamepad::Button(_) => self.translate_trigger_to_button(source_config),
// Trigger -> Axis
Gamepad::Axis(_) => Err(TranslationError::NotImplemented),
// Trigger -> Trigger
Gamepad::Trigger(_) => Ok(self.clone()),
// Trigger -> Accelerometer
Gamepad::Accelerometer => Err(TranslationError::NotImplemented),
// Trigger -> Gyro
Gamepad::Gyro => Err(TranslationError::NotImplemented),
},
// Trigger -> Mouse
Capability::Mouse(mouse) => match mouse {
// Trigger -> Mouse Motion
Mouse::Motion => Err(TranslationError::NotImplemented),
// Trigger -> Mouse Button
Mouse::Button(_) => self.translate_trigger_to_button(source_config),
},
// Trigger -> Keyboard
Capability::Keyboard(_) => self.translate_trigger_to_button(source_config),
},
// Accelerometer -> ...
Gamepad::Accelerometer => Err(TranslationError::NotImplemented),
// Gyro -> ...
Expand All @@ -151,6 +185,137 @@ impl InputValue {
}
}

/// Translate the axis value into mouse motion
fn translate_axis_to_mouse_motion(
&self,
source_config: &CapabilityConfig,
target_config: &CapabilityConfig,
) -> Result<InputValue, TranslationError> {
// Use provided mapping to determine mouse motion value
if let Some(mouse_config) = target_config.mouse.as_ref() {
if let Some(mouse_motion) = mouse_config.motion.as_ref() {
// Get the mouse speed in pixels-per-second
let speed_pps = mouse_motion.speed_pps.unwrap_or(800);

// Get the value from the axis event
let (mut x, mut y) = match self {
InputValue::Vector2 { x, y } => (*x, *y),
InputValue::Vector3 { x, y, z: _ } => (*x, *y),
_ => (None, None),
};

// Check to see if the value is below a given threshold to prevent
// mouse movements for axes that don't recenter to 0.
if let Some(value) = x {
if value.abs() < 0.20 {
x = Some(0.0);
}
}
if let Some(value) = y {
if value.abs() < 0.20 {
y = Some(0.0);
}
}

// Multiply the value by the speed
if let Some(value) = x {
x = Some(value * speed_pps as f64);
}
if let Some(value) = y {
y = Some(value * speed_pps as f64);
}

// If a direction is defined, map only the selected directions to
// the value.
if let Some(direction) = mouse_motion.direction.as_ref() {
// Create a vector2 value based on axis direction
match direction.as_str() {
// Horizontal takes both positive and negative
"horizontal" => Ok(InputValue::Vector2 { x, y: None }),
// Vertical takes both positive and negative
"vertical" => Ok(InputValue::Vector2 { x: None, y }),
// Left should be a negative value
"left" => {
if let Some(x) = x {
if x <= 0.0 {
Ok(InputValue::Vector2 {
x: Some(x),
y: None,
})
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
}
// Right should be a positive value
"right" => {
if let Some(x) = x {
if x >= 0.0 {
Ok(InputValue::Vector2 {
x: Some(x),
y: None,
})
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
}
// Up should be a negative value
"up" => {
if let Some(y) = y {
if y <= 0.0 {
Ok(InputValue::Vector2 {
x: None,
y: Some(y),
})
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
}
// Down should be a positive value
"down" => {
if let Some(y) = y {
if y >= 0.0 {
Ok(InputValue::Vector2 {
x: None,
y: Some(y),
})
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
} else {
Ok(InputValue::Vector2 { x: None, y: None })
}
}
_ => Err(TranslationError::InvalidTargetConfig(format!(
"Invalid axis direction: {direction}"
))),
}
}
// If no direction is defined, map both axes to the mouse values
else {
Ok(InputValue::Vector2 { x, y })
}
} else {
Err(TranslationError::InvalidTargetConfig(
"No mouse motion config to translate axis to mouse motion".to_string(),
))
}
//
} else {
Err(TranslationError::InvalidTargetConfig(
"No mouse config to translate axis to mouse motion".to_string(),
))
}
}

/// Translate the button value into an axis value based on the given config
fn translate_button_to_axis(
&self,
Expand Down Expand Up @@ -323,4 +488,37 @@ impl InputValue {
))
}
}

/// Translate the trigger value into a button value based on the given config.
fn translate_trigger_to_button(
&self,
source_config: &CapabilityConfig,
) -> Result<InputValue, TranslationError> {
if let Some(gamepad_config) = source_config.gamepad.as_ref() {
if let Some(trigger) = gamepad_config.trigger.as_ref() {
// Get the threshold to consider the trigger as 'pressed' or not
let threshold = trigger.deadzone.unwrap_or(0.3);

// Get the trigger value
let value = match self {
InputValue::Float(value) => *value,
_ => 0.0,
};

if value >= threshold {
Ok(InputValue::Bool(true))
} else {
Ok(InputValue::Bool(false))
}
} 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(),
))
}
}
}
Loading

0 comments on commit 7bfd57a

Please sign in to comment.