Skip to content

Commit

Permalink
feat(Manager): add suspend/wake hook methods to handle reconnecting D…
Browse files Browse the repository at this point in the history
…eck target device
  • Loading branch information
ShadowApex committed Nov 21, 2024
1 parent c425c44 commit 9823db5
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ assets = [
{ source = "target/release/inputplumber", dest = "/usr/bin/inputplumber", mode = "755" },
{ source = "rootfs/usr/share/dbus-1/system.d/org.shadowblip.InputPlumber.conf", dest = "/usr/share/dbus-1/system.d/org.shadowblip.InputPlumber.conf", mode = "644" },
{ source = "rootfs/usr/lib/systemd/system/inputplumber.service", dest = "/usr/lib/systemd/system/inputplumber.service", mode = "644" },
{ source = "rootfs/usr/lib/systemd/system/inputplumber-suspend.service", dest = "/usr/lib/systemd/system/inputplumber-suspend.service", mode = "644" },
{ source = "rootfs/usr/share/inputplumber/devices/*.yaml", dest = "/usr/share/inputplumber/devices/", mode = "644" },
{ source = "rootfs/usr/share/inputplumber/schema/*.json", dest = "/usr/share/inputplumber/schema/", mode = "644" },
{ source = "rootfs/usr/share/inputplumber/capability_maps/*.yaml", dest = "/usr/share/inputplumber/capability_maps/", mode = "644" },
Expand Down Expand Up @@ -48,7 +49,7 @@ thiserror = "1.0.61"
tokio = { version = "*", features = ["full"] }
udev = { version = "^0.8", features = ["mio"] }
uhid-virt = "0.0.7"
virtual-usb = { git = "https://github.com/ShadowBlip/virtual-usb-rs.git", rev = "5a7a96a6aedc54f339d9ebff78bf484e5b17728d" }
virtual-usb = { git = "https://github.com/ShadowBlip/virtual-usb-rs.git", rev = "4bca5c6fb9f2b63944a286854405e3e7e0b5d259" }
xdg = "2.5.2"
zbus = { version = "4.3.1", default-features = false, features = ["tokio"] }
zbus_macros = "4.3.1"
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ uninstall: ## Uninstall inputplumber
rm $(PREFIX)/bin/$(NAME)
rm $(PREFIX)/share/dbus-1/system.d/$(DBUS_NAME).conf
rm $(PREFIX)/lib/systemd/system/$(NAME).service
rm $(PREFIX)/lib/systemd/system/$(NAME)-suspend.service
rm $(PREFIX)/lib/udev/hwdb.d/59-inputplumber.hwdb
rm -rf $(PREFIX)/share/$(NAME)/devices/
rm -rf $(PREFIX)/share/$(NAME)/schema/
Expand Down
1 change: 1 addition & 0 deletions pkg/rpm/inputplumber.spec
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ systemctl disable inputplumber.servce
/usr/bin/inputplumber
/usr/share/dbus-1/system.d/org.shadowblip.InputPlumber.conf
/usr/lib/systemd/system/inputplumber.service
/usr/lib/systemd/system/inputplumber-suspend.service
/usr/lib/udev/hwdb.d/59-inputplumber.hwdb
/usr/share/inputplumber/capability_maps/ally_type1.yaml
/usr/share/inputplumber/capability_maps/anbernic_type1.yaml
Expand Down
14 changes: 14 additions & 0 deletions rootfs/usr/lib/systemd/system/inputplumber-suspend.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=InputPlumber Suspend Inhibit Service
Before=sleep.target
StopWhenUnneeded=yes

[Service]
Type=oneshot
StandardOutput=syslog
RemainAfterExit=yes
ExecStart=busctl call org.shadowblip.InputPlumber /org/shadowblip/InputPlumber/Manager org.shadowblip.InputManager HookSleep
ExecStop=busctl call org.shadowblip.InputPlumber /org/shadowblip/InputPlumber/Manager org.shadowblip.InputManager HookWake

[Install]
WantedBy=sleep.target
38 changes: 38 additions & 0 deletions src/dbus/interface/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,42 @@ impl ManagerInterface {

Ok(())
}

/// Used to prepare InputPlumber for system suspend
async fn hook_sleep(&self) -> fdo::Result<()> {
let (sender, mut receiver) = mpsc::channel(1);
self.tx
.send_timeout(
ManagerCommand::SystemSleep { sender },
Duration::from_secs(5),
)
.await
.map_err(|err| fdo::Error::Failed(err.to_string()))?;

// Read the response from the manager
if receiver.recv().await.is_none() {
return Err(fdo::Error::Failed("No response from manager".to_string()));
}

Ok(())
}

/// Used to prepare InputPlumber for resume from system suspend
async fn hook_wake(&self) -> fdo::Result<()> {
let (sender, mut receiver) = mpsc::channel(1);
self.tx
.send_timeout(
ManagerCommand::SystemWake { sender },
Duration::from_secs(5),
)
.await
.map_err(|err| fdo::Error::Failed(err.to_string()))?;

// Read the response from the manager
if receiver.recv().await.is_none() {
return Err(fdo::Error::Failed("No response from manager".to_string()));
}

Ok(())
}
}
22 changes: 22 additions & 0 deletions src/input/composite_device/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,26 @@ impl CompositeDeviceClient {
self.tx.send(CompositeCommand::Stop).await?;
Ok(())
}

