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