use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Information about a single window on the client machine #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WindowInfo { pub id: u64, pub title: String, pub visible: bool, } /// Messages sent from the relay server to a connected client #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ServerMessage { /// Request a screenshot from the client ScreenshotRequest { request_id: Uuid }, /// Capture a specific window by its HWND (works even if behind other windows) WindowScreenshotRequest { request_id: Uuid, window_id: u64 }, /// Show a MessageBox on the client asking the user to do something. /// Blocks until the user clicks OK — use this when you need the user /// to perform a manual action before continuing. PromptRequest { request_id: Uuid, message: String, title: Option, }, /// Execute a shell command on the client ExecRequest { request_id: Uuid, command: String, /// Timeout in milliseconds. None = use client default (30s) timeout_ms: Option, }, /// Simulate a mouse click ClickRequest { request_id: Uuid, x: i32, y: i32, button: MouseButton, }, /// Type text on the client TypeRequest { request_id: Uuid, text: String, }, /// Acknowledge a client message Ack { request_id: Uuid }, /// Server-side error response Error { request_id: Option, message: String, }, /// List all visible windows on the client ListWindowsRequest { request_id: Uuid }, /// Minimize all windows (like Win+D) MinimizeAllRequest { request_id: Uuid }, /// Bring a window to the foreground FocusWindowRequest { request_id: Uuid, window_id: u64 }, /// Maximize a window and bring it to the foreground MaximizeAndFocusRequest { request_id: Uuid, window_id: u64 }, /// Request client version info VersionRequest { request_id: Uuid }, /// Upload a file to the client UploadRequest { request_id: Uuid, path: String, content_base64: String, }, /// Download a file from the client DownloadRequest { request_id: Uuid, path: String, }, /// Launch a program on the client (fire-and-forget) RunRequest { request_id: Uuid, program: String, args: Vec, }, /// Get the contents of the client's clipboard ClipboardGetRequest { request_id: Uuid }, /// Set the contents of the client's clipboard ClipboardSetRequest { request_id: Uuid, text: String }, } /// Messages sent from the client to the relay server #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ClientMessage { /// Client registers itself with optional display name Hello { label: Option }, /// Response to a screenshot request — base64-encoded PNG ScreenshotResponse { request_id: Uuid, image_base64: String, width: u32, height: u32, }, /// Response to an exec request ExecResponse { request_id: Uuid, stdout: String, stderr: String, exit_code: i32, }, /// Generic acknowledgement for click/type/minimize-all/focus/maximize Ack { request_id: Uuid }, /// Client error response Error { request_id: Uuid, message: String, }, /// Response to a list-windows request ListWindowsResponse { request_id: Uuid, windows: Vec, }, /// Response to a version request VersionResponse { request_id: Uuid, version: String, commit: String, }, /// Response to a download request DownloadResponse { request_id: Uuid, content_base64: String, size: u64, }, /// Response to a clipboard-get request ClipboardGetResponse { request_id: Uuid, text: String }, } /// Mouse button variants #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum MouseButton { Left, Right, Middle, } impl Default for MouseButton { fn default() -> Self { MouseButton::Left } } #[cfg(test)] mod tests { use super::*; #[test] fn test_server_message_serialization() { let msg = ServerMessage::ExecRequest { request_id: Uuid::nil(), command: "echo hello".into(), timeout_ms: None, }; let json = serde_json::to_string(&msg).unwrap(); assert!(json.contains("exec_request")); assert!(json.contains("echo hello")); } #[test] fn test_client_message_serialization() { let msg = ClientMessage::Hello { label: Some("test-pc".into()) }; let json = serde_json::to_string(&msg).unwrap(); assert!(json.contains("hello")); assert!(json.contains("test-pc")); } #[test] fn test_roundtrip() { let msg = ClientMessage::ExecResponse { request_id: Uuid::nil(), stdout: "hello\n".into(), stderr: String::new(), exit_code: 0, }; let json = serde_json::to_string(&msg).unwrap(); let decoded: ClientMessage = serde_json::from_str(&json).unwrap(); match decoded { ClientMessage::ExecResponse { exit_code, .. } => assert_eq!(exit_code, 0), _ => panic!("wrong variant"), } } }