use rand::Rng;
use rustenium_bidi_definitions::browsing_context::types::BrowsingContext;
use rustenium_core::transport::ConnectionTransport;
use std::sync::Arc;
use super::bidi::touchscreen::Touchscreen;
use super::mouse::Point;
use super::touch::{ScrollOptions, SwipeOptions, Touch, Viewport};
use super::trajectory::{
generate_durations, generate_trajectory, random_curve_params, weighted_pick,
};
use crate::error::bidi::InputError;
const ZONE_WEIGHTS: &[(usize, f64)] = &[
(0, 0.02), (1, 0.05), (2, 0.02), (3, 0.05), (4, 0.15), (5, 0.05), (6, 0.07), (7, 0.45), (8, 0.07), ];
fn zone_origin(rng: &mut impl Rng, viewport: &Viewport) -> Point {
let zone = weighted_pick(rng, ZONE_WEIGHTS);
let col = zone % 3;
let row = zone / 3;
let w3 = viewport.width / 3.0;
let h3 = viewport.height / 3.0;
let x_lo = col as f64 * w3;
let y_lo = row as f64 * h3;
Point {
x: rng.random_range(x_lo..(x_lo + w3)),
y: rng.random_range(y_lo..(y_lo + h3)),
}
}
pub struct HumanTouchscreen<OT: ConnectionTransport> {
touchscreen: Arc<Touchscreen<OT>>,
}
impl<OT: ConnectionTransport> HumanTouchscreen<OT> {
pub fn new(touchscreen: Arc<Touchscreen<OT>>) -> Self {
Self { touchscreen }
}
async fn swipe_internal(
&self,
from: Point,
to: Point,
duration_ms: u64,
context: &BrowsingContext,
) -> Result<(), InputError> {
let params = random_curve_params(from, to);
let trajectory = generate_trajectory(from, to, ¶ms);
if trajectory.points.is_empty() {
return Ok(());
}
let durations = generate_durations(
trajectory.points.len(),
duration_ms as f64 / 1000.0,
(0.004, 0.025),
);
let first = trajectory.points[0];
let handle = self
.touchscreen
.touch_start(first.x.max(0.0), first.y.max(0.0), context, None)
.await?;
for (i, pt) in trajectory.points.iter().enumerate().skip(1) {
tokio::time::sleep(tokio::time::Duration::from_secs_f64(durations[i])).await;
handle
.move_to(pt.x.max(0.0), pt.y.max(0.0), context)
.await?;
}
let lift_delay = {
let mut rng = rand::rng();
10 + rng.random_range(0..30_u64)
};
tokio::time::sleep(tokio::time::Duration::from_millis(lift_delay)).await;
handle.end(context).await?;
Ok(())
}
}
impl<OT: ConnectionTransport> Touch for HumanTouchscreen<OT> {
async fn tap(&self, point: Point, context: &BrowsingContext) -> Result<(), InputError> {
let handle = self
.touchscreen
.touch_start(point.x, point.y, context, None)
.await?;
let hold = {
let mut rng = rand::rng();
50 + rng.random_range(0..60_u64)
};
tokio::time::sleep(tokio::time::Duration::from_millis(hold)).await;
handle.end(context).await?;
Ok(())
}
async fn swipe(
&self,
from: Point,
to: Point,
context: &BrowsingContext,
options: SwipeOptions,
) -> Result<(), InputError> {
let duration_ms = options.duration_ms.unwrap_or(600);
self.swipe_internal(from, to, duration_ms, context).await
}
async fn scroll_to(
&self,
point: Point,
viewport: &Viewport,
context: &BrowsingContext,
options: ScrollOptions,
) -> Result<(), InputError> {
let duration_ms = options.duration_ms.unwrap_or(600);
let origin = {
let mut rng = rand::rng();
zone_origin(&mut rng, viewport)
};
self.swipe_internal(origin, point, duration_ms, context)
.await
}
async fn long_press(
&self,
point: Point,
hold_ms: u64,
context: &BrowsingContext,
) -> Result<(), InputError> {
let handle = self
.touchscreen
.touch_start(point.x, point.y, context, None)
.await?;
let jitter = {
let mut rng = rand::rng();
rng.random_range(0..50_u64)
};
tokio::time::sleep(tokio::time::Duration::from_millis(hold_ms + jitter)).await;
handle.end(context).await?;
Ok(())
}
}