1use std::collections::HashMap;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Arc;
4use std::time::Duration;
5
6use futures_util::{SinkExt, StreamExt};
7use serde_json::{json, Value};
8use tokio::sync::{mpsc, oneshot, Mutex};
9use tokio::time::timeout;
10use tokio_tungstenite::{connect_async, tungstenite::Message};
11
12use crate::error::{RebindError, Result};
13use crate::types::*;
14
15type PendingMap = Arc<Mutex<HashMap<u64, oneshot::Sender<Value>>>>;
16
17pub struct RebindClient {
38 sender: mpsc::UnboundedSender<Message>,
39 pending: PendingMap,
40 next_id: Arc<AtomicU64>,
41 timeout_ms: u64,
42 event_senders: Arc<Mutex<HashMap<String, mpsc::UnboundedSender<Value>>>>,
44 _task: tokio::task::JoinHandle<()>,
46}
47
48impl RebindClient {
49 pub async fn connect(url: &str) -> Result<Self> {
51 Self::connect_with_options(url, "", 5000).await
52 }
53
54 pub async fn connect_with_token(url: &str, token: &str) -> Result<Self> {
55 Self::connect_with_options(url, token, 5000).await
56 }
57
58 pub async fn connect_with_options(url: &str, token: &str, timeout_ms: u64) -> Result<Self> {
59 let (ws_stream, _) = connect_async(url).await?;
60 let (mut write, mut read) = ws_stream.split();
61
62 let pending: PendingMap = Arc::new(Mutex::new(HashMap::new()));
63 let event_senders: Arc<Mutex<HashMap<String, mpsc::UnboundedSender<Value>>>> =
64 Arc::new(Mutex::new(HashMap::new()));
65 let next_id = Arc::new(AtomicU64::new(1));
66
67 let banner = match read.next().await {
69 Some(Ok(Message::Text(raw))) => serde_json::from_str::<Value>(&raw)?,
70 _ => return Err(RebindError::connection("no hello banner received")),
71 };
72 if banner.get("t").and_then(|v| v.as_str()) != Some("hello") {
73 return Err(RebindError::connection("unexpected banner"));
74 }
75
76 let (tx, mut rx) = mpsc::unbounded_channel::<Message>();
77
78 let writer_task = {
80 tokio::spawn(async move {
81 while let Some(msg) = rx.recv().await {
82 if write.send(msg).await.is_err() {
83 break;
84 }
85 }
86 })
87 };
88
89 let pending_reader = pending.clone();
90 let event_senders_reader = event_senders.clone();
91
92 let reader_task = tokio::spawn(async move {
94 while let Some(Ok(frame)) = read.next().await {
95 let Message::Text(raw) = frame else {
96 continue;
97 };
98 let Ok(msg) = serde_json::from_str::<Value>(&raw) else {
99 continue;
100 };
101 if let Some(id) = msg.get("id").and_then(|v| v.as_u64()) {
102 let mut map = pending_reader.lock().await;
103 if let Some(tx) = map.remove(&id) {
104 let _ = tx.send(msg);
105 }
106 } else if let Some(t) = msg.get("t").and_then(|v| v.as_str()) {
107 let senders = event_senders_reader.lock().await;
108 if let Some(sender) = senders.get(t) {
109 let _ = sender.send(msg);
110 }
111 }
112 }
113 writer_task.abort();
117 let _ = writer_task.await;
118 pending_reader.lock().await.clear();
119 event_senders_reader.lock().await.clear();
120 });
121
122 let client = Self {
123 sender: tx.clone(),
124 pending,
125 next_id,
126 timeout_ms,
127 event_senders,
128 _task: reader_task,
129 };
130
131 if !token.is_empty() {
133 let result = client
134 .rpc(json!({ "t": "auth", "token": token }))
135 .await?;
136 if result.get("ok").and_then(|v| v.as_bool()) != Some(true) {
137 return Err(RebindError::server("bad_token", "server rejected token"));
138 }
139 }
140
141 Ok(client)
142 }
143
144 pub async fn close(self) {
146 let _ = self.sender.send(Message::Close(None));
147 self._task.abort();
148 }
149
150 pub fn hid_down(&self, code: &str) {
153 self.one_shot(json!({ "t": "hid.down", "code": code }));
154 }
155
156 pub fn hid_up(&self, code: &str) {
157 self.one_shot(json!({ "t": "hid.up", "code": code }));
158 }
159
160 pub fn hid_press(&self, code: &str, hold_ms: u32) {
161 self.one_shot(json!({ "t": "hid.press", "code": code, "hold_ms": hold_ms }));
162 }
163
164 pub fn hid_type(&self, text: &str) {
165 self.one_shot(json!({ "t": "hid.type", "text": text }));
166 }
167
168 pub fn hid_move(&self, dx: i32, dy: i32) {
169 self.one_shot(json!({ "t": "hid.move", "dx": dx, "dy": dy }));
170 }
171
172 pub fn hid_move_to(&self, x: i32, y: i32) {
173 self.one_shot(json!({ "t": "hid.move_to", "x": x, "y": y }));
174 }
175
176 pub fn hid_scroll(&self, delta: i32) {
177 self.one_shot(json!({ "t": "hid.scroll", "delta": delta }));
178 }
179
180 pub async fn screen_pixel(&self, x: i32, y: i32) -> Result<Pixel> {
183 let r = self.rpc(json!({ "t": "screen.pixel", "x": x, "y": y })).await?;
184 Ok(Pixel {
185 r: r["r"].as_u64().unwrap_or(0) as u8,
186 g: r["g"].as_u64().unwrap_or(0) as u8,
187 b: r["b"].as_u64().unwrap_or(0) as u8,
188 })
189 }
190
191 pub async fn screen_resolution(&self) -> Result<Resolution> {
192 let r = self.rpc(json!({ "t": "screen.resolution" })).await?;
193 Ok(Resolution {
194 width: r["width"].as_u64().unwrap_or(0) as u32,
195 height: r["height"].as_u64().unwrap_or(0) as u32,
196 })
197 }
198
199 pub async fn system_mouse(&self) -> Result<(i32, i32)> {
200 let r = self.rpc(json!({ "t": "system.mouse" })).await?;
201 Ok((
202 r["x"].as_i64().unwrap_or(0) as i32,
203 r["y"].as_i64().unwrap_or(0) as i32,
204 ))
205 }
206
207 pub async fn system_window(&self) -> Result<WindowInfo> {
208 let r = self.rpc(json!({ "t": "system.window" })).await?;
209 let w = &r["window"];
210 Ok(WindowInfo {
211 title: w["title"].as_str().unwrap_or("").to_string(),
212 process: w["process"].as_str().unwrap_or("").to_string(),
213 x: w["x"].as_i64().unwrap_or(0) as i32,
214 y: w["y"].as_i64().unwrap_or(0) as i32,
215 width: w["width"].as_i64().unwrap_or(0) as i32,
216 height: w["height"].as_i64().unwrap_or(0) as i32,
217 })
218 }
219
220 pub async fn system_time(&self) -> Result<u64> {
221 let r = self.rpc(json!({ "t": "system.time" })).await?;
222 Ok(r["time_ms"].as_u64().unwrap_or(0))
223 }
224
225 pub async fn input_keys(&self) -> Result<Vec<String>> {
226 let r = self.rpc(json!({ "t": "input.keys" })).await?;
227 Ok(r["keys"]
228 .as_array()
229 .unwrap_or(&vec![])
230 .iter()
231 .filter_map(|v| v.as_str().map(str::to_string))
232 .collect())
233 }
234
235 pub async fn input_is_down(&self, code: &str) -> Result<bool> {
236 let r = self.rpc(json!({ "t": "input.is_down", "code": code })).await?;
237 Ok(r["down"].as_bool().unwrap_or(false))
238 }
239
240 pub async fn input_modifiers(&self) -> Result<Modifiers> {
241 let r = self.rpc(json!({ "t": "input.modifiers" })).await?;
242 let m = &r["modifiers"];
243 Ok(Modifiers {
244 shift: m["shift"].as_bool().unwrap_or(false),
245 ctrl: m["ctrl"].as_bool().unwrap_or(false),
246 alt: m["alt"].as_bool().unwrap_or(false),
247 win: m["win"].as_bool().unwrap_or(false),
248 })
249 }
250
251 pub async fn clipboard_get(&self) -> Result<String> {
252 let r = self.rpc(json!({ "t": "clipboard.get" })).await?;
253 Ok(r["text"].as_str().unwrap_or("").to_string())
254 }
255
256 pub async fn clipboard_set(&self, text: &str) -> Result<()> {
257 self.rpc(json!({ "t": "clipboard.set", "text": text })).await?;
258 Ok(())
259 }
260
261 pub async fn window_list(&self, filter: Option<&str>) -> Result<Vec<WindowInfo>> {
262 let req = match filter {
263 Some(f) => json!({ "t": "window.list", "filter": f }),
264 None => json!({ "t": "window.list" }),
265 };
266 let r = self.rpc(req).await?;
267 let windows = r["windows"]
268 .as_array()
269 .unwrap_or(&vec![])
270 .iter()
271 .map(|w| WindowInfo {
272 title: w["title"].as_str().unwrap_or("").to_string(),
273 process: w["process"].as_str().unwrap_or("").to_string(),
274 x: w["x"].as_i64().unwrap_or(0) as i32,
275 y: w["y"].as_i64().unwrap_or(0) as i32,
276 width: w["width"].as_i64().unwrap_or(0) as i32,
277 height: w["height"].as_i64().unwrap_or(0) as i32,
278 })
279 .collect();
280 Ok(windows)
281 }
282
283 pub async fn window_find(&self, title: &str) -> Result<Option<i64>> {
284 let r = self.rpc(json!({ "t": "window.find", "title": title })).await?;
285 Ok(r["handle"].as_i64())
286 }
287
288 pub async fn window_activate(&self, handle: i64) -> Result<()> {
289 self.rpc(json!({ "t": "window.activate", "handle": handle })).await?;
290 Ok(())
291 }
292
293 pub async fn ping(&self) -> Result<u64> {
294 let r = self.rpc(json!({ "t": "ping" })).await?;
295 Ok(r["time_ms"].as_u64().unwrap_or(0))
296 }
297
298 pub async fn call(&self, command: &str, mut args: Value) -> Result<Value> {
310 let Some(obj) = args.as_object_mut() else {
311 return Err(RebindError::Json(serde::de::Error::custom(
312 "call args must be a JSON object",
313 )));
314 };
315 obj.insert("t".to_string(), json!(command));
316 let mut reply = self.rpc(args).await?;
317 if let Some(obj) = reply.as_object_mut() {
318 obj.remove("id");
319 }
320 Ok(reply)
321 }
322
323 pub async fn mouse_events(&self) -> Result<mpsc::UnboundedReceiver<Point>> {
329 let raw_rx = self.subscribe_raw("mouse").await?;
330 let (tx, rx) = mpsc::unbounded_channel();
331 tokio::spawn(async move {
332 let mut raw = raw_rx;
333 while let Some(v) = raw.recv().await {
334 let x = v["x"].as_i64().unwrap_or(0) as i32;
335 let y = v["y"].as_i64().unwrap_or(0) as i32;
336 if tx.send(Point { x, y }).is_err() {
337 break;
338 }
339 }
340 });
341 Ok(rx)
342 }
343
344 pub async fn window_events(&self) -> Result<mpsc::UnboundedReceiver<WindowInfo>> {
346 let raw_rx = self.subscribe_raw("window").await?;
347 let (tx, rx) = mpsc::unbounded_channel();
348 tokio::spawn(async move {
349 let mut raw = raw_rx;
350 while let Some(v) = raw.recv().await {
351 let w = &v["window"];
352 let info = WindowInfo {
353 title: w["title"].as_str().unwrap_or("").to_string(),
354 process: w["process"].as_str().unwrap_or("").to_string(),
355 x: w["x"].as_i64().unwrap_or(0) as i32,
356 y: w["y"].as_i64().unwrap_or(0) as i32,
357 width: w["width"].as_i64().unwrap_or(0) as i32,
358 height: w["height"].as_i64().unwrap_or(0) as i32,
359 };
360 if tx.send(info).is_err() {
361 break;
362 }
363 }
364 });
365 Ok(rx)
366 }
367
368 fn one_shot(&self, mut msg: Value) {
371 if let Some(obj) = msg.as_object_mut() {
373 obj.remove("id");
374 }
375 let text = serde_json::to_string(&msg).unwrap_or_default();
376 let _ = self.sender.send(Message::Text(text.into()));
377 }
378
379 async fn rpc(&self, mut msg: Value) -> Result<Value> {
380 if self.sender.is_closed() {
381 return Err(RebindError::connection("not connected"));
382 }
383
384 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
385 if let Some(obj) = msg.as_object_mut() {
386 obj.insert("id".to_string(), json!(id));
387 }
388
389 let (tx, rx) = oneshot::channel();
390 self.pending.lock().await.insert(id, tx);
391
392 let text = serde_json::to_string(&msg)?;
393 if self.sender.send(Message::Text(text.into())).is_err() {
394 self.pending.lock().await.remove(&id);
395 return Err(RebindError::connection("not connected"));
396 }
397
398 let resp = match timeout(Duration::from_millis(self.timeout_ms), rx).await {
399 Ok(reply) => reply
400 .map_err(|_| RebindError::connection("connection closed while waiting for RPC"))?,
401 Err(_) => {
402 self.pending.lock().await.remove(&id);
403 return Err(RebindError::timeout(
404 msg.get("t")
405 .and_then(|v| v.as_str())
406 .unwrap_or("unknown")
407 .to_string(),
408 ));
409 }
410 };
411
412 if let Some(err) = resp.get("error") {
413 let code = err["code"].as_str().unwrap_or("unknown");
414 let message = err["message"].as_str().unwrap_or("");
415 return Err(RebindError::server(code, message));
416 }
417
418 Ok(resp)
419 }
420
421 async fn subscribe_raw(&self, event: &str) -> Result<mpsc::UnboundedReceiver<Value>> {
422 let (tx, rx) = mpsc::unbounded_channel();
423 self.event_senders.lock().await.insert(event.to_string(), tx);
424 self.rpc(json!({ "t": "subscribe", "events": [event] })).await?;
425 Ok(rx)
426 }
427}