Skip to main content

Push-to-talk microphone control with a daemon-backed command queue

Project description

pttman

Reliable push-to-talk and mic-mute for PipeWire.

pttman is a small user service that makes your mic stay muted when you want it muted:

  • Rapid mute / unmute / toggle key presses are serialized over a Unix datagram socket, so a quick press-release cannot race itself into the wrong state.
  • Your intended mute state is reapplied after suspend, a PipeWire restart, or a mic being unplugged and reconnected.
  • Accidental unmutes from other tools (volume sliders in quick menus, pavucontrol, a stray wpctl call) are reverted within milliseconds.

Requirements

  • PipeWire with PulseAudio compatibility (pipewire-pulse)
  • pactl (from pipewire-pulse or pulseaudio-utils)
  • One of: systemd or OpenRC

Installation

systemd

uv tool install pttman
pttman install-service
systemctl --user start pttman.service

This installs pttman to ~/.local/bin/, copies the systemd user service into place, and enables it. After installing, point your push-to-talk key at the client binary, and make sure ~/.local/bin is on your PATH.

OpenRC user service (0.60+, Alpine edge, etc.)

uv tool install pttman
pttman install-service
rc-service --user pttman start

On OpenRC 0.60 or newer, install-service automatically installs a user-level service to ~/.config/rc/init.d/pttman. Make sure ~/.local/bin is on your PATH.

OpenRC system service (older OpenRC)

sudo uv pip install --system --break-system-packages pttman
sudo pttman install-service
sudo rc-service pttman start

On OpenRC versions before 0.60, install-service installs a system-level init script to /etc/init.d/pttman and adds it to the default runlevel. The service uses supervise-daemon for process supervision with automatic restart.

To configure the user and environment for the daemon, create /etc/conf.d/pttman:

command_user="youruser"
supervise_daemon_args="--env XDG_RUNTIME_DIR=/run/user/1000"

Replace 1000 with your user's UID (id -u youruser).

Alternative: install.sh (systemd)

git clone https://github.com/mwolson/pttman-py.git
cd pttman-py
./install.sh
systemctl --user start pttman.service

This copies pttman to ~/.local/bin/ and installs and enables the user service.

Optional: set defaults

By default, pttman operates on all audio sources. You can optionally save a preferred source so that pttman controls only that one:

pttman list-sources
pttman set-default-source alsa_input.usb-046d_BRIO-03.pro-input-0

This writes to ~/.config/pttman.conf and signals the running daemon to pick up the change.

xremap

On Arch Linux, install the xremap variant that matches your desktop environment (only one should be installed):

paru -S xremap-gnome-bin     # GNOME
paru -S xremap-hyprland-bin  # Hyprland
paru -S xremap-kde-bin       # KDE Plasma
paru -S xremap-niri-bin      # Niri
paru -S xremap-wlroots-bin   # wlroots-based compositors (sway, etc.)

Then configure a push-to-talk key binding:

modmap:
  - name: Push-to-talk
    remap:
      F9:
        skip_key_event: true
        press: { launch: ["/home/your-user/.local/bin/pttman", "press"] }
        release: { launch: ["/home/your-user/.local/bin/pttman", "release"] }

Pressing F9 (as configured above, which is conveniently labeled with a mic icon on some laptop keyboards) tells the daemon to unmute for the duration of the key press; releasing F9 mutes again. press and release are the temporary push-to-talk variants of unmute and mute: they do not change the daemon's recorded preference, so the mic returns to the preferred state after the next pipewire restart or pttman resync.

You can also route your compositor's mic-mute key through pttman. For example, in niri's keybinds.kdl this implements mic toggle (rather than push-to-talk):

XF86AudioMicMute  allow-when-locked=true { spawn "/home/your-user/.local/bin/pttman" "toggle"; }

You can check the current microphone state with:

pttman status

Corsair mice on Arch Linux

If you use a Corsair mouse and want one of its extra buttons to behave like F9, ckb-next is a straightforward way to do it.

On Arch Linux, install ckb-next with:

sudo pacman -S ckb-next

Then launch ckb-next, select your mouse, pick the button you want to use for push-to-talk, and remap that button to F9.

After that, xremap can keep using the F9 rule above, and your Corsair mouse button will trigger push-to-talk through pttman.

If you specifically want the upstream development build instead of the packaged release, the ckb-next project wiki also lists ckb-next-git for Arch-based systems.

Commands

pttman uses subcommands for one-off operations. With no subcommand, it runs the daemon.

