From fe73986a017075db6e9da3d4391d63436bc674a9 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Wed, 13 May 2026 01:43:30 -0400 Subject: [PATCH] Address momus review: fix icons, vertical layout, popup coordination - Replace nerd font glyphs that were stripped during the initial widget writes with verified codepoints sourced from the JetBrains Mono Nerd Font cmap (cpu-64-bit, scale-balance, notification-clear-all, etc). - systemStats now switches to a Column layout when the bar is vertical so the widget no longer overflows a 28px-wide side bar. - Remove notificationCenter from defaults: it registers a notification server and would collide with the mako daemon Omarchy autostarts. - PopupCard owns the popout coordinator lifecycle and delegates close to its widget owner when provided; calendar and media now pass owner. - brightness debounces brightnessctl writes through a coalescing timer to avoid spawning a process per slider tick. - audioPanel tracks unfiltered Pipewire sinks/streams via PwObjectTracker so audio metadata becomes available before filtering. - ModuleSlot re-injects bar/moduleName/settings whenever the bar config serial changes so widget settings update live with bar.json. - Guard null bar references in workspacesPro and systemStats components that are constructed before bar injection. --- default/quickshell/bar/bar-defaults.json | 4 +- default/quickshell/bar/common/PopupCard.qml | 25 +++--- default/quickshell/bar/shell.qml | 19 +++-- default/quickshell/bar/widgets/audioPanel.qml | 33 +++++--- .../quickshell/bar/widgets/bluetoothPanel.qml | 8 +- default/quickshell/bar/widgets/brightness.qml | 22 +++++- default/quickshell/bar/widgets/calendar.qml | 5 +- default/quickshell/bar/widgets/media.qml | 11 +-- .../quickshell/bar/widgets/networkPanel.qml | 4 +- .../bar/widgets/notificationCenter.qml | 6 +- default/quickshell/bar/widgets/powerMenu.qml | 10 +-- .../quickshell/bar/widgets/systemStats.qml | 77 +++++++++++++------ .../quickshell/bar/widgets/weatherFlyout.qml | 4 +- 13 files changed, 152 insertions(+), 76 deletions(-) diff --git a/default/quickshell/bar/bar-defaults.json b/default/quickshell/bar/bar-defaults.json index aad6a492..d39a3b0d 100644 --- a/default/quickshell/bar/bar-defaults.json +++ b/default/quickshell/bar/bar-defaults.json @@ -4,8 +4,8 @@ "centerAnchor": "calendar", "layout": { "left": ["omarchy", "workspacesPro"], - "center": ["media", "calendar", "weatherFlyout", "update", "voxtype", "screenRecording", "idle"], - "right": ["tray", "systemStats", "notificationCenter", "microphone", "bluetoothPanel", "networkPanel", "audioPanel", "brightness", "powerProfile", "battery", "powerMenu"] + "center": ["media", "calendar", "weatherFlyout", "update", "voxtype", "screenRecording", "idle", "notifications"], + "right": ["tray", "systemStats", "microphone", "bluetoothPanel", "networkPanel", "audioPanel", "brightness", "powerProfile", "battery", "powerMenu"] }, "modules": { "calendar": { diff --git a/default/quickshell/bar/common/PopupCard.qml b/default/quickshell/bar/common/PopupCard.qml index 96184dee..e79ba207 100644 --- a/default/quickshell/bar/common/PopupCard.qml +++ b/default/quickshell/bar/common/PopupCard.qml @@ -11,13 +11,13 @@ PopupWindow { property int padding: 14 property int contentWidth: 280 property int contentHeight: 200 - property bool dismissOnOutsideClick: true property bool open: false - onOpenChanged: { - if (!bar) return - if (open) bar.requestPopout(owner || root) - else if (bar.activePopout === (owner || root)) bar.releasePopout(owner || root) + readonly property var coordinatorKey: owner || root + + function closePopout() { + if (owner && "closePopout" in owner) owner.closePopout() + else root.open = false } default property alias contentItem: contentHolder.children @@ -27,6 +27,12 @@ PopupWindow { implicitWidth: contentWidth implicitHeight: contentHeight + onOpenChanged: { + if (!bar) return + if (open) bar.requestPopout(coordinatorKey) + else if (bar.activePopout === coordinatorKey) bar.releasePopout(coordinatorKey) + } + anchor { id: popupAnchor window: anchorItem ? anchorItem.QsWindow.window : null @@ -71,7 +77,7 @@ PopupWindow { border.color: root.bar ? root.bar.foreground : "#cacccc" border.width: 1 radius: 0 - opacity: root.open ? 0.97 : 0 + opacity: root.open ? 0.98 : 0 Behavior on opacity { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } @@ -82,12 +88,5 @@ PopupWindow { anchors.fill: parent anchors.margins: root.padding } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - hoverEnabled: false - propagateComposedEvents: true - } } } diff --git a/default/quickshell/bar/shell.qml b/default/quickshell/bar/shell.qml index 3369ae9f..ec1df123 100644 --- a/default/quickshell/bar/shell.qml +++ b/default/quickshell/bar/shell.qml @@ -1104,11 +1104,20 @@ ShellRoot { active: slot.qmlCustom || slot.firstParty source: slot.qmlCustom ? root.customModuleSource(slot.moduleName) : (slot.firstParty ? slot.firstPartySource : "") anchors.fill: parent - onLoaded: { - if (item && "bar" in item) item.bar = root - if (item && "moduleName" in item) item.moduleName = slot.moduleName - if (item && "settings" in item) item.settings = root.moduleSettings(slot.moduleName) - } + onLoaded: slot.injectProps() + } + + function injectProps() { + var target = qmlLoader.item + if (!target) return + if ("bar" in target) target.bar = root + if ("moduleName" in target) target.moduleName = moduleName + if ("settings" in target) target.settings = root.moduleSettings(moduleName) + } + + Connections { + target: root + function onBarConfigSerialChanged() { slot.injectProps() } } Component { diff --git a/default/quickshell/bar/widgets/audioPanel.qml b/default/quickshell/bar/widgets/audioPanel.qml index 927caa96..63bfc32c 100644 --- a/default/quickshell/bar/widgets/audioPanel.qml +++ b/default/quickshell/bar/widgets/audioPanel.qml @@ -18,21 +18,35 @@ Item { readonly property var source: Pipewire.defaultAudioSource readonly property var nodes: Pipewire.nodes ? Pipewire.nodes.values : [] - readonly property var audioSinks: { + readonly property var candidateSinks: { var list = [] for (var i = 0; i < nodes.length; i++) { var node = nodes[i] - if (node && node.isSink && !node.isStream && node.audio) list.push(node) + if (node && node.isSink && !node.isStream) list.push(node) } return list } + readonly property var candidateStreams: { + var list = [] + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i] + if (node && node.isStream && !node.isSink) list.push(node) + } + return list + } + + readonly property var audioSinks: { + var list = [] + for (var i = 0; i < candidateSinks.length; i++) + if (candidateSinks[i].audio) list.push(candidateSinks[i]) + return list + } + readonly property var audioStreams: { var list = [] - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i] - if (node && node.isStream && !node.isSink && node.audio) list.push(node) - } + for (var i = 0; i < candidateStreams.length; i++) + if (candidateStreams[i].audio) list.push(candidateStreams[i]) return list } @@ -65,8 +79,9 @@ Item { implicitWidth: button.implicitWidth implicitHeight: button.implicitHeight - PwObjectTracker { objects: root.audioSinks } - PwObjectTracker { objects: root.audioStreams } + PwObjectTracker { objects: root.candidateSinks } + PwObjectTracker { objects: root.candidateStreams } + PwObjectTracker { objects: root.sink ? [root.sink] : [] } Common.WidgetButton { id: button @@ -224,7 +239,7 @@ Item { node.properties ? node.properties["device.icon-name"] : "", node.properties ? node.properties["device.product.name"] : "" ].join(" ")).toLowerCase() - if (blob.indexOf("headphone") !== -1 || blob.indexOf("headset") !== -1) return "" + if (blob.indexOf("headphone") !== -1 || blob.indexOf("headset") !== -1) return "󰋋" if (blob.indexOf("bluetooth") !== -1) return "󰂯" if (blob.indexOf("hdmi") !== -1 || blob.indexOf("display") !== -1) return "󰍹" return "󰓃" diff --git a/default/quickshell/bar/widgets/bluetoothPanel.qml b/default/quickshell/bar/widgets/bluetoothPanel.qml index 28836320..2a6187a0 100644 --- a/default/quickshell/bar/widgets/bluetoothPanel.qml +++ b/default/quickshell/bar/widgets/bluetoothPanel.qml @@ -41,7 +41,7 @@ Item { if (!adapter) return "" if (!adapter.enabled) return "󰂲" if (connectedDevices.length > 0) return "󰂱" - return "" + return "󰂯" } visible: adapter !== null @@ -92,7 +92,7 @@ Item { Item { width: parent.width - 200; height: 1 } Common.PillButton { - iconText: root.adapter && root.adapter.enabled ? "" : "" + iconText: root.adapter && root.adapter.enabled ? "󰂯" : "󰂲" text: root.adapter && root.adapter.enabled ? "On" : "Off" foreground: root.bar.foreground horizontalPadding: 8 @@ -102,7 +102,7 @@ Item { } Common.PillButton { - iconText: "" + iconText: "󰂳" foreground: root.bar.foreground horizontalPadding: 8 verticalPadding: 4 @@ -120,7 +120,7 @@ Item { required property var modelData width: parent.width - iconText: modelData && modelData.connected ? "󰂱" : "" + iconText: modelData && modelData.connected ? "󰂱" : "󰂯" text: { var label = modelData ? (modelData.deviceName || modelData.name || modelData.address || "Device") : "" if (modelData && modelData.batteryAvailable) label += " " + Math.round(modelData.battery * 100) + "%" diff --git a/default/quickshell/bar/widgets/brightness.qml b/default/quickshell/bar/widgets/brightness.qml index 588ac03b..8e0fbca3 100644 --- a/default/quickshell/bar/widgets/brightness.qml +++ b/default/quickshell/bar/widgets/brightness.qml @@ -35,11 +35,29 @@ Item { if (!readProc.running) readProc.running = true } + property int pendingPercent: -1 + function setBrightness(percent) { var clamped = Math.max(1, Math.min(100, Math.round(percent))) currentPercent = clamped - writeProc.command = ["bash", "-lc", "brightnessctl set " + clamped + "% >/dev/null"] - writeProc.running = true + pendingPercent = clamped + writeTimer.restart() + } + + Timer { + id: writeTimer + interval: 60 + repeat: false + onTriggered: { + if (writeProc.running) { + writeTimer.restart() + return + } + if (pendingPercent < 0) return + writeProc.command = ["bash", "-lc", "brightnessctl set " + pendingPercent + "% >/dev/null"] + pendingPercent = -1 + writeProc.running = true + } } Component.onCompleted: refresh() diff --git a/default/quickshell/bar/widgets/calendar.qml b/default/quickshell/bar/widgets/calendar.qml index 2fe16abd..fb3655bf 100644 --- a/default/quickshell/bar/widgets/calendar.qml +++ b/default/quickshell/bar/widgets/calendar.qml @@ -68,6 +68,7 @@ Item { id: popup anchorItem: button bar: root.bar + owner: root open: root.popupOpen contentWidth: 300 contentHeight: header.implicitHeight + grid.implicitHeight + 36 @@ -85,7 +86,7 @@ Item { id: prevButton anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - iconText: "" + iconText: "󰅁" foreground: root.bar.foreground horizontalPadding: 8 verticalPadding: 4 @@ -105,7 +106,7 @@ Item { id: nextButton anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - iconText: "" + iconText: "󰅂" foreground: root.bar.foreground horizontalPadding: 8 verticalPadding: 4 diff --git a/default/quickshell/bar/widgets/media.qml b/default/quickshell/bar/widgets/media.qml index 726c85ca..88ac0adb 100644 --- a/default/quickshell/bar/widgets/media.qml +++ b/default/quickshell/bar/widgets/media.qml @@ -28,7 +28,7 @@ Item { } readonly property bool hasMedia: activePlayer !== null && (activePlayer.trackTitle || activePlayer.trackArtist) - readonly property string playIcon: activePlayer && activePlayer.isPlaying ? "" : "" + readonly property string playIcon: activePlayer && activePlayer.isPlaying ? "󰏤" : "󰐊" readonly property string title: activePlayer ? (activePlayer.trackTitle || "") : "" readonly property string artist: activePlayer ? (activePlayer.trackArtist || "") : "" @@ -118,6 +118,7 @@ Item { id: popup anchorItem: root bar: root.bar + owner: root open: root.popupOpen contentWidth: 320 contentHeight: column.implicitHeight + 28 @@ -151,7 +152,7 @@ Item { Text { anchors.centerIn: parent visible: !root.activePlayer || !root.activePlayer.trackArtUrl - text: "" + text: "󰝚" color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: 28 @@ -199,7 +200,7 @@ Item { spacing: 6 Common.PillButton { - iconText: "" + iconText: "󰒮" foreground: root.bar.foreground horizontalPadding: 10 verticalPadding: 6 @@ -209,7 +210,7 @@ Item { } Common.PillButton { - iconText: root.activePlayer && root.activePlayer.isPlaying ? "" : "" + iconText: root.activePlayer && root.activePlayer.isPlaying ? "󰏤" : "󰐊" foreground: root.bar.foreground horizontalPadding: 14 verticalPadding: 6 @@ -220,7 +221,7 @@ Item { } Common.PillButton { - iconText: "" + iconText: "󰒭" foreground: root.bar.foreground horizontalPadding: 10 verticalPadding: 6 diff --git a/default/quickshell/bar/widgets/networkPanel.qml b/default/quickshell/bar/widgets/networkPanel.qml index 394878f8..689ad216 100644 --- a/default/quickshell/bar/widgets/networkPanel.qml +++ b/default/quickshell/bar/widgets/networkPanel.qml @@ -149,7 +149,7 @@ Item { } Common.PillButton { - iconText: "" + iconText: "󰑐" foreground: root.bar.foreground horizontalPadding: 8 verticalPadding: 4 @@ -205,7 +205,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰖩" text: "Open Wi-Fi manager" foreground: root.bar.foreground horizontalPadding: 10 diff --git a/default/quickshell/bar/widgets/notificationCenter.qml b/default/quickshell/bar/widgets/notificationCenter.qml index c08391c9..927ccaec 100644 --- a/default/quickshell/bar/widgets/notificationCenter.qml +++ b/default/quickshell/bar/widgets/notificationCenter.qml @@ -20,7 +20,7 @@ Item { readonly property string icon: { if (dnd) return "󰂛" if (count > 0) return "󱅫" - return "" + return "󰂚" } NotificationServer { @@ -131,7 +131,7 @@ Item { } Common.PillButton { - iconText: "" + iconText: "󰎟" foreground: root.bar.foreground horizontalPadding: 8 verticalPadding: 4 @@ -196,7 +196,7 @@ Item { Common.PillButton { id: dismissBtn - iconText: "" + iconText: "󰅖" foreground: root.bar.foreground horizontalPadding: 4 verticalPadding: 0 diff --git a/default/quickshell/bar/widgets/powerMenu.qml b/default/quickshell/bar/widgets/powerMenu.qml index c54e3af6..6274c678 100644 --- a/default/quickshell/bar/widgets/powerMenu.qml +++ b/default/quickshell/bar/widgets/powerMenu.qml @@ -25,7 +25,7 @@ Item { id: button anchors.fill: parent bar: root.bar - text: "⏻" + text: "󰐥" fontSize: 14 tooltipText: "Power menu" onPressed: function() { root.popupOpen = !root.popupOpen } @@ -54,7 +54,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰌾" text: "Lock" foreground: root.bar.foreground horizontalPadding: 10 @@ -74,7 +74,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰍃" text: "Log out" foreground: root.bar.foreground horizontalPadding: 10 @@ -84,7 +84,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰜉" text: "Reboot" foreground: root.bar.foreground horizontalPadding: 10 @@ -94,7 +94,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰐥" text: "Shut down" foreground: root.bar.urgent horizontalPadding: 10 diff --git a/default/quickshell/bar/widgets/systemStats.qml b/default/quickshell/bar/widgets/systemStats.qml index 82620852..7492add8 100644 --- a/default/quickshell/bar/widgets/systemStats.qml +++ b/default/quickshell/bar/widgets/systemStats.qml @@ -119,30 +119,63 @@ Item { } } - implicitWidth: row.implicitWidth + 6 - implicitHeight: bar ? bar.barSize : 26 + readonly property bool vertical: bar ? bar.vertical : false - Row { - id: row + implicitWidth: vertical ? (bar ? bar.barSize : 28) : (statLayout.item ? statLayout.item.implicitWidth + 6 : 0) + implicitHeight: vertical ? (statLayout.item ? statLayout.item.implicitHeight + 6 : 0) : (bar ? bar.barSize : 26) + + readonly property color statColor: bar ? bar.foreground : "#cacccc" + readonly property string statFont: bar ? bar.fontFamily : "JetBrainsMono Nerd Font" + + Loader { + id: statLayout anchors.centerIn: parent - spacing: 8 + sourceComponent: root.vertical ? statColumn : statRow + } - StatPill { - glyph: "󰍛" - percent: root.cpuPercent - history: root.cpuHistory - vertical: root.bar.vertical - barFg: root.bar.foreground - fontFamily: root.bar.fontFamily + Component { + id: statRow + Row { + spacing: 8 + StatPill { + glyph: "󰻠" + percent: root.cpuPercent + history: root.cpuHistory + vertical: false + barFg: root.statColor + fontFamily: root.statFont + } + StatPill { + glyph: "󰍛" + percent: root.memPercent + history: root.memHistory + vertical: false + barFg: root.statColor + fontFamily: root.statFont + } } + } - StatPill { - glyph: "" - percent: root.memPercent - history: root.memHistory - vertical: root.bar.vertical - barFg: root.bar.foreground - fontFamily: root.bar.fontFamily + Component { + id: statColumn + Column { + spacing: 4 + StatPill { + glyph: "󰻠" + percent: root.cpuPercent + history: root.cpuHistory + vertical: true + barFg: root.statColor + fontFamily: root.statFont + } + StatPill { + glyph: "󰍛" + percent: root.memPercent + history: root.memHistory + vertical: true + barFg: root.statColor + fontFamily: root.statFont + } } } @@ -184,7 +217,7 @@ Item { title: "CPU" value: Math.round(root.cpuPercent) + "%" history: root.cpuHistory - barFg: root.bar.foreground + barFg: root.statColor fontFamily: root.bar.fontFamily width: parent.width } @@ -193,7 +226,7 @@ Item { title: "Memory" value: Math.round(root.memPercent) + "%" history: root.memHistory - barFg: root.bar.foreground + barFg: root.statColor fontFamily: root.bar.fontFamily width: parent.width } @@ -217,7 +250,7 @@ Item { Common.PillButton { width: parent.width - iconText: "󰍛" + iconText: "󰆍" text: "Open btop" foreground: root.bar.foreground horizontalPadding: 10 diff --git a/default/quickshell/bar/widgets/weatherFlyout.qml b/default/quickshell/bar/widgets/weatherFlyout.qml index e26d8e53..37046a42 100644 --- a/default/quickshell/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/bar/widgets/weatherFlyout.qml @@ -120,7 +120,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰑐" text: "Refresh" foreground: root.bar.foreground horizontalPadding: 10 @@ -130,7 +130,7 @@ Item { Common.PillButton { width: parent.width - iconText: "" + iconText: "󰏌" text: "Open wttr.in" foreground: root.bar.foreground horizontalPadding: 10