Metadata-Version: 2.4
Name: pttman
Version: 0.5.1
Summary: Push-to-talk microphone control with a daemon-backed command queue
Project-URL: Homepage, https://github.com/mwolson/pttman-py
Project-URL: Issues, https://github.com/mwolson/pttman-py/issues
Project-URL: Repository, https://github.com/mwolson/pttman-py
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# 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

```bash
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.)

```bash
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)

```bash
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`:

```sh
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)

```bash
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:

```bash
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):

```bash
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:

```yaml
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):

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

You can check the current microphone state with:

```bash
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:

```bash
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.

```text
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`):

```text
--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:

```text
--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

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

### OpenRC (user, 0.60+)

```bash
rc-service --user pttman start
rc-service --user pttman status
```

### OpenRC (system, <0.60)

```bash
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:

```bash
bun run test
```

Integration tests (requires Docker):

```bash
bun run test:integration
```

Both:

```bash
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

```bash
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
