Skip to content

Commit

Permalink
feat(CompositeDevice): Add persist option
Browse files Browse the repository at this point in the history
* Add CompositeDevice config bool option 'persist' 
* If persist is true then continue running CompositeDevice when all SourceDevices have been removed
* Update clear_state for Gamepad targets to default all inputs to stop all input from continuing
  • Loading branch information
blindedone1458 authored Feb 12, 2025
1 parent 9dc4350 commit b3fe66f
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 11 deletions.
5 changes: 5 additions & 0 deletions rootfs/usr/share/inputplumber/schema/composite_device_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
"description": "If true, InputPlumber will automatically try to manage the input device. If this is false, InputPlumber will not try to manage the device unless an external service enables management of the device. Defaults to 'false'",
"type": "boolean",
"default": false
},
"persist": {
"description": "If true, InputPlumber will not stop the CompositeDevice if all the SourceDevices have stopped. This will persist the virtual TargetDevice to allow reconnecting a SourceDevice and resuming input. Defaults to 'false'",
"type": "boolean",
"default": false
}
},
"title": "Options"
Expand Down
1 change: 1 addition & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ pub struct CompositeDeviceConfigOptions {
/// If this is false, InputPlumber will not try to manage the device unless
/// an external service enables management of all devices.
pub auto_manage: Option<bool>,
pub persist: Option<bool>,
}

/// Defines a platform match for loading a [CompositeDeviceConfig]
Expand Down
32 changes: 23 additions & 9 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,16 @@ impl CompositeDevice {
}
self.target_devices = targets;

// Set persist value from config if set, used to determine
// if CompositeDevice self-closes after all SourceDevices have
// been removed.
let persist = self
.config
.options
.as_ref()
.map(|options| options.persist.unwrap_or(false))
.unwrap_or(false);

// Loop and listen for command events
log::debug!("CompositeDevice started");
let mut buffer = Vec::with_capacity(BUFFER_SIZE);
Expand Down Expand Up @@ -394,12 +404,6 @@ impl CompositeDevice {
if let Err(e) = self.on_source_device_removed(device).await {
log::error!("Failed to remove source device: {:?}", e);
}
if self.source_devices_used.is_empty() {
log::debug!(
"No source devices remain. Stopping CompositeDevice {dbus_path}"
);
break 'main;
}
}
CompositeCommand::SourceDeviceRemoved(device) => {
log::debug!("Detected source device removed: {}", device.devnode());
Expand Down Expand Up @@ -523,10 +527,20 @@ impl CompositeDevice {
}

// If no source devices remain after processing the queue, stop
// the device.
// the device unless configured to persist.
if devices_removed && self.source_devices_used.is_empty() {
log::debug!("No source devices remain. Stopping CompositeDevice {dbus_path}");
break 'main;
if persist {
log::debug!("No source devices remain, but CompositeDevice {dbus_path} has persist enabled. Clearing target devices states.");
for (path, target) in &self.target_devices {
log::debug!("Clearing target device: {path}");
if let Err(e) = target.clear_state().await {
log::error!("Failed to clear target device state {path}: {e:?}");
}
}
} else {
log::debug!("No source devices remain. Stopping CompositeDevice {dbus_path}");
break 'main;
}
}
}
log::info!("CompositeDevice stopping: {dbus_path}");
Expand Down
13 changes: 13 additions & 0 deletions src/input/target/dualsense.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,19 @@ impl TargetInputDevice for DualSenseDevice {
let _ = self.device.destroy();
Ok(())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for DualSenseDevice {
Expand Down
15 changes: 14 additions & 1 deletion src/input/target/horipad_steam.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Emulates a Horipad Steam Controller as a target input device.
use std::{cmp::Ordering, error::Error, fmt::Debug, fs::File};
use std::{cmp::Ordering, error::Error, fmt::Debug, fs::File, time::Duration};

use packed_struct::prelude::*;
use uhid_virt::{Bus, CreateParams, StreamError, UHIDDevice};
Expand Down Expand Up @@ -318,6 +318,19 @@ impl TargetInputDevice for HoripadSteamDevice {
let _ = self.device.destroy();
Ok(())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for HoripadSteamDevice {
Expand Down
4 changes: 3 additions & 1 deletion src/input/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ pub trait TargetInputDevice {
/// Clear any local state on the target device. This is typically called
/// whenever the composite device has entered intercept mode to indicate
/// that the target device should stop sending input.
fn clear_state(&mut self) {}
fn clear_state(&mut self) {
log::debug!("Generic clear state called. Do nothing.");
}

/// Called when the target device has been attached to a composite device.
fn on_composite_device_attached(
Expand Down
13 changes: 13 additions & 0 deletions src/input/target/steam_deck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,19 @@ impl TargetInputDevice for SteamDeckDevice {
log::debug!("Finished stopping");
Ok(())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for SteamDeckDevice {
Expand Down
14 changes: 14 additions & 0 deletions src/input/target/xb360.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::input::capability::{Capability, Gamepad, GamepadAxis, GamepadButton,
use crate::input::composite_device::client::CompositeDeviceClient;
use crate::input::event::evdev::EvdevEvent;
use crate::input::event::native::{NativeEvent, ScheduledNativeEvent};
use crate::input::event::value::InputValue;
use crate::input::output_capability::OutputCapability;
use crate::input::output_event::{OutputEvent, UinputOutputEvent};

Expand Down Expand Up @@ -218,6 +219,19 @@ impl TargetInputDevice for XBox360Controller {
}
Some(self.queued_events.drain(..).collect())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for XBox360Controller {
Expand Down
14 changes: 14 additions & 0 deletions src/input/target/xbox_elite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::input::capability::{Capability, Gamepad, GamepadAxis, GamepadButton,
use crate::input::composite_device::client::CompositeDeviceClient;
use crate::input::event::evdev::EvdevEvent;
use crate::input::event::native::{NativeEvent, ScheduledNativeEvent};
use crate::input::event::value::InputValue;
use crate::input::output_capability::OutputCapability;
use crate::input::output_event::{OutputEvent, UinputOutputEvent};

Expand Down Expand Up @@ -229,6 +230,19 @@ impl TargetInputDevice for XboxEliteController {
}
Some(self.queued_events.drain(..).collect())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for XboxEliteController {
Expand Down
14 changes: 14 additions & 0 deletions src/input/target/xbox_series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::input::capability::{Capability, Gamepad, GamepadAxis, GamepadButton,
use crate::input::composite_device::client::CompositeDeviceClient;
use crate::input::event::evdev::EvdevEvent;
use crate::input::event::native::{NativeEvent, ScheduledNativeEvent};
use crate::input::event::value::InputValue;
use crate::input::output_capability::OutputCapability;
use crate::input::output_event::{OutputEvent, UinputOutputEvent};

Expand Down Expand Up @@ -220,6 +221,19 @@ impl TargetInputDevice for XboxSeriesController {
}
Some(self.queued_events.drain(..).collect())
}

/// Clear any local state on the target device.
fn clear_state(&mut self) {
let caps = self.get_capabilities().unwrap_or_else(|_| {
log::error!("No target device capabilities found while clearing state.");
Vec::new()
});
for cap in caps {
let ev = NativeEvent::new(cap, InputValue::Bool(false));
self.queued_events
.push(ScheduledNativeEvent::new(ev, Duration::from_millis(0)));
}
}
}

impl TargetOutputDevice for XboxSeriesController {
Expand Down

0 comments on commit b3fe66f

Please sign in to comment.