gardesk/garshot / c6e696d

Browse files

add garshot-ipc crate with IPC protocol types

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
c6e696ddd3f3fae28680e0509d42c3e1e9e583b1
Parents
cb32fb6
Tree
cf3aa60

2 changed files

StatusFile+-
A garshot-ipc/Cargo.toml 10 0
A garshot-ipc/src/lib.rs 224 0
garshot-ipc/Cargo.tomladded
@@ -0,0 +1,10 @@
1
+[package]
2
+name = "garshot-ipc"
3
+version.workspace = true
4
+edition.workspace = true
5
+license.workspace = true
6
+description = "IPC protocol types for garshot"
7
+
8
+[dependencies]
9
+serde = { workspace = true }
10
+serde_json = { workspace = true }
garshot-ipc/src/lib.rsadded
@@ -0,0 +1,224 @@
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
+}