use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct FrameUpdate {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub rgba_data: Vec<u8>,
}
#[async_trait]
pub trait DesktopProtocol: Send + Sync {
async fn start_frame_loop(
&self,
frame_tx: mpsc::UnboundedSender<FrameUpdate>,
cancel: CancellationToken,
) -> Result<()>;
async fn send_key(&self, key_code: u32, down: bool) -> Result<()>;
async fn send_pointer(&self, x: u16, y: u16, button_mask: u8) -> Result<()>;
async fn request_full_frame(&self) -> Result<()>;
async fn set_clipboard(&self, text: String) -> Result<()>;
fn desktop_size(&self) -> (u16, u16);
async fn resize(&mut self, width: u16, height: u16) -> Result<()>;
async fn disconnect(&mut self) -> Result<()>;
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
pub enum DesktopKind {
#[serde(rename = "RDP", alias = "rdp")]
Rdp,
#[serde(rename = "VNC", alias = "vnc")]
Vnc,
}
#[derive(Deserialize)]
pub struct DesktopConnectRequest {
pub connection_id: String,
pub protocol: DesktopKind,
pub host: String,
pub port: u16,
pub username: Option<String>, pub password: Option<String>,
pub domain: Option<String>, pub resolution: Option<String>,
pub color_depth: Option<u8>,
}
impl std::fmt::Debug for DesktopConnectRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DesktopConnectRequest")
.field("connection_id", &self.connection_id)
.field("protocol", &format_args!("{:?}", self.protocol))
.field("host", &self.host)
.field("port", &self.port)
.field("username", &self.username)
.field(
"password",
&self
.password
.as_ref()
.map(|_| "<redacted>")
.unwrap_or("<none>"),
)
.field("domain", &self.domain)
.field("resolution", &self.resolution)
.field("color_depth", &self.color_depth)
.finish()
}
}
#[derive(Debug, Serialize)]
pub struct DesktopConnectResponse {
pub width: u16,
pub height: u16,
}
#[derive(Clone)]
pub struct RdpConfig {
pub host: String,
pub port: u16,
pub username: String,
#[allow(dead_code)]
pub password: String,
pub domain: Option<String>,
pub width: u16,
pub height: u16,
}
impl std::fmt::Debug for RdpConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RdpConfig")
.field("host", &self.host)
.field("port", &self.port)
.field("username", &self.username)
.field("password", &"<redacted>")
.field("domain", &self.domain)
.field("width", &self.width)
.field("height", &self.height)
.finish()
}
}
#[derive(Clone)]
pub struct VncConfig {
pub host: String,
pub port: u16,
pub password: Option<String>,
pub color_depth: u8, }
impl std::fmt::Debug for VncConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VncConfig")
.field("host", &self.host)
.field("port", &self.port)
.field(
"password",
&self
.password
.as_ref()
.map(|_| "<redacted>")
.unwrap_or("<none>"),
)
.field("color_depth", &self.color_depth)
.finish()
}
}
impl DesktopConnectRequest {
pub fn parse_resolution(&self) -> (u16, u16) {
match self.resolution.as_deref() {
Some("1920x1080") => (1920, 1080),
Some("1280x720") => (1280, 720),
Some("1024x768") => (1024, 768),
_ => (1024, 768), }
}
pub fn to_rdp_config(&self) -> RdpConfig {
let (w, h) = self.parse_resolution();
RdpConfig {
host: self.host.clone(),
port: self.port,
username: self.username.clone().unwrap_or_default(),
password: self.password.clone().unwrap_or_default(),
domain: self.domain.clone(),
width: w,
height: h,
}
}
pub fn to_vnc_config(&self) -> VncConfig {
VncConfig {
host: self.host.clone(),
port: self.port,
password: self.password.clone(),
color_depth: self.color_depth.unwrap_or(24),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn desktop_kind_accepts_uppercase_and_lowercase_wire_values() {
assert_eq!(
serde_json::from_str::<DesktopKind>("\"RDP\"").unwrap(),
DesktopKind::Rdp
);
assert_eq!(
serde_json::from_str::<DesktopKind>("\"rdp\"").unwrap(),
DesktopKind::Rdp
);
assert_eq!(
serde_json::from_str::<DesktopKind>("\"VNC\"").unwrap(),
DesktopKind::Vnc
);
assert_eq!(
serde_json::from_str::<DesktopKind>("\"vnc\"").unwrap(),
DesktopKind::Vnc
);
}
}