use std::sync::Arc;
use rustenium_bidi_definitions::browsing_context::types::BrowsingContext;
use rustenium_core::transport::ConnectionTransport;
use rand::Rng;
use crate::error::bidi::InputError;
use super::bidi::touchscreen::Touchscreen;
use super::mouse::Point;
use super::touch::{Touch, SwipeOptions, ScrollOptions, Viewport};
use super::trajectory::{
generate_trajectory, generate_durations, random_curve_params, weighted_pick,
};
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(())
}
}