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