Skip to content

Commit

Permalink
feat(Capabilities): add get_capabilities for source devices
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadowApex committed Mar 25, 2024
1 parent f9f596d commit 2acb4d0
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 39 deletions.
78 changes: 70 additions & 8 deletions src/input/capability.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::{fmt, str::FromStr};

use crate::config;

/// A capability describes what kind of input events an input device is capable
/// of emitting.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Capability {
/// Used to purposefully disable input capabilities
None,
Expand Down Expand Up @@ -41,7 +43,51 @@ impl FromStr for Capability {
}
}

#[derive(Clone, Debug, PartialEq)]
impl From<config::Capability> for Capability {
fn from(value: config::Capability) -> 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);
}
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.
}
if let Some(button_string) = gamepad.button.clone() {
let button = GamepadButton::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::Gamepad(Gamepad::Button(button));
}
if let Some(trigger_string) = gamepad.trigger.clone() {
let trigger = GamepadTrigger::from_str(&trigger_string);
if trigger.is_err() {
log::error!("Invalid or unimplemented trigger: {trigger_string}");
return Capability::NotImplemented;
}

let trigger = trigger.unwrap();
return Capability::Gamepad(Gamepad::Trigger(trigger));
}
}
if let Some(mouse) = value.mouse.as_ref() {
unimplemented!();
}

