Skip to main content

wallr_core/ipc/
mod.rs

1use serde::{Deserialize, Serialize};
2use std::path::Path;
3use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
4use tokio::net::{UnixListener, UnixStream};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(tag = "command", rename_all = "snake_case")]
8pub enum IpcCommand {
9    Pause,
10    Resume,
11    Reload,
12    Seek {
13        timestamp_ms: u64,
14    },
15    Preview {
16        path: String,
17        effect: Option<crate::animation::Effect>,
18        duration_ms: Option<u32>,
19        #[serde(default)]
20        no_theme: bool,
21        #[serde(default)]
22        theme_override: Option<crate::config::ThemeProvider>,
23        #[serde(default)]
24        monitor: Option<String>,
25        #[serde(default)]
26        scaling_mode: Option<crate::config::ScalingMode>,
27    },
28    Stop,
29    Status,
30    Info,
31    MonitorList,
32    MonitorCurrent,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct IpcResponse {
37    pub success: bool,
38    pub message: Option<String>,
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum IpcError {
43    #[error("daemon not running: {0}")]
44    DaemonNotRunning(String),
45    #[error("IPC I/O error: {0}")]
46    Io(#[from] std::io::Error),
47    #[error("protocol serialization/deserialization error: {0}")]
48    Protocol(#[from] serde_json::Error),
49}
50
51pub async fn send_ipc_command<P: AsRef<Path>>(
52    socket_path: P,
53    command: IpcCommand,
54) -> Result<IpcResponse, IpcError> {
55    let mut stream = UnixStream::connect(socket_path)
56        .await
57        .map_err(|e| IpcError::DaemonNotRunning(e.to_string()))?;
58
59    let req_data = serde_json::to_vec(&command)?;
60    stream.write_all(&req_data).await?;
61    stream.write_all(b"\n").await?;
62    stream.flush().await?;
63
64    let mut reader = BufReader::new(stream);
65    let mut response_line = String::new();
66    reader.read_line(&mut response_line).await?;
67
68    let response: IpcResponse = serde_json::from_str(&response_line)?;
69    Ok(response)
70}
71
72pub async fn start_ipc_server<P, F, Fut>(socket_path: P, handler: F) -> Result<(), IpcError>
73where
74    P: AsRef<Path>,
75    F: Fn(IpcCommand) -> Fut + Send + Sync + 'static,
76    Fut: std::future::Future<Output = IpcResponse> + Send + 'static,
77{
78    let path = socket_path.as_ref();
79    if path.exists() {
80        let _ = std::fs::remove_file(path);
81    }
82
83    let listener = UnixListener::bind(path)?;
84    let handler = std::sync::Arc::new(handler);
85
86    tokio::spawn(async move {
87        loop {
88            match listener.accept().await {
89                Ok((stream, _)) => {
90                    let handler_clone = handler.clone();
91                    tokio::spawn(async move {
92                        let (reader, mut writer) = tokio::io::split(stream);
93                        let mut reader = BufReader::new(reader);
94                        let mut line = String::new();
95
96                        if let Ok(n) = reader.read_line(&mut line).await
97                            && n > 0
98                            && let Ok(cmd) = serde_json::from_str::<IpcCommand>(&line)
99                        {
100                            let response = handler_clone(cmd).await;
101                            if let Ok(res_data) = serde_json::to_vec(&response) {
102                                let _ = writer.write_all(&res_data).await;
103                                let _ = writer.write_all(b"\n").await;
104                                let _ = writer.flush().await;
105                            }
106                        }
107                    });
108                }
109                Err(e) => {
110                    tracing::error!("IPC accept error: {:?}", e);
111                }
112            }
113        }
114    });
115
116    Ok(())
117}