Skip to content

Commit 95d0af8

Browse files
committed
services/pipewire: update volume props from device for device nodes
Yet another device node edge case. In addition to only writing via a pw_device when present, now we only read from one as well. This fixes missing state changes not conveyed by the pw_node. Fixes #35
1 parent 579d589 commit 95d0af8

File tree

5 files changed

+61
-19
lines changed

5 files changed

+61
-19
lines changed

src/services/pipewire/device.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <array>
33
#include <cstdint>
44
#include <functional>
5+
#include <utility>
56

67
#include <pipewire/device.h>
78
#include <qcontainerfwd.h>
@@ -20,6 +21,7 @@
2021
#include <spa/utils/type.h>
2122

2223
#include "core.hpp"
24+
#include "node.hpp"
2325

2426
namespace qs::service::pipewire {
2527

@@ -104,30 +106,43 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) {
104106
qint32 device = 0;
105107
qint32 index = 0;
106108

109+
spa_pod* props = nullptr;
110+
107111
// clang-format off
108112
quint32 id = SPA_PARAM_Route;
109113
spa_pod_parser_get_object(
110114
&parser, SPA_TYPE_OBJECT_ParamRoute, &id,
111115
SPA_PARAM_ROUTE_device, SPA_POD_Int(&device),
112-
SPA_PARAM_ROUTE_index, SPA_POD_Int(&index)
116+
SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
117+
SPA_PARAM_ROUTE_props, SPA_POD_PodObject(&props)
113118
);
114119
// clang-format on
115120

116-
this->stagingIndexes.insert(device, index);
121+
auto volumeProps = PwVolumeProps::parseSpaPod(props);
122+
123+
this->stagingIndexes.append(device);
117124
// Insert into the main map as well, staging's purpose is to remove old entries.
118125
this->routeDeviceIndexes.insert(device, index);
119126

120127
qCDebug(logDevice).nospace() << "Registered device/index pair for " << this
121128
<< ": [device: " << device << ", index: " << index << ']';
129+
130+
emit this->routeVolumesChanged(device, volumeProps);
122131
}
123132

124133
void PwDevice::polled() {
125134
// It is far more likely that the list content has not come in yet than it having no entries,
126135
// and there isn't a way to check in the case that there *aren't* actually any entries.
127-
if (!this->stagingIndexes.isEmpty() && this->stagingIndexes != this->routeDeviceIndexes) {
128-
this->routeDeviceIndexes = this->stagingIndexes;
129-
qCDebug(logDevice) << "Updated device/index pair list for" << this << "to"
130-
<< this->routeDeviceIndexes;
136+
if (!this->stagingIndexes.isEmpty()) {
137+
this->routeDeviceIndexes.removeIf([&](const std::pair<qint32, qint32>& entry) {
138+
if (!stagingIndexes.contains(entry.first)) {
139+
qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first
140+
<< ", index: " << entry.second << "] for" << this;
141+
return true;
142+
}
143+
144+
return false;
145+
});
131146
}
132147
}
133148

src/services/pipewire/device.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
#include <qcontainerfwd.h>
99
#include <qhash.h>
1010
#include <qtmetamacros.h>
11+
#include <qtypes.h>
1112
#include <spa/pod/builder.h>
1213

1314
#include "core.hpp"
15+
#include "node.hpp"
1416
#include "registry.hpp"
1517

1618
namespace qs::service::pipewire {
@@ -32,6 +34,7 @@ class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSIO
3234

3335
signals:
3436
void deviceReady();
37+
void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps);
3538

3639
private slots:
3740
void polled();
@@ -43,7 +46,7 @@ private slots:
4346
onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param);
4447

4548
QHash<qint32, qint32> routeDeviceIndexes;
46-
QHash<qint32, qint32> stagingIndexes;
49+
QList<qint32> stagingIndexes;
4750
void addDeviceIndexPairs(const spa_pod* param);
4851

4952
bool

src/services/pipewire/node.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,13 @@ void PwNode::onParam(
255255
PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): node(node) {
256256
if (node->device) {
257257
QObject::connect(node->device, &PwDevice::deviceReady, this, &PwNodeBoundAudio::onDeviceReady);
258+
259+
QObject::connect(
260+
node->device,
261+
&PwDevice::routeVolumesChanged,
262+
this,
263+
&PwNodeBoundAudio::onDeviceVolumesChanged
264+
);
258265
}
259266
}
260267

