diff --git a/README.md b/README.md index 86d1a06..a431203 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,109 @@ imports = [ inputs.nova-shell.homeModules.default ]; programs.nova-shell.enable = true; ``` +## Configuration + +Surprisingly, the robot managed to wire up a Home Manager module that mostly works. +Here is how to make it do things. + +### Flake setup + +```nix +# flake.nix +inputs = { + nova-shell.url = "git+https://git.berlin.ccc.de/vinzenz/nova-shell"; + nova-shell.inputs.nixpkgs.follows = "nixpkgs"; +}; +``` + +```nix +# home.nix +imports = [ inputs.nova-shell.homeModules.default ]; +``` + +### Turning it on + +```nix +programs.nova-shell.enable = true; +``` + +This installs the bar, the Symbols Nerd Font, and a systemd user service that +starts with `graphical-session.target`. If you use +[stylix](https://github.com/danth/stylix), colors and fonts are populated +automatically — one fewer thing for the AI to have gotten wrong. + +### Disabling modules + +All modules are on by default because the robot was optimistic. Set any to +`false` to get rid of them. Disabling `weather` also skips pulling in +`wttrbar`, which is the one genuinely useful thing the module system does. + +```nix +programs.nova-shell.modules = { + weather = false; # also removes the wttrbar dependency + bluetooth = false; # unless you enjoy a ghost icon on your desktop + backlight = false; # desktops don't have screens that dim, allegedly + battery = false; # see above + temperature = false; # ignorance is thermally efficient + disk = false; + wlogout = false; # if you enjoy living dangerously without a logout button +}; +``` + +Full list of things you can disable: `tray`, `windowTitle`, `clock`, +`notifications`, `mpris`, `volume`, `bluetooth`, `backlight`, `network`, +`powerProfile`, `idleInhibitor`, `weather`, `temperature`, `cpu`, `memory`, +`disk`, `battery`, `wlogout`. + +### Theme + +Theme keys merge on top of whatever stylix provides, so you only need to +specify what you want to override. The AI picked Catppuccin Mocha as the +fallback, because of course it did. + +```nix +programs.nova-shell.theme = { + barHeight = 28; + barOpacity = 0.85; + barPadding = 10; + barSpacing = 8; + radius = 6; + fontSize = 13; + fontFamily = "JetBrains Mono"; + + # override individual palette entries if stylix's choices offend you + colors.base00 = "#1a1a2e"; + colors.base05 = "#e0e0f0"; +}; +``` + +Full list of theme keys and their defaults: + +| Key | Default | Controls | +|-----|---------|----------| +| `colors.base00`–`base0F` | Catppuccin Mocha | Base16 palette | +| `fontFamily` | `"sans-serif"` | Bar text font | +| `iconFontFamily` | `"Symbols Nerd Font"` | Nerd font for icons | +| `fontSize` | `12` | Base font size (px) | +| `barHeight` | `32` | Bar height (px) | +| `barOpacity` | `0.9` | Bar and flyout background opacity | +| `barPadding` | `8` | Left/right bar content margin (px) | +| `barSpacing` | `12` | Gap between modules (px) | +| `moduleSpacing` | `4` | Icon-to-label gap within a module (px) | +| `radius` | `4` | Corner radius for flyouts and menus (px) | + +### Systemd service + +Enabled by default. To attach it to a different target or disable it entirely +because you have opinions about service management: + +```nix +programs.nova-shell.systemd = { + enable = true; + target = "niri.service"; +}; +``` + ## Contributing Sure, why not. It can't get much worse. diff --git a/modules/Backlight.qml b/modules/Backlight.qml index e994c8e..04f05f7 100644 --- a/modules/Backlight.qml +++ b/modules/Backlight.qml @@ -5,7 +5,7 @@ import "." as M M.BarSection { id: root spacing: M.Theme.moduleSpacing - visible: percent > 0 + visible: M.Modules.backlight && percent > 0 tooltip: "Brightness: " + root.percent + "%" property int percent: 0 diff --git a/modules/Bar.qml b/modules/Bar.qml index ecbebb3..909b0fd 100644 --- a/modules/Bar.qml +++ b/modules/Bar.qml @@ -39,8 +39,8 @@ PanelWindow { anchors.verticalCenter: parent.verticalCenter spacing: M.Theme.barSpacing - M.Clock {} - M.Notifications {} + M.Clock { visible: M.Modules.clock } + M.Notifications { visible: M.Modules.notifications } } // ---- left ---- @@ -52,9 +52,11 @@ PanelWindow { M.Tray { bar: bar + visible: M.Modules.tray } M.WindowTitle { Layout.maximumWidth: 400 + visible: M.Modules.windowTitle } } @@ -69,19 +71,19 @@ PanelWindow { Layout.fillWidth: true } M.Mpris {} - M.Volume {} + M.Volume { visible: M.Modules.volume } M.Bluetooth {} M.Backlight {} - M.Network {} - M.PowerProfile {} - M.IdleInhibitor {} - M.Weather {} - M.Temperature {} - M.Cpu {} - M.Memory {} - M.Disk {} + 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 {} + M.Wlogout { visible: M.Modules.wlogout } } } } diff --git a/modules/Battery.qml b/modules/Battery.qml index 3e5c0a2..a8cf911 100644 --- a/modules/Battery.qml +++ b/modules/Battery.qml @@ -5,7 +5,7 @@ import "." as M M.BarSection { id: root spacing: M.Theme.moduleSpacing - visible: UPower.displayDevice?.isLaptopBattery ?? false + visible: M.Modules.battery && (UPower.displayDevice?.isLaptopBattery ?? false) tooltip: { const state = root.charging ? "Charging" : "Discharging"; const t = root.charging ? root.dev?.timeToFull : root.dev?.timeToEmpty; diff --git a/modules/Bluetooth.qml b/modules/Bluetooth.qml index d0eafc3..b0b26fc 100644 --- a/modules/Bluetooth.qml +++ b/modules/Bluetooth.qml @@ -5,7 +5,7 @@ import "." as M M.BarSection { id: root spacing: M.Theme.moduleSpacing - visible: root.state !== "unavailable" + visible: M.Modules.bluetooth && root.state !== "unavailable" tooltip: { if (root.state === "off") return "Bluetooth: off"; if (root.state === "connected") return "Bluetooth: " + root.device; @@ -61,10 +61,9 @@ M.BarSection { anchors.verticalCenter: parent.verticalCenter } - MouseArea { - anchors.fill: parent + TapHandler { cursorShape: Qt.PointingHandCursor - onClicked: { + onTapped: { toggle.cmd = root.state === "off" ? "on" : "off"; toggle.running = true; } diff --git a/modules/Modules.qml b/modules/Modules.qml new file mode 100644 index 0000000..4476b57 --- /dev/null +++ b/modules/Modules.qml @@ -0,0 +1,48 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Io + +QtObject { + id: root + + property bool tray: true + property bool windowTitle: true + property bool clock: true + property bool notifications: true + property bool mpris: true + property bool volume: true + property bool bluetooth: true + property bool backlight: true + property bool network: true + property bool powerProfile: true + property bool idleInhibitor: true + property bool weather: true + property bool temperature: true + property bool cpu: true + property bool memory: true + property bool disk: true + property bool battery: true + property bool wlogout: true + + property FileView _file: FileView { + path: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nova-shell/modules.json" + watchChanges: true + onFileChanged: reload() + onLoaded: root._apply(text()) + } + + function _apply(raw) { + let data; + try { + data = JSON.parse(raw); + } catch (e) { + return; + } + for (const k of Object.keys(data)) { + if (k in root && typeof root[k] === "boolean") + root[k] = data[k]; + } + } +} diff --git a/modules/Mpris.qml b/modules/Mpris.qml index ef08443..060d06a 100644 --- a/modules/Mpris.qml +++ b/modules/Mpris.qml @@ -5,7 +5,7 @@ import "." as M M.BarSection { id: root spacing: M.Theme.moduleSpacing - visible: player !== null + visible: M.Modules.mpris && player !== null tooltip: { const p = root.player; if (!p) @@ -14,7 +14,7 @@ M.BarSection { if (p.trackTitle) parts.push(p.trackTitle); if (p.trackArtists?.length) - parts.push(p.trackArtists.join(", ")); + parts.push(Array.isArray(p.trackArtists) ? p.trackArtists.join(", ") : p.trackArtists); if (p.trackAlbum) parts.push(p.trackAlbum); return parts.join("\n") || p.identity; diff --git a/modules/qmldir b/modules/qmldir index 4805e2f..8c084a6 100644 --- a/modules/qmldir +++ b/modules/qmldir @@ -1,6 +1,7 @@ module modules singleton Theme 1.0 Theme.qml singleton FlyoutState 1.0 FlyoutState.qml +singleton Modules 1.0 Modules.qml Bar 1.0 Bar.qml BarSection 1.0 BarSection.qml Flyout 1.0 Flyout.qml diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 656a1d9..aae7fd2 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -52,6 +52,39 @@ in description = "nova-shell package to use."; }; + modules = lib.mkOption { + description = "Enable or disable individual bar modules."; + default = { }; + type = lib.types.submodule { + options = lib.genAttrs + [ + "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."; + }); + }; + }; + theme = lib.mkOption { type = lib.types.attrsOf lib.types.anything; default = { }; @@ -80,10 +113,15 @@ in config = lib.mkIf cfg.enable { programs.nova-shell.theme = lib.mkIf stylixAvailable (lib.mkDefault stylixTheme); - home.packages = [ - self.packages.${pkgs.stdenv.hostPlatform.system}.nova-shell-cli - pkgs.nerd-fonts.symbols-only - ]; + 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" cfg.modules; xdg.configFile."nova-shell/theme.json".source = (pkgs.formats.json { }).generate "nova-shell-theme.json"