From bc5faec6b8c54ddf4f20f7df7dd13f950df983a4 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 14:05:10 +0100 Subject: [PATCH 01/51] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1bff82..6dbaa38 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,10 @@ workspaces · `Print` region screenshot. `tools/import-palettes.py themes/`. ## Roadmap - +- Fix UI: Install Nerd fonts by default (10 most popular ones) and check if the theme's font is installed and correctly set across the sytem. +- Check if distro is shipping drivers (a test on real hardware was missing wifi driver) +- We need to start creating a menu system (for apps launcher and for theme switching) +- We need to add networkManager applet to waybar so user can easily connect to internet - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme variants if still needed From dccceb4c4888ea07b41b7bada80643665db2b837 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 17:50:55 +0100 Subject: [PATCH 02/51] =?UTF-8?q?fix:=20real-hardware=20QA=20sweep=20?= =?UTF-8?q?=E2=80=94=20wifi,=20fonts,=20firmware,=20Hyprland=200.55,=20off?= =?UTF-8?q?line=20switching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roadmap items 1+2 plus the issues found testing on a Latitude 5410: - Ship the 10 most popular Nerd Fonts by default (iosevka swapped for mononoki: 1.1 GB vs 27 MB) and warn from nomarchy-theme-sync when a configured fonts.mono/fonts.ui family isn't installed (fc-match) — fontconfig substitutes silently otherwise. - hardware.enableRedistributableFirmware: installed systems shipped NO firmware blobs (wifi/SOF audio/BT) — nixos-generate-config only emits microcode lines referencing this flag, it never sets it. - Live ISO wifi: networking.wireless.enable = mkForce false killed NetworkManager's supplicant — since 26.05 the NM module drives its wifi backend THROUGH networking.wireless (dbusControlled). Devices vanished from nmtui with iwlwifi loaded and no rfkill block. - Hyprland 0.55: launch sessions via start-hyprland (watchdog; bare binary warns), add the new gesture keyword (workspace_swipe is gone — touchpads had no gestures at all), media keys via wpctl/bindel/bindl plus the missing mic/play/next/prev binds. - Offline theme switching: pin mustache-go + stdenv + the repo's own standalone HM generation (incl. home-files inputDerivation) into the ISO — stylix re-renders base16 templates per switch and an offline `apply` cascaded into building stdenv from source (1107 drvs). Verified with tools/vm/gap-analysis.py: 17 config drvs, zero fetches. - Stylix: track release-26.05 (kills the HM version-skew warning and the stale home-manager follows), lock updated. - Roadmap: swaync (no notification daemon ships today). Co-Authored-By: Claude Fable 5 --- README.md | 3 +- flake.lock | 17 ++++++----- flake.nix | 30 +++++++++++++++++-- hosts/live.nix | 10 ++++--- modules/home/hyprland.nix | 26 +++++++++++++--- modules/nixos/default.nix | 27 +++++++++++++++-- .../nomarchy-theme-sync.py | 26 ++++++++++++++++ 7 files changed, 117 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6dbaa38..078b56b 100644 --- a/README.md +++ b/README.md @@ -230,8 +230,6 @@ workspaces · `Print` region screenshot. `tools/import-palettes.py themes/`. ## Roadmap -- Fix UI: Install Nerd fonts by default (10 most popular ones) and check if the theme's font is installed and correctly set across the sytem. -- Check if distro is shipping drivers (a test on real hardware was missing wifi driver) - We need to start creating a menu system (for apps launcher and for theme switching) - We need to add networkManager applet to waybar so user can easily connect to internet - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB @@ -241,3 +239,4 @@ workspaces · `Print` region screenshot. - Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) - `hyprlock`/`hypridle`, swayosd, launch-or-focus UX scripts +- swaync notification center (notify-send currently renders bare) diff --git a/flake.lock b/flake.lock index 075584c..7546885 100644 --- a/flake.lock +++ b/flake.lock @@ -208,11 +208,11 @@ ] }, "locked": { - "lastModified": 1780281641, - "narHash": "sha256-M/+hUKoKbHXpV0xGVfELbN1Ds1aoe3pL5p5/t46YhVo=", + "lastModified": 1779766384, + "narHash": "sha256-P7Ohnlq8b8b2fU+Sgkrej7LBTM60LBTkHleLuYzmLmU=", "owner": "nix-community", "repo": "NUR", - "rev": "30f9ae2f04174de63ba8bcf3580ca90843b28a01", + "rev": "57800b7ab648725ccd33551d01484ee14952ad3f", "type": "github" }, "original": { @@ -249,15 +249,16 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1781018772, - "narHash": "sha256-C+cGIUaC6dqfwTbI+BwCd572PbESGA3WYxR1sLTqxkY=", - "owner": "danth", + "lastModified": 1780702455, + "narHash": "sha256-+srjPGNy67nKytYwdlepycL51IG6S34sS4MKRZXK8G0=", + "owner": "nix-community", "repo": "stylix", - "rev": "a378e4c09031fb15a4d65da88aa628f71fc52f6b", + "rev": "54fa19702f4f2c7f6a981a92850678933588af9a", "type": "github" }, "original": { - "owner": "danth", + "owner": "nix-community", + "ref": "release-26.05", "repo": "stylix", "type": "github" } diff --git a/flake.nix b/flake.nix index 11862e5..fe28559 100644 --- a/flake.nix +++ b/flake.nix @@ -10,9 +10,12 @@ }; stylix = { - url = "github:danth/stylix"; + # Track the release branch matching nixpkgs/HM — master is built + # against unstable and warns about the version skew on every + # activation. (No home-manager input to follow anymore: stylix + # vendors its HM integration.) + url = "github:nix-community/stylix/release-26.05"; inputs.nixpkgs.follows = "nixpkgs"; - inputs.home-manager.follows = "home-manager"; }; # Pinned by Nomarchy so distro + hardware quirks are tested together @@ -270,6 +273,20 @@ representativeInstall.config.system.build.toplevel template.homeConfigurations.${username}.activationPackage + # The live session's `home-manager switch` builds the + # REPO's standalone generation (the seeded ~/.nomarchy + # flake) — not the template's, and not the NixOS-module + # one baked into the ISO. It has drvs the other two + # variants don't (user-dbus-services et al.), so pin it + # and its direct build inputs too. + self.homeConfigurations.${username}.activationPackage + self.homeConfigurations.${username}.activationPackage.inputDerivation + # home-files is rebuilt per theme and COPIES some of its + # inputs (user-dbus-services) instead of referencing + # them — the activation closure doesn't carry those, so + # pin home-files' direct build inputs separately. + self.homeConfigurations.${username}.config.home-files.inputDerivation + # A real install rebuilds the drvs that embed the # user's hostname/username/UUIDs (etc, initrd, units, # toplevel, HM activation). inputDerivation pins each @@ -278,6 +295,15 @@ representativeInstall.config.system.build.etc.inputDerivation representativeInstall.config.system.build.initialRamdisk.inputDerivation template.homeConfigurations.${username}.activationPackage.inputDerivation + ] ++ [ + # A theme switch re-renders stylix's base16 templates + # (gtk.css, kvantum, …) on the machine: the renderer and + # the stdenv its trivial drvs build with must be on + # board, or an offline `apply ` cascades into + # building mustache-go (and eventually stdenv) from + # source. Found with tools/vm/gap-analysis.py. + pkgs.mustache-go + pkgs.stdenv ] ++ [ # …and the second-level build tools those rebuilds need # (their parents are themselves rebuilt, so parent diff --git a/hosts/live.nix b/hosts/live.nix index 786a0bc..5e8c04a 100644 --- a/hosts/live.nix +++ b/hosts/live.nix @@ -9,9 +9,11 @@ isoImage.volumeID = lib.mkForce "NOMARCHY_LIVE"; isoImage.edition = lib.mkForce "live"; - # The minimal-CD profile enables wpa_supplicant; Nomarchy ships - # NetworkManager and the two must not run together. - networking.wireless.enable = lib.mkForce false; + # Do NOT touch networking.wireless here: since NixOS 26.05 the + # NetworkManager module drives its wifi backend through it + # (wireless.enable = true + dbusControlled). Force-disabling it + # kills NM's supplicant — wifi devices vanish from nmtui even + # though the driver is loaded (seen on a Latitude 5410 / AX201). # ── Live user: no password, straight into the desktop ────────────── users.users.${username} = { @@ -24,7 +26,7 @@ # Boot straight into Hyprland once; logging out lands on tuigreet. services.greetd.settings.initial_session = { - command = "Hyprland"; + command = "start-hyprland"; user = username; }; diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 6a44b8a..5584ad1 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -99,6 +99,13 @@ in # dispatcher still exists); setting it aborts config parsing. dwindle.preserve_split = true; + # Hyprland 0.51+ replaced gestures:workspace_swipe with the + # gesture keyword — without this, touchpads have NO gestures. + gesture = [ + "3, horizontal, workspace" + "4, pinch, fullscreen" + ]; + misc = { disable_hyprland_logo = true; disable_splash_rendering = true; @@ -129,14 +136,25 @@ in ", Print, exec, grim -g \"$(slurp)\" - | wl-copy" ] ++ workspaceBinds; - binde = [ - ", XF86AudioRaiseVolume, exec, pamixer -i 5" - ", XF86AudioLowerVolume, exec, pamixer -d 5" - ", XF86AudioMute, exec, pamixer -t" + # Media keys: wpctl talks to wireplumber directly (no + # pipewire-pulse shim like pamixer); e = repeat, l = also on a + # locked screen — the standard flags for hardware keys. + bindel = [ + ", XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+" + ", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-" ", XF86MonBrightnessUp, exec, brightnessctl set +5%" ", XF86MonBrightnessDown, exec, brightnessctl set 5%-" ]; + bindl = [ + ", XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" + ", XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" + ", XF86AudioPlay, exec, playerctl play-pause" + ", XF86AudioPause, exec, playerctl play-pause" + ", XF86AudioNext, exec, playerctl next" + ", XF86AudioPrev, exec, playerctl previous" + ]; + bindm = [ "$mod, mouse:272, movewindow" "$mod, mouse:273, resizewindow" diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index f4e03f7..431a0ef 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -28,12 +28,14 @@ in enable = lib.mkDefault true; settings = { default_session = { - command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd Hyprland"; + # start-hyprland is Hyprland 0.55's watchdog launcher; running + # the bare binary makes every session print a warning. + command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd start-hyprland"; user = "greeter"; }; # Boot straight into the session once; logout → normal greeter. initial_session = lib.mkIf (cfg.greeter.autoLogin != null) { - command = "Hyprland"; + command = "start-hyprland"; user = cfg.greeter.autoLogin; }; }; @@ -60,6 +62,13 @@ in hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable; services.blueman.enable = lib.mkDefault cfg.bluetooth.enable; + # ── Firmware ───────────────────────────────────────────────────── + # Blobs for in-kernel drivers: wifi (iwlwifi/ath/rtw/brcm), SOF + # audio, Bluetooth. Drivers ship with the kernel, but without these + # a real machine can boot with no wifi — QEMU never catches it. + # nixos-generate-config also keys CPU microcode off this flag. + hardware.enableRedistributableFirmware = lib.mkDefault true; + # ── BTRFS timeline snapshots (ported from the previous iteration) ─ # Guarded on the actual filesystem so enabling it on an ext4 machine # is a clean no-op rather than a failing timer. @@ -79,9 +88,23 @@ in }; # ── Fonts ──────────────────────────────────────────────────────── + # The ten most popular Nerd Fonts ship by default, so any of them + # can be named in the theme state's fonts.mono and actually resolve + # (nomarchy-theme-sync warns when a configured font is missing). fonts = { packages = with pkgs; [ nerd-fonts.jetbrains-mono + nerd-fonts.fira-code + nerd-fonts.caskaydia-cove + nerd-fonts.hack + nerd-fonts.sauce-code-pro + nerd-fonts.meslo-lg + nerd-fonts.roboto-mono + nerd-fonts.ubuntu-mono + # iosevka would be next by popularity, but its package is 1.1 GB + # (every weight × variant) — too heavy for the ISO and closures. + nerd-fonts.mononoki + nerd-fonts.inconsolata inter noto-fonts noto-fonts-color-emoji diff --git a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py index ba136a7..7f23141 100644 --- a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py +++ b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py @@ -109,6 +109,29 @@ def deep_merge(base: dict, override: dict) -> dict: return out +# ─── Font verification ──────────────────────────────────────────────────── + +def check_fonts(state: dict) -> None: + """Warn when a configured font family isn't installed. fontconfig + substitutes silently, so a typo'd or missing fonts.mono would just + render the whole desktop in some fallback with no error anywhere.""" + if shutil.which("fc-match") is None: + return + for key in ("mono", "ui"): + name = (state.get("fonts") or {}).get(key) + if not name: + continue + result = subprocess.run(["fc-match", "-f", "%{family}", name], + capture_output=True, text=True) + families = [f.strip().lower() for f in result.stdout.split(",")] + if result.returncode != 0 or name.strip().lower() not in families: + fallback = result.stdout.strip() or "unknown" + print(f"nomarchy-theme-sync: warning: fonts.{key} '{name}' is not " + f"installed — fontconfig will substitute '{fallback}'", + file=sys.stderr) + notify(f"Font '{name}' is not installed — using '{fallback}'") + + # ─── Rebuild dispatch ───────────────────────────────────────────────────── def run_switch() -> None: @@ -219,6 +242,7 @@ def cmd_apply(args) -> None: state = deep_merge(load_state(), preset) write_state(state) log(f"theme: {state.get('name', args.theme)}") + check_fonts(state) if not args.no_switch: run_switch() apply_wallpaper(state) @@ -241,6 +265,8 @@ def cmd_set(args) -> None: write_state(state) log(f"set {args.path} = {value!r}") + if args.path.startswith("fonts."): + check_fonts(state) if not args.no_switch: run_switch() apply_wallpaper(state) From a33127cd3c6e5851dc194230349f4b5ed789396d Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 17:59:52 +0100 Subject: [PATCH 03/51] =?UTF-8?q?docs:=20roadmap=20=E2=80=94=20Nomarchy=20?= =?UTF-8?q?distro=20branding=20(os-release,=20ISO=20boot=20menu,=20splash)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 078b56b..f974eda 100644 --- a/README.md +++ b/README.md @@ -240,3 +240,10 @@ workspaces · `Print` region screenshot. boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) - `hyprlock`/`hypridle`, swayosd, launch-or-focus UX scripts - swaync notification center (notify-send currently renders bare) +- **Distro branding:** replace the NixOS brand with Nomarchy everywhere it + surfaces — `system.nixos.distroName`/`distroId` (flows into + `/etc/os-release`, the live ISO's boot-menu label and the installed + system's systemd-boot entries), `isoImage.splashImage`/`grubTheme` for + the ISO boot screen, tuigreet/MOTD text. Caveats: `distroId` also + changes `DEFAULT_HOSTNAME` and some upstream `isNixos` checks, and + nixos-* CLI names stay (renaming tooling is not worth the drift) From 009bdbad8fae6866100e53103144d4f27de6ad8b Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 18:03:58 +0100 Subject: [PATCH 04/51] =?UTF-8?q?docs:=20roadmap=20=E2=80=94=20menu=20syst?= =?UTF-8?q?em=20design:=20fuzzel=20dmenu=20dispatcher,=20themed=20from=20t?= =?UTF-8?q?he=20JSON?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the old Walker/Lua previews-vs-simplicity question in favor of fuzzel: already shipped, nested menus via dmenu mode, INI theme bakeable from theme-state.json like every other app. Co-Authored-By: Claude Fable 5 --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f974eda..c20e5ab 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,20 @@ workspaces · `Print` region screenshot. `tools/import-palettes.py themes/`. ## Roadmap -- We need to start creating a menu system (for apps launcher and for theme switching) +- **Menu system** (apps launcher + theme switching + system actions), built + on fuzzel — already shipped for SUPER+D/SUPER+T, dmenu mode does nested + menus and per-entry icons, and its INI theme can be baked from + theme-state.json like every other app: + - `modules/home/fuzzel.nix`: colors/fonts from the theme JSON (today the + launcher is the one surface that ignores the theme) + - `nomarchy-menu` dispatcher script over `fuzzel --dmenu`, Omarchy-style + nesting: Theme ▸ (apply/wallpaper) · Network ▸ nmtui · Bluetooth ▸ + blueman · Power ▸ lock/suspend/reboot/poweroff · Capture ▸ screenshot; + actions delegate to nomarchy-theme-sync and friends + - decision record: this resolves the old Walker/Lua question in favor of + simplicity — no GTK4 launcher, no second theming pipeline, no image + previews (instant `bg next` covers wallpaper browsing); the dispatcher + owns the menu structure, so the renderer stays swappable - We need to add networkManager applet to waybar so user can easily connect to internet - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme From ed7be0206d6867488b58c0ea6cae26af85d63f4b Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 18:11:28 +0100 Subject: [PATCH 05/51] =?UTF-8?q?docs:=20roadmap=20=E2=80=94=20menu=20scri?= =?UTF-8?q?pt=20modules:=20calc,=20clipboard,=20files,=20web,=20ask-Claude?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index c20e5ab..712dc5d 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,17 @@ workspaces · `Print` region screenshot. nesting: Theme ▸ (apply/wallpaper) · Network ▸ nmtui · Bluetooth ▸ blueman · Power ▸ lock/suspend/reboot/poweroff · Capture ▸ screenshot; actions delegate to nomarchy-theme-sync and friends + - script modules (each a small dmenu pipeline, no plugin framework): + calculator (`--prompt-only` → `qalc -t` → result re-shown via `--mesg`, + Enter copies / chains; needs libqalculate) · clipboard history + (cliphist ⇄ fuzzel ⇄ wl-copy; also adds clipboard persistence) · file + open (`fd . ~` → xdg-open) · web search (query → xdg-open) · **ask + Claude**: `--prompt-only` → `$TERMINAL -e claude ""` — the + claude CLI auths via OAuth against a Pro/Max subscription (no API + key); REPL stays open for follow-ups (claude-code in nixpkgs is + unfree — needs a per-package allowUnfree) + - known limits accepted for now: no live-as-you-type results (dmenu + input is static per invocation) and no image previews - decision record: this resolves the old Walker/Lua question in favor of simplicity — no GTK4 launcher, no second theming pipeline, no image previews (instant `bg next` covers wallpaper browsing); the dispatcher From c1781f45ca635c34c0aae5fac0f304efe6b94c5f Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 18:16:20 +0100 Subject: [PATCH 06/51] feat: allow unfree packages distro-wide nixpkgs.config.allowUnfree in the system module (covers the live ISO via useGlobalPkgs and every mkFlake machine) plus the two explicit `import nixpkgs` sites (repo + lib.nix) so the standalone HM desktop sees the same package set. Unblocks claude-code for the menu system's ask-Claude module, vendor drivers, etc. Opt out with `nixpkgs.config = lib.mkForce { allowUnfree = false; }`. Co-Authored-By: Claude Fable 5 --- flake.nix | 3 +++ lib.nix | 3 +++ modules/nixos/default.nix | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/flake.nix b/flake.nix index fe28559..8df64cd 100644 --- a/flake.nix +++ b/flake.nix @@ -35,6 +35,9 @@ pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; + # Matches nixpkgs.config.allowUnfree in modules/nixos — the + # standalone HM generation must come from like-configured pkgs. + config.allowUnfree = true; }; # The guided installer — live-ISO only (not in the overlay): it bakes diff --git a/lib.nix b/lib.nix index 674ad95..07e442d 100644 --- a/lib.nix +++ b/lib.nix @@ -47,6 +47,9 @@ pkgs = import nixpkgs { inherit system; overlays = [ nomarchy.overlays.default ]; + # Matches nixpkgs.config.allowUnfree in modules/nixos: the system + # and the standalone HM desktop see the same package set. + config.allowUnfree = true; }; in { diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 431a0ef..4ef0cda 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -14,6 +14,12 @@ in imports = [ ./options.nix ]; config = { + # Unfree allowed distro-wide: pragmatic-desktop territory (claude-code + # for the menu's ask-Claude module, vendor drivers, …). The custom + # nixpkgs-config type can't carry a nested mkDefault; disagree with + # `nixpkgs.config = lib.mkForce { allowUnfree = false; }`. + nixpkgs.config.allowUnfree = true; + # ── Wayland session: Hyprland ──────────────────────────────────── # Installs the binary, registers the session, wires up # xdg-desktop-portal-hyprland. Configuration is Home Manager's job. From 1ba1eeed64f9b7b4f938e1323f417574adf13187 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 12 Jun 2026 20:23:28 +0100 Subject: [PATCH 07/51] fix(live): re-enable fontconfig + xdg integration the minimal-CD profile strips MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit installation-cd-minimal forces fonts.fontconfig.enable = false (mkOverride 500) to slim a console-only installer — but this ISO is the desktop. Without fontconfig no family resolves: Waybar's nf-md icons rendered as tofu boxes and Ghostty silently fell back to the wrong font (reported on the Latitude 5410, reproduced + fix verified in QEMU via VNC screenshots: icons render, fc-match resolves JetBrainsMono Nerd Font). Also restore xdg icons/mime/autostart and man pages. Co-Authored-By: Claude Fable 5 --- hosts/live.nix | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hosts/live.nix b/hosts/live.nix index 5e8c04a..d3f0c8d 100644 --- a/hosts/live.nix +++ b/hosts/live.nix @@ -9,6 +9,19 @@ isoImage.volumeID = lib.mkForce "NOMARCHY_LIVE"; isoImage.edition = lib.mkForce "live"; + # The minimal-CD profile slims the image for a CONSOLE installer; this + # ISO is the desktop, so re-enable what it strips. Above all + # fontconfig (upstream forces it off at mkOverride 500): without it + # no configured family resolves — Waybar icons render as tofu and + # Ghostty silently falls back to the wrong font (seen on the + # Latitude 5410). Normal priority (100) beats the override. + fonts.fontconfig.enable = true; + xdg.icons.enable = true; + xdg.mime.enable = true; + xdg.autostart.enable = true; + documentation.enable = true; + documentation.man.enable = true; + # Do NOT touch networking.wireless here: since NixOS 26.05 the # NetworkManager module drives its wifi backend through it # (wireless.enable = true + dbusControlled). Force-disabling it From eda8461304ce5b46b206a1a551a8fd9c420f70c9 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 07:37:32 +0100 Subject: [PATCH 08/51] feat(system): Plymouth splash, distroName, allowUnfree, offline-pin hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Plymouth boot splash ported from the legacy branch (modules/nixos/ plymouth/): logo + eased progress + LUKS entry, background tinted from theme-state.json via the new nomarchy.system.stateFile (wired by mkFlake/lib.nix; null → Tokyo Night fallback). Default on; OFF on the live ISO (boot-message visibility on the install medium). Pulls boot.initrd.systemd, which also drives the keyboard-at-LUKS feature. - system.nixos.distroName = "Nomarchy" (os-release PRETTY_NAME, systemd-boot entries, ISO menu label). distroId left "nixos" (feeds DEFAULT_HOSTNAME + upstream isNixos checks — roadmapped). - nixpkgs.config.allowUnfree distro-wide (here + both import-nixpkgs sites) — unblocks claude-code for the menu's ask-Claude module. - systemd-boot.configurationLimit = 10 so entries don't fill the ESP. - Live ISO: nomarchy.idle.enable = false — hypridle was suspending the VM mid-install (the install-hung regression); installed systems keep it. - flake.nix offline pins (verified 0-leak via a foreign-identity gap-analysis probe): the repo's own standalone HM gen + inputDerivations, mustache-go + stdenv (stylix re-renders base16 per switch), microcode-amd/ intel (enableRedistributableFirmware activated updateMicrocode → source-build cascade), buildEnv's builder.pl, findXMLCatalogs, and the representativeInstall mirror (xkb/initrd-systemd/microcode). Co-Authored-By: Claude Fable 5 --- flake.nix | 32 +++ hosts/live.nix | 11 + lib.nix | 3 + modules/home/fuzzel.nix | 82 +++++++ modules/home/idle.nix | 72 ++++++ modules/home/swaync.nix | 92 ++++++++ modules/nixos/default.nix | 39 +++- modules/nixos/options.nix | 16 ++ modules/nixos/plymouth.nix | 87 ++++++++ modules/nixos/plymouth/bullet.png | Bin 0 -> 293 bytes modules/nixos/plymouth/entry.png | Bin 0 -> 694 bytes modules/nixos/plymouth/lock.png | Bin 0 -> 1537 bytes modules/nixos/plymouth/logo.png | Bin 0 -> 23303 bytes modules/nixos/plymouth/nomarchy.plymouth | 11 + modules/nixos/plymouth/nomarchy.script | 271 +++++++++++++++++++++++ modules/nixos/plymouth/progress_bar.png | Bin 0 -> 314 bytes modules/nixos/plymouth/progress_box.png | Bin 0 -> 314 bytes themes/summer-day/waybar.jsonc | 92 ++++++++ themes/summer-night/waybar.jsonc | 99 +++++++++ 19 files changed, 906 insertions(+), 1 deletion(-) create mode 100644 modules/home/fuzzel.nix create mode 100644 modules/home/idle.nix create mode 100644 modules/home/swaync.nix create mode 100644 modules/nixos/plymouth.nix create mode 100644 modules/nixos/plymouth/bullet.png create mode 100644 modules/nixos/plymouth/entry.png create mode 100644 modules/nixos/plymouth/lock.png create mode 100644 modules/nixos/plymouth/logo.png create mode 100644 modules/nixos/plymouth/nomarchy.plymouth create mode 100644 modules/nixos/plymouth/nomarchy.script create mode 100644 modules/nixos/plymouth/progress_bar.png create mode 100644 modules/nixos/plymouth/progress_box.png create mode 100644 themes/summer-day/waybar.jsonc create mode 100644 themes/summer-night/waybar.jsonc diff --git a/flake.nix b/flake.nix index 8df64cd..825a676 100644 --- a/flake.nix +++ b/flake.nix @@ -148,6 +148,7 @@ specialArgs = { inherit username; }; modules = [ self.nixosModules.nomarchy + { nomarchy.system.stateFile = ./theme-state.json; } ./hosts/default/configuration.nix ]; }; @@ -251,6 +252,20 @@ ({ lib, ... }: { nomarchy.system.snapper.enable = true; nomarchy.system.greeter.autoLogin = username; + # The installer writes these (keyboard-everywhere + # incl. the LUKS prompt) — mirror them or the + # initrd/vconsole pins don't match an offline + # install's rebuild set. + console.useXkbConfig = true; + boot.initrd.systemd.enable = true; + # nixos-generate-config keys microcode off + # enableRedistributableFirmware (now on): the + # real machine enables exactly one vendor — + # pin both, or the offline install builds + # amd-ucode/intel-ucode from source (found the + # hard way: the 2700s regression hang). + hardware.cpu.amd.updateMicrocode = true; + hardware.cpu.intel.updateMicrocode = true; swapDevices = [{ device = "/swap/swapfile"; }]; boot.resumeDevice = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000"; boot.kernelParams = [ "resume_offset=1" ]; @@ -298,6 +313,11 @@ representativeInstall.config.system.build.etc.inputDerivation representativeInstall.config.system.build.initialRamdisk.inputDerivation template.homeConfigurations.${username}.activationPackage.inputDerivation + # home-files COPIES user-dbus-services instead of + # referencing it (same story as the self pin above) — + # a custom-username install rebuilds home-files and + # needs its direct inputs present. + template.homeConfigurations.${username}.config.home-files.inputDerivation ] ++ [ # A theme switch re-renders stylix's base16 templates # (gtk.css, kvantum, …) on the machine: the renderer and @@ -328,6 +348,18 @@ # tries to BUILD remarshal, whose test closure pulls # matplotlib/ffmpeg/x265 (the ~1900-drv cascade). pkgs.remarshal + # system-path/initrd-bin-env are buildEnvs rebuilt per + # machine; their shared builder.pl is build-time-only + # (never in any runtime closure) — an empty env's + # inputDerivation retains it. + (pkgs.buildEnv { name = "nomarchy-pin-buildenv"; paths = [ ]; }).inputDerivation + # /etc/dbus-1 reassembly wants this setup hook. + pkgs.findXMLCatalogs + # nixos-generate-config keys microcode off + # enableRedistributableFirmware (now on): exactly one + # vendor per real machine, so pin both ucode cpios. + pkgs.microcode-amd + pkgs.microcode-intel # …and one representative disko script: its tool closure # (file, which, wrapper hooks, …) isn't otherwise on the diff --git a/hosts/live.nix b/hosts/live.nix index d3f0c8d..ac769a0 100644 --- a/hosts/live.nix +++ b/hosts/live.nix @@ -16,6 +16,11 @@ # Ghostty silently falls back to the wrong font (seen on the # Latitude 5410). Normal priority (100) beats the override. fonts.fontconfig.enable = true; + + # No boot splash on the install medium: the installer ISO boots its + # own initrd path (squashfs), and visibility of boot messages is + # worth more than polish here. Installed systems get the splash. + nomarchy.system.plymouth.enable = false; xdg.icons.enable = true; xdg.mime.enable = true; xdg.autostart.enable = true; @@ -85,6 +90,12 @@ # ── Live-session desktop tweaks ───────────────────────────────────── home-manager.users.${username} = { + # No idle lock/suspend on the install medium: an offline install + # runs 20-30 min unattended, and hypridle would blank the display + # then SUSPEND the machine mid-install (it did — the install-hung + # regression). Installed systems keep idle management. + nomarchy.idle.enable = false; + wayland.windowManager.hyprland.settings = { # QEMU (and some panels) report a tiny "preferred" mode; ask for # the highest resolution instead. diff --git a/lib.nix b/lib.nix index 07e442d..ff9746c 100644 --- a/lib.nix +++ b/lib.nix @@ -65,6 +65,9 @@ # (`nomarchy-theme-sync apply` → `home-manager switch`) works # out of the box — same pinned input as the desktop modules. { environment.systemPackages = [ home-manager.packages.${system}.home-manager ]; } + # System-side theme consumers (Plymouth splash background) + # read the same JSON the desktop does. + { nomarchy.system.stateFile = src + "/theme-state.json"; } ] ++ hardwareModules ++ [ diff --git a/modules/home/fuzzel.nix b/modules/home/fuzzel.nix new file mode 100644 index 0000000..b0f126b --- /dev/null +++ b/modules/home/fuzzel.nix @@ -0,0 +1,82 @@ +# Fuzzel — launcher, dmenu renderer for the menu system, themed from +# theme-state.json like every other surface. Also home of nomarchy-menu, +# the dispatcher script the menu roadmap grows out of (first module: +# power). +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; + + # fuzzel.ini colors are rrggbbaa, no leading #. + hexa = color: alpha: "${lib.removePrefix "#" color}${alpha}"; + + nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' + # Nomarchy menu dispatcher — thin presentation layer over + # `fuzzel --dmenu`; actions delegate to systemctl/hyprctl/ + # nomarchy-theme-sync. The icons are nf-md glyphs (any shipped + # Nerd Font carries them). + case "''${1:-}" in + power) + choice=$(printf '%s\n' \ + "󰍃 Logout" \ + "󰤄 Suspend" \ + "󰒲 Hibernate" \ + "󰜉 Reboot" \ + "󰐥 Shutdown" \ + | fuzzel --dmenu --prompt "power " --lines 5 --width 24) || exit 0 + case "$choice" in + *Logout) hyprctl dispatch exit ;; + *Suspend) systemctl suspend ;; + *Hibernate) systemctl hibernate ;; + *Reboot) systemctl reboot ;; + *Shutdown) systemctl poweroff ;; + esac ;; + *) + echo "usage: nomarchy-menu power" >&2 + exit 64 ;; + esac + ''; +in +{ + config = lib.mkIf cfg.fuzzel.enable { + home.packages = [ nomarchy-menu ]; + + programs.fuzzel = { + enable = true; + settings = { + main = { + # UI font first; the mono Nerd Font follows so menu icons + # (nf-md glyphs) resolve — fcft falls back per glyph. + font = "${t.fonts.ui}:size=${toString (t.fonts.size + 1)}, ${t.fonts.mono}:size=${toString (t.fonts.size + 1)}"; + terminal = cfg.terminal; + layer = "overlay"; + lines = 12; + width = 40; + horizontal-pad = 24; + vertical-pad = 16; + inner-pad = 8; + }; + + colors = { + background = hexa c.base "f2"; + text = hexa c.text "ff"; + prompt = hexa c.subtext "ff"; + placeholder = hexa c.muted "ff"; + input = hexa c.text "ff"; + match = hexa c.accent "ff"; + selection = hexa c.surface "ff"; + selection-text = hexa c.text "ff"; + selection-match = hexa c.accent "ff"; + border = hexa c.accent "ee"; + }; + + border = { + width = t.ui.borderSize; + radius = t.ui.rounding; + }; + }; + }; + }; +} diff --git a/modules/home/idle.nix b/modules/home/idle.nix new file mode 100644 index 0000000..db4b173 --- /dev/null +++ b/modules/home/idle.nix @@ -0,0 +1,72 @@ +# hyprlock + hypridle — screen locking and idle management, themed from +# theme-state.json. One concern, one file: hypridle drives WHEN (idle +# lock, display off, suspend, lock-before-sleep), hyprlock is the +# themed lock screen itself (also behind the power menu's Lock entry). +{ config, lib, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; + inherit (config.nomarchy.lib) rgb; +in +{ + config = lib.mkIf cfg.idle.enable { + programs.hyprlock = { + enable = true; + settings = { + general.hide_cursor = true; + + background = [{ + monitor = ""; + color = rgb c.base; + }]; + + input-field = [{ + monitor = ""; + size = "300, 50"; + outline_thickness = t.ui.borderSize; + dots_size = 0.25; + outer_color = rgb c.accent; + inner_color = rgb c.surface; + font_color = rgb c.text; + check_color = rgb c.warn; + fail_color = rgb c.bad; + rounding = t.ui.rounding; + placeholder_text = "password…"; + }]; + + label = [{ + monitor = ""; + text = "$TIME"; + color = rgb c.text; + font_size = 64; + font_family = t.fonts.ui; + position = "0, 120"; + halign = "center"; + valign = "center"; + }]; + }; + }; + + services.hypridle = { + enable = true; + settings = { + general = { + lock_cmd = "pidof hyprlock || hyprlock"; + before_sleep_cmd = "loginctl lock-session"; + after_sleep_cmd = "hyprctl dispatch dpms on"; + }; + listener = [ + { timeout = 300; on-timeout = "loginctl lock-session"; } + { + timeout = 600; + on-timeout = "hyprctl dispatch dpms off"; + on-resume = "hyprctl dispatch dpms on"; + } + { timeout = 1800; on-timeout = "systemctl suspend"; } + ]; + }; + }; + }; +} diff --git a/modules/home/swaync.nix b/modules/home/swaync.nix new file mode 100644 index 0000000..ed2cba9 --- /dev/null +++ b/modules/home/swaync.nix @@ -0,0 +1,92 @@ +# swaync — notification daemon + control centre, themed from +# theme-state.json. Until this shipped, nothing rendered notify-send at +# all: the theme-switch progress toasts, the CLI's font warnings and the +# live ISO's welcome message were all invisible. +{ config, lib, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; + r = toString t.ui.rounding; +in +{ + config = lib.mkIf cfg.swaync.enable { + services.swaync = { + enable = true; + + settings = { + positionX = "right"; + positionY = "top"; + control-center-margin-top = t.ui.gapsOut; + control-center-margin-right = t.ui.gapsOut; + control-center-width = 420; + notification-window-width = 400; + notification-icon-size = 48; + timeout = 6; + timeout-low = 3; + timeout-critical = 0; # critical stays until dismissed + }; + + style = '' + /* Palette baked from theme-state.json */ + @define-color base ${c.base}; + @define-color surface ${c.surface}; + @define-color text ${c.text}; + @define-color subtext ${c.subtext}; + @define-color accent ${c.accent}; + @define-color bad ${c.bad}; + + .notification { + background: alpha(@base, 0.95); + border: ${toString t.ui.borderSize}px solid alpha(@accent, 0.4); + border-radius: ${r}px; + color: @text; + } + + .notification-content .summary { + color: @text; + font-weight: bold; + } + + .notification-content .body { + color: @subtext; + } + + .notification.critical { + border-color: @bad; + } + + .control-center { + background: alpha(@base, 0.95); + border: ${toString t.ui.borderSize}px solid alpha(@accent, 0.4); + border-radius: ${r}px; + color: @text; + } + + .control-center .notification-row:focus, + .control-center .notification-row:hover { + background: alpha(@surface, 0.6); + border-radius: ${r}px; + } + + .widget-title { + color: @text; + font-weight: bold; + } + + .widget-title > button { + background: @surface; + color: @text; + border: none; + border-radius: ${r}px; + } + + .widget-title > button:hover { + background: @accent; + color: @base; + } + ''; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 4ef0cda..4beaa52 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -11,9 +11,15 @@ let cfg = config.nomarchy.system; in { - imports = [ ./options.nix ]; + imports = [ ./options.nix ./plymouth.nix ]; config = { + # The safe half of distro branding: distroName flows into + # /etc/os-release PRETTY_NAME, systemd-boot entry titles and the + # ISO boot-menu label. distroId stays "nixos" on purpose — it feeds + # DEFAULT_HOSTNAME and upstream isNixos checks (see roadmap). + system.nixos.distroName = lib.mkDefault "Nomarchy"; + # Unfree allowed distro-wide: pragmatic-desktop territory (claude-code # for the menu's ask-Claude module, vendor drivers, …). The custom # nixpkgs-config type can't carry a nested mkDefault; disagree with @@ -114,6 +120,9 @@ in inter noto-fonts noto-fonts-color-emoji + # The legacy-ported waybar identities (summer-day/night) use + # FontAwesome codepoints in their layouts and list it in css. + font-awesome ]; fontconfig.defaultFonts = { monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ]; @@ -125,6 +134,31 @@ in # ── Essential packages ─────────────────────────────────────────── environment.systemPackages = with pkgs; [ nomarchy-theme-sync # provided by overlays.default + + # Friendly wrappers for the two rebuild paths (README §3). Run as + # your user: `nix flake update` must NOT run as root (libgit2 + # refuses the user-owned flake repo) — sudo happens inside, only + # for the system switch. + (pkgs.writeShellScriptBin "sys-update" '' + set -e + if [ "$(id -u)" -eq 0 ]; then + echo "sys-update: run as your normal user (it sudos the rebuild itself)" >&2 + exit 1 + fi + flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" + echo "sys-update: updating flake inputs in $flake" + nix flake update --flake "$flake" + if command -v nixos-rebuild-snap >/dev/null 2>&1; then + sudo nixos-rebuild-snap "$@" # BTRFS snapshot first + else + sudo nixos-rebuild switch --flake "$flake#default" "$@" + fi + '') + (pkgs.writeShellScriptBin "home-update" '' + set -e + exec home-manager switch --flake "''${NOMARCHY_PATH:-$HOME/.nomarchy}" "$@" + '') + git vim wget @@ -152,6 +186,9 @@ in nixos-rebuild switch --flake /etc/nixos#default "$@" ''); + # Don't let boot entries fill the ESP over the years. + boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10; + # ── Nix itself ─────────────────────────────────────────────────── nix = { settings = { diff --git a/modules/nixos/options.nix b/modules/nixos/options.nix index 2b93139..9ed548e 100644 --- a/modules/nixos/options.nix +++ b/modules/nixos/options.nix @@ -21,6 +21,22 @@ ''; }; + plymouth.enable = lib.mkEnableOption '' + the Nomarchy Plymouth boot splash (logo + progress + LUKS prompt), + background-tinted from theme-state.json via nomarchy.system.stateFile. + Recolors on system rebuilds — theme switches don't touch the initrd'' // { default = true; }; + + stateFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = lib.literalExpression "./theme-state.json"; + description = '' + theme-state.json for the system-side consumers (currently the + Plymouth splash background). lib.mkFlake wires it automatically + from your flake; null falls back to the Tokyo Night base color. + ''; + }; + audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; }; bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; }; diff --git a/modules/nixos/plymouth.nix b/modules/nixos/plymouth.nix new file mode 100644 index 0000000..36b9161 --- /dev/null +++ b/modules/nixos/plymouth.nix @@ -0,0 +1,87 @@ +# Plymouth boot splash — Nomarchy-branded (ported from the legacy +# iteration), background tinted from the same theme-state.json that +# drives the desktop (nomarchy.system.stateFile, wired automatically by +# lib.mkFlake). One caveat by design: theme switches are Home +# Manager-only and never touch the initrd, so the splash follows the +# theme as of the last SYSTEM rebuild (`sys-update`), not the last +# `nomarchy-theme-sync apply`. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy.system; + + state = + if cfg.stateFile != null + then builtins.fromJSON (builtins.readFile cfg.stateFile) + else { }; + base = lib.removePrefix "#" ((state.colors or { }).base or "#1a1b26"); + + # Plymouth's Window.SetBackgroundTopColor takes three floats in + # 0.0–1.0; the .plymouth metadata's ConsoleLogBackgroundColor takes a + # 0xRRGGBB hex. Nix has no float math: multiply, integer-divide, pad. + byteToFloat = n: + let + thousandths = (n * 1000) / 255; + s = toString thousandths; + padded = + if lib.stringLength s == 1 then "00${s}" + else if lib.stringLength s == 2 then "0${s}" + else s; + in "0.${padded}"; + channel = off: byteToFloat (lib.fromHexString (lib.substring off 2 base)); + + nomarchy-plymouth = pkgs.stdenv.mkDerivation { + pname = "nomarchy-plymouth"; + version = "1.0"; + + src = ./plymouth; + + installPhase = '' + mkdir -p $out/share/plymouth/themes/nomarchy + cp * $out/share/plymouth/themes/nomarchy/ + + # Point the .plymouth metadata into the store + sed -i "s|/usr/share/plymouth/themes/nomarchy|$out/share/plymouth/themes/nomarchy|g" \ + $out/share/plymouth/themes/nomarchy/nomarchy.plymouth + + # Bake the theme's base color: RGB floats for the script's + # Window.SetBackground* calls, 0xRRGGBB for ConsoleLogBackground. + sed -i \ + -e 's|@BG_R@|${channel 0}|g' \ + -e 's|@BG_G@|${channel 2}|g' \ + -e 's|@BG_B@|${channel 4}|g' \ + $out/share/plymouth/themes/nomarchy/nomarchy.script + sed -i 's|@BG_HEX@|${base}|g' \ + $out/share/plymouth/themes/nomarchy/nomarchy.plymouth + ''; + }; +in +{ + config = lib.mkIf cfg.plymouth.enable { + # Plymouth wants the systemd initrd (also what applies the keyboard + # layout to the LUKS prompt) and a quiet console around it. + boot.initrd.systemd.enable = lib.mkDefault true; + boot.initrd.verbose = lib.mkDefault false; + console.earlySetup = lib.mkDefault true; + boot.consoleLogLevel = lib.mkDefault 0; + + boot.plymouth = { + enable = lib.mkDefault true; + themePackages = lib.mkDefault [ nomarchy-plymouth ]; + theme = lib.mkDefault "nomarchy"; + }; + + # Not mkDefault: kernelParams is a list other modules add to at + # normal priority — a mkDefault def would be dropped entirely, + # losing the quiet/splash boot. These merge with the rest. + boot.kernelParams = [ + "quiet" + "splash" + "loglevel=3" + "rd.systemd.show_status=false" + "rd.udev.log_level=3" + "udev.log_priority=3" + "boot.shell_on_fail" + ]; + }; +} diff --git a/modules/nixos/plymouth/bullet.png b/modules/nixos/plymouth/bullet.png new file mode 100644 index 0000000000000000000000000000000000000000..62249b3bd82b0b670a8a503a3bb69bbfe79cc4f4 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK^6z-MIEH9U zoIA;|uZcmx^}qZQCjEr9ou<6JoZt7m0FRe~3I-q?kdfVZu-@kQlF=GdcP{_{ literal 0 HcmV?d00001 diff --git a/modules/nixos/plymouth/entry.png b/modules/nixos/plymouth/entry.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7891792b8872db933f17a667a449a478c0be53 GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yV3Y&04FuSLa@a2CEqi4B`cIb_Lo1CD@X@-Ch2J0cXVbJ3tZk5>H=O_LuA|BAOCC zHaNEIWB8gpRxhIrP-q3h6ugFYL#HXzL zs@XQt|A*ck*2`WtwNt_B=Yyw}k1W;qd^{d$X}`l_w|(sWyd1OlR=bb4vobhzyu2J; zZnSHU?(>Dgw^zkpzx{Xds&+;O9q+{%->h5%r*w;NJ|6l0@y}PwUNtf>?BDuhVsPE} zg8wPcOLO0+EPi#6fx-T(fyDNE?akAVe!dFQ`p5hrGs6*K8wLlPp~KkoX?J(~!5v@i zWcGyE3NSoazmLT~cXe58{`dY>a%>D6be?{Bxz<`T_MTZ++WO$HQVb2Mv&zq@Pirx0 z4OBAy4vaL_64!{5l*E!$tK_0oAjM#0U}UOmV6JOm5@KXvWnyS$WTI_gU}a!%k#!|7 zD3LVe=BH$)RpQpLAVSa+oZUJsR-+c`p0rfC=y85}Sb4q9e01cG# AHUIzs literal 0 HcmV?d00001 diff --git a/modules/nixos/plymouth/lock.png b/modules/nixos/plymouth/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..3046de1b6a3f20d194f010fee671c936326beb27 GIT binary patch literal 1537 zcmaKseKgYx7{|AHUkq{cvLRb`y@V2`mnmXdjLEhr)$(pBuF;Fg7MiYcO37PGhEj}Q zjkx2~FG}6iyi`n^Yf__8QcEw2yZ-5)d(J)Q^E}@_p65B=bDrm%=QKHZn*rPs4uL=n zhyi{()JjnYT~}M3b6C_2wdkb;L@*(cWhOrZ3aPkg4uNP1iGDtmqZi-uNcB{I6qr4G z#i8)RDVJ5cL1!Nt{l+uGVeBIuqiPA1leLuzROguk=T?Ra9MON;!yO9zRCRCo6Z~dO zZEkEpocTDP92U}<@#4T*rj2a+(x$Pv@seyCNY93qf#zr(*_!G?;{%hf$7{)0wjI)D_X( zm}u}Ln8`jY-p@J5VO8CjI08(3E0;){?Q-|cD59AsumI2J9^x48{P#=1M@1> z`3H_14&4M@jmRG{e0-T%a$_};xp+UGO5W(w?k>lx9F;;;TAULlesb+OonM22Q!c81az}6c*t>v7~tOVPm zhU!UpisjNWSukBNpiE6b9Q+!olgTZa?zW>>3@H1~d|7^cD&u?XCD2;IcY{V&A|v%} zdgu$wb94J!*DI4eckMCCA2lAHSD>gLRdsonw|WzWbCTnPs>+EhLc?+2mx$6;Pv&90 z`0L*_g4}j}dR=HC;<$8$V8H_R+5)ZzLV8t}3+GKlEaK#JdXNo5RObC<{#qWnTDYlq z)?=4}W)I5KWoU07<8~IqmNFI8_hC%4r#B}#Xw19S$T7|LGujzt$Su_wD^oXACsp6Y z@KIlS8YxqxORtv@zY6`OX~XEy`ZfP$tM|_>py#PwsWV|^yI&|}5<#J-3naFWlPt&0 zMM1pA=0DF$cD`C*@wVbov-TLG5Xo`|Cp3#t1lw$AGfc{*?c{bf87cSbWy55&%tM%p zeXIrPK75<{GLPwF=XCHZ+fTLU^X`|rK`Cd;+jrMtGa(^cXr+} zgVU;=CUsvl!V_DzOK)XsWtP;Abb!{3qlSAP;8kvbDCe<{Y>C!!vz1@u+lCa}en)#) z;t*rZHexumT)tUH-d4SN1?85h(q5JM8U5Fz3%*u)MG1WJ{{KN~pSIm6Hn6(LH){pv ze0V$g{08_ag*>vOzNWv9Sb-lDoN*GiYdmB~>U7k^= z!sR4-SaIB}#^(07LNV=C+^Wxvp#$8peZDi2`#_iZs= zqu`0Tb?EN1+Q1#UGG4l`!VHb{C~R_k3u*CyVN%VraV9KWDT5xw*ws95W?`)tyZ4)2<_Ue8M2_Lb zaSg1=`gadV(~oTBd5ZNyUQ=(QUE*OKK)-|hxBH_7i+@D@94nGF6k#qnb#ZuUGt_nK Vg$$|X5%o`p5dDMwntbUe{{_St%Ul2e literal 0 HcmV?d00001 diff --git a/modules/nixos/plymouth/logo.png b/modules/nixos/plymouth/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..dbcde56164722eaa62573419b6d02943fa32c5e7 GIT binary patch literal 23303 zcmY&=2RN1g`@b!Fq=*hBLMq4Bu_8N>P0Gm12;~@cY*|@ZSs7VnMQI@%sbud>H0|+8 z9NYhXjy~Vt=iha?x~|9byvKdN?$kxe8o1eGy{&KCk$8K1pLKL~_OP*bw>#^0 z+aY67j+2Ds7>TBelA+I|`EkdjYo2MlJ4=3IhWy7g+{skVs}!PXTXK%we;QGHgVM;u-onaNHe9e;zf$2*xV7Yz3RA|%+P!v?9;%}+B^!`T?Ce7G^YZed1WGU*GE7f{ zYG1~8_Uu3USbCQ?X@E>X`uhrTMh$gz$pYO8qY6Thd8L90ad@_Lvx%$IbmQ zE5t=;Sf??(J6JtPQts04eSfUVoR}|7P>;%E;`X1#z$gkS_k*lg30vM0{3h8|VyrA`-06Q{jNkvKRA?>p0eH>$R$Y z#!;?^2eP`$Mn77r+ir@lLZAX_J2}KT`FZ+uyR>&*SkciV8M7M{6-_#OKH@$+E>)ju zy>aK|00&c|Tyhj)<&?4ln2U2aQd?p+4Fgc_>wE!!l$Jj(CgVoqF9K*C>`S~t7FzMA z&YNJf271uRa+SL#jivpQ4!YkhM;ZOeU8;I&326^Q53;ZfD;ayD-MP^u?@nWMaW_)6 z9-2)ocD;JpP_(N#sZ97SoxyQQMdb%is*&!!o-50Vd6h3y!is*(qx%N~Q>11Lr13Cu z>t5HRP8h5?wSSHBuD6e1aU+bRCDlLbiPt={zW$^^_;OV@YNsG7LgNBhlrkX{Ur#0y z(Q#0{(U5gSwtvez2U~~h{9d-h&B7vdsxru%%sJ3R%^~uM^{GBSBkHo3N8VAHKl54| zANjO&h$a50_+M~hA7XE$(i&vXnvhdB6)eYGzB2#CL-#5bVx`0B?C%F?*X{6Q({hg8 z=zcHpQp}A4mWqIo_LLGeC6K%AfJyKofiwhvF?S_jv(a$$hgI?3p`q|`+^#SNEb#*Z zWwil-vV&$uSkXi0OZ`g1!&HKPLw++$d5IAb8e3^)V@&p|A+<~u({eMt=zje(&tMY8 zkQAf5W4t}*+Kf9x&ZGqT9UYR#==$GCb&4#VwaUK|ShUN?ac;tI!=@Y7k?%Rnqs3^G#@YT z3+Ml{AAN0ji@ymp^T)x8lK$G#7g<)6nu>~;z2C~?66#WVtooGDSq1|G{hZ>E!@p%4 zQl5gu`HYPZUz2Bd0p^^R`}oA#N!i}daY;4Iu#3O9nKhKwJ`NU0+1 zN@AiTG*}icA0DTkKa%-G+%a{thOwNuU(0V68=c60Rj(V%S6X$-~29p zVMdvdWvK?EH;eyEz`2zNu-j;u=Jgg`R|zL2OZ<$u`716LODbd1>fDvjPZ?2#hMvre zHC7kLaNIGByu1n3px{Y4D-s_wEcdWkID&x^X?<>Bog z)2GF<7&&2p2c_AzcG=0Jc_9%I`MfnD$|ArK`dIEX4QhG=`&SnlOvv5hI(Wb910)V~ zmSEN?D%9b(_sN)G^v?wof!Dr$S^0=;Y{9rbKNn@-` z-fAfIA6F8}J<4b>@^tW@quYu(bNYnVtor~-F_!<4zgJwxiLwWI)>>SvwgUB(EsH-h zazP67T04v_S0-~{Wm@Mr3Sa8!o-73MYetzZxGziM&AQF5x?=`^OeRnH{1g~x zXHXAkiSMv|eV5c_hO&ku^-!kz2!A)CU9k(^q!-6pnbfJ_1?&G<1GnRw4`Hw?je~v8 zY*OX!ODJS{AMK50$$ihb8)6SfCUk-N$sJ#02Va}~vQh>z)|dM~GvMuJz_0v|AlHs;(ti$36W9?Pw%iAP4J`C~N6 zOZdH~220yUbIH~1(xwioJH)fSx&u-T;+s6Ttc!0bLB2jHIyb>Jf655;`NN2W_DrTq zLJp6TKQp;6>ca$2d_cD%T-~A**Bc)x#cc&NxhOVAfCSjGq|jV>;|ub~onz zqqr)|-KJ?YZx8o>{0nz;-bqG4;e+6HR97A+W&HJ4z+4A8RbYzhRJ@$?dt?@&lUtl^ z)t`5M??vkH^0wO<%aXojMDVXq!N6#+S{yPLG$lT+)xuM%ML&;BP&tsyT*i>#m-L>6t znw*5;4SP8XXQ_VxkRIBzYL0f|&$=&dEP)cm>NXR94hCnVGF9)p(jpoHFD%t3{S+Jn z-|@RIu8z0`Tep-}vsaMK^ICby+C={NU@+M_E<6V|ip9vAoxSBHsy!3PmiD?l#PWy5 z#vLqkypHX_9!Bt5c-H9@W?2RUd-5lwgQQLO>{y!xz-V5k!E=duw04?NIh8s0BMrW# z#gLxs1Un`bY8IhlRMzP|nF<`1dj{LdRfxEapPT0kF5!5^P0IC#ZWrK|#JjCk?yKlN zym;5E%E76>8|{0dnsB=3sXzmwK~|Is-%^`Xct|GH+`?7kx!szKE zgM6jeq+uf9Ho}3*@7Vm;NPh5K>Jd-AxtOt|Ihj4mu9g2Fi>mzmPc?5&T+LezYV=$N z!!;fXbDDR8n7GgDg+qa^#@8s;G!2uF^&Hz{TxTWxRE*W(O*mCKa%9puC@;4%LfiF9 zd7{~h@(_lkjzd29R8K_vq@-y@k6-g^z)um{yh^d{3XPWq_HcYr#;>T@uS?eq=YlSn zCurt-$Q`H~s=e(7;?($$FJVc`Izn%d1Nb@#B#1pNPG3Al7Gg!MGR2aXc7)L&Z&s2P zKqLtuQoqakwySo#9kje)CFLU*T}o-elc}073Q$7^`1OL}@@BmC(!47X;t}kr0x*W!y!L05^JD2kT#0)Aopc%Pm|~Ni0M4QEBA(Md zcNR&^fq8vkv1+ME6iWDwr5|c?aN8_m@nVIbb|G9dt={IF!oh^&FJn=-jBZ> zmR~rr<&S$k{p)n%Wt1BG1Od(xQ4=(EACL$}Gk<+{YOx>R0AqinWHLZs$wT*1uUWrT zIo%U+e3`UgP@^PuwVu{)v=*eet#@oyRtkNIH>BtqG_Jn z$QJ#u-iRHHIRD7DJx3jWr^QlLApYh5qO@8@ZChGa?!WWz(w7E4o*T!h=Qe6w`ERHe zr{Vv@DwWeHfQ>*k`4jead$DGfVKz>UI$ol$v)nl-1W^7%H#SV@a!jzs!JFhYhkvbB z2iRW3azt7i%O3TWTppM{&AX9YY3-CWjGt?kddkC{smeFua*$r+mZiTpmerBxzmXg7 z#=~A~S~W|%<-R8TX5cf}?zvx9((ixSE!GCaFrFy;8^f&y1hT77u?U47WirTXduDN< za&Yd_PhN&C21L_)M^Gy1ouyt>(WRua7!dVyHmlU&seVEFTz1#{5})0|dO{cF>!Wpe zW5oYsgV#@eMoYHL&-z&ntm1LV&c-YQE&2t2r2NN`soXEI`d9OQ<#kFs@zZ=_p2O$5 z%Bf|K!Bqbo!U9aWh7N8YgfPxA)rsx)dzWP&$ll&5z^|tSm(h}1lC->+Rt{FIjN@FR z7U?tacK`CUY zb6~`Sf>)^@ae+#v4D-6E{&i?KYJxSCF+P7`Rz+v}&B9*>qY-agGDAK^N{(;MU5WVI zA)!r*Y>~HLP$J%J;ofH5Qv(p%XCDb|`8cz^qRUs9j9NuQHqlCz#;oFFy1UQqLHj1nS|a`%~8_>*&X44{D}{ z6$uqBS(=tuu*ZkV)9seKFP|c)Ho|l{btx+oK^H)ZpP#j5x>dNwZYA98*86>|M6W_O zXbI?&Z(`nc`D3wODkl=EnTxFJ|5=oe{{Syv9zSq((xOfnqr;7mh$Ap>hRIt3qiT3F zdF6=aC1z<5z1JN=@iBS%d|N+=q-DGb3`P+9zIKw8M|SY`48O2;N)32-&68^g&>N@zp(SJfW8P~< z9|Q0ok9wJ^9f>X~WsW*iBi|);q~n>igez5f!^4ts+?&_3SH2+2Oq+L}WG_~;c50)F zrBI-ICxu`#O}3sQdN2mfRLWk>BUV!M_BQLH&zf^JmT!G3ZzbYKMmB?-lku61n_a!m ziJ!CXAH^Km_MIGtb!UFcsXmk{-kja*ALb~=B}ctYg>d5hZmHVi-;LA9?tte(M~_8( z`Eso1lsw6QQ+^I%P6g4a|;;@HX?Y^mGH!o)&My5}^ z8ED2%jMDgudK*P~%N{ZqWCSfzDg10&H7XuWKUDIWDB%$=G{dm0n!`BBwg<@YdUIIO zRNx&3{{*S0x{ZwWY~_?|v4ATN%kW|LYRg?kKrp_v`oKKZq$+UXed}*d_h!%)D>;8E zhVfe&%XdOwUx<0HOccOE#STlqpmq$-j+|bEqK*UHp&)&6CrORuhKJiA)oM|z6Pua> z!I4II-NbQ#~m^1l2?a6V${djir!iD7-Q=a=6R$5X&9W?$-Rd)Tv9D0lSG16`b|G*WL|b_g1T5hIfs3<8d|vs*a9h>&0@kUxU(2!uxr zFA0V3YF<`&t&0v)V?iR3@eBs_)D}lkc4Q+t3aSV}p8Q{QnSiM*VSiuRNhYA?p!tBD zKvE9wqYTKSp4@^JWycC%Fc2wqlXin>_G;mpX=au*)e00|XDZCGSt?`XS1tA?mZIr- z%aY(TN()Yu-F<)RKj$}AuJ~tPU3+$>M(NBa-k9HiOGwy+zWz5y#|}JV$>fjSA>00* zH&}evWY9a$K(nMR(=zxf1HTBR0yX)B)STjJjP~qUN&v&_qoh(KPaj>j{WO1Lxu)G| zu5HsRPA5@}V5fV2qkVFUJkwbEA8q3UH5>hq=Kjg6_uw!LV3~GqX{#m)nm^ZGRy<|U_Z~D&zdcv25#h>8&rG@OIy7-;@6K_){i~?6Wc!Dl|O;)c;smHtuurp?P zRw)*g3(o$qt-$Nqo1oisgqE=R!Q22Okm$&y@~=1Z3^mKPcrF(7e1*|6x{epcK%$XSI%rszmm<+vJ}`>T-7GU=3`qa~6srrVc7-?21PWen@X}l{2(X9;mEk z=SBOX{Gp0L^0>BT8?uYitC+N0@u#~SGJF_~DsHxBC6Dy zX~Uc6kf2+ERJ-72-PfLWwKRnL6r9={T2oM2m z{Q6(T5XLCZvtF;nJD*&)0ZcT#Fb};Yp?&DuYX>c_GF+)r?b@$tw2;Tr_@V_A-00me zf{XX9^uY6oajic!%kJp@?rvGl5~f2|P9nQA`O1=nC-ApscZi|A>6;DOj+{-7_5iKi z8-1{I*sLz8NGu}f2}&gQyn%g?l*VwB#`mxc**52pz9*EhIwXsCjU#zfrydwLyROL7 z9zBfW7XXj-tf#h_n0LJnx<X<6cHL6C`Dt_R_@4(M8aDn3$$rMJag4mZdDg|#ed3ZAyf8_N(k0K(H4qP8YI$*R zZ_EaXFe)zK>&iGo<@6<$yB$;0&w)G4eICnULH?A|)b`iu8qY5~sb&jfPz*>A3W8jr z?fB$g8P#fYRSM6&#|D8HgB&2x{xPI5$<}W#{X0jH@d{g^NVH-z`ZxCXrP;F zRp@rP+`<0kqaPD!4kcvr6fAU_~dD}_iF_gspVaegeny8YXPSBi6NQ@xk5_6 zVCk45M%zK((V=@rZ-g19I7IY|vefp2P_JyeuX945?@HSJJdg+?fx=;OQ*yaZx>Mk+hes;3$V@!$bg%lZ&(?v56jz!G^vL- zzZt&rQt&P7+uekfygDVnNhJO*iqMM{z>Sz<4I@&(0mgL~HPsX&-83 zJx@(UIVD6I}?7_1O<@!L!q9p z352;QUZV0z&g9=-fJ|nTTKkB=_NpqdVJ#skNC|wc z_l!(+L01U03SmHJ}%{(bffh*2li&C(e2MK4GstQwj^mJ z&Ku?}7l0o)M^F=#w=%wF{n}p#oB9dCbP=agelq}lNeNj}I`sgZ?u4_kyeKC6vkz>i zhu21u)*pptMYbJFS|eO9Nn5NYh*?Nub$pEEk9vZ&EItxaF&fVnF6W`4l7!}!?SVM~ zI&85fU`36LWOec7oRkY`r!cVAm(2jCWoM)m6#|f*xlCTIkx^y&T&P5Ek`=*l1XL3y zZ*?o3EOfdDoX5`!dA^_}Q)4*U9+WltCc->XiI+juWL1{`a1nl2*-FyZM*}c*AFlVl z@bCmxuWCo~!AIv|L*jXR9U6i#E#Ldv?G&)2UNG#>SUv@9$4*0FdsfY8+(u2spy=cw zE5yXS;!#eIU#7A01`F2AkjlAuI@wE0rgG#?_Yr*vu>L%v^;cLP z1gS=9`Pw;^AcUVRq!~D2Yg{{Cy4MfU+mF~9lYz!%Ye1YWyMBGUfGYAvP)H_Y0d-rk zFdVx_GA1E(7cKmcb+I{U%Rn+X-PC2=63JDf$8nCqhYxa`(muD9o86wTC$Sm2`V}QM?ovbiUA5h%ca296{=B#K zd#S;q3G5DIKHHeYmKi3b*`U%isbn=FQR3H7y7%$s#iSFU>4Ln#QSfFjEoTc-N5qY= z)$Ic3zDDKzNj3A-#3mS4iEPm%P0;(2_?&_%w{F;JU3aw8(nt5(xRVZHDn|y}i>Y6W zUA6!#|CwRsbA0(Di05X)I=TR{B-nd{M4VCp*H9zS<#3o;S!9wMP^2neF=&Su26Qf{ z6O+uJb7=rj>Z216w)<9`+MZW{IP8+HA+uRbFb~j+kPK>+RqM?L-%>1Py=;k|=__+= zDjRUzeUa7VqC3?chaVxL;*_VFlca6rYIkyT)XF6TIkKf>Jxny^=d6DaDN$_JG%4KR z*6hUg?2cZdLgi&Z-p4;FA503R<}l{&oQ3&;zAP>iwuramLzKX#{q3*Zl!Rj&QpmE! zOZ@uh)5)(aZ&XPe=Uv9IZm)nZ;C!%-ewBehLEa!C_%L^f|2|uO_pn?uhYSYL_rruZ zQZWl3Wf}LhIxvZ3NnPQ`iDYxLnD@Uzi}fBH)!O#)Gmbh;`NWB0h7WksQj+$&$I^~7 za2r=(I23!d^@w92`y9DLA3Go@cLUCdV(nE-8^Z%rOy(voHA`q0dZoj#EI?Gl*NWnQYT-D z-ns!;M&{d^EUgsND`^RADm)>ewV;f4DhrlJ{F<_Tf~O5*g85GVVIZ=dQ(F+_rf zcm;DsYbx%RTyjWs&jGOI`GjoxF*oCq^i@ywCz|nFJr3izQ+P)(s&4?m_h7O4#{htg zK1Yzc7!)71i@~XJfU`mgP&f7|6lHzS*F>!1!KUc?sS0zWJaiq(i7l3_4!9hn#TX_e z1jM9C``aCnq>Fyc`*q+$H{in_1vt+2PYm~XBynEYfVX!=Y|_KNzdW@JiFAg}6{~duP`&NG>%&KVq=02p^-RxOgPcF-jq}|7OaR?g1SG&p zvY^|BV=Xo!3~_?vRpRE2PH52H_o5$w=*d;AB$Equ(rB?Y3m95L>33ri z?rX=b7Y7mM?tl>Y1!w3;YhGji4Tz=IHTnfVx0%8_Y}v-JGfG9Og`@6JP(!k1pnwxg zK*3^Xs>3wp5cy)ha*Q1`C0|)Ym!PlHuy9RY|+l5s!%IWRPD*?gqn9btIq*(*wD zkYMd9dXw&VJgLsX*xX^@jXE|Rfo~4wQ3^bSoE3;>~{P@H3-?wkBdMWD;F*&mlEJ zFq37OH@SB(Q~mwS_VKTv9Gb54^@;3c&N*{lmAj1)y__n6qtSa#Y?v$%)+7H7Em~~C zFh7obKPj2Z;L1FSRFZy+YikAps5M5a8}2^d0`}7c)fRAu#y(83)*2ik>%FO<85G;l zjH0wC*Lzxf44??cynSP`K5s>1^oLz)0-3OqCc9Wu4<0iS;}~UYVE^}s2gtE;|7CIDH6xW_9-5T!dk{W0B}&;a=9o|yy8mr& z&Yu(gg3)deGGNKvEwS8=>F?+bFr9}GwX!w6gXeP=l_Smotrovwax_+~SWl3a!GKi< zn4S_tmC+JQRc{VFXb_|Pn7BvKOaA#Ls}%?~q3*pkMRbA zl4MC12Y_C-Ur-J};wAJK(wbygdMXUn{MpLcE0su~NVKehZv2_fs z-fg3P@IPq_G%i%J6{tI)6<31nI{69cGT`9y_h~uQSrK7_>Vd7*E-yvu$ZL!{sHvk2FtC7I7yat}9*T=yR z7LS9$+<3wn>ZUd|!;A>C>Lby$K=p4q|~kb)7gUxpLgh|(ZBk{DaXHf-O4 zlmLd16uA#`^b5WWMa&I0oljbK;I$(31F&ZD86iGN{Bn%4@PDyXcZS3qDsGaM&WyzM z*-b!z@Q!KdaOM-4)1a^p56VI(3e6xLv5(7ML%R{c2tdn*#v}q{1d1l1`^0VRgU0me z3aEyVZe)27vNP*G2wh3qOVCmD-m#MiXHf*s;LOGMz1X@R zHfy_EQv2J!kyQ*aQq@2MW+KcCofb0MaI_$qn=C{_j67o-7_F?P8rCk^lP|>q+V|7t z#Dn{EC&tga@JAmU*sJn%VoF>5e<`!(Rf{m8YtD3~6#P4s7tsuVGMhT7xQ&3-fLZLp zI+Uws(h|rFI+OU$zuCa_-H3U=pUi3|Z}r50v9536CvsQ#1owqx9L%yq8Ksf`-TKFt z{i|X#$l?(n7W)>W;r%=^@$e2uq7R6Qrt7AHHR8IZ;aK!2;uTDGTWYt@f|y3|; zv+&0fBh}s#a74h;KBTJ|jbg|iWdDo#Grg@VkZ))~O+Rp}AOPU!a{_fOFDFYV5@IM8 z&HmQF&NQR?T+Yycs#2DwmpLgBW+})7YxyI_B1`Q93GMgMBDJs7-FHS1O|zwfVUu!B z+v(RpQDG6y1;@48h`^p;O=FaG!h|xX?rZNelU=b6>0&1Yl1qTM+<%kEtbyJ-%ScG# zc@v~T`%_Z;C2u1*(e@Il=UNw-H0i!`ML>j(>`t?g_+un3h*e69Gzl6{hf0bCGa?Bj zk9N=mO`P@nB~WYz?jWx(9#j zF~wsk<1`mkOY}ZBqxn?}_vdkB1wL1V;8w3vLB8xA-D2$pvlub~k_o2RUix!K3SYYG z*y)4!vQ`X-Q}LVC!MBGhF73sOta$7ZE9w~r^k z`;l}6bju?2qMI``ysg+Rdf&g|K0CxHTNo1!x^~(WJjdlL3(OU68T|uks|zf!Z(_n( zbwvNPz`26YTIgK5G>YCbdq+BQOS17e5F=txP#XG~clqv#4Oq%P!+?RX=9stl7;gpo zi9*#1%5d7nw}FcP5+iTVJYv&9dd!+a~!NC4}{{dd!G|Aj`eSZa6kW*6K)f}js+)G20Dniu5L}+a+X*r4c$ksxY&_W-0a`3dg zVnYAFqi&WWw2;telX!V^)n92$x(DerVsOo&?7?Lrv)*l+Oi>6A3ri_=76D4@U+;2x z?mqCDg&=?0R`A?)x4@nq`+lh74ZV8K8Tz#`aL;AoO$-us16@UY{^J?4s!V`z3{JR^ zVf?>aD5MPhE4p4HIucCOl{6)36F)n#nGS&g{`LBIAzs8#@po!L_5kO)5S*m!_3Zyz z9}x=MG)EHe5c_s36n9d~?rOptuX5Cm|0Q90176-sKjEC`*1s_Yn>)@X`(Z|wkPsRP zXR-8J-5^pbSLqTI)b=5s^(Xr&EolsJC_6>8IN9Wi~=Cqdu_$-pi9-rCI0Ne03I zRFf=>CMstDw`0ehjd3?&a$teOq6?g%3t%4L*u}uHvpCYpa&9kz4Z0DgC5qDhiMWPR zdKwTHDR3Jq&ms>}@O^&`bE9u_E`R0=kP`rYA-=Qu@g!>M*d!OAc`u3>`D_rZ%54OS z5c?|NiBUBUHru2-%t8YxIMga>pp4@_dUjU>_(w<#0ob3YOPCQ0_GM!=_C1FQ zIdB6!gc1(waMlp3AnPifM)#mIr)UwfL{=dd6oRiSf&v$oXt2pb@y#2&g7Wr=S5~oC znW{pZOoWF2>A#+MHmKX$_6p+tL9K|b+NOVDTyuv^#@ZMvcRD7)*4IAsAwgste!Eod z-{9gzBC=L^U4?G~$^@hT78+GB@lePio42r%!GvnWf>y(L&`8c0J)eMN4^9*`Q(rvD zlXOTHqmXlpaK4 zz~{l6f9`8Ty2lQjPw&sR?upkx3cFlodOlsHckQvM9gn5uiO7>4YYn(n?#m{F@{vH6 zh@$t3GFv?f{e^jr5kJyEM2l}z>gjMOatl&oMO~C!mPw1LMD1}u)Q&$x_H(vMWcZiG z5fU8|f8?}c3S8sgQhBt-7g*$vblk>q4h$usu44KpxuH#G}@B zQjrUU3ve|6dJQ#`VGGBrOtQS-ux@*Sn3wTQSx|aG35A#f`}ROl6}+L41ffORs+Glx z9Pf_Esst+RMo@CofxIYm>dV$6oG#_I%A`7R3wHP^Wj|DP)*sepTf_Fw^1oL4LlKO6p$qB^(b_F>j@yu&wER7?QBul5u6-FhI7@m zV!%!~4g{zmXNQ(+hJHL#NAPiw!+C%g$MO+cbRYYkTqd0?f>F0>=&ik>1otSDZ$HmV7d6LUe0 zNPAxP1bqSBIPT?dS(?gO-U?kZK>z0{4HqNvdd6$$UHCxYa^oV-!g*TV#@A0s1Ro|%0Uy%Nt57( zbYInzlXch=^qKVj3L|`{dX6a`+E#+HgpTl!jmOqnwzOk7=s%pZ-p{gBZa;$_lMRK+ z-9*@#i%aOEgMm^QgFm!Ze=dNJMkb+W|PMlbP5MuwyFCYnRE8ToHjTUO{kcZ;2>7lbXttw_! zD|VQ=mGbQjV;(%1*Rxk(m}k#a<=cZYgOOQi)Hn&;PI&kYX^k^~1&0+Uhx6L5(XFca z#M^U}={?=ASDY?!AHIF>)YU7ElOvQBMg+sCi^2wxVq7u|rc6k;Qa;JQozV~AdShJ4 zUXJW>%tg_*%yK7+_+xOgX06pF#$ z8tM^--h5u>gKVf;fP)6x%J zv+sWeGD*dCmXEhCQSH)nZ83XWxi82K74Mw-c<8m5(FFS|DjB=#M5KaQS#AYlZ#|ebBhnk7o5SD#c`?%tb z27_@@PEExgwbplGgM|rp$%A*3?wo4dw7)?3h}W`ugXr(cq2EP7HIzU#M=}elesxH@ zhmh9JmAti=LvFM&o-mqxp$Mx`8S*QoktoCuYkbc|8*C0j3>L&bjKrJ_6=IEg)XM->B3=qe?XTX6?EQS z{Qj$BX_?k%ST7yJTTa-kNvBZ3?nzPEz(e~e3EUK`VZQuw=G#7V7==JYL* zJvZ1NXp;Lai_B@DAyPR~{~(JIg{t`xiKSxS)o(F3&rj&MDz{3=yQMny;{NEb!PPsC z>Myk>B_aojH&kecZdz?IuO_qd&(--->07v8BRl4Y2p0TbB zB7olqgQK>ZW&?MTHZ7NU7A0DuG{2KftKf_{QH zLz9zmO_X_FoDZjeusL~?sXD}@N~9-4<7B)MVdx25#BjKy06h?SQXzzyd@0G9tUi_7 zGwN^!xo%z^Y53i5Q2xm0@dWOvf7Y3I<=D;H`rVY9h_vaF4Gl0oyKNZ%3AsG-_v&FH zrZTAa^OqIx8`y`a@S=9MU#N4u3|wTEkBy3pM7exq*PqC)KcSjM@I}!yr`tk`6DuHz%Lf>>(v;+cM)lohD$-ir}{NTX+DJ&IRtHp9#=__ zs{Z4jj9Y@{f+Fz}Zfdwx^be7c-kd*&3oq2R4Ht+tAr-l?^d^qMpp&GyhW$v2+dZ?r zT!vf_o+5SDCWk4{NR&_y#s6KifNo-N63EpdLYYlt-^69_=+CyV9po=wDM-Ix%GN(X z?f_lELV=s3ToxOe3qK!S#aWIiK-Y2dYmunPAIUe#c0VHR;Cw}`7M2q3+!dUrkO)C6 z!Ha8}ZbT$1RpBu3-HxZQV2KADzL)`Ywcn|Q4sllKd?j8Rd`A7{AY$gTxO1MnY4Wa~mhtc0x5+>rcduW;v@ z!ip5haZ-dvRm>-iH45_?o5hRSP^H3DN{ZYoBX^YKi27k%3#Tjl_gUdN{JjQS)TQy( zbHuLhiaT&OEIx~5?*Z=T8nL+@E_zkP^KnM2-d=iR#dB+E<2sm{WPr#NC=Hsrzr%I2 ze~!+p4f$5#kZXSWDLNs~HT0gUSwqjFB~_zj#R}(E{&VYD-DyoO(Ef`$tySo|ykqrx z_6EnU^yCM)CqlW*p2HsY%zFt1r!uf2533&rcwzfB8jqr=;B`fax3a;XXena#;(uOIi9YYrN+(Oz#!F#MUD@oyO2rq`rL9;c z^CTXhklKwUcz4W9d0#lxJL`Yeu+00*lI#-K``?7d^*2FrA3LralX2&vX^zvQs9sr5 zLjSSBXzWH7^_SsNcYEW2Z($qyb{FQ+s?5rsQum_%@lG^5AxDw^o!a)xl>--RtTxT? zCTU&8wKqTZdEjf(A9c1_p_cCmZ7dBb-9L73Wsd_BR_L$P*EX~ks9i3xe0v^!hy8t5 z-Q}8lZ{`!+gbTAMzl`5^x2Iw@Zdjix*k!j75DI2KyDovf`rg%&S+a7Wlsxlac4mIcnV+1r=XmE^2wJ9g7OPh?X%5Yj{lo(W zEbLW_+dU*tXRslJRm^+9t3gR)6gVETViJE$vy0OUQQF>e2J7MIA?x2~Yw?N;%w76c z&O3X9vIe^3OOX#d+Qh66MQ8J`D)Pu`ve@EYhXuubKx9135!ybbNnZYuG12=tJvZE# za|~Fx-f}uXD)5xP-IJ-6*O#gtO72qK`x!``pMJ^All zaW>&%mC|o%KmF3Wm=%l4KcH;7#@(eFD6*Py>6(;U5O>j1GOVk^yRhx|oLi3b%>(`C zo`+>?zsLdz>ePSImn)Z~Sp>AnnQ;#kAk^#?)b*Yl)sk?`ZtV zl-E-f?+sEZIglLp;;#JGdmwr*QTC_oL(jq=9Z6<&ezet#G>=mGE>(P z**LR*W;U^%mU}zT6Mv}gkZs!FEk7Zzuc5VN^{-SYcsiN6L2=vLeVUP zs^vgdtUe;siWf}Rc9i%=Gon zF>&Vzh8dBNo#sR$6-krH;r~ZMlmwvUQZwJ2%Xn4gQ zgXea*80C+Wja;~TV5;+8M{D5g>;jts5(LRn`9UVI5jMqkhk?7^Bu$#hiPtoKX20|~ z6U|MYPWSRBJ*tvwmZUvs##xzTTV);{qrap*9==4CDmfa3PqEI9${+szym9D9=yE?n zc~BKU(F8`q_&?KWHUy>Mf|vjh#`@+;Dd%N$wfwncqc2@M&_B#vNBK?|!Gz#8N9bMA zSBGWLUL2LIR~TMx=7d~y*$o^DV~WBO}mu&jIup`lP5aU_Qi|qPQ&s!X|TKG z&*YcQ1y?OmvJsLd2l{oD-%_l%4wsz@xk3Ot^p#MT| zQ{B1KMDSiTk-KS@KoF%hbI=NXw`}+DU_A+(OFRJpx#|DZ;gA>OT06%HHqqdzl$sK& z_tam`EA?8Dci|!#V=U@Zspn>JC!>chA%oy?+)nnyssdTFCVmFLIdrG(zHX)Ud=PVS6)yPom$Yhf83~Zsql5Q^ z&&%uo2K5*p1xV8L)zSE#h4m)9rK(oQPC3I0kTh9;pFjL;`&0(Ir_KB^ABb*OB%ihV%A~oei5SllAad(U3k_ zgE!C0%v4mRpVK5|91lE7%b2SuW_6I(ctavutQ9}?o7r)wPxQy`{agzF=VH)RU0*5TSa;8X0;drK zeV*d(hLs8*PKWz5W{x|m<4*@DKkW!}7hUc^@RUCbk0@nyHdh$ zFFamrWqxRM!4y-g=azoR;I>0ci`I+*%0@iDg}orCdnl;wZyU@XvyWkYPpN*PiFOi?to3}(Qt)Ao z9g)SwjJM);c7L>All{_Q*Eg2Z-)pZRJkFu5;0KBereoF#jm13?Q#(GVZB!oOe)U9e zn(8v=wEnb=$t;c^aAi;Tcr$1|cvIa5L?2laC|>X*vU&UQ=8b#KLEkeHTT2f~khP_p ziuGUGCf{>Ap*KxZA4P|{hj!#S^{v2Q+ zH1Fh5dCNnq$LV#ZU31k>nCg`|jMAp^d}Opaobl2pGs*Yxy6of(5%cQg-i0=7byZW( zEM^{O8cW#BpyHqH6#P(jv^wo|tWqvMLD;%8ZTQ-$fHxVJ+PP;qLN7UAc$!sF$=<)D zy+dzl^>TYlsN%fVVkGKA=>cU9Ic^TBwYa}9#d%$%8*tkTiM=U;ZY>K}dZN*+c{ac8 zq>k3J5XbPs-o5|V#FfWGxp#3h(=b_zZq~w;knOcRmYcDal(F81WOQSYu~ig}rLsia zN(_b!1~I8A6q6~T+}sdjFeDO2!o(=+wI%Ow-21ujJAXcNo^#G~o^#Ikdw$RJ{06q_ z8Kl40I@LfFbk^Wv6<6yHg+SH7gY_HmFMB(C4Dzo@3hxwl7gA0GTK5{k#{vMdm&>|Z z!JKa@w(DfoVA1Qe;Sm6%?lLMmGuOU|CFsE~+ot)s3%^3{QeMCfe-y6qyVn*3EGsxD z$Yo{v7gHKtwje9zF0;8*N=L6y*g55MYsBefit#PCXg7Py9cIY>nX6|2bl7s{%#Yb^ znASo5Rp*lRQ9jA!a`N}c?(!3~HZ3_T$%trB=Ey4PMG5@KS#uYOV(s<2n=l5-2!48j zR@maJVGwc>Kw3f%X!1L`zT8gLV}|XIieL-0wuaCj2`NNR@km%4wKD#Kihqs>_M&tu zyDfIQ@(tTBNBQq^IBp+%p9KFl!RTvqZ{&wZnQb<+=8q^u;U2O?(hy&7QCoDXoxY?p zJJvC8+fFy=qbJ1XF-6AR?x;;sssjylJ^{R|tC?Qczk(|FM)_zyX>T|~7%!FTA3Gb= z{a9musyr7IOCG_vOLuAJiE%Jtb$`czsCaVNj}u+B@L#lu>I|9Z%0}J~zegFywYFU8 z)7j~-jkjbnAOh5G6*T8*mj>%VdflM5lw(XtX=b^`1BXSKGZRn<`D70?@vOsMxz2=9 zl0|ZpPYx5BEdo5m_*Y#;3GATNo=f^nbZGSuRg`q zeXIAft|NN?hBID3Rj3{kqju@59oGqYc;L7Y`Kg=?BGGQ*(Q;P$%@f z3UbiQm4IH@H|yU&xi5%$YK6db7$_Z=P1ZMp*MuT8n%QU0*-BQ%SKOkhxiF_}uCfBq z4C#pNNei^k&jgoLW!I3N)YyXmhk*rDbLtSm3xK+u+W5nAf}x0$sIlSPiUC=FcA3^> znU&pT04b&h_U)cdINv|{yBr8ocU41oV&iM~0=<0PIw>X&Q6Tf0tA!eYH|P=7wXYkX zMiSU+oDx}I;khnnMAUXSOysUwk|AS)l&O7)!NFMp{Hi*Q$E7MZdw$yDZ8+HZ?k7ul!SPCg@Dd;iUGHTUtK)i46J zfm7lwAihjhjw4c27QL{o{n|M` zhiU@OI(*zQn!9-Sfv+@{dw;c!5MuVqKf+tHwySQDRo+T#6Q+m$XEuoRSd-lIVzKfg zFJmil`IR#hj<`Q6lJ#Bef9Me8m$eK~&2tFyTyiqKaD_>GZFgfk2l-zO~Jh zds-6YYN1Nyh1y)LAI@c00&V7Bl157mvhy$RZ%;cJgfJ< ziHAi6Fe-xZ9oa*Z@AlC*SAl#--Rk@#@xdjB4b$cwz6AFp*ky;t?~=)t@gMZVtoe>= zk&5~*4?ilR?Q|;p7yRlpp9uUc)3PAs5j|o~UpuVB(#;Ks?@?L(o&5Bi3es|-J@LKu zog9DBatf}{PRs=1E$*|r)91S(x2X!sw*KpzdrK{LL>tVLAe^q z;DmBWPxul5^M=Lh5zBn%U!6jKV!Q2KMu^>OGp`Xz%5v1%a(lKu}tnUly{mIS>-_wH*2+bcq96ZScsBj^R zJSdm+t#PGxW~KE31*e3`KA}<&2R2$e^oudEut17e*EOKA^h2GPTfcDr<;1!?z=I*c z6e*O3jHfF&?o+Vr5Xl3Bz(POeB6kOOFI~WqY)89M9j+s3S4+sxNrq5+)gme+LOAIV z=%IynUi+zDu}R>Mylo?t;ClX*=!h~moHQ8sC{C$HPcT!Dgjx{G&Fr4TeqdZ(w|{$` z!zb#hN9L!mDzr?t69{TQZvoeVzkw@&@94A`E?1$L}Mm$3?u@dM}%W||H`oCbRT z&QS3U{Zp8QjMfZ=8BP2Ra(SvEO}yIAGO~==1A%-m1^G_NPVZfp+^<0!St9#5yqQ?k zYJ0lzo1L+9A7%beHs-^5a_pz$Yrwp^$^-F~Cug@UI&q;YXh!Ka$@1QE6o0?}m~Sx0 z+4xSPW;SAqQ;pTWTn+IOo zM+X}*PT!xu8))RM=dH+_zy>R_g4$!+K;K}MkJ8#oRc0^9nMW*rm-B}de5u6|90M*bk_GjL_aQ%2$M9eBrM;5!vNo%>{g1DtpYfcRgArSh^ z|6x;WmOS6Cb>i6Rqdym@ohS^9vd*t2#^i;DukogMFl;sFI+;SL$?QeL^8?Ccn<+fo zIdsApC!za>7`@=bSGF7FU;OYD+!}=_`CR4K$dQyZ>BdxQeJ`R^>`a~COG#5q_{Z%L z4%ySbA_frjC~GZL+-C*v@k;VF7T?J~Hn2EvArr&)xj?RgmH?WcTqF>3PI3w=8u6$} zZzNUQ`c$@^@R?iz)%+iVgQ}7an)@J`qcok6|K>y)V80yI2CxDH%;by9V&C*h zqpcC<+Z4rm?(b)TpXVdMy&hltF(3#kOQ)x3_3gflANT!lrFp|o;3IMX)%-o=SHrR_VuP3m@IHR3=ay-P_70|NI+l3&Tyh z8$b!`;^X5~x;JbHJ0c;`dIku&%MAo3r47wERv{??74j0BE++>708rZ5BE|;U9PdSGqaDo0uhSz uk*h|pwIyYmyu7p61G|hf1m`7ux0?Q`7iO7t#vg2AErhXkK2muYf9=2QS}DQ+ literal 0 HcmV?d00001 diff --git a/modules/nixos/plymouth/nomarchy.plymouth b/modules/nixos/plymouth/nomarchy.plymouth new file mode 100644 index 0000000..4ade91c --- /dev/null +++ b/modules/nixos/plymouth/nomarchy.plymouth @@ -0,0 +1,11 @@ +[Plymouth Theme] +Name=Nomarchy +Description=Nomarchy splash screen. +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/nomarchy +ScriptFile=/usr/share/plymouth/themes/nomarchy/nomarchy.script +ConsoleLogBackgroundColor=0x@BG_HEX@ +MonospaceFont=Cantarell 11 +Font=Cantarell 11 diff --git a/modules/nixos/plymouth/nomarchy.script b/modules/nixos/plymouth/nomarchy.script new file mode 100644 index 0000000..216dac9 --- /dev/null +++ b/modules/nixos/plymouth/nomarchy.script @@ -0,0 +1,271 @@ +# Nomarchy Plymouth Theme Script + +Window.SetBackgroundTopColor(@BG_R@, @BG_G@, @BG_B@); +Window.SetBackgroundBottomColor(@BG_R@, @BG_G@, @BG_B@); + +logo.image = Image("logo.png"); + +# Calculate scale factor to make logo ~15% of screen height +logo_scale_factor = (Window.GetHeight() * 0.15) / logo.image.GetHeight(); +logo_width = logo.image.GetWidth() * logo_scale_factor; +logo_height = logo.image.GetHeight() * logo_scale_factor; +logo.image = logo.image.Scale(logo_width, logo_height); + +logo.sprite = Sprite(logo.image); +logo.sprite.SetX (Window.GetWidth() / 2 - logo.image.GetWidth() / 2); +logo.sprite.SetY (Window.GetHeight() / 2 - logo.image.GetHeight() / 2); +logo.sprite.SetOpacity (1); + +# Use these to adjust the progress bar timing +global.fake_progress_limit = 0.7; # Target percentage for fake progress (0.0 to 1.0) +global.fake_progress_duration = 15.0; # Duration in seconds to reach limit + +# Progress bar animation variables +global.fake_progress = 0.0; +global.real_progress = 0.0; +global.fake_progress_active = 0; # 0 / 1 boolean +global.animation_frame = 0; +global.fake_progress_start_time = 0; # Track when fake progress started +global.password_shown = 0; # Track if password dialog has been shown +global.max_progress = 0.0; # Track the maximum progress reached to prevent backwards movement + +fun refresh_callback () + { + global.animation_frame++; + + # Animate fake progress to limit over time with easing + if (global.fake_progress_active == 1) + { + # Calculate elapsed time since start + elapsed_time = global.animation_frame / 50.0; # Convert frames to seconds (50 FPS) + + # Calculate linear progress ratio (0 to 1) based on time + time_ratio = elapsed_time / global.fake_progress_duration; + if (time_ratio > 1.0) + time_ratio = 1.0; + + # Apply easing curve: ease-out quadratic + # Formula: 1 - (1 - x)^2 + eased_ratio = 1 - ((1 - time_ratio) * (1 - time_ratio)); + + # Calculate fake progress based on eased ratio + global.fake_progress = eased_ratio * global.fake_progress_limit; + + # Update progress bar with fake progress + update_progress_bar(global.fake_progress); + } + } + + +Plymouth.SetRefreshFunction (refresh_callback); + +#----------------------------------------- Helper Functions -------------------------------- + +fun update_progress_bar(progress) + { + # Only update if progress is moving forward + if (progress > global.max_progress) + { + global.max_progress = progress; + width = Math.Int(progress_bar.original_image.GetWidth() * progress); + if (width < 1) width = 1; # Ensure minimum width of 1 pixel + + progress_bar.image = progress_bar.original_image.Scale(width, progress_bar.original_image.GetHeight()); + progress_bar.sprite.SetImage(progress_bar.image); + } + } + +fun show_progress_bar() + { + progress_box.sprite.SetOpacity(1); + progress_bar.sprite.SetOpacity(1); + } + +fun hide_progress_bar() + { + progress_box.sprite.SetOpacity(0); + progress_bar.sprite.SetOpacity(0); + } + +fun show_password_dialog() + { + lock.sprite.SetOpacity(1); + entry.sprite.SetOpacity(1); + } + +fun hide_password_dialog() + { + lock.sprite.SetOpacity(0); + entry.sprite.SetOpacity(0); + for (index = 0; bullet.sprites[index]; index++) + bullet.sprites[index].SetOpacity(0); + } + +fun start_fake_progress() + { + # Don't reset if we already have progress + if (global.max_progress == 0.0) + { + global.fake_progress = 0.0; + global.real_progress = 0.0; + update_progress_bar(0.0); + } + global.fake_progress_active = 1; + global.animation_frame = 0; + } + +fun stop_fake_progress() + { + global.fake_progress_active = 0; + } + +#----------------------------------------- Dialogue -------------------------------- + +lock.image = Image("lock.png"); +entry.image = Image("entry.png"); +bullet.image = Image("bullet.png"); + +entry.sprite = Sprite(entry.image); +entry.x = Window.GetWidth()/2 - entry.image.GetWidth() / 2; +entry.y = logo.sprite.GetY() + logo.image.GetHeight() + 40; +entry.sprite.SetPosition(entry.x, entry.y, 10001); +entry.sprite.SetOpacity(0); + +# Scale lock to be slightly shorter than entry field height +# Original lock is 84x96, entry height determines scale +lock_height = entry.image.GetHeight() * 0.8; +lock_scale = lock_height / 96; +lock_width = 84 * lock_scale; + +scaled_lock = lock.image.Scale(lock_width, lock_height); +lock.sprite = Sprite(scaled_lock); +lock.x = entry.x - lock_width - 15; +lock.y = entry.y + entry.image.GetHeight()/2 - lock_height/2; +lock.sprite.SetPosition(lock.x, lock.y, 10001); +lock.sprite.SetOpacity(0); + +# Bullet array +bullet.sprites = []; + +fun display_normal_callback () + { + hide_password_dialog(); + + # Get current mode + mode = Plymouth.GetMode(); + + # Only show progress bar for boot and resume modes + if (mode == "boot" || mode == "resume") + { + show_progress_bar(); + start_fake_progress(); + } + } + +fun display_password_callback (prompt, bullets) + { + global.password_shown = 1; # Mark that password dialog has been shown + + # Reset progress when password dialog appears + stop_fake_progress(); + hide_progress_bar(); + global.max_progress = 0.0; + global.fake_progress = 0.0; + global.real_progress = 0.0; + show_password_dialog(); + + # Clear all bullets first + for (index = 0; bullet.sprites[index]; index++) + bullet.sprites[index].SetOpacity(0); + + # Create and show bullets for current password (max 21) + max_bullets = 21; + bullets_to_show = bullets; + if (bullets_to_show > max_bullets) + bullets_to_show = max_bullets; + + for (index = 0; index < bullets_to_show; index++) + { + if (!bullet.sprites[index]) + { + # Scale bullet image to 7x7 pixels + scaled_bullet = bullet.image.Scale(7, 7); + bullet.sprites[index] = Sprite(scaled_bullet); + bullet.x = entry.x + 20 + index * (7 + 5); + bullet.y = entry.y + entry.image.GetHeight() / 2 - 3.5; + bullet.sprites[index].SetPosition(bullet.x, bullet.y, 10002); + } + bullet.sprites[index].SetOpacity(1); + } + } + +Plymouth.SetDisplayNormalFunction(display_normal_callback); +Plymouth.SetDisplayPasswordFunction(display_password_callback); + +#----------------------------------------- Progress Bar -------------------------------- + +progress_box.image = Image("progress_box.png"); +progress_box.sprite = Sprite(progress_box.image); + +progress_box.x = Window.GetWidth() / 2 - progress_box.image.GetWidth() / 2; +progress_box.y = entry.y + entry.image.GetHeight() / 2 - progress_box.image.GetHeight() / 2; +progress_box.sprite.SetPosition(progress_box.x, progress_box.y, 0); +progress_box.sprite.SetOpacity(0); + +progress_bar.original_image = Image("progress_bar.png"); +progress_bar.sprite = Sprite(); +progress_bar.image = progress_bar.original_image.Scale(1, progress_bar.original_image.GetHeight()); + +progress_bar.x = Window.GetWidth() / 2 - progress_bar.original_image.GetWidth() / 2; +progress_bar.y = progress_box.y + (progress_box.image.GetHeight() - progress_bar.original_image.GetHeight()) / 2; +progress_bar.sprite.SetPosition(progress_bar.x, progress_bar.y, 1); +progress_bar.sprite.SetOpacity(0); + +fun progress_callback (duration, progress) + { + global.real_progress = progress; + + # If real progress is above limit, stop fake progress and use real progress + if (progress > global.fake_progress_limit) + { + stop_fake_progress(); + update_progress_bar(progress); + } + } + +Plymouth.SetBootProgressFunction(progress_callback); + +#----------------------------------------- Quit -------------------------------- + +fun quit_callback () +{ + logo.sprite.SetOpacity (1); +} + +Plymouth.SetQuitFunction(quit_callback); + +#----------------------------------------- Message -------------------------------- + +message_sprite = Sprite(); +message_sprite.SetPosition(10, 10, 10000); + +fun display_message_callback (text) +{ + my_image = Image.Text(text, 1, 1, 1); + message_sprite.SetImage(my_image); +} + +fun hide_message_callback (text) +{ + message_sprite.SetOpacity(0); +} + +Plymouth.SetDisplayMessageFunction (display_message_callback); +Plymouth.SetHideMessageFunction (hide_message_callback); + +# Initialize progress bar immediately for normal boots +if (Plymouth.GetMode() == "boot" || Plymouth.GetMode() == "resume") + { + show_progress_bar(); + start_fake_progress(); + } diff --git a/modules/nixos/plymouth/progress_bar.png b/modules/nixos/plymouth/progress_bar.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb9fd74fc853d8a79ff072f5070cc660ea11897 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!xfq#&q>16jl|V`%*(1o8fuTx`fuW&=f#DZW zsNn?zL#Y7+!>a@a2CEqi4B`cIb_Lo1CD;OdLR?pFy!QY9|E)n1Qa};LByV>Y#{W#Z z_kbMs5>H=O_LuA|;<}1kzlXd83h{cnIEF}EPEL?uW#(sKY-3>5a*dk16jl|V`%*(1o8fuTx`fuW&=f#DZW zsNn?zL#Y7+!>a@a2CEqi4B`cIb_Lo1CD;OdLR>ZVoc{m+e`?yP4?q#dByV>Y#{W#Z z_kbMs5>H=O_LuA|;=1xB(>$GkLcE?Xjv*44lM^IZnfVzQ+ZY(NT;t{dB~(jXBT7;d zOH!?pi&B9UgOP!esjh*!u7OF2k+GGDsg<#zwt<0_fx+)>UOXroa`RI%(<*Um=z9HW l15kqo+=i0O+|=Td#M}bhdhY(aoDbB);OXk;vd$@?2>|VqQP%(f literal 0 HcmV?d00001 diff --git a/themes/summer-day/waybar.jsonc b/themes/summer-day/waybar.jsonc new file mode 100644 index 0000000..a45b8f5 --- /dev/null +++ b/themes/summer-day/waybar.jsonc @@ -0,0 +1,92 @@ +{ + "margin-top": 0, + "margin-left": 120, + "margin-bottom": 0, + "margin-right": 120, + "height": 60, + "layer": "top", + "position": "top", + "spacing": 15, + "reload_style_on_change": true, + + "modules-left": ["custom/launcher", "clock", "clock#date"], + "modules-center": ["hyprland/workspaces"], + "modules-right": ["pulseaudio", "network", "battery", "tray", "custom/powermenu"], + + "hyprland/workspaces": { + "disable-scroll": true, + "all-outputs": true, + "on-click": "activate", + "on-scroll-up": "hyprctl dispatch workspace e+1", + "on-scroll-down": "hyprctl dispatch workspace e-1", + "persistent-workspaces": { + "1": [], "2": [], "3": [], "4": [], "5": [], + "6": [], "7": [], "8": [], "9": [], "10": [] + } + }, + + "custom/launcher": { + "interval": "once", + "format": "󰣇", + "on-click": "nomarchy-menu", + "tooltip-format": "Nomarchy menu" + }, + + "pulseaudio": { + "format": "{icon} {volume}%", + "format-bluetooth": "{icon}  {volume}%", + "format-bluetooth-muted": "  muted", + "format-muted": " muted", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "tooltip-format": "Playing at {volume}%" + }, + + "network": { + "format-wifi": " {signalStrength}%", + "format-ethernet": "", + "format-disconnected": "󰤭", + "on-click": "sh -c '$TERMINAL -e nmtui'", + "tooltip-format": "{ipaddr} via {gwaddr}" + }, + + "battery": { + "interval": 60, + "states": { "warning": 30, "critical": 15 }, + "max-length": 10, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-full": " 100%", + "format-icons": ["", "", "", "", ""], + "on-click": "nomarchy-menu power", + "tooltip-format": "Battery status" + }, + + "clock": { + "format": " {:%H:%M}", + "tooltip": true, + "tooltip-format": "{calendar}" + }, + + "clock#date": { + "format": " {:%A, %B %d, %Y}", + "tooltip": false + }, + + "tray": { "spacing": 8 }, + + "custom/powermenu": { + "format": "", + "on-click": "nomarchy-menu power", + "tooltip": false + } +} diff --git a/themes/summer-night/waybar.jsonc b/themes/summer-night/waybar.jsonc new file mode 100644 index 0000000..e5a1430 --- /dev/null +++ b/themes/summer-night/waybar.jsonc @@ -0,0 +1,99 @@ +{ + "margin-top": 0, + "margin-left": 120, + "margin-bottom": 0, + "margin-right": 120, + "height": 61, + "layer": "top", + "position": "top", + "spacing": 15, + "reload_style_on_change": true, + + "modules-left": ["custom/nomarchy", "clock", "clock#date"], + "modules-center": ["hyprland/workspaces"], + "modules-right": ["idle_inhibitor", "pulseaudio", "network", "battery", "tray", "custom/powermenu"], + + "hyprland/workspaces": { + "disable-scroll": true, + "all-outputs": true, + "on-click": "activate", + "on-scroll-up": "hyprctl dispatch workspace r+1", + "on-scroll-down": "hyprctl dispatch workspace r-1", + "persistent-workspaces": { + "1": [], "2": [], "3": [], "4": [], "5": [], + "6": [], "7": [], "8": [], "9": [], "10": [] + } + }, + + "custom/nomarchy": { + "interval": "once", + "format": "󱄅", + "on-click": "nomarchy-menu", + "tooltip-format": "Nomarchy menu" + }, + + "idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": " 󰅶 ", + "deactivated": " 󰾪 " + } + }, + + "pulseaudio": { + "scroll-step": 5, + "format": "{icon} {volume}%", + "format-bluetooth": "{icon} {volume}%", + "format-bluetooth-muted": " {icon}", + "format-muted": " muted", + "format-icons": { + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] + }, + "on-click": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "tooltip-format": "{desc} | {volume}%" + }, + + "network": { + "format-wifi": " {essid}", + "format-ethernet": "", + "format-disconnected": "󰤭", + "on-click": "sh -c '$TERMINAL -e nmtui'", + "tooltip-format": "{ipaddr} via {gwaddr}" + }, + + "battery": { + "interval": 30, + "states": { "warning": 25, "critical": 10 }, + "format": "{icon} {capacity}%", + "format-charging": " {capacity}%", + "format-plugged": " {capacity}%", + "format-icons": ["", "", "", "", ""], + "on-click": "nomarchy-menu power", + "tooltip-format": "Battery status" + }, + + "clock": { + "format": " {:%H:%M}", + "tooltip": true, + "tooltip-format": "{calendar}" + }, + + "clock#date": { + "format": " {:%A, %B %d, %Y}", + "tooltip": false + }, + + "tray": { "spacing": 8 }, + + "custom/powermenu": { + "format": "", + "on-click": "nomarchy-menu power", + "tooltip": false + } +} From 465f013155442045010c0a6d842bd2ea6a78f32c Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 07:37:47 +0100 Subject: [PATCH 09/51] feat(home): menu system, swaync, idle/lock, nm-applet, theme parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Themed fuzzel (modules/home/fuzzel.nix): palette/fonts/border from the JSON, themes//fuzzel.ini whole-swap mechanism. Plus the nomarchy-menu dispatcher: root picker · power (SUPER+Escape) · theme (SUPER+T) · clipboard (SUPER+CTRL+V, cliphist) · calc (qalc) · files (fd→xdg-open) · web (DuckDuckGo). - swaync notifications themed from the JSON (SUPER+N) — until now nothing rendered notify-send (theme toasts, font warnings, welcome msg). - hyprlock + hypridle (modules/home/idle.nix): lock 5min, dpms off 10, suspend 30; enables the power menu's Lock entry. cliphist daemon on. - NetworkManager applet in waybar's tray (preferStatusNotifierItems); network module on-click → $TERMINAL -e nmtui. - nomarchy.keyboard.layout/.variant option → Hyprland kb_layout/kb_variant (the installer writes it; pairs with system-side xkb for tty/LUKS). - Media-key/audio on-click moved to wpctl (pamixer was inconsistent). - Theme parity: summer-day/night carry their legacy bar LAYOUTS as waybar.jsonc whole-swaps (the original import took waybar.css but not config.jsonc, so identity themes styled nonexistent modules). Dead legacy script-modules dropped, Nerd-Fonts-v2 codepoints → FontAwesome/v3 (font-awesome now shipped), logo buttons open nomarchy-menu. New toggles: nomarchy.{fuzzel,swaync,idle}.enable (all default true). Co-Authored-By: Claude Fable 5 --- modules/home/default.nix | 12 ++++ modules/home/fuzzel.nix | 107 ++++++++++++++++++++++++++++++--- modules/home/hyprland.nix | 18 ++++-- modules/home/options.nix | 22 +++++++ modules/home/waybar.nix | 5 +- themes/summer-day/waybar.css | 1 + themes/summer-night/waybar.css | 6 +- 7 files changed, 154 insertions(+), 17 deletions(-) diff --git a/modules/home/default.nix b/modules/home/default.nix index 699ce68..e33dd6c 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -12,8 +12,20 @@ ./waybar.nix ./ghostty.nix ./btop.nix + ./fuzzel.nix # launcher theming + the nomarchy-menu dispatcher + ./swaync.nix # notification daemon, themed from the same JSON + ./idle.nix # hyprlock + hypridle, themed from the same JSON ]; + # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V + # menu module. + services.cliphist.enable = true; + + # Wifi from the bar: nm-applet lives in waybar's tray (SNI flag via + # preferStatusNotifierItems — without it there is no tray icon). + services.network-manager-applet.enable = true; + xsession.preferStatusNotifierItems = true; + home.stateVersion = "26.05"; home.packages = with pkgs; [ diff --git a/modules/home/fuzzel.nix b/modules/home/fuzzel.nix index b0f126b..eee8416 100644 --- a/modules/home/fuzzel.nix +++ b/modules/home/fuzzel.nix @@ -1,7 +1,10 @@ # Fuzzel — launcher, dmenu renderer for the menu system, themed from # theme-state.json like every other surface. Also home of nomarchy-menu, -# the dispatcher script the menu roadmap grows out of (first module: -# power). +# the dispatcher script all menu modules grow out of. +# +# Per-theme identity: themes//fuzzel.ini is a whole-swap (like +# waybar.css) — it replaces the generated config entirely, probed at +# eval time. { config, lib, pkgs, ... }: let @@ -9,43 +12,125 @@ let t = cfg.theme; c = t.colors; + # Per-theme override probe (same convention as waybar.nix). + iniOverride = cfg.themesDir + "/${t.slug}/fuzzel.ini"; + hasIniOverride = builtins.pathExists iniOverride; + # fuzzel.ini colors are rrggbbaa, no leading #. hexa = color: alpha: "${lib.removePrefix "#" color}${alpha}"; nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' # Nomarchy menu dispatcher — thin presentation layer over - # `fuzzel --dmenu`; actions delegate to systemctl/hyprctl/ - # nomarchy-theme-sync. The icons are nf-md glyphs (any shipped - # Nerd Font carries them). + # `fuzzel --dmenu`; actions delegate to nomarchy-theme-sync, + # systemctl, hyprctl and friends. Icons are nf-md glyphs (any + # shipped Nerd Font carries them). `nomarchy-menu` with no + # argument shows the module picker. + + urlencode() { + local s="$1" out="" ch + for ((i = 0; i < ''${#s}; i++)); do + ch=''${s:i:1} + case "$ch" in + [a-zA-Z0-9.~_-]) out+="$ch" ;; + ' ') out+="+" ;; + *) printf -v ch '%%%02X' "'$ch"; out+="$ch" ;; + esac + done + printf '%s' "$out" + } + case "''${1:-}" in power) choice=$(printf '%s\n' \ + "󰌾 Lock" \ "󰍃 Logout" \ "󰤄 Suspend" \ "󰒲 Hibernate" \ "󰜉 Reboot" \ "󰐥 Shutdown" \ - | fuzzel --dmenu --prompt "power " --lines 5 --width 24) || exit 0 + | fuzzel --dmenu --prompt "power " --lines 6 --width 24) || exit 0 case "$choice" in + *Lock) loginctl lock-session ;; *Logout) hyprctl dispatch exit ;; *Suspend) systemctl suspend ;; *Hibernate) systemctl hibernate ;; *Reboot) systemctl reboot ;; *Shutdown) systemctl poweroff ;; esac ;; + + theme) + choice=$(nomarchy-theme-sync list \ + | fuzzel --dmenu --prompt "theme ") || exit 0 + [ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;; + + clipboard) + sel=$(cliphist list | fuzzel --dmenu --prompt "clip " --width 60) || exit 0 + printf '%s' "$sel" | cliphist decode | wl-copy ;; + + calc) + expr=$(fuzzel --prompt-only "= ") || exit 0 + [ -n "$expr" ] || exit 0 + result=$(qalc -t -- "$expr" 2>&1 | tail -n 1) + choice=$(printf '%s\n' "󰆏 Copy result" "󰃬 New calculation" \ + | fuzzel --dmenu --lines 2 --width 40 \ + --prompt "= " --mesg "$expr = $result") || exit 0 + case "$choice" in + *Copy*) printf '%s' "$result" | wl-copy ;; + *New*) exec "$0" calc ;; + esac ;; + + files) + sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ + | head -n 50000 \ + | sed "s|^$HOME/||" \ + | fuzzel --dmenu --prompt "file " --width 60) || exit 0 + [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; + + web) + q=$(fuzzel --prompt-only "search ") || exit 0 + [ -n "$q" ] || exit 0 + exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;; + + "") + choice=$(printf '%s\n' \ + "󰀻 Apps" \ + "󰏘 Theme" \ + "󰅌 Clipboard" \ + "󰃬 Calculator" \ + "󰈞 Files" \ + "󰖟 Web search" \ + "󰐥 Power" \ + | fuzzel --dmenu --prompt "menu " --lines 7 --width 24) || exit 0 + case "$choice" in + *Apps*) exec fuzzel ;; + *Theme*) exec "$0" theme ;; + *Clipboard*) exec "$0" clipboard ;; + *Calc*) exec "$0" calc ;; + *Files*) exec "$0" files ;; + *Web*) exec "$0" web ;; + *Power*) exec "$0" power ;; + esac ;; + *) - echo "usage: nomarchy-menu power" >&2 + echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2 exit 64 ;; esac ''; in { config = lib.mkIf cfg.fuzzel.enable { - home.packages = [ nomarchy-menu ]; + home.packages = [ + nomarchy-menu + pkgs.libqalculate # qalc, the calc module's engine + pkgs.fd # files module + pkgs.xdg-utils # xdg-open for files/web + ]; programs.fuzzel = { enable = true; - settings = { + # Whole-swap themes bring their own fuzzel.ini; otherwise the + # config is generated from the palette. + settings = lib.mkIf (!hasIniOverride) { main = { # UI font first; the mono Nerd Font follows so menu icons # (nf-md glyphs) resolve — fcft falls back per glyph. @@ -78,5 +163,9 @@ in }; }; }; + + xdg.configFile."fuzzel/fuzzel.ini" = lib.mkIf hasIniOverride { + source = iniOverride; + }; }; } diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 5584ad1..5478f88 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -90,7 +90,8 @@ in # ── Behaviour ────────────────────────────────────────────────── input = { - kb_layout = "us"; + kb_layout = config.nomarchy.keyboard.layout; + kb_variant = config.nomarchy.keyboard.variant; follow_mouse = 1; touchpad.natural_scroll = true; }; @@ -120,12 +121,21 @@ in "$mod, V, togglefloating" "$mod SHIFT, E, exit" - # Theme picker: fuzzel menu over the presets; apply writes the - # state and runs home-manager switch (progress via notify-send). - "$mod, T, exec, nomarchy-theme-sync apply \"$(nomarchy-theme-sync list | fuzzel --dmenu --prompt 'theme> ')\"" + # Theme picker (menu dispatcher): apply writes the state and + # runs home-manager switch (progress via notify-send). + "$mod, T, exec, nomarchy-menu theme" # Cycle the current theme's wallpapers (instant, no rebuild). "$mod SHIFT, T, exec, nomarchy-theme-sync bg next" + # Power menu (fuzzel dmenu via the nomarchy-menu dispatcher). + "$mod, Escape, exec, nomarchy-menu power" + + # Notification centre (swaync). + "$mod, N, exec, swaync-client -t" + + # Clipboard history (cliphist via the menu dispatcher). + "$mod CTRL, V, exec, nomarchy-menu clipboard" + # Focus "$mod, H, movefocus, l" "$mod, L, movefocus, r" diff --git a/modules/home/options.nix b/modules/home/options.nix index 69eafb8..5f3aab0 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -23,6 +23,25 @@ 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."; + }; + package = lib.mkOption { type = lib.types.package; default = pkgs.nomarchy-theme-sync; @@ -44,6 +63,9 @@ # ── Component toggles ────────────────────────────────────────── hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; }; waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; }; + fuzzel.enable = lib.mkEnableOption "Nomarchy's themed fuzzel + the nomarchy-menu dispatcher (power menu)" // { 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; }; 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; }; diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index e1bc3e5..2770db0 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -60,7 +60,7 @@ let format = "{icon} {volume}%"; format-muted = "󰝟"; format-icons.default = [ "󰕿" "󰖀" "󰕾" ]; - on-click = "pamixer -t"; + on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; }; network = { @@ -68,6 +68,9 @@ let format-ethernet = "󰈀"; format-disconnected = "󰤭"; tooltip-format = "{ipaddr} via {gwaddr}"; + # nm-applet sits in the tray for the GUI path; this is the + # keyboard-friendly one. + on-click = "${config.nomarchy.terminal} -e nmtui"; }; cpu.format = "󰍛 {usage}%"; diff --git a/themes/summer-day/waybar.css b/themes/summer-day/waybar.css index 18ac8f3..aa1a4e8 100755 --- a/themes/summer-day/waybar.css +++ b/themes/summer-day/waybar.css @@ -49,6 +49,7 @@ window#waybar { #pulseaudio, #network, #battery, +#tray, #custom-powermenu { background-color: @bg0; color: @fg; diff --git a/themes/summer-night/waybar.css b/themes/summer-night/waybar.css index 83466ad..a52bda3 100644 --- a/themes/summer-night/waybar.css +++ b/themes/summer-night/waybar.css @@ -53,8 +53,9 @@ window#waybar { #clock.date, #workspaces, #pulseaudio, +#network, #idle_inhibitor, -#custom-battery, +#battery, #custom-powermenu { background-color: @bg0; color: @fg; @@ -98,7 +99,6 @@ window#waybar { margin-left: 15px; padding-left: 20px; padding-right: 21px; - font-family: Nomarchy; font-size: 20px; } @@ -112,7 +112,7 @@ window#waybar { padding-right: 23px; } -#custom-battery.preservation { +#battery.charging { color: @green; } From 6763bef73e84afb92122d082b534e1c8efa4932b Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 07:38:31 +0100 Subject: [PATCH 10/51] fix(install): pre-activation, writable home.nix, keyboard/locale selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pre-activation failure ("desktop pre-activation failed"): the in-chroot HM build hard-coded `--option substituters ""`, which only works on OFFLINE installs (where the ISO store is copied in). Online installs (now common — wifi works) had no pins and built from source. Fix: build the generation in the live env (its store always has the pins), nix copy the closure to the target, chroot only activates. In-chroot build kept as fallback. Uses path: (not git+file:) to sidestep libgit2 ownership. - Read-only home.nix: templates come out of the store mode 0444 and cp preserves it — chmod -R u+w after staging so the user can edit it. - Keyboard + locale selection: gum prompts (localectl layouts/variants + curated UTF-8 locale list) and NOMARCHY_LOCALE/KB_LAYOUT/KB_VARIANT for unattended. One choice flows everywhere — services.xserver.xkb (source of truth) + console.useXkbConfig (tty/greeter) + boot.initrd.systemd (so the LUKS passphrase prompt uses the layout; script initrd can't) + the generated home.nix's nomarchy.keyboard.* for the Hyprland session. - test-install.sh: NOMARCHY_OVMF env override for sandboxed OVMF paths. Verified end-to-end in QEMU: offline LUKS install completes, boots to a themed desktop (no autogenerated-config banner), Plymouth renders the LUKS prompt on the theme-tinted background. Co-Authored-By: Claude Fable 5 --- pkgs/nomarchy-install/nomarchy-install.sh | 100 ++++++++++++++++++++-- templates/downstream/home.nix | 3 + tools/test-install.sh | 4 +- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/pkgs/nomarchy-install/nomarchy-install.sh b/pkgs/nomarchy-install/nomarchy-install.sh index 8ea40b6..ee7ac37 100644 --- a/pkgs/nomarchy-install/nomarchy-install.sh +++ b/pkgs/nomarchy-install/nomarchy-install.sh @@ -16,6 +16,7 @@ # NOMARCHY_UNATTENDED=1 NOMARCHY_DISK=/dev/vda NOMARCHY_USERNAME=me \ # NOMARCHY_PASSWORD=secret [NOMARCHY_HOSTNAME=nomarchy] \ # [NOMARCHY_TIMEZONE=UTC] [NOMARCHY_LUKS_PASSPHRASE=...] \ +# [NOMARCHY_LOCALE=en_US.UTF-8] [NOMARCHY_KB_LAYOUT=us] [NOMARCHY_KB_VARIANT=] \ # [NOMARCHY_SWAP_GB=N (default: RAM size; 0 = none)] \ # [NOMARCHY_HW="auto"|"none"|"mod1 mod2"] [NOMARCHY_FINISH=none|reboot|poweroff] # nomarchy-install @@ -150,6 +151,9 @@ if [[ "$UNATTENDED" == "1" ]]; then PASSWORD="${NOMARCHY_PASSWORD:?}" HOSTNAME_="${NOMARCHY_HOSTNAME:-nomarchy}" TIMEZONE="${NOMARCHY_TIMEZONE:-UTC}" + LOCALE="${NOMARCHY_LOCALE:-en_US.UTF-8}" + KB_LAYOUT="${NOMARCHY_KB_LAYOUT:-us}" + KB_VARIANT="${NOMARCHY_KB_VARIANT:-}" else while true; do USERNAME=$(gum input --placeholder "username (lowercase, e.g. ada)") @@ -169,6 +173,26 @@ else warn "Invalid hostname." done TIMEZONE=$(timedatectl list-timezones | gum filter --placeholder "timezone (type to search)" || echo UTC) + # Curated UTF-8 locales: the live system only generates en_US, so + # `locale -a` can't enumerate what the TARGET could use. + LOCALE=$(printf '%s\n' \ + en_US en_GB de_DE fr_FR es_ES es_MX pt_PT pt_BR it_IT nl_NL \ + pl_PL ru_RU uk_UA cs_CZ sk_SK sv_SE nb_NO da_DK fi_FI tr_TR \ + el_GR hu_HU ro_RO bg_BG hr_HR sl_SI lt_LT lv_LV et_EE ja_JP \ + ko_KR zh_CN zh_TW ar_EG he_IL hi_IN th_TH vi_VN id_ID \ + | sed 's/$/.UTF-8/' \ + | gum filter --placeholder "language / locale (type to search)" \ + || echo en_US.UTF-8) + KB_LAYOUT=$(localectl list-x11-keymap-layouts 2>/dev/null \ + | gum filter --placeholder "keyboard layout (type to search)" \ + || echo us) + KB_VARIANT="" + if [[ "$KB_LAYOUT" != "us" ]] || gum confirm --default=No "Pick a keyboard variant (intl, nodeadkeys, …)?"; then + KB_VARIANT=$( { echo "(none)"; localectl list-x11-keymap-variants "$KB_LAYOUT" 2>/dev/null; } \ + | gum filter --placeholder "variant for $KB_LAYOUT (pick '(none)' for the default)" \ + || echo "(none)") + [[ "$KB_VARIANT" == "(none)" ]] && KB_VARIANT="" + fi fi [[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || fail "Invalid username '$USERNAME'." [[ -f "/usr/share/zoneinfo/$TIMEZONE" || -e "/etc/zoneinfo/$TIMEZONE" ]] \ @@ -177,6 +201,7 @@ fi HASHED_PASSWORD=$(printf '%s' "$PASSWORD" | mkpasswd -m sha-512 -s) unset PASSWORD info "User: $USERNAME @ $HOSTNAME_ ($TIMEZONE)" +info "Locale: $LOCALE · keyboard: $KB_LAYOUT${KB_VARIANT:+ ($KB_VARIANT)}" # ─── Hardware profile ─────────────────────────────────────────────────── section "Hardware detection" @@ -320,9 +345,35 @@ nixos-generate-config --root /mnt mv /mnt/etc/nixos/hardware-configuration.nix "$FLAKE_DIR/" rm -rf /mnt/etc/nixos -cp "$SHARE/template/home.nix" "$FLAKE_DIR/" cp "$SHARE/template/theme-state.json" "$FLAKE_DIR/" +# home.nix is generated (not copied from the template) so the chosen +# keyboard layout reaches the Hyprland session — standalone HM cannot +# read system.nix. +cat > "$FLAKE_DIR/home.nix" < "$FLAKE_DIR/system.nix" < /mnt/root/nomarchy-hm-activate.sh < /var/log/nomarchy-hm-preactivate.log 2>&1 export PATH=/run/current-system/sw/bin:\$PATH -out=\$(nix --extra-experimental-features "nix-command flakes" \ - build --no-link --print-out-paths \ - --option substituters "" \ - "path:/home/$USERNAME/.nomarchy#homeConfigurations.$USERNAME.activationPackage") +# Normally pre-built in the live env and copied over; the in-chroot +# build (default substituters — the live-side build already proved the +# no-network case) is a last-resort fallback. +out="$hm_out" +if [ -z "\$out" ]; then + out=\$(nix --extra-experimental-features "nix-command flakes" \ + build --no-link --print-out-paths \ + "path:/home/$USERNAME/.nomarchy#homeConfigurations.$USERNAME.activationPackage") +fi install -d -o "$USERNAME" -g users /nix/var/nix/profiles/per-user/$USERNAME install -d -o "$USERNAME" -g users /nix/var/nix/gcroots/per-user/$USERNAME # activate's profile ops need store access; as the user that means a diff --git a/templates/downstream/home.nix b/templates/downstream/home.nix index 29b4ba1..9420a2c 100644 --- a/templates/downstream/home.nix +++ b/templates/downstream/home.nix @@ -9,6 +9,9 @@ # nomarchy.terminal = "kitty"; # swap the default terminal # nomarchy.waybar.enable = false; # bring your own bar # nomarchy.stylix.enable = false; # opt out of GTK/Qt theming + # nomarchy.keyboard.layout = "de"; # session keyboard (pair with + # # xkb + console.useXkbConfig in + # # system.nix for tty/LUKS) home.packages = with pkgs; [ # firefox diff --git a/tools/test-install.sh b/tools/test-install.sh index 48cd1a2..37db568 100755 --- a/tools/test-install.sh +++ b/tools/test-install.sh @@ -47,8 +47,8 @@ if df --output=fstype "$TARGET_DIR" | tail -1 | grep -q tmpfs; then exit 1 fi -OVMF="" -for c in /run/current-system/sw/share/qemu/edk2-x86_64-code.fd \ +OVMF="${NOMARCHY_OVMF:-}" +[ -n "$OVMF" ] || for c in /run/current-system/sw/share/qemu/edk2-x86_64-code.fd \ /run/current-system/sw/share/OVMF/OVMF_CODE.fd \ /usr/share/OVMF/OVMF_CODE.fd; do [ -f "$c" ] && { OVMF="$c"; break; } From 583708d78c32e6ccc7795dbb05f0d0a8bf857c66 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 07:38:31 +0100 Subject: [PATCH 11/51] docs: README for the v1 QA batch (menu, swaync, idle, Plymouth, sys-update) Options/keybinds/layout tables, sys-update/home-update workflow, the new module tree, and roadmap updates (menu system + nm-applet shipped; theme parity, branding round 2, swayosd, ask-Claude module remain). Co-Authored-By: Claude Fable 5 --- README.md | 83 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 712dc5d..fb07e37 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,10 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── hyprland.nix # all JSON-driven │ ├── waybar.nix │ ├── ghostty.nix -│ └── btop.nix +│ ├── btop.nix +│ ├── fuzzel.nix # launcher + nomarchy-menu (power, clip) +│ ├── swaync.nix # notifications, same JSON +│ └── idle.nix # hyprlock + hypridle, same JSON ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -144,6 +147,13 @@ sudo nixos-rebuild switch --flake .#default # system: rare home-manager switch --flake .#me # desktop: every theme change, no sudo ``` +Day-to-day you'll use the shipped shortcuts instead: + +```sh +sys-update # nix flake update + system rebuild (BTRFS snapshot first when available) +home-update # home-manager switch (no flake update, no sudo) +``` + Override anything with plain NixOS/HM options (the distro uses `mkDefault` throughout) or the `nomarchy.*` surface: @@ -151,12 +161,18 @@ throughout) or the `nomarchy.*` surface: |---|---|---| | `nomarchy.stateFile` | — (required) | Path to your theme-state.json | | `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` | +| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes it; pairs with xkb + `console.useXkbConfig` in system.nix) | +| `nomarchy.keyboard.variant` | `""` | XKB variant for the session | | `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config | | `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar | +| `nomarchy.fuzzel.enable` | `true` | Themed fuzzel + `nomarchy-menu` (power menu) | +| `nomarchy.swaync.enable` | `true` | swaync notifications, themed | +| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | | `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.greeter.enable` | `true` | greetd/tuigreet | | `nomarchy.system.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | @@ -199,6 +215,7 @@ per theme — a single place to look, unlike the old distro's split: | `btop.theme` | baked into the generation (generated from the palette when absent) | | `waybar.css` | **whole-swap**: replaces the generated bar style entirely (probed at eval time, self-contained) | | `waybar.jsonc` | whole-swap for the bar *layout* (must be plain JSON) | +| `fuzzel.ini` | **whole-swap**: replaces the generated launcher/menu config entirely | Six ported themes ship a `waybar.css` identity (catppuccin, lumon, nord, retro-82, summer-day, summer-night). Custom user themes can live in @@ -214,11 +231,14 @@ nomarchy-theme-sync set ui.gapsOut 16 # tweak one knob (also a switch) nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuild nomarchy-theme-sync bg auto # back to the theme's default wallpaper nomarchy-theme-sync get colors.accent +sys-update # update inputs + rebuild the system (snapshots first) +home-update # rebuild just the desktop layer ``` Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme -picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+Q` close · `SUPER+1..9` -workspaces · `Print` region screenshot. +picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+Escape` power menu · +`SUPER+N` notifications · `SUPER+CTRL+V` clipboard history · `SUPER+Q` +close · `SUPER+1..9` workspaces · `Print` region screenshot. ## 6. Extending @@ -234,40 +254,45 @@ workspaces · `Print` region screenshot. on fuzzel — already shipped for SUPER+D/SUPER+T, dmenu mode does nested menus and per-entry icons, and its INI theme can be baked from theme-state.json like every other app: - - `modules/home/fuzzel.nix`: colors/fonts from the theme JSON (today the - launcher is the one surface that ignores the theme) - - `nomarchy-menu` dispatcher script over `fuzzel --dmenu`, Omarchy-style - nesting: Theme ▸ (apply/wallpaper) · Network ▸ nmtui · Bluetooth ▸ - blueman · Power ▸ lock/suspend/reboot/poweroff · Capture ▸ screenshot; - actions delegate to nomarchy-theme-sync and friends - - script modules (each a small dmenu pipeline, no plugin framework): - calculator (`--prompt-only` → `qalc -t` → result re-shown via `--mesg`, - Enter copies / chains; needs libqalculate) · clipboard history - (cliphist ⇄ fuzzel ⇄ wl-copy; also adds clipboard persistence) · file - open (`fd . ~` → xdg-open) · web search (query → xdg-open) · **ask - Claude**: `--prompt-only` → `$TERMINAL -e claude ""` — the - claude CLI auths via OAuth against a Pro/Max subscription (no API - key); REPL stays open for follow-ups (claude-code in nixpkgs is - unfree — needs a per-package allowUnfree) + - ✓ shipped: `modules/home/fuzzel.nix` (palette/fonts/border from the + theme JSON, `themes//fuzzel.ini` whole-swap) and the + `nomarchy-menu` dispatcher: root picker (no args) · `power` + (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+Escape) · + `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` + (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo) + - next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ + screenshot · **ask Claude**: `--prompt-only` → + `$TERMINAL -e claude ""` — the claude CLI auths via OAuth + against a Pro/Max subscription (no API key); REPL stays open for + follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on) - known limits accepted for now: no live-as-you-type results (dmenu input is static per invocation) and no image previews - decision record: this resolves the old Walker/Lua question in favor of simplicity — no GTK4 launcher, no second theming pipeline, no image previews (instant `bg next` covers wallpaper browsing); the dispatcher owns the menu structure, so the renderer stays swappable -- We need to add networkManager applet to waybar so user can easily connect to internet +- **Theme parity with legacy:** summer-day/night now carry their legacy + bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script + modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, + logo button opens nomarchy-menu); the other four identity themes are + palette recolors and already match. Remaining: a visual pass over all + six on the live ISO +- **Per-theme fuzzel identity:** the `themes//fuzzel.ini` whole-swap + mechanism ships; what remains is authoring the inis for the six identity + themes from their legacy rofi.rasi files (per-element colors like + summer-night's green inputbar aren't expressible in fuzzel's flat + scheme — approximate, note the rest as a renderer limitation) - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme variants if still needed -- Plymouth + SDDM/greeter theming from the same JSON +- Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since + v1: `nomarchy.system.plymouth.*`, background tinted from the state file) - Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) -- `hyprlock`/`hypridle`, swayosd, launch-or-focus UX scripts -- swaync notification center (notify-send currently renders bare) -- **Distro branding:** replace the NixOS brand with Nomarchy everywhere it - surfaces — `system.nixos.distroName`/`distroId` (flows into - `/etc/os-release`, the live ISO's boot-menu label and the installed - system's systemd-boot entries), `isoImage.splashImage`/`grubTheme` for - the ISO boot screen, tuigreet/MOTD text. Caveats: `distroId` also - changes `DEFAULT_HOSTNAME` and some upstream `isNixos` checks, and - nixos-* CLI names stay (renaming tooling is not worth the drift) +- swayosd (volume/brightness OSD), launch-or-focus UX scripts +- **Distro branding, round 2:** `distroName = "Nomarchy"` ships + (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). + Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot + screen, tuigreet/MOTD text, and the `distroId` question (it changes + `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; + nixos-* CLI names stay regardless) From bd42462291a3b55e02e22ea05a53fb00dbfcfcee Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 13:44:22 +0100 Subject: [PATCH 12/51] fix(home): power menu on SUPER+X, not SUPER+Escape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Super+Escape didn't fire on real hardware (Latitude 5410) while its neighbour binds did — the chord gets swallowed before reaching Hyprland's dispatcher on some setups. SUPER+X is unambiguous. Co-Authored-By: Claude Fable 5 --- README.md | 4 ++-- modules/home/hyprland.nix | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb07e37..e78e6f6 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,7 @@ home-update # rebuild just the desktop layer ``` Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme -picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+Escape` power menu · +picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu · `SUPER+N` notifications · `SUPER+CTRL+V` clipboard history · `SUPER+Q` close · `SUPER+1..9` workspaces · `Print` region screenshot. @@ -257,7 +257,7 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. - ✓ shipped: `modules/home/fuzzel.nix` (palette/fonts/border from the theme JSON, `themes//fuzzel.ini` whole-swap) and the `nomarchy-menu` dispatcher: root picker (no args) · `power` - (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+Escape) · + (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo) - next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 5478f88..c567e83 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -128,7 +128,9 @@ in "$mod SHIFT, T, exec, nomarchy-theme-sync bg next" # Power menu (fuzzel dmenu via the nomarchy-menu dispatcher). - "$mod, Escape, exec, nomarchy-menu power" + # Not Escape: Super+Escape gets swallowed before reaching the + # dispatcher on some setups (input methods / modal handling). + "$mod, X, exec, nomarchy-menu power" # Notification centre (swaync). "$mod, N, exec, swaync-client -t" From d06373b15560db3fc4e7b17712caa038ea4bd0f2 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 14:05:59 +0100 Subject: [PATCH 13/51] feat(home): mkDefault behaviour settings so downstream can plain-override MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nomarchy's Hyprland/Ghostty/Waybar settings were set at normal priority, so a downstream `home.nix` setting any value Nomarchy already set threw a "conflicting definition values" error — overriding required lib.mkForce, contradicting the README's "mkDefault throughout" claim. Now: behaviour/chrome leaves (input, misc, monitor, animations, dwindle, gesture, decoration.blur sizing, ghostty padding/decoration, waybar settings+style) are lib.mkDefault — a plain home.nix assignment wins. Appearance values that flow from theme-state.json (gaps, colors, rounding, opacity, fonts) stay at normal priority on purpose: change them via nomarchy-theme-sync (their source of truth) or lib.mkForce to hardcode. bind/exec-once lists stay normal priority so additions concatenate rather than replace. Verified: behaviour/ghostty plain overrides build; theme values still conflict (steering to the CLI); default rendered config is byte-identical. New docs/OVERRIDES.md with the appearance→CLI / behaviour→home.nix / component→toggle model and worked examples; linked from the README. Co-Authored-By: Claude Fable 5 --- README.md | 8 ++- docs/OVERRIDES.md | 147 ++++++++++++++++++++++++++++++++++++++ modules/home/ghostty.nix | 12 ++-- modules/home/hyprland.nix | 50 ++++++++----- modules/home/waybar.nix | 15 ++-- 5 files changed, 202 insertions(+), 30 deletions(-) create mode 100644 docs/OVERRIDES.md diff --git a/README.md b/README.md index e78e6f6..450ff6a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake) ├── templates/downstream/ # `nix flake init -t` starter for users ├── docs/TESTING.md # how to verify changes (incl. AI-agent rules) +├── docs/OVERRIDES.md # how downstream users override defaults └── tools/ # maintainer-only ├── import-palettes.py # converts old-distro themes → JSON + assets ├── test-live-iso.sh # build the ISO + boot it in QEMU @@ -154,8 +155,11 @@ sys-update # nix flake update + system rebuild (BTRFS snapshot first when ava home-update # home-manager switch (no flake update, no sudo) ``` -Override anything with plain NixOS/HM options (the distro uses `mkDefault` -throughout) or the `nomarchy.*` surface: +Override anything via the `nomarchy.*` surface or plain NixOS/HM options: +appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`, +behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix` +assignment wins, and bind/exec-once lists concatenate. Full guide with +examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | Option | Default | Purpose | |---|---|---| diff --git a/docs/OVERRIDES.md b/docs/OVERRIDES.md new file mode 100644 index 0000000..cc917d3 --- /dev/null +++ b/docs/OVERRIDES.md @@ -0,0 +1,147 @@ +# Overriding Nomarchy's defaults + +You consume Nomarchy as a flake input and own two files: `system.nix` and +`home.nix`. Your config is **merged** with Nomarchy's modules, so changing a +default is just a matter of knowing which of three knobs to reach for. The +rule of thumb: + +> **Appearance → the CLI. Behaviour → `home.nix`. Whole component → toggle it off.** + +## 1. Appearance (gaps, colors, rounding, fonts, opacity) — use the CLI + +Everything that defines the *look* flows from `theme-state.json`, the single +source of truth. Change it with `nomarchy-theme-sync`, which writes the JSON +and rebuilds — one generation, applied to Hyprland, Waybar, Ghostty, btop and +Stylix at once: + +```sh +nomarchy-theme-sync set ui.gapsOut 16 # gaps, borders, rounding, opacity +nomarchy-theme-sync set ui.rounding 0 +nomarchy-theme-sync set fonts.mono "FiraCode Nerd Font" +nomarchy-theme-sync apply gruvbox # whole palette +``` + +These values are deliberately kept at normal priority in the modules, so they +stay owned by the theme system. If you *insist* on pinning one in `home.nix` +regardless of the active theme, use `lib.mkForce` (see §4) — but then the CLI +can no longer change that knob. + +## 2. Behaviour (input, misc, monitor, animations, terminal chrome) — `home.nix` + +Non-appearance defaults are set with `lib.mkDefault`, so a **plain assignment** +in your `home.nix` wins — no `mkForce` needed: + +```nix +# home.nix +{ ... }: +{ + wayland.windowManager.hyprland.settings = { + input.follow_mouse = 0; # was 1 + input.touchpad.natural_scroll = false; + misc.disable_splash_rendering = false; + monitor = [ "DP-1,2560x1440@144,0x0,1" ]; # replaces the default rule + animations.enabled = false; + }; + + programs.ghostty.settings = { + window-padding-x = 4; # was 12 + window-decoration = true; + }; +} +``` + +### Adding vs. overriding lists + +`bind`, `bindel`, `bindl`, `bindm` and `exec-once` are lists kept at normal +priority, so anything you add **concatenates** with Nomarchy's — your binds and +autostarts run *alongside* the defaults: + +```nix +{ + wayland.windowManager.hyprland.settings = { + bind = [ + "$mod, B, exec, firefox" + "$mod SHIFT, S, exec, grim -g \"$(slurp)\" - | swappy -f -" + ]; + exec-once = [ "nm-applet --indicator" ]; + }; +} +``` + +To **remove or replace** a default bind, override the whole `bind` list with +`lib.mkForce [ … ]` (you then own the full list), or rebind the key to +something else (last definition for a key wins in Hyprland). + +You can also drop raw config that doesn't fit the Nix schema: + +```nix +{ wayland.windowManager.hyprland.extraConfig = '' + bindl = , switch:Lid Switch, exec, hyprlock + ''; +} +``` + +### Waybar + +For a themed bar identity, ship whole-swap assets +(`themes//waybar.jsonc` and `waybar.css`) — see the main README. To +replace the bar wholesale from `home.nix`, assign `programs.waybar.settings. +mainBar` / `programs.waybar.style` (both `mkDefault`, so a plain assignment +wins). For a one-off tweak of a single generated key, `lib.mkForce` it. + +## 3. Whole component — toggle it off and bring your own + +Each desktop piece has an enable flag (all default `true`). Turn one off and +Nomarchy contributes nothing for it, leaving the field clear for your own: + +```nix +# home.nix +{ + nomarchy.hyprland.enable = false; # then write your own wayland.windowManager.hyprland + nomarchy.waybar.enable = false; + nomarchy.swaync.enable = false; + nomarchy.idle.enable = false; # no hyprlock/hypridle +} +``` + +```nix +# system.nix +{ + nomarchy.system.plymouth.enable = false; + nomarchy.system.greeter.enable = false; # bring your own login manager +} +``` + +See the option tables in the README for the full list. + +## 4. Last resort: `lib.mkForce` + +To override a value Nomarchy sets at normal priority (an appearance value, or +a whole list), raise your definition's priority: + +```nix +{ lib, ... }: +{ + # hardcode rounding against the theme (the CLI can no longer change it) + wayland.windowManager.hyprland.settings.decoration.rounding = lib.mkForce 0; + + # own the entire keybind list + wayland.windowManager.hyprland.settings.bind = lib.mkForce [ "$mod, Return, exec, kitty" ]; +} +``` + +If a plain assignment errors with *"has conflicting definition values"*, that +value is theme-owned at normal priority — either change it via the CLI (§1) or +`mkForce` it here. + +## Quick reference + +| You want to… | Do this | +|---|---| +| Change gaps / colors / rounding / fonts | `nomarchy-theme-sync set …` or `apply` | +| Change input / misc / monitor / animations / terminal chrome | plain assignment in `home.nix` | +| Add keybinds / autostarts | add to the `bind` / `exec-once` list (concatenates) | +| Replace all keybinds | `bind = lib.mkForce [ … ]` | +| Hardcode an appearance value against the theme | `lib.mkForce` in `home.nix` | +| Drop a whole component | `nomarchy..enable = false` | +| Raw Hyprland lines | `wayland.windowManager.hyprland.extraConfig` | diff --git a/modules/home/ghostty.nix b/modules/home/ghostty.nix index 01c45f5..1c54715 100644 --- a/modules/home/ghostty.nix +++ b/modules/home/ghostty.nix @@ -30,12 +30,14 @@ in palette = lib.imap0 (i: color: "${toString i}=${color}") t.ansi; # ── Chrome ──────────────────────────────────────────────────── + # background-opacity is theme-driven (normal priority — use the + # CLI); the rest are mkDefault so a plain home.nix value wins. background-opacity = t.ui.terminalOpacity; - window-padding-x = 12; - window-padding-y = 12; - window-decoration = false; - gtk-single-instance = true; - confirm-close-surface = false; + window-padding-x = lib.mkDefault 12; + window-padding-y = lib.mkDefault 12; + window-decoration = lib.mkDefault false; + gtk-single-instance = lib.mkDefault true; + confirm-close-surface = lib.mkDefault false; }; }; } diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index c567e83..542e7fd 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -35,8 +35,13 @@ in "$mod" = "SUPER"; "$terminal" = config.nomarchy.terminal; - monitor = [ ",preferred,auto,1" ]; + # mkDefault so a downstream `monitor = [...]` replaces it with a + # plain assignment (the live ISO uses mkForce for the same reason). + monitor = lib.mkDefault [ ",preferred,auto,1" ]; + # exec-once is a list at normal priority, so downstream additions + # CONCATENATE (your autostart runs alongside ours) rather than + # replace. Same for the bind* lists below. exec-once = [ # nixpkgs' swww is awww (renamed fork); the binary is awww-daemon. "awww-daemon" @@ -46,14 +51,19 @@ in ]; # ── Theme-driven look ────────────────────────────────────────── + # These flow from theme-state.json and stay at NORMAL priority: + # change them with `nomarchy-theme-sync set ui.` (the intended + # path), or lib.mkForce in home.nix to hardcode against the theme. + # The non-theme knobs around them are lib.mkDefault — override + # those with a plain home.nix assignment. See docs/OVERRIDES.md. general = { gaps_in = t.ui.gapsIn; gaps_out = t.ui.gapsOut; border_size = t.ui.borderSize; "col.active_border" = "${rgb c.accent} ${rgb c.accentAlt} 45deg"; "col.inactive_border" = rgb c.overlay; - layout = "dwindle"; - resize_on_border = true; + layout = lib.mkDefault "dwindle"; + resize_on_border = lib.mkDefault true; }; decoration = { @@ -62,25 +72,26 @@ in inactive_opacity = t.ui.inactiveOpacity; blur = { enabled = t.ui.blur; - size = 8; - passes = 2; - popups = true; + size = lib.mkDefault 8; + passes = lib.mkDefault 2; + popups = lib.mkDefault true; }; shadow = { enabled = t.ui.shadow; - range = 20; - render_power = 3; + range = lib.mkDefault 20; + render_power = lib.mkDefault 3; color = rgba c.mantle "aa"; }; }; + # ── Behaviour (mkDefault → plain home.nix assignment overrides) ── animations = { - enabled = true; - bezier = [ + enabled = lib.mkDefault true; + bezier = lib.mkDefault [ "smooth, 0.25, 0.1, 0.25, 1.0" "snappy, 0.6, 0.0, 0.1, 1.0" ]; - animation = [ + animation = lib.mkDefault [ "windows, 1, 4, snappy, popin 85%" "border, 1, 8, smooth" "fade, 1, 5, smooth" @@ -88,29 +99,30 @@ in ]; }; - # ── Behaviour ────────────────────────────────────────────────── input = { + # kb_layout/variant come from nomarchy.keyboard.* — set that + # option (the installer writes it; it also drives tty/LUKS). kb_layout = config.nomarchy.keyboard.layout; kb_variant = config.nomarchy.keyboard.variant; - follow_mouse = 1; - touchpad.natural_scroll = true; + follow_mouse = lib.mkDefault 1; + touchpad.natural_scroll = lib.mkDefault true; }; # dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo` # dispatcher still exists); setting it aborts config parsing. - dwindle.preserve_split = true; + dwindle.preserve_split = lib.mkDefault true; # Hyprland 0.51+ replaced gestures:workspace_swipe with the # gesture keyword — without this, touchpads have NO gestures. - gesture = [ + gesture = lib.mkDefault [ "3, horizontal, workspace" "4, pinch, fullscreen" ]; misc = { - disable_hyprland_logo = true; - disable_splash_rendering = true; - force_default_wallpaper = 0; + disable_hyprland_logo = lib.mkDefault true; + disable_splash_rendering = lib.mkDefault true; + force_default_wallpaper = lib.mkDefault 0; }; bind = [ diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index 2770db0..00cf348 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -145,14 +145,21 @@ in enable = true; systemd.enable = true; # started/stopped with graphical-session.target - settings.mainBar = + # mkDefault so downstream can replace the whole bar config/style with + # a plain home.nix assignment. For per-theme identity, prefer the + # whole-swap assets (themes//waybar.{jsonc,css}); for one-off + # tweaks of the generated bar, lib.mkForce a sub-key. See + # docs/OVERRIDES.md. + settings.mainBar = lib.mkDefault ( if hasConfigOverride then builtins.fromJSON (builtins.readFile configOverride) - else generatedSettings; + else generatedSettings + ); - style = + style = lib.mkDefault ( if hasStyleOverride then builtins.readFile styleOverride - else generatedStyle; + else generatedStyle + ); }; } From 76582c1ee95c5e5e97168799ed20fc1f6141bb12 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 14:32:11 +0100 Subject: [PATCH 14/51] =?UTF-8?q?feat:=20file=20manager=20=E2=80=94=20yazi?= =?UTF-8?q?=20(flagship=20TUI)=20+=20Thunar=20(GUI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Legacy shipped only Thunar (and a stray nautilus extension, mid-migration). This pairs a keyboard-driven TUI flagship that fits the distro's identity with the GUI half for point-and-click + the "open folder" handler. yazi (modules/home/yazi.nix, nomarchy.yazi.enable, SUPER+E, `y` shell wrapper cd's on exit): themed from theme-state.json (mgr/mode/status mapped from the palette; merges over yazi's defaults so it tracks the theme) with a curated, correctly-wired plugin set — full-border, git (setup + prepend_fetchers), smart-enter (l/Enter), chmod (cm), mount (M), compress (caa), mediainfo (audio/subtitle only — images/videos keep native thumbnails), glow (markdown previews). Preview/tooling deps: ffmpegthumbnailer, poppler-utils, p7zip, jq, fd, ripgrep, fzf, chafa, mediainfo, glow. Thunar (modules/nixos/file-manager.nix, nomarchy.system.fileManager.enable): GTK file manager (Stylix-themed) + archive/volman/media-tags plugins + gvfs/tumbler/udisks2 backends + thumbnailers + exo. udisks2 also backs yazi's mount plugin. Verified: flake check green, HM generation builds (yazi TOML/init.lua render, all 8 plugins resolve), every package present in the live ISO closure (offline install draws from it — no source-build leak). Not verified: yazi's runtime preview/plugin behaviour needs a real session (TUI; can't drive in CI here). Co-Authored-By: Claude Fable 5 --- README.md | 12 +++- modules/home/default.nix | 1 + modules/home/hyprland.nix | 1 + modules/home/options.nix | 1 + modules/home/yazi.nix | 126 +++++++++++++++++++++++++++++++++ modules/nixos/default.nix | 2 +- modules/nixos/file-manager.nix | 35 +++++++++ modules/nixos/options.nix | 6 ++ 8 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 modules/home/yazi.nix create mode 100644 modules/nixos/file-manager.nix diff --git a/README.md b/README.md index 450ff6a..33dd0a1 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. ├── modules/ │ ├── nixos/ # the distro, system side │ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts -│ │ └── options.nix # nomarchy.system.* toggles +│ │ ├── options.nix # nomarchy.system.* toggles +│ │ ├── plymouth.nix # boot splash, tinted from the JSON +│ │ └── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2 │ └── home/ # the distro, user side │ ├── default.nix # entry point │ ├── options.nix # nomarchy.* option surface @@ -57,7 +59,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── btop.nix │ ├── fuzzel.nix # launcher + nomarchy-menu (power, clip) │ ├── swaync.nix # notifications, same JSON -│ └── idle.nix # hyprlock + hypridle, same JSON +│ ├── idle.nix # hyprlock + hypridle, same JSON +│ └── yazi.nix # flagship TUI file manager + plugins ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -172,11 +175,13 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.fuzzel.enable` | `true` | Themed fuzzel + `nomarchy-menu` (power menu) | | `nomarchy.swaync.enable` | `true` | swaync notifications, themed | | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | +| `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | | `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.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) | | `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet | | `nomarchy.system.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | @@ -241,7 +246,8 @@ home-update # rebuild just the desktop layer Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu · -`SUPER+N` notifications · `SUPER+CTRL+V` clipboard history · `SUPER+Q` +`SUPER+E` file manager (yazi) · `SUPER+N` notifications · `SUPER+CTRL+V` +clipboard history · `SUPER+Q` close · `SUPER+1..9` workspaces · `Print` region screenshot. ## 6. Extending diff --git a/modules/home/default.nix b/modules/home/default.nix index e33dd6c..93419af 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -15,6 +15,7 @@ ./fuzzel.nix # launcher theming + the nomarchy-menu dispatcher ./swaync.nix # notification daemon, themed from the same JSON ./idle.nix # hyprlock + hypridle, themed from the same JSON + ./yazi.nix # flagship TUI file manager, themed + plugins ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 542e7fd..5b30454 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -128,6 +128,7 @@ in bind = [ "$mod, Return, exec, $terminal" "$mod, D, exec, fuzzel" + "$mod, E, exec, $terminal -e yazi" "$mod, Q, killactive" "$mod, F, fullscreen" "$mod, V, togglefloating" diff --git a/modules/home/options.nix b/modules/home/options.nix index 5f3aab0..ea7118d 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -66,6 +66,7 @@ fuzzel.enable = lib.mkEnableOption "Nomarchy's themed fuzzel + the nomarchy-menu dispatcher (power menu)" // { 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; }; 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; }; diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix new file mode 100644 index 0000000..03749e1 --- /dev/null +++ b/modules/home/yazi.nix @@ -0,0 +1,126 @@ +# yazi — the flagship file manager: a fast, keyboard-driven TUI that +# fits the distro's identity (Ghostty's Kitty-graphics previews, fuzzel +# menus, everything from one JSON). Themed from theme-state.json and +# shipped with a curated plugin set. The GUI half (Thunar, "open folder" +# handler) is nomarchy.system.fileManager on the system side. +# +# `y` (the shell wrapper) cd's the shell to yazi's last directory on +# exit; SUPER+E opens it in a fresh terminal. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; +in +{ + config = lib.mkIf cfg.yazi.enable { + # Preview + tooling deps yazi shells out to (thumbnails, search, + # archives, the plugins below). All small CLIs. + home.packages = with pkgs; [ + ffmpegthumbnailer # video thumbnails + poppler-utils # PDF previews (pdftoppm) + p7zip # archive preview + the compress plugin + jq # JSON preview + fd ripgrep fzf # navigation / search integrations + chafa # image fallback when the terminal lacks graphics + mediainfo # mediainfo plugin (audio/subtitle metadata) + glow # glow plugin (markdown rendering) + ]; + + programs.yazi = { + enable = true; + shellWrapperName = "y"; # `y` cd's on exit; `yazi` stays a no-cd launcher + enableBashIntegration = true; + + settings = { + mgr = { + show_hidden = false; + sort_by = "natural"; + sort_dir_first = true; + linemode = "size"; + }; + preview = { + max_width = 1000; + max_height = 1000; + }; + plugin = { + # git status in the linemode (fetcher registered before setup). + prepend_fetchers = [ + { id = "git"; url = "*"; run = "git"; } + { id = "git"; url = "*/"; run = "git"; } + ]; + # Route only audio + subtitles to mediainfo's text metadata; + # images/videos keep yazi's native visual thumbnails. Markdown + # renders through glow. + prepend_previewers = [ + { name = "*.md"; run = "glow"; } + { mime = "audio/*"; run = "mediainfo"; } + { mime = "application/subrip"; run = "mediainfo"; } + ]; + }; + }; + + keymap.mgr.prepend_keymap = [ + { on = "l"; run = "plugin smart-enter"; desc = "Enter dir or open file"; } + { on = ""; run = "plugin smart-enter"; desc = "Enter dir or open file"; } + { on = [ "c" "m" ]; run = "plugin chmod"; desc = "Chmod on selected files"; } + { on = "M"; run = "plugin mount"; desc = "Mount/unmount manager"; } + { on = [ "c" "a" "a" ]; run = "plugin compress"; desc = "Archive selected files"; } + ]; + + # Curated plugins (nixpkgs yaziPlugins). setup = true emits the + # require(...):setup() call in init.lua; the rest are invoked via + # the keymap/previewer/fetcher wiring above. + plugins = { + full-border = { package = pkgs.yaziPlugins.full-border; setup = true; }; + git = { package = pkgs.yaziPlugins.git; setup = true; }; + smart-enter = { package = pkgs.yaziPlugins.smart-enter; }; + chmod = { package = pkgs.yaziPlugins.chmod; }; + mount = { package = pkgs.yaziPlugins.mount; }; + compress = { package = pkgs.yaziPlugins.compress; }; + mediainfo = { package = pkgs.yaziPlugins.mediainfo; }; + glow = { package = pkgs.yaziPlugins.glow; }; + }; + + # Theme from the palette. yazi merges this over its built-in theme, + # so only the accent-bearing UI is specified; everything else keeps + # yazi's defaults (which already follow Ghostty's ANSI colors). + theme = { + mgr = { + cwd = { fg = c.accent; }; + hovered = { fg = c.base; bg = c.accent; }; + preview_hovered = { underline = true; }; + find_keyword = { fg = c.warn; italic = true; }; + find_position = { fg = c.accentAlt; bg = "reset"; italic = true; }; + marker_copied = { fg = c.good; bg = c.good; }; + marker_cut = { fg = c.bad; bg = c.bad; }; + marker_marked = { fg = c.accent; bg = c.accent; }; + marker_selected = { fg = c.warn; bg = c.warn; }; + count_copied = { fg = c.base; bg = c.good; }; + count_cut = { fg = c.base; bg = c.bad; }; + count_selected = { fg = c.base; bg = c.warn; }; + border_symbol = "│"; + border_style = { fg = c.muted; }; + }; + mode = { + normal_main = { fg = c.base; bg = c.accent; bold = true; }; + normal_alt = { fg = c.accent; bg = c.surface; }; + select_main = { fg = c.base; bg = c.good; bold = true; }; + select_alt = { fg = c.good; bg = c.surface; }; + unset_main = { fg = c.base; bg = c.accentAlt; bold = true; }; + unset_alt = { fg = c.accentAlt; bg = c.surface; }; + }; + status = { + perm_type = { fg = c.accent; }; + perm_read = { fg = c.good; }; + perm_write = { fg = c.bad; }; + perm_exec = { fg = c.warn; }; + progress_label = { fg = c.text; bold = true; }; + progress_normal = { fg = c.accent; bg = c.surface; }; + progress_error = { fg = c.bad; bg = c.surface; }; + }; + }; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 4beaa52..286b1bb 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -11,7 +11,7 @@ let cfg = config.nomarchy.system; in { - imports = [ ./options.nix ./plymouth.nix ]; + imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ]; config = { # The safe half of distro branding: distroName flows into diff --git a/modules/nixos/file-manager.nix b/modules/nixos/file-manager.nix new file mode 100644 index 0000000..33a46e2 --- /dev/null +++ b/modules/nixos/file-manager.nix @@ -0,0 +1,35 @@ +# GUI file manager — Thunar (lightweight GTK, Stylix-themed) plus the +# backend services a standalone compositor needs for it to work fully: +# gvfs (trash/mount/network), tumbler (thumbnails), udisks2 (removable +# media — also what yazi's mount plugin drives). The keyboard-driven TUI +# flagship is yazi (modules/home/yazi.nix); this is the point-and-click +# half and the XDG handler for "open folder" / "show in files". +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy.system; +in +{ + config = lib.mkIf cfg.fileManager.enable { + programs.thunar = { + enable = lib.mkDefault true; + plugins = with pkgs.xfce; [ + thunar-archive-plugin # create/extract archives from the context menu + thunar-volman # auto-manage removable drives/media + thunar-media-tags-plugin # rename/inspect audio by tags + ]; + }; + + services.gvfs.enable = lib.mkDefault true; # trash, mounts, network shares + services.tumbler.enable = lib.mkDefault true; # thumbnail generation + services.udisks2.enable = lib.mkDefault true; # mount/unmount removable media + + environment.systemPackages = with pkgs; [ + ffmpegthumbnailer # video thumbnails (Thunar + yazi) + libgsf # ODF thumbnails + poppler-utils # PDF thumbnails / pdftoppm (yazi PDF preview too) + xfce.exo # "Open Terminal Here" and default-app associations + shared-mime-info # MIME database + ]; + }; +} diff --git a/modules/nixos/options.nix b/modules/nixos/options.nix index 9ed548e..10634e8 100644 --- a/modules/nixos/options.nix +++ b/modules/nixos/options.nix @@ -37,6 +37,12 @@ ''; }; + fileManager.enable = lib.mkEnableOption '' + the Thunar GUI file manager + backend services (gvfs/tumbler/udisks2) + for point-and-click file management and the "open folder" handler. + The keyboard-driven TUI flagship (yazi) is the nomarchy.yazi.* home + option'' // { default = true; }; + audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; }; bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; }; From 961314ea866b9f3a59e5ed9546cee8291b70d692 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 14:41:46 +0100 Subject: [PATCH 15/51] feat(home): switch launcher/menu from fuzzel to rofi 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rofi 2.0 landed in nixpkgs 26.05 with native Wayland (mainline upstreamed the lbonn rofi-wayland fork — `wayland enabled`), so the original reason to prefer fuzzel (rofi being X11-only / needing a community fork) is gone. rofi's `.rasi` is far more expressive than fuzzel's flat INI, which lets the launcher be a proper themed hero surface and revives the legacy per-theme launcher designs. - modules/home/rofi.nix replaces fuzzel.nix: per-element theme generated from theme-state.json (accent border, highlighted selection, rounded inputbar) via lib.formats.rasi.mkLiteral; themes//rofi.rasi whole-swap; the nomarchy-menu dispatcher re-pointed to `rofi -dmenu` (power/theme/clipboard/calc with -mesg/files/web + root picker). - SUPER+D is now `rofi -show drun`; the menu binds are unchanged. - nomarchy.fuzzel.enable → nomarchy.rofi.enable; fuzzel dropped from home.packages. Pango handles nf-glyph fallback (no explicit font list needed as fuzzel's fcft did). show-icons off until an icon theme ships. Verified: flake check green, HM generation builds, the generated theme.rasi parses under `rofi -dump-theme`, Hyprland config verifies. Not verified: rofi's on-screen rendering needs a real session. Docs: README menu/override/roadmap sections and the module tree updated; the per-theme launcher roadmap item now points at porting the legacy rofi.rasi designs (the whole-swap mechanism is in place). Co-Authored-By: Claude Fable 5 --- .claude/settings.local.json | 267 ++++++++++++++++++++++++++++++++++++ README.md | 48 +++---- modules/home/default.nix | 3 +- modules/home/fuzzel.nix | 171 ----------------------- modules/home/hyprland.nix | 4 +- modules/home/options.nix | 2 +- modules/home/rofi.nix | 222 ++++++++++++++++++++++++++++++ modules/home/yazi.nix | 2 +- todo.md | 14 ++ 9 files changed, 532 insertions(+), 201 deletions(-) create mode 100644 .claude/settings.local.json delete mode 100644 modules/home/fuzzel.nix create mode 100644 modules/home/rofi.nix create mode 100644 todo.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..305e678 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,267 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -i \"\\\\.nix$\")", + "Bash(xargs ls *)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.activationPackage.outPath)", + "Bash(git *)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.activationScripts.home-manager-activate.text)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.installerIsoGraphical.config.system.activationScripts.home-manager-activate.text)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.config.system.build.toplevel.drvPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.system.build.toplevel.drvPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.activationPackage.drvPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.systemd.services.\"home-manager-nomarchy\".serviceConfig.ExecStart)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.default.config.system.build.vm --no-link --print-out-paths)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.xdg.configFile.\"nomarchy/current/background\".source)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.home-files.outPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.config.home-files --no-link --print-out-paths)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.systemd.user.startServices)", + "Bash(xxd)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#packages.x86_64-linux.allThemeVariants.drvPath)", + "Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/themes/engine/scripts/nomarchy-themes-prebuild)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.activationPackage --no-link --print-out-paths)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.config.home.packages.outPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.home-path.outPath)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr '\\(builtins.readDir ./themes/engine/scripts\\)' --json)", + "Bash(nix-build -I nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos /home/bernardo/Projects/Coding/Nomarchy/flake.nix --argstr attr nixosConfigurations.installerIsoGraphical)", + "Bash(awk '{print $NF}')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.hardware.pulseaudio.enable .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.enable .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.pulse.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.displayManager.sddm.enable .#nixosConfigurations.installerIsoGraphical.config.services.displayManager.defaultSession .#nixosConfigurations.installerIsoGraphical.config.programs.hyprland.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --json .#nixosConfigurations.installerIsoGraphical.config.users.users.nixos.extraGroups)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.hardware.pulseaudio.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.pulse.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.programs.hyprland.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval '.#nixosConfigurations.installerIsoGraphical.config.__TRACKED_VAR__')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.home-manager.users.nixos.home.packages --apply 'builtins.length')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.config.home-manager.users.nomarchy.home.packages --apply 'builtins.length')", + "Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils/nomarchy-test-live-iso)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --json .#nixosConfigurations.installerIsoGraphical.config.boot.initrd.kernelModules)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.programs.waybar.systemd.enable)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.systemd.user.services --apply 'svcs: builtins.concatStringsSep \"\\\\n\" \\(builtins.attrNames svcs\\)')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.gtk.iconTheme.package.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw nixpkgs#everforest-gtk-variant.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' search nixpkgs everforest)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-variant.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-theme.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-variant)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr 'let pkgs = import \\(builtins.getFlake \"/home/bernardo/Projects/Coding/Nomarchy\"\\).inputs.nixpkgs { system = \"x86_64-linux\"; config.allowUnfree = true; }; in builtins.attrNames \\(pkgs // {} \\)')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr 'let pkgs = import \\(builtins.getFlake \"/home/bernardo/Projects/Coding/Nomarchy\"\\).inputs.nixpkgs { system = \"x86_64-linux\"; config.allowUnfree = true; }; in builtins.concatStringsSep \"\\\\n\" \\(builtins.filter \\(n: builtins.match \".*[Ee]verforest.*\" n != null\\) \\(builtins.attrNames pkgs\\)\\)' --raw)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.gtk.iconTheme.name)", + "Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)", + "Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/installer/hardware-db.sh)", + "Read(//tmp/**)", + "Bash(bash -c ' *)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#overlays.default --apply 'o: toString \\(builtins.typeOf o\\)')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#overlays.default --apply 'o: \"overlay present: \" + builtins.typeOf o')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.nixos.distroName)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.nixos.distroId)", + "Bash(nix-instantiate --parse /home/bernardo/Projects/Coding/Nomarchy/installer/disko-golden.nix)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.installerIsoGraphical.config.system.build.toplevel --no-link --print-out-paths)", + "Bash(bash /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh --help)", + "Bash(bash -c 'echo \"HOSTNAME=$HOSTNAME\"')", + "Bash(bash -c 'echo \"HOSTNAME default=$HOSTNAME\"')", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.installerIsoGraphical.config.services.getty.helpLine)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.voxtype.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.opencode.pname)", + "Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.default.config.system.build.vm --no-link)", + "Bash(nix log *)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --json --impure --expr ' *)", + "Bash(cat)", + "Bash(chmod +x /tmp/theme_comparison.sh)", + "Bash(/tmp/theme_comparison.sh)", + "Bash(xargs -I {} dirname {})", + "Bash(xargs -I {} basename {})", + "Bash(chmod +x /tmp/color_compare.sh)", + "Bash(bash /tmp/color_compare.sh)", + "Bash(nix --extra-experimental-features \"nix-command flakes\" eval --raw 'github:nix-community/disko/latest#packages.x86_64-linux.disko.outPath')", + "Bash(find /nix/store/*disko*/lib/disko -name \"luks*\")", + "Bash(nix flake *)", + "Bash(nix-instantiate --parse /home/bernardo/Projects/Coding/Nomarchy/flake.nix)", + "Bash(nix eval *)", + "Bash(bash installer/install.sh --dry-run)", + "Bash(sudo bash *)", + "Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/core/system/scripts/nomarchy-env-update)", + "Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/core/system/scripts/nomarchy-env-update)", + "Bash(sed -n '1450,1500p' /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)", + "Bash(grep -A 5 'nixosConfigurations.$HOSTNAME' /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)", + "Bash(mkdir -p /tmp/globtest)", + "Bash(touch nvme0n1 *)", + "Bash(shopt -s nullglob)", + "Bash(bash -n installer/install.sh)", + "Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-live.config.system.build.toplevel.outPath)", + "Bash(nix --extra-experimental-features \"nix-command flakes\" eval --impure --expr 'let f = import ./core/system/impermanence.nix; in \\(f { config = { nomarchy.system.impermanence = { enable = true; mainLuksName = \"crypted_main\"; }; }; lib = \\(import {}\\).lib; pkgs = null; inputs = { impermanence.nixosModules.impermanence = {}; }; }\\).config.boot.initrd.postDeviceCommands')", + "Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-installer.config.system.build.toplevel.outPath)", + "Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-live.config.nomarchy.system.theme)", + "Bash(chmod +x *)", + "Bash(./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md)", + "Bash(awk '/^## Missing references/,/^## Menu items/' docs/SCRIPTS.md)", + "Bash(bash -n bin/utils/nomarchy-docs-scripts)", + "Bash(bash -n features/scripts/utils/nomarchy-docs-scripts)", + "Bash(bash -n themes/engine/scripts/nomarchy-theme-next)", + "Bash(grep '`unused?`' docs/SCRIPTS.md)", + "Bash(awk -F'|' '{print $2}')", + "Bash(awk *)", + "Bash(grep -c '`unused?`' docs/SCRIPTS.md)", + "Bash(grep -n '`unused?`' docs/SCRIPTS.md)", + "Bash(grep '^| `__TRACKED_VAR__` ' docs/SCRIPTS.md)", + "Bash(nix --extra-experimental-features 'nix-command flakes' flake check --no-build)", + "Bash(grep -c '`kept`' docs/SCRIPTS.md)", + "Bash(nix shell *)", + "Bash(nix shell 'nixpkgs#shellcheck' --command bash -c ' *)", + "Bash(./bin/utils/nomarchy-docs-scripts --out /tmp/SCRIPTS.regen.md)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr ' *)", + "Bash(sed -n '100,108p' core/home/options.nix)", + "Bash(sed -n '185,195p' core/system/options.nix)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --json --expr ' *)", + "Bash(bash -n *)", + "Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --raw --expr ' *)", + "Bash(grep '^| `nomarchy-manual`' docs/SCRIPTS.md)", + "Bash(nix *)", + "Bash(sudo -n cat /etc/environment)", + "Bash(systemctl --user show-environment)", + "Bash(./bin/utils/nomarchy-docs-keybindings --out /tmp/keybindings-fresh.md)", + "Bash(bash bin/utils/nomarchy-docs-keybindings --out /tmp/keybindings-fresh.md)", + "Bash(nix-shell -p shellcheck --run \"shellcheck --severity=error features/scripts/utils/nomarchy-menu features/scripts/utils/nomarchy-launch-screensaver\")", + "Bash(./bin/utils/nomarchy-docs-keybindings --out docs/KEYBINDINGS.md)", + "Bash(grep -rEho 'jq[^\"]*--arg [a-zA-Z]+|\\\\.[a-zA-Z][a-zA-Z_0-9]+ = \\\\$|jq -r .\\\\.[a-zA-Z]+' features/scripts core/system/scripts themes/engine/scripts)", + "Read(//home/bernardo/.config/mako/**)", + "Read(//home/bernardo/.config/nomarchy/default/hypr/**)", + "Read(//home/bernardo/.config/nomarchy/current/theme/**)", + "Read(//etc/nixos/**)", + "Bash(sudo git -C /etc/nixos log --oneline -3)", + "Bash(PATH=/home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils:/home/bernardo/Projects/Coding/Nomarchy/themes/engine/scripts:__TRACKED_VAR__ bash /home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils/nomarchy-installed-summary)", + "Bash(grep *)", + "Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); r=d['results'][0]['extensions'][0]; print\\('version:', r['versions'][0]['version']\\); print\\('displayName:', r['displayName']\\); print\\('publisher:', r['publisher']['publisherName']\\); print\\('extName:', r['extensionName']\\)\")", + "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(x['file'],'=',x['value']\\) for x in d]\")", + "Bash([ -f \"core/home/config/nomarchy/default/hypr/apps/$f.conf\" ])", + "Bash([ -f \"core/home/config/nomarchy/default/hypr/bindings/$f.conf\" ])", + "Bash([ -f \"core/home/config/nomarchy/default/hypr/$f.conf\" ])", + "Bash([ -f \"features/desktop/hyprland/config/$f.conf\" ])", + "Bash(timeout 580 ./bin/utils/nomarchy-eval-matrix)", + "Bash(break)", + "Bash(sed -n 1,45p __TRACKED_VAR__/nix/package.nix)", + "Bash(sed -n 1,50p __TRACKED_VAR__/nix/modules/home-manager.nix)", + "Bash(python3 -c 'import json,sys; d=json.load\\(sys.stdin\\); print\\('\\\\''top path:'\\\\'', d.get\\('\\\\''path'\\\\'','\\\\''?'\\\\''\\)\\); inp=d.get\\('\\\\''inputs'\\\\'',{}\\); print\\('\\\\''inputs:'\\\\'', list\\(inp.keys\\(\\)\\)\\); print\\('\\\\''walker present:'\\\\'', '\\\\''walker'\\\\'' in inp\\); *)", + "Bash(echo \"exit: $?\")", + "Bash(echo 'output: [__TRACKED_VAR__]')", + "Bash(time ./bin/utils/nomarchy-eval-matrix)", + "Bash(python3 -c \"import json,sys; [print\\(x\\) for x in sorted\\(json.load\\(sys.stdin\\)\\)]\")", + "Read(//dev/**)", + "Bash(command -v qemu-system-x86_64 qemu-kvm)", + "Bash(echo \"BUILD_EXIT=$?\")", + "Bash(rm -f /tmp/nomarchy-mon.sock)", + "Bash(mkdir -p /tmp/nomarchy-vmrun)", + "Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' /tmp/nomarchy-vm/bin/run-nomarchy-vm)", + "Bash(echo \"VM_EXIT=$?\")", + "Bash(magick /tmp/shot.png -crop 5120x130+0+0 +repage /tmp/shot-top.png)", + "Bash(magick /tmp/shot.png -crop 1400x420+0+440 +repage /tmp/shot-term.png)", + "Bash(/nix/store/wv8bpzriikv65xnd1vciqpq7rnr8h2q2-bash-5.3p3/bin/bash -c 'compgen -G \"/etc/*\" >/dev/null 2>&1 && echo \"compgen WORKS\" || echo \"compgen MISSING \\(progcomp disabled\\)\"')", + "Bash(compgen -G \"/etc/*\")", + "Bash(/nix/store/wv8bpzriikv65xnd1vciqpq7rnr8h2q2-bash-5.3p3/bin/bash -c 'shopt -s nullglob; a=\\(/etc/hos*\\); echo \"nullglob ok: ${#a[@]} match\\(es\\)\"')", + "Bash(mkdir -p /tmp/nomarchy-vmrun2)", + "Bash(pgrep -f qemu-system)", + "Bash(rm -rf /tmp/nomarchy-vmrun3)", + "Bash(mkdir -p /tmp/nomarchy-vmrun3)", + "Bash(echo \"exit=$?\")", + "Bash(socat - unix-connect:/tmp/nomarchy-mon.sock)", + "Bash(magick /tmp/shot3.ppm /tmp/shot3.png)", + "Bash(magick identify *)", + "Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot3.png\\)\")", + "Bash(magick /tmp/shot3.png -crop 5120x150+0+0 +repage /tmp/shot3-top.png)", + "Bash(pkill -x qemu-system-x86)", + "Bash(pkill -f \"qemu-system-x86_64 -machine\")", + "Bash(rm -rf /tmp/nomarchy-vmrun4)", + "Bash(mkdir -p /tmp/nomarchy-vmrun4)", + "Bash(echo \"boot-exit=$?\")", + "Bash(magick /tmp/shot4.ppm /tmp/shot4.png)", + "Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot4.png\\)\")", + "Bash(magick /tmp/shot4.png -crop 5120x160+0+0 +repage /tmp/shot4-top.png)", + "Bash(rm -rf /tmp/vmrun_final)", + "Bash(mkdir -p /tmp/vmrun_final)", + "Bash(magick /tmp/shot5.ppm /tmp/shot5.png)", + "Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot5.png\\)\")", + "Bash(magick /tmp/shot5.png -crop 5120x170+0+0 +repage /tmp/shot5-top.png)", + "Bash(rm -rf /tmp/vmssh)", + "Bash(mkdir -p /tmp/vmssh)", + "Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' QEMU_NET_OPTS=hostfwd=tcp::2222-:22 /tmp/nomarchy-vm-ssh/bin/run-nixos-vm)", + "Bash(command -v expect)", + "Read(//home/bernardo/.ssh/**)", + "Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' QEMU_NET_OPTS=hostfwd=tcp::2222-:22 /tmp/nomarchy-vm-ssh/bin/run-nomarchy-vm)", + "Bash(echo \"vm-exited rc=$?\")", + "Bash(pkill -f \"run-nomarchy-vm\")", + "Bash(rm -rf /tmp/vmtheme)", + "Bash(mkdir -p /tmp/vmtheme)", + "Bash(echo \"vm exited rc=$?\")", + "Bash(magick /tmp/latte.ppm -resize 1700 /tmp/latte.png)", + "Bash(echo \"saved $\\(magick identify -format '%wx%h' /tmp/latte.png\\)\")", + "Bash(./bin/utils/nomarchy-eval-matrix)", + "Bash(rm -f /tmp/nomarchy.qcow2 /tmp/vm-boot.log)", + "Bash(NIX_DISK_IMAGE=/tmp/nomarchy.qcow2 QEMU_OPTS='-display none -m 4096' /tmp/vm-verify-result/bin/run-nomarchy-vm)", + "Bash(echo \"VM PID $!\")", + "Bash(pkill -f 'qemu-system-x86_64 -machine accel=kvm.*nomarchy')", + "Bash(rm -f /tmp/vm-verify.nix /tmp/nomarchy.qcow2 /tmp/vm-boot.log /tmp/vm-verify-result)", + "Bash(pkill -9 -f 'qemu-system')", + "Bash(rm -f /tmp/nomarchy.qcow2)", + "Bash(python3 -)", + "Bash(sed -n '206,228p' features/scripts/utils/nomarchy-menu)", + "Bash(pkill -9 -f 'run-nixos-vm\\\\|qemu-system-x86_64.*nomarchy')", + "Bash(rm -f /tmp/nomarchy.qcow2 /tmp/vm-verify.nix /tmp/vm-boot.log /tmp/menu-*.sh /tmp/layout-check.sh /tmp/validate-fixes.sh /tmp/menu-cmds.txt /tmp/shipped.txt)", + "Bash(rm -f /tmp/vm-verify-result)", + "Bash(kill -9 1285354)", + "Bash(sed -n '1,30p' features/scripts/battery-monitor.nix)", + "Bash(sed -n '40,75p' features/desktop/waybar/default.nix)", + "Bash(sed -n '140,160p' features/desktop/waybar/themes/summer-night/config.jsonc)", + "Bash(sed -n '10,20p' features/desktop/waybar/config/config.jsonc)", + "Bash(python3 -m json.tool)", + "Bash(/tmp/nomarchy-multidisk-test/run-vm.sh)", + "Bash(socat - UNIX-CONNECT:mon.sock)", + "Bash(/tmp/nomarchy-multidisk-test/boot-from-disk.sh)", + "Bash(socat - UNIX-CONNECT:bootmon.sock)", + "Bash(convert /tmp/nomarchy-multidisk-test/screen.ppm /tmp/nomarchy-multidisk-test/screen.png)", + "Bash(magick /tmp/nomarchy-multidisk-test/screen.ppm /tmp/nomarchy-multidisk-test/screen.png)", + "Bash(shellcheck --severity=error installer/install.sh)", + "Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=0d02ec1d0a05f88ef9e74b516842900c41f0f2fe\")", + "Bash(python3 -c \"import json,sys;[print\\(x['name']\\) for x in json.load\\(sys.stdin\\)]\")", + "Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=release-25.11\")", + "Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=49ca96b2714c5931e17401eff87f3edd42d2b0f2\")", + "Bash(python3 -c \"import json,sys;d=json.load\\(sys.stdin\\);[print\\(x['name']\\) for x in d]\")", + "Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://raw.githubusercontent.com/nix-community/home-manager/49ca96b2714c5931e17401eff87f3edd42d2b0f2/modules/programs/vscode/each-config.nix\")", + "Read(//nix/store/**)", + "Bash(mkdir -p __TRACKED_VAR__/nomarchy-install-test)", + "Bash(qemu-img create *)", + "Read(//home/bernardo/.config/nomarchy/**)", + "Read(//home/bernardo/.local/share/nomarchy/**)", + "Read(//home/bernardo/.config/elephant/**)", + "Read(//home/bernardo/.local/share/elephant/**)", + "Read(//home/bernardo/.config/walker/**)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(python3 -c ' *)", + "Bash(nix-store -r /nix/store/hydnry53i2qbmqq0b0872wlbzdkpq1bd-source)", + "Bash(echo \"EXIT=$?\")", + "Bash(sed -n '44,50p' features/default.nix)", + "Bash(sed -n '14,20p' features/scripts/default.nix)", + "Bash(sed -n '11,17p' themes/engine/scripts.nix)", + "WebSearch", + "WebFetch(domain:github.com)", + "Bash(curl -sL \"https://api.github.com/repos/hyprwm/Hyprland/commits?sha=v0.55.3&per_page=100\")", + "Bash(curl -sL -o /tmp/hypr-fence-fix.patch \"https://github.com/hyprwm/Hyprland/commit/bdf0aaa5409fe2e4715e2796d669e16776de0233.patch\")", + "Bash(rm -f /tmp/nomarchy-2605.qcow2 /tmp/nomarchy-mon.sock /tmp/nomarchy-serial.sock)", + "Bash(NIX_DISK_IMAGE=/tmp/nomarchy-2605.qcow2 QEMU_OPTS=\"-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -serial unix:/tmp/nomarchy-serial.sock,server,nowait\" /tmp/nomarchy-vm-2605/bin/run-nomarchy-vm)", + "Bash(python3 *)", + "Bash(curl -sL https://raw.githubusercontent.com/hyprwm/Hyprland/v0.55.3/CMakeLists.txt)", + "Bash(curl -sL https://raw.githubusercontent.com/hyprwm/Hyprland/v0.55.2/CMakeLists.txt)", + "Bash(NIX_DISK_IMAGE=/tmp/nomarchy-2605.qcow2 QEMU_OPTS=\"-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -serial unix:/tmp/nomarchy-serial.sock,server,nowait\" /tmp/nomarchy-vm-2605-fixed/bin/run-nomarchy-vm)", + "Bash(xargs du -sh)", + "Bash(xargs du -csh)", + "Bash(curl -s https://raw.githubusercontent.com/nix-community/stylix/release-26.05/flake.nix)", + "Bash(nix build *)", + "Bash(echo \"exit $?\")", + "Bash(pkill -f \"test-install.sh\")", + "Bash(pkill -f \"qemu-system-x86_64.*NOMARCHY\\\\|qemu-system-x86_64.*nomarchy\")", + "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-night --no-switch)", + "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-day --no-switch)", + "Bash(xargs cat)" + ] + } +} diff --git a/README.md b/README.md index 33dd0a1..7673393 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── waybar.nix │ ├── ghostty.nix │ ├── btop.nix -│ ├── fuzzel.nix # launcher + nomarchy-menu (power, clip) +│ ├── rofi.nix # launcher + nomarchy-menu (power, clip) │ ├── swaync.nix # notifications, same JSON │ ├── idle.nix # hyprlock + hypridle, same JSON │ └── yazi.nix # flagship TUI file manager + plugins @@ -172,7 +172,7 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.keyboard.variant` | `""` | XKB variant for the session | | `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config | | `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar | -| `nomarchy.fuzzel.enable` | `true` | Themed fuzzel + `nomarchy-menu` (power menu) | +| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher | | `nomarchy.swaync.enable` | `true` | swaync notifications, themed | | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | | `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | @@ -224,7 +224,7 @@ per theme — a single place to look, unlike the old distro's split: | `btop.theme` | baked into the generation (generated from the palette when absent) | | `waybar.css` | **whole-swap**: replaces the generated bar style entirely (probed at eval time, self-contained) | | `waybar.jsonc` | whole-swap for the bar *layout* (must be plain JSON) | -| `fuzzel.ini` | **whole-swap**: replaces the generated launcher/menu config entirely | +| `rofi.rasi` | **whole-swap**: replaces the generated launcher/menu theme entirely | Six ported themes ship a `waybar.css` identity (catppuccin, lumon, nord, retro-82, summer-day, summer-night). Custom user themes can live in @@ -261,37 +261,37 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. ## Roadmap - **Menu system** (apps launcher + theme switching + system actions), built - on fuzzel — already shipped for SUPER+D/SUPER+T, dmenu mode does nested - menus and per-entry icons, and its INI theme can be baked from - theme-state.json like every other app: - - ✓ shipped: `modules/home/fuzzel.nix` (palette/fonts/border from the - theme JSON, `themes//fuzzel.ini` whole-swap) and the + on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from + theme-state.json like every other app, with rich per-element styling: + - ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from + the palette, `themes//rofi.rasi` whole-swap) and the `nomarchy-menu` dispatcher: root picker (no args) · `power` (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` - (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo) + (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo). + SUPER+D is `rofi -show drun`. - next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ - screenshot · **ask Claude**: `--prompt-only` → - `$TERMINAL -e claude ""` — the claude CLI auths via OAuth - against a Pro/Max subscription (no API key); REPL stays open for - follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on) - - known limits accepted for now: no live-as-you-type results (dmenu - input is static per invocation) and no image previews - - decision record: this resolves the old Walker/Lua question in favor of - simplicity — no GTK4 launcher, no second theming pipeline, no image - previews (instant `bg next` covers wallpaper browsing); the dispatcher - owns the menu structure, so the renderer stays swappable + screenshot · **ask Claude**: free-text → `$TERMINAL -e claude + ""` — the claude CLI auths via OAuth against a Pro/Max + subscription (no API key); REPL stays open for follow-ups (claude-code + in nixpkgs is unfree — allowUnfree is on) + - launcher icons: `show-icons` is off (no icon theme shipped yet); + enabling it well means shipping an icon theme (Papirus/Adwaita) + - decision record: resolves the old Walker/Lua question — no GTK4 + launcher, no second theming pipeline; the dispatcher owns the menu + structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 + once mainline gained native Wayland, for its richer theming) - **Theme parity with legacy:** summer-day/night now carry their legacy bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, logo button opens nomarchy-menu); the other four identity themes are palette recolors and already match. Remaining: a visual pass over all six on the live ISO -- **Per-theme fuzzel identity:** the `themes//fuzzel.ini` whole-swap - mechanism ships; what remains is authoring the inis for the six identity - themes from their legacy rofi.rasi files (per-element colors like - summer-night's green inputbar aren't expressible in fuzzel's flat - scheme — approximate, note the rest as a renderer limitation) +- **Per-theme rofi identity:** the `themes//rofi.rasi` whole-swap + mechanism ships; what remains is porting the legacy `rofi.rasi` designs + (summer-day/night already have them in the legacy branch) for the + identity themes — rofi's `.rasi` expresses their per-element looks + (green inputbar, inverted window) directly, unlike the old flat scheme - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme variants if still needed diff --git a/modules/home/default.nix b/modules/home/default.nix index 93419af..4cefbfa 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -12,7 +12,7 @@ ./waybar.nix ./ghostty.nix ./btop.nix - ./fuzzel.nix # launcher theming + the nomarchy-menu dispatcher + ./rofi.nix # launcher theming + the nomarchy-menu dispatcher ./swaync.nix # notification daemon, themed from the same JSON ./idle.nix # hyprlock + hypridle, themed from the same JSON ./yazi.nix # flagship TUI file manager, themed + plugins @@ -31,7 +31,6 @@ home.packages = with pkgs; [ awww # wallpaper daemon with animated transitions (the swww fork) - fuzzel # launcher / dmenu (used by the theme picker bind) libnotify ]; diff --git a/modules/home/fuzzel.nix b/modules/home/fuzzel.nix deleted file mode 100644 index eee8416..0000000 --- a/modules/home/fuzzel.nix +++ /dev/null @@ -1,171 +0,0 @@ -# Fuzzel — launcher, dmenu renderer for the menu system, themed from -# theme-state.json like every other surface. Also home of nomarchy-menu, -# the dispatcher script all menu modules grow out of. -# -# Per-theme identity: themes//fuzzel.ini is a whole-swap (like -# waybar.css) — it replaces the generated config entirely, probed at -# eval time. -{ config, lib, pkgs, ... }: - -let - cfg = config.nomarchy; - t = cfg.theme; - c = t.colors; - - # Per-theme override probe (same convention as waybar.nix). - iniOverride = cfg.themesDir + "/${t.slug}/fuzzel.ini"; - hasIniOverride = builtins.pathExists iniOverride; - - # fuzzel.ini colors are rrggbbaa, no leading #. - hexa = color: alpha: "${lib.removePrefix "#" color}${alpha}"; - - nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' - # Nomarchy menu dispatcher — thin presentation layer over - # `fuzzel --dmenu`; actions delegate to nomarchy-theme-sync, - # systemctl, hyprctl and friends. Icons are nf-md glyphs (any - # shipped Nerd Font carries them). `nomarchy-menu` with no - # argument shows the module picker. - - urlencode() { - local s="$1" out="" ch - for ((i = 0; i < ''${#s}; i++)); do - ch=''${s:i:1} - case "$ch" in - [a-zA-Z0-9.~_-]) out+="$ch" ;; - ' ') out+="+" ;; - *) printf -v ch '%%%02X' "'$ch"; out+="$ch" ;; - esac - done - printf '%s' "$out" - } - - case "''${1:-}" in - power) - choice=$(printf '%s\n' \ - "󰌾 Lock" \ - "󰍃 Logout" \ - "󰤄 Suspend" \ - "󰒲 Hibernate" \ - "󰜉 Reboot" \ - "󰐥 Shutdown" \ - | fuzzel --dmenu --prompt "power " --lines 6 --width 24) || exit 0 - case "$choice" in - *Lock) loginctl lock-session ;; - *Logout) hyprctl dispatch exit ;; - *Suspend) systemctl suspend ;; - *Hibernate) systemctl hibernate ;; - *Reboot) systemctl reboot ;; - *Shutdown) systemctl poweroff ;; - esac ;; - - theme) - choice=$(nomarchy-theme-sync list \ - | fuzzel --dmenu --prompt "theme ") || exit 0 - [ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;; - - clipboard) - sel=$(cliphist list | fuzzel --dmenu --prompt "clip " --width 60) || exit 0 - printf '%s' "$sel" | cliphist decode | wl-copy ;; - - calc) - expr=$(fuzzel --prompt-only "= ") || exit 0 - [ -n "$expr" ] || exit 0 - result=$(qalc -t -- "$expr" 2>&1 | tail -n 1) - choice=$(printf '%s\n' "󰆏 Copy result" "󰃬 New calculation" \ - | fuzzel --dmenu --lines 2 --width 40 \ - --prompt "= " --mesg "$expr = $result") || exit 0 - case "$choice" in - *Copy*) printf '%s' "$result" | wl-copy ;; - *New*) exec "$0" calc ;; - esac ;; - - files) - sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ - | head -n 50000 \ - | sed "s|^$HOME/||" \ - | fuzzel --dmenu --prompt "file " --width 60) || exit 0 - [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; - - web) - q=$(fuzzel --prompt-only "search ") || exit 0 - [ -n "$q" ] || exit 0 - exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;; - - "") - choice=$(printf '%s\n' \ - "󰀻 Apps" \ - "󰏘 Theme" \ - "󰅌 Clipboard" \ - "󰃬 Calculator" \ - "󰈞 Files" \ - "󰖟 Web search" \ - "󰐥 Power" \ - | fuzzel --dmenu --prompt "menu " --lines 7 --width 24) || exit 0 - case "$choice" in - *Apps*) exec fuzzel ;; - *Theme*) exec "$0" theme ;; - *Clipboard*) exec "$0" clipboard ;; - *Calc*) exec "$0" calc ;; - *Files*) exec "$0" files ;; - *Web*) exec "$0" web ;; - *Power*) exec "$0" power ;; - esac ;; - - *) - echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2 - exit 64 ;; - esac - ''; -in -{ - config = lib.mkIf cfg.fuzzel.enable { - home.packages = [ - nomarchy-menu - pkgs.libqalculate # qalc, the calc module's engine - pkgs.fd # files module - pkgs.xdg-utils # xdg-open for files/web - ]; - - programs.fuzzel = { - enable = true; - # Whole-swap themes bring their own fuzzel.ini; otherwise the - # config is generated from the palette. - settings = lib.mkIf (!hasIniOverride) { - main = { - # UI font first; the mono Nerd Font follows so menu icons - # (nf-md glyphs) resolve — fcft falls back per glyph. - font = "${t.fonts.ui}:size=${toString (t.fonts.size + 1)}, ${t.fonts.mono}:size=${toString (t.fonts.size + 1)}"; - terminal = cfg.terminal; - layer = "overlay"; - lines = 12; - width = 40; - horizontal-pad = 24; - vertical-pad = 16; - inner-pad = 8; - }; - - colors = { - background = hexa c.base "f2"; - text = hexa c.text "ff"; - prompt = hexa c.subtext "ff"; - placeholder = hexa c.muted "ff"; - input = hexa c.text "ff"; - match = hexa c.accent "ff"; - selection = hexa c.surface "ff"; - selection-text = hexa c.text "ff"; - selection-match = hexa c.accent "ff"; - border = hexa c.accent "ee"; - }; - - border = { - width = t.ui.borderSize; - radius = t.ui.rounding; - }; - }; - }; - - xdg.configFile."fuzzel/fuzzel.ini" = lib.mkIf hasIniOverride { - source = iniOverride; - }; - }; -} diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 5b30454..1a57669 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -127,7 +127,7 @@ in bind = [ "$mod, Return, exec, $terminal" - "$mod, D, exec, fuzzel" + "$mod, D, exec, rofi -show drun" "$mod, E, exec, $terminal -e yazi" "$mod, Q, killactive" "$mod, F, fullscreen" @@ -140,7 +140,7 @@ in # Cycle the current theme's wallpapers (instant, no rebuild). "$mod SHIFT, T, exec, nomarchy-theme-sync bg next" - # Power menu (fuzzel dmenu via the nomarchy-menu dispatcher). + # Power menu (rofi dmenu via the nomarchy-menu dispatcher). # Not Escape: Super+Escape gets swallowed before reaching the # dispatcher on some setups (input methods / modal handling). "$mod, X, exec, nomarchy-menu power" diff --git a/modules/home/options.nix b/modules/home/options.nix index ea7118d..34c0432 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -63,7 +63,7 @@ # ── Component toggles ────────────────────────────────────────── hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; }; waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; }; - fuzzel.enable = lib.mkEnableOption "Nomarchy's themed fuzzel + the nomarchy-menu dispatcher (power menu)" // { 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; }; diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix new file mode 100644 index 0000000..25e598b --- /dev/null +++ b/modules/home/rofi.nix @@ -0,0 +1,222 @@ +# rofi (2.0, native Wayland on 26.05) — launcher + dmenu renderer for +# the menu system, themed from theme-state.json. rofi's .rasi is far more +# expressive than a flat scheme, so the generated theme styles each +# element (accent border, highlighted selection, rounded inputbar); a +# themes//rofi.rasi whole-swap replaces it entirely. +# +# Home of nomarchy-menu, the dispatcher all menu modules hang off. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; + inherit (config.lib.formats.rasi) mkLiteral; + + px = n: mkLiteral "${toString n}px"; + + # Per-theme override probe (same convention as waybar.css). + rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi"; + hasRasiOverride = builtins.pathExists rasiOverride; + + nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' + # Nomarchy menu dispatcher — thin presentation layer over + # `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl, + # hyprctl and friends. Icons are nf-md glyphs (Pango falls back to a + # Nerd Font for them). `nomarchy-menu` with no argument shows the + # module picker. + + urlencode() { + local s="$1" out="" ch i + for ((i = 0; i < ''${#s}; i++)); do + ch=''${s:i:1} + case "$ch" in + [a-zA-Z0-9.~_-]) out+="$ch" ;; + ' ') out+="+" ;; + *) printf -v ch '%%%02X' "'$ch"; out+="$ch" ;; + esac + done + printf '%s' "$out" + } + + case "''${1:-}" in + power) + choice=$(printf '%s\n' \ + "󰌾 Lock" \ + "󰍃 Logout" \ + "󰤄 Suspend" \ + "󰒲 Hibernate" \ + "󰜉 Reboot" \ + "󰐥 Shutdown" \ + | rofi -dmenu -p power) || exit 0 + case "$choice" in + *Lock) loginctl lock-session ;; + *Logout) hyprctl dispatch exit ;; + *Suspend) systemctl suspend ;; + *Hibernate) systemctl hibernate ;; + *Reboot) systemctl reboot ;; + *Shutdown) systemctl poweroff ;; + esac ;; + + theme) + choice=$(nomarchy-theme-sync list | rofi -dmenu -p theme) || exit 0 + [ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;; + + clipboard) + sel=$(cliphist list | rofi -dmenu -p clip) || exit 0 + printf '%s' "$sel" | cliphist decode | wl-copy ;; + + calc) + expr=$(rofi -dmenu -p "= " < /dev/null) || exit 0 + [ -n "$expr" ] || exit 0 + result=$(qalc -t -- "$expr" 2>&1 | tail -n 1) + choice=$(printf '%s\n' "󰆏 Copy result" "󰃬 New calculation" \ + | rofi -dmenu -p "= " -mesg "$expr = $result") || exit 0 + case "$choice" in + *Copy*) printf '%s' "$result" | wl-copy ;; + *New*) exec "$0" calc ;; + esac ;; + + files) + sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ + | head -n 50000 \ + | sed "s|^$HOME/||" \ + | rofi -dmenu -p file) || exit 0 + [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; + + web) + q=$(rofi -dmenu -p search < /dev/null) || exit 0 + [ -n "$q" ] || exit 0 + exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;; + + "") + choice=$(printf '%s\n' \ + "󰀻 Apps" \ + "󰏘 Theme" \ + "󰅌 Clipboard" \ + "󰃬 Calculator" \ + "󰈞 Files" \ + "󰖟 Web search" \ + "󰐥 Power" \ + | rofi -dmenu -p menu) || exit 0 + case "$choice" in + *Apps*) exec rofi -show drun ;; + *Theme*) exec "$0" theme ;; + *Clipboard*) exec "$0" clipboard ;; + *Calc*) exec "$0" calc ;; + *Files*) exec "$0" files ;; + *Web*) exec "$0" web ;; + *Power*) exec "$0" power ;; + esac ;; + + *) + echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2 + exit 64 ;; + esac + ''; +in +{ + config = lib.mkIf cfg.rofi.enable { + home.packages = [ + nomarchy-menu + pkgs.libqalculate # qalc, the calc module's engine + pkgs.fd # files module + pkgs.xdg-utils # xdg-open for files/web + ]; + + programs.rofi = { + enable = true; + terminal = cfg.terminal; + font = "${t.fonts.ui} 12"; + + extraConfig = { + modi = "drun,run"; + show-icons = false; # no icon theme shipped yet — clean text list + drun-display-format = "{name}"; + display-drun = "apps"; + display-run = "run"; + }; + + # Whole-swap themes bring their own rofi.rasi; otherwise the theme + # is generated from the palette. + theme = + if hasRasiOverride then rasiOverride + else { + "*" = { + bg = mkLiteral c.base; + fg = mkLiteral c.text; + dim = mkLiteral c.subtext; + surface = mkLiteral c.surface; + accent = mkLiteral c.accent; + + background-color = mkLiteral "transparent"; + text-color = mkLiteral "@fg"; + margin = 0; + padding = 0; + spacing = 0; + }; + + "window" = { + background-color = mkLiteral "@bg"; + border = px t.ui.borderSize; + border-color = mkLiteral "@accent"; + border-radius = px t.ui.rounding; + width = mkLiteral "40%"; + padding = px 8; + }; + + "mainbox" = { + children = map mkLiteral [ "inputbar" "message" "listview" ]; + }; + + "inputbar" = { + background-color = mkLiteral "@surface"; + border-radius = px t.ui.rounding; + padding = mkLiteral "8px 12px"; + spacing = px 8; + children = map mkLiteral [ "prompt" "entry" ]; + }; + "prompt" = { text-color = mkLiteral "@accent"; }; + "entry" = { + placeholder = "Search…"; + placeholder-color = mkLiteral "@dim"; + }; + + "listview" = { + lines = 8; + columns = 1; + dynamic = true; + scrollbar = false; + spacing = px 2; + padding = mkLiteral "8px 0px 0px 0px"; + }; + + "element" = { + padding = mkLiteral "8px 12px"; + border-radius = px t.ui.rounding; + spacing = px 8; + }; + "element selected" = { + background-color = mkLiteral "@accent"; + text-color = mkLiteral "@bg"; + }; + "element-icon" = { + background-color = mkLiteral "transparent"; + size = mkLiteral "1em"; + }; + "element-text" = { + background-color = mkLiteral "transparent"; + text-color = mkLiteral "inherit"; + }; + + "message" = { padding = mkLiteral "8px 0px 0px 0px"; }; + "textbox" = { + background-color = mkLiteral "@surface"; + text-color = mkLiteral "@fg"; + border-radius = px t.ui.rounding; + padding = mkLiteral "8px 12px"; + }; + }; + }; + }; +} diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix index 03749e1..37d54fd 100644 --- a/modules/home/yazi.nix +++ b/modules/home/yazi.nix @@ -1,5 +1,5 @@ # yazi — the flagship file manager: a fast, keyboard-driven TUI that -# fits the distro's identity (Ghostty's Kitty-graphics previews, fuzzel +# fits the distro's identity (Ghostty's Kitty-graphics previews, rofi # menus, everything from one JSON). Themed from theme-state.json and # shipped with a curated plugin set. The GUI half (Thunar, "open folder" # handler) is nomarchy.system.fileManager on the system side. diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..d71ba82 --- /dev/null +++ b/todo.md @@ -0,0 +1,14 @@ +# Items to do + +## one item per commit in the repo + +- Let's add some type of progress bar for when the user switch themes? That way we can have a visual feedback instead of the user thinking the selection failed +- Let's add icon themes and make sure each theme selects an icon theme to use (and that the icon theme is pulled for installation) +- Let's install starship or some other tool to make the terminal look nice. We can configure it nicely by default +- We can also add tools and set aliases, for example change cat for bat, grep for ripgrep, etc... +- We can install by default for the user applications to make it a complete desktop workstation, such as vscode, libreoffice, gimp, inkscape, texlivefull. Maybe we should also add a way for the user to "turn off" any of those apps if the user does not want them installed +- Let's tweak the Nomarchy plymouth theme. The logo is too dark and the constrast does not look good with the dark background +- When the machine hibernates the user already needs to type the luks password to resume it. However, the screen gets locked by hyprlock anyway and the user needs to enter the password twice +- Let's have a rofi menu item that shows all the current assigned keybindings +- We can assign SUPER+Space for rofi dmenu and SUPER+m for the main menu. + From abfe872109f5e74e38a52becb10f28f77521b601 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 15:45:39 +0100 Subject: [PATCH 16/51] feat(themes): port legacy rofi identities for summer-day/night MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit themes//rofi.rasi whole-swaps for the two themes that had a distinct launcher look in the legacy branch — inverted window (bg=fg), green inputbar, yellow bottom-border accent, rounded. Adapted: dropped the configuration{} block (rofi.nix owns modi/terminal/show-icons) and swapped the unshipped Iosevka 17 for a shipped Nerd Font. Verified: both .rasi parse under `rofi -dump-theme`; the whole-swap selects per active theme (summer-night → #2d353b/#a7c080, summer-day → #fdf6e3, others → the generated palette theme); flake check green. Co-Authored-By: Claude Fable 5 --- README.md | 8 +-- themes/summer-day/rofi.rasi | 131 ++++++++++++++++++++++++++++++++++ themes/summer-night/rofi.rasi | 131 ++++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 themes/summer-day/rofi.rasi create mode 100644 themes/summer-night/rofi.rasi diff --git a/README.md b/README.md index 7673393..bd72fb0 100644 --- a/README.md +++ b/README.md @@ -288,10 +288,10 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. palette recolors and already match. Remaining: a visual pass over all six on the live ISO - **Per-theme rofi identity:** the `themes//rofi.rasi` whole-swap - mechanism ships; what remains is porting the legacy `rofi.rasi` designs - (summer-day/night already have them in the legacy branch) for the - identity themes — rofi's `.rasi` expresses their per-element looks - (green inputbar, inverted window) directly, unlike the old flat scheme + ships, and summer-day/night carry their legacy designs (inverted window, + green inputbar, yellow bottom-border). Remaining: author `.rasi` + identities for the other four ported themes if/when they want one (the + generated palette theme is the default and looks fine) - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme variants if still needed diff --git a/themes/summer-day/rofi.rasi b/themes/summer-day/rofi.rasi new file mode 100644 index 0000000..5ae6c11 --- /dev/null +++ b/themes/summer-day/rofi.rasi @@ -0,0 +1,131 @@ +/** + * Nomarchy Summer Day — rofi whole-swap (themes//rofi.rasi). + * Ported from the legacy branch. The `configuration {}` block is omitted + * on purpose: modi/terminal/show-icons come from modules/home/rofi.nix. + * Iosevka 17 → a shipped Nerd Font. + */ + +* { + bg0: #fdf6e3; + fg: #5c6a72; + red: #f85552; + yellow: #dfa000; + green: #8da101; + aqua: #35a77c; + blue: #3a94c5; + purple: #df69ba; + grey0: #a6b0a0; + + font: "JetBrainsMono Nerd Font 14"; + background-color: transparent; + text-color: @fg; +} + +window { + transparency: "real"; + background-color: @fg; + text-color: @bg0; + border-bottom: 5px; + border-color: @yellow; + border-radius: 15px; + width: 40%; + height: 50%; + location: center; + anchor: center; + x-offset: 0; + y-offset: 0; +} + +mainbox { + background-color: transparent; + children: [ inputbar, listview ]; + spacing: 0px; + padding: 0px; +} + +inputbar { + children: [ prompt, entry ]; + background-color: @green; + text-color: @bg0; + expand: false; + border-bottom: 5px; + border-color: @grey0; + border-radius: 10px; + margin: 15px; + padding: 10px; + spacing: 15px; +} + +prompt { + enabled: true; + background-color: inherit; + text-color: inherit; +} + +entry { + background-color: inherit; + text-color: inherit; + cursor: text; + placeholder: "Type to search..."; + placeholder-color: @bg0; +} + +listview { + background-color: transparent; + columns: 1; + lines: 8; + spacing: 0px; + cycle: true; + dynamic: true; + layout: vertical; + padding: 20px; +} + +element { + background-color: transparent; + text-color: @bg0; + orientation: horizontal; + border-radius: 10px; + padding: 10px; + spacing: 15px; +} + +element-icon { + background-color: transparent; + text-color: inherit; + size: 40px; + cursor: inherit; +} + +element-text { + background-color: transparent; + text-color: inherit; + highlight: bold; + cursor: inherit; + vertical-align: 0.5; + horizontal-align: 0.0; +} + +element selected { + background-color: @bg0; + text-color: @fg; + border-bottom: 5px; + border-color: #bdc3af; + border-radius: 10px; +} + +element-text selected { + text-color: @fg; +} + +element-icon selected { + text-color: @fg; +} + +scrollbar { + width: 0px; + background-color: transparent; + handle-width: 0px; + handle-color: transparent; + border: 0px; +} diff --git a/themes/summer-night/rofi.rasi b/themes/summer-night/rofi.rasi new file mode 100644 index 0000000..a268ad7 --- /dev/null +++ b/themes/summer-night/rofi.rasi @@ -0,0 +1,131 @@ +/** + * Nomarchy Summer Night — rofi whole-swap (themes//rofi.rasi). + * Ported from the legacy branch. The `configuration {}` block is omitted + * on purpose: modi/terminal/show-icons come from modules/home/rofi.nix. + * Iosevka 17 → a shipped Nerd Font. + */ + +* { + bg0: #2d353b; + fg: #d3c6aa; + red: #e68183; + yellow: #d9bb80; + green: #a7c080; + aqua: #83b6af; + blue: #83b6af; + purple: #d39bb6; + grey0: #868d80; + + font: "JetBrainsMono Nerd Font 14"; + background-color: transparent; + text-color: @fg; +} + +window { + transparency: "real"; + background-color: @fg; + text-color: @bg0; + border-bottom: 5px; + border-color: @yellow; + border-radius: 15px; + width: 40%; + height: 50%; + location: center; + anchor: center; + x-offset: 0; + y-offset: 0; +} + +mainbox { + background-color: transparent; + children: [ inputbar, listview ]; + spacing: 0px; + padding: 0px; +} + +inputbar { + children: [ prompt, entry ]; + background-color: @green; + text-color: @bg0; + expand: false; + border-bottom: 5px; + border-color: @grey0; + border-radius: 10px; + margin: 15px; + padding: 10px; + spacing: 15px; +} + +prompt { + enabled: true; + background-color: inherit; + text-color: inherit; +} + +entry { + background-color: inherit; + text-color: inherit; + cursor: text; + placeholder: "Type to search..."; + placeholder-color: @bg0; +} + +listview { + background-color: transparent; + columns: 1; + lines: 8; + spacing: 0px; + cycle: true; + dynamic: true; + layout: vertical; + padding: 20px; +} + +element { + background-color: transparent; + text-color: @bg0; + orientation: horizontal; + border-radius: 10px; + padding: 10px; + spacing: 15px; +} + +element-icon { + background-color: transparent; + text-color: inherit; + size: 40px; + cursor: inherit; +} + +element-text { + background-color: transparent; + text-color: inherit; + highlight: bold; + cursor: inherit; + vertical-align: 0.5; + horizontal-align: 0.0; +} + +element selected { + background-color: @bg0; + text-color: @fg; + border-bottom: 5px; + border-color: #161a1d; + border-radius: 10px; +} + +element-text selected { + text-color: @fg; +} + +element-icon selected { + text-color: @fg; +} + +scrollbar { + width: 0px; + background-color: transparent; + handle-width: 0px; + handle-color: transparent; + border: 0px; +} From 20df0d8a608e4694b778c5aacb6ed024c25fe786 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 15:48:50 +0100 Subject: [PATCH 17/51] docs: fold todo.md items into the roadmap Theme-switch progress feedback; per-theme icon themes (unblocks rofi show-icons); starship prompt + modern-CLI aliases (bat/eza/rg/fd/zoxide); opt-out default app suite (nomarchy.apps.*); Plymouth logo contrast fix; hibernate double-unlock (hyprlock redundant after LUKS resume); plus a keybindings-cheatsheet menu module and the SUPER+Space / SUPER+M binds. Co-Authored-By: Claude Fable 5 --- README.md | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd72fb0..d942592 100644 --- a/README.md +++ b/README.md @@ -271,12 +271,15 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo). SUPER+D is `rofi -show drun`. - next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ - screenshot · **ask Claude**: free-text → `$TERMINAL -e claude - ""` — the claude CLI auths via OAuth against a Pro/Max - subscription (no API key); REPL stays open for follow-ups (claude-code - in nixpkgs is unfree — allowUnfree is on) - - launcher icons: `show-icons` is off (no icon theme shipped yet); - enabling it well means shipping an icon theme (Papirus/Adwaita) + screenshot · **keybindings cheatsheet** (parse the bind list → rofi, + so SUPER-? shows every shortcut) · **ask Claude**: free-text → + `$TERMINAL -e claude ""` — the claude CLI auths via OAuth + against a Pro/Max subscription (no API key); REPL stays open for + follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on) + - planned binds: `SUPER+Space` → `rofi -dmenu`/quick-launch and + `SUPER+M` → `nomarchy-menu` (main menu); `SUPER+D` stays `-show drun` + - launcher icons: `show-icons` is off until the icon-theme item below + lands (rofi needs an installed icon theme to show app icons) - decision record: resolves the old Walker/Lua question — no GTK4 launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 @@ -306,3 +309,32 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. screen, tuigreet/MOTD text, and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) +- **Theme-switch feedback:** a switch runs `home-manager switch` (seconds + to minutes), and today the only signal is the notify-send toast + ("Applying theme — rebuilding…"). Add a visible progress indicator so a + slow switch never reads as a failed selection — e.g. a spinner/progress + widget from `nomarchy-theme-sync` (it already brackets the rebuild) or a + Waybar/swaync element while the switch runs. +- **Icon themes:** add `icon` to `theme-state.json`, have Stylix/GTK and + rofi (`show-icons`) consume it, and ensure the chosen icon theme is + pulled into the closure per active theme (a dark/light pair, e.g. + Papirus-Dark/Light, picked to match `mode`). Unblocks rofi launcher + icons and tidies Thunar/GTK app icons. +- **Nicer shell out of the box:** ship and default-configure a prompt + (starship, themed from the JSON) plus modern-CLI ergonomics — + `bat`→cat, `eza`→ls, `ripgrep`→grep, `fd`→find, `zoxide`→cd — wired as + aliases/`$PAGER` so the terminal is pleasant by default (overridable). +- **Default application suite:** install a complete-workstation set + (vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a + `nomarchy.apps.*` option surface so each is individually opt-out for + users who want a leaner machine. Watch closure size (texlive-full is + multi-GB — likely default-off or a lighter scheme). +- **Plymouth logo contrast:** the shipped splash logo is too dark against + the dark `base` background; recolor/lighten it (or tint from the palette + `accent`/`text` instead of shipping a fixed navy asset) so it reads on + both light and dark themes. +- **Hibernate double-unlock:** on resume from hibernate the LUKS + passphrase already gates the machine, but hypridle's `before_sleep_cmd` + also locks hyprlock, so the user types a password twice. Lock only on + suspend (not hibernate), or skip the hyprlock prompt when resuming from + a LUKS-encrypted hibernate — see `modules/home/idle.nix`. From 084b2a05004e36c9464620c11a73d812b58d2c2e Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 15:52:41 +0100 Subject: [PATCH 18/51] feat(theme-sync): persistent switch notification so slow switches read as progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Applying theme — rebuilding…" toast used the default timeout, so it vanished seconds into a multi-minute home-manager switch — leaving the user with no signal and the impression the selection failed. Now it's persistent (timeout 0) and all theme notifications share a synchronous tag, so swaync REPLACES it in place with "Theme applied ✓" (or a critical failure toast that persists) when the rebuild finishes. notify() grew persistent/urgency/summary params. Honest indeterminate feedback — HM exposes no percentage, so no fake progress bar. Verified: py_compile clean; the full apply→switch→notify path runs end to end (dummy NOMARCHY_REBUILD). The in-place replacement is visible only in a real swaync session. Co-Authored-By: Claude Fable 5 --- README.md | 11 +++---- .../nomarchy-theme-sync.py | 32 +++++++++++++++---- todo.md | 14 -------- 3 files changed, 31 insertions(+), 26 deletions(-) delete mode 100644 todo.md diff --git a/README.md b/README.md index d942592..04cb95c 100644 --- a/README.md +++ b/README.md @@ -309,12 +309,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. screen, tuigreet/MOTD text, and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) -- **Theme-switch feedback:** a switch runs `home-manager switch` (seconds - to minutes), and today the only signal is the notify-send toast - ("Applying theme — rebuilding…"). Add a visible progress indicator so a - slow switch never reads as a failed selection — e.g. a spinner/progress - widget from `nomarchy-theme-sync` (it already brackets the rebuild) or a - Waybar/swaync element while the switch runs. +- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now + persistent (timeout 0) and replaced in place by "applied ✓" / failure + via a synchronous tag, so a multi-minute switch never reads as a failed + selection. (An honest indeterminate indicator — HM gives no % — a + literal progress widget would be cosmetic; revisit only if wanted.) - **Icon themes:** add `icon` to `theme-state.json`, have Stylix/GTK and rofi (`show-icons`) consume it, and ensure the chosen icon theme is pulled into the closure per active theme (a dark/light pair, e.g. diff --git a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py index 7f23141..16c1e54 100644 --- a/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py +++ b/pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py @@ -59,10 +59,27 @@ def die(msg: str) -> "None": sys.exit(1) -def notify(body: str) -> None: - if shutil.which("notify-send"): - subprocess.run(["notify-send", "-a", "Nomarchy", "Nomarchy", body], - capture_output=True) +# All Nomarchy theme notifications share this synchronous tag, so the +# notification daemon (swaync) REPLACES the previous one in place instead +# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast. +NOTIFY_SYNC_TAG = "nomarchy-theme-sync" + + +def notify(body: str, summary: str = "Nomarchy", *, + persistent: bool = False, urgency: str = "normal") -> None: + """Desktop notification. persistent (timeout 0) keeps it up for the + whole rebuild so a slow switch never reads as a failed selection; + the success/failure call replaces it via the synchronous tag.""" + if shutil.which("notify-send") is None: + return + subprocess.run( + ["notify-send", "-a", "Nomarchy", + "-u", urgency, + "-t", "0" if persistent else "5000", + "-h", f"string:x-canonical-private-synchronous:{NOTIFY_SYNC_TAG}", + summary, body], + capture_output=True, + ) # ─── State management ───────────────────────────────────────────────────── @@ -145,10 +162,13 @@ def run_switch() -> None: return log(f"rebuilding: {' '.join(argv)}") - notify("Applying theme — rebuilding the desktop…") + # Persistent (timeout 0): stays up for the whole rebuild — replaced + # in place by the success/failure notification below — so a multi- + # minute switch never looks like it silently failed. + notify("Applying theme — rebuilding the desktop…", persistent=True) result = subprocess.run(argv) # stream output to the caller's terminal if result.returncode != 0: - notify("Theme rebuild FAILED — see terminal / journal") + notify("Theme rebuild FAILED — see terminal / journal", urgency="critical") die("rebuild failed (state file already updated; fix and re-run)") notify("Theme applied ✓") diff --git a/todo.md b/todo.md deleted file mode 100644 index d71ba82..0000000 --- a/todo.md +++ /dev/null @@ -1,14 +0,0 @@ -# Items to do - -## one item per commit in the repo - -- Let's add some type of progress bar for when the user switch themes? That way we can have a visual feedback instead of the user thinking the selection failed -- Let's add icon themes and make sure each theme selects an icon theme to use (and that the icon theme is pulled for installation) -- Let's install starship or some other tool to make the terminal look nice. We can configure it nicely by default -- We can also add tools and set aliases, for example change cat for bat, grep for ripgrep, etc... -- We can install by default for the user applications to make it a complete desktop workstation, such as vscode, libreoffice, gimp, inkscape, texlivefull. Maybe we should also add a way for the user to "turn off" any of those apps if the user does not want them installed -- Let's tweak the Nomarchy plymouth theme. The logo is too dark and the constrast does not look good with the dark background -- When the machine hibernates the user already needs to type the luks password to resume it. However, the screen gets locked by hyprlock anyway and the user needs to enter the password twice -- Let's have a rofi menu item that shows all the current assigned keybindings -- We can assign SUPER+Space for rofi dmenu and SUPER+m for the main menu. - From 520c878bb30ec282a7a76f440c8b981727695e66 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 16:04:55 +0100 Subject: [PATCH 19/51] feat: swayosd on-screen display for volume/brightness/mute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The media keys called wpctl/brightnessctl directly with no visual feedback — changing volume showed nothing on screen. Now they drive swayosd-client, which performs the action AND shows a themed OSD. - modules/home/osd.nix: services.swayosd, style.css generated from the palette (base bg, accent progress/border); nomarchy.osd.enable. - hyprland media binds → swayosd-client (--output-volume raise/lower/ mute-toggle, --input-volume mute-toggle, --brightness raise/lower). - modules/nixos: services.udev.packages = [ swayosd ] installs the backlight rule (chgrp video + g+w) so brightness works for video-group users without root (the installer/live user is in video). swaync is unaffected (it was already running — this was purely the missing OSD). Verified: flake check green, swayosd.service bound to graphical-session.target, hyprland config verifies, swayosd in the live ISO closure. On-screen rendering needs a real session to confirm. Co-Authored-By: Claude Fable 5 --- README.md | 7 +++-- modules/home/default.nix | 1 + modules/home/hyprland.nix | 18 ++++++------- modules/home/options.nix | 1 + modules/home/osd.nix | 55 +++++++++++++++++++++++++++++++++++++++ modules/nixos/default.nix | 5 ++++ 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 modules/home/osd.nix diff --git a/README.md b/README.md index 04cb95c..3a93cb5 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── rofi.nix # launcher + nomarchy-menu (power, clip) │ ├── swaync.nix # notifications, same JSON │ ├── idle.nix # hyprlock + hypridle, same JSON -│ └── yazi.nix # flagship TUI file manager + plugins +│ ├── yazi.nix # flagship TUI file manager + plugins +│ └── osd.nix # swayosd volume/brightness OSD ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -176,6 +177,7 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.swaync.enable` | `true` | swaync notifications, themed | | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | | `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | +| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute | | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | @@ -302,7 +304,8 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. v1: `nomarchy.system.plymouth.*`, background tinted from the state file) - Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) -- swayosd (volume/brightness OSD), launch-or-focus UX scripts +- launch-or-focus UX scripts (swayosd volume/brightness OSD ships since v1: + `nomarchy.osd.*`, media keys drive `swayosd-client`, themed from the JSON) - **Distro branding, round 2:** `distroName = "Nomarchy"` ships (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot diff --git a/modules/home/default.nix b/modules/home/default.nix index 4cefbfa..861e789 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -16,6 +16,7 @@ ./swaync.nix # notification daemon, themed from the same JSON ./idle.nix # hyprlock + hypridle, themed from the same JSON ./yazi.nix # flagship TUI file manager, themed + plugins + ./osd.nix # swayosd volume/brightness OSD, themed ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 1a57669..30fd8b1 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -161,19 +161,19 @@ in ", Print, exec, grim -g \"$(slurp)\" - | wl-copy" ] ++ workspaceBinds; - # Media keys: wpctl talks to wireplumber directly (no - # pipewire-pulse shim like pamixer); e = repeat, l = also on a - # locked screen — the standard flags for hardware keys. + # Media keys via swayosd-client: it performs the action AND shows + # the on-screen display (the nomarchy.osd module). e = repeat, + # l = also on a locked screen — the standard flags for hardware keys. bindel = [ - ", XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+" - ", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-" - ", XF86MonBrightnessUp, exec, brightnessctl set +5%" - ", XF86MonBrightnessDown, exec, brightnessctl set 5%-" + ", XF86AudioRaiseVolume, exec, swayosd-client --output-volume raise" + ", XF86AudioLowerVolume, exec, swayosd-client --output-volume lower" + ", XF86MonBrightnessUp, exec, swayosd-client --brightness raise" + ", XF86MonBrightnessDown, exec, swayosd-client --brightness lower" ]; bindl = [ - ", XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" - ", XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle" + ", XF86AudioMute, exec, swayosd-client --output-volume mute-toggle" + ", XF86AudioMicMute, exec, swayosd-client --input-volume mute-toggle" ", XF86AudioPlay, exec, playerctl play-pause" ", XF86AudioPause, exec, playerctl play-pause" ", XF86AudioNext, exec, playerctl next" diff --git a/modules/home/options.nix b/modules/home/options.nix index 34c0432..64ccf49 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -67,6 +67,7 @@ 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; }; 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; }; diff --git a/modules/home/osd.nix b/modules/home/osd.nix new file mode 100644 index 0000000..df82ed1 --- /dev/null +++ b/modules/home/osd.nix @@ -0,0 +1,55 @@ +# swayosd — on-screen display for volume / brightness / mute (and caps +# lock). The media keys drive it via swayosd-client (it performs the +# action AND shows the OSD), so changing volume or brightness gives the +# visual feedback that bare wpctl/brightnessctl didn't. Themed from +# theme-state.json. Brightness needs the backlight udev rule shipped +# system-side (modules/nixos/default.nix → services.udev.packages). +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; + + style = pkgs.writeText "swayosd-style.css" '' + window#osd { + border-radius: ${toString t.ui.rounding}px; + border: ${toString t.ui.borderSize}px solid ${c.accent}; + background: alpha(${c.base}, 0.92); + } + window#osd #container { margin: 16px; } + window#osd image, + window#osd label { color: ${c.text}; } + window#osd progressbar:disabled, + window#osd image:disabled { opacity: 0.5; } + window#osd progressbar, + window#osd segmentedprogress { + min-height: 8px; + border-radius: 999px; + background: transparent; + border: none; + } + window#osd trough, + window#osd segment { + min-height: inherit; + border-radius: inherit; + border: none; + background: ${c.surface}; + } + window#osd progress, + window#osd segment.active { + min-height: inherit; + border-radius: inherit; + border: none; + background: ${c.accent}; + } + ''; +in +{ + config = lib.mkIf cfg.osd.enable { + services.swayosd = { + enable = true; + stylePath = style; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 286b1bb..4e0b62b 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -189,6 +189,11 @@ in # Don't let boot entries fill the ESP over the years. boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10; + # Backlight udev rule (chgrp video + g+w on /sys/class/backlight) so + # the desktop's swayosd brightness control works for video-group + # users without root. Harmless baseline even if the OSD is disabled. + services.udev.packages = [ pkgs.swayosd ]; + # ── Nix itself ─────────────────────────────────────────────────── nix = { settings = { From f97054c804cd886b520148bb6f1df56b993c9fe9 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 16:12:19 +0100 Subject: [PATCH 20/51] =?UTF-8?q?feat:=20icon=20themes=20=E2=80=94=20Papir?= =?UTF-8?q?us,=20resolved=20from=20the=20JSON,=20for=20GTK=20+=20rofi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ships papirus-icon-theme and resolves an icon-theme name once in theme.nix → nomarchy.theme.iconTheme: the JSON's optional `icons` field, else Papirus-Dark/Light picked by `mode`. That single value feeds: - Stylix `stylix.icons` → gtk.iconTheme (Thunar and GTK apps) - rofi `show-icons` + `icon-theme` (the launcher now shows app icons) A preset or theme-state.json can set `icons` to any theme in the icon package to override per theme. Verified: flake check green; iconTheme resolves Papirus-Dark for tokyo-night and Papirus-Light for summer-day; gtk.enable true with gtk.iconTheme.name set; papirus in home.packages and the live ISO closure; rofi config.rasi carries icon-theme + show-icons:true. The rendered icons need a real session to see. Co-Authored-By: Claude Fable 5 --- README.md | 14 +++++++------- modules/home/rofi.nix | 3 ++- modules/home/stylix.nix | 11 +++++++++++ modules/home/theme.nix | 15 ++++++++++++++- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3a93cb5..198e40d 100644 --- a/README.md +++ b/README.md @@ -280,8 +280,8 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on) - planned binds: `SUPER+Space` → `rofi -dmenu`/quick-launch and `SUPER+M` → `nomarchy-menu` (main menu); `SUPER+D` stays `-show drun` - - launcher icons: `show-icons` is off until the icon-theme item below - lands (rofi needs an installed icon theme to show app icons) + - launcher icons: ✓ `show-icons` on, drawing from the theme's icon set + (Papirus, via the icon-themes work below) - decision record: resolves the old Walker/Lua question — no GTK4 launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 @@ -317,11 +317,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. via a synchronous tag, so a multi-minute switch never reads as a failed selection. (An honest indeterminate indicator — HM gives no % — a literal progress widget would be cosmetic; revisit only if wanted.) -- **Icon themes:** add `icon` to `theme-state.json`, have Stylix/GTK and - rofi (`show-icons`) consume it, and ensure the chosen icon theme is - pulled into the closure per active theme (a dark/light pair, e.g. - Papirus-Dark/Light, picked to match `mode`). Unblocks rofi launcher - icons and tidies Thunar/GTK app icons. +- **Icon themes:** ✓ ships `papirus-icon-theme`; the resolved name lives on + `nomarchy.theme.iconTheme` (the JSON's optional `icons` field, else + Papirus-Dark/Light by `mode`) and feeds both Stylix's `gtk.iconTheme` + (Thunar/GTK apps) and rofi's `show-icons`. Remaining (optional): per-theme + `icons` overrides for the presets, or shipping more icon packs. - **Nicer shell out of the box:** ship and default-configure a prompt (starship, themed from the JSON) plus modern-CLI ergonomics — `bat`→cat, `eza`→ls, `ripgrep`→grep, `fd`→find, `zoxide`→cd — wired as diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 25e598b..4048940 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -131,7 +131,8 @@ in extraConfig = { modi = "drun,run"; - show-icons = false; # no icon theme shipped yet — clean text list + show-icons = true; # app icons via the theme's icon set + icon-theme = t.iconTheme; # resolved in theme.nix (Papirus-*/per-theme) drun-display-format = "{name}"; display-drun = "apps"; display-run = "run"; diff --git a/modules/home/stylix.nix b/modules/home/stylix.nix index 0edb147..e9be5c1 100644 --- a/modules/home/stylix.nix +++ b/modules/home/stylix.nix @@ -55,6 +55,17 @@ in size = lib.mkDefault 24; }; + # GTK/file-manager/rofi icon theme, resolved from the JSON in + # theme.nix (per-theme `icons`, else Papirus-Dark/Light by mode). + # Stylix sets gtk.iconTheme from this; both dark/light point at the + # already-mode-resolved name. + icons = { + enable = true; + package = lib.mkDefault pkgs.papirus-icon-theme; + dark = t.iconTheme; + light = t.iconTheme; + }; + fonts = { monospace = { name = t.fonts.mono; diff --git a/modules/home/theme.nix b/modules/home/theme.nix index 0710fe8..72420f2 100644 --- a/modules/home/theme.nix +++ b/modules/home/theme.nix @@ -44,11 +44,24 @@ let blur = true; shadow = true; }; + # Icon theme name; "" means "pick by mode" (resolved below). A preset + # or the state file can name any theme in the shipped icon package. + icons = ""; }; + + parsed = lib.recursiveUpdate defaults themeState; + + # Resolve the icon theme once and expose it on nomarchy.theme so both + # the GTK side (stylix.nix) and rofi (rofi.nix) agree. The shipped + # package (papirus-icon-theme) carries the Dark/Light pair. + iconTheme = + if parsed.icons != "" then parsed.icons + else if parsed.mode == "light" then "Papirus-Light" + else "Papirus-Dark"; in { config = { - nomarchy.theme = lib.recursiveUpdate defaults themeState; + nomarchy.theme = parsed // { inherit iconTheme; }; nomarchy.lib = { # "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax) From df7c0ef7ae7951b05d9eded2ff08e933c7840ade Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 16:25:21 +0100 Subject: [PATCH 21/51] feat: zsh default shell + starship/bat/eza/zoxide shell experience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zsh is now the default login shell (programs.zsh.enable + users.defaultUserShell, mkOverride 500 to beat bash.nix's mkDefault; per-user shell= still overrides). modules/home/shell.nix configures the interactive experience (nomarchy.shell.enable): - zsh: completion, autosuggestions, syntax highlighting, big shared history, autocd. - starship prompt themed from the palette (a `nomarchy` palette built from theme-state.json colors). - bat as cat (theme "ansi" → tracks the terminal/theme palette), eza as ls (icons + git + dirs-first), zoxide as cd (--cmd cd). - rg/fd shipped as themselves — deliberately NOT aliased over grep/find (flag differences surprise). home.shell.enableZshIntegration = true is the single lever; starship, eza, zoxide, ghostty and yazi all emit their zsh hooks from it (no per-module edits). Verified: flake check green, default shell resolves to zsh, .zshrc carries the starship/zoxide/eza/yazi hooks + aliases, starship palette renders, all tools in the live ISO closure. Co-Authored-By: Claude Fable 5 --- README.md | 14 +++-- modules/home/default.nix | 1 + modules/home/options.nix | 1 + modules/home/shell.nix | 114 ++++++++++++++++++++++++++++++++++++++ modules/nixos/default.nix | 11 ++++ 5 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 modules/home/shell.nix diff --git a/README.md b/README.md index 198e40d..4af3e5b 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── swaync.nix # notifications, same JSON │ ├── idle.nix # hyprlock + hypridle, same JSON │ ├── yazi.nix # flagship TUI file manager + plugins -│ └── osd.nix # swayosd volume/brightness OSD +│ ├── osd.nix # swayosd volume/brightness OSD +│ └── shell.nix # zsh + starship + bat/eza/zoxide ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -178,6 +179,7 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | | `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | | `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute | +| `nomarchy.shell.enable` | `true` | zsh + starship prompt + bat/eza/zoxide (zsh is the default login shell) | | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | @@ -322,10 +324,12 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. Papirus-Dark/Light by `mode`) and feeds both Stylix's `gtk.iconTheme` (Thunar/GTK apps) and rofi's `show-icons`. Remaining (optional): per-theme `icons` overrides for the presets, or shipping more icon packs. -- **Nicer shell out of the box:** ship and default-configure a prompt - (starship, themed from the JSON) plus modern-CLI ergonomics — - `bat`→cat, `eza`→ls, `ripgrep`→grep, `fd`→find, `zoxide`→cd — wired as - aliases/`$PAGER` so the terminal is pleasant by default (overridable). +- **Nicer shell out of the box:** ✓ zsh is the default login shell, with a + starship prompt themed from the JSON, autosuggestions + syntax + highlighting, and modern-CLI ergonomics — `cat`→bat (theme "ansi", so it + tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`. + `nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd: + their flags differ enough to surprise; `rg`/`fd` ship as themselves.) - **Default application suite:** install a complete-workstation set (vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a `nomarchy.apps.*` option surface so each is individually opt-out for diff --git a/modules/home/default.nix b/modules/home/default.nix index 861e789..b89a04e 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -17,6 +17,7 @@ ./idle.nix # hyprlock + hypridle, themed from the same JSON ./yazi.nix # flagship TUI file manager, themed + plugins ./osd.nix # swayosd volume/brightness OSD, themed + ./shell.nix # zsh + starship + bat/eza/zoxide, themed ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/options.nix b/modules/home/options.nix index 64ccf49..6fa3d86 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -68,6 +68,7 @@ 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; }; 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; }; diff --git a/modules/home/shell.nix b/modules/home/shell.nix new file mode 100644 index 0000000..f0f7c66 --- /dev/null +++ b/modules/home/shell.nix @@ -0,0 +1,114 @@ +# Shell experience — zsh (the default login shell, set system-side) with +# autosuggestions + syntax highlighting, a themed starship prompt, and +# modern CLI tools wired in: bat (cat), eza (ls), zoxide (cd). bat uses +# the "ansi" theme so it follows the terminal palette (Ghostty is themed +# from the same JSON); starship is themed from the palette directly. +# +# home.shell.enableZshIntegration = true is the single lever that makes +# every integrating program (starship, eza, zoxide, ghostty, yazi) emit +# its zsh hooks — no per-module flags needed. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; +in +{ + config = lib.mkIf cfg.shell.enable { + home.shell.enableZshIntegration = true; + + # rg/fd as themselves (NOT aliased over grep/find — their flags + # differ enough to surprise). Available regardless of yazi/rofi. + home.packages = with pkgs; [ ripgrep fd ]; + + programs.zsh = { + enable = true; + enableCompletion = true; + autosuggestion.enable = true; + syntaxHighlighting.enable = true; + autocd = true; + history = { + size = 50000; + save = 50000; + ignoreDups = true; + ignoreSpace = true; + expireDuplicatesFirst = true; + }; + shellAliases = { + cat = "bat --paging=never"; + less = "bat"; + grep = "grep --color=auto"; + # eza enableZshIntegration aliases ls/ll/la; add a couple extras. + lt = "eza --tree --level=2 --icons=auto --group-directories-first"; + tree = "eza --tree --icons=auto --group-directories-first"; + ".." = "cd .."; + "..." = "cd ../.."; + }; + }; + + programs.starship = { + enable = true; + settings = { + add_newline = false; + palette = "nomarchy"; + # starship wants full #rrggbb (or names) — keep the leading #. + palettes.nomarchy = { + inherit (c) base text accent good warn bad subtext; + }; + format = lib.concatStrings [ + "[](accent)" + "$directory" + "$git_branch$git_status" + "$character" + ]; + directory = { + style = "fg:text bg:accent"; + format = "[ $path ]($style)"; + truncation_length = 3; + truncate_to_repo = true; + }; + git_branch = { + style = "fg:base bg:warn"; + format = "[ $symbol$branch ]($style)"; + symbol = " "; + }; + git_status = { + style = "fg:base bg:warn"; + format = "[$all_status$ahead_behind ]($style)"; + }; + character = { + success_symbol = "[ ❯](good)"; + error_symbol = "[ ❯](bad)"; + }; + cmd_duration = { + min_time = 2000; + style = "fg:subtext"; + format = "[ $duration ]($style)"; + }; + }; + }; + + # bat: "ansi" follows the terminal's 16-color palette → tracks the + # active Nomarchy theme (Ghostty bakes it) with no per-theme config. + programs.bat = { + enable = true; + config = { + theme = "ansi"; + style = "numbers,changes,header"; + }; + }; + + programs.eza = { + enable = true; + icons = "auto"; + git = true; + extraOptions = [ "--group-directories-first" ]; + }; + + programs.zoxide = { + enable = true; + options = [ "--cmd cd" ]; # `cd` becomes the smart jumper (z under the hood) + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 4e0b62b..08f720f 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -71,6 +71,17 @@ in services.upower.enable = lib.mkDefault true; networking.networkmanager.enable = lib.mkDefault true; + # zsh as the default login shell (the desktop's shell experience — + # starship/bat/eza/zoxide — is configured home-side in shell.nix). + # programs.zsh.enable wires /etc/zshrc, completion and /etc/shells; + # defaultUserShell applies to normal users that don't set their own. + programs.zsh.enable = lib.mkDefault true; + # mkOverride 500: beats nixpkgs' bash.nix `mkDefault bashInteractive` + # (equal mkDefault priority would conflict) while staying below a + # plain downstream assignment. A per-user `users.users..shell` + # overrides this regardless. + users.defaultUserShell = lib.mkOverride 500 pkgs.zsh; + hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable; services.blueman.enable = lib.mkDefault cfg.bluetooth.enable; From 60138e20d057840dc2095c001674d164d442fac4 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 19:25:40 +0100 Subject: [PATCH 22/51] docs: log known issues & follow-ups in the roadmap Yazi prepend_fetchers group field, starship powerline removal, xfce package deprecations, rofi function keybinds, nix-ld default, hyprland border colors, and waybar phantom workspaces. Co-Authored-By: Claude Opus 4.8 --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 4af3e5b..977135a 100644 --- a/README.md +++ b/README.md @@ -344,3 +344,32 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. also locks hyprlock, so the user types a password twice. Lock only on suspend (not hibernate), or skip the hyprlock prompt when resuming from a LUKS-encrypted hibernate — see `modules/home/idle.nix`. + +## Known issues & follow-ups +- **Yazi TOML parse error on startup:** `[[plugin.prepend_fetchers]]` is + rejected with `missing field 'group'` (Yazi falls back to preset + settings). The fetcher schema gained a required `group` field; add it to + both git fetcher entries (e.g. `group = 0`) in `modules/home/yazi.nix:49`. +- **Starship prompt styling:** drop the powerline look — the current prompt + doesn't read well. Rework the `format`/segment styling in + `modules/home/shell.nix:59` toward a flat, plain prompt. +- **xfce deprecation warnings:** `xfce.exo`, `xfce.thunar-archive-plugin`, + `xfce.thunar-volman`, `xfce.thunar-media-tags-plugin` were moved to + top-level. Switch to `pkgs.xfce4-exo`, `pkgs.thunar-archive-plugin`, + `pkgs.thunar-volman`, `pkgs.thunar-media-tags-plugin` in + `modules/nixos/file-manager.nix`. +- **Rofi function keybindings:** add direct Hyprland binds for the + `nomarchy-menu` sub-functions (calc, web search, file search, clipboard, + …) instead of only reaching them through the menu — see the planned binds + in the Menu-system roadmap item and `modules/home/hyprland.nix`. +- **Enable nix-ld by default:** turn on `programs.nix-ld` distro-wide so + prebuilt/foreign dynamically-linked binaries run out of the box. +- **Hyprland border colors off for some themes:** the active/inactive border + colors don't match the legacy branch for several themes — audit + `col.active_border`/`col.inactive_border` generation in + `modules/home/hyprland.nix` against legacy. +- **Waybar shows non-existent workspaces:** in summer-night/summer-day the + bar renders every workspace icon up-front via `persistent-workspaces` / + `all-outputs` in `themes/summer-{day,night}/waybar.jsonc`. Restore the + legacy behaviour (only show workspaces that exist) here and across the + other themes. From 58d79aad17a0112bd450940430fed836696a4e36 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 19:46:01 +0100 Subject: [PATCH 23/51] feat(menu): network/bluetooth/capture/ask modules + keybindings cheatsheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build out the rofi menu system roadmap item: - New nomarchy-menu modules: network (nmtui), bluetooth (blueman-manager), capture (grim/slurp submenu → clipboard/file), ask (free-text → claude CLI in a terminal), and keybinds (the SUPER+? cheatsheet). - modules/home/keybinds.nix is now the single source of truth for both the Hyprland bind strings and the cheatsheet, so the two can't drift — hyprland.nix maps it into `bind`, rofi.nix renders the padded two-column sheet (generated/mouse binds live in its `extra` rows). - New binds: SUPER+Space (quick launch), SUPER+M (main menu), SUPER+? (cheatsheet). SUPER+D stays rofi -show drun. - Ask Claude pulls claude-code fresh from npm via `npx @anthropic-ai/claude-code@latest` (the nixpkgs package lags model releases); nodejs is bundled for npx. nix flake check passes. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 3 +- README.md | 22 +++++++----- modules/home/hyprland.nix | 45 ++++++----------------- modules/home/keybinds.nix | 56 +++++++++++++++++++++++++++++ modules/home/rofi.nix | 71 ++++++++++++++++++++++++++++++++++++- 5 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 modules/home/keybinds.nix diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 305e678..1323d91 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -261,7 +261,8 @@ "Bash(pkill -f \"qemu-system-x86_64.*NOMARCHY\\\\|qemu-system-x86_64.*nomarchy\")", "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-night --no-switch)", "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-day --no-switch)", - "Bash(xargs cat)" + "Bash(xargs cat)", + "Bash(nix-instantiate --parse modules/home/rofi.nix)" ] } } diff --git a/README.md b/README.md index 977135a..372f1bf 100644 --- a/README.md +++ b/README.md @@ -274,14 +274,20 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo). SUPER+D is `rofi -show drun`. - - next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸ - screenshot · **keybindings cheatsheet** (parse the bind list → rofi, - so SUPER-? shows every shortcut) · **ask Claude**: free-text → - `$TERMINAL -e claude ""` — the claude CLI auths via OAuth - against a Pro/Max subscription (no API key); REPL stays open for - follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on) - - planned binds: `SUPER+Space` → `rofi -dmenu`/quick-launch and - `SUPER+M` → `nomarchy-menu` (main menu); `SUPER+D` stays `-show drun` + - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` + (blueman-manager) · `capture` (grim/slurp submenu: region/full → + clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the + cheatsheet, see below) · `ask` (free-text → claude CLI in a terminal; + auths via OAuth, no API key; pulled fresh via `npx + @anthropic-ai/claude-code@latest` rather than the nixpkgs package, which + lags model releases — `nodejs` is bundled for npx; REPL stays open) + - ✓ keybindings cheatsheet: `modules/home/keybinds.nix` is the **single + source** for both the Hyprland binds and the SUPER+? rofi list, so they + can't drift; `nomarchy-menu keybinds` renders the padded two-column + sheet (generated/mouse binds carried in its `extra` rows) + - ✓ shipped binds: `SUPER+Space` → `rofi -show drun` (quick launch) · + `SUPER+M` → `nomarchy-menu` (main menu) · `SUPER+?` → the cheatsheet; + `SUPER+D` stays `-show drun` - launcher icons: ✓ `show-icons` on, drawing from the theme's icon set (Papirus, via the icon-themes work below) - decision record: resolves the old Walker/Lua question — no GTK4 diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 30fd8b1..d7a2f1d 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -17,6 +17,11 @@ let "$mod SHIFT, ${ws}, movetoworkspace, ${ws}" ]) 9); + + # The keyboard binds — single source shared with the SUPER+? cheatsheet + # (rofi.nix renders the same list). Edit them in ./keybinds.nix. + keybinds = import ./keybinds.nix; + mkBind = b: "${b.mods}, ${b.key}, ${b.action}"; in { wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable { @@ -125,41 +130,11 @@ in force_default_wallpaper = lib.mkDefault 0; }; - bind = [ - "$mod, Return, exec, $terminal" - "$mod, D, exec, rofi -show drun" - "$mod, E, exec, $terminal -e yazi" - "$mod, Q, killactive" - "$mod, F, fullscreen" - "$mod, V, togglefloating" - "$mod SHIFT, E, exit" - - # Theme picker (menu dispatcher): apply writes the state and - # runs home-manager switch (progress via notify-send). - "$mod, T, exec, nomarchy-menu theme" - # Cycle the current theme's wallpapers (instant, no rebuild). - "$mod SHIFT, T, exec, nomarchy-theme-sync bg next" - - # Power menu (rofi dmenu via the nomarchy-menu dispatcher). - # Not Escape: Super+Escape gets swallowed before reaching the - # dispatcher on some setups (input methods / modal handling). - "$mod, X, exec, nomarchy-menu power" - - # Notification centre (swaync). - "$mod, N, exec, swaync-client -t" - - # Clipboard history (cliphist via the menu dispatcher). - "$mod CTRL, V, exec, nomarchy-menu clipboard" - - # Focus - "$mod, H, movefocus, l" - "$mod, L, movefocus, r" - "$mod, K, movefocus, u" - "$mod, J, movefocus, d" - - # Screenshot region to clipboard - ", Print, exec, grim -g \"$(slurp)\" - | wl-copy" - ] ++ workspaceBinds; + # Rendered from ./keybinds.nix (the cheatsheet reads the same list), + # plus the generated per-workspace binds. Like exec-once above this + # stays a normal-priority list, so a downstream `bind = [...]` + # concatenates rather than replaces. + bind = map mkBind keybinds.binds ++ workspaceBinds; # Media keys via swayosd-client: it performs the action AND shows # the on-screen display (the nomarchy.osd module). e = repeat, diff --git a/modules/home/keybinds.nix b/modules/home/keybinds.nix new file mode 100644 index 0000000..0fe7f52 --- /dev/null +++ b/modules/home/keybinds.nix @@ -0,0 +1,56 @@ +# Canonical keybind list — the single source of truth for BOTH the +# Hyprland `bind` strings (hyprland.nix) and the SUPER+? cheatsheet +# rendered by nomarchy-menu (rofi.nix). One place, no second renderer to +# keep in sync — the same rule the theme pipeline follows. +# +# mods — modifier string, Hyprland syntax ("$mod", "$mod SHIFT", "") +# key — the key name +# action — the Hyprland dispatcher (+args) that the bind runs +# desc — human label, shown in the cheatsheet +# +# `extra` holds cheatsheet-only rows for binds generated elsewhere +# (per-workspace numbers, mouse drags) so they still show up under SUPER+?. +{ + binds = [ + { mods = "$mod"; key = "Return"; action = "exec, $terminal"; desc = "Open terminal"; } + { mods = "$mod"; key = "Space"; action = "exec, rofi -show drun"; desc = "Quick launch (apps)"; } + { mods = "$mod"; key = "D"; action = "exec, rofi -show drun"; desc = "App launcher"; } + { mods = "$mod"; key = "M"; action = "exec, nomarchy-menu"; desc = "Main menu"; } + { mods = "$mod"; key = "E"; action = "exec, $terminal -e yazi"; desc = "File manager (yazi)"; } + { mods = "$mod"; key = "Q"; action = "killactive"; desc = "Close window"; } + { mods = "$mod"; key = "F"; action = "fullscreen"; desc = "Fullscreen"; } + { mods = "$mod"; key = "V"; action = "togglefloating"; desc = "Toggle floating"; } + { mods = "$mod SHIFT"; key = "E"; action = "exit"; desc = "Exit Hyprland"; } + + # Theme picker (menu dispatcher): apply writes the state and runs + # home-manager switch (progress via notify-send). + { mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; } + # Cycle the current theme's wallpapers (instant, no rebuild). + { mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-theme-sync bg next"; desc = "Next wallpaper"; } + + # Power menu via the dispatcher. Not Escape: Super+Escape gets + # swallowed before reaching the dispatcher on some setups. + { mods = "$mod"; key = "X"; action = "exec, nomarchy-menu power"; desc = "Power menu"; } + + { mods = "$mod"; key = "N"; action = "exec, swaync-client -t"; desc = "Notification centre"; } + { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } + # SUPER+? (the "question" keysym already implies Shift on most layouts). + { mods = "$mod"; key = "question"; action = "exec, nomarchy-menu keybinds"; desc = "Keybindings cheatsheet"; } + + # Focus + { mods = "$mod"; key = "H"; action = "movefocus, l"; desc = "Focus left"; } + { mods = "$mod"; key = "L"; action = "movefocus, r"; desc = "Focus right"; } + { mods = "$mod"; key = "K"; action = "movefocus, u"; desc = "Focus up"; } + { mods = "$mod"; key = "J"; action = "movefocus, d"; desc = "Focus down"; } + + # Screenshot region to clipboard (the menu's Capture module has more). + { mods = ""; key = "Print"; action = "exec, grim -g \"$(slurp)\" - | wl-copy"; desc = "Screenshot region → clipboard"; } + ]; + + extra = [ + { keys = "SUPER + 1-9"; desc = "Switch to workspace 1-9"; } + { keys = "SUPER + SHIFT + 1-9"; desc = "Move window to workspace 1-9"; } + { keys = "SUPER + drag"; desc = "Move (LMB) / resize (RMB) window"; } + { keys = "Volume / Brightness"; desc = "Hardware keys, shown via the OSD"; } + ]; +} diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 4048940..826ee63 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -19,6 +19,25 @@ let rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi"; hasRasiOverride = builtins.pathExists rasiOverride; + # Keybindings cheatsheet (SUPER+? → the `keybinds` menu module). Built + # from the SAME ./keybinds.nix that hyprland.nix binds, so it can never + # drift from the live shortcuts. "$mod" reads as SUPER; rows are padded + # into two columns. extra[] carries the generated/mouse binds. + keybinds = import ./keybinds.nix; + padRight = w: s: + let n = w - builtins.stringLength s; + in s + (if n > 0 then lib.concatStrings (builtins.genList (_: " ") n) else " "); + prettyKeys = b: + let m = lib.replaceStrings [ "$mod" ] [ "SUPER" ] b.mods; + prefix = if m == "" then "" else lib.concatStringsSep " + " (lib.splitString " " m) + " + "; + key = lib.replaceStrings [ "question" "slash" ] [ "?" "/" ] b.key; + in prefix + key; + cheatRows = + map (b: padRight 22 (prettyKeys b) + b.desc) keybinds.binds + ++ map (e: padRight 22 e.keys + e.desc) keybinds.extra; + cheatsheetFile = pkgs.writeText "nomarchy-keybinds.txt" + (lib.concatStringsSep "\n" cheatRows + "\n"); + nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' # Nomarchy menu dispatcher — thin presentation layer over # `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl, @@ -89,6 +108,45 @@ let [ -n "$q" ] || exit 0 exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;; + network) + # nmtui in a terminal (NetworkManager is the system network stack). + exec ${cfg.terminal} -e nmtui ;; + + bluetooth) + # blueman-manager GUI (services.blueman.enable, system-side). + exec blueman-manager ;; + + capture) + choice=$(printf '%s\n' \ + "󰆟 Region → clipboard" \ + "󰆟 Region → file" \ + "󰍹 Full screen → clipboard" \ + "󰍹 Full screen → file" \ + | rofi -dmenu -p capture) || exit 0 + dir="$HOME/Pictures/Screenshots" + file="$dir/$(date +%Y%m%d-%H%M%S).png" + case "$choice" in + *"Region → clipboard"*) grim -g "$(slurp)" - | wl-copy ;; + *"Region → file"*) mkdir -p "$dir"; grim -g "$(slurp)" "$file" \ + && notify-send "Screenshot saved" "$file" ;; + *"Full screen → clipboard"*) grim - | wl-copy ;; + *"Full screen → file"*) mkdir -p "$dir"; grim "$file" \ + && notify-send "Screenshot saved" "$file" ;; + esac ;; + + keybinds) + # Read-only cheatsheet, generated from keybinds.nix at build time. + rofi -dmenu -i -p keys < ${cheatsheetFile} >/dev/null || exit 0 ;; + + ask) + # Free-text question → claude CLI in a terminal (OAuth, no API key). + # Pulled fresh from npm via npx rather than nixpkgs' claude-code, + # which lags behind model releases; npx caches after first run. The + # REPL stays open for follow-ups. + q=$(rofi -dmenu -p claude < /dev/null) || exit 0 + [ -n "$q" ] || exit 0 + exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;; + "") choice=$(printf '%s\n' \ "󰀻 Apps" \ @@ -97,6 +155,11 @@ let "󰃬 Calculator" \ "󰈞 Files" \ "󰖟 Web search" \ + "󰖩 Network" \ + "󰂯 Bluetooth" \ + "󰄄 Capture" \ + "󰋖 Keybindings" \ + "󰭹 Ask Claude" \ "󰐥 Power" \ | rofi -dmenu -p menu) || exit 0 case "$choice" in @@ -106,11 +169,16 @@ let *Calc*) exec "$0" calc ;; *Files*) exec "$0" files ;; *Web*) exec "$0" web ;; + *Network*) exec "$0" network ;; + *Bluetooth*) exec "$0" bluetooth ;; + *Capture*) exec "$0" capture ;; + *Keybindings*) exec "$0" keybinds ;; + *Ask*) exec "$0" ask ;; *Power*) exec "$0" power ;; esac ;; *) - echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web]" >&2 + echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2 exit 64 ;; esac ''; @@ -122,6 +190,7 @@ in pkgs.libqalculate # qalc, the calc module's engine pkgs.fd # files module pkgs.xdg-utils # xdg-open for files/web + pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm) ]; programs.rofi = { From f06d7f361ccbd405461a19192862b76b41fcde0d Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:06:06 +0100 Subject: [PATCH 24/51] docs: roadmap item for SSH/GPG agent + graphical pinentry Co-Authored-By: Claude Opus 4.8 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 372f1bf..32f2d99 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,13 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. also locks hyprlock, so the user types a password twice. Lock only on suspend (not hibernate), or skip the hyprlock prompt when resuming from a LUKS-encrypted hibernate — see `modules/home/idle.nix`. +- **Key agents & pinentry:** no SSH/GPG agent ships yet, so signing/auth + prompts have nowhere to go. Wire up an agent + a Wayland-native graphical + pinentry: `services.ssh-agent` (or gpg-agent's `enableSshSupport`), + `programs.gnupg.agent` with `pinentry-gnome3`/`pinentry-qt`, `SSH_AUTH_SOCK` + exported, and the pinentry styled from the palette where it can be. Decide + one-agent (gpg-agent fronting SSH) vs. two, and confirm it cooperates with + hyprlock/hypridle on unlock. ## Known issues & follow-ups - **Yazi TOML parse error on startup:** `[[plugin.prepend_fetchers]]` is From 28a2f5d5ae9ebe9cea5764d2bc7bca0aa93157cb Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:14:11 +0100 Subject: [PATCH 25/51] fix: yazi fetcher group field, xfce package renames, enable nix-ld MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three quick known-issue fixes: - yazi: 26.x made the fetcher `group` field required, so the git prepend_fetchers failed to parse and yazi fell back to presets. Add `group = "git"` to both entries (the git plugin's setup() only renders the linemode — the fetcher still needs registering here). - file-manager: xfce.exo and the three Thunar plugins moved to top-level; use pkgs.xfce4-exo / pkgs.thunar-{archive-plugin,volman,media-tags-plugin}. Clears the eval-time deprecation warnings. - nix-ld: enable programs.nix-ld distro-wide so foreign dynamically-linked binaries (downloaded tools, npx-fetched claude-code, language servers) run without patchelf. Also drops the now-stale claude-code mention from the allowUnfree comment (the ask module uses npx). nix flake check passes; xfce warnings gone. Co-Authored-By: Claude Opus 4.8 --- README.md | 22 +++++++++++----------- modules/home/yazi.nix | 7 +++++-- modules/nixos/default.nix | 13 ++++++++++--- modules/nixos/file-manager.nix | 4 ++-- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 32f2d99..0310fe8 100644 --- a/README.md +++ b/README.md @@ -359,24 +359,24 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. hyprlock/hypridle on unlock. ## Known issues & follow-ups -- **Yazi TOML parse error on startup:** `[[plugin.prepend_fetchers]]` is - rejected with `missing field 'group'` (Yazi falls back to preset - settings). The fetcher schema gained a required `group` field; add it to - both git fetcher entries (e.g. `group = 0`) in `modules/home/yazi.nix:49`. +- ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' + `group` field required, so `[[plugin.prepend_fetchers]]` failed to parse + and yazi fell back to presets. Fixed by adding `group = "git"` to both + git fetcher entries in `modules/home/yazi.nix` (the git plugin's + `setup()` only renders the linemode; the fetcher still needs registering). - **Starship prompt styling:** drop the powerline look — the current prompt doesn't read well. Rework the `format`/segment styling in `modules/home/shell.nix:59` toward a flat, plain prompt. -- **xfce deprecation warnings:** `xfce.exo`, `xfce.thunar-archive-plugin`, - `xfce.thunar-volman`, `xfce.thunar-media-tags-plugin` were moved to - top-level. Switch to `pkgs.xfce4-exo`, `pkgs.thunar-archive-plugin`, - `pkgs.thunar-volman`, `pkgs.thunar-media-tags-plugin` in - `modules/nixos/file-manager.nix`. +- ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and + the three Thunar plugins to their new top-level names in + `modules/nixos/file-manager.nix`; the eval warnings are gone. - **Rofi function keybindings:** add direct Hyprland binds for the `nomarchy-menu` sub-functions (calc, web search, file search, clipboard, …) instead of only reaching them through the menu — see the planned binds in the Menu-system roadmap item and `modules/home/hyprland.nix`. -- **Enable nix-ld by default:** turn on `programs.nix-ld` distro-wide so - prebuilt/foreign dynamically-linked binaries run out of the box. +- ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide + (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked + binaries run out of the box. - **Hyprland border colors off for some themes:** the active/inactive border colors don't match the legacy branch for several themes — audit `col.active_border`/`col.inactive_border` generation in diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix index 37d54fd..d63c677 100644 --- a/modules/home/yazi.nix +++ b/modules/home/yazi.nix @@ -46,9 +46,12 @@ in }; plugin = { # git status in the linemode (fetcher registered before setup). + # yazi 26.x made `group` required on fetchers (without it the + # config fails to parse and yazi falls back to presets); "*" + # matches files, "*/" dirs, so they share the "git" group. prepend_fetchers = [ - { id = "git"; url = "*"; run = "git"; } - { id = "git"; url = "*/"; run = "git"; } + { id = "git"; url = "*"; run = "git"; group = "git"; } + { id = "git"; url = "*/"; run = "git"; group = "git"; } ]; # Route only audio + subtitles to mediainfo's text metadata; # images/videos keep yazi's native visual thumbnails. Markdown diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 08f720f..6495f15 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -20,9 +20,9 @@ in # DEFAULT_HOSTNAME and upstream isNixos checks (see roadmap). system.nixos.distroName = lib.mkDefault "Nomarchy"; - # Unfree allowed distro-wide: pragmatic-desktop territory (claude-code - # for the menu's ask-Claude module, vendor drivers, …). The custom - # nixpkgs-config type can't carry a nested mkDefault; disagree with + # Unfree allowed distro-wide: pragmatic-desktop territory (vendor + # GPU/wifi drivers, firmware, fonts, …). The custom nixpkgs-config + # type can't carry a nested mkDefault; disagree with # `nixpkgs.config = lib.mkForce { allowUnfree = false; }`. nixpkgs.config.allowUnfree = true; @@ -85,6 +85,13 @@ in hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable; services.blueman.enable = lib.mkDefault cfg.bluetooth.enable; + # ── Foreign binaries: nix-ld ───────────────────────────────────── + # An ld.so shim so dynamically-linked binaries not built for NixOS + # (downloaded tools, language servers, pip/npm-installed ELFs, the + # npx-fetched claude-code) run without manual patchelf. On by default — + # a pragmatic-desktop expectation. + programs.nix-ld.enable = lib.mkDefault true; + # ── Firmware ───────────────────────────────────────────────────── # Blobs for in-kernel drivers: wifi (iwlwifi/ath/rtw/brcm), SOF # audio, Bluetooth. Drivers ship with the kernel, but without these diff --git a/modules/nixos/file-manager.nix b/modules/nixos/file-manager.nix index 33a46e2..adc8c92 100644 --- a/modules/nixos/file-manager.nix +++ b/modules/nixos/file-manager.nix @@ -13,7 +13,7 @@ in config = lib.mkIf cfg.fileManager.enable { programs.thunar = { enable = lib.mkDefault true; - plugins = with pkgs.xfce; [ + plugins = with pkgs; [ thunar-archive-plugin # create/extract archives from the context menu thunar-volman # auto-manage removable drives/media thunar-media-tags-plugin # rename/inspect audio by tags @@ -28,7 +28,7 @@ in ffmpegthumbnailer # video thumbnails (Thunar + yazi) libgsf # ODF thumbnails poppler-utils # PDF thumbnails / pdftoppm (yazi PDF preview too) - xfce.exo # "Open Terminal Here" and default-app associations + xfce4-exo # "Open Terminal Here" and default-app associations shared-mime-info # MIME database ]; }; From d252f01f17d1adaae3d51ec8bf129d9c60a80b99 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:18:46 +0100 Subject: [PATCH 26/51] feat(shell): flat starship prompt, drop the powerline styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The powerline look (the `[]` separator glyph + bg-filled segments) didn't read well. Switch to a flat prompt: bold-accent directory, warn git branch, subtext git-status/duration, `❯` character — plain colored text, no background fills, each module carrying its own trailing space so there are no stray gaps. Also reference cmd_duration in `format` (it was configured but never shown). Generated starship.toml verified valid; nix flake check passes. Visual pass still wants a live terminal. Co-Authored-By: Claude Opus 4.8 --- README.md | 8 +++++--- modules/home/shell.nix | 30 ++++++++++++++++++------------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 0310fe8..6e9c7e5 100644 --- a/README.md +++ b/README.md @@ -364,9 +364,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. and yazi fell back to presets. Fixed by adding `group = "git"` to both git fetcher entries in `modules/home/yazi.nix` (the git plugin's `setup()` only renders the linemode; the fetcher still needs registering). -- **Starship prompt styling:** drop the powerline look — the current prompt - doesn't read well. Rework the `format`/segment styling in - `modules/home/shell.nix:59` toward a flat, plain prompt. +- ✓ **Starship prompt styling:** dropped the powerline look (the `[]` + separator glyph + `bg:` fills) for a flat prompt — bold-accent directory, + warn git branch, subtext status/duration, `❯` character, all plain + colored text (`modules/home/shell.nix`). Also wired in `cmd_duration`, + which was configured but never referenced in `format`. - ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and the three Thunar plugins to their new top-level names in `modules/nixos/file-manager.nix`; the eval warnings are gone. diff --git a/modules/home/shell.nix b/modules/home/shell.nix index f0f7c66..654294b 100644 --- a/modules/home/shell.nix +++ b/modules/home/shell.nix @@ -56,35 +56,41 @@ in palettes.nomarchy = { inherit (c) base text accent good warn bad subtext; }; + # Flat prompt — colored text only, no powerline separators or + # background fills. Each module emits its own trailing space so + # there are no stray gaps when git/duration are absent. format = lib.concatStrings [ - "[](accent)" "$directory" - "$git_branch$git_status" + "$git_branch" + "$git_status" + "$cmd_duration" "$character" ]; directory = { - style = "fg:text bg:accent"; - format = "[ $path ]($style)"; + style = "bold accent"; + format = "[$path]($style) "; truncation_length = 3; truncate_to_repo = true; }; git_branch = { - style = "fg:base bg:warn"; - format = "[ $symbol$branch ]($style)"; + style = "warn"; + format = "[$symbol$branch]($style) "; symbol = " "; }; git_status = { - style = "fg:base bg:warn"; - format = "[$all_status$ahead_behind ]($style)"; + style = "subtext"; + # Parenthesised → the segment (and its trailing space) only + # render when there's actually a status to show. + format = "([$all_status$ahead_behind]($style) )"; }; character = { - success_symbol = "[ ❯](good)"; - error_symbol = "[ ❯](bad)"; + success_symbol = "[❯](good)"; + error_symbol = "[❯](bad)"; }; cmd_duration = { min_time = 2000; - style = "fg:subtext"; - format = "[ $duration ]($style)"; + style = "subtext"; + format = "[ $duration]($style) "; }; }; }; From 7c34eed2caccee6a1ab2f4531ec7343752286438 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:32:18 +0100 Subject: [PATCH 27/51] fix(theme): solid per-theme hyprland borders, matching legacy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1 forced an accent→accentAlt 45deg gradient on every theme's window border; the legacy identity themes all used a *solid* border — accent for nord/retro-82/lumon, the text tone for kanagawa/summer-day/summer-night. Add a palette-resolved `border` field ({active, inactive}, each a palette key or literal hex) to the theme schema with accent/overlay defaults in theme.nix, consume it solid in hyprland.nix, and declare it in every preset (and the live state). Declaring it everywhere is required because `nomarchy-theme-sync apply` deep-merges presets — an omitted field would stick across switches, like colors already are fully specified per theme. All six identity themes resolve to their exact legacy active border (verified: nord #81a1c1, retro-82 #faa968, lumon #f2fcff, kanagawa #dcd7ba, summer-night #d3c6aa, summer-day #5c6a72). nix flake check passes. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 4 +++- README.md | 11 +++++++---- modules/home/hyprland.nix | 6 ++++-- modules/home/theme.nix | 16 +++++++++++++++- theme-state.json | 4 ++++ themes/catppuccin-latte.json | 4 ++++ themes/catppuccin.json | 4 ++++ themes/ethereal.json | 4 ++++ themes/everforest.json | 4 ++++ themes/flexoki-light.json | 4 ++++ themes/gruvbox.json | 4 ++++ themes/hackerman.json | 4 ++++ themes/kanagawa.json | 4 ++++ themes/lumon.json | 4 ++++ themes/matte-black.json | 4 ++++ themes/miasma.json | 4 ++++ themes/nord.json | 4 ++++ themes/osaka-jade.json | 4 ++++ themes/retro-82.json | 4 ++++ themes/ristretto.json | 4 ++++ themes/rose-pine.json | 4 ++++ themes/summer-day.json | 4 ++++ themes/summer-night.json | 4 ++++ themes/tokyo-night.json | 4 ++++ themes/vantablack.json | 4 ++++ themes/white.json | 4 ++++ 26 files changed, 117 insertions(+), 8 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1323d91..014fdd9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -262,7 +262,9 @@ "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-night --no-switch)", "Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-day --no-switch)", "Bash(xargs cat)", - "Bash(nix-instantiate --parse modules/home/rofi.nix)" + "Bash(nix-instantiate --parse modules/home/rofi.nix)", + "Bash(nix-instantiate --parse modules/home/shell.nix)", + "Bash(command -v nix)" ] } } diff --git a/README.md b/README.md index 6e9c7e5..a82de75 100644 --- a/README.md +++ b/README.md @@ -379,10 +379,13 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. - ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked binaries run out of the box. -- **Hyprland border colors off for some themes:** the active/inactive border - colors don't match the legacy branch for several themes — audit - `col.active_border`/`col.inactive_border` generation in - `modules/home/hyprland.nix` against legacy. +- ✓ **Hyprland border colors off for some themes:** the cause was v1 + forcing an `accent→accentAlt` gradient on every theme; legacy used a + *solid* border (accent for nord/retro-82/lumon, the text tone for + kanagawa/summer-day/summer-night). Added a palette-resolved `border` + field (`{active, inactive}`) to the theme schema (`theme.nix`), consumed + solid in `hyprland.nix`, and declared it in every preset so a switch + always replaces it. All six identity themes now match legacy exactly. - **Waybar shows non-existent workspaces:** in summer-night/summer-day the bar renders every workspace icon up-front via `persistent-workspaces` / `all-outputs` in `themes/summer-{day,night}/waybar.jsonc`. Restore the diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index d7a2f1d..ca36114 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -65,8 +65,10 @@ in gaps_in = t.ui.gapsIn; gaps_out = t.ui.gapsOut; border_size = t.ui.borderSize; - "col.active_border" = "${rgb c.accent} ${rgb c.accentAlt} 45deg"; - "col.inactive_border" = rgb c.overlay; + # Solid borders from theme.border (per-theme, palette-resolved) — + # matches the legacy identity themes, which never used a gradient. + "col.active_border" = rgb t.border.active; + "col.inactive_border" = rgb t.border.inactive; layout = lib.mkDefault "dwindle"; resize_on_border = lib.mkDefault true; }; diff --git a/modules/home/theme.nix b/modules/home/theme.nix index 72420f2..8221f19 100644 --- a/modules/home/theme.nix +++ b/modules/home/theme.nix @@ -47,10 +47,24 @@ let # Icon theme name; "" means "pick by mode" (resolved below). A preset # or the state file can name any theme in the shipped icon package. icons = ""; + # Window border colors. Values are palette keys (resolved against + # `colors` below) or literal #rrggbb. Solid, not a gradient — every + # legacy identity theme used a solid border (accent for most, the text + # tone for kanagawa/summer-*). Each preset declares its own so a theme + # switch always replaces it (deep_merge would otherwise leave it stuck). + border = { active = "accent"; inactive = "overlay"; }; }; parsed = lib.recursiveUpdate defaults themeState; + # A border value is a palette key (look it up in colors) unless it's + # already a literal hex; unknown keys fall through to the raw string. + resolveColor = v: if lib.hasPrefix "#" v then v else parsed.colors.${v} or v; + border = { + active = resolveColor parsed.border.active; + inactive = resolveColor parsed.border.inactive; + }; + # Resolve the icon theme once and expose it on nomarchy.theme so both # the GTK side (stylix.nix) and rofi (rofi.nix) agree. The shipped # package (papirus-icon-theme) carries the Dark/Light pair. @@ -61,7 +75,7 @@ let in { config = { - nomarchy.theme = parsed // { inherit iconTheme; }; + nomarchy.theme = parsed // { inherit iconTheme border; }; nomarchy.lib = { # "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax) diff --git a/theme-state.json b/theme-state.json index 635e11d..04b0e7b 100644 --- a/theme-state.json +++ b/theme-state.json @@ -18,6 +18,10 @@ "warn": "#e0af68", "bad": "#f7768e" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#32344a", "#f7768e", diff --git a/themes/catppuccin-latte.json b/themes/catppuccin-latte.json index a6565d7..321a28e 100644 --- a/themes/catppuccin-latte.json +++ b/themes/catppuccin-latte.json @@ -18,6 +18,10 @@ "warn": "#df8e1d", "bad": "#d20f39" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#bcc0cc", "#d20f39", diff --git a/themes/catppuccin.json b/themes/catppuccin.json index da34057..c7cb9f9 100644 --- a/themes/catppuccin.json +++ b/themes/catppuccin.json @@ -18,6 +18,10 @@ "warn": "#f9e2af", "bad": "#f38ba8" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#45475a", "#f38ba8", diff --git a/themes/ethereal.json b/themes/ethereal.json index 2b8fdc0..2ac204b 100644 --- a/themes/ethereal.json +++ b/themes/ethereal.json @@ -18,6 +18,10 @@ "warn": "#E9BB4F", "bad": "#ED5B5A" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#060B1E", "#ED5B5A", diff --git a/themes/everforest.json b/themes/everforest.json index 054406e..e604bbd 100644 --- a/themes/everforest.json +++ b/themes/everforest.json @@ -18,6 +18,10 @@ "warn": "#dbbc7f", "bad": "#e67e80" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#475258", "#e67e80", diff --git a/themes/flexoki-light.json b/themes/flexoki-light.json index e972540..4be4409 100644 --- a/themes/flexoki-light.json +++ b/themes/flexoki-light.json @@ -18,6 +18,10 @@ "warn": "#D0A215", "bad": "#D14D41" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#100F0F", "#D14D41", diff --git a/themes/gruvbox.json b/themes/gruvbox.json index a95691d..fd8c94a 100644 --- a/themes/gruvbox.json +++ b/themes/gruvbox.json @@ -18,6 +18,10 @@ "warn": "#d8a657", "bad": "#ea6962" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#3c3836", "#ea6962", diff --git a/themes/hackerman.json b/themes/hackerman.json index 39bd534..04d6518 100644 --- a/themes/hackerman.json +++ b/themes/hackerman.json @@ -18,6 +18,10 @@ "warn": "#50f7d4", "bad": "#50f872" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#0B0C16", "#50f872", diff --git a/themes/kanagawa.json b/themes/kanagawa.json index 96076d0..6bc93bb 100644 --- a/themes/kanagawa.json +++ b/themes/kanagawa.json @@ -18,6 +18,10 @@ "warn": "#c0a36e", "bad": "#c34043" }, + "border": { + "active": "text", + "inactive": "overlay" + }, "ansi": [ "#090618", "#c34043", diff --git a/themes/lumon.json b/themes/lumon.json index e8558df..554c34b 100644 --- a/themes/lumon.json +++ b/themes/lumon.json @@ -18,6 +18,10 @@ "warn": "#6fa4c9", "bad": "#4d86b0" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#1b2d40", "#4d86b0", diff --git a/themes/matte-black.json b/themes/matte-black.json index 714b5f2..a5432d2 100644 --- a/themes/matte-black.json +++ b/themes/matte-black.json @@ -18,6 +18,10 @@ "warn": "#b91c1c", "bad": "#D35F5F" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#333333", "#D35F5F", diff --git a/themes/miasma.json b/themes/miasma.json index bcc23cf..910f279 100644 --- a/themes/miasma.json +++ b/themes/miasma.json @@ -18,6 +18,10 @@ "warn": "#b36d43", "bad": "#685742" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#000000", "#685742", diff --git a/themes/nord.json b/themes/nord.json index 6c12550..b3f7910 100644 --- a/themes/nord.json +++ b/themes/nord.json @@ -18,6 +18,10 @@ "warn": "#ebcb8b", "bad": "#bf616a" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#3b4252", "#bf616a", diff --git a/themes/osaka-jade.json b/themes/osaka-jade.json index e794f56..78fc5ee 100644 --- a/themes/osaka-jade.json +++ b/themes/osaka-jade.json @@ -18,6 +18,10 @@ "warn": "#459451", "bad": "#FF5345" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#23372B", "#FF5345", diff --git a/themes/retro-82.json b/themes/retro-82.json index a5c9177..ccd05b6 100644 --- a/themes/retro-82.json +++ b/themes/retro-82.json @@ -18,6 +18,10 @@ "warn": "#e97b3c", "bad": "#f85525" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#00172e", "#f85525", diff --git a/themes/ristretto.json b/themes/ristretto.json index 528d5aa..eab2487 100644 --- a/themes/ristretto.json +++ b/themes/ristretto.json @@ -18,6 +18,10 @@ "warn": "#f9cc6c", "bad": "#fd6883" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#72696a", "#fd6883", diff --git a/themes/rose-pine.json b/themes/rose-pine.json index b11da41..9ada386 100644 --- a/themes/rose-pine.json +++ b/themes/rose-pine.json @@ -18,6 +18,10 @@ "warn": "#ea9d34", "bad": "#b4637a" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#f2e9e1", "#b4637a", diff --git a/themes/summer-day.json b/themes/summer-day.json index 29132ad..da9124a 100644 --- a/themes/summer-day.json +++ b/themes/summer-day.json @@ -18,6 +18,10 @@ "warn": "#dfa000", "bad": "#f85552" }, + "border": { + "active": "text", + "inactive": "overlay" + }, "ansi": [ "#5c6a72", "#f85552", diff --git a/themes/summer-night.json b/themes/summer-night.json index 488d3b1..99b07cf 100644 --- a/themes/summer-night.json +++ b/themes/summer-night.json @@ -18,6 +18,10 @@ "warn": "#d9bb80", "bad": "#e68183" }, + "border": { + "active": "text", + "inactive": "overlay" + }, "ansi": [ "#3c474d", "#e68183", diff --git a/themes/tokyo-night.json b/themes/tokyo-night.json index aa7b209..e60a0d4 100644 --- a/themes/tokyo-night.json +++ b/themes/tokyo-night.json @@ -18,6 +18,10 @@ "warn": "#e0af68", "bad": "#f7768e" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#32344a", "#f7768e", diff --git a/themes/vantablack.json b/themes/vantablack.json index fada473..ef3e4ca 100644 --- a/themes/vantablack.json +++ b/themes/vantablack.json @@ -18,6 +18,10 @@ "warn": "#cecece", "bad": "#a4a4a4" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#0d0d0d", "#a4a4a4", diff --git a/themes/white.json b/themes/white.json index b65f62f..97dff38 100644 --- a/themes/white.json +++ b/themes/white.json @@ -18,6 +18,10 @@ "warn": "#4a4a4a", "bad": "#2a2a2a" }, + "border": { + "active": "accent", + "inactive": "overlay" + }, "ansi": [ "#ffffff", "#2a2a2a", From f6c4a2a5916f472f5a1322fa6b70e507ac2656cd Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:35:25 +0100 Subject: [PATCH 28/51] fix(waybar): stop summer themes rendering phantom workspaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit summer-day/summer-night showed all 10 workspace icons up-front. The cause: legacy used the deprecated `persistent_workspaces` (underscore), which current Waybar silently ignores — so legacy only ever showed *existing* workspaces. The v1 port rewrote it to the modern `persistent-workspaces` (hyphen), which Waybar honours, surfacing all ten as phantoms. Drop the block from both summer waybar.jsonc whole-swaps. The other themes render from waybar.nix, which never declared persistent-workspaces, so they already only show existing workspaces — no change needed there. Co-Authored-By: Claude Opus 4.8 --- README.md | 12 +++++++----- themes/summer-day/waybar.jsonc | 6 +----- themes/summer-night/waybar.jsonc | 6 +----- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a82de75..f94415f 100644 --- a/README.md +++ b/README.md @@ -386,8 +386,10 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. field (`{active, inactive}`) to the theme schema (`theme.nix`), consumed solid in `hyprland.nix`, and declared it in every preset so a switch always replaces it. All six identity themes now match legacy exactly. -- **Waybar shows non-existent workspaces:** in summer-night/summer-day the - bar renders every workspace icon up-front via `persistent-workspaces` / - `all-outputs` in `themes/summer-{day,night}/waybar.jsonc`. Restore the - legacy behaviour (only show workspaces that exist) here and across the - other themes. +- ✓ **Waybar shows non-existent workspaces:** the v1 summer port "fixed" + legacy's deprecated `persistent_workspaces` (underscore — silently + ignored by current Waybar, so legacy only ever showed existing + workspaces) into the modern `persistent-workspaces` (hyphen), which + Waybar honours → all 10 rendered as phantoms. Dropped the block from + `themes/summer-{day,night}/waybar.jsonc`; the other themes use the + generated `waybar.nix`, which never had it, so they already match. diff --git a/themes/summer-day/waybar.jsonc b/themes/summer-day/waybar.jsonc index a45b8f5..8fb252e 100644 --- a/themes/summer-day/waybar.jsonc +++ b/themes/summer-day/waybar.jsonc @@ -18,11 +18,7 @@ "all-outputs": true, "on-click": "activate", "on-scroll-up": "hyprctl dispatch workspace e+1", - "on-scroll-down": "hyprctl dispatch workspace e-1", - "persistent-workspaces": { - "1": [], "2": [], "3": [], "4": [], "5": [], - "6": [], "7": [], "8": [], "9": [], "10": [] - } + "on-scroll-down": "hyprctl dispatch workspace e-1" }, "custom/launcher": { diff --git a/themes/summer-night/waybar.jsonc b/themes/summer-night/waybar.jsonc index e5a1430..ffa36e2 100644 --- a/themes/summer-night/waybar.jsonc +++ b/themes/summer-night/waybar.jsonc @@ -18,11 +18,7 @@ "all-outputs": true, "on-click": "activate", "on-scroll-up": "hyprctl dispatch workspace r+1", - "on-scroll-down": "hyprctl dispatch workspace r-1", - "persistent-workspaces": { - "1": [], "2": [], "3": [], "4": [], "5": [], - "6": [], "7": [], "8": [], "9": [], "10": [] - } + "on-scroll-down": "hyprctl dispatch workspace r-1" }, "custom/nomarchy": { From 05d995f8e0c19eb2b0232d98c7e3af8ef4e2d847 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:39:12 +0100 Subject: [PATCH 29/51] feat(keybinds): direct SUPER+CTRL binds for the menu functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each nomarchy-menu module is now reachable directly, not just through the SUPER+M picker: SUPER+CTRL+ V clipboard · C calc · W web · F files · N network · B bluetooth · S capture · A ask. Added to keybinds.nix (the single source), so they render in the SUPER+? cheatsheet too. No key collisions; nix flake check passes. Co-Authored-By: Claude Opus 4.8 --- README.md | 8 ++++---- modules/home/keybinds.nix | 12 +++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f94415f..92acc4d 100644 --- a/README.md +++ b/README.md @@ -372,10 +372,10 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. - ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and the three Thunar plugins to their new top-level names in `modules/nixos/file-manager.nix`; the eval warnings are gone. -- **Rofi function keybindings:** add direct Hyprland binds for the - `nomarchy-menu` sub-functions (calc, web search, file search, clipboard, - …) instead of only reaching them through the menu — see the planned binds - in the Menu-system roadmap item and `modules/home/hyprland.nix`. +- ✓ **Rofi function keybindings:** direct `SUPER+CTRL+` binds jump + straight to each `nomarchy-menu` module — V clipboard · C calc · W web · + F files · N network · B bluetooth · S capture · A ask — added to + `modules/home/keybinds.nix`, so they also show in the SUPER+? cheatsheet. - ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked binaries run out of the box. diff --git a/modules/home/keybinds.nix b/modules/home/keybinds.nix index 0fe7f52..de2ade5 100644 --- a/modules/home/keybinds.nix +++ b/modules/home/keybinds.nix @@ -33,10 +33,20 @@ { mods = "$mod"; key = "X"; action = "exec, nomarchy-menu power"; desc = "Power menu"; } { mods = "$mod"; key = "N"; action = "exec, swaync-client -t"; desc = "Notification centre"; } - { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } # SUPER+? (the "question" keysym already implies Shift on most layouts). { mods = "$mod"; key = "question"; action = "exec, nomarchy-menu keybinds"; desc = "Keybindings cheatsheet"; } + # Menu functions — SUPER+CTRL+ jumps straight to a + # nomarchy-menu module (all also reachable from the SUPER+M picker). + { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } + { mods = "$mod CTRL"; key = "C"; action = "exec, nomarchy-menu calc"; desc = "Calculator"; } + { mods = "$mod CTRL"; key = "W"; action = "exec, nomarchy-menu web"; desc = "Web search"; } + { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File search"; } + { mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (nmtui)"; } + { mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; } + { mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; } + { mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; } + # Focus { mods = "$mod"; key = "H"; action = "movefocus, l"; desc = "Focus left"; } { mods = "$mod"; key = "L"; action = "movefocus, r"; desc = "Focus right"; } From 3757e7d66fad35b3dff5dead8bed21fd5ea75d5f Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 20:50:45 +0100 Subject: [PATCH 30/51] feat(keys): SSH + GPG agent via gpg-agent, pinentry-qt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add modules/home/keys.nix (nomarchy.keys.enable, default on): one agent — services.gpg-agent with enableSshSupport fronts SSH, so a single pinentry handles every passphrase (GPG signing/decryption and SSH key unlocks). pinentry-qt for a reliable native-Wayland prompt under Hyprland, themed by Stylix's Qt config. Cache TTLs 30 min / 2 h. SSH_AUTH_SOCK is exported by the agent's zsh integration (terminal git/ssh is the supported path). gnome-keyring stays the Secret Service — modern versions run no SSH agent, so there's no socket contention; screen lock doesn't flush the agent cache. nix flake check passes. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 3 ++- README.md | 16 +++++++------- modules/home/default.nix | 1 + modules/home/keys.nix | 42 +++++++++++++++++++++++++++++++++++++ modules/home/options.nix | 1 + 5 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 modules/home/keys.nix diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 014fdd9..47ecc96 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -264,7 +264,8 @@ "Bash(xargs cat)", "Bash(nix-instantiate --parse modules/home/rofi.nix)", "Bash(nix-instantiate --parse modules/home/shell.nix)", - "Bash(command -v nix)" + "Bash(command -v nix)", + "Bash(nix-instantiate --parse modules/home/keys.nix)" ] } } diff --git a/README.md b/README.md index 92acc4d..ecc3ad3 100644 --- a/README.md +++ b/README.md @@ -350,13 +350,15 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. also locks hyprlock, so the user types a password twice. Lock only on suspend (not hibernate), or skip the hyprlock prompt when resuming from a LUKS-encrypted hibernate — see `modules/home/idle.nix`. -- **Key agents & pinentry:** no SSH/GPG agent ships yet, so signing/auth - prompts have nowhere to go. Wire up an agent + a Wayland-native graphical - pinentry: `services.ssh-agent` (or gpg-agent's `enableSshSupport`), - `programs.gnupg.agent` with `pinentry-gnome3`/`pinentry-qt`, `SSH_AUTH_SOCK` - exported, and the pinentry styled from the palette where it can be. Decide - one-agent (gpg-agent fronting SSH) vs. two, and confirm it cooperates with - hyprlock/hypridle on unlock. +- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix` + (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with + `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, + Stylix-themed Qt) handles GPG and SSH passphrases alike. `SSH_AUTH_SOCK` + comes from the agent's zsh integration; cache TTLs (30 min / 2 h) govern + re-prompting. gnome-keyring stays the Secret Service (modern versions run + no SSH agent, so no socket contention); screen lock doesn't flush the + cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI + clients launched outside a shell also see the agent. ## Known issues & follow-ups - ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' diff --git a/modules/home/default.nix b/modules/home/default.nix index b89a04e..4539e79 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -18,6 +18,7 @@ ./yazi.nix # flagship TUI file manager, themed + plugins ./osd.nix # swayosd volume/brightness OSD, themed ./shell.nix # zsh + starship + bat/eza/zoxide, themed + ./keys.nix # gpg-agent (fronts SSH) + pinentry-qt ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/keys.nix b/modules/home/keys.nix new file mode 100644 index 0000000..aea9840 --- /dev/null +++ b/modules/home/keys.nix @@ -0,0 +1,42 @@ +# SSH + GPG agent — one agent for everything. +# +# gpg-agent fronts SSH (enableSshSupport), so a single pinentry handles +# every passphrase: GPG signing/decryption and SSH key unlocks alike. SSH +# keys are added with `ssh-add` (gpg-agent stores them by keygrip). +# +# Pinentry is pinentry-qt — reliable native-Wayland under Hyprland (no +# GNOME session to lean on) and themed by Stylix's Qt config, so the +# passphrase dialog tracks the palette. +# +# SSH_AUTH_SOCK is exported by the agent's shell integration (zsh, on via +# home.shell.enableZshIntegration in shell.nix). Terminal git/ssh is the +# supported path; a GUI client launched outside a shell won't inherit the +# socket — revisit with a session-level export if that's ever wanted. +# +# gnome-keyring (system side) stays the Secret Service for application +# secrets; modern gnome-keyring no longer runs an SSH agent, so there is no +# SSH_AUTH_SOCK contention with gpg-agent. Screen lock (hyprlock/hypridle) +# doesn't flush the agent cache — unlocked keys persist across a lock, as +# expected; the cache TTLs below govern re-prompting instead. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; +in +{ + config = lib.mkIf cfg.keys.enable { + programs.gpg.enable = true; + + services.gpg-agent = { + enable = true; + enableSshSupport = true; + pinentry.package = pkgs.pinentry-qt; + + # Cache unlocked keys for a working session, then re-prompt. + defaultCacheTtl = 1800; # 30 min since last use + maxCacheTtl = 7200; # 2 h hard cap + defaultCacheTtlSsh = 1800; + maxCacheTtlSsh = 7200; + }; + }; +} diff --git a/modules/home/options.nix b/modules/home/options.nix index 6fa3d86..234cb09 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -69,6 +69,7 @@ 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; }; 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; }; From e411d35544119e9809be8ac6c446ec6e23d42a40 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 21:17:42 +0100 Subject: [PATCH 31/51] fix(plymouth): recolor splash art from the palette for contrast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shipped boot art was a fixed navy that vanished against dark theme bases. Recolor each element from theme-state.json at build time (flat fill, alpha preserved): foreground glyphs (logo, padlock, password dots) → text, the field/track boxes (entry, progress_box) → surface, and the progress fill → accent. text/surface both contrast with base in either polarity, so the splash now reads on light and dark themes alike. Adds imagemagick as a build-time input to the theme derivation. Like the background tint, this follows the theme as of the last system rebuild. Co-Authored-By: Claude Opus 4.8 --- modules/nixos/plymouth.nix | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/modules/nixos/plymouth.nix b/modules/nixos/plymouth.nix index 36b9161..b57c262 100644 --- a/modules/nixos/plymouth.nix +++ b/modules/nixos/plymouth.nix @@ -14,7 +14,16 @@ let if cfg.stateFile != null then builtins.fromJSON (builtins.readFile cfg.stateFile) else { }; - base = lib.removePrefix "#" ((state.colors or { }).base or "#1a1b26"); + colorOf = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback); + base = colorOf "base" "#1a1b26"; + # Splash elements are recolored from the palette so they read on any + # base, light or dark: foreground glyphs → text, field/track boxes → + # surface (raised from base in both polarities), the progress fill → + # accent. The shipped art is a fixed navy that vanished on dark themes. + text = colorOf "text" "#a9b1d6"; + surface = colorOf "surface" "#32344a"; + accent = colorOf "accent" "#7aa2f7"; + magick = lib.getExe' pkgs.imagemagick "magick"; # Plymouth's Window.SetBackgroundTopColor takes three floats in # 0.0–1.0; the .plymouth metadata's ConsoleLogBackgroundColor takes a @@ -36,13 +45,26 @@ let src = ./plymouth; + nativeBuildInputs = [ pkgs.imagemagick ]; + installPhase = '' - mkdir -p $out/share/plymouth/themes/nomarchy - cp * $out/share/plymouth/themes/nomarchy/ + themedir=$out/share/plymouth/themes/nomarchy + mkdir -p "$themedir" + cp * "$themedir/" + + # Recolor the splash art from the palette (flat fill, alpha kept) so + # it reads on any base instead of the shipped fixed navy. + recolor() { ${magick} "$themedir/$1" -fill "#$2" -colorize 100 "$themedir/$1"; } + recolor logo.png ${text} # centre monogram + recolor lock.png ${text} # password-prompt padlock + recolor bullet.png ${text} # password dots + recolor entry.png ${surface} # password field box + recolor progress_box.png ${surface} # progress track + recolor progress_bar.png ${accent} # progress fill # Point the .plymouth metadata into the store - sed -i "s|/usr/share/plymouth/themes/nomarchy|$out/share/plymouth/themes/nomarchy|g" \ - $out/share/plymouth/themes/nomarchy/nomarchy.plymouth + sed -i "s|/usr/share/plymouth/themes/nomarchy|$themedir|g" \ + "$themedir/nomarchy.plymouth" # Bake the theme's base color: RGB floats for the script's # Window.SetBackground* calls, 0xRRGGBB for ConsoleLogBackground. @@ -50,9 +72,9 @@ let -e 's|@BG_R@|${channel 0}|g' \ -e 's|@BG_G@|${channel 2}|g' \ -e 's|@BG_B@|${channel 4}|g' \ - $out/share/plymouth/themes/nomarchy/nomarchy.script + "$themedir/nomarchy.script" sed -i 's|@BG_HEX@|${base}|g' \ - $out/share/plymouth/themes/nomarchy/nomarchy.plymouth + "$themedir/nomarchy.plymouth" ''; }; in From fdb0685eec4716ce38e6411768c3df74209883c7 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 21:18:37 +0100 Subject: [PATCH 32/51] docs: mark plymouth contrast done; log GTK/Qt light-dark issue Co-Authored-By: Claude Opus 4.8 --- README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ecc3ad3..e124de8 100644 --- a/README.md +++ b/README.md @@ -341,10 +341,12 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `nomarchy.apps.*` option surface so each is individually opt-out for users who want a leaner machine. Watch closure size (texlive-full is multi-GB — likely default-off or a lighter scheme). -- **Plymouth logo contrast:** the shipped splash logo is too dark against - the dark `base` background; recolor/lighten it (or tint from the palette - `accent`/`text` instead of shipping a fixed navy asset) so it reads on - both light and dark themes. +- ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that + vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every + element from the palette at build time (flat fill, alpha kept): + logo/lock/bullet → text, entry/progress-box → surface, progress-bar → + accent. Reads on light and dark; follows the theme as of the last system + rebuild (like the background tint). - **Hibernate double-unlock:** on resume from hibernate the LUKS passphrase already gates the machine, but hypridle's `before_sleep_cmd` also locks hyprlock, so the user types a password twice. Lock only on @@ -395,3 +397,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. Waybar honours → all 10 rendered as phantoms. Dropped the block from `themes/summer-{day,night}/waybar.jsonc`; the other themes use the generated `waybar.nix`, which never had it, so they already match. +- **GTK/Qt ignore the theme's light/dark mode:** `stylix.nix` sets + `polarity` from `theme.mode`, but GTK4/libadwaita apps (and Qt via the + portal) decide dark vs light from the `org.freedesktop.appearance + color-scheme` / gsettings `org.gnome.desktop.interface color-scheme` key, + which Stylix doesn't write — so a light theme can still render apps dark + (and vice-versa). Fix: set `dconf` `color-scheme` = + `prefer-dark`/`prefer-light` from `t.mode` (and confirm the + xdg-desktop-portal exposes it), so libadwaita/Qt follow the palette. From 6513908067e05a60a15edea75eef677ad376a01a Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 21:31:46 +0100 Subject: [PATCH 33/51] feat(branding): tuigreet greeting + branded MOTD Round 2 of distro branding, text half: tuigreet now shows 'Welcome to ', and users.motd carries a branded banner that doubles as a cheat sheet for the distro helpers (sys-update, home-update, nomarchy-theme-sync, SUPER+?). Both key off system.nixos.distroName so the brand name stays in one place. Deferred (need an asset / a boot test): ISO splashImage/grubTheme art and the distroId switch. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 10 +++++++++- README.md | 4 +++- modules/nixos/default.nix | 18 +++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 47ecc96..62be6e2 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -265,7 +265,15 @@ "Bash(nix-instantiate --parse modules/home/rofi.nix)", "Bash(nix-instantiate --parse modules/home/shell.nix)", "Bash(command -v nix)", - "Bash(nix-instantiate --parse modules/home/keys.nix)" + "Bash(nix-instantiate --parse modules/home/keys.nix)", + "Bash(cp /home/bernardo/Projects/Nomarchy/modules/nixos/plymouth/logo.png /tmp/logo-orig.png)", + "Bash(nix run *)", + "Bash(cp /tmp/logo-text.png /home/bernardo/Projects/Nomarchy/.tmp-logo-preview.png)", + "Bash(cp /tmp/logo-text.png /home/bernardo/Projects/Nomarchy/preview-logo.png)", + "Bash(nix-instantiate --parse modules/nixos/plymouth.nix)", + "Bash(echo \"parse OK\")", + "Bash(echo \"=== build the plymouth theme derivation directly ===\")", + "Bash(nix-instantiate --parse modules/nixos/default.nix)" ] } } diff --git a/README.md b/README.md index e124de8..56e9c68 100644 --- a/README.md +++ b/README.md @@ -316,8 +316,10 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `nomarchy.osd.*`, media keys drive `swayosd-client`, themed from the JSON) - **Distro branding, round 2:** `distroName = "Nomarchy"` ships (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). + ✓ tuigreet greeting (`Welcome to `) and a branded `users.motd` + (doubling as a helper cheat sheet), both keyed off `distroName`. Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot - screen, tuigreet/MOTD text, and the `distroId` question (it changes + screen (needs a designed asset), and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 6495f15..ebc9231 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -9,6 +9,9 @@ let cfg = config.nomarchy.system; + # Reused by the greeter banner and MOTD so the brand name lives in one + # place (system.nixos.distroName, set below). + distroName = config.system.nixos.distroName; in { imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ]; @@ -20,6 +23,19 @@ in # DEFAULT_HOSTNAME and upstream isNixos checks (see roadmap). system.nixos.distroName = lib.mkDefault "Nomarchy"; + # MOTD on TTY/SSH login (the desktop auto-logs into Hyprland, so this + # is mostly seen over SSH or on a bare console). Branded, and doubles + # as a cheat sheet for the distro's own helpers. + users.motd = lib.mkDefault '' + + ${distroName} — a NixOS desktop, themed from one JSON. + + sys-update update inputs + rebuild the system + home-update apply home/theme changes (no sudo) + nomarchy-theme-sync apply switch the whole palette + SUPER+? keybindings cheatsheet + ''; + # Unfree allowed distro-wide: pragmatic-desktop territory (vendor # GPU/wifi drivers, firmware, fonts, …). The custom nixpkgs-config # type can't carry a nested mkDefault; disagree with @@ -42,7 +58,7 @@ in default_session = { # start-hyprland is Hyprland 0.55's watchdog launcher; running # the bare binary makes every session print a warning. - command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --cmd start-hyprland"; + command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}' --cmd start-hyprland"; user = "greeter"; }; # Boot straight into the session once; logout → normal greeter. From cde279af0b5765bf8aacf63ae27fb3f556c30d1b Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 21:46:40 +0100 Subject: [PATCH 34/51] feat(branding): ISO boot splash from the vendored Nomarchy logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vendor the vector logo from the legacy branch (modules/nixos/branding/logo.svg) and use it for the live ISO's isolinux splash: rsvg-convert renders it, recolored to the palette accent and centred on the theme base (from theme-state.json) at build time, wired via isoImage.splashImage in hosts/live.nix. Verified the generated PNG by building it in the sandbox. UEFI/GRUB still uses the stock theme (grubTheme needs a full theme dir — left on the roadmap), as does the distroId switch. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 5 ++- README.md | 7 ++-- hosts/live.nix | 16 +++++++++ modules/nixos/branding/logo.svg | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 modules/nixos/branding/logo.svg diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 62be6e2..f846cf9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -273,7 +273,10 @@ "Bash(nix-instantiate --parse modules/nixos/plymouth.nix)", "Bash(echo \"parse OK\")", "Bash(echo \"=== build the plymouth theme derivation directly ===\")", - "Bash(nix-instantiate --parse modules/nixos/default.nix)" + "Bash(nix-instantiate --parse modules/nixos/default.nix)", + "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png/* preview-splash-built.png)", + "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png preview-splash-built.png)", + "Bash(nix-instantiate --parse hosts/live.nix)" ] } } diff --git a/README.md b/README.md index 56e9c68..96eaf83 100644 --- a/README.md +++ b/README.md @@ -318,8 +318,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). ✓ tuigreet greeting (`Welcome to `) and a branded `users.motd` (doubling as a helper cheat sheet), both keyed off `distroName`. - Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot - screen (needs a designed asset), and the `distroId` question (it changes + ✓ `isoImage.splashImage` — the vendored vector logo + (`modules/nixos/branding/logo.svg`, from legacy) recolored to the palette + accent on the theme base, built at ISO-build time (`hosts/live.nix`). + Remaining: `isoImage.grubTheme` so UEFI boot matches the isolinux splash + (needs a full grub theme dir), and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now diff --git a/hosts/live.nix b/hosts/live.nix index ac769a0..a8e9b2f 100644 --- a/hosts/live.nix +++ b/hosts/live.nix @@ -3,11 +3,27 @@ # exists to test the distro end-to-end on real hardware. { lib, pkgs, username, nomarchySrc, ... }: +let + # ISO boot splash: the Nomarchy monogram recolored to the palette accent, + # centred on the theme base. Built from the vendored vector logo and the + # live theme-state.json (tokyo-night by default). Shows on the isolinux + # (BIOS) boot menu; UEFI/GRUB still uses the stock theme (see roadmap). + state = builtins.fromJSON (builtins.readFile ../theme-state.json); + isoColor = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback); + isoSplash = pkgs.runCommand "nomarchy-iso-splash.png" + { nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg ]; } '' + rsvg-convert -h 320 ${../modules/nixos/branding/logo.svg} > logo.png + magick logo.png -fill "#${isoColor "accent" "7aa2f7"}" -colorize 100 logo-c.png + magick -size 1920x1080 xc:"#${isoColor "base" "1a1b26"}" \ + logo-c.png -gravity center -composite $out + ''; +in { networking.hostName = "nomarchy-live"; isoImage.volumeID = lib.mkForce "NOMARCHY_LIVE"; isoImage.edition = lib.mkForce "live"; + isoImage.splashImage = isoSplash; # The minimal-CD profile slims the image for a CONSOLE installer; this # ISO is the desktop, so re-enable what it strips. Above all diff --git a/modules/nixos/branding/logo.svg b/modules/nixos/branding/logo.svg new file mode 100644 index 0000000..b060ba0 --- /dev/null +++ b/modules/nixos/branding/logo.svg @@ -0,0 +1,64 @@ + + + + From 4017b6cff6a4d05fc6595d49af24cf8824658c51 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 21:53:08 +0100 Subject: [PATCH 35/51] fix(stylix): drive GTK4/Qt dark-light from the palette via color-scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stylix set polarity and the GTK theme but not the XDG portal's org.freedesktop.appearance color-scheme, which GTK4/libadwaita and Qt6 apps actually read to choose dark vs light — so a light theme could still render those apps dark (and vice-versa). Set dconf org/gnome/desktop/interface color-scheme to prefer-light/prefer-dark from theme.mode in stylix.nix. xdg-desktop-portal-gtk already ships and programs.dconf is on system-side, so the portal exposes it to apps. Also log two branding follow-ups from the legacy assets: fastfetch ASCII logo (logo.txt) and the Nomarchy.ttf glyph for the Waybar menu button. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 3 ++- README.md | 26 ++++++++++++++++++-------- modules/home/stylix.nix | 9 +++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f846cf9..f78f76e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -276,7 +276,8 @@ "Bash(nix-instantiate --parse modules/nixos/default.nix)", "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png/* preview-splash-built.png)", "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png preview-splash-built.png)", - "Bash(nix-instantiate --parse hosts/live.nix)" + "Bash(nix-instantiate --parse hosts/live.nix)", + "Bash(nix-instantiate --parse modules/home/stylix.nix)" ] } } diff --git a/README.md b/README.md index 96eaf83..e3654ca 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,16 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. (needs a full grub theme dir), and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) +- **fastfetch branding:** legacy ships an ASCII-art logo + (`core/branding/logo.txt` on the legacy branch) intended as the fastfetch + image. Vendor it, resize to a sensible terminal width, and color it from + the palette (accent), then wire a `fastfetch` config that uses it. (No + fastfetch config in v1 yet.) +- **Nomarchy logo font in Waybar:** legacy's `core/branding/Nomarchy.ttf` + is a glyph font whose logo codepoint was used as the Waybar main-menu + button (e.g. the summer-night bar). Vendor the font, install it, and set + the `custom/nomarchy` button's `format` to that glyph (currently `󱄅`, a + Nerd Font icon) so the button shows the real Nomarchy mark. - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now persistent (timeout 0) and replaced in place by "applied ✓" / failure via a synchronous tag, so a multi-minute switch never reads as a failed @@ -402,11 +412,11 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. Waybar honours → all 10 rendered as phantoms. Dropped the block from `themes/summer-{day,night}/waybar.jsonc`; the other themes use the generated `waybar.nix`, which never had it, so they already match. -- **GTK/Qt ignore the theme's light/dark mode:** `stylix.nix` sets - `polarity` from `theme.mode`, but GTK4/libadwaita apps (and Qt via the - portal) decide dark vs light from the `org.freedesktop.appearance - color-scheme` / gsettings `org.gnome.desktop.interface color-scheme` key, - which Stylix doesn't write — so a light theme can still render apps dark - (and vice-versa). Fix: set `dconf` `color-scheme` = - `prefer-dark`/`prefer-light` from `t.mode` (and confirm the - xdg-desktop-portal exposes it), so libadwaita/Qt follow the palette. +- ✓ **GTK/Qt ignore the theme's light/dark mode:** Stylix set `polarity` + but not the `org.freedesktop.appearance color-scheme` that GTK4/libadwaita + and Qt6 read via the portal, so a light theme could still render apps + dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface + color-scheme` = `prefer-light`/`prefer-dark` from `t.mode` + (xdg-desktop-portal-gtk already ships, and `programs.dconf` is on + system-side), so libadwaita/Qt follow the palette. Needs a live session + to confirm the portal picks it up for all apps. diff --git a/modules/home/stylix.nix b/modules/home/stylix.nix index e9be5c1..ec04d83 100644 --- a/modules/home/stylix.nix +++ b/modules/home/stylix.nix @@ -86,5 +86,14 @@ in sizes.terminal = t.fonts.size; }; }; + + # GTK4/libadwaita and Qt6 apps decide dark vs light from the XDG + # portal's color-scheme (org.freedesktop.appearance), which + # xdg-desktop-portal-gtk sources from this gsettings key. Stylix sets + # the GTK theme and `polarity` but not this, so a light theme still + # rendered libadwaita/Qt apps dark (and vice-versa). Drive it from the + # palette mode. (programs.dconf.enable is already on system-side.) + dconf.settings."org/gnome/desktop/interface".color-scheme = + if t.mode == "light" then "prefer-light" else "prefer-dark"; }; } From 69f7ae90519894bf96e52a62bdceafea51efed8a Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 22:07:28 +0100 Subject: [PATCH 36/51] feat(branding): fastfetch logo + Nomarchy.ttf glyph in the Waybar button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two branding follow-ups, using the vendored legacy assets: - fastfetch (modules/home/fastfetch.nix, nomarchy.fastfetch.enable): the vector logo recolored to the palette accent and rendered to compact block-art via chafa at build time (so it tracks the theme), fronting a curated module list. Replaces the oversized legacy ASCII. - Waybar logo glyph: vendor Nomarchy.ttf (modules/nixos/branding/), install it via fonts.packages, and switch the summer-day/night menu buttons to its U+F000 mark with `font-family: Nomarchy` pinned in CSS (Nerd Fonts also occupy U+F000, so the pin is required). Glyph stored as the  JSON escape for robustness. Also log a roadmap item for a quality-of-life alias collection. Verified: font family resolves as "Nomarchy" and the ttf maps U+F000; both summer jsonc parse; chafa logo builds headless; nix flake check passes. Co-Authored-By: Claude Opus 4.8 --- .claude/settings.local.json | 3 +- README.md | 24 +++++++------ modules/home/default.nix | 1 + modules/home/fastfetch.nix | 50 ++++++++++++++++++++++++++++ modules/home/options.nix | 1 + modules/nixos/branding/Nomarchy.ttf | Bin 0 -> 984 bytes modules/nixos/default.nix | 10 ++++++ themes/summer-day/waybar.css | 4 +++ themes/summer-day/waybar.jsonc | 2 +- themes/summer-night/waybar.css | 3 ++ themes/summer-night/waybar.jsonc | 2 +- 11 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 modules/home/fastfetch.nix create mode 100644 modules/nixos/branding/Nomarchy.ttf diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f78f76e..354cf9b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -277,7 +277,8 @@ "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png/* preview-splash-built.png)", "Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png preview-splash-built.png)", "Bash(nix-instantiate --parse hosts/live.nix)", - "Bash(nix-instantiate --parse modules/home/stylix.nix)" + "Bash(nix-instantiate --parse modules/home/stylix.nix)", + "Bash(nix-shell -p fontconfig --run \"fc-scan --format '%{family}\\\\n' modules/nixos/branding/Nomarchy.ttf\")" ] } } diff --git a/README.md b/README.md index e3654ca..4c50913 100644 --- a/README.md +++ b/README.md @@ -325,16 +325,20 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. (needs a full grub theme dir), and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) -- **fastfetch branding:** legacy ships an ASCII-art logo - (`core/branding/logo.txt` on the legacy branch) intended as the fastfetch - image. Vendor it, resize to a sensible terminal width, and color it from - the palette (accent), then wire a `fastfetch` config that uses it. (No - fastfetch config in v1 yet.) -- **Nomarchy logo font in Waybar:** legacy's `core/branding/Nomarchy.ttf` - is a glyph font whose logo codepoint was used as the Waybar main-menu - button (e.g. the summer-night bar). Vendor the font, install it, and set - the `custom/nomarchy` button's `format` to that glyph (currently `󱄅`, a - Nerd Font icon) so the button shows the real Nomarchy mark. +- ✓ **fastfetch branding:** `modules/home/fastfetch.nix` + (`nomarchy.fastfetch.enable`) — the vendored vector logo, recolored to + the palette accent and rendered to compact block-art via chafa at build + time (tracks the theme), fronting a curated module list. Replaces the + oversized legacy ASCII with a themed, sized logo. +- ✓ **Nomarchy logo font in Waybar:** vendored `Nomarchy.ttf` + (`modules/nixos/branding/`), installed via `fonts.packages`, and the + summer-day/night menu buttons now use its `U+F000` glyph with + `font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy + U+F000, so the pin is required). The other themes have no logo button. +- **Quality-of-life command aliases:** assemble a curated collection of + shell aliases/abbreviations for common operations (git, nix, navigation, + the nomarchy helpers, …), themed into the zsh shell experience + (`modules/home/shell.nix`). Decide scope and which to ship on by default. - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now persistent (timeout 0) and replaced in place by "applied ✓" / failure via a synchronous tag, so a multi-minute switch never reads as a failed diff --git a/modules/home/default.nix b/modules/home/default.nix index 4539e79..5b22305 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -19,6 +19,7 @@ ./osd.nix # swayosd volume/brightness OSD, themed ./shell.nix # zsh + starship + bat/eza/zoxide, themed ./keys.nix # gpg-agent (fronts SSH) + pinentry-qt + ./fastfetch.nix # system info with the themed Nomarchy logo ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/fastfetch.nix b/modules/home/fastfetch.nix new file mode 100644 index 0000000..6d2422e --- /dev/null +++ b/modules/home/fastfetch.nix @@ -0,0 +1,50 @@ +# fastfetch — system summary fronted by the Nomarchy monogram. The logo is +# rendered from the vendored vector mark (modules/nixos/branding/logo.svg) +# via chafa at build time, recolored to the palette accent — so it's a +# compact, themed block-art logo that tracks the theme on each +# home-manager switch (rather than the oversized legacy ASCII). +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + c = cfg.theme.colors; + + logo = pkgs.runCommand "nomarchy-fastfetch-logo" + { nativeBuildInputs = [ pkgs.imagemagick pkgs.librsvg pkgs.chafa ]; } '' + rsvg-convert -w 220 ${../nixos/branding/logo.svg} > logo.png + magick logo.png -fill "${c.accent}" -colorize 100 logo-c.png + chafa --format symbols --symbols block --size 20x10 --colors full --polite on logo-c.png > $out + ''; +in +{ + config = lib.mkIf cfg.fastfetch.enable { + programs.fastfetch = { + enable = true; + settings = { + logo = { + type = "file-raw"; + source = "${logo}"; + padding = { top = 1; left = 2; right = 3; }; + }; + display.separator = " "; + modules = [ + "break" + { type = "os"; key = "distro"; keyColor = "blue"; } + { type = "kernel"; key = "kernel"; keyColor = "blue"; } + { type = "uptime"; key = "uptime"; keyColor = "blue"; } + { type = "packages"; key = "pkgs"; keyColor = "blue"; } + { type = "wm"; key = "wm"; keyColor = "blue"; } + { type = "shell"; key = "shell"; keyColor = "blue"; } + { type = "terminal"; key = "term"; keyColor = "blue"; } + "break" + { type = "cpu"; key = "cpu"; keyColor = "magenta"; } + { type = "gpu"; key = "gpu"; keyColor = "magenta"; } + { type = "memory"; key = "memory"; keyColor = "magenta"; } + { type = "disk"; key = "disk"; keyColor = "magenta"; } + "break" + { type = "colors"; symbol = "circle"; } + ]; + }; + }; + }; +} diff --git a/modules/home/options.nix b/modules/home/options.nix index 234cb09..0a328b7 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -70,6 +70,7 @@ 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; }; diff --git a/modules/nixos/branding/Nomarchy.ttf b/modules/nixos/branding/Nomarchy.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b09cd9fc9f90c535259d76c1bc3641d5918fc73b GIT binary patch literal 984 zcmah{O=uHA6#iysx9KLSNiR&HH9{AOJ*g2NK!K zGnq57qtO*WoM-lW-nOcpWc&x?H;k_ot$LL{4#=t*IHRh@c+I%)E)szdP0HFI`7b1imp2N7Krv8g?HcIe%X&gaXH zw-XBoW_TF`$$;VsJg6!lWm7zbL2WO!FN~k~UTk4Zm_1C+RyJh9xuG-G(r=Zy$Cy#pm7Jd+nqMuTI_*kF@pW?%nkK zhF0#5*)^2uRcbm)NR(ftj4T<7;FkMjTaDX+0a3R2w`KbWwd~3G@Z19!t-%=kr z3p~HjO;+PL9bgSdc$a=&GCvz&op0rFfDOK{NPzpusmvB?^+l(W%8h2(P(TfJEW$yB zO*xDrJLT9`EnjL;mIE6W8F`e@nl2QV%2o~2Y${?2WmWnc`G5J;{%(H^p@^RSsr=t2 IAo$l~d)W`1P5=M^ literal 0 HcmV?d00001 diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index ebc9231..03ccd68 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -12,6 +12,14 @@ let # Reused by the greeter banner and MOTD so the brand name lives in one # place (system.nixos.distroName, set below). distroName = config.system.nixos.distroName; + + # The Nomarchy glyph font: a single-glyph face (the logo at U+F000) used + # by the Waybar menu button in the summer themes. Pinned via + # `font-family: Nomarchy` in their CSS, since Nerd Fonts also occupy + # U+F000 (a glass icon) and would otherwise win. + nomarchyLogoFont = pkgs.runCommandLocal "nomarchy-logo-font" { } '' + install -Dm444 ${./branding/Nomarchy.ttf} $out/share/fonts/truetype/Nomarchy.ttf + ''; in { imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ]; @@ -157,6 +165,8 @@ in # The legacy-ported waybar identities (summer-day/night) use # FontAwesome codepoints in their layouts and list it in css. font-awesome + # The Nomarchy logo glyph (U+F000) for the summer menu button. + nomarchyLogoFont ]; fontconfig.defaultFonts = { monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ]; diff --git a/themes/summer-day/waybar.css b/themes/summer-day/waybar.css index aa1a4e8..85617ca 100755 --- a/themes/summer-day/waybar.css +++ b/themes/summer-day/waybar.css @@ -90,6 +90,10 @@ window#waybar { margin-left: 15px; padding-left: 20px; padding-right: 21px; + /* The Nomarchy mark lives at U+F000 in the dedicated "Nomarchy" font; + pin the family or the Nerd Font's glass icon at U+F000 wins. */ + font-family: Nomarchy; + font-size: 18px; } #custom-powermenu { diff --git a/themes/summer-day/waybar.jsonc b/themes/summer-day/waybar.jsonc index 8fb252e..7909d2c 100644 --- a/themes/summer-day/waybar.jsonc +++ b/themes/summer-day/waybar.jsonc @@ -23,7 +23,7 @@ "custom/launcher": { "interval": "once", - "format": "󰣇", + "format": "\uf000", "on-click": "nomarchy-menu", "tooltip-format": "Nomarchy menu" }, diff --git a/themes/summer-night/waybar.css b/themes/summer-night/waybar.css index a52bda3..da7538c 100644 --- a/themes/summer-night/waybar.css +++ b/themes/summer-night/waybar.css @@ -99,6 +99,9 @@ window#waybar { margin-left: 15px; padding-left: 20px; padding-right: 21px; + /* The Nomarchy mark lives at U+F000 in the dedicated "Nomarchy" font; + pin the family or the Nerd Font's glass icon at U+F000 wins. */ + font-family: Nomarchy; font-size: 20px; } diff --git a/themes/summer-night/waybar.jsonc b/themes/summer-night/waybar.jsonc index ffa36e2..875a96d 100644 --- a/themes/summer-night/waybar.jsonc +++ b/themes/summer-night/waybar.jsonc @@ -23,7 +23,7 @@ "custom/nomarchy": { "interval": "once", - "format": "󱄅", + "format": "\uf000", "on-click": "nomarchy-menu", "tooltip-format": "Nomarchy menu" }, From 2c8e0b47f9d3efebfbbe1711c4afeada54ada873 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:11:20 +0100 Subject: [PATCH 37/51] fix(keyboard): carry the configured layout to the LUKS prompt Setting only services.xserver.xkb.layout moves the graphical session but leaves console.useXkbConfig unset, so the virtual-console keymap -- and the systemd initrd the LUKS passphrase prompt runs in -- fall back to US (seen on a Latitude 5410: X11 Layout gb, VC Keymap unset, LUKS prompt US). Default console.useXkbConfig + console.earlySetup + boot.initrd.systemd.enable distro-wide, so xkb.layout alone reaches the console and the encrypted-disk prompt. Moved the latter two out of plymouth.nix -- they no longer hinge on the splash being enabled. All mkDefault: an explicit console.keyMap or a scripted initrd still wins. Co-Authored-By: Claude Opus 4.8 --- modules/nixos/default.nix | 19 +++++++++++++++++++ modules/nixos/plymouth.nix | 9 +++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 03ccd68..0433e72 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -50,6 +50,25 @@ in # `nixpkgs.config = lib.mkForce { allowUnfree = false; }`. nixpkgs.config.allowUnfree = true; + # ── One keyboard layout everywhere, incl. the LUKS prompt ──────── + # services.xserver.xkb.layout is the single source of truth for the + # layout (the installer writes it; downstream sets it in system.nix). + # These bridge that one knob through to the encrypted-disk prompt — + # without them, setting only xkb.layout moves the graphical session + # but leaves the console keymap unset, so the initrd LUKS passphrase + # prompt falls back to US: + # useXkbConfig the virtual-console keymap follows xkb (TTYs) + # earlySetup bakes that keymap into the initrd + # systemd initrd loads it before cryptsetup asks for the passphrase + # (the legacy scripted initrd cannot; also what + # Plymouth needs — defaulted here so it no longer + # hinges on the splash being enabled). + # All mkDefault, so an explicit console.keyMap or a scripted initrd + # still wins. + console.useXkbConfig = lib.mkDefault true; + console.earlySetup = lib.mkDefault true; + boot.initrd.systemd.enable = lib.mkDefault true; + # ── Wayland session: Hyprland ──────────────────────────────────── # Installs the binary, registers the session, wires up # xdg-desktop-portal-hyprland. Configuration is Home Manager's job. diff --git a/modules/nixos/plymouth.nix b/modules/nixos/plymouth.nix index b57c262..91fb4b1 100644 --- a/modules/nixos/plymouth.nix +++ b/modules/nixos/plymouth.nix @@ -80,11 +80,12 @@ let in { config = lib.mkIf cfg.plymouth.enable { - # Plymouth wants the systemd initrd (also what applies the keyboard - # layout to the LUKS prompt) and a quiet console around it. - boot.initrd.systemd.enable = lib.mkDefault true; + # The systemd initrd (boot.initrd.systemd.enable) and console.earlySetup + # that Plymouth needs are now distro-wide defaults in ./default.nix + # (they also carry the keyboard layout to the LUKS prompt), so the + # splash no longer has to turn them on. Just keep the console quiet + # around the splash. boot.initrd.verbose = lib.mkDefault false; - console.earlySetup = lib.mkDefault true; boot.consoleLogLevel = lib.mkDefault 0; boot.plymouth = { From 66f58fa228926173ef800baa657e5cf192f7a7ff Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:11:20 +0100 Subject: [PATCH 38/51] fix(hyprland): mic mute via wpctl -- swayosd 0.2.1 input-mute is a no-op swayosd-client --input-volume mute-toggle draws the OSD but never flips the default source's mute (its output-mute path works; input does not). Confirmed on hardware: wpctl set-mute @DEFAULT_SOURCE@ toggle flips the state, swayosd does not. Do the real toggle with wpctl and keep an OSD via swayosd's --custom-icon/--custom-message. Co-Authored-By: Claude Opus 4.8 --- modules/home/hyprland.nix | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index ca36114..e40c8e8 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -1,13 +1,31 @@ # Hyprland — fully driven by config.nomarchy.theme (theme-state.json). # Gaps, borders and colors are baked from the JSON at eval time; theme # changes arrive via `home-manager switch`. -{ config, lib, ... }: +{ config, lib, pkgs, ... }: let t = config.nomarchy.theme; c = t.colors; inherit (config.nomarchy.lib) rgb rgba; + # swayosd 0.2.1's `--input-volume mute-toggle` draws the OSD but never + # flips the source mute (verified on hardware: wpctl toggles the state, + # swayosd's input-mute path is a no-op — unlike its working output-mute + # path). Do the real toggle with wpctl, then show an OSD reflecting the + # resulting state via swayosd's --custom-icon/--custom-message. + micMute = pkgs.writeShellScript "nomarchy-mic-mute" '' + ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_SOURCE@ toggle + if ${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SOURCE@ | grep -q MUTED; then + ${pkgs.swayosd}/bin/swayosd-client \ + --custom-icon microphone-sensitivity-muted-symbolic \ + --custom-message "Microphone muted" + else + ${pkgs.swayosd}/bin/swayosd-client \ + --custom-icon microphone-sensitivity-high-symbolic \ + --custom-message "Microphone unmuted" + fi + ''; + # SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated. workspaceBinds = builtins.concatLists (builtins.genList (i: @@ -150,7 +168,8 @@ in bindl = [ ", XF86AudioMute, exec, swayosd-client --output-volume mute-toggle" - ", XF86AudioMicMute, exec, swayosd-client --input-volume mute-toggle" + # swayosd's input mute-toggle is broken on 0.2.1 (see micMute above). + ", XF86AudioMicMute, exec, ${micMute}" ", XF86AudioPlay, exec, playerctl play-pause" ", XF86AudioPause, exec, playerctl play-pause" ", XF86AudioNext, exec, playerctl next" From d9301ad954a4dedecfc7d69e9d13430237729c2a Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:11:20 +0100 Subject: [PATCH 39/51] fix(yazi): previewer match field name -> url (yazi 26.x) yazi 26.x renamed the previewer/fetcher match key from name to url. The fetchers were updated but the markdown previewer still used name, so the whole [plugin] config failed to parse ("at least one of 'url' or 'mime' must be specified") and yazi fell back to its presets. Co-Authored-By: Claude Opus 4.8 --- modules/home/yazi.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix index d63c677..6ca8d7d 100644 --- a/modules/home/yazi.nix +++ b/modules/home/yazi.nix @@ -56,8 +56,12 @@ in # Route only audio + subtitles to mediainfo's text metadata; # images/videos keep yazi's native visual thumbnails. Markdown # renders through glow. + # Match field is `url` (a path glob) or `mime` — yazi 26.x renamed + # the old `name` to `url`, same as the fetchers above. Using `name` + # fails the parse ("at least one of 'url' or 'mime'") and yazi falls + # back to its presets. prepend_previewers = [ - { name = "*.md"; run = "glow"; } + { url = "*.md"; run = "glow"; } { mime = "audio/*"; run = "mediainfo"; } { mime = "application/subrip"; run = "mediainfo"; } ]; From 3f36183021f0a0612274112351284f2ee41260a8 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:11:20 +0100 Subject: [PATCH 40/51] feat(menu): Google web search; note calc rework in the roadmap The web module now opens google.com/search instead of DuckDuckGo. Also log a roadmap item to rework calc: qalc -t misparses common phrasings (15% of 200 -> rem(15, 1 B)) and the result only shows in the next menu's -mesg line -- move toward live results as you type. Co-Authored-By: Claude Opus 4.8 --- README.md | 11 ++++++++++- modules/home/rofi.nix | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c50913..dbe84a3 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `nomarchy-menu` dispatcher: root picker (no args) · `power` (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` - (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo). + (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (Google). SUPER+D is `rofi -show drun`. - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` (blueman-manager) · `capture` (grim/slurp submenu: region/full → @@ -294,6 +294,15 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 once mainline gained native Wayland, for its richer theming) +- **Calculator module rework:** the current `calc` flow + (`modules/home/rofi.nix`) is unsatisfying — you commit the expression + blind, then the result only appears in the *next* menu's `-mesg` line, + and `qalc -t` misparses common phrasings (e.g. `15% of 200` → + `rem(15, 1 B)` instead of `30`). Rework toward live results as you type + (rofi-calc-style: each keystroke re-evaluates and the answer is the top + entry, Enter copies), a more robust qalc invocation, and graceful + handling of parse errors. Decide whether to keep the hand-rolled dmenu + loop or adopt a dedicated calc mode/plugin. - **Theme parity with legacy:** summer-day/night now carry their legacy bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 826ee63..e3a222a 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -106,7 +106,7 @@ let web) q=$(rofi -dmenu -p search < /dev/null) || exit 0 [ -n "$q" ] || exit 0 - exec xdg-open "https://duckduckgo.com/?q=$(urlencode "$q")" ;; + exec xdg-open "https://www.google.com/search?q=$(urlencode "$q")" ;; network) # nmtui in a terminal (NetworkManager is the system network stack). From 7da6bb979ea0dca5a1d994314c7ecdfadab9e763 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:27:32 +0100 Subject: [PATCH 41/51] docs: expand roadmap (power, opt-in services, display/UX, control center) Captures a batch of new roadmap items from a planning pass: - repo sanitize/organize (incl. the now-redundant console.useXkbConfig / boot.initrd.systemd.enable the installer writes, post LUKS-keymap fix) - laptop power/battery management (PPD-vs-TLP, thermald, charge thresholds) - opt-in services & integrations (Nextcloud/Syncthing, LM Studio/Ollama, containers, Tailscale, Steam/OBS, printing, backups, Flatpak) - display/monitor management (hotplug profiles, dock story) - night light (hyprsunset), runtime keyboard-layout switching, swaync DND - snapshot browse/restore UX, update-awareness indicator - a "nomarchy" control center + first-boot welcome Co-Authored-By: Claude Opus 4.8 --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/README.md b/README.md index dbe84a3..f1fb9ac 100644 --- a/README.md +++ b/README.md @@ -389,6 +389,82 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. no SSH agent, so no socket contention); screen lock doesn't flush the cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI clients launched outside a shell also see the agent. +- **Sanitize & organize the repo:** a housekeeping pass for consistency + and clarity. Candidate targets (scope still to be defined): prune + now-redundant config (e.g. the installer still writes + `console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`, + which are distro-wide defaults as of the LUKS-keymap fix); reconcile the + README option tables with the actual `nomarchy.*` surface; audit + comments/docs for drift against the code; and re-check that every file + still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` + rule of thumb. +- **Laptop power / battery management:** today the distro does *no* active + power management — only `services.upower` is on, and that's just battery + *reporting* for the Waybar widget; CPU/thermal/charge behaviour is whatever + the kernel and the nixos-hardware profile default to. Add a real power + story behind a `nomarchy.system.power.*` surface, gated to laptops (the + installer already detects chassis): + - **profiles:** ship `power-profiles-daemon` by default (the upstream- + aligned choice — power-saver/balanced/performance via `powerprofilesctl`), + with a switcher in the rofi menu and a Waybar indicator. Offer **TLP** as + an opt-in for maximum battery life (note: mutually exclusive with PPD, and + more aggressive/opaque). **Decision to make:** PPD-default vs TLP-default. + - **thermal:** `thermald` on Intel (the Latitude is Intel) to avoid + throttling. + - **longevity:** battery charge thresholds (e.g. stop charging at ~80%) + where the hardware supports it. + - **cohere** with `modules/home/idle.nix` (idle lock / display-off / + suspend) and review logind lid handling so the power behaviours line up. +- **Opt-in services & integrations:** the counterpart to the opt-*out* + application suite above — heavier or more personal integrations shipped + **off by default**, each a `nomarchy.services..enable` toggle a + downstream flips on in one line. Keeps the base lean while making common + additions trivial. Candidates by area: + - **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`) + - **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional + GPU accel) — pairs with the menu's Ask-Claude philosophy + - **containers/VMs:** Docker/Podman; libvirt + virt-manager + - **networking:** Tailscale (`services.tailscale`); WireGuard + - **gaming/media:** Steam (`programs.steam`); OBS Studio + - **devices:** printing (CUPS + Avahi); KDE Connect / phone integration; + OpenRGB + - **backup:** restic/borg (`services.restic`) + - **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs + + Decisions: the curated set; whether system services and GUI apps share one + surface or split (`nomarchy.services.*` system-side vs home-side packages); + and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are + already covered (`allowUnfree = true`). +- **Display / monitor management:** today Hyprland just uses + `monitor = ,preferred,auto,1` (mkDefault). Add a real external-monitor/dock + story: declarative per-output config, hotplug profiles (kanshi-style, or + Hyprland's own monitor rules), and optionally a GUI arranger (nwg-displays + writes Hyprland monitor config). Decide declarative-only vs GUI, and the + option shape (`nomarchy.monitors` vs plain Hyprland `monitor` lists). +- **Night light / blue-light filter:** schedulable colour-temperature shift + via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) — + sunset/sunrise or a fixed schedule, with a menu + Waybar toggle. +- **Runtime keyboard-layout switching:** the session layout is a single + `nomarchy.keyboard.layout` today. Support a list of layouts with a toggle + bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar + indicator for the active layout. Natural follow-on to the LUKS-keymap work + — keep the system (console/initrd) and session layouts in sync. +- **Do-Not-Disturb:** a swaync DND toggle (`swaync-client -dn`) wired into + the menu and a Waybar indicator, to silence notifications for + presentations/focus. +- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline + snapshots (`nomarchy.system.snapper`); surface them from the desktop — a + rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from- + snapshot — so rollback isn't CLI-only. +- **Update awareness:** updates are manual today (`sys-update`/`home-update`); + add a Waybar indicator / notification when flake inputs are stale or a new + nixpkgs rev is available (optionally with a pending-change count). Augments, + never replaces, the explicit rebuild flow. +- **"nomarchy" control center:** a single TUI/GUI front-end over the common + toggles — theme, power profile, opt-in services, display, DND — built on + the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already + uses. Plus a first-boot welcome that lands new installs in a guided "pick + your theme / essentials" flow (ties into the branding work). ## Known issues & follow-ups - ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' From 58096c09ed2a75631688c57c543f0b16ece2c9b6 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:51:01 +0100 Subject: [PATCH 42/51] fix(hyprland): show the mic level in the mute OSD The wpctl-based mic toggle lost the volume bar the native swayosd input OSD used to draw -- it only showed an icon + text. swayosd 0.3.x exposes --custom-progress (0.0-1.0), so read the level back from wpctl after the toggle and render icon + bar (muted/active glyph via --custom-icon), matching the output-volume OSD. Parsing is pure bash builtins so the script needs no PATH beyond the two pinned tools. Co-Authored-By: Claude Opus 4.8 --- modules/home/hyprland.nix | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index e40c8e8..13260da 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -8,22 +8,23 @@ let c = t.colors; inherit (config.nomarchy.lib) rgb rgba; - # swayosd 0.2.1's `--input-volume mute-toggle` draws the OSD but never - # flips the source mute (verified on hardware: wpctl toggles the state, - # swayosd's input-mute path is a no-op — unlike its working output-mute - # path). Do the real toggle with wpctl, then show an OSD reflecting the - # resulting state via swayosd's --custom-icon/--custom-message. + # swayosd's `--input-volume mute-toggle` draws the OSD but never flips the + # source mute (verified on hardware: wpctl toggles the state, swayosd's + # input-mute path is a no-op — unlike its working output-mute path). Do the + # real toggle with wpctl, then re-render the same icon + volume-bar OSD the + # native input path would, reading the resulting state back from wpctl: + # --custom-progress is the mic level (0.0-1.0), --custom-icon the + # muted/active glyph. Pure bash builtins for parsing so the script needs no + # PATH beyond the two pinned tools. micMute = pkgs.writeShellScript "nomarchy-mic-mute" '' ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_SOURCE@ toggle - if ${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SOURCE@ | grep -q MUTED; then - ${pkgs.swayosd}/bin/swayosd-client \ - --custom-icon microphone-sensitivity-muted-symbolic \ - --custom-message "Microphone muted" - else - ${pkgs.swayosd}/bin/swayosd-client \ - --custom-icon microphone-sensitivity-high-symbolic \ - --custom-message "Microphone unmuted" - fi + out=$(${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SOURCE@) + set -- $out # "Volume: 0.85 [MUTED]" -> $2 is the level + case "$out" in + *MUTED*) icon=microphone-sensitivity-muted-symbolic ;; + *) icon=microphone-sensitivity-high-symbolic ;; + esac + ${pkgs.swayosd}/bin/swayosd-client --custom-icon "$icon" --custom-progress "$2" ''; # SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated. From e0182ae7ddf359215555476fcb607a297b561db1 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 12:51:01 +0100 Subject: [PATCH 43/51] docs: explain the sys-update -> home-update ordering home-update rebuilds the desktop against the current flake.lock and never touches it; only sys-update runs `nix flake update`. So pulling a distro change that affects the home layer requires sys-update first (updates the lock), then home-update -- the reverse order rebuilds against stale inputs and silently skips the new home-side changes (hit while testing the mic fix). Also note hyprctl reload after a home-side keybind change. Co-Authored-By: Claude Opus 4.8 --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index f1fb9ac..5782196 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,17 @@ sys-update # nix flake update + system rebuild (BTRFS snapshot first when ava home-update # home-manager switch (no flake update, no sudo) ``` +**Order matters when pulling distro updates.** `home-update` does *not* +touch the lock — it rebuilds the desktop against the **current** +`flake.lock`. A new Nomarchy revision (new keybinds, theming, modules) +arrives only when the lock is updated, which `sys-update` does +(`nix flake update`). So to pull an update that affects the desktop layer: +run `sys-update` **first** (updates the lock + rebuilds the system), **then** +`home-update` (re-applies the desktop against the new lock). Doing them in +the other order rebuilds the desktop against the *old* inputs and silently +skips the new home-side changes. After a home-side keybind/config change, +also `hyprctl reload` (or relogin) so the running session re-reads it. + Override anything via the `nomarchy.*` surface or plain NixOS/HM options: appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`, behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix` From 8853f6ae491558669c51fc12b8a76bf756b7528e Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 13:06:10 +0100 Subject: [PATCH 44/51] feat(idle): no double-unlock after an encrypted hibernate resume hypridle locks hyprlock before every sleep -- right for suspend, but redundant for hibernate: the LUKS passphrase entered at boot already gates the resume, so the user typed a password twice. Add a nomarchy-hibernate- unlock systemd unit (WantedBy hibernate.target, After systemd-hibernate.service => runs post-resume) that dismisses hyprlock, gated on the disk being LUKS- encrypted. Suspend (and the RAM-resume phase of suspend-then-hibernate) keep locking -- they have no passphrase gate; an unencrypted hibernate keeps its lock too. idle.nix gains a pointer comment; roadmap item marked done. Co-Authored-By: Claude Opus 4.8 --- README.md | 11 +++++++---- modules/home/idle.nix | 4 ++++ modules/nixos/default.nix | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5782196..d1ca9e3 100644 --- a/README.md +++ b/README.md @@ -386,11 +386,14 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. logo/lock/bullet → text, entry/progress-box → surface, progress-bar → accent. Reads on light and dark; follows the theme as of the last system 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` - also locks hyprlock, so the user types a password twice. Lock only on - suspend (not hibernate), or skip the hyprlock prompt when resuming from - a LUKS-encrypted hibernate — see `modules/home/idle.nix`. + also locked hyprlock, so the user typed a password twice. Fixed with a + `nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) + that dismisses hyprlock *after* a hibernate resume (`WantedBy + hibernate.target`, `After systemd-hibernate.service` → runs post-resume), + gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase + of suspend-then-hibernate) keep locking — they have no passphrase gate. - ✓ **Key agents & pinentry:** ships `modules/home/keys.nix` (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, diff --git a/modules/home/idle.nix b/modules/home/idle.nix index db4b173..eaec51b 100644 --- a/modules/home/idle.nix +++ b/modules/home/idle.nix @@ -54,6 +54,10 @@ in settings = { general = { lock_cmd = "pidof hyprlock || hyprlock"; + # Locks before every sleep. For suspend that's exactly right; on an + # encrypted hibernate the LUKS resume already gates the machine, so + # the system side dismisses this lock post-resume to avoid a double + # unlock — see nomarchy-hibernate-unlock in modules/nixos/default.nix. before_sleep_cmd = "loginctl lock-session"; after_sleep_cmd = "hyprctl dispatch dpms on"; }; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 0433e72..c24c95d 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -114,6 +114,26 @@ in services.upower.enable = lib.mkDefault true; networking.networkmanager.enable = lib.mkDefault true; + # No double-unlock on hibernate. hypridle locks hyprlock before every + # sleep (right for suspend), but a hibernate resume is already gated by + # the LUKS passphrase entered at boot — so the user would type a password + # twice. Once we're back from an *encrypted* hibernate, drop the now- + # redundant hyprlock. Ordered After the hibernate service, so it runs + # post-resume; pulled in only by hibernate.target (suspend, and the + # RAM-resume phase of suspend-then-hibernate, stay locked — they have no + # passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock. + systemd.services.nomarchy-hibernate-unlock = + lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) { + description = "Dismiss hyprlock after resuming from an encrypted hibernate"; + after = [ "systemd-hibernate.service" ]; + wantedBy = [ "hibernate.target" ]; + serviceConfig = { + Type = "oneshot"; + # `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure. + ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock"; + }; + }; + # zsh as the default login shell (the desktop's shell experience — # starship/bat/eza/zoxide — is configured home-side in shell.nix). # programs.zsh.enable wires /etc/zshrc, completion and /etc/shells; From 00f53114991bca1ab1cb218a7aa226023cf3ac57 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 17:35:42 +0100 Subject: [PATCH 45/51] feat(power): laptop power management (power-profiles-daemon + TLP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `nomarchy.system.power.*` surface (modules/nixos/power.nix), replacing the bare `services.upower` (Waybar reporting only) baseline: - power-profiles-daemon ships by default; switched through polkit (no root prompt). TLP is the opt-in `backend = "tlp"` for deeper battery tuning — the two are mutually exclusive (asserted). - thermald behind `power.thermal.enable` (Intel-only). - `power.batteryChargeLimit` (e.g. 80): a backend-independent sysfs oneshot writes charge_control_end_threshold; gated on `power.laptop`. Switcher + indicator live home-side and self-gate on a battery being present + powerprofilesctl running, so they hide on desktops and under TLP — no system->home wiring (like the Waybar battery widget): - rofi.nix: `nomarchy-menu power-profile` module + a picker entry shown only on laptops with PPD. - waybar.nix: first `custom/*` module — click cycles the profile. Installer writes `power.laptop = true` (battery probe) and `power.thermal.enable` (GenuineIntel) into the generated system.nix, and scaffolds the charge limit commented-out. Co-Authored-By: Claude Opus 4.8 --- README.md | 39 ++++++++------- modules/home/rofi.nix | 53 ++++++++++++++------ modules/home/waybar.nix | 41 ++++++++++++++-- modules/nixos/default.nix | 2 +- modules/nixos/options.nix | 53 ++++++++++++++++++++ modules/nixos/power.nix | 60 +++++++++++++++++++++++ pkgs/nomarchy-install/nomarchy-install.sh | 22 ++++++++- 7 files changed, 233 insertions(+), 37 deletions(-) create mode 100644 modules/nixos/power.nix diff --git a/README.md b/README.md index d1ca9e3..a639ef6 100644 --- a/README.md +++ b/README.md @@ -412,23 +412,28 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. comments/docs for drift against the code; and re-check that every file still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb. -- **Laptop power / battery management:** today the distro does *no* active - power management — only `services.upower` is on, and that's just battery - *reporting* for the Waybar widget; CPU/thermal/charge behaviour is whatever - the kernel and the nixos-hardware profile default to. Add a real power - story behind a `nomarchy.system.power.*` surface, gated to laptops (the - installer already detects chassis): - - **profiles:** ship `power-profiles-daemon` by default (the upstream- - aligned choice — power-saver/balanced/performance via `powerprofilesctl`), - with a switcher in the rofi menu and a Waybar indicator. Offer **TLP** as - an opt-in for maximum battery life (note: mutually exclusive with PPD, and - more aggressive/opaque). **Decision to make:** PPD-default vs TLP-default. - - **thermal:** `thermald` on Intel (the Latitude is Intel) to avoid - throttling. - - **longevity:** battery charge thresholds (e.g. stop charging at ~80%) - where the hardware supports it. - - **cohere** with `modules/home/idle.nix` (idle lock / display-off / - suspend) and review logind lid handling so the power behaviours line up. +- **Laptop power / battery management:** a real power story behind a + `nomarchy.system.power.*` surface (`modules/nixos/power.nix`), replacing the + old "only `services.upower` for Waybar reporting" baseline: + - ✓ **profiles:** `power-profiles-daemon` ships by default (the upstream- + aligned choice — power-saver/balanced/performance via `powerprofilesctl`, + switched through polkit so no root prompt). A `power-profile` menu module + (in the SUPER+M picker) and the first Waybar `custom/*` indicator (click to + cycle) both **self-gate** on a battery being present + PPD running, so they + hide on desktops and under TLP — no system→home wiring. **TLP** is the + opt-in (`backend = "tlp"`) for deeper battery tuning; the two are mutually + exclusive (asserted). + - ✓ **thermal:** `thermald` behind `power.thermal.enable`; the installer + turns it on for a `GenuineIntel` CPU. + - ✓ **longevity:** `power.batteryChargeLimit` (e.g. 80) — a backend- + independent sysfs oneshot writes `charge_control_end_threshold`. Off by + default; the installer scaffolds it commented-out on laptops. + - ✓ **installer:** writes `power.laptop = true` (battery probe) and + `power.thermal.enable` (Intel) into the generated `system.nix`. + - Remaining: **cohere** with `modules/home/idle.nix` (AC-vs-battery idle + timeouts / no auto-suspend on AC) and review logind lid handling, plus a + boot-only→event-driven charge-limit re-apply (udev) if a firmware resets + the threshold on unplug. - **Opt-in services & integrations:** the counterpart to the opt-*out* application suite above — heavier or more personal integrations shipped **off by default**, each a `nomarchy.services..enable` toggle a diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index e3a222a..2365afb 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -77,6 +77,20 @@ let *Shutdown) systemctl poweroff ;; esac ;; + power-profile) + # power-profiles-daemon profile switcher (the system side ships it + # by default on laptops — nomarchy.system.power). Switching goes + # through polkit, so no root prompt. + if ! command -v powerprofilesctl >/dev/null 2>&1; then + notify-send "Power profiles" "power-profiles-daemon isn't running on this machine." + exit 0 + fi + cur=$(powerprofilesctl get 2>/dev/null) + choice=$(powerprofilesctl list 2>/dev/null \ + | sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \ + | rofi -dmenu -p "profile (now: $cur)") || exit 0 + [ -n "$choice" ] && powerprofilesctl set "$choice" ;; + theme) choice=$(nomarchy-theme-sync list | rofi -dmenu -p theme) || exit 0 [ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;; @@ -148,20 +162,28 @@ let exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;; "") - choice=$(printf '%s\n' \ - "󰀻 Apps" \ - "󰏘 Theme" \ - "󰅌 Clipboard" \ - "󰃬 Calculator" \ - "󰈞 Files" \ - "󰖟 Web search" \ - "󰖩 Network" \ - "󰂯 Bluetooth" \ - "󰄄 Capture" \ - "󰋖 Keybindings" \ - "󰭹 Ask Claude" \ - "󰐥 Power" \ - | rofi -dmenu -p menu) || exit 0 + entries=( + "󰀻 Apps" + "󰏘 Theme" + "󰅌 Clipboard" + "󰃬 Calculator" + "󰈞 Files" + "󰖟 Web search" + "󰖩 Network" + "󰂯 Bluetooth" + "󰄄 Capture" + "󰋖 Keybindings" + "󰭹 Ask Claude" + ) + # Power-profile picker only when this is a laptop (battery present) + # running power-profiles-daemon — matches the Waybar indicator's + # self-gating; desktops and the TLP backend don't show it. + bats=(/sys/class/power_supply/BAT*) + if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then + entries+=("󰓅 Power profile") + fi + entries+=("󰐥 Power") + choice=$(printf '%s\n' "''${entries[@]}" | rofi -dmenu -p menu) || exit 0 case "$choice" in *Apps*) exec rofi -show drun ;; *Theme*) exec "$0" theme ;; @@ -174,11 +196,12 @@ let *Capture*) exec "$0" capture ;; *Keybindings*) exec "$0" keybinds ;; *Ask*) exec "$0" ask ;; + *"Power profile"*) exec "$0" power-profile ;; *Power*) exec "$0" power ;; esac ;; *) - echo "usage: nomarchy-menu [power|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2 + echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2 exit 64 ;; esac ''; diff --git a/modules/home/waybar.nix b/modules/home/waybar.nix index 00cf348..0cf1f59 100644 --- a/modules/home/waybar.nix +++ b/modules/home/waybar.nix @@ -7,11 +7,39 @@ # //waybar.css (and optionally waybar.jsonc) which # replace the generated style/layout entirely. Probed at eval time — # pure, it's flake source. -{ config, lib, ... }: +{ config, lib, pkgs, ... }: let t = config.nomarchy.theme; + # Power-profile indicator (power-profiles-daemon). Both scripts self- + # gate: they exit silently unless this is a laptop (a battery is + # present) running PPD, so the module hides itself on desktops and + # under the TLP backend — no system→home coupling. Speedometer glyphs + # (slow/medium/fast) map to power-saver/balanced/performance. + powerProfileStatus = pkgs.writeShellScript "nomarchy-powerprofile-status" '' + case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac + command -v powerprofilesctl >/dev/null 2>&1 || exit 0 + prof=$(powerprofilesctl get 2>/dev/null) || exit 0 + case "$prof" in + power-saver) icon="󰾆" ;; + performance) icon="󰓅" ;; + *) icon="󰾅" ;; + esac + printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof" + ''; + # Cycle to the next profile this machine actually offers (wraps around). + powerProfileCycle = pkgs.writeShellScript "nomarchy-powerprofile-cycle" '' + command -v powerprofilesctl >/dev/null 2>&1 || exit 0 + cur=$(powerprofilesctl get 2>/dev/null) || exit 0 + avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p') + next=$(printf '%s\n' "$avail" | awk -v cur="$cur" ' + { a[NR] = $0 } + END { for (i = 1; i <= NR; i++) if (a[i] == cur) { print a[i % NR + 1]; f = 1 } + if (!f && NR) print a[1] }') + [ -n "$next" ] && powerprofilesctl set "$next" + ''; + # Per-theme override probe. assetDir = config.nomarchy.themesDir + "/${t.slug}"; styleOverride = assetDir + "/waybar.css"; @@ -38,7 +66,7 @@ let modules-left = [ "hyprland/workspaces" "hyprland/window" ]; modules-center = [ "clock" ]; - modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "battery" ]; + modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" "battery" ]; "hyprland/workspaces" = { format = "{icon}"; @@ -83,6 +111,13 @@ let format-icons = [ "󰁺" "󰁼" "󰁾" "󰂀" "󰁹" ]; }; + "custom/powerprofile" = { + exec = "${powerProfileStatus}"; + return-type = "json"; + interval = 5; + on-click = "${powerProfileCycle}"; + }; + tray.spacing = 8; }; @@ -129,7 +164,7 @@ let font-weight: bold; } - #tray, #pulseaudio, #network, #cpu, #memory, #battery { + #tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #battery { color: @subtext; padding: 0 10px; } diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index c24c95d..c3fb98a 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -22,7 +22,7 @@ let ''; in { - imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ]; + imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ]; config = { # The safe half of distro branding: distroName flows into diff --git a/modules/nixos/options.nix b/modules/nixos/options.nix index 10634e8..9815bbf 100644 --- a/modules/nixos/options.nix +++ b/modules/nixos/options.nix @@ -51,5 +51,58 @@ `nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the root filesystem is BTRFS with a /.snapshots subvolume (the installer creates one)''; + + power = { + enable = lib.mkEnableOption '' + active power management. By default ships power-profiles-daemon — + the upstream-aligned power-saver/balanced/performance model, + switchable from the menu (nomarchy-menu power-profile) and shown + in Waybar on laptops — plus thermald and a battery charge limit + where applicable. Harmless on desktops (the profile daemon just + offers balanced/performance)'' // { default = true; }; + + backend = lib.mkOption { + type = lib.types.enum [ "ppd" "tlp" ]; + default = "ppd"; + description = '' + Which daemon governs CPU/platform power. "ppd" + (power-profiles-daemon) is the default: a clean three-profile + model with the menu switcher and Waybar indicator. "tlp" trades + that for TLP's deeper, more aggressive battery tuning, at the + cost of the profile switcher (TLP has no profile concept). The + two manage the same knobs and are mutually exclusive. + ''; + }; + + laptop = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Marks this machine as a laptop, gating battery-only features + (the charge limit below). The installer sets it to true when it + detects a battery at install time. + ''; + }; + + thermal.enable = lib.mkEnableOption '' + thermald, Intel's thermal-management daemon, to avoid aggressive + throttling under sustained load. Intel-only — the installer + enables it when it detects a GenuineIntel CPU. Harmless alongside + either backend''; + + batteryChargeLimit = lib.mkOption { + type = lib.types.nullOr (lib.types.ints.between 50 100); + default = null; + example = 80; + description = '' + Stop charging at this percentage to extend battery lifespan, + where the hardware exposes a charge threshold + (/sys/class/power_supply/BAT*/charge_control_end_threshold). + null leaves charging at the firmware default. Backend-independent + (a small systemd unit writes the sysfs knob at boot), so it + works under PPD too; needs nomarchy.system.power.laptop. + ''; + }; + }; }; } diff --git a/modules/nixos/power.nix b/modules/nixos/power.nix new file mode 100644 index 0000000..8e8ce22 --- /dev/null +++ b/modules/nixos/power.nix @@ -0,0 +1,60 @@ +# Active power management. One concern, one file: this is the system +# side — the power daemon (power-profiles-daemon by default, or TLP), +# thermald, and the battery charge limit. The profile *switcher* and the +# Waybar *indicator* live home-side (rofi.nix / waybar.nix); they self- +# gate on powerprofilesctl being present, so there's no system→home wiring +# to keep in sync (the same way the Waybar battery widget auto-hides on +# desktops). See the roadmap in README.md. +{ config, lib, ... }: + +let + cfg = config.nomarchy.system.power; + ppd = cfg.backend == "ppd"; + tlp = cfg.backend == "tlp"; + chargeLimit = cfg.laptop && cfg.batteryChargeLimit != null; +in +{ + config = lib.mkIf cfg.enable { + # PPD and TLP both drive CPU/platform power — running both fights. + # `backend` selects exactly one; this assertion only fires if a + # downstream force-enables the other service directly. + assertions = [{ + assertion = !(config.services.power-profiles-daemon.enable + && config.services.tlp.enable); + message = '' + nomarchy: power-profiles-daemon and TLP are mutually exclusive. + Pick one with nomarchy.system.power.backend ("ppd" or "tlp"). + ''; + }]; + + # Unprivileged profile switching (the menu/Waybar) goes through + # polkit, already enabled distro-wide in default.nix. + services.power-profiles-daemon.enable = lib.mkDefault ppd; + services.tlp.enable = lib.mkDefault tlp; + + # thermald is Intel-only, so off unless asked (the installer enables + # it on a GenuineIntel CPU). Sits happily next to either backend. + services.thermald.enable = lib.mkDefault cfg.thermal.enable; + + # Battery charge limit via sysfs. PPD can't cap charge at all, and + # TLP's own knob only applies under TLP — so a tiny oneshot writes + # the threshold directly, independent of the backend. Boot-time only: + # some firmwares reset the threshold on unplug; revisit with a udev + # hook if that bites. `-` paths that don't exist are skipped, so this + # is a clean no-op on hardware without the control. + systemd.services.nomarchy-battery-charge-limit = lib.mkIf chargeLimit { + description = "Cap battery charging at ${toString cfg.batteryChargeLimit}%"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + script = '' + for thresh in /sys/class/power_supply/BAT*/charge_control_end_threshold; do + [ -w "$thresh" ] && echo ${toString cfg.batteryChargeLimit} > "$thresh" + done + exit 0 + ''; + }; + }; +} diff --git a/pkgs/nomarchy-install/nomarchy-install.sh b/pkgs/nomarchy-install/nomarchy-install.sh index ee7ac37..2d108ea 100644 --- a/pkgs/nomarchy-install/nomarchy-install.sh +++ b/pkgs/nomarchy-install/nomarchy-install.sh @@ -408,6 +408,26 @@ NIX ) fi +# Laptop power: power-profiles-daemon ships by default; mark this a laptop +# so battery-only features apply, and enable thermald on Intel. Keyed off +# the same battery probe that chose the common-pc-laptop hardware profile. +POWER_CONFIG="" +if [[ " ${HW_PROFILES[*]:-} " == *" common-pc-laptop "* ]]; then + power_lines=" # Laptop power management (power-profiles-daemon + menu/Waybar + # switcher). Uncomment to stop charging at 80% to extend battery life. + nomarchy.system.power.laptop = true; + # nomarchy.system.power.batteryChargeLimit = 80;" + if grep -q GenuineIntel /proc/cpuinfo 2>/dev/null; then + power_lines+=" + nomarchy.system.power.thermal.enable = true; # thermald (Intel)" + fi + POWER_CONFIG=$(cat < "$FLAKE_DIR/system.nix" < "$FLAKE_DIR/system.nix" < Date: Sun, 14 Jun 2026 17:44:23 +0100 Subject: [PATCH 46/51] feat(system): enable fwupd by default; document power options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ship `services.fwupd.enable` distro-wide (mkDefault) for firmware updates via LVFS — the daemon + metadata-refresh timer only, no automatic flashing, so applying an update stays an explicit `fwupdmgr update`. A no-op where LVFS sees no devices; overridable natively. Sits with the other mkDefault desktop services rather than a nomarchy toggle (it's a single service, like upower). Also documents the `nomarchy.system.power.*` options in the README table (missed when the power module landed) and adds a short note on the fwupd default. Co-Authored-By: Claude Opus 4.8 --- README.md | 12 ++++++++++++ modules/nixos/default.nix | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/README.md b/README.md index a639ef6..716171e 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,18 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet | | `nomarchy.system.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | +| `nomarchy.system.power.enable` | `true` | Active power management (see below) | +| `nomarchy.system.power.backend` | `"ppd"` | `"ppd"` (power-profiles-daemon + menu/Waybar switcher) or `"tlp"` (deeper battery tuning, no switcher) — mutually exclusive | +| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present | +| `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU | +| `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` | + +Beyond the `nomarchy.*` surface, the system layer turns on the usual +desktop services with `lib.mkDefault` (override natively). One worth +calling out: **`services.fwupd.enable`** is on by default for firmware +updates via LVFS — it only refreshes metadata, never flashes on its own, +so run `fwupdmgr update` to apply. Disable with `services.fwupd.enable = +false;` on machines without real firmware (VMs/headless). ## 4. How theming works diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index c3fb98a..ac4ab14 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -112,6 +112,15 @@ in services.gnome.gnome-keyring.enable = lib.mkDefault true; services.dbus.enable = lib.mkDefault true; services.upower.enable = lib.mkDefault true; + + # Firmware updates via LVFS. Ships the daemon + its metadata-refresh + # timer only — it never flashes anything on its own; applying an update + # is an explicit `fwupdmgr update`. On by default as desktop security + # hygiene (BIOS/UEFI/Thunderbolt/SSD fixes land here); a clean no-op + # where LVFS sees no devices (VMs/headless). Disable natively with + # `services.fwupd.enable = false`. + services.fwupd.enable = lib.mkDefault true; + networking.networkmanager.enable = lib.mkDefault true; # No double-unlock on hibernate. hypridle locks hyprlock before every From 6a22eba0c426a99b8ab1d2de9d9bbd589998f91f Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 17:46:30 +0100 Subject: [PATCH 47/51] docs: add roadmap item for a full docs review & restructure A dedicated prose pass distinct from the repo-sanitize item: split the ~200-line roadmap out of the README, reconcile the option tables with the live nomarchy.* surface, and review docs/ + the downstream template for drift. Co-Authored-By: Claude Opus 4.8 --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 716171e..28fda59 100644 --- a/README.md +++ b/README.md @@ -424,6 +424,18 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. comments/docs for drift against the code; and re-check that every file still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb. +- **Full docs review & restructure:** a dedicated pass over all the prose, + not just the code-adjacent cleanup the repo-sanitize item covers. The + README has grown to ~540 lines with a ~200-line roadmap inline — the + biggest single move is likely **splitting the roadmap out** (e.g. + `ROADMAP.md` / `docs/`) so the README stays a focused "what it is / how + to install / how to override" entry point. Also: reconcile every option + table against the live `nomarchy.*` surface (snapper and others are + missing); a pass over `docs/OVERRIDES.md` + `docs/TESTING.md` and the + `templates/downstream/README.md` for drift; check the install/first-run + story reads cleanly end to end; and decide whether anything wants a real + docs site vs. staying Markdown-in-repo. Pairs with the first-boot welcome + and control-center items (shared "how do I…" surface). - **Laptop power / battery management:** a real power story behind a `nomarchy.system.power.*` surface (`modules/nixos/power.nix`), replacing the old "only `services.upower` for Waybar reporting" baseline: From 51c11e1df39396e214fb5c7b2c8c7b275f910f69 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 17:53:49 +0100 Subject: [PATCH 48/51] feat(idle): suspend on battery only, cohering with power management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hypridle's suspend listener now runs `nomarchy-on-ac || systemctl suspend`, so a plugged-in machine no longer suspends itself mid-idle (long builds, media, presentations survive). It also fires sooner — 15 min, vs the old fixed 30 — since it now only applies unplugged. Lock and screen-off are unchanged on both power sources, and logind's lid handling stays at its sensible defaults (suspend on lid close, ignore when docked). Closes the idle-cohesion follow-up under the laptop-power roadmap item. Co-Authored-By: Claude Opus 4.8 --- README.md | 13 +++++++++---- modules/home/idle.nix | 21 +++++++++++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 28fda59..d056327 100644 --- a/README.md +++ b/README.md @@ -454,10 +454,15 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. default; the installer scaffolds it commented-out on laptops. - ✓ **installer:** writes `power.laptop = true` (battery probe) and `power.thermal.enable` (Intel) into the generated `system.nix`. - - Remaining: **cohere** with `modules/home/idle.nix` (AC-vs-battery idle - timeouts / no auto-suspend on AC) and review logind lid handling, plus a - boot-only→event-driven charge-limit re-apply (udev) if a firmware resets - the threshold on unplug. + - ✓ **idle cohesion:** `modules/home/idle.nix` now suspends only on + battery (and sooner — 15 min vs the old fixed 30), gating the hypridle + suspend listener on a `nomarchy-on-ac` probe so a plugged-in machine + stays up mid-idle; lock/screen-off are unchanged on both sources. + logind's lid handling is left at its defaults (suspend on lid close, + ignore when docked) — an explicit "I'm done" that coheres with the + idle behaviour. + - Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a + firmware resets the threshold on unplug. - **Opt-in services & integrations:** the counterpart to the opt-*out* application suite above — heavier or more personal integrations shipped **off by default**, each a `nomarchy.services..enable` toggle a diff --git a/modules/home/idle.nix b/modules/home/idle.nix index eaec51b..62d7849 100644 --- a/modules/home/idle.nix +++ b/modules/home/idle.nix @@ -2,13 +2,23 @@ # theme-state.json. One concern, one file: hypridle drives WHEN (idle # lock, display off, suspend, lock-before-sleep), hyprlock is the # themed lock screen itself (also behind the power menu's Lock entry). -{ config, lib, ... }: +{ config, lib, pkgs, ... }: let cfg = config.nomarchy; t = cfg.theme; c = t.colors; inherit (config.nomarchy.lib) rgb; + + # Exits 0 when running on AC: a mains adapter reports `online` 1 (battery + # supplies have no `online` node, so they never match). Used as + # `${onAc} || ` so the action is skipped while plugged in. + onAc = pkgs.writeShellScript "nomarchy-on-ac" '' + for f in /sys/class/power_supply/*/online; do + [ -r "$f" ] && [ "$(cat "$f")" = "1" ] && exit 0 + done + exit 1 + ''; in { config = lib.mkIf cfg.idle.enable { @@ -62,13 +72,20 @@ in after_sleep_cmd = "hyprctl dispatch dpms on"; }; listener = [ + # Lock and screen-off are the same on either power source — + # they're about privacy and the panel, not battery. { timeout = 300; on-timeout = "loginctl lock-session"; } { timeout = 600; on-timeout = "hyprctl dispatch dpms off"; on-resume = "hyprctl dispatch dpms on"; } - { timeout = 1800; on-timeout = "systemctl suspend"; } + # Suspend only on battery, and sooner than the old fixed 30 min + # (it now only fires unplugged). Plugged in, the machine stays + # up — long builds, media, presentations aren't killed mid-idle. + # Closing the lid still suspends on AC (logind's default): that's + # an explicit "I'm done", distinct from sitting idle. + { timeout = 900; on-timeout = "${onAc} || systemctl suspend"; } ]; }; }; From db202f90e4e9844086802806a480dd1623b3eb32 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 18:09:11 +0100 Subject: [PATCH 49/51] feat(menu): adopt rofi plugins for calc, files, emoji MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-rolled calc/files modules with purpose-built rofi modi (via programs.rofi.plugins) and add an emoji picker — all themed through the same .rasi as every other modi: - calc -> rofi-calc: live results as you type via libqalculate directly, which also dodges the qalc CLI's "15% of 200" -> rem(15, 1 B) misparse. Enter copies; the menu persists to chain calculations. Drops the libqalculate (qalc CLI) dependency. - files -> rofi-file-browser-extended: live directory navigation + xdg-open, replacing the flat 50k-cap `fd` dump of $HOME. yazi (SUPER+E) stays the power browser; `fd` already ships via shell.nix. - emoji -> rofi-emoji (new module): glyph picker, copies through the plugin's Wayland clipboard adapter. Bound to SUPER+CTRL+E in keybinds.nix (so it lands in the SUPER+? cheatsheet too). rofi 2.0 rebuilds cleanly with all three plugin .so (ABI-matched, same nixpkgs). README roadmap updated: calc rework marked shipped, with a follow-up item for the script-based plugins (network/secrets/audio). Co-Authored-By: Claude Opus 4.8 --- README.md | 34 +++++++++++++++++++--------- modules/home/keybinds.nix | 3 ++- modules/home/rofi.nix | 47 ++++++++++++++++++++++++--------------- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d056327..4b3fe6a 100644 --- a/README.md +++ b/README.md @@ -295,8 +295,9 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `nomarchy-menu` dispatcher: root picker (no args) · `power` (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` - (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (Google). - SUPER+D is `rofi -show drun`. + (rofi-calc, live, SUPER+CTRL+C) · `files` (rofi-file-browser, live + navigation, SUPER+CTRL+F) · `emoji` (rofi-emoji, SUPER+CTRL+E) · + `web` (Google). SUPER+D is `rofi -show drun`. - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` (blueman-manager) · `capture` (grim/slurp submenu: region/full → clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the @@ -317,15 +318,26 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 once mainline gained native Wayland, for its richer theming) -- **Calculator module rework:** the current `calc` flow - (`modules/home/rofi.nix`) is unsatisfying — you commit the expression - blind, then the result only appears in the *next* menu's `-mesg` line, - and `qalc -t` misparses common phrasings (e.g. `15% of 200` → - `rem(15, 1 B)` instead of `30`). Rework toward live results as you type - (rofi-calc-style: each keystroke re-evaluates and the answer is the top - entry, Enter copies), a more robust qalc invocation, and graceful - handling of parse errors. Decide whether to keep the hand-rolled dmenu - loop or adopt a dedicated calc mode/plugin. +- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the + expression blind (result only in the *next* menu's `-mesg`) and `qalc -t` + misparsed common phrasings (`15% of 200` → `rem(15, 1 B)`, the natural- + language "of" tripping the CLI). Resolved by adopting purpose-built rofi + modi (`programs.rofi.plugins`), all themed through the same `.rasi`: + - **calc** → **rofi-calc**: live results as you type via libqalculate + directly (dodging the qalc-CLI "of" bug), Enter copies, menu persists + to chain calculations. + - **files** → **rofi-file-browser-extended**: live directory navigation + (Backspace to ascend, toggle hidden) opening with xdg-open, replacing + the flat `fd` dump of `$HOME`. yazi (SUPER+E) stays the power browser. + - **emoji** → **rofi-emoji** (new module, SUPER+CTRL+E): glyph picker, + copies via the plugin's Wayland clipboard adapter. +- **More menu modules from rofi tools:** the script-based counterparts + (run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate + replacement of an existing flow: **rofi-network-manager** (a keyboard + wifi/VPN picker vs today's `nmtui`-in-terminal `network`), **rofi-rbw / + rofi-pass** (a secrets module — Bitwarden via rbw, or `pass` — pairs with + `keys.nix`), and **rofi-pulse-select** (an audio sink/source switcher). + Decide per-module whether it earns replacing the current path. - **Theme parity with legacy:** summer-day/night now carry their legacy bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, diff --git a/modules/home/keybinds.nix b/modules/home/keybinds.nix index de2ade5..54b610a 100644 --- a/modules/home/keybinds.nix +++ b/modules/home/keybinds.nix @@ -41,7 +41,8 @@ { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } { mods = "$mod CTRL"; key = "C"; action = "exec, nomarchy-menu calc"; desc = "Calculator"; } { mods = "$mod CTRL"; key = "W"; action = "exec, nomarchy-menu web"; desc = "Web search"; } - { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File search"; } + { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File browser"; } + { mods = "$mod CTRL"; key = "E"; action = "exec, nomarchy-menu emoji"; desc = "Emoji picker"; } { mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (nmtui)"; } { mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; } { mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; } diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 2365afb..37d759c 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -100,22 +100,27 @@ let printf '%s' "$sel" | cliphist decode | wl-copy ;; calc) - expr=$(rofi -dmenu -p "= " < /dev/null) || exit 0 - [ -n "$expr" ] || exit 0 - result=$(qalc -t -- "$expr" 2>&1 | tail -n 1) - choice=$(printf '%s\n' "󰆏 Copy result" "󰃬 New calculation" \ - | rofi -dmenu -p "= " -mesg "$expr = $result") || exit 0 - case "$choice" in - *Copy*) printf '%s' "$result" | wl-copy ;; - *New*) exec "$0" calc ;; - esac ;; + # Live calculator (rofi-calc): re-evaluates every keystroke via + # libqalculate — which dodges the qalc CLI's "15% of 200" misparse — + # with the answer as the top entry. Enter copies the result; the + # menu stays open to chain calculations (Esc closes). + exec rofi -show calc -modi calc -no-show-match -no-sort \ + -calc-command "echo -n '{result}' | wl-copy" ;; files) - sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ - | head -n 50000 \ - | sed "s|^$HOME/||" \ - | rofi -dmenu -p file) || exit 0 - [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; + # Live file browser (rofi-file-browser-extended): navigate into + # directories, go up with Backspace, toggle hidden files with the + # rofi custom key, and open the selection with xdg-open. Starts in + # $HOME. The keyboard-heavy TUI flagship for real work is yazi + # (SUPER+E); this is the quick launcher path. + exec rofi -show file-browser-extended -modi file-browser-extended \ + -file-browser-dir "$HOME" -file-browser-cmd xdg-open ;; + + emoji) + # Emoji / symbol picker (rofi-emoji). The default action copies the + # glyph to the clipboard through the plugin's Wayland adapter + # (wl-copy); the alternate action types it into the focused window. + exec rofi -show emoji -modi emoji ;; web) q=$(rofi -dmenu -p search < /dev/null) || exit 0 @@ -168,6 +173,7 @@ let "󰅌 Clipboard" "󰃬 Calculator" "󰈞 Files" + "󰒨 Emoji" "󰖟 Web search" "󰖩 Network" "󰂯 Bluetooth" @@ -190,6 +196,7 @@ let *Clipboard*) exec "$0" clipboard ;; *Calc*) exec "$0" calc ;; *Files*) exec "$0" files ;; + *Emoji*) exec "$0" emoji ;; *Web*) exec "$0" web ;; *Network*) exec "$0" network ;; *Bluetooth*) exec "$0" bluetooth ;; @@ -201,7 +208,7 @@ let esac ;; *) - echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|web|network|bluetooth|capture|keybinds|ask]" >&2 + echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask]" >&2 exit 64 ;; esac ''; @@ -210,9 +217,7 @@ in config = lib.mkIf cfg.rofi.enable { home.packages = [ nomarchy-menu - pkgs.libqalculate # qalc, the calc module's engine - pkgs.fd # files module - pkgs.xdg-utils # xdg-open for files/web + pkgs.xdg-utils # xdg-open for the web + file-browser modules pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm) ]; @@ -221,6 +226,12 @@ in terminal = cfg.terminal; font = "${t.fonts.ui} 12"; + # Native rofi modi backing three menu modules: calc (live, via + # libqalculate — no qalc CLI needed), emoji (glyph picker, copies via + # its bundled Wayland adapter), file-browser (live navigation). All + # render through the generated/whole-swap theme like every other modi. + plugins = [ pkgs.rofi-calc pkgs.rofi-emoji pkgs.rofi-file-browser ]; + extraConfig = { modi = "drun,run"; show-icons = true; # app icons via the theme's icon set From 882ba6646b00e3c181bfec361fa33fcf942886fb Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 18:16:06 +0100 Subject: [PATCH 50/51] revert(menu): keep the hand-rolled fd files module, drop rofi-file-browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rofi-file-browser-extended navigate-a-tree model felt worse than flat fuzzy-find for a quick launcher (yazi already covers real browsing), so `files` goes back to `fd` → `rofi -dmenu` → xdg-open. calc (rofi-calc) and emoji (rofi-emoji) stay. Re-adds `fd` to the module's packages and notes the dropped plugin in the roadmap. Co-Authored-By: Claude Opus 4.8 --- README.md | 13 +++++++------ modules/home/keybinds.nix | 2 +- modules/home/rofi.nix | 25 ++++++++++++------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 4b3fe6a..3544db4 100644 --- a/README.md +++ b/README.md @@ -295,9 +295,9 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. `nomarchy-menu` dispatcher: root picker (no args) · `power` (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` - (rofi-calc, live, SUPER+CTRL+C) · `files` (rofi-file-browser, live - navigation, SUPER+CTRL+F) · `emoji` (rofi-emoji, SUPER+CTRL+E) · - `web` (Google). SUPER+D is `rofi -show drun`. + (rofi-calc, live, SUPER+CTRL+C) · `files` (fd → xdg-open, + SUPER+CTRL+F) · `emoji` (rofi-emoji, SUPER+CTRL+E) · `web` (Google). + SUPER+D is `rofi -show drun`. - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` (blueman-manager) · `capture` (grim/slurp submenu: region/full → clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the @@ -326,11 +326,12 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. - **calc** → **rofi-calc**: live results as you type via libqalculate directly (dodging the qalc-CLI "of" bug), Enter copies, menu persists to chain calculations. - - **files** → **rofi-file-browser-extended**: live directory navigation - (Backspace to ascend, toggle hidden) opening with xdg-open, replacing - the flat `fd` dump of `$HOME`. yazi (SUPER+E) stays the power browser. - **emoji** → **rofi-emoji** (new module, SUPER+CTRL+E): glyph picker, copies via the plugin's Wayland clipboard adapter. + - **files** stays the hand-rolled `fd` → `rofi -dmenu` → xdg-open fuzzy + search: rofi-file-browser-extended was tried and dropped — its + navigate-a-tree model felt worse than flat fuzzy-find for a quick + launcher, and yazi (SUPER+E) already covers real browsing. - **More menu modules from rofi tools:** the script-based counterparts (run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate replacement of an existing flow: **rofi-network-manager** (a keyboard diff --git a/modules/home/keybinds.nix b/modules/home/keybinds.nix index 54b610a..7b60b1c 100644 --- a/modules/home/keybinds.nix +++ b/modules/home/keybinds.nix @@ -41,7 +41,7 @@ { mods = "$mod CTRL"; key = "V"; action = "exec, nomarchy-menu clipboard"; desc = "Clipboard history"; } { mods = "$mod CTRL"; key = "C"; action = "exec, nomarchy-menu calc"; desc = "Calculator"; } { mods = "$mod CTRL"; key = "W"; action = "exec, nomarchy-menu web"; desc = "Web search"; } - { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File browser"; } + { mods = "$mod CTRL"; key = "F"; action = "exec, nomarchy-menu files"; desc = "File search"; } { mods = "$mod CTRL"; key = "E"; action = "exec, nomarchy-menu emoji"; desc = "Emoji picker"; } { mods = "$mod CTRL"; key = "N"; action = "exec, nomarchy-menu network"; desc = "Network (nmtui)"; } { mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; } diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 37d759c..39b5f0d 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -108,13 +108,11 @@ let -calc-command "echo -n '{result}' | wl-copy" ;; files) - # Live file browser (rofi-file-browser-extended): navigate into - # directories, go up with Backspace, toggle hidden files with the - # rofi custom key, and open the selection with xdg-open. Starts in - # $HOME. The keyboard-heavy TUI flagship for real work is yazi - # (SUPER+E); this is the quick launcher path. - exec rofi -show file-browser-extended -modi file-browser-extended \ - -file-browser-dir "$HOME" -file-browser-cmd xdg-open ;; + sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \ + | head -n 50000 \ + | sed "s|^$HOME/||" \ + | rofi -dmenu -p file) || exit 0 + [ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;; emoji) # Emoji / symbol picker (rofi-emoji). The default action copies the @@ -217,7 +215,8 @@ in config = lib.mkIf cfg.rofi.enable { home.packages = [ nomarchy-menu - pkgs.xdg-utils # xdg-open for the web + file-browser modules + pkgs.fd # files module (fuzzy search over $HOME) + pkgs.xdg-utils # xdg-open for the files + web modules pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm) ]; @@ -226,11 +225,11 @@ in terminal = cfg.terminal; font = "${t.fonts.ui} 12"; - # Native rofi modi backing three menu modules: calc (live, via - # libqalculate — no qalc CLI needed), emoji (glyph picker, copies via - # its bundled Wayland adapter), file-browser (live navigation). All - # render through the generated/whole-swap theme like every other modi. - plugins = [ pkgs.rofi-calc pkgs.rofi-emoji pkgs.rofi-file-browser ]; + # Native rofi modi backing two menu modules: calc (live, via + # libqalculate — no qalc CLI needed) and emoji (glyph picker, copies + # via its bundled Wayland adapter). Both render through the + # generated/whole-swap theme like every other modi. + plugins = [ pkgs.rofi-calc pkgs.rofi-emoji ]; extraConfig = { modi = "drun,run"; From afdb928ed7f1b917364656d50cef5abf86cc1e08 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sun, 14 Jun 2026 18:29:16 +0100 Subject: [PATCH 51/51] docs: split roadmap into docs/ROADMAP.md, reconcile README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roadmap + known-issues had grown to ~280 lines, nearly half the README. Moved both into docs/ROADMAP.md (forward-looking plans + a log of shipped fixes) and left a short pointer, so the README is back to a focused entry point (572 -> 302 lines). Also fixed drift surfaced in the pass: - layout tree: add modules/nixos/power.nix, modules/home/{keybinds,keys, fastfetch}.nix and docs/ROADMAP.md, which were missing. - option table: add keys.enable, fastfetch.enable, system.snapper.enable (default false — the installer opts in) and system.greeter.autoLogin. OVERRIDES.md, TESTING.md and the downstream template README were checked and are accurate (.#default / .#me names and the 21-preset count match), so they're left as-is. Docs stay Markdown-in-repo — no docs site. Co-Authored-By: Claude Opus 4.8 --- README.md | 302 +++--------------------------------------------- docs/ROADMAP.md | 291 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+), 286 deletions(-) create mode 100644 docs/ROADMAP.md diff --git a/README.md b/README.md index 3544db4..2848563 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts │ │ ├── options.nix # nomarchy.system.* toggles │ │ ├── plymouth.nix # boot splash, tinted from the JSON -│ │ └── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2 +│ │ ├── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2 +│ │ └── power.nix # power-profiles-daemon/TLP, thermald, charge limit │ └── home/ # the distro, user side │ ├── default.nix # entry point │ ├── options.nix # nomarchy.* option surface @@ -57,12 +58,15 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── waybar.nix │ ├── ghostty.nix │ ├── btop.nix -│ ├── rofi.nix # launcher + nomarchy-menu (power, clip) +│ ├── rofi.nix # launcher + nomarchy-menu (calc, emoji, clip…) +│ ├── keybinds.nix # single source: Hyprland binds + SUPER+? sheet │ ├── swaync.nix # notifications, same JSON │ ├── idle.nix # hyprlock + hypridle, same JSON │ ├── yazi.nix # flagship TUI file manager + plugins │ ├── osd.nix # swayosd volume/brightness OSD -│ └── shell.nix # zsh + starship + bat/eza/zoxide +│ ├── shell.nix # zsh + starship + bat/eza/zoxide +│ ├── keys.nix # gpg-agent fronting SSH, pinentry-qt +│ └── fastfetch.nix # themed Nomarchy logo + system info ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -72,6 +76,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. ├── templates/downstream/ # `nix flake init -t` starter for users ├── docs/TESTING.md # how to verify changes (incl. AI-agent rules) ├── docs/OVERRIDES.md # how downstream users override defaults +├── docs/ROADMAP.md # forward-looking plans + shipped-fixes log └── tools/ # maintainer-only ├── import-palettes.py # converts old-distro themes → JSON + assets ├── test-live-iso.sh # build the ISO + boot it in QEMU @@ -194,12 +199,16 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | +| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo | +| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt | | `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.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) | | `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet | +| `nomarchy.system.greeter.autoLogin` | `null` | Auto-login this user into Hyprland (installer sets it on LUKS machines) | | `nomarchy.system.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | +| `nomarchy.system.snapper.enable` | `false` | Hourly/daily BTRFS timeline snapshots + `nixos-rebuild-snap` (installer enables it; no-op unless root is BTRFS) | | `nomarchy.system.power.enable` | `true` | Active power management (see below) | | `nomarchy.system.power.backend` | `"ppd"` | `"ppd"` (power-profiles-daemon + menu/Waybar switcher) or `"tlp"` (deeper battery tuning, no switcher) — mutually exclusive | | `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present | @@ -286,287 +295,8 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot. - **Importing more old-distro palettes:** `tools/import-palettes.py themes/`. -## Roadmap -- **Menu system** (apps launcher + theme switching + system actions), built - on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from - theme-state.json like every other app, with rich per-element styling: - - ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from - the palette, `themes//rofi.rasi` whole-swap) and the - `nomarchy-menu` dispatcher: root picker (no args) · `power` - (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · - `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` - (rofi-calc, live, SUPER+CTRL+C) · `files` (fd → xdg-open, - SUPER+CTRL+F) · `emoji` (rofi-emoji, SUPER+CTRL+E) · `web` (Google). - SUPER+D is `rofi -show drun`. - - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` - (blueman-manager) · `capture` (grim/slurp submenu: region/full → - clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the - cheatsheet, see below) · `ask` (free-text → claude CLI in a terminal; - auths via OAuth, no API key; pulled fresh via `npx - @anthropic-ai/claude-code@latest` rather than the nixpkgs package, which - lags model releases — `nodejs` is bundled for npx; REPL stays open) - - ✓ keybindings cheatsheet: `modules/home/keybinds.nix` is the **single - source** for both the Hyprland binds and the SUPER+? rofi list, so they - can't drift; `nomarchy-menu keybinds` renders the padded two-column - sheet (generated/mouse binds carried in its `extra` rows) - - ✓ shipped binds: `SUPER+Space` → `rofi -show drun` (quick launch) · - `SUPER+M` → `nomarchy-menu` (main menu) · `SUPER+?` → the cheatsheet; - `SUPER+D` stays `-show drun` - - launcher icons: ✓ `show-icons` on, drawing from the theme's icon set - (Papirus, via the icon-themes work below) - - decision record: resolves the old Walker/Lua question — no GTK4 - launcher, no second theming pipeline; the dispatcher owns the menu - structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 - once mainline gained native Wayland, for its richer theming) -- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the - expression blind (result only in the *next* menu's `-mesg`) and `qalc -t` - misparsed common phrasings (`15% of 200` → `rem(15, 1 B)`, the natural- - language "of" tripping the CLI). Resolved by adopting purpose-built rofi - modi (`programs.rofi.plugins`), all themed through the same `.rasi`: - - **calc** → **rofi-calc**: live results as you type via libqalculate - directly (dodging the qalc-CLI "of" bug), Enter copies, menu persists - to chain calculations. - - **emoji** → **rofi-emoji** (new module, SUPER+CTRL+E): glyph picker, - copies via the plugin's Wayland clipboard adapter. - - **files** stays the hand-rolled `fd` → `rofi -dmenu` → xdg-open fuzzy - search: rofi-file-browser-extended was tried and dropped — its - navigate-a-tree model felt worse than flat fuzzy-find for a quick - launcher, and yazi (SUPER+E) already covers real browsing. -- **More menu modules from rofi tools:** the script-based counterparts - (run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate - replacement of an existing flow: **rofi-network-manager** (a keyboard - wifi/VPN picker vs today's `nmtui`-in-terminal `network`), **rofi-rbw / - rofi-pass** (a secrets module — Bitwarden via rbw, or `pass` — pairs with - `keys.nix`), and **rofi-pulse-select** (an audio sink/source switcher). - Decide per-module whether it earns replacing the current path. -- **Theme parity with legacy:** summer-day/night now carry their legacy - bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script - modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, - logo button opens nomarchy-menu); the other four identity themes are - palette recolors and already match. Remaining: a visual pass over all - six on the live ISO -- **Per-theme rofi identity:** the `themes//rofi.rasi` whole-swap - ships, and summer-day/night carry their legacy designs (inverted window, - green inputbar, yellow bottom-border). Remaining: author `.rasi` - identities for the other four ported themes if/when they want one (the - generated palette theme is the default and looks fine) -- **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB - re-copy on every state write is the main eval tax), then pre-built theme - variants if still needed -- Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since - v1: `nomarchy.system.plymouth.*`, background tinted from the state file) -- Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy - boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) -- launch-or-focus UX scripts (swayosd volume/brightness OSD ships since v1: - `nomarchy.osd.*`, media keys drive `swayosd-client`, themed from the JSON) -- **Distro branding, round 2:** `distroName = "Nomarchy"` ships - (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). - ✓ tuigreet greeting (`Welcome to `) and a branded `users.motd` - (doubling as a helper cheat sheet), both keyed off `distroName`. - ✓ `isoImage.splashImage` — the vendored vector logo - (`modules/nixos/branding/logo.svg`, from legacy) recolored to the palette - accent on the theme base, built at ISO-build time (`hosts/live.nix`). - Remaining: `isoImage.grubTheme` so UEFI boot matches the isolinux splash - (needs a full grub theme dir), and the `distroId` question (it changes - `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; - nixos-* CLI names stay regardless) -- ✓ **fastfetch branding:** `modules/home/fastfetch.nix` - (`nomarchy.fastfetch.enable`) — the vendored vector logo, recolored to - the palette accent and rendered to compact block-art via chafa at build - time (tracks the theme), fronting a curated module list. Replaces the - oversized legacy ASCII with a themed, sized logo. -- ✓ **Nomarchy logo font in Waybar:** vendored `Nomarchy.ttf` - (`modules/nixos/branding/`), installed via `fonts.packages`, and the - summer-day/night menu buttons now use its `U+F000` glyph with - `font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy - U+F000, so the pin is required). The other themes have no logo button. -- **Quality-of-life command aliases:** assemble a curated collection of - shell aliases/abbreviations for common operations (git, nix, navigation, - the nomarchy helpers, …), themed into the zsh shell experience - (`modules/home/shell.nix`). Decide scope and which to ship on by default. -- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now - persistent (timeout 0) and replaced in place by "applied ✓" / failure - via a synchronous tag, so a multi-minute switch never reads as a failed - selection. (An honest indeterminate indicator — HM gives no % — a - literal progress widget would be cosmetic; revisit only if wanted.) -- **Icon themes:** ✓ ships `papirus-icon-theme`; the resolved name lives on - `nomarchy.theme.iconTheme` (the JSON's optional `icons` field, else - Papirus-Dark/Light by `mode`) and feeds both Stylix's `gtk.iconTheme` - (Thunar/GTK apps) and rofi's `show-icons`. Remaining (optional): per-theme - `icons` overrides for the presets, or shipping more icon packs. -- **Nicer shell out of the box:** ✓ zsh is the default login shell, with a - starship prompt themed from the JSON, autosuggestions + syntax - highlighting, and modern-CLI ergonomics — `cat`→bat (theme "ansi", so it - tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`. - `nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd: - their flags differ enough to surprise; `rg`/`fd` ship as themselves.) -- **Default application suite:** install a complete-workstation set - (vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a - `nomarchy.apps.*` option surface so each is individually opt-out for - users who want a leaner machine. Watch closure size (texlive-full is - multi-GB — likely default-off or a lighter scheme). -- ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that - vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every - element from the palette at build time (flat fill, alpha kept): - logo/lock/bullet → text, entry/progress-box → surface, progress-bar → - accent. Reads on light and dark; follows the theme as of the last system - rebuild (like the background tint). -- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS - passphrase already gates the machine, but hypridle's `before_sleep_cmd` - also locked hyprlock, so the user typed a password twice. Fixed with a - `nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) - that dismisses hyprlock *after* a hibernate resume (`WantedBy - hibernate.target`, `After systemd-hibernate.service` → runs post-resume), - gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase - of suspend-then-hibernate) keep locking — they have no passphrase gate. -- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix` - (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with - `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, - Stylix-themed Qt) handles GPG and SSH passphrases alike. `SSH_AUTH_SOCK` - comes from the agent's zsh integration; cache TTLs (30 min / 2 h) govern - re-prompting. gnome-keyring stays the Secret Service (modern versions run - no SSH agent, so no socket contention); screen lock doesn't flush the - cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI - clients launched outside a shell also see the agent. -- **Sanitize & organize the repo:** a housekeeping pass for consistency - and clarity. Candidate targets (scope still to be defined): prune - now-redundant config (e.g. the installer still writes - `console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`, - which are distro-wide defaults as of the LUKS-keymap fix); reconcile the - README option tables with the actual `nomarchy.*` surface; audit - comments/docs for drift against the code; and re-check that every file - still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` - rule of thumb. -- **Full docs review & restructure:** a dedicated pass over all the prose, - not just the code-adjacent cleanup the repo-sanitize item covers. The - README has grown to ~540 lines with a ~200-line roadmap inline — the - biggest single move is likely **splitting the roadmap out** (e.g. - `ROADMAP.md` / `docs/`) so the README stays a focused "what it is / how - to install / how to override" entry point. Also: reconcile every option - table against the live `nomarchy.*` surface (snapper and others are - missing); a pass over `docs/OVERRIDES.md` + `docs/TESTING.md` and the - `templates/downstream/README.md` for drift; check the install/first-run - story reads cleanly end to end; and decide whether anything wants a real - docs site vs. staying Markdown-in-repo. Pairs with the first-boot welcome - and control-center items (shared "how do I…" surface). -- **Laptop power / battery management:** a real power story behind a - `nomarchy.system.power.*` surface (`modules/nixos/power.nix`), replacing the - old "only `services.upower` for Waybar reporting" baseline: - - ✓ **profiles:** `power-profiles-daemon` ships by default (the upstream- - aligned choice — power-saver/balanced/performance via `powerprofilesctl`, - switched through polkit so no root prompt). A `power-profile` menu module - (in the SUPER+M picker) and the first Waybar `custom/*` indicator (click to - cycle) both **self-gate** on a battery being present + PPD running, so they - hide on desktops and under TLP — no system→home wiring. **TLP** is the - opt-in (`backend = "tlp"`) for deeper battery tuning; the two are mutually - exclusive (asserted). - - ✓ **thermal:** `thermald` behind `power.thermal.enable`; the installer - turns it on for a `GenuineIntel` CPU. - - ✓ **longevity:** `power.batteryChargeLimit` (e.g. 80) — a backend- - independent sysfs oneshot writes `charge_control_end_threshold`. Off by - default; the installer scaffolds it commented-out on laptops. - - ✓ **installer:** writes `power.laptop = true` (battery probe) and - `power.thermal.enable` (Intel) into the generated `system.nix`. - - ✓ **idle cohesion:** `modules/home/idle.nix` now suspends only on - battery (and sooner — 15 min vs the old fixed 30), gating the hypridle - suspend listener on a `nomarchy-on-ac` probe so a plugged-in machine - stays up mid-idle; lock/screen-off are unchanged on both sources. - logind's lid handling is left at its defaults (suspend on lid close, - ignore when docked) — an explicit "I'm done" that coheres with the - idle behaviour. - - Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a - firmware resets the threshold on unplug. -- **Opt-in services & integrations:** the counterpart to the opt-*out* - application suite above — heavier or more personal integrations shipped - **off by default**, each a `nomarchy.services..enable` toggle a - downstream flips on in one line. Keeps the base lean while making common - additions trivial. Candidates by area: - - **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`) - - **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional - GPU accel) — pairs with the menu's Ask-Claude philosophy - - **containers/VMs:** Docker/Podman; libvirt + virt-manager - - **networking:** Tailscale (`services.tailscale`); WireGuard - - **gaming/media:** Steam (`programs.steam`); OBS Studio - - **devices:** printing (CUPS + Avahi); KDE Connect / phone integration; - OpenRGB - - **backup:** restic/borg (`services.restic`) - - **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs +## Roadmap & known issues - Decisions: the curated set; whether system services and GUI apps share one - surface or split (`nomarchy.services.*` system-side vs home-side packages); - and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are - already covered (`allowUnfree = true`). -- **Display / monitor management:** today Hyprland just uses - `monitor = ,preferred,auto,1` (mkDefault). Add a real external-monitor/dock - story: declarative per-output config, hotplug profiles (kanshi-style, or - Hyprland's own monitor rules), and optionally a GUI arranger (nwg-displays - writes Hyprland monitor config). Decide declarative-only vs GUI, and the - option shape (`nomarchy.monitors` vs plain Hyprland `monitor` lists). -- **Night light / blue-light filter:** schedulable colour-temperature shift - via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) — - sunset/sunrise or a fixed schedule, with a menu + Waybar toggle. -- **Runtime keyboard-layout switching:** the session layout is a single - `nomarchy.keyboard.layout` today. Support a list of layouts with a toggle - bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar - indicator for the active layout. Natural follow-on to the LUKS-keymap work - — keep the system (console/initrd) and session layouts in sync. -- **Do-Not-Disturb:** a swaync DND toggle (`swaync-client -dn`) wired into - the menu and a Waybar indicator, to silence notifications for - presentations/focus. -- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline - snapshots (`nomarchy.system.snapper`); surface them from the desktop — a - rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from- - snapshot — so rollback isn't CLI-only. -- **Update awareness:** updates are manual today (`sys-update`/`home-update`); - add a Waybar indicator / notification when flake inputs are stale or a new - nixpkgs rev is available (optionally with a pending-change count). Augments, - never replaces, the explicit rebuild flow. -- **"nomarchy" control center:** a single TUI/GUI front-end over the common - toggles — theme, power profile, opt-in services, display, DND — built on - the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already - uses. Plus a first-boot welcome that lands new installs in a guided "pick - your theme / essentials" flow (ties into the branding work). - -## Known issues & follow-ups -- ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' - `group` field required, so `[[plugin.prepend_fetchers]]` failed to parse - and yazi fell back to presets. Fixed by adding `group = "git"` to both - git fetcher entries in `modules/home/yazi.nix` (the git plugin's - `setup()` only renders the linemode; the fetcher still needs registering). -- ✓ **Starship prompt styling:** dropped the powerline look (the `[]` - separator glyph + `bg:` fills) for a flat prompt — bold-accent directory, - warn git branch, subtext status/duration, `❯` character, all plain - colored text (`modules/home/shell.nix`). Also wired in `cmd_duration`, - which was configured but never referenced in `format`. -- ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and - the three Thunar plugins to their new top-level names in - `modules/nixos/file-manager.nix`; the eval warnings are gone. -- ✓ **Rofi function keybindings:** direct `SUPER+CTRL+` binds jump - straight to each `nomarchy-menu` module — V clipboard · C calc · W web · - F files · N network · B bluetooth · S capture · A ask — added to - `modules/home/keybinds.nix`, so they also show in the SUPER+? cheatsheet. -- ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide - (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked - binaries run out of the box. -- ✓ **Hyprland border colors off for some themes:** the cause was v1 - forcing an `accent→accentAlt` gradient on every theme; legacy used a - *solid* border (accent for nord/retro-82/lumon, the text tone for - kanagawa/summer-day/summer-night). Added a palette-resolved `border` - field (`{active, inactive}`) to the theme schema (`theme.nix`), consumed - solid in `hyprland.nix`, and declared it in every preset so a switch - always replaces it. All six identity themes now match legacy exactly. -- ✓ **Waybar shows non-existent workspaces:** the v1 summer port "fixed" - legacy's deprecated `persistent_workspaces` (underscore — silently - ignored by current Waybar, so legacy only ever showed existing - workspaces) into the modern `persistent-workspaces` (hyphen), which - Waybar honours → all 10 rendered as phantoms. Dropped the block from - `themes/summer-{day,night}/waybar.jsonc`; the other themes use the - generated `waybar.nix`, which never had it, so they already match. -- ✓ **GTK/Qt ignore the theme's light/dark mode:** Stylix set `polarity` - but not the `org.freedesktop.appearance color-scheme` that GTK4/libadwaita - and Qt6 read via the portal, so a light theme could still render apps - dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface - color-scheme` = `prefer-light`/`prefer-dark` from `t.mode` - (xdg-desktop-portal-gtk already ships, and `programs.dconf` is on - system-side), so libadwaita/Qt follow the palette. Needs a live session - to confirm the portal picks it up for all apps. +See **[docs/ROADMAP.md](docs/ROADMAP.md)** — forward-looking plans plus the +log of shipped fixes. Kept out of the README so this stays a focused entry +point. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..ad78800 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,291 @@ +# Roadmap & changelog + +Forward-looking plans, plus a running log of shipped fixes (the +"Known issues & follow-ups" section). Split out of the README so that +stays a focused entry point — what Nomarchy is, how to install it, and +how to override it. Items marked ✓ are shipped. + +## Roadmap +- **Menu system** (apps launcher + theme switching + system actions), built + on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from + theme-state.json like every other app, with rich per-element styling: + - ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from + the palette, `themes//rofi.rasi` whole-swap) and the + `nomarchy-menu` dispatcher: root picker (no args) · `power` + (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · + `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` + (rofi-calc, live, SUPER+CTRL+C) · `files` (fd → xdg-open, + SUPER+CTRL+F) · `emoji` (rofi-emoji, SUPER+CTRL+E) · `web` (Google). + SUPER+D is `rofi -show drun`. + - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` + (blueman-manager) · `capture` (grim/slurp submenu: region/full → + clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the + cheatsheet, see below) · `ask` (free-text → claude CLI in a terminal; + auths via OAuth, no API key; pulled fresh via `npx + @anthropic-ai/claude-code@latest` rather than the nixpkgs package, which + lags model releases — `nodejs` is bundled for npx; REPL stays open) + - ✓ keybindings cheatsheet: `modules/home/keybinds.nix` is the **single + source** for both the Hyprland binds and the SUPER+? rofi list, so they + can't drift; `nomarchy-menu keybinds` renders the padded two-column + sheet (generated/mouse binds carried in its `extra` rows) + - ✓ shipped binds: `SUPER+Space` → `rofi -show drun` (quick launch) · + `SUPER+M` → `nomarchy-menu` (main menu) · `SUPER+?` → the cheatsheet; + `SUPER+D` stays `-show drun` + - launcher icons: ✓ `show-icons` on, drawing from the theme's icon set + (Papirus, via the icon-themes work below) + - decision record: resolves the old Walker/Lua question — no GTK4 + launcher, no second theming pipeline; the dispatcher owns the menu + structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 + once mainline gained native Wayland, for its richer theming) +- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the + expression blind (result only in the *next* menu's `-mesg`) and `qalc -t` + misparsed common phrasings (`15% of 200` → `rem(15, 1 B)`, the natural- + language "of" tripping the CLI). Resolved by adopting purpose-built rofi + modi (`programs.rofi.plugins`), all themed through the same `.rasi`: + - **calc** → **rofi-calc**: live results as you type via libqalculate + directly (dodging the qalc-CLI "of" bug), Enter copies, menu persists + to chain calculations. + - **emoji** → **rofi-emoji** (new module, SUPER+CTRL+E): glyph picker, + copies via the plugin's Wayland clipboard adapter. + - **files** stays the hand-rolled `fd` → `rofi -dmenu` → xdg-open fuzzy + search: rofi-file-browser-extended was tried and dropped — its + navigate-a-tree model felt worse than flat fuzzy-find for a quick + launcher, and yazi (SUPER+E) already covers real browsing. +- **More menu modules from rofi tools:** the script-based counterparts + (run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate + replacement of an existing flow: **rofi-network-manager** (a keyboard + wifi/VPN picker vs today's `nmtui`-in-terminal `network`), **rofi-rbw / + rofi-pass** (a secrets module — Bitwarden via rbw, or `pass` — pairs with + `keys.nix`), and **rofi-pulse-select** (an audio sink/source switcher). + Decide per-module whether it earns replacing the current path. +- **Theme parity with legacy:** summer-day/night now carry their legacy + bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script + modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, + logo button opens nomarchy-menu); the other four identity themes are + palette recolors and already match. Remaining: a visual pass over all + six on the live ISO +- **Per-theme rofi identity:** the `themes//rofi.rasi` whole-swap + ships, and summer-day/night carry their legacy designs (inverted window, + green inputbar, yellow bottom-border). Remaining: author `.rasi` + identities for the other four ported themes if/when they want one (the + generated palette theme is the default and looks fine) +- **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB + re-copy on every state write is the main eval tax), then pre-built theme + variants if still needed +- Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since + v1: `nomarchy.system.plymouth.*`, background tinted from the state file) +- Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy + boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) +- launch-or-focus UX scripts (swayosd volume/brightness OSD ships since v1: + `nomarchy.osd.*`, media keys drive `swayosd-client`, themed from the JSON) +- **Distro branding, round 2:** `distroName = "Nomarchy"` ships + (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). + ✓ tuigreet greeting (`Welcome to `) and a branded `users.motd` + (doubling as a helper cheat sheet), both keyed off `distroName`. + ✓ `isoImage.splashImage` — the vendored vector logo + (`modules/nixos/branding/logo.svg`, from legacy) recolored to the palette + accent on the theme base, built at ISO-build time (`hosts/live.nix`). + Remaining: `isoImage.grubTheme` so UEFI boot matches the isolinux splash + (needs a full grub theme dir), and the `distroId` question (it changes + `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; + nixos-* CLI names stay regardless) +- ✓ **fastfetch branding:** `modules/home/fastfetch.nix` + (`nomarchy.fastfetch.enable`) — the vendored vector logo, recolored to + the palette accent and rendered to compact block-art via chafa at build + time (tracks the theme), fronting a curated module list. Replaces the + oversized legacy ASCII with a themed, sized logo. +- ✓ **Nomarchy logo font in Waybar:** vendored `Nomarchy.ttf` + (`modules/nixos/branding/`), installed via `fonts.packages`, and the + summer-day/night menu buttons now use its `U+F000` glyph with + `font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy + U+F000, so the pin is required). The other themes have no logo button. +- **Quality-of-life command aliases:** assemble a curated collection of + shell aliases/abbreviations for common operations (git, nix, navigation, + the nomarchy helpers, …), themed into the zsh shell experience + (`modules/home/shell.nix`). Decide scope and which to ship on by default. +- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now + persistent (timeout 0) and replaced in place by "applied ✓" / failure + via a synchronous tag, so a multi-minute switch never reads as a failed + selection. (An honest indeterminate indicator — HM gives no % — a + literal progress widget would be cosmetic; revisit only if wanted.) +- **Icon themes:** ✓ ships `papirus-icon-theme`; the resolved name lives on + `nomarchy.theme.iconTheme` (the JSON's optional `icons` field, else + Papirus-Dark/Light by `mode`) and feeds both Stylix's `gtk.iconTheme` + (Thunar/GTK apps) and rofi's `show-icons`. Remaining (optional): per-theme + `icons` overrides for the presets, or shipping more icon packs. +- **Nicer shell out of the box:** ✓ zsh is the default login shell, with a + starship prompt themed from the JSON, autosuggestions + syntax + highlighting, and modern-CLI ergonomics — `cat`→bat (theme "ansi", so it + tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`. + `nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd: + their flags differ enough to surprise; `rg`/`fd` ship as themselves.) +- **Default application suite:** install a complete-workstation set + (vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a + `nomarchy.apps.*` option surface so each is individually opt-out for + users who want a leaner machine. Watch closure size (texlive-full is + multi-GB — likely default-off or a lighter scheme). +- ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that + vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every + element from the palette at build time (flat fill, alpha kept): + logo/lock/bullet → text, entry/progress-box → surface, progress-bar → + accent. Reads on light and dark; follows the theme as of the last system + rebuild (like the background tint). +- ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS + passphrase already gates the machine, but hypridle's `before_sleep_cmd` + also locked hyprlock, so the user typed a password twice. Fixed with a + `nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) + that dismisses hyprlock *after* a hibernate resume (`WantedBy + hibernate.target`, `After systemd-hibernate.service` → runs post-resume), + gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase + of suspend-then-hibernate) keep locking — they have no passphrase gate. +- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix` + (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with + `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, + Stylix-themed Qt) handles GPG and SSH passphrases alike. `SSH_AUTH_SOCK` + comes from the agent's zsh integration; cache TTLs (30 min / 2 h) govern + re-prompting. gnome-keyring stays the Secret Service (modern versions run + no SSH agent, so no socket contention); screen lock doesn't flush the + cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI + clients launched outside a shell also see the agent. +- **Sanitize & organize the repo:** a housekeeping pass for consistency + and clarity. Candidate targets (scope still to be defined): prune + now-redundant config (e.g. the installer still writes + `console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`, + which are distro-wide defaults as of the LUKS-keymap fix); reconcile the + README option tables with the actual `nomarchy.*` surface; audit + comments/docs for drift against the code; and re-check that every file + still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` + rule of thumb. +- **Full docs review & restructure:** a dedicated pass over all the prose, + not just the code-adjacent cleanup the repo-sanitize item covers. The + README has grown to ~540 lines with a ~200-line roadmap inline — the + biggest single move is likely **splitting the roadmap out** (e.g. + `ROADMAP.md` / `docs/`) so the README stays a focused "what it is / how + to install / how to override" entry point. Also: reconcile every option + table against the live `nomarchy.*` surface (snapper and others are + missing); a pass over `docs/OVERRIDES.md` + `docs/TESTING.md` and the + `templates/downstream/README.md` for drift; check the install/first-run + story reads cleanly end to end; and decide whether anything wants a real + docs site vs. staying Markdown-in-repo. Pairs with the first-boot welcome + and control-center items (shared "how do I…" surface). +- **Laptop power / battery management:** a real power story behind a + `nomarchy.system.power.*` surface (`modules/nixos/power.nix`), replacing the + old "only `services.upower` for Waybar reporting" baseline: + - ✓ **profiles:** `power-profiles-daemon` ships by default (the upstream- + aligned choice — power-saver/balanced/performance via `powerprofilesctl`, + switched through polkit so no root prompt). A `power-profile` menu module + (in the SUPER+M picker) and the first Waybar `custom/*` indicator (click to + cycle) both **self-gate** on a battery being present + PPD running, so they + hide on desktops and under TLP — no system→home wiring. **TLP** is the + opt-in (`backend = "tlp"`) for deeper battery tuning; the two are mutually + exclusive (asserted). + - ✓ **thermal:** `thermald` behind `power.thermal.enable`; the installer + turns it on for a `GenuineIntel` CPU. + - ✓ **longevity:** `power.batteryChargeLimit` (e.g. 80) — a backend- + independent sysfs oneshot writes `charge_control_end_threshold`. Off by + default; the installer scaffolds it commented-out on laptops. + - ✓ **installer:** writes `power.laptop = true` (battery probe) and + `power.thermal.enable` (Intel) into the generated `system.nix`. + - ✓ **idle cohesion:** `modules/home/idle.nix` now suspends only on + battery (and sooner — 15 min vs the old fixed 30), gating the hypridle + suspend listener on a `nomarchy-on-ac` probe so a plugged-in machine + stays up mid-idle; lock/screen-off are unchanged on both sources. + logind's lid handling is left at its defaults (suspend on lid close, + ignore when docked) — an explicit "I'm done" that coheres with the + idle behaviour. + - Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a + firmware resets the threshold on unplug. +- **Opt-in services & integrations:** the counterpart to the opt-*out* + application suite above — heavier or more personal integrations shipped + **off by default**, each a `nomarchy.services..enable` toggle a + downstream flips on in one line. Keeps the base lean while making common + additions trivial. Candidates by area: + - **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`) + - **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional + GPU accel) — pairs with the menu's Ask-Claude philosophy + - **containers/VMs:** Docker/Podman; libvirt + virt-manager + - **networking:** Tailscale (`services.tailscale`); WireGuard + - **gaming/media:** Steam (`programs.steam`); OBS Studio + - **devices:** printing (CUPS + Avahi); KDE Connect / phone integration; + OpenRGB + - **backup:** restic/borg (`services.restic`) + - **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs + + Decisions: the curated set; whether system services and GUI apps share one + surface or split (`nomarchy.services.*` system-side vs home-side packages); + and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are + already covered (`allowUnfree = true`). +- **Display / monitor management:** today Hyprland just uses + `monitor = ,preferred,auto,1` (mkDefault). Add a real external-monitor/dock + story: declarative per-output config, hotplug profiles (kanshi-style, or + Hyprland's own monitor rules), and optionally a GUI arranger (nwg-displays + writes Hyprland monitor config). Decide declarative-only vs GUI, and the + option shape (`nomarchy.monitors` vs plain Hyprland `monitor` lists). +- **Night light / blue-light filter:** schedulable colour-temperature shift + via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) — + sunset/sunrise or a fixed schedule, with a menu + Waybar toggle. +- **Runtime keyboard-layout switching:** the session layout is a single + `nomarchy.keyboard.layout` today. Support a list of layouts with a toggle + bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar + indicator for the active layout. Natural follow-on to the LUKS-keymap work + — keep the system (console/initrd) and session layouts in sync. +- **Do-Not-Disturb:** a swaync DND toggle (`swaync-client -dn`) wired into + the menu and a Waybar indicator, to silence notifications for + presentations/focus. +- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline + snapshots (`nomarchy.system.snapper`); surface them from the desktop — a + rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from- + snapshot — so rollback isn't CLI-only. +- **Update awareness:** updates are manual today (`sys-update`/`home-update`); + add a Waybar indicator / notification when flake inputs are stale or a new + nixpkgs rev is available (optionally with a pending-change count). Augments, + never replaces, the explicit rebuild flow. +- **"nomarchy" control center:** a single TUI/GUI front-end over the common + toggles — theme, power profile, opt-in services, display, DND — built on + the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already + uses. Plus a first-boot welcome that lands new installs in a guided "pick + your theme / essentials" flow (ties into the branding work). + +## Known issues & follow-ups +- ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' + `group` field required, so `[[plugin.prepend_fetchers]]` failed to parse + and yazi fell back to presets. Fixed by adding `group = "git"` to both + git fetcher entries in `modules/home/yazi.nix` (the git plugin's + `setup()` only renders the linemode; the fetcher still needs registering). +- ✓ **Starship prompt styling:** dropped the powerline look (the `[]` + separator glyph + `bg:` fills) for a flat prompt — bold-accent directory, + warn git branch, subtext status/duration, `❯` character, all plain + colored text (`modules/home/shell.nix`). Also wired in `cmd_duration`, + which was configured but never referenced in `format`. +- ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and + the three Thunar plugins to their new top-level names in + `modules/nixos/file-manager.nix`; the eval warnings are gone. +- ✓ **Rofi function keybindings:** direct `SUPER+CTRL+` binds jump + straight to each `nomarchy-menu` module — V clipboard · C calc · W web · + F files · N network · B bluetooth · S capture · A ask — added to + `modules/home/keybinds.nix`, so they also show in the SUPER+? cheatsheet. +- ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide + (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked + binaries run out of the box. +- ✓ **Hyprland border colors off for some themes:** the cause was v1 + forcing an `accent→accentAlt` gradient on every theme; legacy used a + *solid* border (accent for nord/retro-82/lumon, the text tone for + kanagawa/summer-day/summer-night). Added a palette-resolved `border` + field (`{active, inactive}`) to the theme schema (`theme.nix`), consumed + solid in `hyprland.nix`, and declared it in every preset so a switch + always replaces it. All six identity themes now match legacy exactly. +- ✓ **Waybar shows non-existent workspaces:** the v1 summer port "fixed" + legacy's deprecated `persistent_workspaces` (underscore — silently + ignored by current Waybar, so legacy only ever showed existing + workspaces) into the modern `persistent-workspaces` (hyphen), which + Waybar honours → all 10 rendered as phantoms. Dropped the block from + `themes/summer-{day,night}/waybar.jsonc`; the other themes use the + generated `waybar.nix`, which never had it, so they already match. +- ✓ **GTK/Qt ignore the theme's light/dark mode:** Stylix set `polarity` + but not the `org.freedesktop.appearance color-scheme` that GTK4/libadwaita + and Qt6 read via the portal, so a light theme could still render apps + dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface + color-scheme` = `prefer-light`/`prefer-dark` from `t.mode` + (xdg-desktop-portal-gtk already ships, and `programs.dconf` is on + system-side), so libadwaita/Qt follow the palette. Needs a live session + to confirm the portal picks it up for all apps.