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