summaryrefslogtreecommitdiff
path: root/overlays
diff options
context:
space:
mode:
Diffstat (limited to 'overlays')
-rw-r--r--overlays/quickshell/default.nix2
-rw-r--r--overlays/quickshell/lock-state-changed.patch12
-rw-r--r--overlays/quickshell/pipewire.patch460
3 files changed, 474 insertions, 0 deletions
diff --git a/overlays/quickshell/default.nix b/overlays/quickshell/default.nix
index ac722010..622d69a3 100644
--- a/overlays/quickshell/default.nix
+++ b/overlays/quickshell/default.nix
@@ -3,6 +3,8 @@
3 quickshell = prev.quickshell.overrideAttrs (oldAttrs: { 3 quickshell = prev.quickshell.overrideAttrs (oldAttrs: {
4 patches = (oldAttrs.patches or []) ++ [ 4 patches = (oldAttrs.patches or []) ++ [
5 ./greetd-response.patch 5 ./greetd-response.patch
6 ./lock-state-changed.patch
7 ./pipewire.patch
6 ]; 8 ];
7 }); 9 });
8} 10}
diff --git a/overlays/quickshell/lock-state-changed.patch b/overlays/quickshell/lock-state-changed.patch
new file mode 100644
index 00000000..4be273fa
--- /dev/null
+++ b/overlays/quickshell/lock-state-changed.patch
@@ -0,0 +1,12 @@
1diff --git i/src/wayland/session_lock.cpp w/src/wayland/session_lock.cpp
2index 0ecf9ec..3dbd19b 100644
3--- i/src/wayland/session_lock.cpp
4+++ w/src/wayland/session_lock.cpp
5@@ -127,6 +127,7 @@ void WlSessionLock::realizeLockTarget(WlSessionLock* old) {
6 this->updateSurfaces(false);
7
8 if (!this->manager->lock()) this->lockTarget = false;
9+ emit this->lockStateChanged();
10
11 this->updateSurfaces(true, old);
12 } else {
diff --git a/overlays/quickshell/pipewire.patch b/overlays/quickshell/pipewire.patch
new file mode 100644
index 00000000..33025d8b
--- /dev/null
+++ b/overlays/quickshell/pipewire.patch
@@ -0,0 +1,460 @@
1diff --git i/src/services/pipewire/device.cpp w/src/services/pipewire/device.cpp
2index 616e7d0..0c55008 100644
3--- i/src/services/pipewire/device.cpp
4+++ w/src/services/pipewire/device.cpp
5@@ -3,6 +3,7 @@
6 #include <cstdint>
7 #include <functional>
8 #include <utility>
9+#include <algorithm>
10
11 #include <pipewire/device.h>
12 #include <qcontainerfwd.h>
13@@ -19,6 +20,8 @@
14 #include <spa/pod/pod.h>
15 #include <spa/pod/vararg.h>
16 #include <spa/utils/type.h>
17+#include <spa/monitor/device.h>
18+#include <spa/utils/keys.h>
19
20 #include "../../core/logcat.hpp"
21 #include "core.hpp"
22@@ -46,6 +49,25 @@ void PwDevice::unbindHooks() {
23 this->mWaitingForDevice = false;
24 }
25
26+void PwDevice::initProps(const spa_dict* props) {
27+ if (const auto* deviceName = spa_dict_lookup(props, SPA_KEY_DEVICE_NAME)) {
28+ this->name = deviceName;
29+ }
30+
31+ if (const auto* deviceDesc = spa_dict_lookup(props, SPA_KEY_DEVICE_DESCRIPTION)) {
32+ this->description = deviceDesc;
33+ }
34+
35+ if (const auto* deviceNick = spa_dict_lookup(props, SPA_KEY_DEVICE_NICK)) {
36+ this->nick = deviceNick;
37+ }
38+
39+ if (const auto* mediaClass = spa_dict_lookup(props, SPA_KEY_MEDIA_CLASS)) {
40+ this->type = mediaClass;
41+ }
42+}
43+
44+
45 const pw_device_events PwDevice::EVENTS = {
46 .version = PW_VERSION_DEVICE_EVENTS,
47 .info = &PwDevice::onInfo,
48@@ -71,6 +93,11 @@ void PwDevice::onInfo(void* data, const pw_device_info* info) {
49 }
50
51 break;
52+ } else if (param.id == SPA_PARAM_EnumProfile && param.flags & SPA_PARAM_INFO_READ) {
53+ self->validProfiles.clear();
54+ pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
55+ } else if (param.id == SPA_PARAM_Profile && param.flags & SPA_PARAM_INFO_READ) {
56+ pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
57 }
58 }
59 }
60@@ -97,6 +124,15 @@ void PwDevice::onParam(
61 }
62
63 self->addDeviceIndexPairs(param);
64+ } else if (id == SPA_PARAM_EnumProfile) {
65+ PwProfile profile = PwProfile::parseSpaPod(param);
66+ self->profilesUpdated = true;
67+ self->profiles.insertOrAssign(profile.index, profile);
68+ self->validProfiles.insert(profile.index);
69+ } else if (id == SPA_PARAM_Profile) {
70+ PwProfile profile = PwProfile::parseSpaPod(param);
71+ self->currentProfileUpdated = true;
72+ self->currentProfile = profile;
73 }
74 }
75
76@@ -145,6 +181,21 @@ void PwDevice::polled() {
77 return false;
78 });
79 }
80+ if (this->profilesUpdated) {
81+ this->profiles.removeIf([&](const std::pair<qint32, PwProfile>& entry) {
82+ return !this->validProfiles.contains(entry.first);
83+ });
84+ this->profilesUpdated = false;
85+ QList<PwProfile> profiles = this->profiles.values();
86+ std::sort(profiles.begin(), profiles.end(), [](const PwProfile& a, const PwProfile& b) { return a.index < b.index; });
87+ emit this->profilesChanged(profiles);
88+ }
89+ if (this->currentProfileUpdated) {
90+ this->currentProfileUpdated = false;
91+ if (this->currentProfile) {
92+ emit this->currentProfileChanged(*this->currentProfile);
93+ }
94+ }
95 }
96
97 bool PwDevice::setVolumes(qint32 routeDevice, const QVector<float>& volumes) {
98@@ -182,6 +233,15 @@ bool PwDevice::setMuted(qint32 routeDevice, bool muted) {
99 });
100 }
101
102+void PwDevice::setProfile(qint32 profileIndex) {
103+ auto buffer = std::array<uint8_t, 1024>();
104+ auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
105+ auto* pod = spa_pod_builder_add_object(&builder,
106+ SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
107+ SPA_PARAM_PROFILE_index, SPA_POD_Int(profileIndex));
108+ pw_device_set_param(this->proxy(), SPA_PARAM_Profile, 0, static_cast<spa_pod*>(pod));
109+}
110+
111 void PwDevice::waitForDevice() { this->mWaitingForDevice = true; }
112 bool PwDevice::waitingForDevice() const { return this->mWaitingForDevice; }
113
114@@ -222,4 +282,24 @@ bool PwDevice::setRouteProps(
115 return true;
116 }
117
118+PwProfile PwProfile::parseSpaPod(const spa_pod* param) {
119+ PwProfile profile;
120+
121+ const auto* indexProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_index);
122+ const auto* descProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_description);
123+ const auto* nameProp = spa_pod_find_prop(param, nullptr, SPA_PARAM_PROFILE_name);
124+
125+ spa_pod_get_int(&indexProp->value, &profile.index);
126+
127+ const char* desc_cstr = nullptr;
128+ spa_pod_get_string(&descProp->value, &desc_cstr);
129+ profile.description = QString(desc_cstr);
130+
131+ const char* name_cstr = nullptr;
132+ spa_pod_get_string(&nameProp->value, &name_cstr);
133+ profile.name = QString(name_cstr);
134+
135+ return profile;
136+}
137+
138 } // namespace qs::service::pipewire
139diff --git i/src/services/pipewire/device.hpp w/src/services/pipewire/device.hpp
140index 1a1f705..ee64858 100644
141--- i/src/services/pipewire/device.hpp
142+++ w/src/services/pipewire/device.hpp
143@@ -1,6 +1,7 @@
144 #pragma once
145
146 #include <functional>
147+#include <optional>
148
149 #include <pipewire/core.h>
150 #include <pipewire/device.h>
151@@ -17,6 +18,20 @@
152
153 namespace qs::service::pipewire {
154
155+struct PwProfile {
156+ Q_GADGET;
157+ Q_PROPERTY(qint32 index MEMBER index)
158+ Q_PROPERTY(QString description MEMBER description)
159+ Q_PROPERTY(QString name MEMBER name)
160+
161+public:
162+ qint32 index;
163+ QString description;
164+ QString name;
165+
166+ static PwProfile parseSpaPod(const spa_pod* param);
167+};
168+
169 class PwDevice;
170
171 class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE> {
172@@ -25,6 +40,12 @@ class PwDevice: public PwBindable<pw_device, PW_TYPE_INTERFACE_Device, PW_VERSIO
173 public:
174 void bindHooks() override;
175 void unbindHooks() override;
176+ void initProps(const spa_dict* props) override;
177+
178+ QString name;
179+ QString description;
180+ QString nick;
181+ QString type;
182
183 bool setVolumes(qint32 routeDevice, const QVector<float>& volumes);
184 bool setMuted(qint32 routeDevice, bool muted);
185@@ -32,9 +53,16 @@ public:
186 void waitForDevice();
187 [[nodiscard]] bool waitingForDevice() const;
188
189+ void setProfile(qint32 profileIndex);
190+
191+ QHash<qint32, PwProfile> profiles;
192+ std::optional<PwProfile> currentProfile;
193+
194 signals:
195 void deviceReady();
196 void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps);
197+ void profilesChanged(QList<PwProfile> profiles);
198+ void currentProfileChanged(PwProfile profile);
199
200 private slots:
201 void polled();
202@@ -49,6 +77,11 @@ private:
203 QList<qint32> stagingIndexes;
204 void addDeviceIndexPairs(const spa_pod* param);
205
206+ bool profilesUpdated = false;
207+ QSet<qint32> validProfiles;
208+
209+ bool currentProfileUpdated = false;
210+
211 bool
212 setRouteProps(qint32 routeDevice, const std::function<void*(spa_pod_builder*)>& propsCallback);
213
214diff --git i/src/services/pipewire/qml.cpp w/src/services/pipewire/qml.cpp
215index 9efb17e..921d12a 100644
216--- i/src/services/pipewire/qml.cpp
217+++ w/src/services/pipewire/qml.cpp
218@@ -9,6 +9,9 @@
219 #include <qtypes.h>
220 #include <qvariant.h>
221
222+#include <cstdint>
223+#include <algorithm>
224+
225 #include "../../core/model.hpp"
226 #include "connection.hpp"
227 #include "defaults.hpp"
228@@ -54,6 +57,12 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) {
229
230 QObject::connect(&connection->registry, &PwRegistry::nodeAdded, this, &Pipewire::onNodeAdded);
231
232+ for (auto* device: connection->registry.devices.values()) {
233+ this->onDeviceAdded(device);
234+ }
235+
236+ QObject::connect(&connection->registry, &PwRegistry::deviceAdded, this, &Pipewire::onDeviceAdded);
237+
238 for (auto* link: connection->registry.links.values()) {
239 this->onLinkAdded(link);
240 }
241@@ -123,6 +132,19 @@ void Pipewire::onNodeRemoved(QObject* object) {
242 this->mNodes.removeObject(iface);
243 }
244
245+ObjectModel<PwDeviceIface>* Pipewire::devices() { return &this->mDevices; }
246+
247+void Pipewire::onDeviceAdded(PwDevice* device) {
248+ auto* iface = PwDeviceIface::instance(device);
249+ QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onDeviceRemoved);
250+ this->mDevices.insertObject(iface);
251+}
252+
253+void Pipewire::onDeviceRemoved(QObject* object) {
254+ auto* iface = static_cast<PwDeviceIface*>(object); // NOLINT
255+ this->mDevices.removeObject(iface);
256+}
257+
258 ObjectModel<PwLinkIface>* Pipewire::links() { return &this->mLinks; }
259
260 void Pipewire::onLinkAdded(PwLink* link) {
261@@ -357,6 +379,8 @@ QVariantMap PwNodeIface::properties() const {
262
263 PwNodeAudioIface* PwNodeIface::audio() const { return this->audioIface; }
264
265+PwDeviceIface* PwNodeIface::device() const { return PwDeviceIface::instance(this->mNode->device); }
266+
267 PwNodeIface* PwNodeIface::instance(PwNode* node) {
268 if (node == nullptr) return nullptr;
269
270@@ -481,4 +505,42 @@ void PwObjectTracker::objectDestroyed(QObject* object) {
271 emit this->objectsChanged();
272 }
273
274+PwDeviceIface::PwDeviceIface(PwDevice* device): PwObjectIface(device), mDevice(device) {
275+ QObject::connect(device, &PwDevice::profilesChanged, this, &PwDeviceIface::deviceProfilesChanged);
276+ QObject::connect(device, &PwDevice::currentProfileChanged, this, &PwDeviceIface::deviceCurrentProfileChanged);
277+}
278+
279+void PwDeviceIface::deviceProfilesChanged(QList<PwProfile>) { emit this->profilesChanged(); }
280+void PwDeviceIface::deviceCurrentProfileChanged(PwProfile) { emit this->currentProfileChanged(); }
281+
282+quint32 PwDeviceIface::id() const { return this->mDevice->id; }
283+QString PwDeviceIface::name() const { return this->mDevice->name; }
284+QString PwDeviceIface::description() const { return this->mDevice->description; }
285+QString PwDeviceIface::nickname() const { return this->mDevice->nick; }
286+QString PwDeviceIface::type() const { return this->mDevice->type; }
287+QList<PwProfile> PwDeviceIface::profiles() const {
288+ QList<PwProfile> profiles = this->mDevice->profiles.values();
289+ std::sort(profiles.begin(), profiles.end(), [](const PwProfile& a, const PwProfile& b) { return a.index < b.index; });
290+ return profiles;
291+}
292+qint32 PwDeviceIface::currentProfile() const { return this->mDevice->currentProfile->index; }
293+
294+PwDeviceIface* PwDeviceIface::instance(PwDevice* device) {
295+ if (device == nullptr) return nullptr;
296+
297+ auto v = device->property("iface");
298+ if (v.canConvert<PwDeviceIface*>()) {
299+ return v.value<PwDeviceIface*>();
300+ }
301+
302+ auto* instance = new PwDeviceIface(device);
303+ device->setProperty("iface", QVariant::fromValue(instance));
304+
305+ return instance;
306+}
307+
308+void PwDeviceIface::setProfile(qint32 profileIndex) {
309+ this->mDevice->setProfile(profileIndex);
310+}
311+
312 } // namespace qs::service::pipewire
313diff --git i/src/services/pipewire/qml.hpp w/src/services/pipewire/qml.hpp
314index e3489a1..e5e1891 100644
315--- i/src/services/pipewire/qml.hpp
316+++ w/src/services/pipewire/qml.hpp
317@@ -12,11 +12,13 @@
318 #include "../../core/model.hpp"
319 #include "link.hpp"
320 #include "node.hpp"
321+#include "device.hpp"
322 #include "registry.hpp"
323
324 namespace qs::service::pipewire {
325
326 class PwNodeIface;
327+class PwDeviceIface;
328 class PwLinkIface;
329 class PwLinkGroupIface;
330
331@@ -65,6 +67,8 @@ class Pipewire: public QObject {
332 /// - @@PwNode.audio - if non null the node is an audio node.
333 QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::pipewire::PwNodeIface>*);
334 Q_PROPERTY(UntypedObjectModel* nodes READ nodes CONSTANT);
335+ QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::pipewire::PwDeviceIface>*);
336+ Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
337 /// All links present in pipewire.
338 ///
339 /// Links connect pipewire nodes to each other, and can be used to determine
340@@ -134,6 +138,7 @@ public:
341 explicit Pipewire(QObject* parent = nullptr);
342
343 [[nodiscard]] ObjectModel<PwNodeIface>* nodes();
344+ [[nodiscard]] ObjectModel<PwDeviceIface>* devices();
345 [[nodiscard]] ObjectModel<PwLinkIface>* links();
346 [[nodiscard]] ObjectModel<PwLinkGroupIface>* linkGroups();
347
348@@ -159,7 +164,9 @@ signals:
349
350 private slots:
351 void onNodeAdded(PwNode* node);
352+ void onDeviceAdded(PwDevice* node);
353 void onNodeRemoved(QObject* object);
354+ void onDeviceRemoved(QObject* object);
355 void onLinkAdded(PwLink* link);
356 void onLinkRemoved(QObject* object);
357 void onLinkGroupAdded(PwLinkGroup* group);
358@@ -167,6 +174,7 @@ private slots:
359
360 private:
361 ObjectModel<PwNodeIface> mNodes {this};
362+ ObjectModel<PwDeviceIface> mDevices {this};
363 ObjectModel<PwLinkIface> mLinks {this};
364 ObjectModel<PwLinkGroupIface> mLinkGroups {this};
365 };
366@@ -315,6 +323,7 @@ class PwNodeIface: public PwObjectIface {
367 /// > [!NOTE] The node may be used before it is fully bound, but some data
368 /// > may be missing or incorrect.
369 Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged);
370+ Q_PROPERTY(qs::service::pipewire::PwDeviceIface* device READ device CONSTANT);
371 QML_NAMED_ELEMENT(PwNode);
372 QML_UNCREATABLE("PwNodes cannot be created directly");
373
374@@ -332,6 +341,7 @@ public:
375 [[nodiscard]] PwNodeType::Flags type() const;
376 [[nodiscard]] QVariantMap properties() const;
377 [[nodiscard]] PwNodeAudioIface* audio() const;
378+ [[nodiscard]] PwDeviceIface* device() const;
379
380 static PwNodeIface* instance(PwNode* node);
381
382@@ -344,6 +354,44 @@ private:
383 PwNodeAudioIface* audioIface = nullptr;
384 };
385
386+class PwDeviceIface: public PwObjectIface {
387+ Q_OBJECT;
388+ Q_PROPERTY(quint32 id READ id CONSTANT);
389+ Q_PROPERTY(QString name READ name CONSTANT);
390+ Q_PROPERTY(QString description READ description CONSTANT);
391+ Q_PROPERTY(QString nickname READ nickname CONSTANT);
392+ Q_PROPERTY(QString type READ type CONSTANT);
393+ Q_PROPERTY(QList<PwProfile> profiles READ profiles NOTIFY profilesChanged);
394+ Q_PROPERTY(qint32 currentProfile READ currentProfile NOTIFY currentProfileChanged);
395+
396+ QML_NAMED_ELEMENT(PwDevice);
397+ QML_UNCREATABLE("PwDevices cannot be created directly");
398+
399+signals:
400+ void profilesChanged();
401+ void currentProfileChanged();
402+
403+public:
404+ explicit PwDeviceIface(PwDevice* node);
405+
406+ [[nodiscard]] quint32 id() const;
407+ [[nodiscard]] QString name() const;
408+ [[nodiscard]] QString description() const;
409+ [[nodiscard]] QString nickname() const;
410+ [[nodiscard]] QString type() const;
411+ QList<PwProfile> profiles() const;
412+ qint32 currentProfile() const;
413+
414+ Q_INVOKABLE void setProfile(qint32 profileIndex);
415+
416+ static PwDeviceIface* instance(PwDevice* node);
417+private:
418+ PwDevice* mDevice;
419+
420+ void deviceProfilesChanged(QList<PwProfile> profiles);
421+ void deviceCurrentProfileChanged(PwProfile profile);
422+};
423+
424 ///! A connection between pipewire nodes.
425 /// Note that there is one link per *channel* of a connection between nodes.
426 /// You usually want @@PwLinkGroup.
427diff --git i/src/services/pipewire/registry.cpp w/src/services/pipewire/registry.cpp
428index c08fc1d..50c6d7a 100644
429--- i/src/services/pipewire/registry.cpp
430+++ w/src/services/pipewire/registry.cpp
431@@ -196,6 +196,7 @@ void PwRegistry::onGlobal(
432 device->initProps(props);
433
434 self->devices.emplace(id, device);
435+ emit self->deviceAdded(device);
436 }
437 }
438
439@@ -211,6 +212,9 @@ void PwRegistry::onGlobalRemoved(void* data, quint32 id) {
440 } else if (auto* node = self->nodes.value(id)) {
441 self->nodes.remove(id);
442 node->safeDestroy();
443+ } else if (auto* device = self->devices.value(id)) {
444+ self->devices.remove(id);
445+ device->safeDestroy();
446 }
447 }
448
449diff --git i/src/services/pipewire/registry.hpp w/src/services/pipewire/registry.hpp
450index 8473f04..87e0766 100644
451--- i/src/services/pipewire/registry.hpp
452+++ w/src/services/pipewire/registry.hpp
453@@ -132,6 +132,7 @@ public:
454
455 signals:
456 void nodeAdded(PwNode* node);
457+ void deviceAdded(PwDevice* node);
458 void linkAdded(PwLink* link);
459 void linkGroupAdded(PwLinkGroup* group);
460 void metadataAdded(PwMetadata* metadata);