use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tracing::{debug, instrument};
use viewpoint_cdp::CdpConnection;
use viewpoint_cdp::protocol::input::{
DispatchMouseEventParams, DispatchMouseWheelParams, MouseButton, MouseEventType,
};
use crate::error::LocatorError;
#[derive(Debug)]
struct MouseState {
x: f64,
y: f64,
button: Option<MouseButton>,
}
impl MouseState {
fn new() -> Self {
Self {
x: 0.0,
y: 0.0,
button: None,
}
}
}
#[derive(Debug)]
pub struct Mouse {
connection: Arc<CdpConnection>,
session_id: String,
state: Mutex<MouseState>,
}
impl Mouse {
pub(crate) fn new(connection: Arc<CdpConnection>, session_id: String) -> Self {
Self {
connection,
session_id,
state: Mutex::new(MouseState::new()),
}
}
pub fn move_(&self, x: f64, y: f64) -> MoveBuilder<'_> {
MoveBuilder {
mouse: self,
x,
y,
steps: 1,
}
}
pub fn click(&self, x: f64, y: f64) -> ClickBuilder<'_> {
ClickBuilder {
mouse: self,
x,
y,
button: MouseButton::Left,
click_count: 1,
delay: None,
}
}
#[instrument(level = "debug", skip(self), fields(x = x, y = y))]
pub async fn dblclick(&self, x: f64, y: f64) -> Result<(), LocatorError> {
debug!("Double-clicking at ({}, {})", x, y);
self.move_(x, y).send().await?;
self.down_internal(MouseButton::Left, 1).await?;
self.up_internal(MouseButton::Left, 1).await?;
self.down_internal(MouseButton::Left, 2).await?;
self.up_internal(MouseButton::Left, 2).await?;
Ok(())
}
pub fn down(&self) -> DownBuilder<'_> {
DownBuilder {
mouse: self,
button: MouseButton::Left,
click_count: 1,
}
}
pub fn up(&self) -> UpBuilder<'_> {
UpBuilder {
mouse: self,
button: MouseButton::Left,
click_count: 1,
}
}
#[instrument(level = "debug", skip(self), fields(delta_x = delta_x, delta_y = delta_y))]
pub async fn wheel(&self, delta_x: f64, delta_y: f64) -> Result<(), LocatorError> {
let state = self.state.lock().await;
let x = state.x;
let y = state.y;
drop(state);
debug!(
"Mouse wheel at ({}, {}): delta=({}, {})",
x, y, delta_x, delta_y
);
let params = DispatchMouseWheelParams {
event_type: MouseEventType::MouseWheel,
x,
y,
delta_x,
delta_y,
modifiers: None,
pointer_type: None,
};
self.connection
.send_command::<_, serde_json::Value>(
"Input.dispatchMouseEvent",
Some(params),
Some(&self.session_id),
)
.await?;
Ok(())
}
async fn move_internal(&self, x: f64, y: f64, steps: u32) -> Result<(), LocatorError> {
let (start_x, start_y) = {
let state = self.state.lock().await;
(state.x, state.y)
};
if steps <= 1 {
self.dispatch_move(x, y).await?;
} else {
for i in 1..=steps {
let progress = f64::from(i) / f64::from(steps);
let current_x = start_x + (x - start_x) * progress;
let current_y = start_y + (y - start_y) * progress;
self.dispatch_move(current_x, current_y).await?;
}
}
{
let mut state = self.state.lock().await;
state.x = x;
state.y = y;
}
Ok(())
}
async fn dispatch_move(&self, x: f64, y: f64) -> Result<(), LocatorError> {
let params = DispatchMouseEventParams::mouse_move(x, y);
self.connection
.send_command::<_, serde_json::Value>(
"Input.dispatchMouseEvent",
Some(params),
Some(&self.session_id),
)
.await?;
Ok(())
}
async fn down_internal(
&self,
button: MouseButton,
click_count: i32,
) -> Result<(), LocatorError> {
let (x, y) = {
let state = self.state.lock().await;
(state.x, state.y)
};
debug!(
"Mouse down at ({}, {}), button={:?}, count={}",
x, y, button, click_count
);
let mut params = DispatchMouseEventParams::mouse_down(x, y, button);
params.click_count = Some(click_count);
self.connection
.send_command::<_, serde_json::Value>(
"Input.dispatchMouseEvent",
Some(params),
Some(&self.session_id),
)
.await?;
{
let mut state = self.state.lock().await;
state.button = Some(button);
}
Ok(())
}
async fn up_internal(&self, button: MouseButton, click_count: i32) -> Result<(), LocatorError> {
let (x, y) = {
let state = self.state.lock().await;
(state.x, state.y)
};
debug!(
"Mouse up at ({}, {}), button={:?}, count={}",
x, y, button, click_count
);
let mut params = DispatchMouseEventParams::mouse_up(x, y, button);
params.click_count = Some(click_count);
self.connection
.send_command::<_, serde_json::Value>(
"Input.dispatchMouseEvent",
Some(params),
Some(&self.session_id),
)
.await?;
{
let mut state = self.state.lock().await;
state.button = None;
}
Ok(())
}
}
#[derive(Debug)]
pub struct MoveBuilder<'a> {
mouse: &'a Mouse,
x: f64,
y: f64,
steps: u32,
}
impl MoveBuilder<'_> {
#[must_use]
pub fn steps(mut self, steps: u32) -> Self {
self.steps = steps.max(1);
self
}
#[instrument(level = "debug", skip(self), fields(x = self.x, y = self.y, steps = self.steps))]
pub async fn send(self) -> Result<(), LocatorError> {
debug!(
"Moving mouse to ({}, {}) in {} steps",
self.x, self.y, self.steps
);
self.mouse.move_internal(self.x, self.y, self.steps).await
}
}
#[derive(Debug)]
pub struct ClickBuilder<'a> {
mouse: &'a Mouse,
x: f64,
y: f64,
button: MouseButton,
click_count: i32,
delay: Option<Duration>,
}
impl ClickBuilder<'_> {
#[must_use]
pub fn button(mut self, button: MouseButton) -> Self {
self.button = button;
self
}
#[must_use]
pub fn click_count(mut self, count: i32) -> Self {
self.click_count = count;
self
}
#[must_use]
pub fn delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
#[instrument(level = "debug", skip(self), fields(x = self.x, y = self.y, button = ?self.button))]
pub async fn send(self) -> Result<(), LocatorError> {
debug!(
"Clicking at ({}, {}), button={:?}",
self.x, self.y, self.button
);
self.mouse.move_(self.x, self.y).send().await?;
self.mouse
.down_internal(self.button, self.click_count)
.await?;
if let Some(delay) = self.delay {
tokio::time::sleep(delay).await;
}
self.mouse
.up_internal(self.button, self.click_count)
.await?;
Ok(())
}
}
#[derive(Debug)]
pub struct DownBuilder<'a> {
mouse: &'a Mouse,
button: MouseButton,
click_count: i32,
}
impl DownBuilder<'_> {
#[must_use]
pub fn button(mut self, button: MouseButton) -> Self {
self.button = button;
self
}
#[must_use]
pub fn click_count(mut self, count: i32) -> Self {
self.click_count = count;
self
}
#[instrument(level = "debug", skip(self), fields(button = ?self.button))]
pub async fn send(self) -> Result<(), LocatorError> {
self.mouse
.down_internal(self.button, self.click_count)
.await
}
}
#[derive(Debug)]
pub struct UpBuilder<'a> {
mouse: &'a Mouse,
button: MouseButton,
click_count: i32,
}
impl UpBuilder<'_> {
#[must_use]
pub fn button(mut self, button: MouseButton) -> Self {
self.button = button;
self
}
#[must_use]
pub fn click_count(mut self, count: i32) -> Self {
self.click_count = count;
self
}
#[instrument(level = "debug", skip(self), fields(button = ?self.button))]
pub async fn send(self) -> Result<(), LocatorError> {
self.mouse.up_internal(self.button, self.click_count).await
}
}