Backends¶
Each backend is a consumer of an EventStream — the timing engine never performs I/O itself (see Home → Core design decision). Every optional dependency is lazily imported inside the function that needs it, so import humaninput never requires any of them.
| backend | dependency | does |
|---|---|---|
terminal |
none | replays typing into stdout with real timing (the default) |
json |
none | dumps the raw event stream |
svg |
none | animated SVG with a blinking cursor, no JS |
css_keyframes |
none | a CSS @keyframes typing animation |
asciinema |
none | writes a .cast v2 file |
matplotlib |
matplotlib |
plots trajectories and interval histograms |
pynput |
pynput |
drives the real OS keyboard/mouse |
browser |
none (BYO Playwright Page) |
raw CDP key/mouse dispatch into a live page |
touch |
none (BYO Playwright Page) |
raw CDP touch dispatch into a live page |
terminal¶
from humaninput.backends import terminal
terminal.play(stream, speed=1.0, out=None, width=None)
Writes keydown characters at the timestamps in the stream, scaled by 1/speed. Row-aware redraw via ANSI — once the buffer wraps onto multiple terminal rows, a naive clear-line-and-redraw only fixes the last row, so this repaints all wrapped rows. width defaults to the real terminal width.
json¶
from humaninput.backends import json as json_backend
json_backend.to_json(stream, indent=2) -> str
json_backend.write(stream, path_or_file, indent=2)
Dumps the raw event stream (EventStream.to_dicts()) — the format every other backend, and humaninput fit's round-trip tests, treat as the canonical interchange shape.
svg¶
from humaninput.backends import svg
svg.render(stream, **kwargs) -> str
svg.write(stream, path, **kwargs)
Animated SVG with a blinking cursor and no JavaScript — safe to drop straight into a README or any Markdown renderer that allows inline SVG (this is what generates the animation at the top of the README).
css_keyframes¶
from humaninput.backends import css_keyframes
css_keyframes.render(stream, **kwargs) -> str
A CSS @keyframes typing animation for embedding directly in a web page's stylesheet.
asciinema¶
from humaninput.backends import asciinema
asciinema.write(stream, path, **kwargs)
Writes an asciinema .cast v2 file — playable with the asciinema CLI or embeddable via asciinema-player.
matplotlib¶
from humaninput.backends import matplotlib as mpl_backend
mpl_backend.plot_trajectory(stream, show=True, ax=None)
mpl_backend.plot_interval_histogram(stream, profile=None, median_ms=None, show=True, ax=None)
Used for the validation figures in the README and for eyeballing a generated mouse trajectory or interval distribution during profile tuning.
pynput¶
from humaninput.backends import pynput as pynput_backend
pynput_backend.play_keys(stream) # KeyEvent stream
pynput_backend.play_mouse(stream) # MouseEvent stream
Drives the real OS keyboard/mouse. pip install humaninput[pynput]. Prints a warning on first use of either function per process. Implements a fail-safe modeled on pyautogui's: if the real cursor is found at a screen corner when a mouse event is about to be dispatched, playback aborts immediately (RuntimeError).
This is what humaninput/tools/automate.py and shadow_writer.py are built on — see Tools.
browser¶
from humaninput.backends import browser
browser.play_keys(stream, page) # KeyEvent stream
browser.play_mouse(stream, page) # MouseEvent stream
page is a live Playwright Page (sync API) that the caller already created — this module never imports playwright itself, it just calls page.context.new_cdp_session(page) and sends raw CDP commands (Input.dispatchKeyEvent, Input.dispatchMouseEvent). This is deliberate: Playwright's own page.type()/page.click() have their own internal pacing, which would throw away this library's timing model. Dispatching at the CDP layer preserves it.
click/dblclick marker events in a MouseEvent stream are skipped — the down/up pair already dispatched is what a real click is made of.
from playwright.sync_api import sync_playwright
from humaninput import Typist, profiles
from humaninput.backends import browser
with sync_playwright() as p:
page = p.chromium.launch().new_page()
page.goto("https://example.com")
stream = Typist(profile=profiles.load("touch_typist"), seed=1).type("hello")
browser.play_keys(stream, page)
touch¶
from humaninput.backends import touch
touch.play_touch(stream, page) # TouchEvent stream
Same no-import pattern as browser, dispatching Input.dispatchTouchEvent. tap marker events are skipped — touchstart/touchend are the real input. Pairs with humaninput.touch.Touch (tap, drag) and the mobile_thumbs profile's [touch] section — see The model → Touch.
from humaninput.touch import Touch
from humaninput.backends import touch as touch_backend
t = Touch(profile=profiles.load("mobile_thumbs"), seed=1)
touch_backend.play_touch(t.tap(120, 640), page)