pttman: System-wide push-to-talk for Linux

Published:
Keywords: linux

Contents

The Problem

Most push-to-talk implementations live inside individual apps. Discord has one, Teams has one, Zoom has a setting buried somewhere in its preferences. But if you want push-to-talk behavior everywhere on Linux, across every app that touches your microphone, you’re out of luck. There’s no system-level toggle that mutes and unmutes your mic on a key press/release in a way that actually works reliably.

The naive approach is a shell script that calls pactl set-source-mute on key down and key up. That works until you press and release the key quickly, or hold it and release while another press event is still in flight. PipeWire processes those pactl calls concurrently, and the mute commands interleave: you end up with a mic that’s stuck open or stuck muted, depending on which process finished last.

What pttman Does

pttman is a small Python daemon that provides push-to-talk microphone control for PipeWire. It runs as a user service, listens on a Unix datagram socket, and serializes mute/unmute requests so that rapid key presses never race each other.

Two properties make it useful as a system-wide solution:

  • It operates at the PipeWire/PulseAudio layer, so every app that uses your microphone sees the mute state change. No per-app configuration needed.
  • By default, it controls all audio sources on the system. If you have a USB headset and a webcam mic, both get muted and unmuted together. You can also lock it to a single source if you prefer.

The client side is a fire-and-forget datagram send. When you press a key, a lightweight client process sends "unmute" to the socket and exits. On release, it sends "mute". The daemon drains all pending messages and applies the last one, so if events pile up during a rapid press/release, the final state is always correct.

If the daemon isn’t running, client commands fall back to direct pactl execution. You don’t lose functionality; you just lose the race-condition protection.

Setup

Install with uv and start the service:

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

install-service detects your init system automatically. It supports systemd (user service) and OpenRC (user service on 0.60+, system service on older versions).

By default, pttman controls all audio sources. To restrict it to one:

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

set-default-source writes to ~/.config/pttman.conf and signals the running daemon to reload, so the change takes effect immediately.

Key Binding

You need something to send press/release events. I use xremap, which supports distinct actions for key press and key release:

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

Pressing F9 unmutes, releasing it mutes. On some laptop keyboards,

F9 has a mic icon, which makes it a natural fit.

You can also route your compositor’s mic-mute key through pttman for a toggle instead of push-to-talk. For example, in niri’s keybinds.kdl:

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

How It Works

The daemon is a single-threaded Python process (no external dependencies beyond the standard library) that listens on $XDG_RUNTIME_DIR/pttman.sock. When a message arrives, it drains the socket non-blocking and coalesces: only the last queued command executes. This means a burst of unmute, mute, unmute, mute collapses to a single mute call to pactl.

A background thread runs pactl subscribe to watch for audio source changes. If you plug in a new USB mic while the daemon is running and you’re in all-sources mode, pttman picks it up automatically and applies the current mute state to it.

Configuration changes are picked up via SIGHUP, which set-default-source sends automatically.

  • pttman on GitHub
  • xremap, a key remapper for Linux that supports press/release actions