# 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` | `x@` | `disable`."; }; position = lib.mkOption { type = lib.types.str; default = "auto"; description = "`auto` | `auto-right`/`auto-left`/… | `x`."; }; 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 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 (/waybar.css, /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. ''; }; 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 \")."; }; }; }); 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 (lib.types.listOf monitorType); default = { }; example = lib.literalExpression '' { docked = [ { name = "eDP-1"; resolution = "disable"; } { name = "DP-3"; position = "0x0"; } { name = "DP-4"; position = "auto-right"; } ]; undocked = [ { name = "eDP-1"; position = "0x0"; } ]; } ''; description = '' Named display layouts for the same outputs (docked, undocked, …), each a list of nomarchy.monitors-style entries. Switch from the menu (System › Display › Profiles) or `nomarchy-display-profile apply `: 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."; }; }; }