Compare commits
2 Commits
a606c0d498
...
42801548b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42801548b7 | ||
|
|
1b0eeeaf6c |
@@ -201,6 +201,8 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
|||||||
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
|
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
|
||||||
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
|
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
|
||||||
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
|
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
|
||||||
|
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
|
||||||
|
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
|
||||||
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
||||||
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
||||||
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
||||||
|
|||||||
@@ -131,13 +131,15 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
accent. Reads on light and dark; follows the theme as of the last system
|
accent. Reads on light and dark; follows the theme as of the last system
|
||||||
rebuild (like the background tint).
|
rebuild (like the background tint).
|
||||||
- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS
|
- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS
|
||||||
passphrase already gates the machine, but hypridle's `before_sleep_cmd`
|
passphrase already gates the machine, but locking hyprlock before sleep
|
||||||
also locked hyprlock, so the user typed a password twice. Fixed with a
|
meant a second password. Fixed by *not locking* before an encrypted
|
||||||
`nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`)
|
hibernate: a `nomarchy-lock-before-sleep` systemd unit
|
||||||
that dismisses hyprlock *after* a hibernate resume (`WantedBy
|
(`modules/nixos/default.nix`) takes over from hypridle's `before_sleep_cmd`
|
||||||
hibernate.target`, `After systemd-hibernate.service` → runs post-resume),
|
and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then-
|
||||||
gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase
|
hibernate) always, but skips `hibernate.target` when the disk is LUKS-
|
||||||
of suspend-then-hibernate) keep locking — they have no passphrase gate.
|
encrypted. (A first attempt that dismissed hyprlock *after* resume was
|
||||||
|
wrong — killing a Wayland session-lock client trips its "go to a tty"
|
||||||
|
crash failsafe instead of unlocking, which is the error screen it caused.)
|
||||||
- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix`
|
- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix`
|
||||||
(`nomarchy.keys.enable`): one agent — `services.gpg-agent` with
|
(`nomarchy.keys.enable`): one agent — `services.gpg-agent` with
|
||||||
`enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland,
|
`enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland,
|
||||||
@@ -231,12 +233,18 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
surface or split (`nomarchy.services.*` system-side vs home-side packages);
|
surface or split (`nomarchy.services.*` system-side vs home-side packages);
|
||||||
and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are
|
and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are
|
||||||
already covered (`allowUnfree = true`).
|
already covered (`allowUnfree = true`).
|
||||||
- **Display / monitor management:** today Hyprland just uses
|
- ✓ **Display / monitor management:** declarative `nomarchy.monitors` (a
|
||||||
`monitor = ,preferred,auto,1` (mkDefault). Add a real external-monitor/dock
|
per-output schema — name/resolution/position/scale/transform/mirror/
|
||||||
story: declarative per-output config, hotplug profiles (kanshi-style, or
|
bitdepth/vrr/disable) generates Hyprland `monitor` rules, keeping the
|
||||||
Hyprland's own monitor rules), and optionally a GUI arranger (nwg-displays
|
`,preferred,auto,1` wildcard as the fallback; Hyprland applies them on
|
||||||
writes Hyprland monitor config). Decide declarative-only vs GUI, and the
|
hotplug, so a declared external/dock output arranges itself on connect
|
||||||
option shape (`nomarchy.monitors` vs plain Hyprland `monitor` lists).
|
(no kanshi — it fights Hyprland's own output management). `nwg-displays`
|
||||||
|
ships behind `nomarchy.displays.enable` as an interactive arranger (a
|
||||||
|
helper to find values; the declarative config stays the source of truth —
|
||||||
|
its output file isn't sourced). Remaining: true docked/undocked **profile
|
||||||
|
switching** of the *same* outputs (Hyprland's per-output rules cover the
|
||||||
|
common "external connects → arrange" case, not multi-layout toggles), and
|
||||||
|
optionally workspace-to-monitor binding.
|
||||||
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
||||||
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
||||||
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
||||||
|
|||||||
@@ -41,6 +41,19 @@ let
|
|||||||
# (rofi.nix renders the same list). Edit them in ./keybinds.nix.
|
# (rofi.nix renders the same list). Edit them in ./keybinds.nix.
|
||||||
keybinds = import ./keybinds.nix;
|
keybinds = import ./keybinds.nix;
|
||||||
mkBind = b: "${b.mods}, ${b.key}, ${b.action}";
|
mkBind = b: "${b.mods}, ${b.key}, ${b.action}";
|
||||||
|
|
||||||
|
# A nomarchy.monitors entry -> a Hyprland `monitor` rule. Unset optional
|
||||||
|
# fields are omitted; `resolution = "disable"` collapses to the short form.
|
||||||
|
monitorRule = m:
|
||||||
|
if m.resolution == "disable" then "${m.name}, disable"
|
||||||
|
else lib.concatStringsSep ", " (
|
||||||
|
[ m.name m.resolution (toString m.position) (toString m.scale) ]
|
||||||
|
++ lib.optionals (m.transform != null) [ "transform" (toString m.transform) ]
|
||||||
|
++ lib.optionals (m.mirror != null) [ "mirror" m.mirror ]
|
||||||
|
++ lib.optionals (m.bitdepth != null) [ "bitdepth" (toString m.bitdepth) ]
|
||||||
|
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
||||||
|
++ lib.optional (m.extra != "") m.extra
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
||||||
@@ -59,9 +72,12 @@ in
|
|||||||
"$mod" = "SUPER";
|
"$mod" = "SUPER";
|
||||||
"$terminal" = config.nomarchy.terminal;
|
"$terminal" = config.nomarchy.terminal;
|
||||||
|
|
||||||
# mkDefault so a downstream `monitor = [...]` replaces it with a
|
# `,preferred,auto,1` is the wildcard fallback for any output not in
|
||||||
# plain assignment (the live ISO uses mkForce for the same reason).
|
# nomarchy.monitors; the generated rules are appended and win by name,
|
||||||
monitor = lib.mkDefault [ ",preferred,auto,1" ];
|
# and Hyprland applies them on hotplug. mkDefault so a downstream
|
||||||
|
# `monitor = [...]` replaces the lot (the live ISO uses mkForce too).
|
||||||
|
monitor = lib.mkDefault
|
||||||
|
([ ",preferred,auto,1" ] ++ map monitorRule config.nomarchy.monitors);
|
||||||
|
|
||||||
# exec-once is a list at normal priority, so downstream additions
|
# exec-once is a list at normal priority, so downstream additions
|
||||||
# CONCATENATE (your autostart runs alongside ours) rather than
|
# CONCATENATE (your autostart runs alongside ours) rather than
|
||||||
@@ -183,4 +199,12 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# nwg-displays: interactive monitor arranger (applies live via hyprctl and
|
||||||
|
# writes a config file). A helper to find values for nomarchy.monitors —
|
||||||
|
# the declarative config stays the source of truth (we don't source its
|
||||||
|
# output). Gated on its toggle; pointless without the Hyprland session.
|
||||||
|
home.packages = lib.optionals
|
||||||
|
(config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
||||||
|
[ pkgs.nwg-displays ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,13 @@ in
|
|||||||
settings = {
|
settings = {
|
||||||
general = {
|
general = {
|
||||||
lock_cmd = "pidof hyprlock || hyprlock";
|
lock_cmd = "pidof hyprlock || hyprlock";
|
||||||
# Locks before every sleep. For suspend that's exactly right; on an
|
# No before_sleep_cmd here: locking before sleep is driven
|
||||||
# encrypted hibernate the LUKS resume already gates the machine, so
|
# system-side so it can fire for suspend but skip an encrypted
|
||||||
# the system side dismisses this lock post-resume to avoid a double
|
# hibernate — whose LUKS resume already gates the machine, so a
|
||||||
# unlock — see nomarchy-hibernate-unlock in modules/nixos/default.nix.
|
# hyprlock on top is a second password (and a Wayland session-lock
|
||||||
before_sleep_cmd = "loginctl lock-session";
|
# can't be safely dropped after the fact — killing the locker trips
|
||||||
|
# its "go to a tty" crash failsafe). See nomarchy-lock-before-sleep
|
||||||
|
# in modules/nixos/default.nix.
|
||||||
after_sleep_cmd = "hyprctl dispatch dpms on";
|
after_sleep_cmd = "hyprctl dispatch dpms on";
|
||||||
};
|
};
|
||||||
listener = [
|
listener = [
|
||||||
|
|||||||
@@ -2,6 +2,60 @@
|
|||||||
# configure in their home.nix. Kept small on purpose.
|
# configure in their home.nix. Kept small on purpose.
|
||||||
{ lib, pkgs, ... }:
|
{ 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 = {
|
options.nomarchy = {
|
||||||
# ── Required ───────────────────────────────────────────────────
|
# ── Required ───────────────────────────────────────────────────
|
||||||
@@ -60,6 +114,27 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 ──────────────────────────────────────────
|
# ── Component toggles ──────────────────────────────────────────
|
||||||
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
||||||
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
||||||
@@ -74,6 +149,7 @@
|
|||||||
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { 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; };
|
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; };
|
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) ───────────────────────────────────────
|
# ── Computed (read-only) ───────────────────────────────────────
|
||||||
theme = lib.mkOption {
|
theme = lib.mkOption {
|
||||||
|
|||||||
@@ -123,23 +123,32 @@ in
|
|||||||
|
|
||||||
networking.networkmanager.enable = lib.mkDefault true;
|
networking.networkmanager.enable = lib.mkDefault true;
|
||||||
|
|
||||||
# No double-unlock on hibernate. hypridle locks hyprlock before every
|
# No double-unlock on hibernate. Locking the session before sleep is
|
||||||
# sleep (right for suspend), but a hibernate resume is already gated by
|
# right for suspend (resumes from RAM, no other gate), but an encrypted
|
||||||
# the LUKS passphrase entered at boot — so the user would type a password
|
# hibernate already resumes through the LUKS passphrase — a hyprlock on
|
||||||
# twice. Once we're back from an *encrypted* hibernate, drop the now-
|
# top is a second password. And we can't just drop the lock after the
|
||||||
# redundant hyprlock. Ordered After the hibernate service, so it runs
|
# resume: a Wayland session-lock whose client dies without releasing
|
||||||
# post-resume; pulled in only by hibernate.target (suspend, and the
|
# trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the
|
||||||
# RAM-resume phase of suspend-then-hibernate, stay locked — they have no
|
# screen locked for safety). So don't lock before an encrypted hibernate
|
||||||
# passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock.
|
# in the first place. hypridle can't tell suspend from hibernate, hence a
|
||||||
systemd.services.nomarchy-hibernate-unlock =
|
# system unit hooked to the sleep targets: it locks on the RAM-resume
|
||||||
lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) {
|
# sleeps always, and on hibernate only when the disk is unencrypted (no
|
||||||
description = "Dismiss hyprlock after resuming from an encrypted hibernate";
|
# LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd.
|
||||||
after = [ "systemd-hibernate.service" ];
|
systemd.services.nomarchy-lock-before-sleep =
|
||||||
wantedBy = [ "hibernate.target" ];
|
let
|
||||||
|
encrypted = builtins.attrNames config.boot.initrd.luks.devices != [ ];
|
||||||
|
# Sleeps whose normal resume is from RAM — always lock these.
|
||||||
|
ramTargets = [ "suspend.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
|
||||||
|
ramServices = [ "systemd-suspend.service" "systemd-hybrid-sleep.service" "systemd-suspend-then-hibernate.service" ];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
description = "Lock the session before sleep (skipped for an encrypted hibernate)";
|
||||||
|
before = ramServices ++ lib.optional (!encrypted) "systemd-hibernate.service";
|
||||||
|
wantedBy = ramTargets ++ lib.optional (!encrypted) "hibernate.target";
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
# `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure.
|
# Plural: a system unit has no session of its own to lock.
|
||||||
ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock";
|
ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user