Night light is off by default and configured entirely through the menu,
writing into the downstream flake instead of ~/.local/state — so the
choice is git-tracked and reproducible across a clone+rebuild.
Two flags under a new settings.* section of the state file (exposed as
the nomarchy.settings option):
- settings.nightlight.installed — sticky, gates the hyprsunset unit;
nomarchy.nightlight.enable mkDefault-reads it. First enable from the
menu rebuilds to create the unit (the one accepted rebuild).
- settings.nightlight.on — runtime on/off, toggled instantly
(theme-sync set --no-switch + systemctl), no rebuild. An ExecCondition
reads the live flag at session start so an off survives reboot;
splitting it from the sticky flag means a later unrelated rebuild
never undoes an instant-off (no decay).
Drops the ~/.local/state/nomarchy/nightlight-off marker and the
ConditionPathExists hack. theme-sync set now skips the wallpaper
re-apply for non-wallpaper keys, and its rebuild notifications are
generic ("changes" not "theme").
First cut of the broader principle — any user-settable config gets a
menu writer that lands it in the flake; no state outside the checkout.
Docs (README §4, ROADMAP, template) updated to reflect it.
Eval- and round-trip-verified; on-hardware check pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
276 lines
11 KiB
Nix
276 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).";
|
||
};
|
||
|
||
settings = lib.mkOption {
|
||
type = lib.types.attrs;
|
||
readOnly = true;
|
||
description = ''
|
||
Parsed feature settings — the `settings` section of the state file,
|
||
what the menu/Waybar toggles write (e.g. settings.nightlight.enable).
|
||
Feature options mkDefault-read from here, so a menu toggle lands in the
|
||
in-flake state (git-tracked, reproducible) rather than ~/.local/state.
|
||
'';
|
||
};
|
||
|
||
lib = lib.mkOption {
|
||
type = lib.types.attrs;
|
||
readOnly = true;
|
||
description = "Color-format helpers shared by the theme consumers.";
|
||
};
|
||
};
|
||
}
|