Why I Made Yet Another Push to Talk Tool for Linux
Why I Made Yet Another Push to Talk Tool for Linux
Linux probably has a lot of push to talk tools and honestly most of them work fine. The problem is that many of them feel more like setups than actual software.
You install a binary, then spend time making a systemd service, changing startup configs, and fixing random things after reboot. None of this is hard, but after rebuilding the same setup multiple times it starts getting annoying.
At some point I realized the problem was not push to talk itself. The problem was that most tools stop one step too early. I did not want another CLI utility. I wanted something that behaved like a proper daemon.
So I made ptt-mic (I know, the name is terrible, might change it to r-mptt in future).
What is ptt-mic
ptt-mic is a push to talk daemon for Linux written in Rust. It reads evdev input events and runs commands when a button is pressed or released.
That is basically the whole idea.
No GUI, no built in audio backend, and no giant desktop integration layer. Just input handling, runtime state, mode switching, and command execution.
The daemon stays running in the background while the config file controls behavior.
I wanted the daemon part built in
One thing that bothered me with many Linux push to talk tools is that they feel like command line apps first, while the daemon part is left to the user.
A normal setup usually looks something like this:
mkdir -p ~/.config/systemd/user
cp something.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now something.service
There is nothing wrong with this approach, but it feels unnecessary for software that is supposed to stay running all the time anyway.
So ptt-mic handles this itself:
ptt-mic install
That command creates the service, reloads systemd, enables it, and starts it automatically.
Small thing, but it makes the whole tool feel much cleaner to use.
Config file instead of endless flags
A lot of Linux tools slowly turn into giant command lines. That works at first, but after a while the setup becomes difficult to read and even harder to maintain.
With ptt-mic, behavior is defined in a TOML config file instead:
[general]
device = "/dev/input/event6"
default_mode = "desktop"
[mode.desktop]
[[mode.desktop.binds]]
button = "KEY_V"
press = [
"pactl",
"set-source-mute",
"@DEFAULT_SOURCE@",
"0"
]
release = [
"pactl",
"set-source-mute",
"@DEFAULT_SOURCE@",
"1"
]
The daemon handles runtime state while the config file describes behavior. That ended up feeling much easier to manage compared to stacking more and more CLI flags together.
I did not want to hardcode audio support
A lot of push to talk tools are tied directly to PipeWire. That makes sense because most Linux desktop users already use PipeWire, but I did not want the daemon itself to care about audio systems.
I wanted it to care about input events, state, and actions. Everything else stays outside the daemon.
So instead of hardcoding audio support, ptt-mic simply runs commands.
press = [
"pactl",
"set-source-mute",
"@DEFAULT_SOURCE@",
"0"
]
release = [
"pactl",
"set-source-mute",
"@DEFAULT_SOURCE@",
"1"
]
That same system can also control OBS, notifications, scripts, or basically anything with a CLI.
If it can run in a shell, ptt-mic can trigger it.
At its core, the daemon is really just input events triggering actions.
Linux desktop stuff gets messy fast
One thing I learned while making this project is that Linux desktop workflows get messy very quickly, especially on Wayland.
Some features need compositor specific code.
For example, ptt-mic supports per app configs by checking the focused window app ID and switching behavior automatically. Right now this only works on Niri, not because I only wanted Niri support, but because every compositor handles focus and app IDs differently.
There is no real standard way to do this (as far as I know).
So while the main daemon tries to stay generic, some features still need compositor specific integrations. Trying to fully abstract Linux desktop behavior usually just creates more problems later.
Runtime mode switching
One thing I really wanted was runtime mode switching because different situations need different behavior.
Sometimes I want desktop voice chat, streaming controls, gaming binds.
So ptt-mic can switch modes while the daemon is still running:
ptt-mic mode desktop
ptt-mic mode obs
No restart needed.
The daemon keeps running while the workflow changes around it.
It is still built around my own setup
I should probably mention this clearly.
ptt-mic was originally made for my own setup and right now it has mostly been tested on CachyOS, Niri, and PipeWire.
So there are definitely rough edges.
But honestly, I think that also helped keep the project focused. I was solving my own workflow first instead of trying to support every Linux setup immediately.
The actual goal
I do not want ptt-mic to become a giant desktop app.
I mostly wanted something that survives reboot properly, keeps config in one place, behaves predictably, supports multiple workflows, and feels like a real service instead of shell glue.
That ended up becoming ptt-mic.
Project
- Codeberg: https://codeberg.org/Cinders/ptt-mic
- Written in Rust
- Linux only