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