An app that makes every interaction with your trackpoint awkward.
moanpoint listens to your laptop's pointing stick (ThinkPad TrackPoint, pointing stick, etc.) and plays moaning sounds whose volume scales with how hard you push it.
One command. It clones the repo, installs uv and any missing build tools, and sets up the background service:
curl -LsSf https://raw.githubusercontent.com/nicolasgutierrezdev/moanpoint/main/install.sh | bashWant a specific voice? Pass arguments through bash -s --:
curl -LsSf https://raw.githubusercontent.com/nicolasgutierrezdev/moanpoint/main/install.sh | bash -s -- --voice miaPrefer to read the script first (recommended, as always):
git clone https://github.com/nicolasgutierrezdev/moanpoint.git
cd moanpoint
./install.shThe installer will:
- Install missing build tools for your distro (Arch, Debian/Ubuntu, Fedora, openSUSE)
- Install
uvif you don't have it - Add your user to the
inputgroup (needed to read the pointing stick device) - Install Python dependencies into a project-local
.venv, reusing your system Python when it is usable - Install, enable and start a systemd user service
It is safe to re-run at any time.
Note: If the installer added you to the
inputgroup, log out and back in, then runsystemctl --user restart moanpoint.
Usage: ./install.sh [options]
Options:
--voice NAME Install with a specific voice (e.g. mia, sofia).
--linger Enable lingering so moanpoint starts at boot without login.
--no-service Install dependencies only, skip the systemd service.
-h, --help Show this help.
main.py carries PEP 723 inline dependencies, so uv can run it straight from a fresh clone — no virtualenv, no uv sync, nothing to clean up afterwards:
uv run main.py- Linux (evdev and udev are Linux-only)
- A laptop with a pointing stick / TrackPoint
gitandcurl- A C compiler and kernel headers (the installer installs these for you)
- systemd — optional, only for running as a background service
- A working audio setup (PulseAudio or PipeWire)
Python itself is not a prerequisite. The installer uses your system Python if it is 3.10+ and has
the development headers (Python.h); otherwise uv downloads a suitable interpreter automatically.
The installer handles this. The table is only for manual setups:
| Distro | Command |
|---|---|
| Arch / Manjaro | sudo pacman -S --needed base-devel git curl uv |
| Debian / Ubuntu | sudo apt install build-essential git curl |
| Fedora | sudo dnf install gcc kernel-headers git curl |
| openSUSE | sudo zypper install gcc linux-glibc-devel git curl |
Arch ships uv in the extra repository. On other distros the installer pulls it from astral.sh.
Managed by uv and pinned in uv.lock. No manual pip installs needed.
| Package | Version | Purpose |
|---|---|---|
| evdev | >=1.9.3 | Read raw input events from the pointing stick |
| pygame-ce | >=2.5.7 | Load and play WAV audio, control volume |
evdev has no prebuilt wheels, so it compiles a small C extension during installation — that's the only reason a compiler is needed.
pygame-ce is the community fork of pygame and a drop-in replacement — the code still
does import pygame. It is used here because it ships wheels for current Python releases. Upstream
pygame has none past 3.13, so it would build from source and quietly leave out pygame.mixer unless
SDL2_mixer's headers happen to be installed, giving you an install with no sound.
Run directly without installing as a service:
uv run main.pyusage: moanpoint [-h] [--voice NAME] [--list-voices]
options:
-h, --help show this help message and exit
--voice NAME Voice to use (e.g. 'mia', 'sofia'). Overrides MOANPOINT_VOICE env var.
--list-voices List available voices and exit.
uv run main.py --list-voicesOutput:
Available voices:
luna
mia
sofia
uv run main.py --voice sofiaYou can also use the bare filename with extension:
uv run main.py --voice sofia.wavSet MOANPOINT_VOICE in your shell or service file:
MOANPOINT_VOICE=mia uv run main.pyPriority order: --voice argument > MOANPOINT_VOICE env var > default (luna).
If a voice file cannot be found, moanpoint falls back to luna with a warning.
After running install.sh, the service is already running. Use standard systemctl commands:
# Check status
systemctl --user status moanpoint
# Stop
systemctl --user stop moanpoint
# Start
systemctl --user start moanpoint
# Restart (e.g. after changing the voice)
systemctl --user restart moanpoint
# Follow the logs
journalctl --user -u moanpoint -f
# Disable autostart
systemctl --user disable moanpointThe unit runs the project's .venv/bin/python directly, so uv doesn't need to be on the PATH at boot.
Easiest way is to re-run the installer:
./install.sh --voice sofiaOr edit ~/.config/systemd/user/moanpoint.service by hand, uncomment the Environment= line and set your voice:
Environment=MOANPOINT_VOICE=sofiaThen reload and restart:
systemctl --user daemon-reload
systemctl --user restart moanpointFor the service to automatically start when your system boots (even without logging in), enable lingering:
sudo loginctl enable-linger $USER./install.sh --linger does this for you.
Drop any .wav file into the voices/ directory and refer to it by its stem name:
cp /path/to/myfavorite.wav voices/myfavorite.wav
uv run main.py --voice myfavorite./uninstall.shStops and removes the service and deletes the .venv. It prints optional follow-up steps (leaving the input group, disabling lingering, removing the downloaded copy).
If you installed with the curl one-liner, the repo was cloned to ~/.local/share/moanpoint (or $XDG_DATA_HOME/moanpoint), and the uninstaller lives there:
~/.local/share/moanpoint/uninstall.sh
rm -rf ~/.local/share/moanpointRun it from the checkout — unlike install.sh, it can't be piped from curl.
MIT — see LICENSE.