Compare commits

..

No commits in common. "9e1716aa394328b9de34e110072f4bcd65497c7c" and "24c90dae11c5b64345db6b797f99d6d730a21fb1" have entirely different histories.

19 changed files with 111 additions and 213 deletions

View file

@ -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 - 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) - 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 - 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 - 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 - 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 - 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 battery = false; # see above, but for power
temperature = false; # what you don't measure can't alarm you temperature = false; # what you don't measure can't alarm you
disk = false; # the number will only make you anxious 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`, Full list of things you can disable: `workspaces`, `tray`, `windowTitle`,
`clock`, `notifications`, `mpris`, `volume`, `bluetooth`, `backlight`, `clock`, `notifications`, `mpris`, `volume`, `bluetooth`, `backlight`,
`network`, `powerProfile`, `idleInhibitor`, `weather`, `temperature`, `cpu`, `network`, `powerProfile`, `idleInhibitor`, `weather`, `temperature`, `cpu`,
`memory`, `disk`, `battery`, `power`. `memory`, `disk`, `battery`, `wlogout`.
### Theme ### Theme

View file

@ -9,8 +9,7 @@ M.BarSection {
tooltip: "Brightness: " + root.percent + "%" tooltip: "Brightness: " + root.percent + "%"
property int percent: 0 property int percent: 0
onPercentChanged: if (percent > 0) onPercentChanged: if (percent > 0) M.OsdState.show(percent / 100, "\uF185")
M.OsdState.show(percent / 100, "\uF185")
Process { Process {
id: adjProc id: adjProc

View file

@ -39,12 +39,8 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: M.Theme.barSpacing spacing: M.Theme.barSpacing
M.Clock { M.Clock { visible: M.Modules.clock }
visible: M.Modules.clock M.Notifications { visible: M.Modules.notifications }
}
M.Notifications {
visible: M.Modules.notifications
}
} }
// ---- left ---- // ---- left ----
@ -54,10 +50,7 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: M.Theme.barSpacing spacing: M.Theme.barSpacing
M.Workspaces { M.Workspaces { bar: bar; visible: M.Modules.workspaces }
bar: bar
visible: M.Modules.workspaces
}
M.Tray { M.Tray {
bar: bar bar: bar
visible: M.Modules.tray visible: M.Modules.tray
@ -66,9 +59,7 @@ PanelWindow {
Layout.maximumWidth: 400 Layout.maximumWidth: 400
visible: M.Modules.windowTitle visible: M.Modules.windowTitle
} }
Item { Item { Layout.fillWidth: true }
Layout.fillWidth: true
}
} }
// ---- right ---- // ---- right ----
@ -81,63 +72,20 @@ PanelWindow {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
} }
// Media
M.BarGroup {
M.Mpris {} M.Mpris {}
M.Volume { M.Volume { visible: M.Modules.volume }
visible: M.Modules.volume
}
}
// Connectivity
M.BarGroup {
M.Network {
visible: M.Modules.network
}
M.Bluetooth {} M.Bluetooth {}
}
// Controls
M.BarGroup {
M.Backlight {} M.Backlight {}
M.PowerProfile { M.Network { visible: M.Modules.network }
visible: M.Modules.powerProfile M.PowerProfile { visible: M.Modules.powerProfile }
} M.IdleInhibitor { visible: M.Modules.idleInhibitor }
M.IdleInhibitor { M.Weather { visible: M.Modules.weather }
visible: M.Modules.idleInhibitor M.Temperature { visible: M.Modules.temperature }
} M.Cpu { visible: M.Modules.cpu }
} M.Memory { visible: M.Modules.memory }
M.Disk { visible: M.Modules.disk }
// 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 {} M.Battery {}
} M.Wlogout { bar: bar; visible: M.Modules.wlogout }
// Power
M.Power {
bar: bar
visible: M.Modules.power
}
} }
} }
} }

View file

@ -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
}
}

View file

