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}