diff --git a/README.md b/README.md index d06c4c0..a70b484 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ exactly when you should be most suspicious. - OSD overlay for volume and brightness changes — works with niri hotkeys, not just the bar - Screen corner rounding — tiny overlay windows that draw quarter-circle masks, configurable via `screenRadius` (set to 0 if you prefer your corners sharp and your desktop ugly) - Weather via wttrbar with configurable arguments and rich HTML tooltips -- Power menu with lock, suspend, logout, reboot, shutdown +- Power menu with lock, suspend, logout, reboot, shutdown — no more `wlogout` dependency - Flyout tooltips that actually filter by screen on multi-monitor setups, which only took three attempts to get right - Home Manager module with stylix integration, per-module enable/disable, and a theme system that hot-reloads - treefmt + nixfmt for formatting, because even AI slop deserves consistent indentation @@ -75,14 +75,14 @@ programs.nova-shell.modules = { battery = false; # see above, but for power temperature = false; # what you don't measure can't alarm you disk = false; # the number will only make you anxious - power = false; # if you enjoy living dangerously without a logout button + wlogout = false; # if you enjoy living dangerously without a logout button }; ``` Full list of things you can disable: `workspaces`, `tray`, `windowTitle`, `clock`, `notifications`, `mpris`, `volume`, `bluetooth`, `backlight`, `network`, `powerProfile`, `idleInhibitor`, `weather`, `temperature`, `cpu`, -`memory`, `disk`, `battery`, `power`. +`memory`, `disk`, `battery`, `wlogout`. ### Theme diff --git a/modules/Backlight.qml b/modules/Backlight.qml index 040dc5a..03be350 100644 --- a/modules/Backlight.qml +++ b/modules/Backlight.qml @@ -9,8 +9,7 @@ M.BarSection { tooltip: "Brightness: " + root.percent + "%" property int percent: 0 - onPercentChanged: if (percent > 0) - M.OsdState.show(percent / 100, "\uF185") + onPercentChanged: if (percent > 0) M.OsdState.show(percent / 100, "\uF185") Process { id: adjProc diff --git a/modules/Bar.qml b/modules/Bar.qml index c0ed034..c0ee33b 100644 --- a/modules/Bar.qml +++ b/modules/Bar.qml @@ -39,12 +39,8 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: M.Theme.barSpacing - M.Clock { - visible: M.Modules.clock - } - M.Notifications { - visible: M.Modules.notifications - } + M.Clock { visible: M.Modules.clock } + M.Notifications { visible: M.Modules.notifications } } // ---- left ---- @@ -54,10 +50,7 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: M.Theme.barSpacing - M.Workspaces { - bar: bar - visible: M.Modules.workspaces - } + M.Workspaces { bar: bar; visible: M.Modules.workspaces } M.Tray { bar: bar visible: M.Modules.tray @@ -66,9 +59,7 @@ PanelWindow { Layout.maximumWidth: 400 visible: M.Modules.windowTitle } - Item { - Layout.fillWidth: true - } + Item { Layout.fillWidth: true } } // ---- right ---- @@ -81,63 +72,20 @@ PanelWindow { Item { Layout.fillWidth: true } - - // Media - M.BarGroup { - M.Mpris {} - M.Volume { - visible: M.Modules.volume - } - } - - // Connectivity - M.BarGroup { - M.Network { - visible: M.Modules.network - } - M.Bluetooth {} - } - - // Controls - M.BarGroup { - M.Backlight {} - M.PowerProfile { - visible: M.Modules.powerProfile - } - M.IdleInhibitor { - visible: M.Modules.idleInhibitor - } - } - - // System - M.BarGroup { - M.Cpu { - visible: M.Modules.cpu - } - M.Memory { - visible: M.Modules.memory - } - M.Temperature { - visible: M.Modules.temperature - } - } - - // Status - M.BarGroup { - M.Weather { - visible: M.Modules.weather - } - M.Disk { - visible: M.Modules.disk - } - M.Battery {} - } - - // Power - M.Power { - bar: bar - visible: M.Modules.power - } + M.Mpris {} + M.Volume { visible: M.Modules.volume } + M.Bluetooth {} + M.Backlight {} + M.Network { visible: M.Modules.network } + M.PowerProfile { visible: M.Modules.powerProfile } + M.IdleInhibitor { visible: M.Modules.idleInhibitor } + M.Weather { visible: M.Modules.weather } + M.Temperature { visible: M.Modules.temperature } + M.Cpu { visible: M.Modules.cpu } + M.Memory { visible: M.Modules.memory } + M.Disk { visible: M.Modules.disk } + M.Battery {} + M.Wlogout { bar: bar; visible: M.Modules.wlogout } } } } diff --git a/modules/BarGroup.qml b/modules/BarGroup.qml deleted file mode 100644 index 1e2d37f..0000000 --- a/modules/BarGroup.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick -import "." as M - -Rectangle { - id: root - - default property alias content: row.children - - color: "transparent" - border.color: M.Theme.base02 - border.width: 1 - radius: M.Theme.radius - - visible: row.visibleChildren.length > 0 - - implicitWidth: row.implicitWidth + _pad * 2 - implicitHeight: row.implicitHeight + _pad * 2 - - readonly property int _pad: 6 - - Row { - id: row - anchors.centerIn: parent - spacing: M.Theme.moduleSpacing + 2 - } -} diff --git a/modules/Battery.qml b/modules/Battery.qml index 5505efa..a138917 100644 --- a/modules/Battery.qml +++ b/modules/Battery.qml @@ -17,7 +17,10 @@ M.BarSection { readonly property var dev: UPower.displayDevice readonly property real pct: (dev?.percentage ?? 0) * 100 readonly property bool charging: dev?.state === UPowerDeviceState.Charging - readonly property color _stateColor: charging ? M.Theme.base0B : pct < 15 ? M.Theme.base08 : pct < 30 ? M.Theme.base09 : M.Theme.base0B + readonly property color _stateColor: charging ? M.Theme.base0B + : pct < 15 ? M.Theme.base08 + : pct < 30 ? M.Theme.base09 + : M.Theme.base0B M.BarIcon { icon: { diff --git a/modules/Bluetooth.qml b/modules/Bluetooth.qml index 66ff6d7..64e35f8 100644 --- a/modules/Bluetooth.qml +++ b/modules/Bluetooth.qml @@ -7,10 +7,8 @@ M.BarSection { spacing: M.Theme.moduleSpacing visible: M.Modules.bluetooth && root.state !== "unavailable" tooltip: { - if (root.state === "off") - return "Bluetooth: off"; - if (root.state === "connected") - return "Bluetooth: " + root.device; + if (root.state === "off") return "Bluetooth: off"; + if (root.state === "connected") return "Bluetooth: " + root.device; return "Bluetooth: on"; } @@ -20,14 +18,20 @@ M.BarSection { function _parse(text) { const t = text.trim(); const sep = t.indexOf(":"); - root.state = sep === -1 ? t : t.slice(0, sep); + root.state = sep === -1 ? t : t.slice(0, sep); root.device = sep === -1 ? "" : t.slice(sep + 1); } Process { id: proc running: true - command: ["sh", "-c", "s=$(bluetoothctl show 2>/dev/null); " + "[ -z \"$s\" ] && echo unavailable && exit; " + "echo \"$s\" | grep -q 'Powered: yes' || { echo off:; exit; }; " + "d=$(bluetoothctl info 2>/dev/null | awk -F': ' '/\\tName:/{n=$2}/Connected: yes/{c=1}END{if(c)print n}'); " + "[ -n \"$d\" ] && echo \"connected:$d\" || echo on:"] + command: ["sh", "-c", + "s=$(bluetoothctl show 2>/dev/null); " + + "[ -z \"$s\" ] && echo unavailable && exit; " + + "echo \"$s\" | grep -q 'Powered: yes' || { echo off:; exit; }; " + + "d=$(bluetoothctl info 2>/dev/null | awk -F': ' '/\\tName:/{n=$2}/Connected: yes/{c=1}END{if(c)print n}'); " + + "[ -n \"$d\" ] && echo \"connected:$d\" || echo on:" + ] stdout: StdioCollector { onStreamFinished: root._parse(text) } @@ -43,13 +47,14 @@ M.BarSection { id: toggle property string cmd: "" command: ["bluetoothctl", "power", cmd] - onRunningChanged: if (!running && cmd !== "") - proc.running = true + onRunningChanged: if (!running && cmd !== "") proc.running = true } M.BarIcon { icon: "\uF294" - color: root.state === "connected" ? M.Theme.base0D : root.state === "off" ? M.Theme.base04 : M.Theme.base0D + color: root.state === "connected" ? M.Theme.base0D + : root.state === "off" ? M.Theme.base04 + : M.Theme.base0D anchors.verticalCenter: parent.verticalCenter } M.BarLabel { diff --git a/modules/Flyout.qml b/modules/Flyout.qml index 78810ef..80ad34d 100644 --- a/modules/Flyout.qml +++ b/modules/Flyout.qml @@ -20,7 +20,10 @@ PanelWindow { // Flush below bar, centered on hovered item margins.top: 0 - margins.left: Math.max(0, Math.min(Math.round(M.FlyoutState.itemX - implicitWidth / 2), screen.width - implicitWidth)) + margins.left: Math.max(0, Math.min( + Math.round(M.FlyoutState.itemX - implicitWidth / 2), + screen.width - implicitWidth + )) implicitWidth: label.implicitWidth + M.Theme.barPadding * 2 implicitHeight: label.implicitHeight + M.Theme.barPadding * 2 diff --git a/modules/Modules.qml b/modules/Modules.qml index d8a5d04..c8c6d1d 100644 --- a/modules/Modules.qml +++ b/modules/Modules.qml @@ -25,7 +25,7 @@ QtObject { property bool memory: true property bool disk: true property bool battery: true - property bool power: true + property bool wlogout: true property var weatherArgs: ["--nerd"] diff --git a/modules/PowerMenu.qml b/modules/PowerMenu.qml index 5b50de5..59ab4b6 100644 --- a/modules/PowerMenu.qml +++ b/modules/PowerMenu.qml @@ -10,7 +10,7 @@ PanelWindow { required property var screen required property real anchorX - signal menuClosed + signal menuClosed() signal runCommand(var cmd) readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== "" @@ -40,7 +40,10 @@ PanelWindow { Item { id: panel - x: Math.max(0, Math.min(Math.round(menuWindow.anchorX - menuCol.width / 2), menuWindow.width - menuCol.width)) + x: Math.max(0, Math.min( + Math.round(menuWindow.anchorX - menuCol.width / 2), + menuWindow.width - menuCol.width + )) y: 0 width: menuCol.width @@ -70,36 +73,11 @@ PanelWindow { Repeater { model: [ - { - label: "Lock", - icon: "\uF023", - cmd: ["loginctl", "lock-session"], - color: M.Theme.base0D - }, - { - label: "Suspend", - icon: "\uF186", - cmd: ["systemctl", "suspend"], - color: M.Theme.base0E - }, - { - label: "Logout", - icon: "\uF2F5", - cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""], - color: M.Theme.base0A - }, - { - label: "Reboot", - icon: "\uF021", - cmd: ["systemctl", "reboot"], - color: M.Theme.base09 - }, - { - label: "Shutdown", - icon: "\uF011", - cmd: ["systemctl", "poweroff"], - color: M.Theme.base08 - } + { label: "Lock", icon: "\uF023", cmd: ["loginctl", "lock-session"], color: M.Theme.base0D }, + { label: "Suspend", icon: "\uF186", cmd: ["systemctl", "suspend"], color: M.Theme.base0E }, + { label: "Logout", icon: "\uF2F5", cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""], color: M.Theme.base0A }, + { label: "Reboot", icon: "\uF021", cmd: ["systemctl", "reboot"], color: M.Theme.base09 }, + { label: "Shutdown", icon: "\uF011", cmd: ["systemctl", "poweroff"], color: M.Theme.base08 } ] delegate: Item { diff --git a/modules/PowerProfile.qml b/modules/PowerProfile.qml index f533c98..904492f 100644 --- a/modules/PowerProfile.qml +++ b/modules/PowerProfile.qml @@ -8,7 +8,9 @@ M.BarIcon { property string profile: "" - color: root.profile === "performance" ? M.Theme.base09 : root.profile === "power-saver" ? M.Theme.base0B : M.Theme.base05 + color: root.profile === "performance" ? M.Theme.base09 + : root.profile === "power-saver" ? M.Theme.base0B + : M.Theme.base05 icon: { if (root.profile === "performance") diff --git a/modules/ScreenCorners.qml b/modules/ScreenCorners.qml index 0e0b356..1164743 100644 --- a/modules/ScreenCorners.qml +++ b/modules/ScreenCorners.qml @@ -70,24 +70,8 @@ Item { } } - Corner { - corner: 0 - anchors.top: true - anchors.left: true - } - Corner { - corner: 1 - anchors.top: true - anchors.right: true - } - Corner { - corner: 2 - anchors.bottom: true - anchors.left: true - } - Corner { - corner: 3 - anchors.bottom: true - anchors.right: true - } + Corner { corner: 0; anchors.top: true; anchors.left: true } + Corner { corner: 1; anchors.top: true; anchors.right: true } + Corner { corner: 2; anchors.bottom: true; anchors.left: true } + Corner { corner: 3; anchors.bottom: true; anchors.right: true } } diff --git a/modules/Temperature.qml b/modules/Temperature.qml index a96c57a..9b1d98e 100644 --- a/modules/Temperature.qml +++ b/modules/Temperature.qml @@ -8,7 +8,9 @@ M.BarSection { tooltip: "Temperature: " + root.celsius + "\u00B0C" property int celsius: 0 - readonly property color _stateColor: celsius > 80 ? M.Theme.base08 : celsius > 60 ? M.Theme.base09 : M.Theme.base0C + readonly property color _stateColor: celsius > 80 ? M.Theme.base08 + : celsius > 60 ? M.Theme.base09 + : M.Theme.base0C FileView { id: thermal diff --git a/modules/Tray.qml b/modules/Tray.qml index a824348..2474a3f 100644 --- a/modules/Tray.qml +++ b/modules/Tray.qml @@ -30,7 +30,8 @@ RowLayout { HoverHandler { onHoveredChanged: { - const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription].filter(s => s).join("\n") || iconItem.modelData.title; + const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription] + .filter(s => s).join("\n") || iconItem.modelData.title; if (hovered && tip) { M.FlyoutState.text = tip; M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); diff --git a/modules/TrayMenu.qml b/modules/TrayMenu.qml index 51cfc43..30aa161 100644 --- a/modules/TrayMenu.qml +++ b/modules/TrayMenu.qml @@ -13,7 +13,7 @@ PanelWindow { required property var screen required property real anchorX - signal menuClosed + signal menuClosed() // Current menu level — swapped when entering/leaving submenus property var _currentHandle: handle @@ -41,7 +41,10 @@ PanelWindow { Item { id: panel - x: Math.max(0, Math.min(Math.round(menuWindow.anchorX - menuCol.width / 2), menuWindow.width - menuCol.width)) + x: Math.max(0, Math.min( + Math.round(menuWindow.anchorX - menuCol.width / 2), + menuWindow.width - menuCol.width + )) y: 0 width: menuCol.width @@ -140,7 +143,8 @@ PanelWindow { anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 - color: rowArea.containsMouse && entryItem.modelData.enabled ? M.Theme.base02 : "transparent" + color: rowArea.containsMouse && entryItem.modelData.enabled + ? M.Theme.base02 : "transparent" radius: M.Theme.radius } diff --git a/modules/WindowTitle.qml b/modules/WindowTitle.qml index 08e694c..de1661a 100644 --- a/modules/WindowTitle.qml +++ b/modules/WindowTitle.qml @@ -14,8 +14,7 @@ M.BarSection { property string _title: "" property string _appId: "" readonly property string _iconSource: { - if (!root._appId) - return ""; + if (!root._appId) return ""; const entry = DesktopEntries.heuristicLookup(root._appId); return entry ? Quickshell.iconPath(entry.icon) : ""; } diff --git a/modules/Power.qml b/modules/Wlogout.qml similarity index 97% rename from modules/Power.qml rename to modules/Wlogout.qml index 4e388f2..32dc365 100644 --- a/modules/Power.qml +++ b/modules/Wlogout.qml @@ -1,6 +1,5 @@ import QtQuick import Quickshell -import Quickshell.Io import "." as M M.BarIcon { diff --git a/modules/Workspaces.qml b/modules/Workspaces.qml index d4ebd17..e185d6f 100644 --- a/modules/Workspaces.qml +++ b/modules/Workspaces.qml @@ -44,7 +44,8 @@ Row { try { const ev = JSON.parse(line); if (ev.WorkspacesChanged !== undefined) { - root._allWorkspaces = ev.WorkspacesChanged.workspaces.sort((a, b) => a.idx - b.idx); + root._allWorkspaces = ev.WorkspacesChanged.workspaces + .sort((a, b) => a.idx - b.idx); } else if (ev.WorkspaceActivated !== undefined) { if (ev.WorkspaceActivated.focused) root._activeId = ev.WorkspaceActivated.id; diff --git a/modules/qmldir b/modules/qmldir index 0a84d2e..8dd63bb 100644 --- a/modules/qmldir +++ b/modules/qmldir @@ -4,7 +4,6 @@ singleton FlyoutState 1.0 FlyoutState.qml singleton OsdState 1.0 OsdState.qml singleton Modules 1.0 Modules.qml Bar 1.0 Bar.qml -BarGroup 1.0 BarGroup.qml BarSection 1.0 BarSection.qml Flyout 1.0 Flyout.qml Workspaces 1.0 Workspaces.qml @@ -30,4 +29,4 @@ Weather 1.0 Weather.qml PowerProfile 1.0 PowerProfile.qml IdleInhibitor 1.0 IdleInhibitor.qml Notifications 1.0 Notifications.qml -Power 1.0 Power.qml +Wlogout 1.0 Wlogout.qml diff --git a/nix/hm-module.nix b/nix/hm-module.nix index d2b37d2..0f00a76 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -56,37 +56,33 @@ in description = "Enable or disable individual bar modules."; default = { }; type = lib.types.submodule { - options = - lib.genAttrs - [ - "workspaces" - "tray" - "windowTitle" - "clock" - "notifications" - "mpris" - "volume" - "bluetooth" - "backlight" - "network" - "powerProfile" - "idleInhibitor" - "weather" - "temperature" - "cpu" - "memory" - "disk" - "battery" - "power" - ] - ( - name: - lib.mkOption { - type = lib.types.bool; - default = true; - description = "Enable the ${name} module."; - } - ); + options = lib.genAttrs + [ + "workspaces" + "tray" + "windowTitle" + "clock" + "notifications" + "mpris" + "volume" + "bluetooth" + "backlight" + "network" + "powerProfile" + "idleInhibitor" + "weather" + "temperature" + "cpu" + "memory" + "disk" + "battery" + "wlogout" + ] + (name: lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable the ${name} module."; + }); }; }; @@ -127,11 +123,12 @@ in }; config = lib.mkIf cfg.enable { - home.packages = [ - self.packages.${pkgs.stdenv.hostPlatform.system}.nova-shell-cli - pkgs.nerd-fonts.symbols-only - ] - ++ lib.optional cfg.modules.weather pkgs.wttrbar; + home.packages = + [ + self.packages.${pkgs.stdenv.hostPlatform.system}.nova-shell-cli + pkgs.nerd-fonts.symbols-only + ] + ++ lib.optional cfg.modules.weather pkgs.wttrbar; xdg.configFile."nova-shell/modules.json".source = (pkgs.formats.json { }).generate "nova-shell-modules.json"