Skip to main content

rustenium/input/cdp/
touchscreen.rs

1use std::sync::Arc;
2use tokio::sync::Mutex;
3
4use rustenium_cdp_definitions::browser_protocol::input::commands::{
5    DispatchTouchEvent, DispatchTouchEventMethod, DispatchTouchEventParams, DispatchTouchEventType,
6};
7use rustenium_cdp_definitions::browser_protocol::input::types::TouchPoint;
8use rustenium_core::error::CdpCommandResultError;
9use rustenium_core::session::CdpSession;
10use rustenium_core::WebsocketConnectionTransport;
11use tokio::sync::Mutex as TokioMutex;
12
13use crate::error::cdp::InputError;
14use crate::input::mouse::Point;
15
16/// A single active touch point, returned by [`Touchscreen::touch_start`].
17pub struct TouchHandle {
18    session: Arc<TokioMutex<CdpSession<WebsocketConnectionTransport>>>,
19    touchscreen: Arc<Touchscreen>,
20    id: usize,
21    position: Arc<Mutex<Point>>,
22}
23
24impl TouchHandle {
25    fn new(
26        session: Arc<TokioMutex<CdpSession<WebsocketConnectionTransport>>>,
27        touchscreen: Arc<Touchscreen>,
28        id: usize,
29        x: f64,
30        y: f64,
31    ) -> Self {
32        Self {
33            session,
34            touchscreen,
35            id,
36            position: Arc::new(Mutex::new(Point { x: x.round(), y: y.round() })),
37        }
38    }
39
40    fn make_point(x: f64, y: f64, id: usize) -> TouchPoint {
41        let mut p = TouchPoint::new(x.round(), y.round());
42        p.id = Some(id as f64);
43        p.radius_x = Some(1.0);
44        p.radius_y = Some(1.0);
45        p.force = Some(1.0);
46        p
47    }
48
49    /// Move this touch point to a new position.
50    pub async fn move_to(&self, x: f64, y: f64) -> Result<(), InputError> {
51        let new_pos = Point { x: x.round(), y: y.round() };
52        let cmd = DispatchTouchEvent {
53            method: DispatchTouchEventMethod::DispatchTouchEvent,
54            params: DispatchTouchEventParams::new(
55                DispatchTouchEventType::TouchMove,
56                vec![Self::make_point(new_pos.x, new_pos.y, self.id)],
57            ),
58        };
59        self.session.lock().await.send(cmd).await
60            .map_err(|e| InputError::CommandError(CdpCommandResultError::SessionSendError(e)))?;
61        *self.position.lock().await = new_pos;
62        Ok(())
63    }
64
65    /// Release this touch point.
66    pub async fn end(&self) -> Result<(), InputError> {
67        let cmd = DispatchTouchEvent {
68            method: DispatchTouchEventMethod::DispatchTouchEvent,
69            params: DispatchTouchEventParams::new(DispatchTouchEventType::TouchEnd, vec![]),
70        };
71        self.session.lock().await.send(cmd).await
72            .map_err(|e| InputError::CommandError(CdpCommandResultError::SessionSendError(e)))?;
73        self.touchscreen.remove_handle(self.id).await;
74        Ok(())
75    }
76}
77
78/// Simulates touch gestures via `Input.dispatchTouchEvent`.
79#[derive(Clone)]
80pub struct Touchscreen {
81    session: Arc<TokioMutex<CdpSession<WebsocketConnectionTransport>>>,
82    touches: Arc<Mutex<Vec<usize>>>,
83    id_counter: Arc<Mutex<usize>>,
84}
85
86impl Touchscreen {
87    pub fn new(session: Arc<TokioMutex<CdpSession<WebsocketConnectionTransport>>>) -> Self {
88        Self {
89            session,
90            touches: Arc::new(Mutex::new(Vec::new())),
91            id_counter: Arc::new(Mutex::new(0)),
92        }
93    }
94
95    /// Start a new touch at `(x, y)` and return a handle to control it.
96    pub async fn touch_start(self: &Arc<Self>, x: f64, y: f64) -> Result<TouchHandle, InputError> {
97        let id = {
98            let mut counter = self.id_counter.lock().await;
99            let id = *counter;
100            *counter += 1;
101            id
102        };
103
104        let mut p = TouchPoint::new(x.round(), y.round());
105        p.id = Some(id as f64);
106        p.radius_x = Some(1.0);
107        p.radius_y = Some(1.0);
108        p.force = Some(1.0);
109
110        let cmd = DispatchTouchEvent {
111            method: DispatchTouchEventMethod::DispatchTouchEvent,
112            params: DispatchTouchEventParams::new(DispatchTouchEventType::TouchStart, vec![p]),
113        };
114        self.session.lock().await.send(cmd).await
115            .map_err(|e| InputError::CommandError(CdpCommandResultError::SessionSendError(e)))?;
116
117        self.touches.lock().await.push(id);
118
119        Ok(TouchHandle::new(self.session.clone(), self.clone(), id, x, y))
120    }
121
122    pub(crate) async fn remove_handle(&self, id: usize) {
123        self.touches.lock().await.retain(|&t| t != id);
124    }
125}