veilus-human-input 0.1.0

Human-like mouse paths, wheel scrolling and typing rhythm for browser automation: pure functions, parameters supplied by the caller.
Documentation
  • Coverage
  • 61.11%
    44 out of 72 items documented1 out of 20 items with examples
  • Size
  • Source code size: 54.5 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 564.2 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 5s Average build duration of successful builds.
  • all releases: 5s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • veilus/human-input
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • veilusbrowser

veilus-human-input

Human-like mouse paths, wheel scrolling and typing rhythm for browser automation, in Rust.

Every function is pure: it takes parameters and a random number generator and returns points, deltas and durations. Sending them — CDP Input.dispatchMouseEvent / Input.dispatchKeyEvent, WebDriver, OS events — is up to you. No I/O, no async runtime, and a seeded RNG makes every result reproducible.

[dependencies]
veilus-human-input = "0.1"
rand = "0.8"
use rand::{rngs::StdRng, SeedableRng};
use veilus_human_input::{keyboard, mouse, wheel};

let mut rng = StdRng::seed_from_u64(42);

// Mouse: from the last pointer position to a random point inside a button.
let p = mouse::MouseParams::default();
let target = mouse::pick_point((300.0, 200.0, 420.0, 240.0), &p, &mut rng);
for step in mouse::path((40.0, 40.0), target, 120.0, &p, &mut rng) {
    // send mouseMoved at (step.x, step.y), then wait step.delay
}
let hover = mouse::hover_pause(&p, &mut rng); // then mousePressed
let hold = mouse::press_hold(&p, &mut rng); // then mouseReleased

// Wheel: one scroll of ~1,800 px as macOS acceleration produces it.
let w = wheel::WheelParams::default();
for (delta_y, wait) in wheel::wheel_events(wheel::WheelStyle::MacAccelerated, 1_800, &w, &mut rng) {
    // send mouseWheel with deltaY = delta_y, then wait
}

// Typing: hold time and pause for each character, plus the physical key.
for (ch, t) in "Hello".chars().zip(keyboard::timings("Hello", &keyboard::TypingParams::default(), &mut rng)) {
    let key = keyboard::us_key(ch); // code "KeyH", keyCode 72, shift: true
    // keyDown, wait t.hold, keyUp, wait t.gap
}

The models

Mouse. A cubic Bézier curve whose two control points sit on the same side of the straight line, so the pointer bends one way instead of wobbling, with a smoothstep speed profile (accelerate, then decelerate). Movement time follows Fitts' law — far and small targets take longer — and points are spaced by a fixed polling interval, the way a real mouse reports its position. Long moves overshoot the target and come back in a short, almost straight correction. Some moves wander through a few waypoints first. The click point is random inside the middle of the box, never the exact center. While the button is held, the pointer sometimes drifts by a pixel.

Wheel. Notched: equal steps with pauses, as a wheel with detents. MacAccelerated: one flick ramps up from a tiny first delta to a plateau of large deltas a few milliseconds apart, and the distance actually scrolled varies a little around the request.

Typing. The pause between keys is log-normal (a long right tail), longer after a space or punctuation, with occasional hesitations; each key is held for a random time. us_key maps a character to KeyboardEvent.code, the Windows keyCode and the Shift state on a US layout. Characters outside that layout (accented letters, emoji) return None — they need an input method, which is not modelled. total_budget optionally caps the time for long text.

Every number is a field of MouseParams, WheelParams or TypingParams.

How close to a person?

We recorded one person on one page — 20 clicks on targets of random size and position, three typed sentences, one long scroll — then had the crate do the same tasks in a Chromium browser, and compared the two with a Kolmogorov–Smirnov test per feature and a cross-validated logistic-regression classifier (0.5 accuracy means it cannot tell them apart).

Parameters Mouse classifier Keyboard classifier
Default, unchanged 1.00 0.63 ± 0.08
Default, tick set to the recorded mouse's report interval 0.38 ± 0.14 0.59 ± 0.05

The first row is the lesson: set tick to the device you claim to be. The default is 8 ms (a 125 Hz USB mouse); the recorded mouse reported every 11 ms, and a single feature — the median gap between pointer events — separated the two perfectly. With that one field matched, no mouse feature stood out from the noise of a 20-segment sample.

Limits, stated plainly: one session of one person, on macOS, with a wheel mouse; 20 mouse segments and 142 key pairs. That says "close to this person", not "close to people". The defaults are reasonable starting points, not a measurement of anyone — tune them for the users you model. It makes no promise that automation goes unnoticed; it removes the most obvious machine signatures from input timing and geometry.

License

MIT or Apache-2.0, at your option.

Made by Veilus.