Rust · 9259 bytes Raw Blame History
1 //! Network protocol definitions for HyprKVM
2 //!
3 //! All messages exchanged between HyprKVM daemons are defined here.
4
5 use serde::{Deserialize, Serialize};
6
7 use crate::{Direction, ModifierState};
8
9 /// Protocol version - increment on breaking changes
10 pub const PROTOCOL_VERSION: u32 = 1;
11
12 /// All possible network messages
13 #[derive(Debug, Clone, Serialize, Deserialize)]
14 #[serde(tag = "type", rename_all = "snake_case")]
15 pub enum Message {
16 // Connection lifecycle
17 Hello(HelloPayload),
18 HelloAck(HelloAckPayload),
19 Goodbye,
20
21 // Topology
22 TopologyUpdate(TopologyPayload),
23 TopologyAck,
24
25 // Control transfer
26 Enter(EnterPayload),
27 EnterAck(EnterAckPayload),
28 Leave(LeavePayload),
29 LeaveAck,
30
31 // Input events
32 InputEvent(InputEventPayload),
33 InputBatch(Vec<InputEventPayload>),
34
35 // Clipboard
36 ClipboardOffer(ClipboardOfferPayload),
37 ClipboardRequest(ClipboardRequestPayload),
38 ClipboardData(ClipboardDataPayload),
39
40 // Health
41 Ping { timestamp: u64 },
42 Pong { timestamp: u64 },
43 }
44
45 // ============================================================================
46 // Connection Messages
47 // ============================================================================
48
49 #[derive(Debug, Clone, Serialize, Deserialize)]
50 pub struct HelloPayload {
51 /// Protocol version
52 pub protocol_version: u32,
53 /// Machine name
54 pub machine_name: String,
55 /// Supported capabilities
56 pub capabilities: Vec<String>,
57 /// Direction the sender has configured for the recipient (peer uses opposite)
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub my_direction_for_you: Option<crate::Direction>,
60 }
61
62 #[derive(Debug, Clone, Serialize, Deserialize)]
63 pub struct HelloAckPayload {
64 /// Whether handshake was accepted
65 pub accepted: bool,
66 /// Protocol version (for negotiation)
67 pub protocol_version: u32,
68 /// Machine name
69 pub machine_name: String,
70 /// Error message if not accepted
71 pub error: Option<String>,
72 }
73
74 // ============================================================================
75 // Topology Messages
76 // ============================================================================
77
78 #[derive(Debug, Clone, Serialize, Deserialize)]
79 pub struct TopologyPayload {
80 /// This machine's name
81 pub machine_name: String,
82 /// Known neighbors
83 pub neighbors: Vec<NeighborInfo>,
84 }
85
86 #[derive(Debug, Clone, Serialize, Deserialize)]
87 pub struct NeighborInfo {
88 pub name: String,
89 pub direction: Direction,
90 }
91
92 // ============================================================================
93 // Control Transfer Messages
94 // ============================================================================
95
96 #[derive(Debug, Clone, Serialize, Deserialize)]
97 pub struct EnterPayload {
98 /// Direction we're entering from (from sender's perspective)
99 pub from_direction: Direction,
100 /// Cursor entry position
101 pub cursor_pos: CursorEntryPos,
102 /// Current modifier state
103 pub modifiers: ModifierState,
104 /// Unique transfer ID
105 pub transfer_id: u64,
106 }
107
108 #[derive(Debug, Clone, Serialize, Deserialize)]
109 #[serde(rename_all = "snake_case")]
110 pub enum CursorEntryPos {
111 /// Position along the edge (0.0 = top/left, 1.0 = bottom/right)
112 EdgeRelative(f64),
113 /// Absolute coordinates
114 Absolute { x: i32, y: i32 },
115 }
116
117 #[derive(Debug, Clone, Serialize, Deserialize)]
118 pub struct EnterAckPayload {
119 /// Whether entry was accepted
120 pub success: bool,
121 /// Transfer ID for correlation
122 pub transfer_id: u64,
123 /// Actual cursor position after entry
124 pub actual_cursor_pos: Option<(i32, i32)>,
125 /// Error message if not success
126 pub error: Option<String>,
127 }
128
129 #[derive(Debug, Clone, Serialize, Deserialize)]
130 pub struct LeavePayload {
131 /// Direction we're leaving towards
132 pub to_direction: Direction,
133 /// Cursor position for target machine
134 pub cursor_pos: CursorEntryPos,
135 /// Current modifier state
136 pub modifiers: ModifierState,
137 /// Transfer ID for correlation
138 pub transfer_id: u64,
139 }
140
141 // ============================================================================
142 // Input Messages
143 // ============================================================================
144
145 #[derive(Debug, Clone, Serialize, Deserialize)]
146 pub struct InputEventPayload {
147 /// Sequence number for ordering
148 pub sequence: u64,
149 /// Timestamp in microseconds
150 pub timestamp_us: u64,
151 /// The actual event
152 pub event: InputEventType,
153 }
154
155 #[derive(Debug, Clone, Serialize, Deserialize)]
156 #[serde(tag = "type", rename_all = "snake_case")]
157 pub enum InputEventType {
158 KeyDown { keycode: u32 },
159 KeyUp { keycode: u32 },
160 ModifierState {
161 shift: bool,
162 ctrl: bool,
163 alt: bool,
164 super_key: bool,
165 },
166 PointerMotion { dx: f64, dy: f64 },
167 PointerButton { button: u32, pressed: bool },
168 Scroll { horizontal: f64, vertical: f64 },
169 }
170
171 // ============================================================================
172 // Clipboard Messages
173 // ============================================================================
174
175 #[derive(Debug, Clone, Serialize, Deserialize)]
176 pub struct ClipboardOfferPayload {
177 /// Unique ID for this clipboard state
178 pub clipboard_id: u64,
179 /// Available MIME types
180 pub mime_types: Vec<String>,
181 /// Size hint in bytes (if known)
182 pub size_hint: Option<u64>,
183 /// Content hash for deduplication
184 pub content_hash: Option<String>,
185 }
186
187 #[derive(Debug, Clone, Serialize, Deserialize)]
188 pub struct ClipboardRequestPayload {
189 /// Which clipboard to fetch
190 pub clipboard_id: u64,
191 /// Preferred MIME type
192 pub mime_type: String,
193 }
194
195 #[derive(Debug, Clone, Serialize, Deserialize)]
196 pub struct ClipboardDataPayload {
197 /// Clipboard ID
198 pub clipboard_id: u64,
199 /// MIME type of data
200 pub mime_type: String,
201 /// Base64-encoded data
202 pub data: String,
203 /// Chunk index (if chunked)
204 pub chunk_index: Option<u32>,
205 /// Total chunks (if chunked)
206 pub total_chunks: Option<u32>,
207 }
208
209 // ============================================================================
210 // IPC Messages (CLI <-> Daemon)
211 // ============================================================================
212
213 /// Target for switch command - either direction or machine name
214 #[derive(Debug, Clone, Serialize, Deserialize)]
215 #[serde(untagged)]
216 pub enum SwitchTarget {
217 Direction(Direction),
218 MachineName(String),
219 }
220
221 /// IPC request from CLI to daemon
222 #[derive(Debug, Clone, Serialize, Deserialize)]
223 #[serde(tag = "type", rename_all = "snake_case")]
224 pub enum IpcRequest {
225 /// Request to move focus in a direction (keyboard navigation)
226 Move { direction: Direction },
227 /// Get daemon status
228 Status,
229 /// List connected peers
230 ListPeers,
231 /// Ping a specific peer by name
232 PingPeer { peer_name: String },
233
234 // Control transfer
235 /// Transfer control to another machine (by direction or name)
236 Switch { target: SwitchTarget },
237 /// Return control to this machine
238 Return,
239
240 // Input management
241 /// Force release input capture
242 Release,
243 /// Enable/disable edge barrier (prevent cursor from leaving)
244 SetBarrier { enabled: bool },
245
246 // Connection management
247 /// Disconnect from a peer
248 Disconnect { peer_name: String },
249 /// Force reconnection to a peer
250 Reconnect { peer_name: String },
251
252 // Configuration
253 /// Get current configuration
254 GetConfig,
255 /// Reload configuration from file
256 Reload,
257
258 // Daemon control
259 /// Graceful shutdown
260 Shutdown,
261 /// Get daemon logs
262 GetLogs {
263 /// Number of lines to retrieve (default: 50)
264 lines: Option<u32>,
265 /// Stream new lines (not yet implemented)
266 follow: bool,
267 },
268 }
269
270 /// IPC response from daemon to CLI
271 #[derive(Debug, Clone, Serialize, Deserialize)]
272 #[serde(tag = "type", rename_all = "snake_case")]
273 pub enum IpcResponse {
274 /// Move was handled - transferred to another machine
275 Transferred { to_machine: String },
276 /// Move should be handled locally (no edge crossing)
277 DoLocalMove,
278 /// Status response
279 Status {
280 state: String,
281 connected_peers: Vec<String>,
282 /// Daemon uptime in seconds
283 uptime_secs: u64,
284 /// This machine's name
285 machine_name: String,
286 },
287 /// Peer list
288 Peers { peers: Vec<PeerInfo> },
289 /// Ping result
290 PingResult {
291 peer_name: String,
292 /// Round-trip time in milliseconds (None if peer not connected)
293 latency_ms: Option<u64>,
294 /// Error message if ping failed
295 error: Option<String>,
296 },
297 /// Error occurred
298 Error { message: String },
299
300 // New responses for CLI expansion
301 /// Generic success response
302 Ok { message: String },
303 /// Configuration dump
304 Config { toml: String },
305 /// Log lines
306 Logs { lines: Vec<String> },
307 }
308
309 /// Info about a connected peer
310 #[derive(Debug, Clone, Serialize, Deserialize)]
311 pub struct PeerInfo {
312 pub name: String,
313 pub direction: Direction,
314 pub connected: bool,
315 /// Configured address for this peer
316 pub address: String,
317 /// Connection status: "connected", "disconnected", "connecting"
318 pub status: String,
319 }
320