PipeWire virtual-sink manager and headset ChatMix control for Linux gamers.
Route per-app audio between virtual sinks and drive your headset's ChatMix wheel from one place — a Linux-native take on what SteelSeries Sonar does on Windows.
- Virtual sinks — creates
game_sink+chat_sinkon first launch; create / delete more on the fly. - Per-app routing — drag-and-drop applications between sinks, or pick from a dropdown. Drag back to the source lane to send an app to the default sink.
- Per-stream volume + mute — real
pactl set-sink-input-volume/set-sink-input-mute, debounced sliders. - ChatMix wheel integration — turn the headset wheel and watch the game/chat split flip live (HID polling), or drive it manually from the dashboard slider.
- Port linking — power-user panel to wire any source output port to any target input port (
pw-link). - Friendlier stream names — resolves
application.name→application.process.binary→/proc/<pid>/cmdline→ amedia.role-based hybrid label. Rename anything inline. - System tray — closes to tray and keeps monitoring ChatMix in the background. Show / Hide / Quit from the tray menu.
- Start at login — optional autostart toggle in the Maintenance page.
- Guided setup — the Maintenance page checks
libhidapi, installs the udev rule (viapkexec), and toggles autostart.
| Sonar core feature | Penguin Wave |
|---|---|
| Game / Chat / Media split | ✅ virtual sinks + drag-drop routing |
| Independent volume / mute | ✅ per-stream, debounced |
| ChatMix blend | ✅ HID wheel + manual slider |
| Game EQ presets | ⏳ planned (filter-chain dormant) |
| Streamer submix | ⏳ achievable via custom sinks, no guided UX |
- SteelSeries Arctis Nova 7
Adding more: implement DeviceTrait in PenguinWave/src-tauri/src/headsets/ and register it in AppStateManager::initialize_supported_devices.
- PipeWire userland on
PATH:pactl,pw-cli,pw-link- Arch:
pipewire pipewire-pulse wireplumber - Debian/Ubuntu:
pipewire pipewire-pulse pipewire-bin
- Arch:
- Linux only — uses
/procand HID viahidapi(libhidapi). - HID permission for the headset — install the bundled udev rule (the in-app Maintenance page does this for you).
- A system-tray host if your compositor lacks one (e.g. niri needs
waybar/ironbarwith a tray module).
# packaging assets live in PenguinWave/packaging/ (PKGBUILD WIP)
# once published:
# yay -S penguinwavegit clone <repo> penguin-wave
cd penguin-wave/PenguinWave
npm install
npm run tauri:build
# .deb / AppImage / rpm in src-tauri/target/release/bundle/cd PenguinWave
npm run tauri:debug # full app, RUST_BACKTRACE=full RUST_LOG=debug
npm run dev # frontend only on http://localhost:1420 (no Tauri commands)Wayland note: the app sets
WEBKIT_DISABLE_DMABUF_RENDERER=1on Linux so the window reliably reappears from the tray (webkitgtk hide/show bug on Wayland compositors such as niri).
React (Vite) ──invoke──▶ Tauri 2 commands ──shell out──▶ pactl / pw-cli / pw-link
▲ │ │
│ ▼ ▼
listen() ◀──emit───── chatmix poll thread (100ms) ◀──HID── hidapi
- Frontend: React 18, TanStack Query 5, wouter, Radix + shadcn/ui ("new-york"), Tailwind 3,
@dnd-kit. - Backend: Rust edition 2021, Tauri 2,
hidapi,anyhow/thiserror.
.
├── assets/ # brand logos (svg / png / with-text)
├── PenguinWave/ # the application
│ ├── src/ # React frontend (pages, components, hooks, queries, types)
│ ├── src-tauri/ # Rust crate (controllers, system/pipewire, event/chatmix_listener, headsets, utils)
│ └── packaging/ # .desktop entry, udev rule, hicolor icons
└── README.md
- PKGBUILD + AUR publish (
-binfrom GitHub Releases + source build) - CI release pipeline (tagged binaries + checksums)
- More headsets (Arctis Nova Pro, other ChatMix devices)
- Game EQ presets (wire up the dormant filter-chain)
- Streamer submix UX
Licensed under the Apache License 2.0