summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif/shell
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2025-09-08 20:00:22 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2025-09-08 20:00:22 +0200
commit8a551339cbfaf106ac7d6f1ca5230196be539167 (patch)
tree20dd3cb9f12dd94fa22fcd2d866c3cf586f8931a /accounts/gkleen@sif/shell
parent14d4d05acc235ab7033316d16530783c90e95faa (diff)
downloadnixos-8a551339cbfaf106ac7d6f1ca5230196be539167.tar
nixos-8a551339cbfaf106ac7d6f1ca5230196be539167.tar.gz
nixos-8a551339cbfaf106ac7d6f1ca5230196be539167.tar.bz2
nixos-8a551339cbfaf106ac7d6f1ca5230196be539167.tar.xz
nixos-8a551339cbfaf106ac7d6f1ca5230196be539167.zip
...
Diffstat (limited to 'accounts/gkleen@sif/shell')
-rw-r--r--accounts/gkleen@sif/shell/default.nix6
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt24
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp18
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp21
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp16
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp13
-rw-r--r--accounts/gkleen@sif/shell/quickshell-plugins/default.nix8
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Bar.qml7
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Clock.qml3
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Lockscreen.qml15
-rw-r--r--accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml24
-rw-r--r--accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml354
-rw-r--r--accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml18
-rw-r--r--accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml129
-rw-r--r--accounts/gkleen@sif/shell/quickshell/shell.qml2
15 files changed, 652 insertions, 6 deletions
diff --git a/accounts/gkleen@sif/shell/default.nix b/accounts/gkleen@sif/shell/default.nix
index 85e034d6..5025dd90 100644
--- a/accounts/gkleen@sif/shell/default.nix
+++ b/accounts/gkleen@sif/shell/default.nix
@@ -96,6 +96,12 @@
96 # "${config.programs.niri.package}/share/wayland-sessions/niri.desktop" 96 # "${config.programs.niri.package}/share/wayland-sessions/niri.desktop"
97 ]; 97 ];
98 username = builtins.toJSON config.home.username; 98 username = builtins.toJSON config.home.username;
99 mdi = builtins.toJSON (pkgs.fetchFromGitHub {
100 owner = "Templarian";
101 repo = "MaterialDesign";
102 rev = "2424e748e0cc63ab7b9c095a099b9fe239b737c0";
103 hash = "sha256-QMGl7soAhErrrnY3aKOZpt49yebkSNzy10p/v5OaqQ0=";
104 });
99 }; 105 };
100 }; 106 };
101 }; 107 };
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
index 2123ed35..a7e88fa7 100644
--- a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt
@@ -92,7 +92,7 @@ endfunction()
92cmake_minimum_required(VERSION 3.20) 92cmake_minimum_required(VERSION 3.20)
93project(custom LANGUAGES CXX) 93project(custom LANGUAGES CXX)
94 94
95find_package(Qt6 REQUIRED COMPONENTS Core Qml) 95find_package(Qt6 REQUIRED COMPONENTS Core Qml DBus)
96 96
97qt_standard_project_setup(REQUIRES 6.6) 97qt_standard_project_setup(REQUIRES 6.6)
98 98
@@ -102,16 +102,32 @@ qt6_add_qml_module(customplugin
102 PLUGIN_TARGET customplugin 102 PLUGIN_TARGET customplugin
103) 103)
104 104
105target_sources(customplugin PRIVATE 105set_source_files_properties(org.keepassxc.KeePassXC.MainWindow.xml PROPERTIES
106 Chrono.cpp Chrono.hpp 106 CLASSNAME DBusKeePassXC
107 FileSelector.cpp FileSelector.hpp 107 NO_NAMESPACE TRUE
108) 108)
109 109
110qt_add_dbus_interface(DBUS_INTERFACES
111 org.keepassxc.KeePassXC.MainWindow.xml
112 dbus_keepassxc
113)
114
115include_directories(${CMAKE_SOURCE_DIR}/build)
116
110target_compile_features(customplugin PUBLIC cxx_std_26) 117target_compile_features(customplugin PUBLIC cxx_std_26)
111 118
112target_link_libraries(customplugin PRIVATE 119target_link_libraries(customplugin PRIVATE
113 Qt6::Core 120 Qt6::Core
114 Qt6::Qml 121 Qt6::Qml
122 Qt6::DBus
123)
124
125target_sources(customplugin PRIVATE
126 Chrono.cpp Chrono.hpp
127 FileSelector.cpp FileSelector.hpp
128 KeePassXC.cpp KeePassXC.hpp
129 Systemd.cpp Systemd.hpp
130 ${DBUS_INTERFACES}
115) 131)
116 132
117install_qml_module(customplugin) 133install_qml_module(customplugin)
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
new file mode 100644
index 00000000..f6e4dd6e
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.cpp
@@ -0,0 +1,18 @@
1#include "KeePassXC.hpp"
2
3#include <QDBusConnection>
4
5KeePassXC::KeePassXC() {
6 this->service = new DBusKeePassXC(DBusKeePassXC::staticInterfaceName(), "/keepassxc", QDBusConnection::sessionBus(), this);
7}
8KeePassXC::~KeePassXC() {
9 if (this->service)
10 delete this->service;
11}
12
13void KeePassXC::lockAllDatabases() {
14 if (!this->service)
15 return;
16
17 this->service->lockAllDatabases();
18}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
new file mode 100644
index 00000000..c4cd71e0
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/KeePassXC.hpp
@@ -0,0 +1,21 @@
1#pragma once
2
3#include "dbus_keepassxc.h"
4
5#include <QObject>
6#include <QtQmlIntegration/qqmlintegration.h>
7
8class KeePassXC : public QObject {
9 Q_OBJECT;
10 QML_SINGLETON;
11 QML_ELEMENT;
12
13public:
14 explicit KeePassXC();
15 ~KeePassXC();
16
17 Q_INVOKABLE void lockAllDatabases();
18
19private:
20 DBusKeePassXC* service = nullptr;
21};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
new file mode 100644
index 00000000..9ccd8ba0
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp
@@ -0,0 +1,16 @@
1#include "Systemd.hpp"
2
3#include <QDBusConnection>
4#include <QDBusMessage>
5
6void Systemd::stopUserUnit(const QString& unit, const QString& mode) {
7 QDBusMessage m = QDBusMessage::createMethodCall(
8 "org.freedesktop.systemd1",
9 "/org/freedesktop/systemd1",
10 "org.freedesktop.systemd1.Manager",
11 "StopUnit"
12 );
13 m << unit;
14 m << mode;
15 QDBusConnection::sessionBus().send(m);
16}
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
new file mode 100644
index 00000000..883a96f3
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp
@@ -0,0 +1,13 @@
1#pragma once
2
3#include <QObject>
4#include <QtQmlIntegration/qqmlintegration.h>
5
6class Systemd : public QObject {
7 Q_OBJECT;
8 QML_SINGLETON;
9 QML_ELEMENT;
10
11public:
12 Q_INVOKABLE void stopUserUnit(const QString& unit, const QString& mode);
13};
diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
index fafea90e..33b76f61 100644
--- a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
+++ b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix
@@ -3,11 +3,19 @@
3, cmake 3, cmake
4, qt6 4, qt6
5, fmt 5, fmt
6, keepassxc
7, systemd
6}: 8}:
9
7stdenv.mkDerivation rec { 10stdenv.mkDerivation rec {
8 name = "quickshell-custom"; 11 name = "quickshell-custom";
9 12
10 src = ./.; 13 src = ./.;
14
15 prePatch = ''
16 cp ${keepassxc.src}/src/gui/org.keepassxc.KeePassXC.MainWindow.xml .
17 '';
18
11 nativeBuildInputs = [ cmake qt6.wrapQtAppsHook ]; 19 nativeBuildInputs = [ cmake qt6.wrapQtAppsHook ];
12 buildInputs = [ 20 buildInputs = [
13 qt6.qtbase 21 qt6.qtbase
diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml
index 38225d74..3652af54 100644
--- a/accounts/gkleen@sif/shell/quickshell/Bar.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml
@@ -64,6 +64,13 @@ PanelWindow {
64 anchors.verticalCenter: parent.verticalCenter 64 anchors.verticalCenter: parent.verticalCenter
65 spacing: 0 65 spacing: 0
66 66
67 PipewireWidget {}
68
69 Item {
70 height: parent.height
71 width: 4
72 }
73
67 SystemTray {} 74 SystemTray {}
68 75
69 Item { 76 Item {
diff --git a/accounts/gkleen@sif/shell/quickshell/Clock.qml b/accounts/gkleen@sif/shell/quickshell/Clock.qml
index 382af168..4644d5e7 100644
--- a/accounts/gkleen@sif/shell/quickshell/Clock.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Clock.qml
@@ -99,9 +99,10 @@ Item {
99 id: clockTooltipContent 99 id: clockTooltipContent
100 100
101 margin: 8 101 margin: 8
102 leftMargin: 0
103 102
104 ColumnLayout { 103 ColumnLayout {
104 anchors.centerIn: parent
105
105 Text { 106 Text {
106 id: yearLabel 107 id: yearLabel
107 108
diff --git a/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
index cc82a275..8e739359 100644
--- a/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
+++ b/accounts/gkleen@sif/shell/quickshell/Lockscreen.qml
@@ -2,6 +2,9 @@ import Quickshell
2import Quickshell.Wayland 2import Quickshell.Wayland
3import Quickshell.Io 3import Quickshell.Io
4import Quickshell.Services.Pam 4import Quickshell.Services.Pam
5import Quickshell.Services.Mpris
6import Custom as Custom
7import qs.Services
5import QtQml 8import QtQml
6 9
7Scope { 10Scope {
@@ -38,9 +41,19 @@ Scope {
38 WlSessionLock { 41 WlSessionLock {
39 id: lock 42 id: lock
40 43
41 onLockedChanged: { 44 onLockStateChanged: {
42 if (!locked && pam.active) 45 if (!locked && pam.active)
43 pam.abort(); 46 pam.abort();
47
48 if (locked) {
49 Custom.KeePassXC.lockAllDatabases();
50 Array.from(Mpris.players.values).forEach(player => {
51 if (player.canPause && player.isPlaying)
52 player.pause();
53 });
54 // Custom.Systemd.stopUserUnit("gpg-agent.service", "replace");
55 GpgAgent.reloadAgent();
56 }
44 } 57 }
45 58
46 WlSessionLockSurface { 59 WlSessionLockSurface {
diff --git a/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
new file mode 100644
index 00000000..387dcc8b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml
@@ -0,0 +1,24 @@
1import QtQuick
2import QtQuick.Effects
3
4Item {
5 id: icon
6
7 required property string icon
8 property color color: "white"
9
10 Image {
11 id: sourceImage
12 source: "file://" + @mdi@ + "/svg/" + icon.icon + ".svg"
13 anchors.fill: parent
14
15 layer.enabled: true
16 layer.effect: MultiEffect {
17 id: effect
18
19 brightness: 1
20 colorization: 1
21 colorizationColor: icon.color
22 }
23 }
24}
diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
new file mode 100644
index 00000000..5bf0ac3a
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml
@@ -0,0 +1,354 @@
1import QtQuick
2import QtQuick.Layouts
3import QtQuick.Controls.Fusion
4import Quickshell
5import Quickshell.Services.Pipewire
6import Quickshell.Widgets
7
8Item {
9 height: parent.height
10 width: volumeIcon.width + 8
11 anchors.verticalCenter: parent.verticalCenter
12
13 PwObjectTracker {
14 objects: [Pipewire.defaultAudioSink]
15 }
16
17 WrapperMouseArea {
18 id: widgetMouseArea
19
20 anchors.fill: parent
21 hoverEnabled: true
22 cursorShape: Qt.PointingHandCursor
23
24 onClicked: {
25 if (!Pipewire.defaultAudioSink)
26 return;
27 Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted;
28 }
29
30 property real sensitivity: (1 / 20) / 120
31 onWheel: event => {
32 if (!Pipewire.defaultAudioSink)
33 return;
34 Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity;
35 }
36
37 Rectangle {
38 id: volumeWidget
39
40 anchors.fill: parent
41 color: {
42 if (widgetMouseArea.containsMouse)
43 return "#33808080";
44 return "transparent";
45 }
46
47 Item {
48 anchors.fill: parent
49
50 MaterialDesignIcon {
51 id: volumeIcon
52
53 width: 16
54 height: 16
55 anchors.centerIn: parent
56
57 icon: {
58 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
59 return "volume-off";
60 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
61 return "volume-low";
62 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
63 return "volume-medium";
64 return "volume-high";
65 }
66 color: "#555"
67 }
68 }
69 }
70 }
71
72 Loader {
73 id: tooltipLoader
74
75 active: false
76
77 Connections {
78 target: widgetMouseArea
79 function onContainsMouseChanged() {
80 if (widgetMouseArea.containsMouse)
81 tooltipLoader.active = true;
82 }
83 }
84
85 PwObjectTracker {
86 objects: Pipewire.devices
87 }
88 PwObjectTracker {
89 objects: Pipewire.nodes
90 }
91
92 sourceComponent: PopupWindow {
93 id: tooltip
94
95 property bool openPopup: false
96 property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup
97
98 anchor {
99 item: widgetMouseArea
100 edges: Edges.Bottom | Edges.Left
101 }
102 visible: false
103
104 onNextVisibleChanged: hangTimer.restart()
105
106 Timer {
107 id: hangTimer
108 interval: 100
109 onTriggered: {
110 tooltip.visible = tooltip.nextVisible;
111 if (!tooltip.visible)
112 tooltipLoader.active = false;
113 }
114 }
115
116 implicitWidth: tooltipContent.width
117 implicitHeight: tooltipContent.height
118 color: "black"
119
120 WrapperMouseArea {
121 id: tooltipMouseArea
122
123 hoverEnabled: true
124 enabled: true
125
126 anchors.fill: parent
127
128 WrapperItem {
129 id: tooltipContent
130
131 margin: 8
132 bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight)
133
134 GridLayout {
135 id: tooltipLayout
136
137 columns: 4
138
139 Repeater {
140 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
141
142 Item {
143 id: descItem
144
145 required property var modelData
146 required property int index
147
148 Layout.column: 0
149 Layout.row: index
150
151 implicitWidth: descText.contentWidth
152 implicitHeight: descText.contentHeight
153
154 Text {
155 id: descText
156
157 color: "white"
158 font.pointSize: 10
159 font.family: "Fira Sans"
160
161 text: descItem.modelData.description
162 }
163 }
164 }
165
166 Repeater {
167 id: defaultSinkRepeater
168
169 model: {
170 Array.from(Pipewire.devices.values)
171 .filter(dev => dev.type == "Audio/Device")
172 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id ));
173 }
174
175 Item {
176 id: defaultSinkItem
177
178 required property var modelData
179 required property int index
180
181 PwObjectTracker {
182 objects: [defaultSinkItem.modelData]
183 }
184
185 Layout.column: 1
186 Layout.row: index
187
188 Layout.fillHeight: true
189
190 implicitWidth: 16 + 8
191
192 WrapperMouseArea {
193 id: defaultSinkMouseArea
194
195 anchors.fill: parent
196 hoverEnabled: true
197 cursorShape: Qt.PointingHandCursor
198
199 onClicked: {
200 Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData
201 }
202
203 Rectangle {
204 id: defaultSinkWidget
205
206 anchors.fill: parent
207 color: {
208 if (defaultSinkMouseArea.containsMouse)
209 return "#33808080";
210 return "transparent";
211 }
212
213 MaterialDesignIcon {
214 width: 16
215 height: 16
216 anchors.centerIn: parent
217
218 icon: {
219 if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id)
220 return "speaker";
221 return "speaker-off";
222 }
223 color: icon == "speaker" ? "white" : "#555"
224 }
225 }
226 }
227 }
228 }
229
230 Repeater {
231 id: defaultSourceRepeater
232
233 model: {
234 Array.from(Pipewire.devices.values)
235 .filter(dev => dev.type == "Audio/Device")
236 .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id ));
237 }
238
239 Item {
240 id: defaultSourceItem
241
242 required property var modelData
243 required property int index
244
245 PwObjectTracker {
246 objects: [defaultSourceItem.modelData]
247 }
248
249 Layout.column: 2
250 Layout.row: index
251
252 Layout.fillHeight: true
253
254 implicitWidth: 16 + 8
255
256 WrapperMouseArea {
257 id: defaultSourceMouseArea
258
259 anchors.fill: parent
260 hoverEnabled: true
261 cursorShape: Qt.PointingHandCursor
262
263 onClicked: {
264 Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData
265 }
266
267 Rectangle {
268 id: defaultSourceWidget
269
270 anchors.fill: parent
271 color: {
272 if (defaultSourceMouseArea.containsMouse)
273 return "#33808080";
274 return "transparent";
275 }
276
277 MaterialDesignIcon {
278 width: 16
279 height: 16
280 anchors.centerIn: parent
281
282 icon: {
283 if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id)
284 return "microphone";
285 return "microphone-off";
286 }
287 color: icon == "microphone" ? "white" : "#555"
288 }
289 }
290 }
291 }
292 }
293
294 Repeater {
295 id: profileRepeater
296
297 model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device")
298
299 Item {
300 id: profileItem
301
302 required property var modelData
303 required property int index
304
305 PwObjectTracker {
306 objects: [profileItem.modelData]
307 }
308
309 Layout.column: 3
310 Layout.row: index
311
312 Layout.fillWidth: true
313
314 implicitWidth: Math.max(profileBox.implicitWidth, 300)
315 implicitHeight: profileBox.height
316
317 ComboBox {
318 id: profileBox
319
320 model: profileItem.modelData.profiles
321
322 textRole: "description"
323 valueRole: "index"
324 onActivated: profileItem.modelData.setProfile(currentValue)
325
326 anchors.fill: parent
327
328 implicitContentWidthPolicy: ComboBox.WidestText
329
330 Connections {
331 target: profileItem.modelData
332 function onCurrentProfileChanged() {
333 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
334 }
335 }
336 Component.onCompleted: {
337 profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile);
338 }
339
340 Connections {
341 target: profileBox.popup
342 function onVisibleChanged() {
343 tooltip.openPopup = profileBox.popup.visible
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353 }
354}
diff --git a/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
new file mode 100644
index 00000000..3de69535
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml
@@ -0,0 +1,18 @@
1pragma Singleton
2
3import Quickshell
4import Quickshell.Io
5
6Singleton {
7 id: root
8
9 Socket {
10 id: agentSocket
11 connected: true
12 path: `${Quickshell.env("XDG_RUNTIME_DIR")}/gnupg/S.gpg-agent`
13 }
14
15 function reloadAgent() {
16 agentSocket.write("RELOADAGENT\n")
17 }
18}
diff --git a/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
new file mode 100644
index 00000000..3a78d91b
--- /dev/null
+++ b/accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml
@@ -0,0 +1,129 @@
1import QtQuick
2import QtQuick.Layouts
3import Quickshell
4import Quickshell.Services.Pipewire
5import Quickshell.Widgets
6
7Scope {
8 id: root
9
10 property bool show: false
11 property bool inhibited: true
12
13 PwObjectTracker {
14 objects: [ Pipewire.defaultAudioSink ]
15 }
16
17 Connections {
18 target: Pipewire.defaultAudioSink?.audio
19
20 function onVolumeChanged() {
21 root.show = true;
22 hideTimer.reset();
23 }
24 function onMutedChanged() {
25 root.show = true;
26 hideTimer.reset();
27 }
28 }
29
30 onShowChanged: {
31 if (show)
32 hideTimer.restart();
33 }
34
35 Timer {
36 id: hideTimer
37 interval: 2000
38 onTriggered: root.show = false
39 }
40
41 Timer {
42 id: startInhibit
43 interval: 100
44 running: true
45 onTriggered: root.inhibited = false;
46 }
47
48 LazyLoader {
49 active: root.show && !root.inhibited
50
51 Variants {
52 model: Quickshell.screens
53
54 delegate: Scope {
55 id: screenScope
56
57 required property var modelData
58
59 PanelWindow {
60 id: window
61
62 screen: screenScope.modelData
63
64 anchors.top: true
65 margins.top: (screen.height - window.height) / 2
66 exclusiveZone: 0
67
68 implicitWidth: 400
69 implicitHeight: 50
70
71 mask: Region {}
72
73 color: "transparent"
74
75 Rectangle {
76 anchors.fill: parent
77 color: Qt.rgba(0, 0, 0, 0.75)
78 }
79
80 RowLayout {
81 id: layout
82
83 anchors.centerIn: parent
84
85 height: 50 - 8*2
86 width: 400 - 8*2
87
88 MaterialDesignIcon {
89 id: volumeIcon
90
91 implicitWidth: parent.height
92 implicitHeight: parent.height
93
94 icon: {
95 if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted)
96 return "volume-off";
97 if (Pipewire.defaultAudioSink.audio.volume <= 0.33)
98 return "volume-low";
99 if (Pipewire.defaultAudioSink.audio.volume <= 0.67)
100 return "volume-medium";
101 return "volume-high";
102 }
103 }
104
105 Rectangle {
106 Layout.fillWidth: true
107
108 implicitHeight: 10
109
110 color: "#50ffffff"
111
112 Rectangle {
113 anchors {
114 left: parent.left
115 top: parent.top
116 bottom: parent.bottom
117 }
118
119 color: Pipewire.defaultAudioSink?.audio.muted ? "#70ffffff" : "white"
120
121 implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0)
122 }
123 }
124 }
125 }
126 }
127 }
128 }
129}
diff --git a/accounts/gkleen@sif/shell/quickshell/shell.qml b/accounts/gkleen@sif/shell/quickshell/shell.qml
index 1da9457d..3657f77f 100644
--- a/accounts/gkleen@sif/shell/quickshell/shell.qml
+++ b/accounts/gkleen@sif/shell/quickshell/shell.qml
@@ -41,4 +41,6 @@ ShellRoot {
41 } 41 }
42 42
43 Lockscreen {} 43 Lockscreen {}
44
45 VolumeOSD {}
44} 46}