From d20393e077b8d97b18f4a224ddcb20caf6dac23b Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 10 Sep 2025 15:57:26 +0200 Subject: ... --- accounts/gkleen@sif/shell/quickshell/Bar.qml | 31 +++++- .../gkleen@sif/shell/quickshell/BatteryWidget.qml | 61 +++++++++++ .../gkleen@sif/shell/quickshell/BrightnessOSD.qml | 117 +++++++++++++++++++++ .../shell/quickshell/BrightnessWidget.qml | 33 ++++++ .../shell/quickshell/MaterialDesignIcon.qml | 17 ++- .../gkleen@sif/shell/quickshell/PipewireWidget.qml | 3 +- .../gkleen@sif/shell/quickshell/PrivacyWidget.qml | 49 +++++++++ .../shell/quickshell/Services/Brightness.qml | 68 ++++++++++++ .../shell/quickshell/Services/Privacy.qml | 63 +++++++++++ .../gkleen@sif/shell/quickshell/SystemTray.qml | 6 +- accounts/gkleen@sif/shell/quickshell/UnixIPC.qml | 10 ++ accounts/gkleen@sif/shell/quickshell/shell.qml | 1 + 12 files changed, 449 insertions(+), 10 deletions(-) create mode 100644 accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml create mode 100644 accounts/gkleen@sif/shell/quickshell/Services/Privacy.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 3652af54..399b566f 100644 --- a/accounts/gkleen@sif/shell/quickshell/Bar.qml +++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml @@ -1,7 +1,6 @@ import Quickshell import QtQuick - PanelWindow { id: bar @@ -64,25 +63,49 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: 0 - PipewireWidget {} + PrivacyWidget { + id: privacy + } + + Item { + enabled: privacy.active + height: parent.height + width: 8 + } + + BatteryWidget {} + + Item { + height: parent.height + width: 8 + } + + BrightnessWidget {} Item { height: parent.height width: 4 } + PipewireWidget {} + + Item { + height: parent.height + width: 2 + } + SystemTray {} Item { height: parent.height - width: 4 + width: 2 } KeyboardLayout {} Item { height: parent.height - width: 4 + width: 8 - 4 } Clock {} diff --git a/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml new file mode 100644 index 00000000..896440f1 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/BatteryWidget.qml @@ -0,0 +1,61 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.UPower + +Item { + id: root + + height: parent.height + width: batteryIcon.width + anchors.verticalCenter: parent.verticalCenter + + property var batteryDevice: Array.from(UPower.devices.values).find(dev => dev.isLaptopBattery) + + WrapperMouseArea { + id: widgetMouseArea + + anchors.fill: parent + + hoverEnabled: true + + Item { + anchors.fill: parent + + MaterialDesignIcon { + id: batteryIcon + + implicitSize: 14 + anchors.centerIn: parent + + icon: { + if (!root.batteryDevice?.ready) + return "battery-unknown"; + + if (root.batteryDevice.state == UPowerDeviceState.FullyCharged) + return "power-plug-battery"; + + const perdec = 10 * Math.max(1, Math.ceil(root.batteryDevice.percentage * 10)); + if (root.batteryDevice.state == UPowerDeviceState.Charging) + return `battery-charging-${perdec}`; + if (perdec == 100) + return "battery"; + return `battery-${perdec}`; + } + color: { + if (!root.batteryDevice?.ready) + return "#555"; + + if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 20 * 60) + return "#f2201f"; + if (root.batteryDevice.state != UPowerDeviceState.FullyCharged && root.batteryDevice.state != UPowerDeviceState.Charging && root.batteryDevice.timeToEmpty < 40 * 60) + return "#f28a21"; + if (root.batteryDevice.state != UPowerDeviceState.FullyCharged) + return "#fff"; + return "#555"; + } + } + + } + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml new file mode 100644 index 00000000..a432179e --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/BrightnessOSD.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs.Services + +Scope { + id: root + + property bool show: false + property bool inhibited: true + + Connections { + target: Brightness + + function onCurrBrightnessChanged() { + root.show = true; + hideTimer.restart(); + } + } + + onShowChanged: { + if (show) + hideTimer.restart(); + } + + Timer { + id: hideTimer + interval: 1000 + onTriggered: root.show = false + } + + Timer { + id: startInhibit + interval: 100 + running: true + onTriggered: { + root.show = false; + 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 / 2 - 50 + 3.5 + exclusiveZone: 0 + exclusionMode: ExclusionMode.Ignore + + 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: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}` + } + + Rectangle { + Layout.fillWidth: true + + implicitHeight: 10 + + color: "#50ffffff" + + Rectangle { + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + } + + color: "white" + + implicitWidth: parent.width * Brightness.currBrightness + } + } + } + } + } + } + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml new file mode 100644 index 00000000..664b28e2 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/BrightnessWidget.qml @@ -0,0 +1,33 @@ +import QtQuick +import Quickshell +import Quickshell.Widgets +import qs.Services + +Item { + height: parent.height + width: brightnessIcon.width + anchors.verticalCenter: parent.verticalCenter + + WrapperMouseArea { + id: widgetMouseArea + + anchors.fill: parent + + property real sensitivity: (1 / 50) / 120 + onWheel: event => Brightness.currBrightness += event.angleDelta.y * sensitivity + + Item { + anchors.fill: parent + + MaterialDesignIcon { + id: brightnessIcon + + implicitSize: 14 + anchors.centerIn: parent + + icon: `brightness-${Math.min(7, Math.floor(Brightness.currBrightness * 7) + 1)}` + color: "#555" + } + } + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml index 387dcc8b..155a009e 100644 --- a/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml +++ b/accounts/gkleen@sif/shell/quickshell/MaterialDesignIcon.qml @@ -2,15 +2,26 @@ import QtQuick import QtQuick.Effects Item { - id: icon + id: root required property string icon property color color: "white" + property real implicitSize: 0 + + readonly property real actualSize: Math.min(root.width, root.height) + + implicitWidth: root.implicitSize + implicitHeight: root.implicitSize + Image { id: sourceImage - source: "file://" + @mdi@ + "/svg/" + icon.icon + ".svg" + source: "file://" + @mdi@ + "/svg/" + root.icon + ".svg" anchors.fill: parent + fillMode: Image.PreserveAspectFit + + sourceSize.width: root.actualSize + sourceSize.height: root.actualSize layer.enabled: true layer.effect: MultiEffect { @@ -18,7 +29,7 @@ Item { brightness: 1 colorization: 1 - colorizationColor: icon.color + colorizationColor: root.color } } } diff --git a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml index 7fdb5006..007ce100 100644 --- a/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml +++ b/accounts/gkleen@sif/shell/quickshell/PipewireWidget.qml @@ -50,8 +50,7 @@ Item { MaterialDesignIcon { id: volumeIcon - width: 16 - height: 16 + implicitSize: 14 anchors.centerIn: parent icon: { diff --git a/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml new file mode 100644 index 00000000..bb02528b --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/PrivacyWidget.qml @@ -0,0 +1,49 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs.Services + +Item { + height: parent.height + width: layout.childrenRect.width + anchors.verticalCenter: parent.verticalCenter + + readonly property bool active: Boolean(Privacy.activeItems) + + RowLayout { + id: layout + + anchors.fill: parent + + spacing: 8 + + Repeater { + model: Privacy.activeItems + + Item { + id: privacyItem + + required property var modelData; + + height: parent.height + width: icon.width + + MaterialDesignIcon { + id: icon + + implicitSize: 14 + anchors.centerIn: parent + + icon: { + if (privacyItem.modelData == Privacy.Item.Microphone) + return "microphone"; + if (privacyItem.modelData == Privacy.Item.Screensharing) + return "monitor-share"; + } + color: "#f2201f" + } + } + } + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml new file mode 100644 index 00000000..545ef24f --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/Services/Brightness.qml @@ -0,0 +1,68 @@ +pragma Singleton + +import QtQml +import Quickshell +import Quickshell.Io +import Custom as Custom + +Singleton { + id: root + + property string subsystem: "backlight" + property string device: "intel_backlight" + + property real currBrightness + property real exponent: 4 + + function calcCurrBrightness() { + if (!currFile.loaded || !maxFile.loaded) + return undefined; + const curr = Number(currFile.text()); + const max = Number(maxFile.text()); + const val = Math.pow(curr / max, 1 / root.exponent); + return val; + } + + Component.onCompleted: root.currBrightness = root.calcCurrBrightness() + Connections { + target: currFile + onLoaded: root.currBrightness = root.calcCurrBrightness() + } + Connections { + target: maxFile + onLoaded: root.currBrightness = root.calcCurrBrightness() + } + + onCurrBrightnessChanged: { + root.currBrightness = Math.max(0, Math.min(1, root.currBrightness)); + + const prev = root.calcCurrBrightness(); + if (typeof prev === 'undefined' || Math.abs(root.currBrightness - prev) < 0.01) + return; + + const max = Number(maxFile.text()); + const actual = Number(currFile.text()); + let curr = Math.max(0, Math.min(max, Math.pow(root.currBrightness, root.exponent) * max)); + if (Math.round(curr) == actual && curr < actual) + curr = Math.max(0, actual - 1); + else if (Math.round(curr) == actual && curr > actual) + curr = Math.min(max, actual + 1); + // root.currBrightness = Math.pow(curr / max, 1 / root.exponent); + Custom.Systemd.setBrightness(root.subsystem, root.device, Math.round(curr)); + } + + FileView { + id: currFile + path: `/sys/class/${root.subsystem}/${root.device}/brightness` + blockAllReads: true + watchChanges: true + onFileChanged: reload() + } + FileView { + id: maxFile + path: `/sys/class/${root.subsystem}/${root.device}/max_brightness` + blockAllReads: true + watchChanges: true + onFileChanged: reload() + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml new file mode 100644 index 00000000..9c813e49 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/Services/Privacy.qml @@ -0,0 +1,63 @@ +pragma Singleton + +import QtQml +import Quickshell +import Quickshell.Services.Pipewire + +Singleton { + id: root + + PwObjectTracker { + objects: Pipewire.nodes.values + } + + enum Item { + Microphone, + Screensharing + } + + readonly property list activeItems: { + var items = []; + if (microphoneActive) + items.push(Privacy.Item.Microphone); + if (screensharingActive) + items.push(Privacy.Item.Screensharing); + return items; + } + + readonly property bool microphoneActive: { + if (!Pipewire.ready || !Pipewire.nodes?.values) { + return false + } + + for (const node of Pipewire.nodes.values) { + if (!node || (node.type & PwNodeType.AudioInStream) != PwNodeType.AudioInStream) + continue; + + if (node.properties?.["stream.monitor"] === "true") + continue; + + if (node.audio?.muted) + continue; + + return true; + } + + return false; + } + + readonly property bool screensharingActive: { + if (!Pipewire.ready || !Pipewire.nodes?.values) { + return false + } + + for (const node of Pipewire.nodes.values) { + if (!node || (node.type & PwNodeType.VideoInStream) != PwNodeType.VideoInStream) + continue; + + return true; + } + + return false; + } +} diff --git a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml index 55b1690e..956f3995 100644 --- a/accounts/gkleen@sif/shell/quickshell/SystemTray.qml +++ b/accounts/gkleen@sif/shell/quickshell/SystemTray.qml @@ -116,7 +116,7 @@ Item { color: "black" implicitWidth: Math.max(tooltipTitle.contentWidth, tooltipDescription.contentWidth) + 16 - implicitHeight: tooltipTitle.contentHeight + tooltipDescription.contentHeight + 16 + implicitHeight: (trayItem.tooltipTitle ? tooltipTitle.contentHeight : 0) + (trayItem.tooltipDescription ? tooltipDescription.contentHeight : 0) + 16 WrapperMouseArea { id: tooltipMouseArea @@ -130,6 +130,8 @@ Item { Text { id: tooltipTitle + enabled: trayItem.tooltipTitle + font.pointSize: 10 font.family: "Fira Sans" font.bold: true @@ -141,6 +143,8 @@ Item { Text { id: tooltipDescription + enabled: trayItem.tooltipDescription + font.pointSize: 10 font.family: "Fira Sans" color: "white" diff --git a/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml index 7b308ec0..742ef4f5 100644 --- a/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml +++ b/accounts/gkleen@sif/shell/quickshell/UnixIPC.qml @@ -1,6 +1,7 @@ import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire +import qs.Services Scope { id: root @@ -16,6 +17,8 @@ Scope { if (command.Volume) root.onCommandVolume(command.Volume); + else if (command.Brightness) + root.onCommandBrightness(command.Brightness); else console.warn("UnixIPC: Command not handled:", JSON.stringify(command)); } catch (e) { @@ -46,4 +49,11 @@ Scope { if (command["mic-muted"] === "toggle") Pipewire.defaultAudioSource.audio.muted = !Pipewire.defaultAudioSource.audio.muted; } + + function onCommandBrightness(command) { + if (command === "up") + Brightness.currBrightness += 0.02 + if (command === "down") + Brightness.currBrightness -= 0.02 + } } diff --git a/accounts/gkleen@sif/shell/quickshell/shell.qml b/accounts/gkleen@sif/shell/quickshell/shell.qml index 3bb2ae00..0fa45f79 100644 --- a/accounts/gkleen@sif/shell/quickshell/shell.qml +++ b/accounts/gkleen@sif/shell/quickshell/shell.qml @@ -43,6 +43,7 @@ ShellRoot { Lockscreen {} VolumeOSD {} + BrightnessOSD {} UnixIPC {} } -- cgit v1.2.3