/// Calls the suspend handler to perform system suspend-related tasks.
pub async fn suspend(&self) -> Result<(), ClientError> {
let (tx, mut rx) = channel(1);
self.tx.send(CompositeCommand::Suspend(tx)).await?;

if let Some(result) = rx.recv().await {
return Ok(result);
}
Err(ClientError::ChannelClosed)
}

/// Calls the resume handler to perform system wake from suspend-related tasks.
pub async fn resume(&self) -> Result<(), ClientError> {
let (tx, mut rx) = channel(1);
self.tx.send(CompositeCommand::Resume(tx)).await?;

if let Some(result) = rx.recv().await {
return Ok(result);
}
Err(ClientError::ChannelClosed)
}
}
2 changes: 2 additions & 0 deletions src/input/composite_device/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ pub enum CompositeCommand {
WriteEvent(NativeEvent),
WriteSendEvent(NativeEvent),
Stop,
Suspend(mpsc::Sender<()>),
Resume(mpsc::Sender<()>),
}
98 changes: 98 additions & 0 deletions src/input/composite_device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ pub struct CompositeDevice {
/// This is used to block/requeue multiple calls to set_target_devices().
/// E.g. ["/org/shadowblip/InputPlumber/devices/target/gamepad0"]
target_devices_queued: HashSet<String>,
/// List of active target device types (e.g. "deck", "ds5", "xb360") that
/// were active before system suspend.
target_devices_suspended: Vec<String>,
/// Map of DBusDevice DBus paths to their respective transmitter channel.
/// E.g. {"/org/shadowblip/InputPlumber/devices/target/dbus0": <Sender>}
target_dbus_devices: HashMap<String, TargetDeviceClient>,
Expand Down Expand Up @@ -182,6 +185,7 @@ impl CompositeDevice {
target_devices: HashMap::new(),
target_devices_by_capability: HashMap::new(),
target_devices_queued: HashSet::new(),
target_devices_suspended: Vec::new(),
target_dbus_devices: HashMap::new(),
ff_effect_ids: (0..64).collect(),
ff_effect_id_source_map: HashMap::new(),
Expand Down Expand Up @@ -470,6 +474,26 @@ impl CompositeDevice {
);
break 'main;
}
CompositeCommand::Suspend(sender) => {
log::info!(
"Preparing for system suspend for: {}",
self.dbus_path.as_ref().unwrap_or(&"".to_string())
);
self.handle_suspend().await;
if let Err(e) = sender.send(()).await {
log::error!("Failed to send suspend response: {e:?}");
}
}
CompositeCommand::Resume(sender) => {
log::info!(
"Preparing for system resume for: {}",
self.dbus_path.as_ref().unwrap_or(&"".to_string())
);
self.handle_resume().await;
if let Err(e) = sender.send(()).await {
log::error!("Failed to send resume response: {e:?}");
}
}
}
}

Expand Down Expand Up @@ -1966,4 +1990,78 @@ impl CompositeDevice {
}
});
}

