Skip to content

Rebuild routing when PipeWire moves the Wave's nodes - #10

Open
Sharpienero wants to merge 2 commits into
rikkichy:mainfrom
Sharpienero:fix/wave-node-redetect
Open

Rebuild routing when PipeWire moves the Wave's nodes#10
Sharpienero wants to merge 2 commits into
rikkichy:mainfrom
Sharpienero:fix/wave-node-redetect

Conversation

@Sharpienero

@Sharpienero Sharpienero commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Two fixes for the same failure. The mixer keeps addressing PipeWire nodes that no longer exist, and the routing it thinks it has stops existing with them. Output goes silent while the UI reads as correct, because the only broken thing is a link the UI never draws.

Re-detect the Wave's nodes instead of caching them at startup

Mixer resolves mic and hp once in its constructor, and every routing decision afterwards uses those two strings. PipeWire is free to destroy and recreate those nodes at any point (a daemon restart, a suspend, the Wave unplugged and returned), and nothing reports it.

The case I hit: the Personal Mix to headphones loopback is spawned once in _do_start behind if self.hp. Start the app before the Wave enumerates, or restart PipeWire after it, and hp is still the None from construction. The loopback is skipped, every app keeps feeding openwave_personal_mix, and that mix terminates nowhere. The mix matrix, the volumes and the device page all look right.

The worker now re-resolves both names between tasks, tears down whatever was built against a name that moved, and lets reconcile rebuild it. _ensure_hp_loopback makes the headphone loopback a reconciled resource like any other cell, so it also appears when the Wave arrives late and goes away when it leaves.

The mic meter has the same latch. pw-cat binds a node name at spawn, so a meter left pointed at a destroyed node reads flat zero forever, which is indistinguishable from a dead mic. Mixer reports device changes to the UI and the meter is re-pointed with them.

Interval is 5s, two pactl calls, on the worker thread. It is a recovery path rather than a control one, so nothing the user does waits on it, and it does no work when nothing has moved.

Respawn a loopback whose subprocess has exited

Separate bug on the same surface. _spawn_loopback treats a key's presence in _procs as proof the loopback exists, and nothing removes an entry whose child has died. A PipeWire restart takes every pw-loopback with it, so afterwards the dict holds exited processes that reconcile still counts as live routing and never rebuilds.

Testing

There is no suite in the repo, so I drove the watchdog with the subprocess layer stubbed and checked: the HP loopback is created by reconcile rather than _do_start; a dead loopback is respawned; a node rename tears down the stale loopback and rebuilds against the new name; unplugging removes it and replugging restores it; and an idle pass spawns and destroys nothing. Happy to add that as a real test file if you want one in-tree.

_spawn_loopback treats a key's presence in _procs as proof the loopback
exists, and nothing removes an entry whose child has died. A PipeWire restart
takes every pw-loopback with it, so afterwards the dict holds exited processes
that reconcile still counts as live routing and never rebuilds.

Check the child is running before trusting the entry.
Mixer resolves mic and hp once in its constructor, and every routing decision
afterwards uses those two strings. PipeWire can destroy and recreate those
nodes at any time and nothing reports it, so the cached names go stale and the
mixer keeps addressing nodes that are gone.

The visible case is the Personal Mix -> headphones loopback, spawned once in
_do_start behind `if self.hp`. Start before the Wave enumerates, or restart
PipeWire after it, and hp is still the None from construction: the loopback is
skipped, apps keep feeding openwave_personal_mix, and that mix goes nowhere.
Output is silent while the matrix and the volumes all read as correct.

Re-resolve both names on the worker, tear down what was built against a name
that moved, and let reconcile rebuild it. That makes the headphone loopback a
reconciled resource like any cell, so it also appears when the Wave arrives
late. The mic meter binds a node name at spawn and reads flat zero against a
destroyed one, so device changes are reported to the UI and the meter follows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant