helios-remote/crates/common/src/protocol.rs

189 lines
5.5 KiB
Rust

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<String>,
},
/// Execute a shell command on the client
ExecRequest {
request_id: Uuid,
command: String,
/// Timeout in milliseconds. None = use client default (30s)
timeout_ms: Option<u64>,
},
/// 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<Uuid>,
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<String>,
},
/// 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<String> },
/// 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<WindowInfo>,
},
/// 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"),
}
}
}