Skip to content

Commit fdc78ae

Browse files
committed
service/pipewire: add a way to set preferred default nodes
1 parent f889f08 commit fdc78ae

File tree

4 files changed

+107
-13
lines changed

4 files changed

+107
-13
lines changed

src/services/pipewire/defaults.cpp

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
#include <array>
33
#include <cstring>
44

5+
#include <qjsondocument.h>
6+
#include <qjsonobject.h>
57
#include <qlogging.h>
68
#include <qloggingcategory.h>
79
#include <qobject.h>
10+
#include <qstringview.h>
811
#include <qtmetamacros.h>
912
#include <spa/utils/json.h>
1013

@@ -63,7 +66,7 @@ void PwDefaultTracker::onMetadataProperty(const char* key, const char* type, con
6366
} else return;
6467

6568
QString name;
66-
if (strcmp(type, "Spa:String:JSON") == 0) {
69+
if (type != nullptr && value != nullptr && strcmp(type, "Spa:String:JSON") == 0) {
6770
auto failed = true;
6871
auto iter = std::array<spa_json, 2>();
6972
spa_json_init(&iter[0], value, strlen(value));
@@ -138,6 +141,70 @@ void PwDefaultTracker::onNodeDestroyed(QObject* node) {
138141
}
139142
}
140143

144+
void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
145+
if (node != nullptr) {
146+
if (!node->isSink) {
147+
qCCritical(logDefaults) << "Cannot change default sink to a node that is not a sink.";
148+
return;
149+
}
150+
151+
this->changeConfiguredSinkName(node->name);
152+
} else {
153+
this->changeConfiguredSinkName("");
154+
}
155+
}
156+
157+
void PwDefaultTracker::changeConfiguredSinkName(const QString& sink) {
158+
if (sink == this->mDefaultConfiguredSinkName) return;
159+
160+
if (this->setConfiguredDefault("default.configured.audio.sink", sink)) {
161+
this->mDefaultConfiguredSinkName = sink;
162+
qCInfo(logDefaults) << "Set default configured sink to" << sink;
163+
}
164+
}
165+
166+
void PwDefaultTracker::changeConfiguredSource(PwNode* node) {
167+
if (node != nullptr) {
168+
if (node->isSink) {
169+
qCCritical(logDefaults) << "Cannot change default source to a node that is not a source.";
170+
return;
171+
}
172+
173+
this->changeConfiguredSourceName(node->name);
174+
} else {
175+
this->changeConfiguredSourceName("");
176+
}
177+
}
178+
179+
void PwDefaultTracker::changeConfiguredSourceName(const QString& source) {
180+
if (source == this->mDefaultConfiguredSourceName) return;
181+
182+
if (this->setConfiguredDefault("default.configured.audio.source", source)) {
183+
this->mDefaultConfiguredSourceName = source;
184+
qCInfo(logDefaults) << "Set default configured source to" << source;
185+
}
186+
}
187+
188+
bool PwDefaultTracker::setConfiguredDefault(const char* key, const QString& value) {
189+
auto* meta = this->defaultsMetadata.object();
190+
191+
if (!meta || !meta->proxy()) {
192+
qCCritical(logDefaults) << "Cannot set default node as metadata is not ready.";
193+
return false;
194+
}
195+
196+
if (value.isEmpty()) {
197+
meta->setProperty(key, "Spa:String:JSON", nullptr);
198+
} else {
199+
// Spa json is a superset of json so we can avoid the awful spa json api when serializing.
200+
auto json = QJsonDocument({{"name", value}}).toJson(QJsonDocument::Compact);
201+
202+
meta->setProperty(key, "Spa:String:JSON", json.toStdString().c_str());
203+
}
204+
205+
return true;
206+
}
207+
141208
void PwDefaultTracker::setDefaultSink(PwNode* node) {
142209
if (node == this->mDefaultSink) return;
143210
qCInfo(logDefaults) << "Default sink changed to" << node;

src/services/pipewire/defaults.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ class PwDefaultTracker: public QObject {
1818

1919
[[nodiscard]] PwNode* defaultConfiguredSink() const;
2020
[[nodiscard]] const QString& defaultConfiguredSinkName() const;
21+
void changeConfiguredSink(PwNode* node);
22+
void changeConfiguredSinkName(const QString& sink);
2123

2224
[[nodiscard]] PwNode* defaultConfiguredSource() const;
2325
[[nodiscard]] const QString& defaultConfiguredSourceName() const;
26+
void changeConfiguredSource(PwNode* node);
27+
void changeConfiguredSourceName(const QString& source);
2428

2529
signals:
2630
void defaultSinkChanged();
@@ -54,6 +58,8 @@ private slots:
5458
void setDefaultConfiguredSource(PwNode* node);
5559
void setDefaultConfiguredSourceName(const QString& name);
5660

61+
bool setConfiguredDefault(const char* key, const QString& value);
62+
5763
PwRegistry* registry;
5864
PwBindableRef<PwMetadata> defaultsMetadata;
5965

src/services/pipewire/qml.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,19 @@ PwNodeIface* Pipewire::defaultConfiguredAudioSink() const { // NOLINT
143143
return PwNodeIface::instance(node);
144144
}
145145

146+
void Pipewire::setDefaultConfiguredAudioSink(PwNodeIface* node) {
147+
PwConnection::instance()->defaults.changeConfiguredSink(node ? node->node() : nullptr);
148+
}
149+
146150
PwNodeIface* Pipewire::defaultConfiguredAudioSource() const { // NOLINT
147151
auto* node = PwConnection::instance()->defaults.defaultConfiguredSource();
148152
return PwNodeIface::instance(node);
149153
}
150154

155+
void Pipewire::setDefaultConfiguredAudioSource(PwNodeIface* node) {
156+
PwConnection::instance()->defaults.changeConfiguredSource(node ? node->node() : nullptr);
157+
}
158+
151159
PwNodeIface* PwNodeLinkTracker::node() const { return this->mNode; }
152160

153161
void PwNodeLinkTracker::setNode(PwNodeIface* node) {

src/services/pipewire/qml.hpp

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,28 +60,38 @@ class Pipewire: public QObject {
6060
Q_PROPERTY(ObjectModel<PwLinkGroupIface>* linkGroups READ linkGroups CONSTANT);
6161
/// The default audio sink (output) or `null`.
6262
///
63+
/// This is the default sink currently in use by pipewire, and the one applications
64+
/// are currently using.
65+
///
66+
/// To set the default sink, use @@preferredDefaultAudioSink.
67+
///
6368
/// > [!INFO] When the default sink changes, this property may breifly become null.
6469
/// > This depends on your hardware.
6570
Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged);
6671
/// The default audio source (input) or `null`.
6772
///
73+
/// This is the default source currently in use by pipewire, and the one applications
74+
/// are currently using.
75+
///
76+
/// To set the default source, use @@preferredDefaultAudioSource.
77+
///
6878
/// > [!INFO] When the default source changes, this property may breifly become null.
6979
/// > This depends on your hardware.
7080
Q_PROPERTY(PwNodeIface* defaultAudioSource READ defaultAudioSource NOTIFY defaultAudioSourceChanged);
71-
/// The configured default audio sink (output) or `null`.
81+
/// The preferred default audio sink (output) or `null`.
7282
///
73-
/// This is not the same as @@defaultAudioSink. While @@defaultAudioSink is the
74-
/// sink that will be used by applications, @@defaultConfiguredAudioSink is the
75-
/// sink requested to be the default by quickshell or another configuration tool,
76-
/// which might not exist or be valid.
77-
Q_PROPERTY(PwNodeIface* defaultConfiguredAudioSink READ defaultConfiguredAudioSink NOTIFY defaultConfiguredAudioSinkChanged);
78-
/// The configured default audio source (input) or `null`.
83+
/// This is a hint to pipewire telling it which sink should be the default when possible.
84+
/// @@defaultAudioSink may differ when it is not possible for pipewire to pick this node.
7985
///
80-
/// This is not the same as @@defaultAudioSource. While @@defaultAudioSource is the
81-
/// source that will be used by applications, @@defaultConfiguredAudioSource is the
82-
/// source requested to be the default by quickshell or another configuration tool,
83-
/// which might not exist or be valid.
84-
Q_PROPERTY(PwNodeIface* defaultConfiguredAudioSource READ defaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
86+
/// See @@defaultAudioSink for the current default sink, regardless of preference.
87+
Q_PROPERTY(PwNodeIface* preferredDefaultAudioSink READ defaultConfiguredAudioSink WRITE setDefaultConfiguredAudioSink NOTIFY defaultConfiguredAudioSinkChanged);
88+
/// The preferred default audio source (input) or `null`.
89+
///
90+
/// This is a hint to pipewire telling it which source should be the default when possible.
91+
/// @@defaultAudioSource may differ when it is not possible for pipewire to pick this node.
92+
///
93+
/// See @@defaultAudioSource for the current default source, regardless of preference.
94+
Q_PROPERTY(PwNodeIface* preferredDefaultAudioSource READ defaultConfiguredAudioSource WRITE setDefaultConfiguredAudioSource NOTIFY defaultConfiguredAudioSourceChanged);
8595
// clang-format on
8696
QML_ELEMENT;
8797
QML_SINGLETON;
@@ -97,7 +107,10 @@ class Pipewire: public QObject {
97107
[[nodiscard]] PwNodeIface* defaultAudioSource() const;
98108

99109
[[nodiscard]] PwNodeIface* defaultConfiguredAudioSink() const;
110+
static void setDefaultConfiguredAudioSink(PwNodeIface* node);
111+
100112
[[nodiscard]] PwNodeIface* defaultConfiguredAudioSource() const;
113+
static void setDefaultConfiguredAudioSource(PwNodeIface* node);
101114

102115
signals:
103116
void defaultAudioSinkChanged();

0 commit comments

Comments
 (0)