pttman                                 Run the daemon (default)
pttman get-default-source              Print the default source from the config file
pttman install-service                 Install and enable the service (systemd or OpenRC)
pttman list-sources                    List available audio sources
pttman mute                            Mute the microphone and record it as the preference
pttman press                           Temporarily unmute (push-to-talk, does not change preference)
pttman release                         Temporarily mute (push-to-talk, does not change preference)
pttman resync                          Ask the daemon to reapply its desired mute state
pttman set-default-source SOURCE       Save default source and signal the daemon
pttman status                          Print the current microphone state
pttman toggle                          Toggle the microphone mute state and record the new state as the preference
pttman uninstall-service               Disable and remove the service (systemd or OpenRC)
pttman unmute                          Unmute the microphone and record it as the preference

mute, unmute, and toggle record the result as the daemon's preference, so the mic comes back in that state after a pipewire-pulse restart, hotplug, or pttman resync. press and release are the push-to-talk variants: they change the mute bit but do not update the preference, so a press-release cycle never overwrites the baseline you set with pttman mute/unmute/toggle.

Options

These flags apply to the daemon and to action commands (mute, unmute, toggle, press, release, status):

--source SOURCE     Audio source name to control (default: config file, then all sources)
--all-sources       Operate on all audio sources (overrides --source from config)
--start-muted       Mute managed sources when the daemon starts (default: true)
--no-start-muted    Leave mic state untouched when the daemon starts

--source and --all-sources are mutually exclusive.

The daemon tracks a "desired" mute state per source: the last state set via pttman mute/unmute/toggle, falling back to the --start-muted default for sources with no explicit preference (including newly hotplugged ones). External changes from tools like noctalia or pavucontrol are reverted within milliseconds: the daemon watches pactl subscribe, detects drift against its last-applied state, and then reapplies the intended mute state.

This covers the common failure modes (an accidental volume-slider unmute in a quick menu, a stray wpctl from another script) without updating the preference, so the baseline stays where pttman mute/unmute put it. The daemon also reapplies the desired state when the PipeWire-Pulse layer restarts (e.g. after suspend, or an explicit restart from aproman) or a source is added or removed. Use pttman resync to trigger a manual reapply on demand.

Configuration File

pttman reads defaults from ~/.config/pttman.conf (or $XDG_CONFIG_HOME/pttman.conf). The file uses one flag per line:

--source=alsa_input.usb-046d_BRIO-03.pro-input-0

Supported flags:

  • --source=NAME -- control only this source
  • --all-sources=true -- control all sources (the default when no config file exists)
  • --start-muted=true|false -- whether the daemon mutes managed sources at startup (default: true)

--source and --all-sources are mutually exclusive. Unrecognized flags cause an error at startup. Command-line arguments always take precedence over the config file.

set-default-source automatically signals the running daemon to reload the config file and update the source for future operations.

Service

systemd

systemctl --user start pttman.service
systemctl --user status pttman.service
journalctl --user -u pttman.service -f

OpenRC (user, 0.60+)

rc-service --user pttman start
rc-service --user pttman status

OpenRC (system, <0.60)

sudo rc-service pttman start
rc-service pttman status

The daemon listens on $XDG_RUNTIME_DIR/pttman.sock. If the daemon is not running, client commands fall back to direct pactl execution.

Development

Testing

Unit tests:

bun run test

Integration tests (requires Docker):

bun run test:integration

Both:

bun run test:all

The integration tests use Docker containers to verify the systemd and OpenRC service files work end-to-end (install, start, stop, uninstall).

Hooks

lefthook install
lefthook run pre-commit --all-files

The pre-commit hook runs uvx ruff check, uvx ty check, and the unit test suite.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pttman-0.5.1.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pttman-0.5.1-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file pttman-0.5.1.tar.gz.

File metadata

  • Download URL: pttman-0.5.1.tar.gz
  • Upload date:
  • Size: 30.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pttman-0.5.1.tar.gz
Algorithm Hash digest
SHA256 3171f152036f60b77b224b893dbe4f52f1fa536c56fc476d8135ac46553f9896
MD5 c97781a061b35450266410ced06df1fb
BLAKE2b-256 c8aaafb234ede09455fd07554fb428c01e38050b0be3e76af32867d8aacceb54

See more details on using hashes here.

Provenance

The following attestation bundles were made for pttman-0.5.1.tar.gz:

Publisher: publish.yml on mwolson/pttman-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pttman-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: pttman-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 13.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pttman-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b36ce3badb6c142142b272a14f4d2214be517cb2f743f31a73d35543117123b7
MD5 748e096c656c74c5c9836d4b1f2f883a
BLAKE2b-256 ada4016619444fd3e571573ba9dda38517562c04eba34e79545f70c584ff8264

See more details on using hashes here.

Provenance

The following attestation bundles were made for pttman-0.5.1-py3-none-any.whl:

Publisher: publish.yml on mwolson/pttman-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page