/// Called when notified by the input manager that system suspend is about
/// to happen.
async fn handle_suspend(&mut self) {
// Clear the list of suspended target devices
self.target_devices_suspended.clear();

// Create a list of target devices that should be stopped on suspend
let mut targets_to_stop = HashMap::new();

// Record what target devices are currently used so they can be restored
// when the system is resumed.
for (path, target) in self.target_devices.clone().into_iter() {
let target_type = match target.get_type().await {
Ok(kind) => kind,
Err(err) => {
log::error!("Failed to get target device type: {err:?}");
continue;
}
};

// The "deck" target device does not support suspend
if target_type.as_str() == "deck" {
targets_to_stop.insert(path, target);
}

self.target_devices_suspended.push(target_type);
}
log::info!(
"Target devices before suspend: {:?}",
self.target_devices_suspended
);

// Tear down any target devices that do not support suspend
for (path, target) in targets_to_stop.into_iter() {
log::info!("Stopping target device: {path}");
self.target_devices.remove(&path);
for (_, target_devices) in self.target_devices_by_capability.iter_mut() {
target_devices.remove(&path);
}
if let Err(e) = target.stop().await {
log::error!("Failed to stop old target device: {e:?}");
}

// Wait a few beats to ensure that the target device is really gone
tokio::time::sleep(Duration::from_millis(200)).await;
}
}

/// Called when notified by the input manager that system resume is about
/// to happen.
async fn handle_resume(&mut self) {
log::info!(
"Restoring target devices: {:?}",
self.target_devices_suspended
);

// Only handle resume if a deck controller target device was used
if !self.target_devices_suspended.contains(&"deck".to_string()) {
self.target_devices_suspended.clear();
return;
}

// Set the target devices back to the ones used before suspend
if let Err(err) = self
.set_target_devices(self.target_devices_suspended.clone())
.await
{
log::error!("Failed to set restore target devices: {err:?}");
}

// Clear the list of suspended target devices
self.target_devices_suspended.clear();
}
}
50 changes: 50 additions & 0 deletions src/input/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ pub enum ManagerCommand {
sender: mpsc::Sender<bool>,
},
SetManageAllDevices(bool),
SystemSleep {
sender: mpsc::Sender<()>,
},
SystemWake {
sender: mpsc::Sender<()>,
},
}

/// Manages input devices
Expand Down Expand Up @@ -341,6 +347,50 @@ impl Manager {
log::error!("Failed to send response: {e:?}");
}
}
ManagerCommand::SystemSleep { sender } => {
log::info!("Preparing for system suspend");

// Call the suspend handler on each composite device and wait
// for a response.
let composite_devices = self.composite_devices.clone();
tokio::task::spawn(async move {
for device in composite_devices.values() {
if let Err(e) = device.suspend().await {
log::error!("Failed to call suspend handler on device: {e:?}");
}
}

// Respond to the sender to inform them that suspend tasks
// have completed.
if let Err(e) = sender.send(()).await {
log::error!("Failed to send response: {e:?}");
}

log::info!("Finished preparing for system suspend");
});
}
ManagerCommand::SystemWake { sender } => {
log::info!("Preparing for system resume");

// Call the resume handler on each composite device and wait
// for a response.
let composite_devices = self.composite_devices.clone();
tokio::task::spawn(async move {
for device in composite_devices.values() {
if let Err(e) = device.resume().await {
log::error!("Failed to call resume handler on device: {e:?}");
}
}

// Respond to the sender to inform them that resume tasks
// have completed.
if let Err(e) = sender.send(()).await {
log::error!("Failed to send response: {e:?}");
}

log::info!("Finished preparing for system resume");
});
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/input/target/steam_deck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,23 @@ impl TargetInputDevice for SteamDeckDevice {

/// Stop the virtual USB read/write threads
fn stop(&mut self) -> Result<(), InputError> {
log::debug!("Stopping virtual Deck controller");
self.device.stop();

// Read from the device
let xfer = self.device.blocking_read()?;

// Handle any non-standard transfers
if let Some(xfer) = xfer {
let reply = self.handle_xfer(xfer);

// Write to the device if a reply is necessary
if let Some(reply) = reply {
self.device.write(reply)?;
}
}

log::debug!("Finished stopping");
Ok(())
}
}
Expand Down

0 comments on commit 9823db5

Please sign in to comment.