Add opt-in nomarchy.updates (modules/home/updates.nix): a nomarchy-updates checker on a systemd user timer (interval, default daily) that surfaces a Waybar indicator + a notification when updates are available, without ever changing anything (you still run sys-update / flatpak update). It counts: - flake inputs behind upstream — only the flake's DIRECT inputs (root.inputs: nixpkgs, the Nomarchy input, home-manager, stylix…), not the transitive closure, via `git ls-remote` vs the locked rev. Offline → skipped, never a false alarm. - Flatpak updates, when the flatpak CLI is present (.flatpak, gated on services.flatpak being on) — Bernardo's suggestion. Waybar custom/updates self-gates (hidden until the timer finds something; accent " N", signal 9 for instant refresh), in the generated bar and both summer whole-swaps. The notification fires only when the count grows, so a daily timer doesn't nag. Click → the upgrade flow in a terminal (sys-update / flatpak update, each confirmed). nomarchy-updates ships always on PATH and self-gates so the static whole-swap bars can exec it even when the feature is off. Verified: flake check clean; home generation builds; the jq direct-input filter + status self-gate exercised against the real flake.lock; script passes bash -n. Pending an on-machine check (ls-remote/notify/timer need a live session). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
265 lines
11 KiB
Nix
265 lines
11 KiB
Nix
# User-level `nomarchy.*` options — the full surface downstream users
|
||
# configure in their home.nix. Kept small on purpose.
|
||
{ lib, pkgs, ... }:
|
||
|
||
let
|
||
# One output's layout — turned into a Hyprland `monitor` rule in
|
||
# hyprland.nix. Friendlier than raw positional CSV; unset optional fields
|
||
# are omitted from the rule.
|
||
monitorType = lib.types.submodule {
|
||
options = {
|
||
name = lib.mkOption {
|
||
type = lib.types.str;
|
||
example = "HDMI-A-1";
|
||
description = "Output name (see `hyprctl monitors`), or a `desc:<…>` match.";
|
||
};
|
||
resolution = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "preferred";
|
||
description = "`preferred` | `highres` | `highrr` | `<w>x<h>@<hz>` | `disable`.";
|
||
};
|
||
position = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "auto";
|
||
description = "`auto` | `auto-right`/`auto-left`/… | `<x>x<y>`.";
|
||
};
|
||
scale = lib.mkOption {
|
||
type = lib.types.oneOf [ lib.types.str lib.types.int lib.types.float ];
|
||
default = 1;
|
||
description = "Scale factor (1, 1.5, …) or `auto`.";
|
||
};
|
||
transform = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.int;
|
||
default = null;
|
||
description = "Rotation 0–7 (90°/180°/270° = 1/2/3).";
|
||
};
|
||
mirror = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = null;
|
||
description = "Mirror another output by name.";
|
||
};
|
||
bitdepth = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.int;
|
||
default = null;
|
||
description = "Colour bit depth (8 or 10).";
|
||
};
|
||
vrr = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.int;
|
||
default = null;
|
||
description = "Variable refresh: 0 off, 1 on, 2 fullscreen-only.";
|
||
};
|
||
extra = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "";
|
||
description = "Extra comma-separated Hyprland monitor args, appended verbatim.";
|
||
};
|
||
};
|
||
};
|
||
in
|
||
{
|
||
options.nomarchy = {
|
||
# ── Required ───────────────────────────────────────────────────
|
||
stateFile = lib.mkOption {
|
||
type = lib.types.path;
|
||
example = lib.literalExpression "./theme-state.json";
|
||
description = ''
|
||
Path to theme-state.json, the single source of truth for all UI
|
||
configuration. Must live inside your flake (so evaluation stays
|
||
pure) and be git-tracked. nomarchy-theme-sync writes to the
|
||
on-disk copy; rebuilds bake it into the generation.
|
||
'';
|
||
};
|
||
|
||
# ── Preferences ────────────────────────────────────────────────
|
||
terminal = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "ghostty";
|
||
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
|
||
};
|
||
|
||
keyboard.layout = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "us";
|
||
example = "de";
|
||
description = ''
|
||
XKB layout for the Hyprland session. The console (and the LUKS
|
||
passphrase prompt) take theirs from the system side — the
|
||
installer writes services.xserver.xkb + console.useXkbConfig
|
||
into system.nix with the same value.
|
||
'';
|
||
};
|
||
|
||
keyboard.variant = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "";
|
||
example = "nodeadkeys";
|
||
description = "XKB variant for the Hyprland session.";
|
||
};
|
||
|
||
keyboard.layouts = lib.mkOption {
|
||
type = lib.types.listOf lib.types.str;
|
||
default = [ ];
|
||
example = [ "de" "fr" ];
|
||
description = ''
|
||
Extra candidate layouts offered by the interactive new-keyboard
|
||
picker. When non-empty, a small watcher runs in the session: when a
|
||
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
||
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
||
the primary nomarchy.keyboard.layout), applies the choice to that
|
||
keyboard only (a per-device kb_layout, so the built-in board is left
|
||
alone), and remembers it per-device (~/.local/state) — re-applying
|
||
automatically on later reconnects. The runtime-remember complement to
|
||
the declarative keyboard.devices; a stateful runtime piece by design.
|
||
'';
|
||
};
|
||
|
||
keyboard.devices = lib.mkOption {
|
||
type = lib.types.attrsOf (lib.types.submodule {
|
||
options = {
|
||
layout = lib.mkOption {
|
||
type = lib.types.str;
|
||
description = "XKB layout for this specific keyboard.";
|
||
};
|
||
variant = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "";
|
||
description = "XKB variant for this keyboard.";
|
||
};
|
||
};
|
||
});
|
||
default = { };
|
||
example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
|
||
description = ''
|
||
Per-device keyboard layout. The key is the device name from
|
||
`hyprctl devices` (lower-case, hyphenated). Each entry generates a
|
||
Hyprland `device` block that overrides the session-wide
|
||
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
||
keyboard that's physically a different layout than the laptop's
|
||
built-in one. Hyprland applies it automatically whenever that
|
||
keyboard is connected.
|
||
'';
|
||
};
|
||
|
||
package = lib.mkOption {
|
||
type = lib.types.package;
|
||
default = pkgs.nomarchy-theme-sync;
|
||
defaultText = lib.literalExpression "pkgs.nomarchy-theme-sync";
|
||
description = "The nomarchy-theme-sync package (provided by overlays.default).";
|
||
};
|
||
|
||
themesDir = lib.mkOption {
|
||
type = lib.types.path;
|
||
default = ../../themes;
|
||
defaultText = lib.literalExpression "\"\${nomarchy}/themes\"";
|
||
description = ''
|
||
Theme presets/assets directory probed at eval time for per-theme
|
||
app overrides (<slug>/waybar.css, <slug>/waybar.jsonc). Point it
|
||
at your own directory to ship custom layouts downstream.
|
||
'';
|
||
};
|
||
|
||
nightlight = {
|
||
enable = lib.mkEnableOption ''
|
||
a scheduled blue-light filter (hyprsunset): warm at night, no shift
|
||
by day. Opt-in; tune the temperature + sunrise/sunset below'';
|
||
|
||
temperature = lib.mkOption {
|
||
type = lib.types.int;
|
||
default = 4000;
|
||
example = 3500;
|
||
description = "Warm colour temperature (K) applied at night — lower is warmer.";
|
||
};
|
||
|
||
sunrise = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "07:00";
|
||
example = "06:30";
|
||
description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift.";
|
||
};
|
||
|
||
sunset = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "20:00";
|
||
example = "21:00";
|
||
description = "Time (HH:MM) the filter turns ON — warm.";
|
||
};
|
||
};
|
||
|
||
updates = {
|
||
enable = lib.mkEnableOption ''
|
||
passive update awareness: a background check (systemd user timer) that
|
||
compares the flake's locked inputs (nixpkgs, the Nomarchy input, …)
|
||
against upstream and — when Flatpak is enabled — counts Flatpak
|
||
updates, surfacing a Waybar indicator + a notification when something
|
||
is available. It never changes anything; you still run sys-update /
|
||
home-update / flatpak update yourself'';
|
||
|
||
interval = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "daily";
|
||
example = "6h";
|
||
description = "How often to check, as a systemd OnCalendar expression.";
|
||
};
|
||
|
||
flatpak = lib.mkOption {
|
||
type = lib.types.bool;
|
||
default = true;
|
||
description = ''
|
||
Also count available Flatpak updates when the `flatpak` CLI is
|
||
present (i.e. nomarchy.services.flatpak is on). No effect otherwise.
|
||
'';
|
||
};
|
||
};
|
||
|
||
monitors = lib.mkOption {
|
||
type = lib.types.listOf monitorType;
|
||
default = [ ];
|
||
example = lib.literalExpression ''
|
||
[
|
||
{ name = "eDP-1"; position = "0x0"; }
|
||
{ name = "HDMI-A-1"; position = "auto-right"; scale = 1; }
|
||
]
|
||
'';
|
||
description = ''
|
||
Declarative per-output monitor layout. Each entry becomes a Hyprland
|
||
`monitor` rule; Hyprland applies them on hotplug, so a declared
|
||
external/dock output arranges itself when connected. The built-in
|
||
`,preferred,auto,1` wildcard stays as the fallback for any output you
|
||
don't list. For raw control set
|
||
`wayland.windowManager.hyprland.settings.monitor` directly instead
|
||
(it replaces this). Run `nwg-displays` (nomarchy.displays.enable) to
|
||
find the right values interactively.
|
||
'';
|
||
};
|
||
|
||
# ── Component toggles ──────────────────────────────────────────
|
||
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
||
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
||
rofi.enable = lib.mkEnableOption "Nomarchy's themed rofi launcher + the nomarchy-menu dispatcher" // { default = true; };
|
||
swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; };
|
||
idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; };
|
||
yazi.enable = lib.mkEnableOption "the yazi TUI file manager, themed with a curated plugin set" // { default = true; };
|
||
osd.enable = lib.mkEnableOption "swayosd on-screen display for volume/brightness/mute" // { default = true; };
|
||
shell.enable = lib.mkEnableOption "the zsh shell experience (starship prompt, bat/eza/zoxide)" // { default = true; };
|
||
keys.enable = lib.mkEnableOption "the SSH + GPG agent (gpg-agent fronting SSH, pinentry-qt)" // { default = true; };
|
||
fastfetch.enable = lib.mkEnableOption "fastfetch system info fronted by the themed Nomarchy logo" // { default = true; };
|
||
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
|
||
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
|
||
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
|
||
displays.enable = lib.mkEnableOption "the nwg-displays interactive monitor arranger (a helper to find nomarchy.monitors values; the declarative config stays the source of truth)" // { default = true; };
|
||
|
||
# ── Computed (read-only) ───────────────────────────────────────
|
||
theme = lib.mkOption {
|
||
type = lib.types.attrs;
|
||
readOnly = true;
|
||
description = "The parsed theme state (stateFile merged over defaults).";
|
||
};
|
||
|
||
lib = lib.mkOption {
|
||
type = lib.types.attrs;
|
||
readOnly = true;
|
||
description = "Color-format helpers shared by the theme consumers.";
|
||
};
|
||
};
|
||
}
|