@@ -278,13 +285,17 @@ void PwNodeBoundAudio::onInfo(const pw_node_info* info) {
278285

279286
void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) {
280287
if (id == SPA_PARAM_Props && index == 0) {
281-
this->updateVolumeProps(param);
288+
if (this->node->device) {
289+
qCDebug(logNode) << "Skipping node volume props update for" << this->node
290+
<< "in favor of device updates.";
291+
return;
292+
}
293+
294+
this->updateVolumeProps(PwVolumeProps::parseSpaPod(param));
282295
}
283296
}
284297

285-
void PwNodeBoundAudio::updateVolumeProps(const spa_pod* param) {
286-
auto volumeProps = PwVolumeProps::parseSpaPod(param);
287-
298+
void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) {
288299
if (volumeProps.volumes.size() != volumeProps.channels.size()) {
289300
qCWarning(logNode) << "Cannot update volume props of" << this->node
290301
<< "- channelVolumes and channelMap are not the same size. Sizes:"
@@ -489,6 +500,18 @@ void PwNodeBoundAudio::onDeviceReady() {
489500
}
490501
}
491502

503+
void PwNodeBoundAudio::onDeviceVolumesChanged(
504+
qint32 routeDevice,
505+
const PwVolumeProps& volumeProps
506+
) {
507+
if (this->node->device && this->node->routeDevice == routeDevice) {
508+
qCDebug(logNode) << "Got updated device volume props for" << this->node << "via"
509+
<< this->node->device;
510+
511+
this->updateVolumeProps(volumeProps);
512+
}
513+
}
514+
492515
PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
493516
auto props = PwVolumeProps();
494517

src/services/pipewire/node.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,10 @@ class PwNodeBoundAudio
203203

204204
private slots:
205205
void onDeviceReady();
206+
void onDeviceVolumesChanged(qint32 routeDevice, const PwVolumeProps& props);
206207

207208
private:
208-
void updateVolumeProps(const spa_pod* param);
209+
void updateVolumeProps(const PwVolumeProps& volumeProps);
209210

210211
bool mMuted = false;
211212
QVector<PwAudioChannel::Enum> mChannels;

src/services/pipewire/qml.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,22 +219,22 @@ class PwNodeAudioIface: public QObject {
219219
// clang-format off
220220
/// If the node is currently muted. Setting this property changes the mute state.
221221
///
222-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
222+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
223223
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged);
224224
/// The average volume over all channels of the node.
225225
/// Setting this property modifies the volume of all channels proportionately.
226226
///
227-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
227+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
228228
Q_PROPERTY(float volume READ averageVolume WRITE setAverageVolume NOTIFY volumesChanged);
229229
/// The audio channels present on the node.
230230
///
231-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
231+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
232232
Q_PROPERTY(QVector<qs::service::pipewire::PwAudioChannel::Enum> channels READ channels NOTIFY channelsChanged);
233233
/// The volumes of each audio channel individually. Each entry corrosponds to
234234
/// the volume of the channel at the same index in @@channels. @@volumes and @@channels
235235
/// will always be the same length.
236236
///
237-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
237+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
238238
Q_PROPERTY(QVector<float> volumes READ volumes WRITE setVolumes NOTIFY volumesChanged);
239239
// clang-format on
240240
QML_NAMED_ELEMENT(PwNodeAudio);
@@ -300,7 +300,7 @@ class PwNodeIface: public PwObjectIface {
300300
/// - `media.title` - The title of the currently playing media.
301301
/// - `media.artist` - The artist of the currently playing media.
302302
///
303-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
303+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
304304
Q_PROPERTY(QVariantMap properties READ properties NOTIFY propertiesChanged);
305305
/// Extra information present only if the node sends or receives audio.
306306
///
@@ -357,7 +357,7 @@ class PwLinkIface: public PwObjectIface {
357357
Q_PROPERTY(qs::service::pipewire::PwNodeIface* source READ source CONSTANT);
358358
/// The current state of the link.
359359
///
360-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
360+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
361361
Q_PROPERTY(PwLinkState::Enum state READ state NOTIFY stateChanged);
362362
QML_NAMED_ELEMENT(PwLink);
363363
QML_UNCREATABLE("PwLinks cannot be created directly");
@@ -392,7 +392,7 @@ class PwLinkGroupIface
392392
Q_PROPERTY(qs::service::pipewire::PwNodeIface* source READ source CONSTANT);
393393
/// The current state of the link group.
394394
///
395-
/// > [!WARNING] This property is invalid unless the node is [bound](../pwobjecttracker).
395+
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
396396
Q_PROPERTY(qs::service::pipewire::PwLinkState::Enum state READ state NOTIFY stateChanged);
397397
QML_NAMED_ELEMENT(PwLinkGroup);
398398
QML_UNCREATABLE("PwLinkGroups cannot be created directly");

0 commit comments

Comments
 (0)