1use anyhow::Result;
2use async_trait::async_trait;
3use serde::{Deserialize, Serialize};
4use tokio::sync::mpsc;
5use tokio_util::sync::CancellationToken;
6
7#[allow(dead_code)]
15#[derive(Clone, Debug)]
16pub struct FrameUpdate {
17 pub x: u16,
18 pub y: u16,
19 pub width: u16,
20 pub height: u16,
21 pub rgba_data: Vec<u8>,
23}
24
25#[async_trait]
30pub trait DesktopProtocol: Send + Sync {
31 async fn start_frame_loop(
34 &self,
35 frame_tx: mpsc::UnboundedSender<FrameUpdate>,
36 cancel: CancellationToken,
37 ) -> Result<()>;
38
39 async fn send_key(&self, key_code: u32, down: bool) -> Result<()>;
41
42 async fn send_pointer(&self, x: u16, y: u16, button_mask: u8) -> Result<()>;
44
45 async fn request_full_frame(&self) -> Result<()>;
47
48 async fn set_clipboard(&self, text: String) -> Result<()>;
50
51 fn desktop_size(&self) -> (u16, u16);
53
54 async fn resize(&mut self, width: u16, height: u16) -> Result<()>;
58
59 async fn disconnect(&mut self) -> Result<()>;
61}
62
63#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
72pub enum DesktopKind {
73 #[serde(rename = "RDP", alias = "rdp")]
74 Rdp,
75 #[serde(rename = "VNC", alias = "vnc")]
76 Vnc,
77}
78
79#[derive(Deserialize)]
81pub struct DesktopConnectRequest {
82 pub connection_id: String,
83 pub protocol: DesktopKind,
84 pub host: String,
85 pub port: u16,
86 pub username: Option<String>, pub password: Option<String>,
88 pub domain: Option<String>, pub resolution: Option<String>,
91 pub color_depth: Option<u8>,
93}
94
95impl std::fmt::Debug for DesktopConnectRequest {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("DesktopConnectRequest")
98 .field("connection_id", &self.connection_id)
99 .field("protocol", &format_args!("{:?}", self.protocol))
100 .field("host", &self.host)
101 .field("port", &self.port)
102 .field("username", &self.username)
103 .field(
104 "password",
105 &self
106 .password
107 .as_ref()
108 .map(|_| "<redacted>")
109 .unwrap_or("<none>"),
110 )
111 .field("domain", &self.domain)
112 .field("resolution", &self.resolution)
113 .field("color_depth", &self.color_depth)
114 .finish()
115 }
116}
117
118#[derive(Debug, Serialize)]
120pub struct DesktopConnectResponse {
121 pub width: u16,
122 pub height: u16,
123}
124
125#[derive(Clone)]
130pub struct RdpConfig {
131 pub host: String,
132 pub port: u16,
133 pub username: String,
134 #[allow(dead_code)]
137 pub password: String,
138 pub domain: Option<String>,
139 pub width: u16,
140 pub height: u16,
141}
142
143impl std::fmt::Debug for RdpConfig {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("RdpConfig")
146 .field("host", &self.host)
147 .field("port", &self.port)
148 .field("username", &self.username)
149 .field("password", &"<redacted>")
150 .field("domain", &self.domain)
151 .field("width", &self.width)
152 .field("height", &self.height)
153 .finish()
154 }
155}
156
157#[derive(Clone)]
158pub struct VncConfig {
159 pub host: String,
160 pub port: u16,
161 pub password: Option<String>,
162 pub color_depth: u8, }
164
165impl std::fmt::Debug for VncConfig {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("VncConfig")
168 .field("host", &self.host)
169 .field("port", &self.port)
170 .field(
171 "password",
172 &self
173 .password
174 .as_ref()
175 .map(|_| "<redacted>")
176 .unwrap_or("<none>"),
177 )
178 .field("color_depth", &self.color_depth)
179 .finish()
180 }
181}
182
183impl DesktopConnectRequest {
184 pub fn parse_resolution(&self) -> (u16, u16) {
186 match self.resolution.as_deref() {
187 Some("1920x1080") => (1920, 1080),
188 Some("1280x720") => (1280, 720),
189 Some("1024x768") => (1024, 768),
190 _ => (1024, 768), }
192 }
193
194 pub fn to_rdp_config(&self) -> RdpConfig {
196 let (w, h) = self.parse_resolution();
197 RdpConfig {
198 host: self.host.clone(),
199 port: self.port,
200 username: self.username.clone().unwrap_or_default(),
201 password: self.password.clone().unwrap_or_default(),
202 domain: self.domain.clone(),
203 width: w,
204 height: h,
205 }
206 }
207
208 pub fn to_vnc_config(&self) -> VncConfig {
210 VncConfig {
211 host: self.host.clone(),
212 port: self.port,
213 password: self.password.clone(),
214 color_depth: self.color_depth.unwrap_or(24),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn desktop_kind_accepts_uppercase_and_lowercase_wire_values() {
225 assert_eq!(
226 serde_json::from_str::<DesktopKind>("\"RDP\"").unwrap(),
227 DesktopKind::Rdp
228 );
229 assert_eq!(
230 serde_json::from_str::<DesktopKind>("\"rdp\"").unwrap(),
231 DesktopKind::Rdp
232 );
233 assert_eq!(
234 serde_json::from_str::<DesktopKind>("\"VNC\"").unwrap(),
235 DesktopKind::Vnc
236 );
237 assert_eq!(
238 serde_json::from_str::<DesktopKind>("\"vnc\"").unwrap(),
239 DesktopKind::Vnc
240 );
241 }
242}