Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ForceFeedback): add force feedback support #6

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ log = { version = "0.4.20", features = [
"max_level_trace",
"release_max_level_debug",
] }
nix = { version = "0.28.0", features = ["fs"] }
packed_struct = "0.10.1"
serde = { version = "1.0.192", features = ["derive"] }
serde_yaml = "0.9.27"
Expand Down
201 changes: 192 additions & 9 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::{
collections::{HashMap, HashSet},
borrow::Borrow,
collections::{BTreeSet, HashMap, HashSet},
error::Error,
};

use evdev::InputEvent;
use tokio::{
sync::{broadcast, mpsc},
task::JoinSet,
time::{sleep, Duration},
time::Duration,
};
use zbus::{fdo, Connection};
use zbus_macros::dbus_interface;
Expand All @@ -23,12 +25,15 @@ use crate::{
Event,
},
manager::SourceDeviceInfo,
output_event::UinputOutputEvent,
source::{self, SourceDevice},
target::TargetCommand,
},
udev::{hide_device, unhide_device},
};

use super::{output_event::OutputEvent, source::SourceCommand};

/// Size of the command channel buffer for processing input events and commands.
const BUFFER_SIZE: usize = 2048;

Expand All @@ -51,6 +56,7 @@ pub enum InterceptMode {
#[derive(Debug, Clone)]
pub enum Command {
ProcessEvent(String, Event),
ProcessOutputEvent(OutputEvent),
GetCapabilities(mpsc::Sender<HashSet<Capability>>),
SetInterceptMode(InterceptMode),
GetInterceptMode(mpsc::Sender<InterceptMode>),
Expand Down Expand Up @@ -280,8 +286,11 @@ pub struct CompositeDevice {
tx: broadcast::Sender<Command>,
/// Receiver channel for listening for commands
rx: broadcast::Receiver<Command>,
/// Map of source device id to their respective transmitter channel.
/// E.g. {"evdev://event0": <Sender>}
source_devices: HashMap<String, mpsc::Sender<SourceCommand>>,
/// Source devices that this composite device will consume.
source_devices: Vec<SourceDevice>,
source_devices_discovered: Vec<SourceDevice>,
/// HashSet of source devices that are blocked from passing their input events to target
/// events.
source_devices_blocked: HashSet<String>,
Expand All @@ -297,6 +306,12 @@ pub struct CompositeDevice {
/// Map of DBusDevice DBus paths to their respective transmitter channel.
/// E.g. {"/org/shadowblip/InputPlumber/devices/target/dbus0": <Sender>}
target_dbus_devices: HashMap<String, mpsc::Sender<TargetCommand>>,
/// Set of available Force Feedback effect IDs that are not in use
ff_effect_ids: BTreeSet<i16>,
/// Source devices use their own IDs for uploaded force feedback effects.
/// This mapping maps the composite device effect ids to source device effect ids.
/// E.g. {3: {"evdev://event0": 6}}
ff_effect_id_source_map: HashMap<i16, HashMap<String, i16>>,
}

impl CompositeDevice {
Expand All @@ -322,13 +337,16 @@ impl CompositeDevice {
intercept_mode: InterceptMode::None,
tx,
rx,
source_devices: Vec::new(),
source_devices: HashMap::new(),
source_devices_discovered: Vec::new(),
source_devices_blocked: HashSet::new(),
source_device_paths: Vec::new(),
source_device_tasks: JoinSet::new(),
source_devices_used: Vec::new(),
target_devices: HashMap::new(),
target_dbus_devices: HashMap::new(),
ff_effect_ids: (0..64).collect(),
ff_effect_id_source_map: HashMap::new(),
};

// Load the capability map if one was defined
Expand Down Expand Up @@ -390,6 +408,16 @@ impl CompositeDevice {
self.run_source_devices().await?;

// Keep track of all target devices
for target in targets.values() {
if let Err(e) = target
.send(TargetCommand::SetCompositeDevice(self.tx.clone()))
.await
{
return Err(
format!("Failed to set composite device for target device: {:?}", e).into(),
);
}
}
self.target_devices = targets;

// Loop and listen for command events
Expand All @@ -402,6 +430,11 @@ impl CompositeDevice {
log::error!("Failed to process event: {:?}", e);
}
}
Command::ProcessOutputEvent(event) => {
if let Err(e) = self.process_output_event(event).await {
log::error!("Failed to process output event: {:?}", e);
}
}
Command::GetCapabilities(sender) => {
if let Err(e) = sender.send(self.capabilities.clone()).await {
log::error!("Failed to send capabilities: {:?}", e);
Expand Down Expand Up @@ -577,13 +610,15 @@ impl CompositeDevice {

log::debug!("Starting new source devices");
// Start listening for events from all source devices
let sources = self.source_devices.drain(..);
let sources = self.source_devices_discovered.drain(..);
for source in sources {
match source {
// If the source device is an event device (i.e. from /dev/input/eventXX),
// then start listening for inputs from that device.
SourceDevice::EventDevice(device) => {
SourceDevice::EventDevice(mut device) => {
let device_id = device.get_id();
let source_tx = device.transmitter();
self.source_devices.insert(device_id.clone(), source_tx);
let tx = self.tx.clone();
self.source_device_tasks.spawn(async move {
if let Err(e) = device.run().await {
Expand All @@ -600,6 +635,8 @@ impl CompositeDevice {
// then start listening for inputs from that device.
SourceDevice::HIDRawDevice(device) => {
let device_id = device.get_id();
let source_tx = device.transmitter();
self.source_devices.insert(device_id.clone(), source_tx);
let tx = self.tx.clone();
self.source_device_tasks.spawn(async move {
if let Err(e) = device.run().await {
Expand All @@ -616,6 +653,8 @@ impl CompositeDevice {
// then start listening for inputs from that device.
SourceDevice::IIODevice(device) => {
let device_id = device.get_id();
let source_tx = device.transmitter();
self.source_devices.insert(device_id.clone(), source_tx);
let tx = self.tx.clone();
self.source_device_tasks.spawn(async move {
if let Err(e) = device.run().await {
Expand Down Expand Up @@ -679,6 +718,150 @@ impl CompositeDevice {
Ok(())
}

/// Process a single output event from a target device.
async fn process_output_event(&mut self, event: OutputEvent) -> Result<(), Box<dyn Error>> {
log::trace!("Received output event: {:?}", event);

// Handle any output events that need to upload FF effect data
if let OutputEvent::Uinput(uinput) = event.borrow() {
match uinput {
UinputOutputEvent::FFUpload(data, target_dev) => {
// Upload the effect data to the source devices
let mut source_effect_ids = HashMap::new();
for (source_id, source) in self.source_devices.iter() {
log::debug!("Uploading effect to {source_id}");
let (tx, rx) = std::sync::mpsc::channel();
source.send(SourceCommand::UploadEffect(*data, tx)).await?;

// Wait for the result of the upload
match rx.recv_timeout(Duration::from_secs(1)) {
Ok(upload_result) => {
if let Err(e) = upload_result {
log::debug!(
"Failed to upload FF effect to {source_id}: {:?}",
e
);
continue;
}
let source_effect_id = upload_result.unwrap();
log::debug!("Successfully uploaded effect with source effect id {source_effect_id}");
source_effect_ids.insert(source_id.clone(), source_effect_id);
}
Err(err) => {
log::error!(
"Failed to receive response from source device {source_id} to upload effect: {:?}",
err
);
}
}
}

// If no source devices uploaded the effect, don't bother
// allocating an effect id.
if source_effect_ids.is_empty() {
log::debug!("No source device available to handle FF effect");
target_dev.send(None)?;
}

// If upload was successful, return an effect ID
let id = self.ff_effect_ids.iter().next().copied();
if let Some(id) = id {
log::debug!("Uploaded effect with effect id {id}");
self.ff_effect_ids.remove(&id);
self.ff_effect_id_source_map.insert(id, source_effect_ids);
target_dev.send(Some(id))?;
} else {
target_dev.send(None)?;
}
}
UinputOutputEvent::FFErase(effect_id) => {
let effect_id = *effect_id as i16;
// Erase the effect from source devices
if let Some(source_effect_ids) = self.ff_effect_id_source_map.get(&effect_id) {
for (source_id, source_effect_id) in source_effect_ids.iter() {
let Some(source) = self.source_devices.get(source_id) else {
continue;
};
log::debug!("Erasing effect from {source_id}");
let (tx, rx) = std::sync::mpsc::channel();
source
.send(SourceCommand::EraseEffect(*source_effect_id, tx))
.await?;

// Wait for the result of the erase
match rx.recv_timeout(Duration::from_secs(1)) {
Ok(erase_result) => {
if let Err(e) = erase_result {
log::debug!(
"Failed to erase FF effect from {source_id}: {:?}",
e
);
continue;
}
}
Err(err) => {
log::error!("Failed to receive response from source device {source_id} to erase effect: {:?}", err);
}
}
}
}

// Add the effect ID to list of available effect ids
log::debug!("Erased effect with effect id {effect_id}");
self.ff_effect_ids.insert(effect_id);
self.ff_effect_id_source_map.remove(&effect_id);
}
}

log::trace!("Available effect IDs: {:?}", self.ff_effect_ids);
log::debug!("Used effect IDs: {:?}", self.ff_effect_id_source_map);

return Ok(());
}

// TODO: Only write the event to devices that are capabile of handling it
for (source_id, source) in self.source_devices.iter() {
// If this is a force feedback event, translate the effect id into
// the source device's effect id.
if let OutputEvent::Evdev(input_event) = event {
if input_event.event_type().0 == evdev::EventType::FORCEFEEDBACK.0 {
// Lookup the source effect ids for the effect
let effect_id = input_event.code() as i16;
let value = input_event.value();
let Some(source_effect_ids) = self.ff_effect_id_source_map.get(&effect_id)
else {
log::warn!("Received FF event with unknown id: {effect_id}");
continue;
};

// Lookup the source effect id for this source device
let Some(source_effect_id) = source_effect_ids.get(source_id) else {
log::warn!("Unable to find source effect id for effect {effect_id} from {source_id}");
continue;
};

// Create a new FF event with the source device effect id.
let new_event = InputEvent::new_now(
evdev::EventType::FORCEFEEDBACK.0,
*source_effect_id as u16,
value,
);
let output_event = OutputEvent::Evdev(new_event);

// Write the FF event to the source device
let event = SourceCommand::WriteEvent(output_event);
source.send(event).await?;
continue;
}
}

let event = SourceCommand::WriteEvent(event.clone());
source.send(event.clone()).await?;
}

Ok(())
}

/// Translate and write the given event to the appropriate target devices
async fn handle_event(&mut self, event: NativeEvent) -> Result<(), Box<dyn Error>> {
// Check if we need to reverse the event list.
Expand Down Expand Up @@ -1089,7 +1272,7 @@ impl CompositeDevice {
let id = device.get_id();
let device_path = device.get_device_path();
let source_device = source::SourceDevice::EventDevice(device);
self.source_devices.push(source_device);
self.source_devices_discovered.push(source_device);
self.source_device_paths.push(device_path);
self.source_devices_used.push(id.clone());

Expand Down Expand Up @@ -1119,7 +1302,7 @@ impl CompositeDevice {
let id = device.get_id();
let device_path = device.get_device_path();
let source_device = source::SourceDevice::HIDRawDevice(device);
self.source_devices.push(source_device);
self.source_devices_discovered.push(source_device);
self.source_device_paths.push(device_path);
self.source_devices_used.push(id.clone());

Expand Down Expand Up @@ -1148,7 +1331,7 @@ impl CompositeDevice {
let id = device.get_id();
let device_path = device.get_device_path();
let source_device = source::SourceDevice::IIODevice(device);
self.source_devices.push(source_device);
self.source_devices_discovered.push(source_device);
self.source_device_paths.push(device_path);
self.source_devices_used.push(id.clone());

Expand Down
1 change: 1 addition & 0 deletions src/input/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod evdev;
pub mod native;
pub mod value;

/// Events are events that flow from source devices to target devices
#[derive(Debug, Clone)]
pub enum Event {
Evdev(evdev::EvdevEvent),
Expand Down
2 changes: 2 additions & 0 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ pub mod capability;
pub mod composite_device;
pub mod event;
pub mod manager;
pub mod output_capability;
pub mod output_event;
pub mod source;
pub mod target;
16 changes: 16 additions & 0 deletions src/input/output_capability.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/// Output capabilities describe what kind of output events a source input device
/// is capable of handling. E.g. Force Feedback, LED control, etc.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OutputCapability {
ForceFeedback,
#[allow(clippy::upper_case_acronyms)]
LED(LED),
}

/// LED capability
#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum LED {
Brightness,
Color,
}
19 changes: 19 additions & 0 deletions src/input/output_event/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::sync::mpsc::Sender;

use ::evdev::{FFEffectData, InputEvent};

/// Output events are events that flow from target devices back to source devices
#[derive(Debug, Clone)]
pub enum OutputEvent {
Evdev(InputEvent),
Uinput(UinputOutputEvent),
}

#[derive(Debug, Clone)]
pub enum UinputOutputEvent {
/// Effect data to upload to a source device and a channel to send back
/// the effect ID.
FFUpload(FFEffectData, Sender<Option<i16>>),
/// Effect id to erase
FFErase(u32),
}
Loading
Loading