Skip to content

Bar crashes (SIGSEGV) when MPD audio node connects to PipeWire — binding loop in playerStreamNode #190

Description

@Haretsu-Kimagure

Summary

When MPD starts playing audio via a PipeWire output, the bar process (quickshell) immediately
crashes with SIGSEGV. The top panel disappears, brightness resets, and the shell restarts in
a loop. The issue is a reactive binding loop in modules/bar/Media.qml: playerStreamNode
reads from Audio.outputAppNodes, which PwObjectTracker then modifies, which re-fires the
binding, creating an infinite loop that segfaults quickshell.

Steps to reproduce

  1. Configure MPD with a PipeWire output (type "pipewire" in mpd.conf)
  2. Start any MPD client and play a track
  3. The mpd.PipeWire stream node appears in Audio.outputAppNodes
  4. Bar crashes immediately

Expected behavior

Bar stays alive. MPD playback is reflected in the media widget without any crash.

Logs

WARN scene: QML Media at @modules/bar/BarContent.qml[260:9]: Binding loop detected for property "playerStreamNode":
qs:@/qs/modules/bar/Media.qml:46:5
inir.service: Main process exited, code=dumped, status=11/SEGV
inir.service: Failed with result 'core-dump'.

Version

2.27.0(commit 0143406)

Compositor

26.04

Quickshell version

Quickshell 0.3.0 (revision , distributed by Arch Linux)

Qt version

qt6-base 6.11.1-1

Distro

Arch Linux

Panel family

ii

Relevant configuration

Additional notes

Root Cause

playerStreamNode in modules/bar/Media.qml is a reactive binding that reads
Audio.outputAppNodes. The PwObjectTracker directly below it is also bound to
playerStreamNode. When a new PipeWire node appears:

  1. Audio.outputAppNodes changes → playerStreamNode re-evaluates
  2. PwObjectTracker.objects changes → Audio.outputAppNodes changes again
  3. → infinite loop → SIGSEGV
// current (broken)
readonly property var playerStreamNode: {
    ...
    return Audio.outputAppNodes.find(...) ?? null
}
PwObjectTracker { objects: root.playerStreamNode ? [root.playerStreamNode] : [] }

Suggested Fix

Convert playerStreamNode to an imperative property updated via Connections, with an
identity guard to prevent re-triggering the cycle:

property var playerStreamNode: null

function _findPlayerStreamNode() {
    const p = root.activePlayer
    if (!p) return null
    const id = (p.identity ?? "").toLowerCase()
    const entry = (p.desktopEntry ?? "").toLowerCase()
    if (!id && !entry) return null
    return Audio.outputAppNodes.find(n => {
        const an = ((n.properties?.["application.name"] ?? n.name) ?? "").toLowerCase()
        if (!an) return false
        return (id && (an.includes(id) || id.includes(an)))
            || (entry && (an.includes(entry) || entry.includes(an)))
    }) ?? null
}

Connections {
    target: Audio
    function onOutputAppNodesChanged() {
        const found = root._findPlayerStreamNode()
        if (found !== root.playerStreamNode)
            root.playerStreamNode = found
    }
}

PwObjectTracker { objects: root.playerStreamNode ? [root.playerStreamNode] : [] }

Also add playerStreamNode = _findPlayerStreamNode() inside onActivePlayerChanged to
handle player switches correctly.

I found out about that bug using claude and suggested fix is made by him so i should say about that.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions