mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
AudioPanel: move audio stream logic to AudioService, filter 'quickshell' audio (#1645)
This commit is contained in:
@@ -146,130 +146,7 @@ SmartPanel {
|
||||
// Find application streams that are actually playing audio (connected to default sink)
|
||||
// Use linkGroups to find nodes connected to the default audio sink
|
||||
// Note: We need to use link IDs since source/target properties require binding
|
||||
readonly property var appStreams: {
|
||||
if (!Pipewire.ready || !AudioService.sink) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var defaultSink = AudioService.sink;
|
||||
var defaultSinkId = defaultSink.id;
|
||||
var connectedStreamIds = {};
|
||||
var connectedStreams = [];
|
||||
|
||||
// Use PwNodeLinkTracker to get properly bound link groups
|
||||
if (!sinkLinkTracker.linkGroups) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check if linkGroups is an array or ObjectModel
|
||||
var linkGroupsCount = 0;
|
||||
if (sinkLinkTracker.linkGroups.length !== undefined) {
|
||||
linkGroupsCount = sinkLinkTracker.linkGroups.length;
|
||||
} else if (sinkLinkTracker.linkGroups.count !== undefined) {
|
||||
linkGroupsCount = sinkLinkTracker.linkGroups.count;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (linkGroupsCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect intermediate node IDs that are connected to the sink
|
||||
var intermediateNodeIds = {};
|
||||
|
||||
// Process link groups from sinkLinkTracker
|
||||
var nodesToCheck = [];
|
||||
|
||||
for (var i = 0; i < linkGroupsCount; i++) {
|
||||
var linkGroup;
|
||||
if (sinkLinkTracker.linkGroups.get) {
|
||||
linkGroup = sinkLinkTracker.linkGroups.get(i);
|
||||
} else {
|
||||
linkGroup = sinkLinkTracker.linkGroups[i];
|
||||
}
|
||||
|
||||
if (!linkGroup || !linkGroup.source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var sourceNode = linkGroup.source;
|
||||
|
||||
// If it's a stream node, add it directly
|
||||
if (sourceNode.isStream && sourceNode.audio) {
|
||||
if (!connectedStreamIds[sourceNode.id]) {
|
||||
connectedStreamIds[sourceNode.id] = true;
|
||||
connectedStreams.push(sourceNode);
|
||||
}
|
||||
} else {
|
||||
// Not a stream - this is an intermediate node, track it
|
||||
intermediateNodeIds[sourceNode.id] = true;
|
||||
nodesToCheck.push(sourceNode);
|
||||
}
|
||||
}
|
||||
|
||||
// If we found intermediate nodes, we need to find streams connected to them
|
||||
// Since Pipewire.linkGroups is not directly accessible, we'll use a heuristic:
|
||||
// When intermediate nodes are present, include all active stream nodes
|
||||
// (reasonable assumption: if audio is playing, streams are connected)
|
||||
if (nodesToCheck.length > 0 || connectedStreams.length === 0) {
|
||||
try {
|
||||
// Get all nodes from Pipewire
|
||||
var allNodes = [];
|
||||
if (Pipewire.nodes) {
|
||||
if (Pipewire.nodes.count !== undefined) {
|
||||
var nodeCount = Pipewire.nodes.count;
|
||||
for (var n = 0; n < nodeCount; n++) {
|
||||
var node;
|
||||
if (Pipewire.nodes.get) {
|
||||
node = Pipewire.nodes.get(n);
|
||||
} else {
|
||||
node = Pipewire.nodes[n];
|
||||
}
|
||||
if (node)
|
||||
allNodes.push(node);
|
||||
}
|
||||
} else if (Pipewire.nodes.values) {
|
||||
allNodes = Pipewire.nodes.values;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all stream nodes
|
||||
for (var j = 0; j < allNodes.length; j++) {
|
||||
var node = allNodes[j];
|
||||
if (!node || !node.isStream || !node.audio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var streamId = node.id;
|
||||
if (connectedStreamIds[streamId]) {
|
||||
continue; // Already added
|
||||
}
|
||||
|
||||
// When intermediate nodes are present, include all stream nodes
|
||||
// This is a reasonable heuristic since if audio is playing, they're likely connected
|
||||
if (Object.keys(intermediateNodeIds).length > 0) {
|
||||
connectedStreamIds[streamId] = true;
|
||||
connectedStreams.push(node);
|
||||
} else if (connectedStreams.length === 0) {
|
||||
// Fallback: if no streams found yet, include as fallback
|
||||
connectedStreamIds[streamId] = true;
|
||||
connectedStreams.push(node);
|
||||
}
|
||||
}
|
||||
} catch (e)
|
||||
// Error finding stream nodes - continue with what we have
|
||||
{}
|
||||
}
|
||||
|
||||
return connectedStreams;
|
||||
}
|
||||
|
||||
// Track links to the default sink using PwNodeLinkTracker (properly binds links)
|
||||
PwNodeLinkTracker {
|
||||
id: sinkLinkTracker
|
||||
node: AudioService.sink
|
||||
}
|
||||
readonly property var appStreams: AudioService.appStreams
|
||||
|
||||
// Use implicitHeight from content + margins to avoid binding loops
|
||||
property real contentPreferredHeight: mainColumn.implicitHeight + Style.marginL * 2
|
||||
|
||||
@@ -271,7 +271,6 @@ ColumnLayout {
|
||||
Layout.topMargin: Style.marginS
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
visible: Settings.data.bar.displayMode === "auto_hide"
|
||||
spacing: Style.marginS
|
||||
|
||||
@@ -76,6 +76,13 @@ Singleton {
|
||||
// Filtered device nodes (non-stream sinks and sources)
|
||||
readonly property var deviceNodes: Pipewire.ready ? Pipewire.nodes.values.reduce((acc, node) => {
|
||||
if (!node.isStream) {
|
||||
// Filter out quickshell nodes (unlikely to be devices, but for consistency)
|
||||
const name = node.name || "";
|
||||
const mediaName = (node.properties && node.properties["media.name"]) || "";
|
||||
if (name === "quickshell" || mediaName === "quickshell") {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (node.isSink) {
|
||||
acc.sinks.push(node);
|
||||
} else if (node.audio) {
|
||||
@@ -122,6 +129,114 @@ Singleton {
|
||||
objects: root.source ? [root.source] : []
|
||||
}
|
||||
|
||||
// Track links to the default sink to find active streams
|
||||
PwNodeLinkTracker {
|
||||
id: sinkLinkTracker
|
||||
node: root.sink
|
||||
}
|
||||
|
||||
// Find application streams that are connected to the default sink
|
||||
readonly property var appStreams: {
|
||||
if (!Pipewire.ready || !root.sink) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var connectedStreamIds = {};
|
||||
var connectedStreams = [];
|
||||
|
||||
// Use PwNodeLinkTracker to get properly bound link groups
|
||||
if (!sinkLinkTracker.linkGroups) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var linkGroupsCount = 0;
|
||||
if (sinkLinkTracker.linkGroups.length !== undefined) {
|
||||
linkGroupsCount = sinkLinkTracker.linkGroups.length;
|
||||
} else if (sinkLinkTracker.linkGroups.count !== undefined) {
|
||||
linkGroupsCount = sinkLinkTracker.linkGroups.count;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (linkGroupsCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var intermediateNodeIds = {};
|
||||
var nodesToCheck = [];
|
||||
|
||||
for (var i = 0; i < linkGroupsCount; i++) {
|
||||
var linkGroup;
|
||||
if (sinkLinkTracker.linkGroups.get) {
|
||||
linkGroup = sinkLinkTracker.linkGroups.get(i);
|
||||
} else {
|
||||
linkGroup = sinkLinkTracker.linkGroups[i];
|
||||
}
|
||||
|
||||
if (!linkGroup || !linkGroup.source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var sourceNode = linkGroup.source;
|
||||
|
||||
// Filter out quickshell
|
||||
const name = sourceNode.name || "";
|
||||
const mediaName = (sourceNode.properties && sourceNode.properties["media.name"]) || "";
|
||||
if (name === "quickshell" || mediaName === "quickshell") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it's a stream node, add it directly
|
||||
if (sourceNode.isStream && sourceNode.audio) {
|
||||
if (!connectedStreamIds[sourceNode.id]) {
|
||||
connectedStreamIds[sourceNode.id] = true;
|
||||
connectedStreams.push(sourceNode);
|
||||
}
|
||||
} else {
|
||||
// Not a stream - this is an intermediate node, track it
|
||||
intermediateNodeIds[sourceNode.id] = true;
|
||||
nodesToCheck.push(sourceNode);
|
||||
}
|
||||
}
|
||||
|
||||
// If we found intermediate nodes, we need to find streams connected to them
|
||||
if (nodesToCheck.length > 0 || connectedStreams.length === 0) {
|
||||
try {
|
||||
var allNodes = Pipewire.nodes.values || [];
|
||||
|
||||
// Find all stream nodes
|
||||
for (var j = 0; j < allNodes.length; j++) {
|
||||
var node = allNodes[j];
|
||||
if (!node || !node.isStream || !node.audio) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter out quickshell
|
||||
const nodeName = node.name || "";
|
||||
const nodeMediaName = (node.properties && node.properties["media.name"]) || "";
|
||||
if (nodeName === "quickshell" || nodeMediaName === "quickshell") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var streamId = node.id;
|
||||
if (connectedStreamIds[streamId]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.keys(intermediateNodeIds).length > 0) {
|
||||
connectedStreamIds[streamId] = true;
|
||||
connectedStreams.push(node);
|
||||
} else if (connectedStreams.length === 0) {
|
||||
connectedStreamIds[streamId] = true;
|
||||
connectedStreams.push(node);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return connectedStreams;
|
||||
}
|
||||
|
||||
// Bind all devices to ensure their properties are available
|
||||
PwObjectTracker {
|
||||
objects: [...root.sinks, ...root.sources]
|
||||
|
||||
Reference in New Issue
Block a user