human-presence
Keep your computer "in use" with pointer motion that actually looks human.
macOS · Windows · Linux (X11)
Inspired by keep-presence by carrot69 — https://github.com/carrot69/keep-presence
Most keep-awake tools jump the cursor one pixel and call it a day. That is a teleport: no path, no acceleration, always the same direction. This one generates real trajectories instead.
human-presence -g 25 90
--------------------------------------------------------------
human-presence gap 25-90s speed x1
display 0: 1512x982 at (0,0)
display 1: 1920x1080 at (1512,-459)
Ctrl-C to stop.
--------------------------------------------------------------
10:54:52 micro 20px -> (1185,452)
10:55:33 hop 79px -> (1257,518)
10:55:33 scrolled 4 notches up
10:56:41 travel 612px -> (410,244)
What makes the motion human
| Property | How it is modelled |
|---|---|
| Path shape | Cubic Bézier with a randomised bow. ~75% single arc, ~25% lazy S. Never a straight line. |
| Speed | Minimum-jerk profile — zero velocity at both ends, so it eases in and eases out. |
| Duration | Fitts's law, MT = a + b·log2(D/W + 1), with per-move noise. 50 px ≈ 280 ms, 1200 px ≈ 730 ms. |
| Precision | Per-step tremor, plus overshoot and a corrective submovement on ~35% of long reaches. |
| Hesitation | Occasional mid-flight pause on long travels, like a person re-aiming. |
| Timing | Log-uniform gaps, so short pauses outnumber long ones. Sometimes a burst of 2–4 quick actions. |
| Target choice | ~45% micro-drift (2–25 px), ~35% short hop (60–350 px), ~20% long travel across the screen. |
| Scrolling | Bursts of 2–6 notches in one direction, each weaker than the last. |
Supported systems
| System | Status | How it injects input | Extra install |
|---|---|---|---|
| macOS | supported | Quartz event tap | pyobjc (automatic) |
| Windows 10/11 | supported | SendInput |
none — ctypes is built in |
| Linux on Xorg | supported | XTest | system X libraries |
| Linux on Wayland | not possible | — | — |
Every supported system is checked in CI on each push, and not only for "does it import". One test injects a real pointer move and asserts the operating system's own idle clock resets — the single behaviour the whole tool depends on. If that ever stops being true on a platform, the build fails.
Wayland users, read this. Ubuntu has shipped Wayland as the default desktop
since 22.04, and Wayland blocks programs from injecting input. That is a
security decision, not a missing feature, and no version of this tool can work
around it. To use human-presence on Ubuntu: log out, click the gear icon on the
login screen, choose "Ubuntu on Xorg", and log back in. Run
echo $XDG_SESSION_TYPE to check — it should print x11. If you launch under
Wayland the tool tells you this and exits, rather than running forever with no
effect.
Install
Requires Python 3.9+.
pip install human-presence
On Ubuntu, the X libraries are usually already present. If not:
sudo apt install libx11-6 libxtst6 libxss1 libxinerama1
From a checkout:
git clone https://github.com/agn-7/human-presence.git
cd human-presence
pip install -e '.[dev]'
Permissions
macOS will not deliver synthetic pointer events until you allow it. Open System Settings → Privacy & Security → Accessibility and enable whichever app runs the command — Terminal, iTerm, or your IDE. Without permission the events are silently dropped.
Windows and Linux/Xorg need no special permission.
On any system, use --verify to check it is working: if the idle clock does
not reset to 0.0s after an action, the events are not landing.
Usage
human-presence [-g MIN MAX] [--speed X] [--hours HH:MM-HH:MM] [--display N]
[--margin PX] [--no-scroll] [--keys] [--yield-seconds S]
[--dry-run] [--verify] [--seed N] [-q]
| Flag | Meaning |
|---|---|
-g, --gap MIN MAX |
Seconds of real inactivity before acting. A fresh random value in this range every time. Default 25 90. |
--speed X |
Movement speed multiplier. 0.7 is slower and calmer. Default 1.0. |
--hours HH:MM-HH:MM |
Only run inside this daily window. Overnight wrap works: 22:00-06:00. |
--display N |
Pin to one display. Default: whichever screen holds the cursor. |
--margin PX |
Pixels to keep clear of every screen edge. Default 60. |
--no-scroll |
Never scroll. |
--keys |
Also tap shift occasionally. Off by default. |
--yield-seconds S |
How long to stand down after you touch the mouse. Default 120. |
--dry-run |
Print the plan, never touch the cursor. |
--verify |
Print the system idle clock before and after each action. |
--seed N |
Fix the RNG, for reproducible tests. |
-q, --quiet |
Only log actions. |
Choosing a gap
Keep the maximum below your screen-sleep setting (System Settings → Lock Screen).
| Command | Behaviour |
|---|---|
-g 25 90 |
Good default. |
-g 60 240 |
Calmer. Use when screen sleep is 5 minutes or more. |
-g 10 30 |
Busy. Only when something needs very frequent activity. |
Preview before the real run
human-presence --dry-run -g 3 6
Nothing moves. You only see what it would do.
How idle is detected
Each platform reads the same clock its own screensaver reads, so keyboard activity counts as presence, not just the mouse. The tool stays quiet while you are genuinely working, and only acts once you actually stop.
| System | Idle clock |
|---|---|
| macOS | CGEventSourceSecondsSinceLastEventType |
| Windows | GetLastInputInfo |
| Linux/Xorg | XScreenSaverQueryInfo |
Because that clock is also reset by the tool's own events, a gap is measured from whichever came last: you, or it.
Staying out of the way
- You always win. Touch the mouse and it stands down for
--yield-seconds. It also aborts a move mid-flight if the cursor stops tracking the path. - Off the furniture. Every point is clamped into a safe box, inset from all edges. On macOS that also means below the menu bar and clear of the hot corners that trigger Mission Control. On Windows and Linux the tool uses the work area the system reports, so the taskbar, dock and panel are already excluded wherever you keep them.
- Multi-display aware on all three systems, including stacked or offset monitor layouts and screens positioned left of the origin.
Run it at login
macOS
The repository ships a ready template at packaging/com.agn7.human-presence.plist.
Copy it to ~/Library/LaunchAgents/, edit the paths and flags inside, then:
launchctl load ~/Library/LaunchAgents/com.agn7.human-presence.plist
launchctl list | grep human-presence # confirm it is running
launchctl unload ~/Library/LaunchAgents/com.agn7.human-presence.plist # stop
Logs go to /tmp/human-presence.log and /tmp/human-presence.err.log.
launchd does not expand ~, so use absolute paths if you change them.
Grant Accessibility permission to /usr/local/bin/python3 (or whichever
interpreter the plist points at), not to Terminal.
Windows
Press Win+R, run shell:startup, and put a shortcut there pointing at:
pythonw -m human_presence --gap 25 90 --quiet
pythonw runs it without a console window.
Linux (Xorg)
Add it to your desktop's Startup Applications, with the command:
human-presence --gap 25 90 --quiet
Development
pip install -e '.[dev]'
pytest
ruff check .
Tests cover the movement model, the safe-area maths, the platform arithmetic (absolute-coordinate mapping, tick-counter wrap, Wayland detection) and the CLI. They all run on every operating system, because the movement model is driven through an in-memory fake pointer rather than a real cursor.
The layout:
src/human_presence/
cli.py movement model + CLI — no OS calls at all
backends/
base.py the Pointer interface, and the fake used by tests
darwin.py Quartz
win32.py SendInput via ctypes
x11.py XTest via ctypes
Adding a platform means writing one file in backends/. Nothing in cli.py
changes.
Credits and prior art
This project was inspired by keep-presence by carrot69, released under CC0:
That project supplied the idea — a small program that keeps a machine looking occupied while you are away — and the shape of the command-line interface. Thanks to its author.
human-presence is an independent implementation rather than a fork. No code was copied. The differences:
| keep-presence | human-presence | |
|---|---|---|
| Movement | one-pixel teleport, 4 fixed directions | Bézier path, minimum-jerk speed, Fitts timing |
| Idle detection | compares cursor position between loops | system idle clock, so typing counts too |
| Randomness | the wait interval only | wait, distance, direction, duration, curvature |
| Platform library | pynput |
direct system calls, no pynput |
| Platforms | macOS, Windows, Linux | macOS, Windows, Linux/Xorg |
License
MIT. The full text is in the LICENSE file of the source distribution.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file human_presence-0.2.0.tar.gz.
File metadata
- Download URL: human_presence-0.2.0.tar.gz
- Upload date:
- Size: 29.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dcb4d281fe909b678304473a1df36b0928827e360c9ccd647e3343ec8a681f9e
|
|
| MD5 |
7512de18e33f34b41390d52a1db443bf
|
|
| BLAKE2b-256 |
b3352f070485bad08916430395552f09cde1c2aa174c5d0886f39a17b81cd795
|
Provenance
The following attestation bundles were made for human_presence-0.2.0.tar.gz:
Publisher:
publish.yml on agn-7/human-presence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
human_presence-0.2.0.tar.gz -
Subject digest:
dcb4d281fe909b678304473a1df36b0928827e360c9ccd647e3343ec8a681f9e - Sigstore transparency entry: 2615869419
- Sigstore integration time:
-
Permalink:
agn-7/human-presence@557fd92d7faf551c6c024459b6179e11233f222e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/agn-7
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@557fd92d7faf551c6c024459b6179e11233f222e -
Trigger Event:
release
-
Statement type:
File details
Details for the file human_presence-0.2.0-py3-none-any.whl.
File metadata
- Download URL: human_presence-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d3c94f86e29b58d7c5cbbee60dd632cdafc2dc9a0c6a78e289a90e7bf0036ec
|
|
| MD5 |
9eb4b038b2e106266f2293f3815cb298
|
|
| BLAKE2b-256 |
99d68d7b730394070572d397d2995941ce626b5f4527719e48fa924da221007a
|
Provenance
The following attestation bundles were made for human_presence-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on agn-7/human-presence
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
human_presence-0.2.0-py3-none-any.whl -
Subject digest:
8d3c94f86e29b58d7c5cbbee60dd632cdafc2dc9a0c6a78e289a90e7bf0036ec - Sigstore transparency entry: 2615869432
- Sigstore integration time:
-
Permalink:
agn-7/human-presence@557fd92d7faf551c6c024459b6179e11233f222e -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/agn-7
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@557fd92d7faf551c6c024459b6179e11233f222e -
Trigger Event:
release
-
Statement type: