From 8a551339cbfaf106ac7d6f1ca5230196be539167 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Mon, 8 Sep 2025 20:00:22 +0200 Subject: ... --- accounts/gkleen@sif/shell/quickshell/Bar.qml | 7 + accounts/gkleen@sif/shell/quickshell/Clock.qml | 3 +- .../gkleen@sif/shell/quickshell/Lockscreen.qml | 15 +- .../shell/quickshell/MaterialDesignIcon.qml | 24 ++ .../gkleen@sif/shell/quickshell/PipewireWidget.qml | 354 +++++++++++++++++++++ .../shell/quickshell/Services/GpgAgent.qml | 18 ++ accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml | 129 ++++++++ accounts/gkleen@sif/shell/quickshell/shell.qml | 2 + 8 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/Services/GpgAgent.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/VolumeOSD.qml (limited to 'accounts/gkleen@sif/shell/quickshell') 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 { anchors.verticalCenter: parent.verticalCenter spacing: 0 + PipewireWidget {} + + Item { + height: parent.height + width: 4 + } + SystemTray {} 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 { id: clockTooltipContent margin: 8 - leftMargin: 0 ColumnLayout { + anchors.centerIn: parent + Text { id: yearLabel 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 import Quickshell.Wayland import Quickshell.Io import Quickshell.Services.Pam +import Quickshell.Services.Mpris +import Custom as Custom +import qs.Services import QtQml Scope { @@ -38,9 +41,19 @@ Scope { WlSessionLock { id: lock - onLockedChanged: { + onLockStateChanged: { if (!locked && pam.active) pam.abort(); + + if (locked) { + Custom.KeePassXC.lockAllDatabases(); + Array.from(Mpris.players.values).forEach(player => { + if (player.canPause && player.isPlaying) + player.pause(); + }); + // Custom.Systemd.stopUserUnit("gpg-agent.service", "replace"); + GpgAgent.reloadAgent(); + } } 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 @@ +import QtQuick +import QtQuick.Effects + +Item { + id: icon + + required property string icon + property color color: "white" + + Image { + id: sourceImage + source: "file://" + @mdi@ + "/svg/" + icon.icon + ".svg" + anchors.fill: parent + + layer.enabled: true + layer.effect: MultiEffect { + id: effect + + brightness: 1 + colorization: 1 + colorizationColor: icon.color + } + } +} 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 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Fusion +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Widgets + +Item { + height: parent.height + width: volumeIcon.width + 8 + anchors.verticalCenter: parent.verticalCenter + + PwObjectTracker { + objects: [Pipewire.defaultAudioSink] + } + + WrapperMouseArea { + id: widgetMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + if (!Pipewire.defaultAudioSink) + return; + Pipewire.defaultAudioSink.audio.muted = !Pipewire.defaultAudioSink.audio.muted; + } + + property real sensitivity: (1 / 20) / 120 + onWheel: event => { + if (!Pipewire.defaultAudioSink) + return; + Pipewire.defaultAudioSink.audio.volume += event.angleDelta.y * sensitivity; + } + + Rectangle { + id: volumeWidget + + anchors.fill: parent + color: { + if (widgetMouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } + + Item { + anchors.fill: parent + + MaterialDesignIcon { + id: volumeIcon + + width: 16 + height: 16 + anchors.centerIn: parent + + icon: { + if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted) + return "volume-off"; + if (Pipewire.defaultAudioSink.audio.volume <= 0.33) + return "volume-low"; + if (Pipewire.defaultAudioSink.audio.volume <= 0.67) + return "volume-medium"; + return "volume-high"; + } + color: "#555" + } + } + } + } + + Loader { + id: tooltipLoader + + active: false + + Connections { + target: widgetMouseArea + function onContainsMouseChanged() { + if (widgetMouseArea.containsMouse) + tooltipLoader.active = true; + } + } + + PwObjectTracker { + objects: Pipewire.devices + } + PwObjectTracker { + objects: Pipewire.nodes + } + + sourceComponent: PopupWindow { + id: tooltip + + property bool openPopup: false + property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup + + anchor { + item: widgetMouseArea + edges: Edges.Bottom | Edges.Left + } + visible: false + + onNextVisibleChanged: hangTimer.restart() + + Timer { + id: hangTimer + interval: 100 + onTriggered: { + tooltip.visible = tooltip.nextVisible; + if (!tooltip.visible) + tooltipLoader.active = false; + } + } + + implicitWidth: tooltipContent.width + implicitHeight: tooltipContent.height + color: "black" + + WrapperMouseArea { + id: tooltipMouseArea + + hoverEnabled: true + enabled: true + + anchors.fill: parent + + WrapperItem { + id: tooltipContent + + margin: 8 + bottomMargin: 8 + Math.max(0, 200 - tooltipLayout.implicitHeight) + + GridLayout { + id: tooltipLayout + + columns: 4 + + Repeater { + model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") + + Item { + id: descItem + + required property var modelData + required property int index + + Layout.column: 0 + Layout.row: index + + implicitWidth: descText.contentWidth + implicitHeight: descText.contentHeight + + Text { + id: descText + + color: "white" + font.pointSize: 10 + font.family: "Fira Sans" + + text: descItem.modelData.description + } + } + } + + Repeater { + id: defaultSinkRepeater + + model: { + Array.from(Pipewire.devices.values) + .filter(dev => dev.type == "Audio/Device") + .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSink && node.device?.id == device.id )); + } + + Item { + id: defaultSinkItem + + required property var modelData + required property int index + + PwObjectTracker { + objects: [defaultSinkItem.modelData] + } + + Layout.column: 1 + Layout.row: index + + Layout.fillHeight: true + + implicitWidth: 16 + 8 + + WrapperMouseArea { + id: defaultSinkMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + Pipewire.preferredDefaultAudioSink = defaultSinkItem.modelData + } + + Rectangle { + id: defaultSinkWidget + + anchors.fill: parent + color: { + if (defaultSinkMouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } + + MaterialDesignIcon { + width: 16 + height: 16 + anchors.centerIn: parent + + icon: { + if (defaultSinkItem.modelData?.id == Pipewire.defaultAudioSink?.id) + return "speaker"; + return "speaker-off"; + } + color: icon == "speaker" ? "white" : "#555" + } + } + } + } + } + + Repeater { + id: defaultSourceRepeater + + model: { + Array.from(Pipewire.devices.values) + .filter(dev => dev.type == "Audio/Device") + .map(device => Array.from(Pipewire.nodes.values).find(node => node.type == PwNodeType.AudioSource && node.device?.id == device.id )); + } + + Item { + id: defaultSourceItem + + required property var modelData + required property int index + + PwObjectTracker { + objects: [defaultSourceItem.modelData] + } + + Layout.column: 2 + Layout.row: index + + Layout.fillHeight: true + + implicitWidth: 16 + 8 + + WrapperMouseArea { + id: defaultSourceMouseArea + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + onClicked: { + Pipewire.preferredDefaultAudioSource = defaultSourceItem.modelData + } + + Rectangle { + id: defaultSourceWidget + + anchors.fill: parent + color: { + if (defaultSourceMouseArea.containsMouse) + return "#33808080"; + return "transparent"; + } + + MaterialDesignIcon { + width: 16 + height: 16 + anchors.centerIn: parent + + icon: { + if (defaultSourceItem.modelData?.id == Pipewire.defaultAudioSource?.id) + return "microphone"; + return "microphone-off"; + } + color: icon == "microphone" ? "white" : "#555" + } + } + } + } + } + + Repeater { + id: profileRepeater + + model: Array.from(Pipewire.devices.values).filter(dev => dev.type == "Audio/Device") + + Item { + id: profileItem + + required property var modelData + required property int index + + PwObjectTracker { + objects: [profileItem.modelData] + } + + Layout.column: 3 + Layout.row: index + + Layout.fillWidth: true + + implicitWidth: Math.max(profileBox.implicitWidth, 300) + implicitHeight: profileBox.height + + ComboBox { + id: profileBox + + model: profileItem.modelData.profiles + + textRole: "description" + valueRole: "index" + onActivated: profileItem.modelData.setProfile(currentValue) + + anchors.fill: parent + + implicitContentWidthPolicy: ComboBox.WidestText + + Connections { + target: profileItem.modelData + function onCurrentProfileChanged() { + profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); + } + } + Component.onCompleted: { + profileBox.currentIndex = Array.from(profileItem.modelData.profiles).findIndex(profile => profile.index == profileItem.modelData.currentProfile); + } + + Connections { + target: profileBox.popup + function onVisibleChanged() { + tooltip.openPopup = profileBox.popup.visible + } + } + } + } + } + } + } + } + } + } +} 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 @@ +pragma Singleton + +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + Socket { + id: agentSocket + connected: true + path: `${Quickshell.env("XDG_RUNTIME_DIR")}/gnupg/S.gpg-agent` + } + + function reloadAgent() { + agentSocket.write("RELOADAGENT\n") + } +} 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 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import Quickshell.Widgets + +Scope { + id: root + + property bool show: false + property bool inhibited: true + + PwObjectTracker { + objects: [ Pipewire.defaultAudioSink ] + } + + Connections { + target: Pipewire.defaultAudioSink?.audio + + function onVolumeChanged() { + root.show = true; + hideTimer.reset(); + } + function onMutedChanged() { + root.show = true; + hideTimer.reset(); + } + } + + onShowChanged: { + if (show) + hideTimer.restart(); + } + + Timer { + id: hideTimer + interval: 2000 + onTriggered: root.show = false + } + + Timer { + id: startInhibit + interval: 100 + running: true + onTriggered: root.inhibited = false; + } + + LazyLoader { + active: root.show && !root.inhibited + + Variants { + model: Quickshell.screens + + delegate: Scope { + id: screenScope + + required property var modelData + + PanelWindow { + id: window + + screen: screenScope.modelData + + anchors.top: true + margins.top: (screen.height - window.height) / 2 + exclusiveZone: 0 + + implicitWidth: 400 + implicitHeight: 50 + + mask: Region {} + + color: "transparent" + + Rectangle { + anchors.fill: parent + color: Qt.rgba(0, 0, 0, 0.75) + } + + RowLayout { + id: layout + + anchors.centerIn: parent + + height: 50 - 8*2 + width: 400 - 8*2 + + MaterialDesignIcon { + id: volumeIcon + + implicitWidth: parent.height + implicitHeight: parent.height + + icon: { + if (!Pipewire.defaultAudioSink || Pipewire.defaultAudioSink.audio.muted) + return "volume-off"; + if (Pipewire.defaultAudioSink.audio.volume <= 0.33) + return "volume-low"; + if (Pipewire.defaultAudioSink.audio.volume <= 0.67) + return "volume-medium"; + return "volume-high"; + } + } + + Rectangle { + Layout.fillWidth: true + + implicitHeight: 10 + + color: "#50ffffff" + + Rectangle { + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + + color: Pipewire.defaultAudioSink?.audio.muted ? "#70ffffff" : "white" + + implicitWidth: parent.width * (Pipewire.defaultAudioSink?.audio.volume ?? 0) + } + } + } + } + } + } + } +} 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 { } Lockscreen {} + + VolumeOSD {} } -- cgit v1.2.3