Rust · 5880 bytes Raw Blame History
1 //! IPC protocol types for garshot daemon communication.
2 //!
3 //! This crate defines the request/response types used for communication
4 //! between garshotctl (and other clients) and the garshot daemon.
5
6 use serde::{Deserialize, Serialize};
7 use std::path::PathBuf;
8
9 /// Request from client to daemon.
10 #[derive(Debug, Clone, Serialize, Deserialize)]
11 #[serde(tag = "command", rename_all = "snake_case")]
12 pub enum Request {
13 /// Capture full screen.
14 Screen {
15 /// Specific monitor name (None = all monitors combined).
16 #[serde(default)]
17 monitor: Option<String>,
18 /// Include cursor in screenshot.
19 #[serde(default)]
20 cursor: bool,
21 /// Output mode.
22 #[serde(default)]
23 output: OutputMode,
24 },
25
26 /// Interactive region selection with blur overlay.
27 Region {
28 /// Include cursor in screenshot.
29 #[serde(default)]
30 cursor: bool,
31 /// Output mode.
32 #[serde(default)]
33 output: OutputMode,
34 },
35
36 /// Capture specific window.
37 Window {
38 /// Window ID (None = active window).
39 #[serde(default)]
40 id: Option<u32>,
41 /// Include window decorations.
42 #[serde(default)]
43 decorations: bool,
44 /// Include cursor in screenshot.
45 #[serde(default)]
46 cursor: bool,
47 /// Output mode.
48 #[serde(default)]
49 output: OutputMode,
50 },
51
52 /// Capture currently active/focused window.
53 ActiveWindow {
54 /// Include window decorations.
55 #[serde(default)]
56 decorations: bool,
57 /// Include cursor in screenshot.
58 #[serde(default)]
59 cursor: bool,
60 /// Output mode.
61 #[serde(default)]
62 output: OutputMode,
63 },
64
65 /// Get daemon status.
66 Status,
67
68 /// List available monitors.
69 ListMonitors,
70
71 /// Update configuration value.
72 SetConfig {
73 /// Configuration key.
74 key: String,
75 /// New value.
76 value: serde_json::Value,
77 },
78
79 /// Reload configuration from disk.
80 ReloadConfig,
81
82 /// Graceful shutdown.
83 Shutdown,
84 }
85
86 /// Output mode for screenshots.
87 #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
88 #[serde(rename_all = "snake_case")]
89 pub enum OutputMode {
90 /// Save to file (default behavior).
91 #[default]
92 File,
93 /// Output raw image data to stdout.
94 Stdout,
95 /// Copy to clipboard.
96 Clipboard,
97 /// Both save to file and copy to clipboard.
98 FileAndClipboard,
99 }
100
101 /// Response from daemon to client.
102 #[derive(Debug, Clone, Serialize, Deserialize)]
103 pub struct Response {
104 /// Whether the operation succeeded.
105 pub success: bool,
106 /// Path to saved file (if saved to file).
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub path: Option<PathBuf>,
109 /// Base64-encoded image data (if stdout mode).
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub data: Option<String>,
112 /// Error message (if success = false).
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub error: Option<String>,
115 /// Additional info (for status, list commands).
116 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub info: Option<serde_json::Value>,
118 }
119
120 impl Response {
121 /// Create a successful response with no data.
122 pub fn ok() -> Self {
123 Self {
124 success: true,
125 path: None,
126 data: None,
127 error: None,
128 info: None,
129 }
130 }
131
132 /// Create a successful response with a file path.
133 pub fn ok_with_path(path: PathBuf) -> Self {
134 Self {
135 success: true,
136 path: Some(path),
137 data: None,
138 error: None,
139 info: None,
140 }
141 }
142
143 /// Create a successful response with additional info.
144 pub fn ok_with_info(info: serde_json::Value) -> Self {
145 Self {
146 success: true,
147 path: None,
148 data: None,
149 error: None,
150 info: Some(info),
151 }
152 }
153
154 /// Create an error response.
155 pub fn error(msg: impl Into<String>) -> Self {
156 Self {
157 success: false,
158 path: None,
159 data: None,
160 error: Some(msg.into()),
161 info: None,
162 }
163 }
164 }
165
166 /// Event from daemon (for subscriptions, future use).
167 #[derive(Debug, Clone, Serialize, Deserialize)]
168 #[serde(tag = "event", rename_all = "snake_case")]
169 pub enum Event {
170 /// Screenshot capture completed.
171 CaptureComplete {
172 /// Path to saved file.
173 path: PathBuf,
174 },
175 /// Interactive selection started.
176 SelectionStarted,
177 /// Interactive selection was cancelled.
178 SelectionCancelled,
179 }
180
181 /// Get the socket path for garshot daemon.
182 pub fn socket_path() -> PathBuf {
183 std::env::var("XDG_RUNTIME_DIR")
184 .map(PathBuf::from)
185 .unwrap_or_else(|_| PathBuf::from("/tmp"))
186 .join("garshot.sock")
187 }
188
189 #[cfg(test)]
190 mod tests {
191 use super::*;
192
193 #[test]
194 fn test_request_serialization() {
195 let req = Request::Screen {
196 monitor: None,
197 cursor: true,
198 output: OutputMode::File,
199 };
200 let json = serde_json::to_string(&req).unwrap();
201 assert!(json.contains("\"command\":\"screen\""));
202 assert!(json.contains("\"cursor\":true"));
203 }
204
205 #[test]
206 fn test_response_ok() {
207 let resp = Response::ok();
208 assert!(resp.success);
209 assert!(resp.error.is_none());
210 }
211
212 #[test]
213 fn test_response_error() {
214 let resp = Response::error("test error");
215 assert!(!resp.success);
216 assert_eq!(resp.error.as_deref(), Some("test error"));
217 }
218
219 #[test]
220 fn test_socket_path() {
221 let path = socket_path();
222 assert!(path.to_string_lossy().contains("garshot.sock"));
223 }
224 }
225