Capability::NotImplemented
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Gamepad {
/// Gamepad Buttons typically use binary input that represents button presses
Button(GamepadButton),
Expand All @@ -66,7 +112,7 @@ impl fmt::Display for Gamepad {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Mouse {
/// Represents (x, y) relative mouse motion
Motion,
Expand All @@ -83,7 +129,7 @@ impl fmt::Display for Mouse {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MouseButton {
/// Left mouse button
Left,
Expand Down Expand Up @@ -122,7 +168,7 @@ impl fmt::Display for MouseButton {
}

/// Gamepad Buttons typically use binary input that represents button presses
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GamepadButton {
/// South action, Sony Cross x, Xbox A, Nintendo B
South,
Expand Down Expand Up @@ -283,7 +329,7 @@ impl FromStr for GamepadButton {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GamepadAxis {
LeftStick,
RightStick,
Expand All @@ -307,7 +353,7 @@ impl fmt::Display for GamepadAxis {
}
}

#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GamepadTrigger {
LeftTrigger,
LeftTouchpadForce,
Expand All @@ -330,7 +376,23 @@ impl fmt::Display for GamepadTrigger {
}
}

#[derive(Clone, Debug, PartialEq)]
impl FromStr for GamepadTrigger {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"LeftTrigger" => Ok(GamepadTrigger::LeftTrigger),
"LeftTouchpadForce" => Ok(GamepadTrigger::LeftTouchpadForce),
"LeftStickForce" => Ok(GamepadTrigger::LeftStickForce),
"RightTrigger" => Ok(GamepadTrigger::RightTrigger),
"RightTouchpadForce" => Ok(GamepadTrigger::RightTouchpadForce),
"RightStickForce" => Ok(GamepadTrigger::RightStickForce),
_ => Err(()),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Keyboard {
KeyEsc,
Key1,
Expand Down
93 changes: 85 additions & 8 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::{collections::HashMap, error::Error, str::FromStr};
use std::{
collections::{HashMap, HashSet},
error::Error,
str::FromStr,
};

use tokio::{
sync::{broadcast, mpsc},
Expand Down Expand Up @@ -46,6 +50,7 @@ pub enum InterceptMode {
#[derive(Debug, Clone)]
pub enum Command {
ProcessEvent(Event),
GetCapabilities(mpsc::Sender<HashSet<Capability>>),
SetInterceptMode(InterceptMode),
GetInterceptMode(mpsc::Sender<InterceptMode>),
GetSourceDevicePaths(mpsc::Sender<Vec<String>>),
Expand Down Expand Up @@ -78,6 +83,40 @@ impl DBusInterface {
Ok("CompositeDevice".into())
}

/// List of capabilities that all source devices implement
#[dbus_interface(property)]
async fn capabilities(&self) -> fdo::Result<Vec<String>> {
let (sender, mut receiver) = mpsc::channel::<HashSet<Capability>>(1);
self.tx
.send(Command::GetCapabilities(sender))
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
let Some(capabilities) = receiver.recv().await else {
return Ok(Vec::new());
};

let mut capability_strings = Vec::new();
for cap in capabilities {
let str = match cap {
Capability::Gamepad(gamepad) => match gamepad {
Gamepad::Button(button) => format!("Gamepad:Button:{}", button),
Gamepad::Axis(axis) => format!("Gamepad:Axis:{}", axis),
Gamepad::Trigger(trigger) => format!("Gamepad:Trigger:{}", trigger),
Gamepad::Accelerometer => "Gamepad:Accelerometer".to_string(),
Gamepad::Gyro => "Gamepad:Gyro".to_string(),
},
Capability::Mouse(mouse) => match mouse {
super::capability::Mouse::Motion => "Mouse:Motion".to_string(),
super::capability::Mouse::Button(button) => format!("Mouse:Button:{}", button),
},
Capability::Keyboard(key) => format!("Keyboard:{}", key),
_ => cap.to_string(),
};
capability_strings.push(str);
}

Ok(capability_strings)
}

/// List of source devices that this composite device is processing inputs for
#[dbus_interface(property)]
async fn source_device_paths(&self) -> fdo::Result<Vec<String>> {
Expand Down Expand Up @@ -174,6 +213,8 @@ pub struct CompositeDevice {
conn: Connection,
/// Configuration for the CompositeDevice
config: CompositeDeviceConfig,
/// Capabilities describe all input capabilities from all source devices
capabilities: HashSet<Capability>,
/// Capability mapping for the CompositeDevice
capability_map: Option<CapabilityMap>,
/// List of input capabilities that can be translated by the capability map
Expand Down Expand Up @@ -220,6 +261,7 @@ impl CompositeDevice {
let mut device = Self {
conn,
config,
capabilities: HashSet::new(),
capability_map,
translatable_capabilities: Vec::new(),
translatable_active_inputs: Vec::new(),
Expand All @@ -235,7 +277,26 @@ impl CompositeDevice {
target_devices: HashMap::new(),
target_dbus_devices: HashMap::new(),
};

// Load the capability map if one was defined
if device.capability_map.is_some() {
device.load_capability_map()?;
}

// If a capability map is defined, add those target capabilities to
// the hashset of implemented capabilities.
if let Some(map) = device.capability_map.as_ref() {
for mapping in map.mapping.clone() {
let cap = mapping.target_event.clone().into();
if cap == Capability::NotImplemented {
continue;
}
device.capabilities.insert(cap);
}
}

device.add_source_device(device_info)?;

Ok(device)
}

Expand All @@ -262,11 +323,6 @@ impl CompositeDevice {
) -> Result<(), Box<dyn Error>> {
log::debug!("Starting composite device");

// Load the capability map if one was defined
if self.capability_map.is_some() {
self.load_capability_map()?;
}

// Start all source devices
self.run_source_devices().await?;

Expand All @@ -283,6 +339,11 @@ impl CompositeDevice {
log::error!("Failed to process event: {:?}", e);
}
}
Command::GetCapabilities(sender) => {
if let Err(e) = sender.send(self.capabilities.clone()).await {
log::error!("Failed to send capabilities: {:?}", e);
}
}
Command::SetInterceptMode(mode) => self.set_intercept_mode(mode),
Command::GetInterceptMode(sender) => {
if let Err(e) = sender.send(self.intercept_mode.clone()).await {
Expand Down Expand Up @@ -862,8 +923,17 @@ impl CompositeDevice {
// Create an instance of the device
log::debug!("Adding source device: {:?}", info);
let device = source::evdev::EventDevice::new(info, self.tx.clone());

// Get the capabilities of the source device.
//let capabilities = device.get_capabilities()?;
// TODO: When we *remove* a source device, we also need to remove
// capabilities
let capabilities = device.get_capabilities()?;
for cap in capabilities {
if self.translatable_capabilities.contains(&cap) {
continue;
}
self.capabilities.insert(cap);
}

// TODO: Based on the capability map in the config, translate
// the capabilities.
Expand All @@ -881,7 +951,13 @@ impl CompositeDevice {
let device = source::hidraw::HIDRawDevice::new(info, self.tx.clone());

// Get the capabilities of the source device.
//let capabilities = device.get_capabilities()?;
let capabilities = device.get_capabilities()?;
for cap in capabilities {
if self.translatable_capabilities.contains(&cap) {
continue;
}
self.capabilities.insert(cap);
}

let id = device.get_id();
let device_path = device.get_device_path();
Expand All @@ -891,6 +967,7 @@ impl CompositeDevice {
self.source_devices_used.push(id);
}
}

Ok(())
}
}
52 changes: 30 additions & 22 deletions src/input/source/evdev.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{collections::HashMap, error::Error};

use evdev::{AbsoluteAxisCode, Device, EventType, KeyCode};
use evdev::{AbsoluteAxisCode, Device, EventType, InputEvent, KeyCode};
use tokio::sync::broadcast;
use zbus::{fdo, Connection};
use zbus_macros::dbus_interface;
Expand Down Expand Up @@ -175,34 +175,42 @@ impl EventDevice {
let events = device.supported_events();
for event in events.iter() {
match event {
EventType::SYNCHRONIZATION => (),
EventType::SYNCHRONIZATION => {
capabilities.push(Capability::Sync);
}
EventType::KEY => {
let Some(keys) = device.supported_keys() else {
continue;
};
for key in keys.iter() {
let capability = match key {
KeyCode::KEY_LEFT => Capability::None,
KeyCode::KEY_UP => Capability::None,
KeyCode::BTN_SOUTH => {
Capability::Gamepad(Gamepad::Button(GamepadButton::South))
}
KeyCode::BTN_NORTH => {
Capability::Gamepad(Gamepad::Button(GamepadButton::North))
}
KeyCode::BTN_WEST => {
Capability::Gamepad(Gamepad::Button(GamepadButton::West))
}
KeyCode::BTN_EAST => {
Capability::Gamepad(Gamepad::Button(GamepadButton::East))
}
_ => Capability::None,
};
capabilities.push(capability);
let input_event = InputEvent::new(event.0, key.0, 0);
let evdev_event = EvdevEvent::from(input_event);
let cap = evdev_event.as_capability();
capabilities.push(cap);
}
}
EventType::RELATIVE => {
let Some(rel) = device.supported_relative_axes() else {
continue;
};
for axis in rel.iter() {
let input_event = InputEvent::new(event.0, axis.0, 0);
let evdev_event = EvdevEvent::from(input_event);
let cap = evdev_event.as_capability();
capabilities.push(cap);
}
}
EventType::ABSOLUTE => {
let Some(abs) = device.supported_absolute_axes() else {
continue;
};
for axis in abs.iter() {
let input_event = InputEvent::new(event.0, axis.0, 0);
let evdev_event = EvdevEvent::from(input_event);
let cap = evdev_event.as_capability();
capabilities.push(cap);
}
}
EventType::RELATIVE => (),
EventType::ABSOLUTE => (),
EventType::MISC => (),
EventType::SWITCH => (),
EventType::LED => (),
Expand Down
Loading

0 comments on commit 2acb4d0

Please sign in to comment.