wayclip-daemon 0.2.0

Recording and management of Wayclip daemon
use crate::linux::ipc::commands::IpcCommand;
use gilrs::{EventType, Gilrs};
use log::debug;
use std::{collections::HashSet, time::Duration};
use tokio::sync::{mpsc, oneshot};
use tokio_util::sync::CancellationToken;
use wayclip_core::models::{error::WayclipError, input::controller::WayclipControllerCombo};

pub struct ControllerManager {
    cancel_token: Option<CancellationToken>,
}

impl ControllerManager {
    pub fn new() -> Self {
        Self { cancel_token: None }
    }

    pub fn start(
        &mut self,
        trigger_combo: WayclipControllerCombo,
        command_sender: mpsc::Sender<IpcCommand>,
    ) -> Result<(), WayclipError> {
        self.stop();
        let mut gilrs = Gilrs::new()?;
        let token = CancellationToken::new();
        let loop_token = token.clone();
        self.cancel_token = Some(token);

        tokio::task::spawn_blocking(move || {
            let mut held: HashSet<gilrs::Button> = HashSet::new();
            let mut combo_already_triggered = false;

            while !loop_token.is_cancelled() {
                if let Some(event) = gilrs.next_event_blocking(Some(Duration::from_millis(100))) {
                    match event.event {
                        EventType::ButtonPressed(button, _) => {
                            held.insert(button);

                            if trigger_combo.is_satisfied(&held) && !combo_already_triggered {
                                debug!("Controller combo triggered");
                                combo_already_triggered = true;

                                let tx = command_sender.clone();
                                tokio::spawn(async move {
                                    let (sender, receiver) = oneshot::channel();
                                    if tx
                                        .send(IpcCommand::SaveClip {
                                            custom_name: None,
                                            responder: sender,
                                        })
                                        .await
                                        .is_err()
                                    {
                                        log::warn!("Daemon core offline, could not trigger clip");
                                        return;
                                    }

                                    match receiver.await {
                                        Ok(Ok(clip_name)) => {
                                            log::info!(
                                                "Clip saved successfully via controller: {clip_name}"
                                            );
                                        }
                                        Ok(Err(e)) => log::error!("Failed to save clip: {e:?}"),
                                        Err(_) => log::warn!("Daemon core dropped reply channel"),
                                    }
                                });
                            }
                        }
                        EventType::ButtonReleased(button, _) => {
                            held.remove(&button);

                            if !trigger_combo.is_satisfied(&held) {
                                combo_already_triggered = false;
                            }
                        }
                        EventType::Disconnected => {
                            held.clear();
                            combo_already_triggered = false;
                        }
                        _ => {}
                    }
                }
            }
        });

        Ok(())
    }

    pub fn stop(&mut self) {
        if let Some(t) = self.cancel_token.take() {
            t.cancel();
        }
    }
}

impl Drop for ControllerManager {
    fn drop(&mut self) {
        self.stop();
    }
}