Round 5. Codex and I converged independently on the same conclusion — a Link-only ancestry walk does not catch the leak — it from the crate/header/ WirePlumber sources, me from the live graph. Its sandbox could not reach the daemon (pw-dump: Operation not permitted), so the measurements are mine. Reproduced the EasyEffects topology with module-null-sink + module-loopback (same shape, no EasyEffects needed). Result: there is NO Link object between a forwarder's input leg and its output leg. Walking upstream from the leaking node over Links alone finds no inbound links at all — a dead end that reads as "clean". The legs are related only by shared node.link-group / client.id / pulse.module.id. So the signal graph needs three edge types: 1. Link edges — measured: registry Links carry all four endpoint props. 2. Sink-monitor — measured FREE at node granularity: the monitor connection IS a real Link whose output node is the sink itself. Codex held that this must be modelled explicitly; that is true only for a port-granular walk. Taint walks at node granularity, links are created per port. 3. Owner bridge — node.link-group when present, else client.id (measured shared across the forwarder's legs, distinct per app). Only modules set link-group, so client.id is what covers ordinary apps. New §6.1.1: bridge taint must be CONDITIONAL on the input leg being tainted. "Client has both legs ⇒ exclude" would exclude every app using a microphone. Firefox in a Meet call stays shareable; Firefox sharing desktop audio does not. Also: §6.5 rejects the cheap "hardware-sink-only" predicate with a measurement — the forwarder's output leg links directly to alsa_output, so the shortcut passes the leak and excludes the innocent app, backwards on both halves. §6.3 barrier corrected: core sync/done is a previous-work roundtrip, not graph quiescence. §6.4 adds crate version, endpoint fast path + bind fallback, and full-recompute cost. §5.2 correction 5 rewritten: application.process.id lives on the Node and is the app's own PID; pipewire.sec.pid lives on the Client and is pipewire-pulse's for every Pulse client. That resolves four rounds of contradictory PID claims. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PeerSpeak
Decentralized, peer-to-peer voice chat — full-mesh, NAT-traversing, with no central server. Built in Rust on iroh (QUIC), PipeWire audio, the Opus codec, and an iced GUI.
Create a room, share the join ticket, and talk. Everyone connects directly to everyone else; relays are only used to punch through NATs when a direct path isn't available.
Screenshots
| Launch screen | In a room |
|---|---|
![]() |
![]() |
| Settings |
|---|
![]() |
Features
Rooms & sessions
- Create a room → shareable join ticket; join by pasting a ticket.
- Full-mesh multi-peer rooms with live presence.
- Recent-rooms list to hop back into a room someone's still in.
- Remembered nickname and in-call duration timer.
Audio
- PipeWire capture/playback, selectable input and output devices, per-app gain.
- Opus codec (48 kHz mono, 20 ms frames) with an adaptive jitter buffer + packet-loss concealment.
- Noise gate with a draggable threshold on a live mic meter (test your mic off-call too).
- Mix-bus soft limiter and opt-in echo cancellation (PipeWire WebRTC AEC + noise suppression).
Voice controls
- Self-mute, deafen, and rebindable push-to-talk.
- Per-peer volume, local mute, and speaking indicators.
Text chat
- In-room text chat over the gossip plane, with clickable links and inline image/audio attachments.
- Drag-selectable, copyable messages; right-click context menu on all text fields.
Shared music listening
- Build a personal playlist of local audio files with a full transport (play/pause, seek, reorder).
- Let others tune in: peers stream your current track, timeline-synced and gapless, sitting under voice at their own volume.
Screen share (via pixelpass)
- Share your screen; peers click 👁 Watch to open the stream in mpv (vlc fallback).
- Live badges on sharing peers; per-app audio capture.
Recording & notifications
- Local call recording (mic + incoming mix → WAV in
~/peerspeak-recordings/). - Desktop notifications and event chimes with per-event custom sound overrides.
UI & networking
- Selectable room layouts (3-Column, Bottom Dock, Drawer) with draggable, persisted dividers.
- 10 built-in themes (Catppuccin, Dracula, Nord, Tokyo Night, Gruvbox, Solarized…), all WCAG-AA checked.
- Network mode picker (relay-no-discovery default, full n0, or direct-only); retained-address reconnect.
- Config, window size/position, and all preferences persisted to
~/.config/peerspeak/.
See docs/FEATURES.md for the full inventory and field-test status, and docs/ARCHITECTURE.md for internals.
Roadmap
- Contacts & invites — friends list with invite-notification one-click join (design in
docs/contacts-plan.md). - Spatial audio & per-peer EQ.
- Soundboard — play short clips into the call mix.
- Room persistence / invite links beyond the raw ticket.
- Windows support — cross-compiles and launches under Wine today; needs a real WASAPI audio pass (see
docs/WINDOWS.md).
Building
PeerSpeak builds with a stable Rust toolchain (edition 2024). Install the system dependencies below, then:
cargo build --release
./target/release/peerspeak
System dependencies
Arch Linux
sudo pacman -S --needed rust pipewire opus pkgconf git
Debian / Ubuntu
sudo apt install build-essential pkg-config clang libclang-dev \
libpipewire-0.3-dev libopus-dev libasound2-dev libxcb1-dev
Plus a Rust toolchain via rustup. clang/libclang are needed for the PipeWire bindings (bindgen).
At runtime you need a running PipeWire server. Screen sharing additionally requires pixelpass on your PATH, and mpv (or vlc) to watch a peer's share.
Packaging
- Arch:
cd packaging && makepkg -si(usespackaging/PKGBUILD). - Debian/Ubuntu:
.debis built withcargo-debfrom the[package.metadata.deb]block inCargo.toml. Build inside a Debian/Ubuntu environment so the binary links that distro's glibc. - Windows: see
docs/WINDOWS.md.
License
PeerSpeak is licensed under the MIT License, © 2026 mollusk.
Third-party components bundled with PeerSpeak (the Rust dependency tree, the
statically bundled Opus codec on some builds, and embedded fonts) are all under
permissive licenses; their texts and a full dependency manifest are collected in
THIRD_PARTY_LICENSES.


