Rust · 3036 bytes Raw Blame History
1 //! IPC module for CLI <-> Daemon communication
2 //!
3 //! Uses a Unix socket at $XDG_RUNTIME_DIR/hyprkvm.sock
4
5 use std::path::PathBuf;
6 use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
7 use tokio::net::{UnixListener, UnixStream};
8
9 use hyprkvm_common::protocol::{IpcRequest, IpcResponse};
10
11 /// Get the IPC socket path
12 pub fn socket_path() -> PathBuf {
13 let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
14 .unwrap_or_else(|_| "/tmp".to_string());
15 PathBuf::from(runtime_dir).join("hyprkvm.sock")
16 }
17
18 /// IPC Server for receiving CLI commands
19 pub struct IpcServer {
20 listener: UnixListener,
21 }
22
23 impl IpcServer {
24 /// Create and bind the IPC server
25 pub async fn bind() -> std::io::Result<Self> {
26 let path = socket_path();
27
28 // Remove old socket if it exists
29 let _ = std::fs::remove_file(&path);
30
31 let listener = UnixListener::bind(&path)?;
32 tracing::info!("IPC server listening on {}", path.display());
33
34 Ok(Self { listener })
35 }
36
37 /// Accept a new connection
38 pub async fn accept(&self) -> std::io::Result<IpcConnection> {
39 let (stream, _) = self.listener.accept().await?;
40 Ok(IpcConnection { stream })
41 }
42 }
43
44 /// A single IPC connection
45 pub struct IpcConnection {
46 stream: UnixStream,
47 }
48
49 impl IpcConnection {
50 /// Receive a request
51 pub async fn recv(&mut self) -> std::io::Result<Option<IpcRequest>> {
52 let mut reader = BufReader::new(&mut self.stream);
53 let mut line = String::new();
54
55 let n = reader.read_line(&mut line).await?;
56 if n == 0 {
57 return Ok(None);
58 }
59
60 serde_json::from_str(&line)
61 .map(Some)
62 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
63 }
64
65 /// Send a response
66 pub async fn send(&mut self, response: &IpcResponse) -> std::io::Result<()> {
67 let json = serde_json::to_string(response)?;
68 self.stream.write_all(json.as_bytes()).await?;
69 self.stream.write_all(b"\n").await?;
70 self.stream.flush().await?;
71 Ok(())
72 }
73 }
74
75 /// IPC client for sending commands to daemon
76 pub struct IpcClient {
77 stream: UnixStream,
78 }
79
80 impl IpcClient {
81 /// Connect to the daemon
82 pub async fn connect() -> std::io::Result<Self> {
83 let path = socket_path();
84 let stream = UnixStream::connect(&path).await?;
85 Ok(Self { stream })
86 }
87
88 /// Send a request and get response
89 pub async fn request(&mut self, req: &IpcRequest) -> std::io::Result<IpcResponse> {
90 // Send request
91 let json = serde_json::to_string(req)?;
92 self.stream.write_all(json.as_bytes()).await?;
93 self.stream.write_all(b"\n").await?;
94 self.stream.flush().await?;
95
96 // Read response
97 let mut reader = BufReader::new(&mut self.stream);
98 let mut line = String::new();
99 reader.read_line(&mut line).await?;
100
101 serde_json::from_str(&line)
102 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
103 }
104 }
105