Files
Nomarchy/modules/home/options.nix
Bernardo Magri 3d5cb21302
Some checks failed
Check / eval (push) Has been cancelled
feat(cli): nomarchy-pull / nomarchy-rebuild / nomarchy-home
Split lifecycle into three clear commands (user pick):
  nomarchy-pull     git pull (if checkout) + flake.lock update
  nomarchy-rebuild  system switch on current lock (snap + nvd)
  nomarchy-home     Home Manager switch

Full upgrade: pull && rebuild && home. Legacy sys-update / sys-rebuild /
home-update remain as wrappers. nixos-rebuild-snap uses NOMARCHY_PATH.

V0: flake check --no-build green.
2026-07-10 13:32:50 +01:00

387 lines
16 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# User-level `nomarchy.*` options — the full surface downstream users
# configure in their home.nix. Kept small on purpose.
{ config, 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 07 (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.";
};
};
};
# One display profile: a monitor layout plus optional workspace→output
# pinning. A bare list of monitor entries still works (the original
# shape) — hyprland.nix normalizes it to { monitors = […]; }.
# (either, not coercedTo: coercedTo refuses list-of-submodule sources.)
displayProfileType = lib.types.either
(lib.types.listOf monitorType)
(lib.types.submodule {
options = {
monitors = lib.mkOption {
type = lib.types.listOf monitorType;
default = [ ];
description = "nomarchy.monitors-style entries; each replaces the base entry for the same output whole.";
};
workspaces = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
example = { "1" = "DP-3"; "9" = "eDP-1"; };
description = ''
Workspace output pinning while this profile is active
(Hyprland `workspace = <ws>, monitor:<output>` rules).
Applied instantly on profile switch (existing workspaces are
moved over) and baked at the next rebuild.
'';
};
};
});
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 = config.nomarchy.settings.terminal or "ghostty";
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
};
keyboard.layout = lib.mkOption {
type = lib.types.str;
default = config.nomarchy.settings.keyboard.layout or "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 in the git-tracked in-flake state
(settings.keyboard.devices, not ~/.local/state) re-applied
automatically on later reconnects and across reboots. Each remembered
choice graduates into nomarchy.keyboard.devices on the next rebuild
(a generated device block). The runtime-remember complement to the
declarative keyboard.devices.
'';
};
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. The interactive watcher
(nomarchy.keyboard.layouts) also writes its remembered picks here on
the next rebuild, so a runtime choice graduates into reproducible config.
'';
};
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 nomarchy-pull /
nomarchy-rebuild / nomarchy-home / flatpak update yourself'' // { default = config.nomarchy.settings.updates.enable or false; };
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.
'';
};
launchOrFocus = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
key = lib.mkOption {
type = lib.types.str;
example = "B";
description = "The key (with `mods`) that focuses-or-launches the app.";
};
mods = lib.mkOption {
type = lib.types.str;
default = "$mod";
description = "Modifier string, Hyprland syntax (\"$mod\", \"$mod SHIFT\").";
};
class = lib.mkOption {
type = lib.types.str;
example = "firefox";
description = "Window class to focus (case-insensitive; see `hyprctl clients`).";
};
command = lib.mkOption {
type = lib.types.str;
default = "";
description = "Command to launch when no window matches (defaults to the class).";
};
desc = lib.mkOption {
type = lib.types.str;
default = "";
description = "Cheatsheet label (defaults to \"Focus or launch <command>\").";
};
};
});
default = [ ];
example = lib.literalExpression ''
[
{ key = "B"; class = "firefox"; }
{ key = "O"; class = "obsidian"; }
]
'';
description = ''
Launch-or-focus binds: the key focuses the app's existing window
(case-insensitive class match) or launches it if none is open. Each
entry generates a Hyprland bind AND a SUPER+? cheatsheet row. The
launcher self-gates: a bind whose command isn't installed notifies
instead of failing silently.
'';
};
displayProfiles = lib.mkOption {
type = lib.types.attrsOf displayProfileType;
default = { };
example = lib.literalExpression ''
{
docked = {
monitors = [
{ name = "eDP-1"; resolution = "disable"; }
{ name = "DP-3"; position = "0x0"; }
{ name = "DP-4"; position = "auto-right"; }
];
workspaces = { "1" = "DP-3"; "9" = "DP-4"; };
};
undocked = [ { name = "eDP-1"; position = "0x0"; } ];
}
'';
description = ''
Named display layouts for the same outputs (docked, undocked, ):
a list of nomarchy.monitors-style entries, or an attrset with
`monitors` plus optional `workspaces` (workspace output pinning
while the profile is active). Switch from the menu (System
Display Profiles) or `nomarchy-display-profile apply <name>`:
the profile's rules apply instantly via hyprctl and the choice
persists in the in-flake state (settings.displayProfile), so the
next rebuild bakes the active profile's entries over
nomarchy.monitors by name. Outputs a profile doesn't name keep
their base rules.
'';
};
# ── 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; };
batteryNotify.enable = lib.mkEnableOption "low-battery notifications at the bar's thresholds (25% low, 10% critical that one stays up until dismissed); self-gating, a silent no-op on machines without a battery" // { 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; };
viewers.enable = lib.mkEnableOption "the document/image viewers (zathura, Stylix-themed, + imv)" // { default = true; };
mime.enable = lib.mkEnableOption "default file associations (xdg mimeapps.list: PDF/image/video/text/browser/directory); entries for absent apps are skipped, so it degrades with the package suite" // { 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.";
};
};
}