@ -17,7 +17,10 @@ M.BarSection {
readonly property var dev: UPower.displayDevice readonly property var dev: UPower.displayDevice
readonly property real pct: (dev?.percentage ?? 0) * 100 readonly property real pct: (dev?.percentage ?? 0) * 100
readonly property bool charging: dev?.state === UPowerDeviceState.Charging 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 { M.BarIcon {
icon: { icon: {

View file

@ -7,10 +7,8 @@ M.BarSection {
spacing: M.Theme.moduleSpacing spacing: M.Theme.moduleSpacing
visible: M.Modules.bluetooth && root.state !== "unavailable" visible: M.Modules.bluetooth && root.state !== "unavailable"
tooltip: { tooltip: {
if (root.state === "off") if (root.state === "off") return "Bluetooth: off";
return "Bluetooth: off"; if (root.state === "connected") return "Bluetooth: " + root.device;
if (root.state === "connected")
return "Bluetooth: " + root.device;
return "Bluetooth: on"; return "Bluetooth: on";
} }
@ -27,7 +25,13 @@ M.BarSection {
Process { Process {
id: proc id: proc
running: true 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 { stdout: StdioCollector {
onStreamFinished: root._parse(text) onStreamFinished: root._parse(text)
} }
@ -43,13 +47,14 @@ M.BarSection {
id: toggle id: toggle
property string cmd: "" property string cmd: ""
command: ["bluetoothctl", "power", cmd] command: ["bluetoothctl", "power", cmd]
onRunningChanged: if (!running && cmd !== "") onRunningChanged: if (!running && cmd !== "") proc.running = true
proc.running = true
} }
M.BarIcon { M.BarIcon {
icon: "\uF294" 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 anchors.verticalCenter: parent.verticalCenter
} }
M.BarLabel { M.BarLabel {

View file

@ -20,7 +20,10 @@ PanelWindow {
// Flush below bar, centered on hovered item // Flush below bar, centered on hovered item
margins.top: 0 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 implicitWidth: label.implicitWidth + M.Theme.barPadding * 2
implicitHeight: label.implicitHeight + M.Theme.barPadding * 2 implicitHeight: label.implicitHeight + M.Theme.barPadding * 2

View file

@ -25,7 +25,7 @@ QtObject {
property bool memory: true property bool memory: true
property bool disk: true property bool disk: true
property bool battery: true property bool battery: true
property bool power: true property bool wlogout: true
property var weatherArgs: ["--nerd"] property var weatherArgs: ["--nerd"]

View file

@ -10,7 +10,7 @@ PanelWindow {
required property var screen required property var screen
required property real anchorX required property real anchorX
signal menuClosed signal menuClosed()
signal runCommand(var cmd) signal runCommand(var cmd)
readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== "" readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== ""
@ -40,7 +40,10 @@ PanelWindow {
Item { Item {
id: panel 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 y: 0
width: menuCol.width width: menuCol.width
@ -70,36 +73,11 @@ PanelWindow {
Repeater { Repeater {
model: [ model: [
{ { label: "Lock", icon: "\uF023", cmd: ["loginctl", "lock-session"], color: M.Theme.base0D },
label: "Lock", { label: "Suspend", icon: "\uF186", cmd: ["systemctl", "suspend"], color: M.Theme.base0E },
icon: "\uF023", { label: "Logout", icon: "\uF2F5", cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""], color: M.Theme.base0A },
cmd: ["loginctl", "lock-session"], { label: "Reboot", icon: "\uF021", cmd: ["systemctl", "reboot"], color: M.Theme.base09 },
color: M.Theme.base0D { label: "Shutdown", icon: "\uF011", cmd: ["systemctl", "poweroff"], color: M.Theme.base08 }
},
{
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 { delegate: Item {

View file

@ -8,7 +8,9 @@ M.BarIcon {
property string profile: "" 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: { icon: {
if (root.profile === "performance") if (root.profile === "performance")

View file

@ -70,24 +70,8 @@ Item {
} }
} }
Corner { Corner { corner: 0; anchors.top: true; anchors.left: true }
corner: 0 Corner { corner: 1; anchors.top: true; anchors.right: true }
anchors.top: true Corner { corner: 2; anchors.bottom: true; anchors.left: true }
anchors.left: true Corner { corner: 3; anchors.bottom: true; anchors.right: 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
}
} }

View file

@ -8,7 +8,9 @@ M.BarSection {
tooltip: "Temperature: " + root.celsius + "\u00B0C" tooltip: "Temperature: " + root.celsius + "\u00B0C"
property int celsius: 0 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 { FileView {
id: thermal id: thermal

View file

@ -30,7 +30,8 @@ RowLayout {
HoverHandler { HoverHandler {
onHoveredChanged: { 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) { if (hovered && tip) {
M.FlyoutState.text = tip; M.FlyoutState.text = tip;
M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0); M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);

View file

@ -13,7 +13,7 @@ PanelWindow {
required property var screen required property var screen
required property real anchorX required property real anchorX
signal menuClosed signal menuClosed()
// Current menu level swapped when entering/leaving submenus // Current menu level swapped when entering/leaving submenus
property var _currentHandle: handle property var _currentHandle: handle
@ -41,7 +41,10 @@ PanelWindow {
Item { Item {
id: panel 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 y: 0
width: menuCol.width width: menuCol.width
@ -140,7 +143,8 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 4 anchors.leftMargin: 4
anchors.rightMargin: 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 radius: M.Theme.radius
} }

View file

@ -14,8 +14,7 @@ M.BarSection {
property string _title: "" property string _title: ""
property string _appId: "" property string _appId: ""
readonly property string _iconSource: { readonly property string _iconSource: {
if (!root._appId) if (!root._appId) return "";
return "";
const entry = DesktopEntries.heuristicLookup(root._appId); const entry = DesktopEntries.heuristicLookup(root._appId);
return entry ? Quickshell.iconPath(entry.icon) : ""; return entry ? Quickshell.iconPath(entry.icon) : "";
} }

View file

@ -1,6 +1,5 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import "." as M import "." as M
M.BarIcon { M.BarIcon {

View file

@ -44,7 +44,8 @@ Row {
try { try {
const ev = JSON.parse(line); const ev = JSON.parse(line);
if (ev.WorkspacesChanged !== undefined) { 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) { } else if (ev.WorkspaceActivated !== undefined) {
if (ev.WorkspaceActivated.focused) if (ev.WorkspaceActivated.focused)
root._activeId = ev.WorkspaceActivated.id; root._activeId = ev.WorkspaceActivated.id;

View file

@ -4,7 +4,6 @@ singleton FlyoutState 1.0 FlyoutState.qml
singleton OsdState 1.0 OsdState.qml singleton OsdState 1.0 OsdState.qml
singleton Modules 1.0 Modules.qml singleton Modules 1.0 Modules.qml
Bar 1.0 Bar.qml Bar 1.0 Bar.qml
BarGroup 1.0 BarGroup.qml
BarSection 1.0 BarSection.qml BarSection 1.0 BarSection.qml
Flyout 1.0 Flyout.qml Flyout 1.0 Flyout.qml
Workspaces 1.0 Workspaces.qml Workspaces 1.0 Workspaces.qml
@ -30,4 +29,4 @@ Weather 1.0 Weather.qml
PowerProfile 1.0 PowerProfile.qml PowerProfile 1.0 PowerProfile.qml
IdleInhibitor 1.0 IdleInhibitor.qml IdleInhibitor 1.0 IdleInhibitor.qml
Notifications 1.0 Notifications.qml Notifications 1.0 Notifications.qml
Power 1.0 Power.qml Wlogout 1.0 Wlogout.qml

View file

@ -56,8 +56,7 @@ in
description = "Enable or disable individual bar modules."; description = "Enable or disable individual bar modules.";
default = { }; default = { };
type = lib.types.submodule { type = lib.types.submodule {
options = options = lib.genAttrs
lib.genAttrs
[ [
"workspaces" "workspaces"
"tray" "tray"
@ -77,16 +76,13 @@ in
"memory" "memory"
"disk" "disk"
"battery" "battery"
"power" "wlogout"
] ]
( (name: lib.mkOption {
name:
lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = "Enable the ${name} module."; description = "Enable the ${name} module.";
} });
);
}; };
}; };
@ -127,7 +123,8 @@ in
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.packages = [ home.packages =
[
self.packages.${pkgs.stdenv.hostPlatform.system}.nova-shell-cli self.packages.${pkgs.stdenv.hostPlatform.system}.nova-shell-cli
pkgs.nerd-fonts.symbols-only pkgs.nerd-fonts.symbols-only
] ]