# 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.
```toml
[dependencies]
veilus-human-input = "0.1"
rand = "0.8"
```
```rust
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).
| `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](https://veilus.io).