189 lines
5.5 KiB
Rust
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"),
|
|
}
|
|
}
|
|
}
|