From 0eda2c71c9a54fa042ab6762bec839daf9d78a6f Mon Sep 17 00:00:00 2001 From: Damocles Date: Sun, 12 Apr 2026 16:32:33 +0200 Subject: [PATCH] per-group borders, workspace hover, battery notifications, richer network tooltip --- modules/Bar.qml | 9 +++++++++ modules/BarGroup.qml | 3 ++- modules/Battery.qml | 20 ++++++++++++++++++++ modules/Bluetooth.qml | 1 + modules/Network.qml | 43 +++++++++++++++++++++++++++++++----------- modules/TrayMenu.qml | 5 ++--- modules/Workspaces.qml | 4 +++- 7 files changed, 69 insertions(+), 16 deletions(-) diff --git a/modules/Bar.qml b/modules/Bar.qml index e0a6dd4..7bfc4ed 100644 --- a/modules/Bar.qml +++ b/modules/Bar.qml @@ -40,6 +40,7 @@ PanelWindow { spacing: M.Theme.barSpacing M.BarGroup { + borderColor: M.Theme.base0D M.Clock { visible: M.Modules.clock } M.Notifications { visible: M.Modules.notifications } } @@ -53,12 +54,15 @@ PanelWindow { spacing: M.Theme.barSpacing M.BarGroup { + borderColor: M.Theme.base0D M.Workspaces { bar: bar; visible: M.Modules.workspaces } } M.BarGroup { + borderColor: M.Theme.base0D M.Tray { bar: bar; visible: M.Modules.tray } } M.BarGroup { + borderColor: M.Theme.base0D M.WindowTitle { Layout.maximumWidth: 400 visible: M.Modules.windowTitle @@ -78,18 +82,21 @@ PanelWindow { // Media M.BarGroup { + borderColor: M.Theme.base0E M.Mpris {} M.Volume { visible: M.Modules.volume } } // Connectivity M.BarGroup { + borderColor: M.Theme.base0D M.Network { visible: M.Modules.network } M.Bluetooth {} } // Controls M.BarGroup { + borderColor: M.Theme.base0A M.Backlight {} M.PowerProfile { visible: M.Modules.powerProfile } M.IdleInhibitor { visible: M.Modules.idleInhibitor } @@ -97,6 +104,7 @@ PanelWindow { // Stats M.BarGroup { + borderColor: M.Theme.base08 M.Cpu { visible: M.Modules.cpu } M.Memory { visible: M.Modules.memory } M.Temperature { visible: M.Modules.temperature } @@ -106,6 +114,7 @@ PanelWindow { // Power M.BarGroup { + borderColor: M.Theme.base08 M.Battery {} M.Power { bar: bar; visible: M.Modules.power } } diff --git a/modules/BarGroup.qml b/modules/BarGroup.qml index 1e2d37f..c628b9d 100644 --- a/modules/BarGroup.qml +++ b/modules/BarGroup.qml @@ -5,9 +5,10 @@ Rectangle { id: root default property alias content: row.children + property color borderColor: M.Theme.base02 color: "transparent" - border.color: M.Theme.base02 + border.color: borderColor border.width: 1 radius: M.Theme.radius diff --git a/modules/Battery.qml b/modules/Battery.qml index ffd61e7..3196a84 100644 --- a/modules/Battery.qml +++ b/modules/Battery.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell.Io import Quickshell.Services.UPower import "." as M @@ -21,6 +22,25 @@ M.BarSection { Behavior on _stateColor { ColorAnimation { duration: 300 } } + property bool _warnSent: false + property bool _critSent: false + + onChargingChanged: { _warnSent = false; _critSent = false; } + onPctChanged: { + if (charging) return; + if (pct < 15 && !_critSent) { + _critSent = true; _warnSent = true; + _notif.command = ["notify-send", "--urgency=critical", "--icon=battery-low", "--category=device", "Very Low Battery", "Connect to power now!"]; + _notif.running = true; + } else if (pct < 25 && !_warnSent) { + _warnSent = true; + _notif.command = ["notify-send", "--icon=battery-caution", "--category=device", "Low Battery"]; + _notif.running = true; + } + } + + Process { id: _notif } + M.BarIcon { icon: { if (root.charging) diff --git a/modules/Bluetooth.qml b/modules/Bluetooth.qml index 66ff6d7..8a9833b 100644 --- a/modules/Bluetooth.qml +++ b/modules/Bluetooth.qml @@ -65,4 +65,5 @@ M.BarSection { toggle.running = true; } } + } diff --git a/modules/Network.qml b/modules/Network.qml index 9afde20..5f36332 100644 --- a/modules/Network.qml +++ b/modules/Network.qml @@ -6,33 +6,44 @@ M.BarSection { id: root spacing: M.Theme.moduleSpacing tooltip: { - if (root.state === "wifi") - return "WiFi: " + root.essid + (root.ifname ? "\nInterface: " + root.ifname : ""); - if (root.state === "eth") - return "Ethernet: " + root.ifname; - if (root.state === "linked") - return "Linked: " + root.ifname; - return "Disconnected"; + const parts = []; + if (root.state === "wifi") { + parts.push("WiFi: " + root.essid); + if (root.signal) parts.push("Signal: " + root.signal + "%"); + } else if (root.state === "eth") { + parts.push("Ethernet"); + } else if (root.state === "linked") { + parts.push("Linked"); + } else { + return "Disconnected"; + } + if (root.ipAddr) parts.push("IP: " + root.ipAddr); + if (root.ifname) parts.push("Interface: " + root.ifname); + return parts.join("\n"); } property string ifname: "" property string essid: "" property string state: "disconnected" + property string ipAddr: "" + property string signal: "" Process { id: proc running: true - command: ["sh", "-c", "line=$(nmcli -t -f NAME,TYPE,DEVICE connection show --active 2>/dev/null | head -1); if [ -n \"$line\" ]; then echo \"$line\"; else dev=$(nmcli -t -f DEVICE,STATE device 2>/dev/null | grep ':connected' | grep -v ':unmanaged\\|:unavailable\\|:disconnected\\|:connecting' | head -1 | cut -d: -f1); if [ -n \"$dev\" ]; then echo \"linked:linked:$dev\"; fi; fi"] + command: ["sh", "-c", "line=$(nmcli -t -f NAME,TYPE,DEVICE connection show --active 2>/dev/null | head -1); if [ -z \"$line\" ]; then dev=$(nmcli -t -f DEVICE,STATE device 2>/dev/null | grep ':connected' | grep -v ':unmanaged\\|:unavailable\\|:disconnected\\|:connecting' | head -1 | cut -d: -f1); [ -n \"$dev\" ] && line=\"linked:linked:$dev\"; fi; [ -z \"$line\" ] && exit 0; echo \"$line\"; dev=$(echo \"$line\" | cut -d: -f3); ip=$(nmcli -t -f IP4.ADDRESS device show \"$dev\" 2>/dev/null | head -1 | cut -d: -f2); echo \"ip:${ip:-}\"; sig=$(nmcli -t -f GENERAL.SIGNAL device show \"$dev\" 2>/dev/null | head -1 | cut -d: -f2); echo \"sig:${sig:-}\""] stdout: StdioCollector { onStreamFinished: { - const line = text.trim(); - if (!line) { + const lines = text.trim().split("\n"); + if (!lines[0]) { root.state = "disconnected"; root.essid = ""; root.ifname = ""; + root.ipAddr = ""; + root.signal = ""; return; } - const parts = line.split(":"); + const parts = lines[0].split(":"); root.essid = parts[0] || ""; root.ifname = parts[2] || ""; if ((parts[1] || "").includes("wireless")) @@ -41,6 +52,15 @@ M.BarSection { root.state = "linked"; else root.state = "eth"; + // Parse extra info lines + root.ipAddr = ""; + root.signal = ""; + for (let i = 1; i < lines.length; i++) { + if (lines[i].startsWith("ip:")) + root.ipAddr = lines[i].slice(3); + else if (lines[i].startsWith("sig:")) + root.signal = lines[i].slice(4); + } } } } @@ -70,4 +90,5 @@ M.BarSection { color: root.state === "disconnected" ? M.Theme.base08 : M.Theme.base0C anchors.verticalCenter: parent.verticalCenter } + } diff --git a/modules/TrayMenu.qml b/modules/TrayMenu.qml index 0abc7db..07ed19a 100644 --- a/modules/TrayMenu.qml +++ b/modules/TrayMenu.qml @@ -10,8 +10,7 @@ M.PopupPanel { property var _currentHandle: handle property var _handleStack: [] - QsMenuOpener { - id: opener + property QsMenuOpener _opener: QsMenuOpener { menu: menuWindow._currentHandle } @@ -52,7 +51,7 @@ M.PopupPanel { } Repeater { - model: opener.children + model: menuWindow._opener.children delegate: Item { id: entryItem diff --git a/modules/Workspaces.qml b/modules/Workspaces.qml index 86107b4..3b20d2b 100644 --- a/modules/Workspaces.qml +++ b/modules/Workspaces.qml @@ -69,9 +69,11 @@ Row { required property var modelData readonly property bool active: modelData.id === root._activeId + property bool _hovered: false HoverHandler { onHoveredChanged: { + pill._hovered = hovered; const name = pill.modelData.name || ("Workspace " + pill.modelData.idx); if (hovered) { M.FlyoutState.text = name; @@ -87,7 +89,7 @@ Row { width: 20 height: 20 radius: M.Theme.radius - color: pill.active ? M.Theme.base0D : M.Theme.base02 + color: pill.active ? M.Theme.base0D : (pill._hovered ? M.Theme.base03 : M.Theme.base02) Behavior on color { ColorAnimation { duration: 150 } } Text {