Compare commits
84 Commits
77a0e70146
...
v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82659566ac | ||
|
|
994025f0c8 | ||
|
|
b0b2a4d732 | ||
|
|
964a5c98b7 | ||
|
|
c45fb80b79 | ||
|
|
0c668c2eb1 | ||
|
|
1f4bd3d5fa | ||
|
|
017bb1aa41 | ||
|
|
b856bb93e8 | ||
|
|
e0ee5b9e7e | ||
|
|
f68b245d92 | ||
|
|
fb748c217c | ||
|
|
8d3911de1a | ||
|
|
6ad80c045e | ||
|
|
54c6e100fb | ||
|
|
eb76bd61b4 | ||
|
|
71f37bfc30 | ||
|
|
69f959365e | ||
|
|
aef45b3c8d | ||
|
|
0897391cfb | ||
|
|
5c83b52727 | ||
|
|
0751102915 | ||
|
|
5c59da73e7 | ||
|
|
42801548b7 | ||
|
|
1b0eeeaf6c | ||
| a606c0d498 | |||
| cd495d1720 | |||
| dbcb8efd1e | |||
| 07e467027b | |||
| 9a5473e179 | |||
| db6a0ba455 | |||
| 8c47716f9a | |||
| f60f3655f9 | |||
| afdb928ed7 | |||
| 882ba6646b | |||
| db202f90e4 | |||
| 51c11e1df3 | |||
| 6a22eba0c4 | |||
| c47eb5f688 | |||
| 00f5311499 | |||
|
|
8853f6ae49 | ||
|
|
e0182ae7dd | ||
|
|
58096c09ed | ||
|
|
7da6bb979e | ||
|
|
3f36183021 | ||
|
|
d9301ad954 | ||
|
|
66f58fa228 | ||
|
|
2c8e0b47f9 | ||
|
|
69f7ae9051 | ||
|
|
4017b6cff6 | ||
|
|
cde279af0b | ||
|
|
6513908067 | ||
|
|
fdb0685eec | ||
|
|
e411d35544 | ||
|
|
3757e7d66f | ||
|
|
05d995f8e0 | ||
|
|
f6c4a2a591 | ||
|
|
7c34eed2ca | ||
|
|
d252f01f17 | ||
|
|
28a2f5d5ae | ||
|
|
f06d7f361c | ||
|
|
58d79aad17 | ||
|
|
60138e20d0 | ||
|
|
df7c0ef7ae | ||
|
|
f97054c804 | ||
|
|
520c878bb3 | ||
|
|
084b2a0500 | ||
|
|
20df0d8a60 | ||
|
|
abfe872109 | ||
|
|
961314ea86 | ||
|
|
76582c1ee9 | ||
|
|
d06373b155 | ||
|
|
bd42462291 | ||
|
|
583708d78c | ||
|
|
6763bef73e | ||
|
|
465f013155 | ||
|
|
eda8461304 | ||
|
|
1ba1eeed64 | ||
|
|
c1781f45ca | ||
|
|
ed7be0206d | ||
|
|
009bdbad8f | ||
|
|
a33127cd3c | ||
|
|
dccceb4c48 | ||
| bc5faec6b8 |
3
.gitignore
vendored
@@ -8,3 +8,6 @@ result-*
|
|||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Claude Code machine-local settings (permissions etc.)
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
150
README.md
@@ -45,7 +45,10 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
|
|||||||
├── modules/
|
├── modules/
|
||||||
│ ├── nixos/ # the distro, system side
|
│ ├── nixos/ # the distro, system side
|
||||||
│ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts
|
│ │ ├── 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
|
||||||
|
│ │ └── power.nix # power-profiles-daemon/TLP, thermald, charge limit
|
||||||
│ └── home/ # the distro, user side
|
│ └── home/ # the distro, user side
|
||||||
│ ├── default.nix # entry point
|
│ ├── default.nix # entry point
|
||||||
│ ├── options.nix # nomarchy.* option surface
|
│ ├── options.nix # nomarchy.* option surface
|
||||||
@@ -54,7 +57,16 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
|
|||||||
│ ├── hyprland.nix # all JSON-driven
|
│ ├── hyprland.nix # all JSON-driven
|
||||||
│ ├── waybar.nix
|
│ ├── waybar.nix
|
||||||
│ ├── ghostty.nix
|
│ ├── ghostty.nix
|
||||||
│ └── btop.nix
|
│ ├── btop.nix
|
||||||
|
│ ├── 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
|
||||||
|
│ ├── keys.nix # gpg-agent fronting SSH, pinentry-qt
|
||||||
|
│ └── fastfetch.nix # themed Nomarchy logo + system info
|
||||||
├── hosts/
|
├── hosts/
|
||||||
│ ├── default/ # reference machine (thin: boot, user, hostname)
|
│ ├── default/ # reference machine (thin: boot, user, hostname)
|
||||||
│ └── live.nix # bootable live ISO (try the distro, no install)
|
│ └── live.nix # bootable live ISO (try the distro, no install)
|
||||||
@@ -63,6 +75,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
|
|||||||
│ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
|
│ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
|
||||||
├── templates/downstream/ # `nix flake init -t` starter for users
|
├── templates/downstream/ # `nix flake init -t` starter for users
|
||||||
├── docs/TESTING.md # how to verify changes (incl. AI-agent rules)
|
├── 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
|
└── tools/ # maintainer-only
|
||||||
├── import-palettes.py # converts old-distro themes → JSON + assets
|
├── import-palettes.py # converts old-distro themes → JSON + assets
|
||||||
├── test-live-iso.sh # build the ISO + boot it in QEMU
|
├── test-live-iso.sh # build the ISO + boot it in QEMU
|
||||||
@@ -144,22 +158,91 @@ sudo nixos-rebuild switch --flake .#default # system: rare
|
|||||||
home-manager switch --flake .#me # desktop: every theme change, no sudo
|
home-manager switch --flake .#me # desktop: every theme change, no sudo
|
||||||
```
|
```
|
||||||
|
|
||||||
Override anything with plain NixOS/HM options (the distro uses `mkDefault`
|
Day-to-day you'll use the shipped shortcuts instead:
|
||||||
throughout) or the `nomarchy.*` surface:
|
|
||||||
|
```sh
|
||||||
|
sys-update # nix flake update + system rebuild (BTRFS snapshot first when available)
|
||||||
|
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`
|
||||||
|
assignment wins, and bind/exec-once lists concatenate. Full guide with
|
||||||
|
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||||||
|
|
||||||
| Option | Default | Purpose |
|
| Option | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
|
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
|
||||||
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
|
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
|
||||||
|
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
|
||||||
|
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
||||||
|
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
|
||||||
|
| `nomarchy.keyboard.layouts` | `[]` | Extra candidate layouts; when set, a watcher prompts (rofi) for a layout on a newly-connected keyboard and remembers it per-device |
|
||||||
|
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day |
|
||||||
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
||||||
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
||||||
|
| `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 |
|
||||||
|
| `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.ghostty.enable` | `true` | Nomarchy's Ghostty |
|
||||||
| `nomarchy.btop.enable` | `true` | btop with per-theme colors |
|
| `nomarchy.btop.enable` | `true` | btop with per-theme colors |
|
||||||
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
|
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
|
||||||
|
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
|
||||||
|
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
|
||||||
|
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
|
||||||
|
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
|
||||||
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
||||||
|
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
||||||
|
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
||||||
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
| `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.audio.enable` | `true` | Pipewire stack |
|
||||||
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
|
| `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 |
|
||||||
|
| `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` |
|
||||||
|
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN (then `sudo tailscale up`) |
|
||||||
|
| `nomarchy.services.syncthing.enable` | `false` | Opt-in: Syncthing file sync as the login user (GUI at `127.0.0.1:8384`) |
|
||||||
|
| `nomarchy.services.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
|
||||||
|
| `nomarchy.services.flatpak.enable` | `false` | Opt-in: Flatpak + the Flathub remote |
|
||||||
|
| `nomarchy.services.pika.enable` | `false` | Opt-in: Pika Backup (GUI Borg backups) |
|
||||||
|
| `nomarchy.services.steam.enable` | `false` | Opt-in: Steam via `programs.steam` (32-bit libs, controller udev, Remote-Play ports) |
|
||||||
|
| `nomarchy.services.libvirt.enable` | `false` | Opt-in: libvirt/KVM + virt-manager GUI (login user added to `libvirtd`) |
|
||||||
|
| `nomarchy.services.obs.enable` | `false` | Opt-in: OBS Studio + a v4l2loopback virtual camera (selectable as a webcam in Zoom/Teams) |
|
||||||
|
| `nomarchy.services.docker.enable` | `false` | Opt-in: rootful Docker (user in `docker` group); don't enable alongside podman |
|
||||||
|
| `nomarchy.services.kdeconnect.enable` | `false` | Opt-in: KDE Connect phone integration (opens its firewall ports) |
|
||||||
|
| `nomarchy.services.gamemode.enable` | `false` | Opt-in: Feral GameMode performance daemon (`gamemoderun`) |
|
||||||
|
| `nomarchy.services.adb.enable` | `false` | Opt-in: Android adb/fastboot tools (systemd handles device udev rules) |
|
||||||
|
| `nomarchy.services.wireshark.enable` | `false` | Opt-in: Wireshark Qt GUI (user in `wireshark` group, capture without root) |
|
||||||
|
| `nomarchy.services.ollama.enable` | `false` | Opt-in: Ollama local LLM runtime on `127.0.0.1:11434` (CPU; GPU via `services.ollama.acceleration`) |
|
||||||
|
| `nomarchy.services.printing.enable` | `false` | Opt-in: CUPS + Avahi/mDNS network printer discovery |
|
||||||
|
| `nomarchy.services.openrgb.enable` | `false` | Opt-in: OpenRGB daemon for RGB peripheral/motherboard lighting |
|
||||||
|
| `nomarchy.services.restic.enable` | `false` | Opt-in: scheduled daily restic backup (set `.repository` + `.passwordFile`; 7/4/6 retention; list/restore via the `restic-nomarchy` wrapper) |
|
||||||
|
|
||||||
|
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
|
## 4. How theming works
|
||||||
|
|
||||||
@@ -199,6 +282,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) |
|
| `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.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) |
|
| `waybar.jsonc` | whole-swap for the bar *layout* (must be plain JSON) |
|
||||||
|
| `rofi.rasi` | **whole-swap**: replaces the generated launcher/menu theme entirely |
|
||||||
|
|
||||||
Six ported themes ship a `waybar.css` identity (catppuccin, lumon, nord,
|
Six ported themes ship a `waybar.css` identity (catppuccin, lumon, nord,
|
||||||
retro-82, summer-day, summer-night). Custom user themes can live in
|
retro-82, summer-day, summer-night). Custom user themes can live in
|
||||||
@@ -214,11 +298,41 @@ 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 next # cycle wallpapers — instant, no rebuild
|
||||||
nomarchy-theme-sync bg auto # back to the theme's default wallpaper
|
nomarchy-theme-sync bg auto # back to the theme's default wallpaper
|
||||||
nomarchy-theme-sync get colors.accent
|
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
|
Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme
|
||||||
picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+Q` close · `SUPER+1..9`
|
picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu ·
|
||||||
workspaces · `Print` region screenshot.
|
`SUPER+E` file manager (yazi) · `SUPER+N` notifications · `SUPER+CTRL+V`
|
||||||
|
clipboard history · `SUPER+Q`
|
||||||
|
close · `SUPER+1..9` workspaces · `Print` region screenshot.
|
||||||
|
|
||||||
|
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
|
||||||
|
all; the curated set:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# navigation
|
||||||
|
.. ... .... # cd up 1 / 2 / 3 levels
|
||||||
|
|
||||||
|
# git
|
||||||
|
g gst # git · git status -sb
|
||||||
|
ga gaa # git add · git add -A
|
||||||
|
gc gcm # git commit · git commit -m
|
||||||
|
gco gsw gb # checkout · switch · branch
|
||||||
|
gd gds # diff · diff --staged
|
||||||
|
gl glg # log graph (last 20 · all branches)
|
||||||
|
gp gpl gf # push · pull · fetch --all --prune
|
||||||
|
|
||||||
|
# nix (system/home rebuilds keep their full sys-update / home-update names)
|
||||||
|
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
|
||||||
|
nfu nfc # nix flake update · check
|
||||||
|
nsearch ngc # nix search nixpkgs · nix-collect-garbage -d
|
||||||
|
|
||||||
|
# misc
|
||||||
|
path # print $PATH, one entry per line
|
||||||
|
reload # exec zsh (reload the shell)
|
||||||
|
```
|
||||||
|
|
||||||
## 6. Extending
|
## 6. Extending
|
||||||
|
|
||||||
@@ -228,13 +342,21 @@ workspaces · `Print` region screenshot.
|
|||||||
the Nix modules. One place — there is no second renderer to keep in sync.
|
the Nix modules. One place — there is no second renderer to keep in sync.
|
||||||
- **Importing more old-distro palettes:**
|
- **Importing more old-distro palettes:**
|
||||||
`tools/import-palettes.py <palettes-dir> themes/`.
|
`tools/import-palettes.py <palettes-dir> themes/`.
|
||||||
|
- **New opt-in feature (convention):** when a feature is off by default and
|
||||||
|
needs the user to set `nomarchy.*` options (e.g. night light, per-device
|
||||||
|
keyboard layouts, monitor layout, power management), ship a **commented**
|
||||||
|
example of it in `templates/downstream/home.nix` or `system.nix`. New
|
||||||
|
installs then enable it by uncommenting + tweaking, rather than hunting the
|
||||||
|
docs for the option name.
|
||||||
|
- **Applications:** the starter complete-workstation set (LibreOffice, VS
|
||||||
|
Code, GIMP, Inkscape) lives in the `home.packages` list in your
|
||||||
|
`home.nix` — it's a plain package list, so curate it directly: delete a
|
||||||
|
line to slim the machine, uncomment an extra (a browser, email, full TeX
|
||||||
|
Live), or add your own. No `nomarchy.apps.*` toggles — a package list is
|
||||||
|
already its own opt-out, so the distro doesn't impose these or wrap them.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap & known issues
|
||||||
|
|
||||||
- **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB
|
See **[docs/ROADMAP.md](docs/ROADMAP.md)** — forward-looking plans plus the
|
||||||
re-copy on every state write is the main eval tax), then pre-built theme
|
log of shipped fixes. Kept out of the README so this stays a focused entry
|
||||||
variants if still needed
|
point.
|
||||||
- Plymouth + SDDM/greeter theming from the same JSON
|
|
||||||
- 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
|
|
||||||
|
|||||||
147
docs/OVERRIDES.md
Normal file
@@ -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/<slug>/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.<component>.enable = false` |
|
||||||
|
| Raw Hyprland lines | `wayland.windowManager.hyprland.extraConfig` |
|
||||||
473
docs/ROADMAP.md
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
# 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/<slug>/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)
|
||||||
|
- ✓ **Organize the picker into category submenus:** the root picker had grown
|
||||||
|
into a long flat list of 16 entries. It's now six: **Apps · Theme · Tools ›
|
||||||
|
· System › · Power · Keybindings**. `Tools` (Calculator, Clipboard, Emoji,
|
||||||
|
Files, Web, Capture, Ask) and `System` (Network, Bluetooth, DND, and the
|
||||||
|
self-gated Snapshots / Power-profile) are submenus the dispatcher routes via
|
||||||
|
`nomarchy-menu tools|system`, each ending in a `← Back` entry that re-opens
|
||||||
|
the root. The direct `SUPER+CTRL+<mnemonic>` binds still hit the leaves
|
||||||
|
straight (bypassing the menu), and the self-gated entries still hide when
|
||||||
|
unavailable. Remaining (optional): a **Look & Feel** category once there are
|
||||||
|
more appearance toggles to group with Theme (night-light, wallpaper).
|
||||||
|
**General pattern (ongoing):** the grouping is a standing convention, not a
|
||||||
|
one-off — any new feature that earns a menu entry must be placed in the
|
||||||
|
right submenu (don't let the root creep back to a flat list), with its
|
||||||
|
direct `SUPER+CTRL+<mnemonic>` bind and self-gating as applicable.
|
||||||
|
- ✓ **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/<slug>/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 <distroName>`) 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:** a curated set ships on by default in
|
||||||
|
`modules/home/shell.nix` — navigation (`..`/`...`/`....`), a git block
|
||||||
|
(`g`, `gst`, `ga`/`gaa`, `gc`/`gcm`, `gco`/`gsw`, `gb`, `gd`/`gds`,
|
||||||
|
`gl`/`glg`, `gp`/`gpl`, `gf`), and a nix block (`ns`, `nr`, `nfu`, `nfc`,
|
||||||
|
`nsearch`, `ngc`), plus `path` and `reload`. Scope decision: short where it
|
||||||
|
helps but **never shadow a real binary** (same rule as rg/fd — the git block
|
||||||
|
deliberately avoids `gs`/ghostscript); system/home rebuilds keep their full
|
||||||
|
`sys-update`/`home-update` names rather than getting cryptic aliases. The set
|
||||||
|
is documented in README §5 (Day-to-day), and `alias` lists them in-shell.
|
||||||
|
- **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:** a starter complete-workstation set —
|
||||||
|
`libreoffice-fresh`, `vscode`, `gimp`, `inkscape` — ships *active* in the
|
||||||
|
downstream `templates/downstream/home.nix` `home.packages`, with the heavier
|
||||||
|
opt-ins (`texliveFull` — multi-GB; `texliveMedium` is lighter — plus
|
||||||
|
browser/email) commented just below. **Decision: no `nomarchy.apps.*` option
|
||||||
|
surface.** Unlike `nomarchy.services.*` (real config behind each toggle —
|
||||||
|
systemd units, subuid ranges, the Flathub oneshot), a bare package install
|
||||||
|
has nothing behind the toggle: `mkIf x { environment.systemPackages = [p]; }`
|
||||||
|
just reinholds what a package list already gives, and the honest opt-out for
|
||||||
|
a package is *deleting the line*. So the suite is the user's own curated list
|
||||||
|
(extending the existing `# firefox` convention), not a distro-module feature —
|
||||||
|
which also keeps the apps out of the live ISO automatically (it never starts
|
||||||
|
from the template) and respects that editor/office/graphics are personal-taste
|
||||||
|
choices, not defaults to impose. Unfree `vscode` evaluates fine — `mkFlake`
|
||||||
|
and the system both set `allowUnfree = true`.
|
||||||
|
- ✓ **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 locking hyprlock before sleep
|
||||||
|
meant a second password. Fixed by *not locking* before an encrypted
|
||||||
|
hibernate: a `nomarchy-lock-before-sleep` systemd unit
|
||||||
|
(`modules/nixos/default.nix`) takes over from hypridle's `before_sleep_cmd`
|
||||||
|
and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then-
|
||||||
|
hibernate) always, but skips `hibernate.target` when the disk is LUKS-
|
||||||
|
encrypted. (A first attempt that dismissed hyprlock *after* resume was
|
||||||
|
wrong — killing a Wayland session-lock client trips its "go to a tty"
|
||||||
|
crash failsafe instead of unlocking, which is the error screen it caused.)
|
||||||
|
- ✓ **Key agents & pinentry:** ships `modules/home/keys.nix`
|
||||||
|
(`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.
|
||||||
|
- ✓ pruned now-redundant config: the installer no longer writes
|
||||||
|
`console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`
|
||||||
|
(distro-wide defaults since the LUKS-keymap fix), nor the offline-pin
|
||||||
|
test fixture in `flake.nix`.
|
||||||
|
- ✓ reconciled the README option tables with the live `nomarchy.*`
|
||||||
|
surface (added keys/fastfetch/snapper/greeter.autoLogin).
|
||||||
|
- ✓ untracked `.claude/settings.local.json` (machine-local) + gitignored.
|
||||||
|
- ✓ **branch/release model:** `main` is the development default; `v1` is
|
||||||
|
the release pointer downstreams pin (`?ref=v1`), advanced to main once
|
||||||
|
a batch is tested — rolling, no tags. Brought `main` current (it was a
|
||||||
|
stale legacy-merge), and deleted the dead `refactor` / `wave/nixos-26.05`
|
||||||
|
branches (kept `legacy` for history). See the comment in `flake.nix`.
|
||||||
|
- ✓ re-checked that every file earns its place under the
|
||||||
|
`modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb: no orphaned
|
||||||
|
modules (`keybinds.nix` is a *data* module `import`-ed by hyprland/rofi,
|
||||||
|
so correctly absent from `default.nix`), no dead-code markers, no
|
||||||
|
editor/backup cruft, each `themes/<slug>.json` palette pairs with its
|
||||||
|
optional `<slug>/` overrides dir, and `hosts/{default,live}` are the
|
||||||
|
documented reference + ISO hosts. Structure is clean as-is.
|
||||||
|
- Open decision (deferred): the repo declares **no formatter** (no flake
|
||||||
|
`formatter`, no `.editorconfig`); `.nix` files use deliberate aligned
|
||||||
|
hand-formatting, so `nixfmt-rfc-style --check` flags ~33 files. Adopting a
|
||||||
|
formatter would be a one-time repo-wide reformat that flattens that
|
||||||
|
alignment — a maintainer call, not a silent cleanup, so left untouched.
|
||||||
|
- **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.
|
||||||
|
- ✓ **Waybar parity:** the `custom/powerprofile` indicator now shows in the
|
||||||
|
summer-day/night whole-swap themes too. `powerProfileStatus`/`Cycle` are
|
||||||
|
named `writeShellScriptBin`s on PATH (`waybar.nix` home.packages), so the
|
||||||
|
generated config and the static `waybar.jsonc` both exec them by bare
|
||||||
|
name (the same parity the DND bell has). General pattern: any new
|
||||||
|
generated-config module needs adding to the whole-swap themes too.
|
||||||
|
- **Full hardware enablement (beyond nixos-hardware):** nixos-hardware
|
||||||
|
profiles cover *basic* per-model functionality (quirks, drivers), but the
|
||||||
|
*advanced* features of a machine — keyed more on the CPU/GPU generation
|
||||||
|
than the model — are left for the user to wire up by hand. Close that gap
|
||||||
|
without making it finnicky: extend the installer's existing autodetection
|
||||||
|
(DMI → nixos-hardware profile today; add CPU vendor/arch, GPU, NPU,
|
||||||
|
fingerprint reader, SSD) to switch on the *safe, broadly-beneficial*
|
||||||
|
features automatically, and expose a small `nomarchy.hardware.*` surface
|
||||||
|
(per-vendor where it helps, e.g. `nomarchy.hardware.amd.*`) for the heavy
|
||||||
|
or experimental bits. Goal: full utilisation out of the box — opt-*out* for
|
||||||
|
the safe defaults, opt-*in* for the multi-GB / experimental extras.
|
||||||
|
Complements nixos-hardware (model quirks) rather than replacing it; keep
|
||||||
|
the detection in the installer's `hardware-db` so an installed machine
|
||||||
|
bakes the right defaults, all overridable through `nomarchy.hardware.*`.
|
||||||
|
Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
|
||||||
|
- **GPU compute — ROCm:** the 780M is an RDNA3 iGPU (gfx1103) ROCm doesn't
|
||||||
|
officially list, so it needs `HSA_OVERRIDE_GFX_VERSION=11.0.0`. Multi-GB
|
||||||
|
closure → opt-in (`nomarchy.hardware.amd.rocm.enable`); unlocks GPU
|
||||||
|
PyTorch / Ollama.
|
||||||
|
- **Ryzen AI NPU (XDNA):** the `amdxdna` kernel driver (mainline 6.14+) +
|
||||||
|
the XRT/xdna userspace runtime + firmware. Niche/experimental → opt-in.
|
||||||
|
- **AMD P-State EPP (Zen 4 power):** `amd_pstate=active` for the EPP
|
||||||
|
governor, which power-profiles-daemon (already shipped, `power.nix`)
|
||||||
|
drives per profile. Broadly beneficial on Zen 2+ → default on when the
|
||||||
|
installer sees an AMD CPU.
|
||||||
|
- **Hardware video acceleration (VA-API):** mesa's radeonsi exposes VA-API
|
||||||
|
on AMD (`LIBVA_DRIVER_NAME=radeonsi`, libva + drivers in
|
||||||
|
`hardware.graphics.extraPackages`); Intel uses `intel-media-driver`.
|
||||||
|
Broadly beneficial → default on, vendor-detected.
|
||||||
|
- **LVFS firmware & biometrics:** fwupd already ships (✓, `services.fwupd`).
|
||||||
|
Add fingerprint: `services.fprintd` when a reader is detected, with
|
||||||
|
opt-in PAM integration for login/sudo (password-only stays the default
|
||||||
|
for the cautious).
|
||||||
|
- **SSD periodic TRIM:** `services.fstrim.enable` (weekly) — safe and
|
||||||
|
beneficial, so default on (trivial enough it may warrant being
|
||||||
|
distro-wide regardless).
|
||||||
|
Sub-items here can graduate into their own roadmap entries as they're
|
||||||
|
scoped; the unifying work is the detection + `nomarchy.hardware.*` surface.
|
||||||
|
- **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.<name>.enable` toggle a
|
||||||
|
downstream flips on in one line. Keeps the base lean while making common
|
||||||
|
additions trivial. **✓ Seventeen shipped** in `modules/nixos/services.nix`
|
||||||
|
(system-side), each with a commented example in the downstream `system.nix`
|
||||||
|
template: `tailscale`, `syncthing` (runs as the login user, GUI on
|
||||||
|
127.0.0.1:8384), `podman` (rootless, `docker` aliased, user subuid/subgid),
|
||||||
|
`flatpak` (+ Flathub remote added by a oneshot), `pika` (Pika Backup GUI),
|
||||||
|
`steam` (`programs.steam` — the 32-bit stack, controller udev, Remote-Play
|
||||||
|
ports a bare package can't), `libvirt` (libvirtd + virt-manager, user added
|
||||||
|
to the `libvirtd` group), `obs` (OBS Studio + a v4l2loopback virtual camera
|
||||||
|
usable as a webcam in Zoom/Teams), `docker` (rootful; asserts against the
|
||||||
|
podman docker-compat), `kdeconnect`, `gamemode`, `adb` (android-tools;
|
||||||
|
systemd handles the udev rules), `wireshark` (Qt GUI + the `wireshark`
|
||||||
|
group), `ollama` (local LLM API on 127.0.0.1:11434), `printing` (CUPS +
|
||||||
|
Avahi mDNS), `openrgb` (RGB lighting daemon), and `restic` (scheduled
|
||||||
|
daily backup — a small option surface: `repository`/`passwordFile`/`paths`,
|
||||||
|
7/4/6 retention). More candidates by area:
|
||||||
|
- ✓ **cloud/sync:** Syncthing (`services.syncthing`); Nextcloud client is a
|
||||||
|
bare tray app (in the app-suite menu, not a service)
|
||||||
|
- ✓ **local AI:** Ollama (`nomarchy.services.ollama`, optional GPU accel)
|
||||||
|
ships; LM Studio is a (commented) app-suite package (a bare GUI, no
|
||||||
|
service) — pairs with the menu's Ask-Claude philosophy
|
||||||
|
- ✓ **containers/VMs:** Podman (`virtualisation.podman`); libvirt +
|
||||||
|
virt-manager (`nomarchy.services.libvirt`); Docker (`nomarchy.services.docker`)
|
||||||
|
- ✓ **networking:** Tailscale (`services.tailscale`); WireGuard needs no
|
||||||
|
toggle — NetworkManager (on by default) imports `.conf` tunnels natively;
|
||||||
|
the `wireguard-tools` CLI is in the app-suite menu
|
||||||
|
- ✓ **gaming/media:** Steam (`nomarchy.services.steam`); OBS Studio
|
||||||
|
(`nomarchy.services.obs`, with the v4l2loopback virtual camera); GameMode
|
||||||
|
(`nomarchy.services.gamemode`)
|
||||||
|
- ✓ **devices:** printing (CUPS + Avahi, `nomarchy.services.printing`); KDE
|
||||||
|
Connect (`nomarchy.services.kdeconnect`); OpenRGB
|
||||||
|
(`nomarchy.services.openrgb`). Plus dev tooling — Android `adb` and
|
||||||
|
`wireshark` toggles
|
||||||
|
- ✓ **backup:** Pika Backup (`pika-backup`, GUI over Borg); scheduled
|
||||||
|
headless restic (`nomarchy.services.restic`)
|
||||||
|
- ✓ **escape hatch:** Flatpak (`services.flatpak`) + Flathub, for apps
|
||||||
|
outside nixpkgs
|
||||||
|
|
||||||
|
Surface decided: `nomarchy.services.*`, system-side for system services
|
||||||
|
(home-side ones can extend the same prefix later). Remaining: curate the
|
||||||
|
rest (Ollama, containers, Steam/OBS, printing, backups, Flatpak …). The
|
||||||
|
opt-out application *suite* is deliberately **not** a parallel
|
||||||
|
`nomarchy.apps.*` surface — it's a curated `home.packages` list in the
|
||||||
|
downstream template (see "Default application suite" above), since a bare
|
||||||
|
package install has no config to put behind a toggle. Unfree entries are
|
||||||
|
already covered (`allowUnfree = true`).
|
||||||
|
- ✓ **Display / monitor management:** declarative `nomarchy.monitors` (a
|
||||||
|
per-output schema — name/resolution/position/scale/transform/mirror/
|
||||||
|
bitdepth/vrr/disable) generates Hyprland `monitor` rules, keeping the
|
||||||
|
`,preferred,auto,1` wildcard as the fallback; Hyprland applies them on
|
||||||
|
hotplug, so a declared external/dock output arranges itself on connect
|
||||||
|
(no kanshi — it fights Hyprland's own output management). `nwg-displays`
|
||||||
|
ships behind `nomarchy.displays.enable` as an interactive arranger (a
|
||||||
|
helper to find values; the declarative config stays the source of truth —
|
||||||
|
its output file isn't sourced). Remaining: true docked/undocked **profile
|
||||||
|
switching** of the *same* outputs (Hyprland's per-output rules cover the
|
||||||
|
common "external connects → arrange" case, not multi-layout toggles), and
|
||||||
|
optionally workspace-to-monitor binding.
|
||||||
|
- ✓ **Night light / blue-light filter:** `nomarchy.nightlight` (opt-in) —
|
||||||
|
a scheduled colour-temperature shift via `hyprsunset` (Hyprland-native),
|
||||||
|
warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
|
||||||
|
drives the HM `services.hyprsunset` with two time-based profiles
|
||||||
|
(`sunrise` → identity, `sunset` → `temperature`), so hyprsunset handles the
|
||||||
|
schedule and the on-login state. Needs an on-hardware check that hyprsunset
|
||||||
|
applies the active profile at session start. Remaining (optional): a menu +
|
||||||
|
Waybar toggle to force it on/off, and geo (lat/long) auto sunset/sunrise
|
||||||
|
(would mean wlsunset, which schedules by location).
|
||||||
|
- **Keyboard layouts (per-device + switching):**
|
||||||
|
- ✓ **Per-device declarative layout:** `nomarchy.keyboard.devices`
|
||||||
|
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
|
||||||
|
`device` blocks that override the session `nomarchy.keyboard.layout` for a
|
||||||
|
named keyboard — e.g. an external board that's physically a different
|
||||||
|
layout than the laptop's. Hyprland applies it whenever that device
|
||||||
|
connects (the "external keyboard remembers its layout" case, done the
|
||||||
|
declarative way). A Waybar `hyprland/language` indicator shows the active
|
||||||
|
layout, gated on more than one layout being in play (comma in the session
|
||||||
|
layout, or any per-device override) so single-layout bars stay clean.
|
||||||
|
- ✓ **Interactive new-keyboard prompt (runtime + persist):** set
|
||||||
|
`nomarchy.keyboard.layouts` (candidate layouts) and a `nomarchy-keyboard-
|
||||||
|
watch` daemon runs (exec-once): it polls `hyprctl devices`, and when a
|
||||||
|
keyboard connects *after* login that isn't in `keyboard.devices` and
|
||||||
|
hasn't been chosen before, it pops a rofi layout picker, applies the
|
||||||
|
choice with `hyprctl switchxkblayout` (an index into the candidate set,
|
||||||
|
which `input.kb_layout` carries), and remembers it per-device in
|
||||||
|
`~/.local/state/nomarchy/keyboard-layouts` — re-applied silently on later
|
||||||
|
reconnects. The boot-time set (incl. the built-in keyboard) is never
|
||||||
|
prompted. The runtime-remember complement to the declarative
|
||||||
|
`keyboard.devices`; a stateful runtime piece by design. **Pending an
|
||||||
|
on-hardware test** (hotplug isn't verifiable in CI). Remaining (bonus):
|
||||||
|
offer to write a choice into `keyboard.devices` so it graduates to the
|
||||||
|
reproducible config.
|
||||||
|
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
|
||||||
|
`grp:` options) for switching layouts on one keyboard; add the
|
||||||
|
`hyprland/language` module to the summer whole-swap themes for parity
|
||||||
|
(the gating doesn't translate to their static JSON). Keep the system
|
||||||
|
(console/initrd) and session layouts in sync (the LUKS-keymap work).
|
||||||
|
- ✓ **Do-Not-Disturb:** swaync DND toggle wired into the menu
|
||||||
|
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
||||||
|
indicator (`custom/notification` via `swaync-client -swb`: shows the
|
||||||
|
notification count + DND state, left-click toggles the panel,
|
||||||
|
right-click toggles DND). Turning DND off confirms with a toast; turning
|
||||||
|
it on is silent (notifications are suppressed — the bell-off glyph is the
|
||||||
|
cue).
|
||||||
|
- ✓ **Snapshot browse/restore UX:** `nomarchy-menu snapshot` (in the SUPER+M
|
||||||
|
picker, self-gated on snapper) launches **btrfs-assistant** — the
|
||||||
|
desktop browse/diff/restore/rollback UI, elevating via polkit (the safe
|
||||||
|
choice for a destructive, root-only op vs a fat-fingerable rofi rollback).
|
||||||
|
Shipped system-side gated on `nomarchy.system.snapper`. Also extended
|
||||||
|
snapper to snapshot **/home** by default (hourly 5 / daily 7 / weekly 4)
|
||||||
|
when it's its own BTRFS subvolume; a boot oneshot creates the required
|
||||||
|
`/home/.snapshots` subvolume if missing (covers existing installs, no disko
|
||||||
|
change). **The backend is VM-verified** (a full BTRFS+LUKS install: both
|
||||||
|
`root`+`home` configs, a `/home` timeline snapshot taken, `/home/.snapshots`
|
||||||
|
a real subvolume, the oneshot `Result=success`).
|
||||||
|
- ⚠ **Known bug — btrfs-assistant 2.2 segfaults on launch** in nixpkgs
|
||||||
|
26.05: it crashes inside `libbtrfsutil.so.1.4.0` (a library ABI mismatch,
|
||||||
|
confirmed in the VM regardless of GL/Qt platform — so it's not a VM/GL
|
||||||
|
artifact and will crash on hardware too). The menu wiring is correct and
|
||||||
|
the app is installed, but the GUI doesn't open. The snapshot *backend*
|
||||||
|
(the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning
|
||||||
|
its libbtrfsutil, or wait for an upstream bump; meanwhile the menu entry
|
||||||
|
is a no-op when the binary crashes. Fallback if it lingers: a rofi-based
|
||||||
|
snapshot menu (no btrfs-assistant dependency).
|
||||||
|
- Remaining (optional): a keyboard-driven rofi browse for quick glances;
|
||||||
|
boot-from-snapshot needs a systemd-boot equivalent of grub-btrfs.
|
||||||
|
- **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+<mnemonic>` 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. Confirmed in a live
|
||||||
|
session (headless QEMU): the portal's `org.freedesktop.appearance`
|
||||||
|
`color-scheme` reads `1` on a dark theme and flips to `2` after switching
|
||||||
|
to a light one (catppuccin-latte) — so xdg-desktop-portal-gtk does relay
|
||||||
|
the dconf value GTK4/Qt apps read.
|
||||||
17
flake.lock
generated
@@ -208,11 +208,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1780281641,
|
"lastModified": 1779766384,
|
||||||
"narHash": "sha256-M/+hUKoKbHXpV0xGVfELbN1Ds1aoe3pL5p5/t46YhVo=",
|
"narHash": "sha256-P7Ohnlq8b8b2fU+Sgkrej7LBTM60LBTkHleLuYzmLmU=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "NUR",
|
"repo": "NUR",
|
||||||
"rev": "30f9ae2f04174de63ba8bcf3580ca90843b28a01",
|
"rev": "57800b7ab648725ccd33551d01484ee14952ad3f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -249,15 +249,16 @@
|
|||||||
"tinted-zed": "tinted-zed"
|
"tinted-zed": "tinted-zed"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1781018772,
|
"lastModified": 1780702455,
|
||||||
"narHash": "sha256-C+cGIUaC6dqfwTbI+BwCd572PbESGA3WYxR1sLTqxkY=",
|
"narHash": "sha256-+srjPGNy67nKytYwdlepycL51IG6S34sS4MKRZXK8G0=",
|
||||||
"owner": "danth",
|
"owner": "nix-community",
|
||||||
"repo": "stylix",
|
"repo": "stylix",
|
||||||
"rev": "a378e4c09031fb15a4d65da88aa628f71fc52f6b",
|
"rev": "54fa19702f4f2c7f6a981a92850678933588af9a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "danth",
|
"owner": "nix-community",
|
||||||
|
"ref": "release-26.05",
|
||||||
"repo": "stylix",
|
"repo": "stylix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|||||||
66
flake.nix
@@ -10,9 +10,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
stylix = {
|
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.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.home-manager.follows = "home-manager";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Pinned by Nomarchy so distro + hardware quirks are tested together
|
# Pinned by Nomarchy so distro + hardware quirks are tested together
|
||||||
@@ -32,6 +35,9 @@
|
|||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ self.overlays.default ];
|
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
|
# The guided installer — live-ISO only (not in the overlay): it bakes
|
||||||
@@ -45,8 +51,11 @@
|
|||||||
(nixpkgs.lib.concatStringsSep "\n"
|
(nixpkgs.lib.concatStringsSep "\n"
|
||||||
(builtins.attrNames nixos-hardware.nixosModules));
|
(builtins.attrNames nixos-hardware.nixosModules));
|
||||||
nixpkgsPath = nixpkgs.outPath;
|
nixpkgsPath = nixpkgs.outPath;
|
||||||
# v1 is the release branch: installed machines track it on
|
# Branch model: `main` is the development default; `v1` is the
|
||||||
# `nix flake update` instead of the forge's default branch.
|
# release pointer — the stable line for the NixOS 26.05 rewrite,
|
||||||
|
# advanced to main once a batch is tested (rolling, no tags). A
|
||||||
|
# future major bump would become `v2`. Installed machines track
|
||||||
|
# `?ref=v1` on `nix flake update`, not the forge's default branch.
|
||||||
flakeUrl = "git+${gitUrl}?ref=v1";
|
flakeUrl = "git+${gitUrl}?ref=v1";
|
||||||
# Future updates re-resolve from the forge (original); the install
|
# Future updates re-resolve from the forge (original); the install
|
||||||
# itself resolves from the ISO store (locked = path) — fully
|
# itself resolves from the ISO store (locked = path) — fully
|
||||||
@@ -142,6 +151,7 @@
|
|||||||
specialArgs = { inherit username; };
|
specialArgs = { inherit username; };
|
||||||
modules = [
|
modules = [
|
||||||
self.nixosModules.nomarchy
|
self.nixosModules.nomarchy
|
||||||
|
{ nomarchy.system.stateFile = ./theme-state.json; }
|
||||||
./hosts/default/configuration.nix
|
./hosts/default/configuration.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -245,6 +255,14 @@
|
|||||||
({ lib, ... }: {
|
({ lib, ... }: {
|
||||||
nomarchy.system.snapper.enable = true;
|
nomarchy.system.snapper.enable = true;
|
||||||
nomarchy.system.greeter.autoLogin = username;
|
nomarchy.system.greeter.autoLogin = username;
|
||||||
|
# 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"; }];
|
swapDevices = [{ device = "/swap/swapfile"; }];
|
||||||
boot.resumeDevice = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000";
|
boot.resumeDevice = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000";
|
||||||
boot.kernelParams = [ "resume_offset=1" ];
|
boot.kernelParams = [ "resume_offset=1" ];
|
||||||
@@ -270,6 +288,20 @@
|
|||||||
representativeInstall.config.system.build.toplevel
|
representativeInstall.config.system.build.toplevel
|
||||||
template.homeConfigurations.${username}.activationPackage
|
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
|
# A real install rebuilds the drvs that embed the
|
||||||
# user's hostname/username/UUIDs (etc, initrd, units,
|
# user's hostname/username/UUIDs (etc, initrd, units,
|
||||||
# toplevel, HM activation). inputDerivation pins each
|
# toplevel, HM activation). inputDerivation pins each
|
||||||
@@ -278,6 +310,20 @@
|
|||||||
representativeInstall.config.system.build.etc.inputDerivation
|
representativeInstall.config.system.build.etc.inputDerivation
|
||||||
representativeInstall.config.system.build.initialRamdisk.inputDerivation
|
representativeInstall.config.system.build.initialRamdisk.inputDerivation
|
||||||
template.homeConfigurations.${username}.activationPackage.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
|
||||||
|
# the stdenv its trivial drvs build with must be on
|
||||||
|
# board, or an offline `apply <theme>` 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
|
# …and the second-level build tools those rebuilds need
|
||||||
# (their parents are themselves rebuilt, so parent
|
# (their parents are themselves rebuilt, so parent
|
||||||
@@ -299,6 +345,18 @@
|
|||||||
# tries to BUILD remarshal, whose test closure pulls
|
# tries to BUILD remarshal, whose test closure pulls
|
||||||
# matplotlib/ffmpeg/x265 (the ~1900-drv cascade).
|
# matplotlib/ffmpeg/x265 (the ~1900-drv cascade).
|
||||||
pkgs.remarshal
|
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
|
# …and one representative disko script: its tool closure
|
||||||
# (file, which, wrapper hooks, …) isn't otherwise on the
|
# (file, which, wrapper hooks, …) isn't otherwise on the
|
||||||
|
|||||||
@@ -3,15 +3,51 @@
|
|||||||
# exists to test the distro end-to-end on real hardware.
|
# exists to test the distro end-to-end on real hardware.
|
||||||
{ lib, pkgs, username, nomarchySrc, ... }:
|
{ 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";
|
networking.hostName = "nomarchy-live";
|
||||||
|
|
||||||
isoImage.volumeID = lib.mkForce "NOMARCHY_LIVE";
|
isoImage.volumeID = lib.mkForce "NOMARCHY_LIVE";
|
||||||
isoImage.edition = lib.mkForce "live";
|
isoImage.edition = lib.mkForce "live";
|
||||||
|
isoImage.splashImage = isoSplash;
|
||||||
|
|
||||||
# The minimal-CD profile enables wpa_supplicant; Nomarchy ships
|
# The minimal-CD profile slims the image for a CONSOLE installer; this
|
||||||
# NetworkManager and the two must not run together.
|
# ISO is the desktop, so re-enable what it strips. Above all
|
||||||
networking.wireless.enable = lib.mkForce false;
|
# 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;
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
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
|
||||||
|
# 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 ──────────────
|
# ── Live user: no password, straight into the desktop ──────────────
|
||||||
users.users.${username} = {
|
users.users.${username} = {
|
||||||
@@ -24,7 +60,7 @@
|
|||||||
|
|
||||||
# Boot straight into Hyprland once; logging out lands on tuigreet.
|
# Boot straight into Hyprland once; logging out lands on tuigreet.
|
||||||
services.greetd.settings.initial_session = {
|
services.greetd.settings.initial_session = {
|
||||||
command = "Hyprland";
|
command = "start-hyprland";
|
||||||
user = username;
|
user = username;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,6 +106,12 @@
|
|||||||
|
|
||||||
# ── Live-session desktop tweaks ─────────────────────────────────────
|
# ── Live-session desktop tweaks ─────────────────────────────────────
|
||||||
home-manager.users.${username} = {
|
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 = {
|
wayland.windowManager.hyprland.settings = {
|
||||||
# QEMU (and some panels) report a tiny "preferred" mode; ask for
|
# QEMU (and some panels) report a tiny "preferred" mode; ask for
|
||||||
# the highest resolution instead.
|
# the highest resolution instead.
|
||||||
|
|||||||
6
lib.nix
@@ -47,6 +47,9 @@
|
|||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = [ nomarchy.overlays.default ];
|
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
|
in
|
||||||
{
|
{
|
||||||
@@ -62,6 +65,9 @@
|
|||||||
# (`nomarchy-theme-sync apply` → `home-manager switch`) works
|
# (`nomarchy-theme-sync apply` → `home-manager switch`) works
|
||||||
# out of the box — same pinned input as the desktop modules.
|
# out of the box — same pinned input as the desktop modules.
|
||||||
{ environment.systemPackages = [ home-manager.packages.${system}.home-manager ]; }
|
{ 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
|
++ hardwareModules
|
||||||
++ [
|
++ [
|
||||||
|
|||||||
@@ -12,13 +12,30 @@
|
|||||||
./waybar.nix
|
./waybar.nix
|
||||||
./ghostty.nix
|
./ghostty.nix
|
||||||
./btop.nix
|
./btop.nix
|
||||||
|
./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
|
||||||
|
./osd.nix # swayosd volume/brightness OSD, themed
|
||||||
|
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||||
|
./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
|
||||||
|
# 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.stateVersion = "26.05";
|
||||||
|
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
awww # wallpaper daemon with animated transitions (the swww fork)
|
awww # wallpaper daemon with animated transitions (the swww fork)
|
||||||
fuzzel # launcher / dmenu (used by the theme picker bind)
|
|
||||||
libnotify
|
libnotify
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
50
modules/home/fastfetch.nix
Normal file
@@ -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"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -30,12 +30,14 @@ in
|
|||||||
palette = lib.imap0 (i: color: "${toString i}=${color}") t.ansi;
|
palette = lib.imap0 (i: color: "${toString i}=${color}") t.ansi;
|
||||||
|
|
||||||
# ── Chrome ────────────────────────────────────────────────────
|
# ── 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;
|
background-opacity = t.ui.terminalOpacity;
|
||||||
window-padding-x = 12;
|
window-padding-x = lib.mkDefault 12;
|
||||||
window-padding-y = 12;
|
window-padding-y = lib.mkDefault 12;
|
||||||
window-decoration = false;
|
window-decoration = lib.mkDefault false;
|
||||||
gtk-single-instance = true;
|
gtk-single-instance = lib.mkDefault true;
|
||||||
confirm-close-surface = false;
|
confirm-close-surface = lib.mkDefault false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
# Hyprland — fully driven by config.nomarchy.theme (theme-state.json).
|
# Hyprland — fully driven by config.nomarchy.theme (theme-state.json).
|
||||||
# Gaps, borders and colors are baked from the JSON at eval time; theme
|
# Gaps, borders and colors are baked from the JSON at eval time; theme
|
||||||
# changes arrive via `home-manager switch`.
|
# changes arrive via `home-manager switch`.
|
||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
t = config.nomarchy.theme;
|
t = config.nomarchy.theme;
|
||||||
c = t.colors;
|
c = t.colors;
|
||||||
inherit (config.nomarchy.lib) rgb rgba;
|
inherit (config.nomarchy.lib) rgb rgba;
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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.
|
# SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated.
|
||||||
workspaceBinds = builtins.concatLists (builtins.genList
|
workspaceBinds = builtins.concatLists (builtins.genList
|
||||||
(i:
|
(i:
|
||||||
@@ -17,6 +36,82 @@ let
|
|||||||
"$mod SHIFT, ${ws}, movetoworkspace, ${ws}"
|
"$mod SHIFT, ${ws}, movetoworkspace, ${ws}"
|
||||||
])
|
])
|
||||||
9);
|
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}";
|
||||||
|
|
||||||
|
# A nomarchy.monitors entry -> a Hyprland `monitor` rule. Unset optional
|
||||||
|
# fields are omitted; `resolution = "disable"` collapses to the short form.
|
||||||
|
monitorRule = m:
|
||||||
|
if m.resolution == "disable" then "${m.name}, disable"
|
||||||
|
else lib.concatStringsSep ", " (
|
||||||
|
[ m.name m.resolution (toString m.position) (toString m.scale) ]
|
||||||
|
++ lib.optionals (m.transform != null) [ "transform" (toString m.transform) ]
|
||||||
|
++ lib.optionals (m.mirror != null) [ "mirror" m.mirror ]
|
||||||
|
++ lib.optionals (m.bitdepth != null) [ "bitdepth" (toString m.bitdepth) ]
|
||||||
|
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
||||||
|
++ lib.optional (m.extra != "") m.extra
|
||||||
|
);
|
||||||
|
|
||||||
|
# Keyboard layout candidates: the primary nomarchy.keyboard.layout (split on
|
||||||
|
# commas) plus any nomarchy.keyboard.layouts. Drives input.kb_layout so all
|
||||||
|
# of them are switchable, and feeds the watcher's picker.
|
||||||
|
kbLayouts = lib.unique (
|
||||||
|
(lib.splitString "," config.nomarchy.keyboard.layout)
|
||||||
|
++ config.nomarchy.keyboard.layouts
|
||||||
|
);
|
||||||
|
kbAutoSwitch = config.nomarchy.keyboard.layouts != [ ];
|
||||||
|
|
||||||
|
# On a keyboard that connects after login (not declared in
|
||||||
|
# keyboard.devices, not already remembered), ask for a layout and persist it
|
||||||
|
# per-device, re-applying silently on later reconnects. Stateful runtime
|
||||||
|
# piece — the complement to the declarative keyboard.devices. Reliable
|
||||||
|
# primitives only: poll hyprctl devices, apply via switchxkblayout (an index
|
||||||
|
# into kbLayouts). NOTE: needs an on-hardware test (hotplug isn't verifiable
|
||||||
|
# in CI).
|
||||||
|
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
|
||||||
|
set -u
|
||||||
|
layouts="${lib.concatStringsSep " " kbLayouts}"
|
||||||
|
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
|
||||||
|
state="''${XDG_STATE_HOME:-$HOME/.local/state}/nomarchy/keyboard-layouts"
|
||||||
|
mkdir -p "$(dirname "$state")"; [ -f "$state" ] || : > "$state"
|
||||||
|
|
||||||
|
layout_index() { i=0; for l in $layouts; do [ "$l" = "$1" ] && { printf %s "$i"; return; }; i=$((i + 1)); done; printf %s -1; }
|
||||||
|
apply() { idx=$(layout_index "$2"); [ "$idx" -ge 0 ] && hyprctl switchxkblayout "$1" "$idx" >/dev/null 2>&1; }
|
||||||
|
keyboards() { hyprctl devices -j | jq -r '.keyboards[].name'; }
|
||||||
|
is_declared() { case " $declared " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
||||||
|
saved_for() { while IFS='=' read -r k v; do [ "$k" = "$1" ] && { printf '%s' "$v"; return; }; done < "$state"; }
|
||||||
|
|
||||||
|
# Startup: re-apply remembered layouts, never prompt — so the built-in
|
||||||
|
# keyboard just stays on the session default.
|
||||||
|
prev=" "
|
||||||
|
for kb in $(keyboards); do
|
||||||
|
prev="$prev$kb "
|
||||||
|
is_declared "$kb" && continue
|
||||||
|
s=$(saved_for "$kb"); [ -n "$s" ] && apply "$kb" "$s"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Watch for keyboards that connect later.
|
||||||
|
while :; do
|
||||||
|
sleep 3
|
||||||
|
cur=" "
|
||||||
|
for kb in $(keyboards); do
|
||||||
|
cur="$cur$kb "
|
||||||
|
case "$prev" in *" $kb "*) continue ;; esac
|
||||||
|
is_declared "$kb" && continue
|
||||||
|
s=$(saved_for "$kb")
|
||||||
|
if [ -n "$s" ]; then
|
||||||
|
apply "$kb" "$s"
|
||||||
|
else
|
||||||
|
choice=$(printf '%s\n' $layouts | rofi -dmenu -p "Layout · $kb")
|
||||||
|
[ -n "$choice" ] && { printf '%s=%s\n' "$kb" "$choice" >> "$state"; apply "$kb" "$choice"; }
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
prev="$cur"
|
||||||
|
done
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
||||||
@@ -35,25 +130,40 @@ in
|
|||||||
"$mod" = "SUPER";
|
"$mod" = "SUPER";
|
||||||
"$terminal" = config.nomarchy.terminal;
|
"$terminal" = config.nomarchy.terminal;
|
||||||
|
|
||||||
monitor = [ ",preferred,auto,1" ];
|
# `,preferred,auto,1` is the wildcard fallback for any output not in
|
||||||
|
# nomarchy.monitors; the generated rules are appended and win by name,
|
||||||
|
# and Hyprland applies them on hotplug. mkDefault so a downstream
|
||||||
|
# `monitor = [...]` replaces the lot (the live ISO uses mkForce too).
|
||||||
|
monitor = lib.mkDefault
|
||||||
|
([ ",preferred,auto,1" ] ++ map monitorRule config.nomarchy.monitors);
|
||||||
|
|
||||||
|
# exec-once is a list at normal priority, so downstream additions
|
||||||
|
# CONCATENATE (your autostart runs alongside ours) rather than
|
||||||
|
# replace. Same for the bind* lists below.
|
||||||
exec-once = [
|
exec-once = [
|
||||||
# nixpkgs' swww is awww (renamed fork); the binary is awww-daemon.
|
# nixpkgs' swww is awww (renamed fork); the binary is awww-daemon.
|
||||||
"awww-daemon"
|
"awww-daemon"
|
||||||
# Paint the wallpaper as soon as the session is up (waits for
|
# Paint the wallpaper as soon as the session is up (waits for
|
||||||
# the daemon internally).
|
# the daemon internally).
|
||||||
"nomarchy-theme-sync wallpaper"
|
"nomarchy-theme-sync wallpaper"
|
||||||
];
|
] ++ lib.optional kbAutoSwitch "${keyboardWatch}/bin/nomarchy-keyboard-watch";
|
||||||
|
|
||||||
# ── Theme-driven look ──────────────────────────────────────────
|
# ── Theme-driven look ──────────────────────────────────────────
|
||||||
|
# These flow from theme-state.json and stay at NORMAL priority:
|
||||||
|
# change them with `nomarchy-theme-sync set ui.<key>` (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 = {
|
general = {
|
||||||
gaps_in = t.ui.gapsIn;
|
gaps_in = t.ui.gapsIn;
|
||||||
gaps_out = t.ui.gapsOut;
|
gaps_out = t.ui.gapsOut;
|
||||||
border_size = t.ui.borderSize;
|
border_size = t.ui.borderSize;
|
||||||
"col.active_border" = "${rgb c.accent} ${rgb c.accentAlt} 45deg";
|
# Solid borders from theme.border (per-theme, palette-resolved) —
|
||||||
"col.inactive_border" = rgb c.overlay;
|
# matches the legacy identity themes, which never used a gradient.
|
||||||
layout = "dwindle";
|
"col.active_border" = rgb t.border.active;
|
||||||
resize_on_border = true;
|
"col.inactive_border" = rgb t.border.inactive;
|
||||||
|
layout = lib.mkDefault "dwindle";
|
||||||
|
resize_on_border = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
|
|
||||||
decoration = {
|
decoration = {
|
||||||
@@ -62,25 +172,26 @@ in
|
|||||||
inactive_opacity = t.ui.inactiveOpacity;
|
inactive_opacity = t.ui.inactiveOpacity;
|
||||||
blur = {
|
blur = {
|
||||||
enabled = t.ui.blur;
|
enabled = t.ui.blur;
|
||||||
size = 8;
|
size = lib.mkDefault 8;
|
||||||
passes = 2;
|
passes = lib.mkDefault 2;
|
||||||
popups = true;
|
popups = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
shadow = {
|
shadow = {
|
||||||
enabled = t.ui.shadow;
|
enabled = t.ui.shadow;
|
||||||
range = 20;
|
range = lib.mkDefault 20;
|
||||||
render_power = 3;
|
render_power = lib.mkDefault 3;
|
||||||
color = rgba c.mantle "aa";
|
color = rgba c.mantle "aa";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ── Behaviour (mkDefault → plain home.nix assignment overrides) ──
|
||||||
animations = {
|
animations = {
|
||||||
enabled = true;
|
enabled = lib.mkDefault true;
|
||||||
bezier = [
|
bezier = lib.mkDefault [
|
||||||
"smooth, 0.25, 0.1, 0.25, 1.0"
|
"smooth, 0.25, 0.1, 0.25, 1.0"
|
||||||
"snappy, 0.6, 0.0, 0.1, 1.0"
|
"snappy, 0.6, 0.0, 0.1, 1.0"
|
||||||
];
|
];
|
||||||
animation = [
|
animation = lib.mkDefault [
|
||||||
"windows, 1, 4, snappy, popin 85%"
|
"windows, 1, 4, snappy, popin 85%"
|
||||||
"border, 1, 8, smooth"
|
"border, 1, 8, smooth"
|
||||||
"fade, 1, 5, smooth"
|
"fade, 1, 5, smooth"
|
||||||
@@ -88,53 +199,64 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# ── Behaviour ──────────────────────────────────────────────────
|
|
||||||
input = {
|
input = {
|
||||||
kb_layout = "us";
|
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
||||||
follow_mouse = 1;
|
# option (the installer writes it; it also drives tty/LUKS).
|
||||||
touchpad.natural_scroll = true;
|
kb_layout = lib.concatStringsSep "," kbLayouts;
|
||||||
|
kb_variant = config.nomarchy.keyboard.variant;
|
||||||
|
follow_mouse = lib.mkDefault 1;
|
||||||
|
touchpad.natural_scroll = lib.mkDefault true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Per-device keyboard layouts (nomarchy.keyboard.devices): each
|
||||||
|
# overrides the session kb_layout for one named keyboard — e.g. an
|
||||||
|
# external board that's physically a different layout than the laptop's
|
||||||
|
# built-in one. Hyprland applies them whenever the device connects.
|
||||||
|
device = lib.mapAttrsToList
|
||||||
|
(name: d: { inherit name; kb_layout = d.layout; kb_variant = d.variant; })
|
||||||
|
config.nomarchy.keyboard.devices;
|
||||||
|
|
||||||
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
||||||
# dispatcher still exists); setting it aborts config parsing.
|
# 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 = lib.mkDefault [
|
||||||
|
"3, horizontal, workspace"
|
||||||
|
"4, pinch, fullscreen"
|
||||||
|
];
|
||||||
|
|
||||||
misc = {
|
misc = {
|
||||||
disable_hyprland_logo = true;
|
disable_hyprland_logo = lib.mkDefault true;
|
||||||
disable_splash_rendering = true;
|
disable_splash_rendering = lib.mkDefault true;
|
||||||
force_default_wallpaper = 0;
|
force_default_wallpaper = lib.mkDefault 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
bind = [
|
# Rendered from ./keybinds.nix (the cheatsheet reads the same list),
|
||||||
"$mod, Return, exec, $terminal"
|
# plus the generated per-workspace binds. Like exec-once above this
|
||||||
"$mod, D, exec, fuzzel"
|
# stays a normal-priority list, so a downstream `bind = [...]`
|
||||||
"$mod, Q, killactive"
|
# concatenates rather than replaces.
|
||||||
"$mod, F, fullscreen"
|
bind = map mkBind keybinds.binds ++ workspaceBinds;
|
||||||
"$mod, V, togglefloating"
|
|
||||||
"$mod SHIFT, E, exit"
|
|
||||||
|
|
||||||
# Theme picker: fuzzel menu over the presets; apply writes the
|
# Media keys via swayosd-client: it performs the action AND shows
|
||||||
# state and runs home-manager switch (progress via notify-send).
|
# the on-screen display (the nomarchy.osd module). e = repeat,
|
||||||
"$mod, T, exec, nomarchy-theme-sync apply \"$(nomarchy-theme-sync list | fuzzel --dmenu --prompt 'theme> ')\""
|
# l = also on a locked screen — the standard flags for hardware keys.
|
||||||
# Cycle the current theme's wallpapers (instant, no rebuild).
|
bindel = [
|
||||||
"$mod SHIFT, T, exec, nomarchy-theme-sync bg next"
|
", 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"
|
||||||
|
];
|
||||||
|
|
||||||
# Focus
|
bindl = [
|
||||||
"$mod, H, movefocus, l"
|
", XF86AudioMute, exec, swayosd-client --output-volume mute-toggle"
|
||||||
"$mod, L, movefocus, r"
|
# swayosd's input mute-toggle is broken on 0.2.1 (see micMute above).
|
||||||
"$mod, K, movefocus, u"
|
", XF86AudioMicMute, exec, ${micMute}"
|
||||||
"$mod, J, movefocus, d"
|
", XF86AudioPlay, exec, playerctl play-pause"
|
||||||
|
", XF86AudioPause, exec, playerctl play-pause"
|
||||||
# Screenshot region to clipboard
|
", XF86AudioNext, exec, playerctl next"
|
||||||
", Print, exec, grim -g \"$(slurp)\" - | wl-copy"
|
", XF86AudioPrev, exec, playerctl previous"
|
||||||
] ++ workspaceBinds;
|
|
||||||
|
|
||||||
binde = [
|
|
||||||
", XF86AudioRaiseVolume, exec, pamixer -i 5"
|
|
||||||
", XF86AudioLowerVolume, exec, pamixer -d 5"
|
|
||||||
", XF86AudioMute, exec, pamixer -t"
|
|
||||||
", XF86MonBrightnessUp, exec, brightnessctl set +5%"
|
|
||||||
", XF86MonBrightnessDown, exec, brightnessctl set 5%-"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
bindm = [
|
bindm = [
|
||||||
@@ -143,4 +265,12 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# nwg-displays: interactive monitor arranger (applies live via hyprctl and
|
||||||
|
# writes a config file). A helper to find values for nomarchy.monitors —
|
||||||
|
# the declarative config stays the source of truth (we don't source its
|
||||||
|
# output). Gated on its toggle; pointless without the Hyprland session.
|
||||||
|
home.packages = lib.optionals
|
||||||
|
(config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
||||||
|
[ pkgs.nwg-displays ];
|
||||||
}
|
}
|
||||||
|
|||||||
95
modules/home/idle.nix
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# 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, 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} || <action>` 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 {
|
||||||
|
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 = "<i>password…</i>";
|
||||||
|
}];
|
||||||
|
|
||||||
|
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";
|
||||||
|
# No before_sleep_cmd here: locking before sleep is driven
|
||||||
|
# system-side so it can fire for suspend but skip an encrypted
|
||||||
|
# hibernate — whose LUKS resume already gates the machine, so a
|
||||||
|
# hyprlock on top is a second password (and a Wayland session-lock
|
||||||
|
# can't be safely dropped after the fact — killing the locker trips
|
||||||
|
# its "go to a tty" crash failsafe). See nomarchy-lock-before-sleep
|
||||||
|
# in modules/nixos/default.nix.
|
||||||
|
after_sleep_cmd = "hyprctl dispatch dpms on";
|
||||||
|
};
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
# 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"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
68
modules/home/keybinds.nix
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# 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"; }
|
||||||
|
# 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+<mnemonic> 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 = "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"; }
|
||||||
|
{ mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; }
|
||||||
|
{ mods = "$mod CTRL"; key = "D"; action = "exec, nomarchy-menu dnd"; desc = "Do Not Disturb toggle"; }
|
||||||
|
|
||||||
|
# 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"; }
|
||||||
|
];
|
||||||
|
}
|
||||||
42
modules/home/keys.nix
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
25
modules/home/nightlight.nix
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
|
||||||
|
# gamma/temperature tool). Warm at night, identity (no shift) by day;
|
||||||
|
# hyprsunset's time-based `profile` entries handle the schedule and pick the
|
||||||
|
# right state on session start. Opt-in via nomarchy.nightlight.enable.
|
||||||
|
#
|
||||||
|
# The hyprsunset HM service module is provided by home-manager; this only
|
||||||
|
# configures it. Override anything with plain services.hyprsunset.* options.
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy.nightlight;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.hyprsunset = {
|
||||||
|
enable = true;
|
||||||
|
settings.profile = [
|
||||||
|
# Daytime: identity = no colour change.
|
||||||
|
{ time = cfg.sunrise; identity = true; }
|
||||||
|
# Night: shift to the warm temperature.
|
||||||
|
{ time = cfg.sunset; temperature = cfg.temperature; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,6 +2,60 @@
|
|||||||
# configure in their home.nix. Kept small on purpose.
|
# configure in their home.nix. Kept small on purpose.
|
||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# One output's layout — turned into a Hyprland `monitor` rule in
|
||||||
|
# hyprland.nix. Friendlier than raw positional CSV; unset optional fields
|
||||||
|
# are omitted from the rule.
|
||||||
|
monitorType = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "HDMI-A-1";
|
||||||
|
description = "Output name (see `hyprctl monitors`), or a `desc:<…>` match.";
|
||||||
|
};
|
||||||
|
resolution = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "preferred";
|
||||||
|
description = "`preferred` | `highres` | `highrr` | `<w>x<h>@<hz>` | `disable`.";
|
||||||
|
};
|
||||||
|
position = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "auto";
|
||||||
|
description = "`auto` | `auto-right`/`auto-left`/… | `<x>x<y>`.";
|
||||||
|
};
|
||||||
|
scale = lib.mkOption {
|
||||||
|
type = lib.types.oneOf [ lib.types.str lib.types.int lib.types.float ];
|
||||||
|
default = 1;
|
||||||
|
description = "Scale factor (1, 1.5, …) or `auto`.";
|
||||||
|
};
|
||||||
|
transform = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Rotation 0–7 (90°/180°/270° = 1/2/3).";
|
||||||
|
};
|
||||||
|
mirror = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Mirror another output by name.";
|
||||||
|
};
|
||||||
|
bitdepth = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Colour bit depth (8 or 10).";
|
||||||
|
};
|
||||||
|
vrr = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.int;
|
||||||
|
default = null;
|
||||||
|
description = "Variable refresh: 0 off, 1 on, 2 fullscreen-only.";
|
||||||
|
};
|
||||||
|
extra = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Extra comma-separated Hyprland monitor args, appended verbatim.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
options.nomarchy = {
|
options.nomarchy = {
|
||||||
# ── Required ───────────────────────────────────────────────────
|
# ── Required ───────────────────────────────────────────────────
|
||||||
@@ -23,6 +77,68 @@
|
|||||||
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
|
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
keyboard.layout = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "us";
|
||||||
|
example = "de";
|
||||||
|
description = ''
|
||||||
|
XKB layout for the Hyprland session. The console (and the LUKS
|
||||||
|
passphrase prompt) take theirs from the system side — the
|
||||||
|
installer writes services.xserver.xkb + console.useXkbConfig
|
||||||
|
into system.nix with the same value.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
keyboard.variant = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
example = "nodeadkeys";
|
||||||
|
description = "XKB variant for the Hyprland session.";
|
||||||
|
};
|
||||||
|
|
||||||
|
keyboard.layouts = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [ "de" "fr" ];
|
||||||
|
description = ''
|
||||||
|
Extra candidate layouts offered by the interactive new-keyboard
|
||||||
|
picker. When non-empty, a small watcher runs in the session: when a
|
||||||
|
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
||||||
|
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
||||||
|
the primary nomarchy.keyboard.layout), applies the choice, and
|
||||||
|
remembers it per-device (~/.local/state) — re-applying automatically
|
||||||
|
on later reconnects. The runtime-remember complement to the
|
||||||
|
declarative keyboard.devices; a stateful runtime piece by design.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
keyboard.devices = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
layout = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "XKB layout for this specific keyboard.";
|
||||||
|
};
|
||||||
|
variant = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "XKB variant for this keyboard.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
|
||||||
|
description = ''
|
||||||
|
Per-device keyboard layout. The key is the device name from
|
||||||
|
`hyprctl devices` (lower-case, hyphenated). Each entry generates a
|
||||||
|
Hyprland `device` block that overrides the session-wide
|
||||||
|
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
||||||
|
keyboard that's physically a different layout than the laptop's
|
||||||
|
built-in one. Hyprland applies it automatically whenever that
|
||||||
|
keyboard is connected.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
default = pkgs.nomarchy-theme-sync;
|
default = pkgs.nomarchy-theme-sync;
|
||||||
@@ -41,12 +157,69 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nightlight = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
a scheduled blue-light filter (hyprsunset): warm at night, no shift
|
||||||
|
by day. Opt-in; tune the temperature + sunrise/sunset below'';
|
||||||
|
|
||||||
|
temperature = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 4000;
|
||||||
|
example = 3500;
|
||||||
|
description = "Warm colour temperature (K) applied at night — lower is warmer.";
|
||||||
|
};
|
||||||
|
|
||||||
|
sunrise = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "07:00";
|
||||||
|
example = "06:30";
|
||||||
|
description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift.";
|
||||||
|
};
|
||||||
|
|
||||||
|
sunset = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "20:00";
|
||||||
|
example = "21:00";
|
||||||
|
description = "Time (HH:MM) the filter turns ON — warm.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
monitors = lib.mkOption {
|
||||||
|
type = lib.types.listOf monitorType;
|
||||||
|
default = [ ];
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
{ name = "eDP-1"; position = "0x0"; }
|
||||||
|
{ name = "HDMI-A-1"; position = "auto-right"; scale = 1; }
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Declarative per-output monitor layout. Each entry becomes a Hyprland
|
||||||
|
`monitor` rule; Hyprland applies them on hotplug, so a declared
|
||||||
|
external/dock output arranges itself when connected. The built-in
|
||||||
|
`,preferred,auto,1` wildcard stays as the fallback for any output you
|
||||||
|
don't list. For raw control set
|
||||||
|
`wayland.windowManager.hyprland.settings.monitor` directly instead
|
||||||
|
(it replaces this). Run `nwg-displays` (nomarchy.displays.enable) to
|
||||||
|
find the right values interactively.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# ── Component toggles ──────────────────────────────────────────
|
# ── Component toggles ──────────────────────────────────────────
|
||||||
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
||||||
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
||||||
|
rofi.enable = lib.mkEnableOption "Nomarchy's themed rofi launcher + the nomarchy-menu dispatcher" // { default = true; };
|
||||||
|
swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; };
|
||||||
|
idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; };
|
||||||
|
yazi.enable = lib.mkEnableOption "the yazi TUI file manager, themed with a curated plugin set" // { default = true; };
|
||||||
|
osd.enable = lib.mkEnableOption "swayosd on-screen display for volume/brightness/mute" // { default = true; };
|
||||||
|
shell.enable = lib.mkEnableOption "the zsh shell experience (starship prompt, bat/eza/zoxide)" // { default = true; };
|
||||||
|
keys.enable = lib.mkEnableOption "the SSH + GPG agent (gpg-agent fronting SSH, pinentry-qt)" // { default = true; };
|
||||||
|
fastfetch.enable = lib.mkEnableOption "fastfetch system info fronted by the themed Nomarchy logo" // { default = true; };
|
||||||
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
|
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
|
||||||
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
|
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
|
||||||
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
|
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
|
||||||
|
displays.enable = lib.mkEnableOption "the nwg-displays interactive monitor arranger (a helper to find nomarchy.monitors values; the declarative config stays the source of truth)" // { default = true; };
|
||||||
|
|
||||||
# ── Computed (read-only) ───────────────────────────────────────
|
# ── Computed (read-only) ───────────────────────────────────────
|
||||||
theme = lib.mkOption {
|
theme = lib.mkOption {
|
||||||
|
|||||||
55
modules/home/osd.nix
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
391
modules/home/rofi.nix
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
# 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/<slug>/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;
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
# hyprctl and friends. Menu entries carry real icons from the theme's
|
||||||
|
# icon set (Papirus) via rofi's per-row icon protocol — see row() below.
|
||||||
|
# `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"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Emit one dmenu row with a themed icon: `row "Label" icon-name`. The NUL
|
||||||
|
# + 0x1f field separator is rofi's per-row icon protocol; printed straight
|
||||||
|
# to the pipe because bash can't store NUL in a variable. Rendered when
|
||||||
|
# rofi has show-icons (on globally; passed explicitly on the icon menus).
|
||||||
|
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
|
||||||
|
|
||||||
|
case "''${1:-}" in
|
||||||
|
power)
|
||||||
|
choice=$( {
|
||||||
|
row "Lock" system-lock-screen
|
||||||
|
row "Logout" system-log-out
|
||||||
|
row "Suspend" system-suspend
|
||||||
|
row "Hibernate" system-hibernate
|
||||||
|
row "Reboot" system-reboot
|
||||||
|
row "Shutdown" system-shutdown
|
||||||
|
} | rofi -dmenu -show-icons -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 ;;
|
||||||
|
|
||||||
|
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" ;;
|
||||||
|
|
||||||
|
clipboard)
|
||||||
|
sel=$(cliphist list | rofi -dmenu -p clip) || exit 0
|
||||||
|
printf '%s' "$sel" | cliphist decode | wl-copy ;;
|
||||||
|
|
||||||
|
calc)
|
||||||
|
# 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" ;;
|
||||||
|
|
||||||
|
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
|
||||||
|
[ -n "$q" ] || exit 0
|
||||||
|
exec xdg-open "https://www.google.com/search?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=$( {
|
||||||
|
row "Region → clipboard" applets-screenshooter
|
||||||
|
row "Region → file" applets-screenshooter
|
||||||
|
row "Full screen → clipboard" camera-photo
|
||||||
|
row "Full screen → file" camera-photo
|
||||||
|
} | rofi -dmenu -show-icons -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" ;;
|
||||||
|
|
||||||
|
dnd)
|
||||||
|
# Toggle swaync Do-Not-Disturb (-d prints the new state). With DND
|
||||||
|
# on, notifications are suppressed — the Waybar bell-off icon is the
|
||||||
|
# cue — so only confirm with a toast when turning it back off.
|
||||||
|
[ "$(swaync-client -d)" = "false" ] \
|
||||||
|
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
||||||
|
exit 0 ;;
|
||||||
|
|
||||||
|
snapshot)
|
||||||
|
# btrfs-assistant: snapshot browse / diff / restore / rollback over
|
||||||
|
# snapper, elevating via polkit. Shipped system-side with
|
||||||
|
# nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
|
||||||
|
command -v btrfs-assistant >/dev/null 2>&1 \
|
||||||
|
|| { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
|
||||||
|
exec btrfs-assistant ;;
|
||||||
|
|
||||||
|
tools)
|
||||||
|
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
||||||
|
# directly (SUPER+CTRL+<mnemonic>); this just keeps the root short.
|
||||||
|
choice=$( {
|
||||||
|
row "Calculator" accessories-calculator
|
||||||
|
row "Clipboard" edit-paste
|
||||||
|
row "Emoji" face-smile-big
|
||||||
|
row "Files" system-file-manager
|
||||||
|
row "Web search" system-search
|
||||||
|
row "Capture" applets-screenshooter
|
||||||
|
row "Ask Claude" internet-chat
|
||||||
|
row "Back" go-previous
|
||||||
|
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
||||||
|
case "$choice" in
|
||||||
|
*Calc*) exec "$0" calc ;;
|
||||||
|
*Clipboard*) exec "$0" clipboard ;;
|
||||||
|
*Emoji*) exec "$0" emoji ;;
|
||||||
|
*Files*) exec "$0" files ;;
|
||||||
|
*Web*) exec "$0" web ;;
|
||||||
|
*Capture*) exec "$0" capture ;;
|
||||||
|
*Ask*) exec "$0" ask ;;
|
||||||
|
*Back*) exec "$0" ;;
|
||||||
|
esac ;;
|
||||||
|
|
||||||
|
system)
|
||||||
|
# System submenu — connectivity and machine state. Snapshots and the
|
||||||
|
# power-profile picker self-gate (BTRFS+snapper / laptop on ppd), so
|
||||||
|
# the submenu shrinks to match the hardware, like the Waybar modules.
|
||||||
|
bats=(/sys/class/power_supply/BAT*)
|
||||||
|
choice=$( {
|
||||||
|
row "Network" network-wireless
|
||||||
|
row "Bluetooth" bluetooth
|
||||||
|
row "Do Not Disturb" notification-disabled
|
||||||
|
command -v btrfs-assistant >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||||
|
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||||
|
row "Power profile" preferences-system-power
|
||||||
|
fi
|
||||||
|
row "Back" go-previous
|
||||||
|
} | rofi -dmenu -show-icons -p System) || exit 0
|
||||||
|
case "$choice" in
|
||||||
|
*Network*) exec "$0" network ;;
|
||||||
|
*Bluetooth*) exec "$0" bluetooth ;;
|
||||||
|
*"Do Not Disturb"*) exec "$0" dnd ;;
|
||||||
|
*Snapshots*) exec "$0" snapshot ;;
|
||||||
|
*"Power profile"*) exec "$0" power-profile ;;
|
||||||
|
*Back*) exec "$0" ;;
|
||||||
|
esac ;;
|
||||||
|
|
||||||
|
"")
|
||||||
|
# Root picker — grouped into category submenus so the top level stays
|
||||||
|
# short and scannable. Apps/Theme/Power/Keybindings are frequent enough
|
||||||
|
# to keep at the top; everything else lives under Tools or System.
|
||||||
|
choice=$( {
|
||||||
|
row "Apps" applications-all
|
||||||
|
row "Theme" preferences-desktop-theme
|
||||||
|
row "Tools" applications-utilities
|
||||||
|
row "System" preferences-system
|
||||||
|
row "Power" system-shutdown
|
||||||
|
row "Keybindings" preferences-desktop-keyboard
|
||||||
|
} | rofi -dmenu -show-icons -p Menu) || exit 0
|
||||||
|
case "$choice" in
|
||||||
|
*Apps*) exec rofi -show drun ;;
|
||||||
|
*Theme*) exec "$0" theme ;;
|
||||||
|
*Tools*) exec "$0" tools ;;
|
||||||
|
*System*) exec "$0" system ;;
|
||||||
|
*Power*) exec "$0" power ;;
|
||||||
|
*Keybindings*) exec "$0" keybinds ;;
|
||||||
|
esac ;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask|dnd|snapshot]" >&2
|
||||||
|
exit 64 ;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf cfg.rofi.enable {
|
||||||
|
home.packages = [
|
||||||
|
nomarchy-menu
|
||||||
|
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)
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.rofi = {
|
||||||
|
enable = true;
|
||||||
|
terminal = cfg.terminal;
|
||||||
|
font = "${t.fonts.ui} 12";
|
||||||
|
|
||||||
|
# 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";
|
||||||
|
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";
|
||||||
|
# Size the window to the actual number of rows instead of always
|
||||||
|
# reserving `lines` (8) — so a 6-entry menu doesn't leave empty space,
|
||||||
|
# while the app launcher still fills + scrolls past `lines`.
|
||||||
|
fixed-num-lines = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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 4; # a little air so the rounded zebra rows separate
|
||||||
|
padding = mkLiteral "8px 0px 0px 0px";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Rounded, roomy rows with subtle zebra striping: alternate rows
|
||||||
|
# lift to @surface, the selected row to @accent. normal/alternate/
|
||||||
|
# selected are rofi's row parities (the `.normal` suffix is the
|
||||||
|
# match-state, untouched by our menus).
|
||||||
|
"element" = {
|
||||||
|
padding = mkLiteral "10px 14px";
|
||||||
|
border-radius = px t.ui.rounding;
|
||||||
|
spacing = px 12; # gap between the (larger) icon and the label
|
||||||
|
};
|
||||||
|
"element normal.normal" = {
|
||||||
|
background-color = mkLiteral "@bg";
|
||||||
|
text-color = mkLiteral "@fg";
|
||||||
|
};
|
||||||
|
"element alternate.normal" = {
|
||||||
|
background-color = mkLiteral "@surface";
|
||||||
|
text-color = mkLiteral "@fg";
|
||||||
|
};
|
||||||
|
"element selected.normal" = {
|
||||||
|
background-color = mkLiteral "@accent";
|
||||||
|
text-color = mkLiteral "@bg";
|
||||||
|
};
|
||||||
|
"element-icon" = {
|
||||||
|
background-color = mkLiteral "transparent";
|
||||||
|
size = px t.ui.iconSize; # real icons (Papirus), not 1em font glyphs
|
||||||
|
};
|
||||||
|
"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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
157
modules/home/shell.nix
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# 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;
|
||||||
|
};
|
||||||
|
# Curated, on by default. The rule (same as rg/fd above): short where
|
||||||
|
# it helps, but never shadow a real binary — so the git block keeps off
|
||||||
|
# `gs` (ghostscript), and nothing here masks a coreutil.
|
||||||
|
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";
|
||||||
|
|
||||||
|
# Navigation — climb without counting dots.
|
||||||
|
".." = "cd ..";
|
||||||
|
"..." = "cd ../..";
|
||||||
|
"...." = "cd ../../..";
|
||||||
|
|
||||||
|
# Git — the verbs you retype all day.
|
||||||
|
g = "git";
|
||||||
|
gst = "git status -sb";
|
||||||
|
ga = "git add";
|
||||||
|
gaa = "git add -A";
|
||||||
|
gc = "git commit";
|
||||||
|
gcm = "git commit -m";
|
||||||
|
gco = "git checkout";
|
||||||
|
gsw = "git switch";
|
||||||
|
gb = "git branch";
|
||||||
|
gd = "git diff";
|
||||||
|
gds = "git diff --staged";
|
||||||
|
gl = "git log --oneline --graph --decorate -20";
|
||||||
|
glg = "git log --oneline --graph --decorate --all";
|
||||||
|
gp = "git push";
|
||||||
|
gpl = "git pull";
|
||||||
|
gf = "git fetch --all --prune";
|
||||||
|
|
||||||
|
# Nix — the flake/store verbs (system/home rebuilds already have the
|
||||||
|
# sys-update / home-update commands, so they're not re-aliased here).
|
||||||
|
ns = "nix shell"; # ns nixpkgs#ripgrep
|
||||||
|
nr = "nix run"; # nr nixpkgs#cowsay
|
||||||
|
nfu = "nix flake update";
|
||||||
|
nfc = "nix flake check";
|
||||||
|
nsearch = "nix search nixpkgs";
|
||||||
|
ngc = "nix-collect-garbage -d"; # user generations; sudo for system roots
|
||||||
|
|
||||||
|
# Misc.
|
||||||
|
path = "echo $PATH | tr ':' '\\n'";
|
||||||
|
reload = "exec zsh";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
# 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 [
|
||||||
|
"$directory"
|
||||||
|
"$git_branch"
|
||||||
|
"$git_status"
|
||||||
|
"$cmd_duration"
|
||||||
|
"$character"
|
||||||
|
];
|
||||||
|
directory = {
|
||||||
|
style = "bold accent";
|
||||||
|
format = "[$path]($style) ";
|
||||||
|
truncation_length = 3;
|
||||||
|
truncate_to_repo = true;
|
||||||
|
};
|
||||||
|
git_branch = {
|
||||||
|
style = "warn";
|
||||||
|
format = "[$symbol$branch]($style) ";
|
||||||
|
symbol = " ";
|
||||||
|
};
|
||||||
|
git_status = {
|
||||||
|
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)";
|
||||||
|
};
|
||||||
|
cmd_duration = {
|
||||||
|
min_time = 2000;
|
||||||
|
style = "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)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -55,6 +55,17 @@ in
|
|||||||
size = lib.mkDefault 24;
|
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 = {
|
fonts = {
|
||||||
monospace = {
|
monospace = {
|
||||||
name = t.fonts.mono;
|
name = t.fonts.mono;
|
||||||
@@ -75,5 +86,14 @@ in
|
|||||||
sizes.terminal = t.fonts.size;
|
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";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
92
modules/home/swaync.nix
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -38,17 +38,45 @@ let
|
|||||||
gapsOut = 12;
|
gapsOut = 12;
|
||||||
borderSize = 2;
|
borderSize = 2;
|
||||||
rounding = 10;
|
rounding = 10;
|
||||||
|
iconSize = 36; # rofi launcher + menu icon size (px) for the generated theme
|
||||||
activeOpacity = 1.0;
|
activeOpacity = 1.0;
|
||||||
inactiveOpacity = 0.95;
|
inactiveOpacity = 0.95;
|
||||||
terminalOpacity = 0.96;
|
terminalOpacity = 0.96;
|
||||||
blur = true;
|
blur = true;
|
||||||
shadow = 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 = "";
|
||||||
|
# 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.
|
||||||
|
iconTheme =
|
||||||
|
if parsed.icons != "" then parsed.icons
|
||||||
|
else if parsed.mode == "light" then "Papirus-Light"
|
||||||
|
else "Papirus-Dark";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = {
|
config = {
|
||||||
nomarchy.theme = lib.recursiveUpdate defaults themeState;
|
nomarchy.theme = parsed // { inherit iconTheme border; };
|
||||||
|
|
||||||
nomarchy.lib = {
|
nomarchy.lib = {
|
||||||
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
|
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
|
||||||
|
|||||||
@@ -7,11 +7,50 @@
|
|||||||
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
||||||
# replace the generated style/layout entirely. Probed at eval time —
|
# replace the generated style/layout entirely. Probed at eval time —
|
||||||
# pure, it's flake source.
|
# pure, it's flake source.
|
||||||
{ config, lib, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
t = config.nomarchy.theme;
|
t = config.nomarchy.theme;
|
||||||
|
|
||||||
|
# Show the active-keyboard-layout indicator only when more than one layout
|
||||||
|
# is in play — multiple session layouts (comma-separated) or per-device
|
||||||
|
# overrides (nomarchy.keyboard.devices) — so single-layout bars stay clean.
|
||||||
|
showLanguage = lib.hasInfix "," config.nomarchy.keyboard.layout
|
||||||
|
|| config.nomarchy.keyboard.layouts != [ ]
|
||||||
|
|| config.nomarchy.keyboard.devices != { };
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Named writeShellScriptBins (put on PATH via home.packages below) rather
|
||||||
|
# than bare writeShellScript store paths, so the whole-swap themes' static
|
||||||
|
# waybar.jsonc can exec them by name too — same as the swaync bell.
|
||||||
|
powerProfileStatus = pkgs.writeShellScriptBin "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.writeShellScriptBin "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.
|
# Per-theme override probe.
|
||||||
assetDir = config.nomarchy.themesDir + "/${t.slug}";
|
assetDir = config.nomarchy.themesDir + "/${t.slug}";
|
||||||
styleOverride = assetDir + "/waybar.css";
|
styleOverride = assetDir + "/waybar.css";
|
||||||
@@ -38,7 +77,9 @@ let
|
|||||||
|
|
||||||
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||||
modules-center = [ "clock" ];
|
modules-center = [ "clock" ];
|
||||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "battery" ];
|
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" ]
|
||||||
|
++ lib.optional showLanguage "hyprland/language"
|
||||||
|
++ [ "battery" "custom/notification" ];
|
||||||
|
|
||||||
"hyprland/workspaces" = {
|
"hyprland/workspaces" = {
|
||||||
format = "{icon}";
|
format = "{icon}";
|
||||||
@@ -56,11 +97,18 @@ let
|
|||||||
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Active keyboard layout (per focused device) — only placed in
|
||||||
|
# modules-right when showLanguage (see above).
|
||||||
|
"hyprland/language" = {
|
||||||
|
format = " {short}";
|
||||||
|
tooltip = false;
|
||||||
|
};
|
||||||
|
|
||||||
pulseaudio = {
|
pulseaudio = {
|
||||||
format = "{icon} {volume}%";
|
format = "{icon} {volume}%";
|
||||||
format-muted = "";
|
format-muted = "";
|
||||||
format-icons.default = [ "" "" "" ];
|
format-icons.default = [ "" "" "" ];
|
||||||
on-click = "pamixer -t";
|
on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
||||||
};
|
};
|
||||||
|
|
||||||
network = {
|
network = {
|
||||||
@@ -68,6 +116,9 @@ let
|
|||||||
format-ethernet = "";
|
format-ethernet = "";
|
||||||
format-disconnected = "";
|
format-disconnected = "";
|
||||||
tooltip-format = "{ipaddr} via {gwaddr}";
|
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}%";
|
cpu.format = " {usage}%";
|
||||||
@@ -80,6 +131,38 @@ let
|
|||||||
format-icons = [ "" "" "" "" "" ];
|
format-icons = [ "" "" "" "" "" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"custom/powerprofile" = {
|
||||||
|
exec = "nomarchy-powerprofile-status";
|
||||||
|
return-type = "json";
|
||||||
|
interval = 5;
|
||||||
|
on-click = "nomarchy-powerprofile-cycle";
|
||||||
|
};
|
||||||
|
|
||||||
|
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
||||||
|
# (text/tooltip/class) on every change, so it tracks count and DND with
|
||||||
|
# no polling. Left-click toggles the panel; right-click toggles DND.
|
||||||
|
# The `dnd-*` classes (bell-off glyph) are styled muted below.
|
||||||
|
"custom/notification" = {
|
||||||
|
exec = "swaync-client -swb";
|
||||||
|
return-type = "json";
|
||||||
|
exec-if = "which swaync-client";
|
||||||
|
format = "{icon}";
|
||||||
|
format-icons = {
|
||||||
|
none = "";
|
||||||
|
notification = "";
|
||||||
|
dnd-none = "";
|
||||||
|
dnd-notification = "";
|
||||||
|
inhibited-none = "";
|
||||||
|
inhibited-notification = "";
|
||||||
|
dnd-inhibited-none = "";
|
||||||
|
dnd-inhibited-notification = "";
|
||||||
|
};
|
||||||
|
on-click = "swaync-client -t -sw";
|
||||||
|
on-click-right = "swaync-client -d -sw";
|
||||||
|
tooltip = true;
|
||||||
|
escape = true;
|
||||||
|
};
|
||||||
|
|
||||||
tray.spacing = 8;
|
tray.spacing = 8;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,11 +209,16 @@ let
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tray, #pulseaudio, #network, #cpu, #memory, #battery {
|
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #language, #battery, #custom-notification {
|
||||||
color: @subtext;
|
color: @subtext;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* notifications waiting → accent; Do-Not-Disturb → muted bell-off */
|
||||||
|
#custom-notification.notification { color: @accent; }
|
||||||
|
#custom-notification.dnd-none,
|
||||||
|
#custom-notification.dnd-notification { color: @muted; }
|
||||||
|
|
||||||
#pulseaudio.muted { color: @muted; }
|
#pulseaudio.muted { color: @muted; }
|
||||||
#battery.warning { color: @warn; }
|
#battery.warning { color: @warn; }
|
||||||
#battery.critical { color: @bad; }
|
#battery.critical { color: @bad; }
|
||||||
@@ -142,14 +230,26 @@ in
|
|||||||
enable = true;
|
enable = true;
|
||||||
systemd.enable = true; # started/stopped with graphical-session.target
|
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/<slug>/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
|
if hasConfigOverride
|
||||||
then builtins.fromJSON (builtins.readFile configOverride)
|
then builtins.fromJSON (builtins.readFile configOverride)
|
||||||
else generatedSettings;
|
else generatedSettings
|
||||||
|
);
|
||||||
|
|
||||||
style =
|
style = lib.mkDefault (
|
||||||
if hasStyleOverride
|
if hasStyleOverride
|
||||||
then builtins.readFile styleOverride
|
then builtins.readFile styleOverride
|
||||||
else generatedStyle;
|
else generatedStyle
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The power-profile helpers on PATH, so both the generated bar and the
|
||||||
|
# whole-swap themes' static waybar.jsonc can exec them by name.
|
||||||
|
home.packages = lib.optionals config.nomarchy.waybar.enable
|
||||||
|
[ powerProfileStatus powerProfileCycle ];
|
||||||
}
|
}
|
||||||
|
|||||||
133
modules/home/yazi.nix
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# yazi — the flagship file manager: a fast, keyboard-driven TUI that
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# `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).
|
||||||
|
# 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"; 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
|
||||||
|
# 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 = [
|
||||||
|
{ url = "*.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 = "<Enter>"; 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; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
modules/nixos/branding/Nomarchy.ttf
Normal file
64
modules/nixos/branding/logo.svg
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="157.40488mm"
|
||||||
|
height="162.48518mm"
|
||||||
|
viewBox="0 0 157.40488 162.48518"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:export-filename="logo.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="0.82803284"
|
||||||
|
inkscape:cx="216.175"
|
||||||
|
inkscape:cy="452.27675"
|
||||||
|
inkscape:window-width="1914"
|
||||||
|
inkscape:window-height="1012"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1"><inkscape:page
|
||||||
|
x="0"
|
||||||
|
y="-1.1741086e-21"
|
||||||
|
width="157.40488"
|
||||||
|
height="162.48518"
|
||||||
|
id="page2"
|
||||||
|
margin="0"
|
||||||
|
bleed="0" /></sodipodi:namedview><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-28.332214,-25.599269)"><g
|
||||||
|
id="g4"
|
||||||
|
transform="translate(3.1953242,-22.686801)"
|
||||||
|
style="fill:#000088;fill-opacity:1"><path
|
||||||
|
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="M 25.136891,85.823024 25.557592,210.77125 88.452409,174.38061 67.417351,160.49747 57.110174,166.38729 V 105.80633 Z"
|
||||||
|
id="path1" /><path
|
||||||
|
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="M 67.728991,112.41131 182.54178,185.60757 153.16137,202.85259 67.830432,148.17947 Z"
|
||||||
|
id="path2" /><path
|
||||||
|
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="M 139.74857,145.88014 140.00405,110.4959 54.800856,56.333749 25.675926,73.32329 Z"
|
||||||
|
id="path3" /><path
|
||||||
|
style="fill:#000088;fill-opacity:1;stroke-width:0.264583"
|
||||||
|
d="M 182.2863,172.70573 V 48.286069 l -62.59305,36.406166 20.82177,13.668277 10.21927,-5.74834 0.12774,60.165978 z"
|
||||||
|
id="path4" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -9,11 +9,66 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.nomarchy.system;
|
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;
|
||||||
|
|
||||||
|
# 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
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./options.nix ];
|
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ];
|
||||||
|
|
||||||
config = {
|
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";
|
||||||
|
|
||||||
|
# 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 <theme> 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
|
||||||
|
# `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 ────────────────────────────────────
|
# ── Wayland session: Hyprland ────────────────────────────────────
|
||||||
# Installs the binary, registers the session, wires up
|
# Installs the binary, registers the session, wires up
|
||||||
# xdg-desktop-portal-hyprland. Configuration is Home Manager's job.
|
# xdg-desktop-portal-hyprland. Configuration is Home Manager's job.
|
||||||
@@ -28,12 +83,14 @@ in
|
|||||||
enable = lib.mkDefault true;
|
enable = lib.mkDefault true;
|
||||||
settings = {
|
settings = {
|
||||||
default_session = {
|
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 --greeting 'Welcome to ${distroName}' --cmd start-hyprland";
|
||||||
user = "greeter";
|
user = "greeter";
|
||||||
};
|
};
|
||||||
# Boot straight into the session once; logout → normal greeter.
|
# Boot straight into the session once; logout → normal greeter.
|
||||||
initial_session = lib.mkIf (cfg.greeter.autoLogin != null) {
|
initial_session = lib.mkIf (cfg.greeter.autoLogin != null) {
|
||||||
command = "Hyprland";
|
command = "start-hyprland";
|
||||||
user = cfg.greeter.autoLogin;
|
user = cfg.greeter.autoLogin;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -55,17 +112,80 @@ in
|
|||||||
services.gnome.gnome-keyring.enable = lib.mkDefault true;
|
services.gnome.gnome-keyring.enable = lib.mkDefault true;
|
||||||
services.dbus.enable = lib.mkDefault true;
|
services.dbus.enable = lib.mkDefault true;
|
||||||
services.upower.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;
|
networking.networkmanager.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
# No double-unlock on hibernate. Locking the session before sleep is
|
||||||
|
# right for suspend (resumes from RAM, no other gate), but an encrypted
|
||||||
|
# hibernate already resumes through the LUKS passphrase — a hyprlock on
|
||||||
|
# top is a second password. And we can't just drop the lock after the
|
||||||
|
# resume: a Wayland session-lock whose client dies without releasing
|
||||||
|
# trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the
|
||||||
|
# screen locked for safety). So don't lock before an encrypted hibernate
|
||||||
|
# in the first place. hypridle can't tell suspend from hibernate, hence a
|
||||||
|
# system unit hooked to the sleep targets: it locks on the RAM-resume
|
||||||
|
# sleeps always, and on hibernate only when the disk is unencrypted (no
|
||||||
|
# LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd.
|
||||||
|
systemd.services.nomarchy-lock-before-sleep =
|
||||||
|
let
|
||||||
|
encrypted = builtins.attrNames config.boot.initrd.luks.devices != [ ];
|
||||||
|
# Sleeps whose normal resume is from RAM — always lock these.
|
||||||
|
ramTargets = [ "suspend.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
|
||||||
|
ramServices = [ "systemd-suspend.service" "systemd-hybrid-sleep.service" "systemd-suspend-then-hibernate.service" ];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
description = "Lock the session before sleep (skipped for an encrypted hibernate)";
|
||||||
|
before = ramServices ++ lib.optional (!encrypted) "systemd-hibernate.service";
|
||||||
|
wantedBy = ramTargets ++ lib.optional (!encrypted) "hibernate.target";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
# Plural: a system unit has no session of its own to lock.
|
||||||
|
ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# 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.<n>.shell`
|
||||||
|
# overrides this regardless.
|
||||||
|
users.defaultUserShell = lib.mkOverride 500 pkgs.zsh;
|
||||||
|
|
||||||
hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable;
|
hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable;
|
||||||
services.blueman.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
|
||||||
|
# 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) ─
|
# ── BTRFS timeline snapshots (ported from the previous iteration) ─
|
||||||
# Guarded on the actual filesystem so enabling it on an ext4 machine
|
# Guarded on the actual filesystem so enabling it on an ext4 machine
|
||||||
# is a clean no-op rather than a failing timer.
|
# is a clean no-op rather than a failing timer.
|
||||||
services.snapper.configs = lib.mkIf
|
services.snapper.configs = lib.mkIf
|
||||||
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
|
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
|
||||||
{
|
({
|
||||||
root = {
|
root = {
|
||||||
SUBVOLUME = "/";
|
SUBVOLUME = "/";
|
||||||
TIMELINE_CREATE = true;
|
TIMELINE_CREATE = true;
|
||||||
@@ -76,15 +196,67 @@ in
|
|||||||
TIMELINE_LIMIT_MONTHLY = "0";
|
TIMELINE_LIMIT_MONTHLY = "0";
|
||||||
TIMELINE_LIMIT_YEARLY = "0";
|
TIMELINE_LIMIT_YEARLY = "0";
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
# /home is user data — snapshot it too when it's its own BTRFS subvolume
|
||||||
|
# (the installer's @home). Keep the recent hourlies for oops-recovery
|
||||||
|
# plus dailies, and a month of weeklies; "at least daily" by default.
|
||||||
|
// lib.optionalAttrs ((config.fileSystems."/home".fsType or "") == "btrfs") {
|
||||||
|
home = {
|
||||||
|
SUBVOLUME = "/home";
|
||||||
|
TIMELINE_CREATE = true;
|
||||||
|
TIMELINE_CLEANUP = true;
|
||||||
|
TIMELINE_LIMIT_HOURLY = "5";
|
||||||
|
TIMELINE_LIMIT_DAILY = "7";
|
||||||
|
TIMELINE_LIMIT_WEEKLY = "4";
|
||||||
|
TIMELINE_LIMIT_MONTHLY = "0";
|
||||||
|
TIMELINE_LIMIT_YEARLY = "0";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
# snapper needs a `.snapshots` subvolume inside each tracked subvolume.
|
||||||
|
# The installer made one for / (@snapshots → /.snapshots) but not /home,
|
||||||
|
# so create a nested one there if missing (idempotent via the condition;
|
||||||
|
# excluded from /home snapshots since BTRFS snapshots are non-recursive).
|
||||||
|
# Runs at boot so it also fixes already-installed machines — no disko
|
||||||
|
# change needed.
|
||||||
|
systemd.services.nomarchy-home-snapshots-subvol = lib.mkIf
|
||||||
|
(cfg.snapper.enable && (config.fileSystems."/home".fsType or "") == "btrfs") {
|
||||||
|
description = "Create /home/.snapshots for snapper if missing";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = [ "snapper-timeline.service" "snapper-cleanup.service" ];
|
||||||
|
unitConfig.ConditionPathExists = "!/home/.snapshots";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs subvolume create /home/.snapshots";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# ── Fonts ────────────────────────────────────────────────────────
|
# ── 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 = {
|
fonts = {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
nerd-fonts.jetbrains-mono
|
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
|
inter
|
||||||
noto-fonts
|
noto-fonts
|
||||||
noto-fonts-color-emoji
|
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
|
||||||
|
# The Nomarchy logo glyph (U+F000) for the summer menu button.
|
||||||
|
nomarchyLogoFont
|
||||||
];
|
];
|
||||||
fontconfig.defaultFonts = {
|
fontconfig.defaultFonts = {
|
||||||
monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ];
|
monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ];
|
||||||
@@ -96,6 +268,31 @@ in
|
|||||||
# ── Essential packages ───────────────────────────────────────────
|
# ── Essential packages ───────────────────────────────────────────
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
nomarchy-theme-sync # provided by overlays.default
|
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
|
git
|
||||||
vim
|
vim
|
||||||
wget
|
wget
|
||||||
@@ -121,13 +318,29 @@ in
|
|||||||
--cleanup-algorithm number
|
--cleanup-algorithm number
|
||||||
echo "Rebuilding..."
|
echo "Rebuilding..."
|
||||||
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
||||||
'');
|
'')
|
||||||
|
# The desktop snapshot manager (browse / diff / restore / rollback over
|
||||||
|
# snapper, elevating via polkit) — what `nomarchy-menu snapshot` launches.
|
||||||
|
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant;
|
||||||
|
|
||||||
|
# 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 itself ───────────────────────────────────────────────────
|
||||||
nix = {
|
nix = {
|
||||||
settings = {
|
settings = {
|
||||||
experimental-features = [ "nix-command" "flakes" ];
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
auto-optimise-store = lib.mkDefault true;
|
auto-optimise-store = lib.mkDefault true;
|
||||||
|
# The downstream flake (~/.nomarchy) is meant to be a live working
|
||||||
|
# tree: nomarchy-theme-sync rewrites theme-state.json on every
|
||||||
|
# switch (and you needn't commit each tweak), so the "Git tree is
|
||||||
|
# dirty" warning fires on every rebuild and is pure noise here.
|
||||||
|
warn-dirty = lib.mkDefault false;
|
||||||
};
|
};
|
||||||
gc = {
|
gc = {
|
||||||
automatic = lib.mkDefault true;
|
automatic = lib.mkDefault true;
|
||||||
|
|||||||
35
modules/nixos/file-manager.nix
Normal file
@@ -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; [
|
||||||
|
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)
|
||||||
|
xfce4-exo # "Open Terminal Here" and default-app associations
|
||||||
|
shared-mime-info # MIME database
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,6 +21,28 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
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; };
|
audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; };
|
||||||
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; };
|
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; };
|
||||||
|
|
||||||
@@ -29,5 +51,58 @@
|
|||||||
`nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the
|
`nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the
|
||||||
root filesystem is BTRFS with a /.snapshots subvolume (the installer
|
root filesystem is BTRFS with a /.snapshots subvolume (the installer
|
||||||
creates one)'';
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
110
modules/nixos/plymouth.nix
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 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 { };
|
||||||
|
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
|
||||||
|
# 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;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ pkgs.imagemagick ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
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|$themedir|g" \
|
||||||
|
"$themedir/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' \
|
||||||
|
"$themedir/nomarchy.script"
|
||||||
|
sed -i 's|@BG_HEX@|${base}|g' \
|
||||||
|
"$themedir/nomarchy.plymouth"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf cfg.plymouth.enable {
|
||||||
|
# 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;
|
||||||
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
modules/nixos/plymouth/bullet.png
Normal file
|
After Width: | Height: | Size: 293 B |
BIN
modules/nixos/plymouth/entry.png
Normal file
|
After Width: | Height: | Size: 694 B |
BIN
modules/nixos/plymouth/lock.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
modules/nixos/plymouth/logo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
11
modules/nixos/plymouth/nomarchy.plymouth
Normal file
@@ -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
|
||||||
271
modules/nixos/plymouth/nomarchy.script
Normal file
@@ -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();
|
||||||
|
}
|
||||||
BIN
modules/nixos/plymouth/progress_bar.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
modules/nixos/plymouth/progress_box.png
Normal file
|
After Width: | Height: | Size: 314 B |
60
modules/nixos/power.nix
Normal file
@@ -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
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
280
modules/nixos/services.nix
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
# Opt-in services & integrations — off by default, one toggle each, enabled
|
||||||
|
# in system.nix (see the commented examples in the downstream template).
|
||||||
|
# These are system-side: their own `nomarchy.services.*` namespace, distinct
|
||||||
|
# from the distro's core `nomarchy.system.*` toggles. `username` (from
|
||||||
|
# specialArgs) is read lazily, only where a service needs it.
|
||||||
|
{ config, lib, pkgs, ... }@args:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy.services;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.nomarchy.services = {
|
||||||
|
tailscale.enable = lib.mkEnableOption ''
|
||||||
|
Tailscale, the mesh VPN — ships the daemon; authenticate once with
|
||||||
|
`sudo tailscale up`'';
|
||||||
|
|
||||||
|
syncthing.enable = lib.mkEnableOption ''
|
||||||
|
Syncthing continuous file sync, running as the login user — add
|
||||||
|
folders and devices in the web UI at http://127.0.0.1:8384'';
|
||||||
|
|
||||||
|
podman.enable = lib.mkEnableOption ''
|
||||||
|
Podman for rootless containers, with `docker` aliased to it and DNS on
|
||||||
|
the default network; the login user gets a subuid/subgid range'';
|
||||||
|
|
||||||
|
flatpak.enable = lib.mkEnableOption ''
|
||||||
|
Flatpak, with the Flathub remote added system-wide — install apps with
|
||||||
|
`flatpak install flathub <app>`'';
|
||||||
|
|
||||||
|
pika.enable = lib.mkEnableOption ''
|
||||||
|
Pika Backup, a friendly GUI for scheduled Borg backups (the app;
|
||||||
|
configure repositories and schedules inside it)'';
|
||||||
|
|
||||||
|
steam.enable = lib.mkEnableOption ''
|
||||||
|
Steam (Valve's game store/launcher) via programs.steam — which wires up
|
||||||
|
the 32-bit graphics libraries, controller udev rules and Remote-Play
|
||||||
|
firewall ports a bare package can't. Unfree'';
|
||||||
|
|
||||||
|
libvirt.enable = lib.mkEnableOption ''
|
||||||
|
libvirt/KVM virtualisation with the virt-manager GUI — runs the
|
||||||
|
libvirtd daemon and adds the login user to the `libvirtd` group, so VMs
|
||||||
|
can be managed without root'';
|
||||||
|
|
||||||
|
obs.enable = lib.mkEnableOption ''
|
||||||
|
OBS Studio for screen recording and streaming, plus a v4l2loopback
|
||||||
|
virtual camera — so an OBS scene can be selected as a webcam in Zoom,
|
||||||
|
Teams or a browser (screen capture itself already works over the
|
||||||
|
desktop's PipeWire portal)'';
|
||||||
|
|
||||||
|
docker.enable = lib.mkEnableOption ''
|
||||||
|
Docker (rootful) with the login user in the `docker` group. Mutually
|
||||||
|
exclusive with the podman toggle's docker-compat (both provide the
|
||||||
|
`docker` command) — enable only one'';
|
||||||
|
|
||||||
|
kdeconnect.enable = lib.mkEnableOption ''
|
||||||
|
KDE Connect for phone integration — shared clipboard, notifications and
|
||||||
|
file transfer with a paired phone; opens the firewall ports it needs'';
|
||||||
|
|
||||||
|
gamemode.enable = lib.mkEnableOption ''
|
||||||
|
Feral GameMode — a daemon games ask to apply a performance CPU governor
|
||||||
|
and IO/nice priorities while running (via `gamemoderun`)'';
|
||||||
|
|
||||||
|
adb.enable = lib.mkEnableOption ''
|
||||||
|
the Android platform tools (adb/fastboot) for flashing/debugging phones.
|
||||||
|
systemd ≥258 applies the device uaccess udev rules automatically, so the
|
||||||
|
tools alone are enough — no group dance'';
|
||||||
|
|
||||||
|
wireshark.enable = lib.mkEnableOption ''
|
||||||
|
Wireshark (the Qt GUI) for packet capture, with the login user in the
|
||||||
|
`wireshark` group so capture works without root'';
|
||||||
|
|
||||||
|
ollama.enable = lib.mkEnableOption ''
|
||||||
|
Ollama, a local LLM runtime serving an API on 127.0.0.1:11434 (pair it
|
||||||
|
with the lmstudio/alpaca GUIs). CPU by default — set
|
||||||
|
`services.ollama.acceleration` natively for GPU offload'';
|
||||||
|
|
||||||
|
printing.enable = lib.mkEnableOption ''
|
||||||
|
CUPS printing with Avahi/mDNS, so network printers are auto-discovered
|
||||||
|
(add vendor drivers via `services.printing.drivers`)'';
|
||||||
|
|
||||||
|
openrgb.enable = lib.mkEnableOption ''
|
||||||
|
the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
|
||||||
|
and (where supported) motherboards — installs the udev rules for device
|
||||||
|
access. Some motherboard controllers also need `hardware.i2c.enable`'';
|
||||||
|
|
||||||
|
restic = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
a scheduled (daily) restic backup — headless, complementing the Pika
|
||||||
|
GUI. Backs up `paths` with sane retention (7 daily / 4 weekly /
|
||||||
|
6 monthly); needs a `repository` and a `passwordFile` (below). List
|
||||||
|
and restore with the generated `restic-nomarchy` wrapper (repo +
|
||||||
|
password baked in), e.g. `restic-nomarchy snapshots`'';
|
||||||
|
|
||||||
|
repository = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
example = "/mnt/backup/restic";
|
||||||
|
description = ''
|
||||||
|
The restic repository as a path or URL — e.g. `/mnt/backup/restic`,
|
||||||
|
`sftp:user@host:/srv/restic`, `b2:bucket:/path`. Required when
|
||||||
|
`restic.enable` is set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/etc/nomarchy/restic-password";
|
||||||
|
description = ''
|
||||||
|
Path to a file holding the repository password. Give an absolute
|
||||||
|
path that lives ON THE MACHINE — not a `./file` in the flake, which
|
||||||
|
would copy the secret into the world-readable Nix store. Required
|
||||||
|
when `restic.enable` is set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
paths = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ "/home" ];
|
||||||
|
description = "Paths to include in the backup.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
(lib.mkIf cfg.tailscale.enable {
|
||||||
|
services.tailscale.enable = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.syncthing.enable {
|
||||||
|
services.syncthing = {
|
||||||
|
enable = true;
|
||||||
|
user = args.username;
|
||||||
|
# Manage folders/devices in the GUI; don't let the (empty) declarative
|
||||||
|
# config wipe them on a rebuild.
|
||||||
|
overrideDevices = false;
|
||||||
|
overrideFolders = false;
|
||||||
|
guiAddress = "127.0.0.1:8384";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.podman.enable {
|
||||||
|
virtualisation.podman = {
|
||||||
|
enable = true;
|
||||||
|
dockerCompat = lib.mkDefault true; # `docker` → podman (off if you run real docker)
|
||||||
|
defaultNetwork.settings.dns_enabled = true; # DNS for rootless containers
|
||||||
|
};
|
||||||
|
# Rootless podman needs a subuid/subgid range for the user.
|
||||||
|
users.users.${args.username}.autoSubUidGidRange = lib.mkDefault true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.flatpak.enable {
|
||||||
|
services.flatpak.enable = true;
|
||||||
|
# Flatpak ships no remotes; add Flathub system-wide. Idempotent
|
||||||
|
# (--if-not-exists), and it reads the .flatpakrepo over the network, so
|
||||||
|
# it waits for connectivity and simply retries next boot if offline.
|
||||||
|
systemd.services.nomarchy-flathub = {
|
||||||
|
description = "Add the Flathub Flatpak remote";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
path = [ pkgs.flatpak ];
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = ''
|
||||||
|
flatpak remote-add --system --if-not-exists flathub \
|
||||||
|
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.pika.enable {
|
||||||
|
environment.systemPackages = [ pkgs.pika-backup ];
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.steam.enable {
|
||||||
|
# The Steam package alone misses the system bits a game needs: the
|
||||||
|
# 32-bit graphics stack, controller udev rules, Remote-Play ports.
|
||||||
|
programs.steam.enable = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.libvirt.enable {
|
||||||
|
virtualisation.libvirtd.enable = true;
|
||||||
|
programs.virt-manager.enable = true;
|
||||||
|
# Group membership lets the user reach the system libvirtd socket
|
||||||
|
# (manage VMs) without sudo. Merges with the user's other extraGroups.
|
||||||
|
users.users.${args.username}.extraGroups = [ "libvirtd" ];
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.obs.enable {
|
||||||
|
environment.systemPackages = [ pkgs.obs-studio ];
|
||||||
|
# Virtual camera: OBS streams a scene into a v4l2 loopback device that
|
||||||
|
# Zoom/Teams/browsers then pick up as a webcam. exclusive_caps=1 is the
|
||||||
|
# bit that makes those apps actually list the device.
|
||||||
|
boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
|
||||||
|
boot.kernelModules = [ "v4l2loopback" ];
|
||||||
|
boot.extraModprobeConfig = ''
|
||||||
|
options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.docker.enable {
|
||||||
|
virtualisation.docker.enable = true;
|
||||||
|
users.users.${args.username}.extraGroups = [ "docker" ];
|
||||||
|
# Podman's docker-compat and real Docker both claim the `docker`
|
||||||
|
# command — guard against enabling both (unless you turn that off).
|
||||||
|
assertions = [{
|
||||||
|
assertion = !(cfg.podman.enable && config.virtualisation.podman.dockerCompat);
|
||||||
|
message = ''
|
||||||
|
nomarchy.services: docker and podman both provide the `docker`
|
||||||
|
command. Enable only one, or set
|
||||||
|
`virtualisation.podman.dockerCompat = false`.
|
||||||
|
'';
|
||||||
|
}];
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.kdeconnect.enable {
|
||||||
|
programs.kdeconnect.enable = true; # installs the app and opens its ports
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.gamemode.enable {
|
||||||
|
programs.gamemode.enable = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.adb.enable {
|
||||||
|
# `programs.adb` was removed — systemd ≥258 handles the device uaccess
|
||||||
|
# udev rules, so the tools package is all that's needed now.
|
||||||
|
environment.systemPackages = [ pkgs.android-tools ];
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.wireshark.enable {
|
||||||
|
programs.wireshark = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.wireshark; # the Qt GUI (the module defaults to the CLI)
|
||||||
|
};
|
||||||
|
users.users.${args.username}.extraGroups = [ "wireshark" ];
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.ollama.enable {
|
||||||
|
services.ollama.enable = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.printing.enable {
|
||||||
|
services.printing.enable = true;
|
||||||
|
# mDNS so network printers show up without manual setup.
|
||||||
|
services.avahi = {
|
||||||
|
enable = true;
|
||||||
|
nssmdns4 = true;
|
||||||
|
openFirewall = true;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.openrgb.enable {
|
||||||
|
services.hardware.openrgb.enable = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.restic.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.restic.repository != "";
|
||||||
|
message = "nomarchy.services.restic.repository must be set when restic is enabled.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.restic.passwordFile != null;
|
||||||
|
message = "nomarchy.services.restic.passwordFile must be set when restic is enabled.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
services.restic.backups.nomarchy = {
|
||||||
|
repository = cfg.restic.repository;
|
||||||
|
passwordFile = cfg.restic.passwordFile;
|
||||||
|
paths = cfg.restic.paths;
|
||||||
|
initialize = true; # create the repo on first run if it doesn't exist
|
||||||
|
extraBackupArgs = [ "--exclude-caches" ];
|
||||||
|
pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 6" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "daily";
|
||||||
|
Persistent = true; # catch up a missed run after downtime
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
# NOMARCHY_UNATTENDED=1 NOMARCHY_DISK=/dev/vda NOMARCHY_USERNAME=me \
|
# NOMARCHY_UNATTENDED=1 NOMARCHY_DISK=/dev/vda NOMARCHY_USERNAME=me \
|
||||||
# NOMARCHY_PASSWORD=secret [NOMARCHY_HOSTNAME=nomarchy] \
|
# NOMARCHY_PASSWORD=secret [NOMARCHY_HOSTNAME=nomarchy] \
|
||||||
# [NOMARCHY_TIMEZONE=UTC] [NOMARCHY_LUKS_PASSPHRASE=...] \
|
# [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_SWAP_GB=N (default: RAM size; 0 = none)] \
|
||||||
# [NOMARCHY_HW="auto"|"none"|"mod1 mod2"] [NOMARCHY_FINISH=none|reboot|poweroff]
|
# [NOMARCHY_HW="auto"|"none"|"mod1 mod2"] [NOMARCHY_FINISH=none|reboot|poweroff]
|
||||||
# nomarchy-install
|
# nomarchy-install
|
||||||
@@ -150,6 +151,9 @@ if [[ "$UNATTENDED" == "1" ]]; then
|
|||||||
PASSWORD="${NOMARCHY_PASSWORD:?}"
|
PASSWORD="${NOMARCHY_PASSWORD:?}"
|
||||||
HOSTNAME_="${NOMARCHY_HOSTNAME:-nomarchy}"
|
HOSTNAME_="${NOMARCHY_HOSTNAME:-nomarchy}"
|
||||||
TIMEZONE="${NOMARCHY_TIMEZONE:-UTC}"
|
TIMEZONE="${NOMARCHY_TIMEZONE:-UTC}"
|
||||||
|
LOCALE="${NOMARCHY_LOCALE:-en_US.UTF-8}"
|
||||||
|
KB_LAYOUT="${NOMARCHY_KB_LAYOUT:-us}"
|
||||||
|
KB_VARIANT="${NOMARCHY_KB_VARIANT:-}"
|
||||||
else
|
else
|
||||||
while true; do
|
while true; do
|
||||||
USERNAME=$(gum input --placeholder "username (lowercase, e.g. ada)")
|
USERNAME=$(gum input --placeholder "username (lowercase, e.g. ada)")
|
||||||
@@ -169,6 +173,26 @@ else
|
|||||||
warn "Invalid hostname."
|
warn "Invalid hostname."
|
||||||
done
|
done
|
||||||
TIMEZONE=$(timedatectl list-timezones | gum filter --placeholder "timezone (type to search)" || echo UTC)
|
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
|
fi
|
||||||
[[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || fail "Invalid username '$USERNAME'."
|
[[ "$USERNAME" =~ ^[a-z_][a-z0-9_-]*$ ]] || fail "Invalid username '$USERNAME'."
|
||||||
[[ -f "/usr/share/zoneinfo/$TIMEZONE" || -e "/etc/zoneinfo/$TIMEZONE" ]] \
|
[[ -f "/usr/share/zoneinfo/$TIMEZONE" || -e "/etc/zoneinfo/$TIMEZONE" ]] \
|
||||||
@@ -177,6 +201,7 @@ fi
|
|||||||
HASHED_PASSWORD=$(printf '%s' "$PASSWORD" | mkpasswd -m sha-512 -s)
|
HASHED_PASSWORD=$(printf '%s' "$PASSWORD" | mkpasswd -m sha-512 -s)
|
||||||
unset PASSWORD
|
unset PASSWORD
|
||||||
info "User: $USERNAME @ $HOSTNAME_ ($TIMEZONE)"
|
info "User: $USERNAME @ $HOSTNAME_ ($TIMEZONE)"
|
||||||
|
info "Locale: $LOCALE · keyboard: $KB_LAYOUT${KB_VARIANT:+ ($KB_VARIANT)}"
|
||||||
|
|
||||||
# ─── Hardware profile ───────────────────────────────────────────────────
|
# ─── Hardware profile ───────────────────────────────────────────────────
|
||||||
section "Hardware detection"
|
section "Hardware detection"
|
||||||
@@ -320,9 +345,35 @@ nixos-generate-config --root /mnt
|
|||||||
mv /mnt/etc/nixos/hardware-configuration.nix "$FLAKE_DIR/"
|
mv /mnt/etc/nixos/hardware-configuration.nix "$FLAKE_DIR/"
|
||||||
rm -rf /mnt/etc/nixos
|
rm -rf /mnt/etc/nixos
|
||||||
|
|
||||||
cp "$SHARE/template/home.nix" "$FLAKE_DIR/"
|
|
||||||
cp "$SHARE/template/theme-state.json" "$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" <<EOF
|
||||||
|
# Your user environment. The Nomarchy desktop (Hyprland, Waybar,
|
||||||
|
# Ghostty, theming engine, Stylix) comes from homeModules.nomarchy;
|
||||||
|
# tune it via the nomarchy.* options, add your own packages and
|
||||||
|
# programs below.
|
||||||
|
{ pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
# Keyboard for the desktop session; console + LUKS prompt get the
|
||||||
|
# same layout from system.nix (xkb + console.useXkbConfig).
|
||||||
|
nomarchy.keyboard.layout = "$KB_LAYOUT";
|
||||||
|
nomarchy.keyboard.variant = "$KB_VARIANT";
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
# 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
|
||||||
|
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
# firefox
|
||||||
|
];
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
hw_nix=""
|
hw_nix=""
|
||||||
for p in "${HW_PROFILES[@]:-}"; do
|
for p in "${HW_PROFILES[@]:-}"; do
|
||||||
[[ -n "$p" ]] && hw_nix+=" \"$p\""
|
[[ -n "$p" ]] && hw_nix+=" \"$p\""
|
||||||
@@ -357,6 +408,26 @@ NIX
|
|||||||
)
|
)
|
||||||
fi
|
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 <<NIX
|
||||||
|
|
||||||
|
$power_lines
|
||||||
|
NIX
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
# initialHashedPassword is safe to template: mkpasswd's alphabet is
|
# initialHashedPassword is safe to template: mkpasswd's alphabet is
|
||||||
# [a-zA-Z0-9./$] — no Nix string metacharacters.
|
# [a-zA-Z0-9./$] — no Nix string metacharacters.
|
||||||
cat > "$FLAKE_DIR/system.nix" <<EOF
|
cat > "$FLAKE_DIR/system.nix" <<EOF
|
||||||
@@ -371,7 +442,16 @@ cat > "$FLAKE_DIR/system.nix" <<EOF
|
|||||||
|
|
||||||
networking.hostName = "$HOSTNAME_";
|
networking.hostName = "$HOSTNAME_";
|
||||||
time.timeZone = "$TIMEZONE";
|
time.timeZone = "$TIMEZONE";
|
||||||
i18n.defaultLocale = "en_US.UTF-8";
|
i18n.defaultLocale = "$LOCALE";
|
||||||
|
|
||||||
|
# One keyboard layout everywhere: xkb is the source of truth, and the
|
||||||
|
# distro defaults (console.useXkbConfig + the systemd initrd, set in
|
||||||
|
# the nomarchy module) derive the virtual console and the LUKS
|
||||||
|
# passphrase prompt from it — so only the chosen layout is written here.
|
||||||
|
# The Hyprland session reads the same layout from nomarchy.keyboard.* in
|
||||||
|
# home.nix.
|
||||||
|
services.xserver.xkb.layout = "$KB_LAYOUT";
|
||||||
|
services.xserver.xkb.variant = "$KB_VARIANT";
|
||||||
|
|
||||||
# Your login user — \`username\` flows in from flake.nix automatically.
|
# Your login user — \`username\` flows in from flake.nix automatically.
|
||||||
users.users.\${username} = {
|
users.users.\${username} = {
|
||||||
@@ -379,7 +459,7 @@ cat > "$FLAKE_DIR/system.nix" <<EOF
|
|||||||
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
||||||
initialHashedPassword = "$HASHED_PASSWORD";
|
initialHashedPassword = "$HASHED_PASSWORD";
|
||||||
};
|
};
|
||||||
$AUTOLOGIN_CONFIG$RESUME_CONFIG
|
$AUTOLOGIN_CONFIG$POWER_CONFIG$RESUME_CONFIG
|
||||||
# Hourly/daily BTRFS timeline snapshots + nixos-rebuild-snap.
|
# Hourly/daily BTRFS timeline snapshots + nixos-rebuild-snap.
|
||||||
nomarchy.system.snapper.enable = true;
|
nomarchy.system.snapper.enable = true;
|
||||||
|
|
||||||
@@ -410,6 +490,9 @@ fi
|
|||||||
# outright. The first normal NixOS user is always 1000:users(100); the
|
# outright. The first normal NixOS user is always 1000:users(100); the
|
||||||
# account doesn't exist in the target yet, so numeric ids it is.
|
# account doesn't exist in the target yet, so numeric ids it is.
|
||||||
chown -R 1000:100 "$FLAKE_DIR"
|
chown -R 1000:100 "$FLAKE_DIR"
|
||||||
|
# The templates come out of the nix store mode 0444 and cp preserves
|
||||||
|
# that — without this the user owns home.nix but can't edit it.
|
||||||
|
chmod -R u+w "$FLAKE_DIR"
|
||||||
|
|
||||||
# /etc/nixos on the installed system points at the user-owned flake.
|
# /etc/nixos on the installed system points at the user-owned flake.
|
||||||
mkdir -p /mnt/etc
|
mkdir -p /mnt/etc
|
||||||
@@ -464,6 +547,25 @@ success "System installed (bootloader in place)"
|
|||||||
# fully themed desktop, not bare Hyprland. Best-effort: a failure here
|
# fully themed desktop, not bare Hyprland. Best-effort: a failure here
|
||||||
# only costs the user one `home-manager switch` after logging in.
|
# only costs the user one `home-manager switch` after logging in.
|
||||||
section "Baking the desktop"
|
section "Baking the desktop"
|
||||||
|
|
||||||
|
# Build the generation HERE in the live environment, not in the chroot:
|
||||||
|
# the live store always carries the offline pin set, while the target
|
||||||
|
# store only gets the full ISO store on OFFLINE installs — an online
|
||||||
|
# install skips that copy, and the old in-chroot build (substituters
|
||||||
|
# deliberately empty) then tried to compile the world from source.
|
||||||
|
# Building against the live store is the exact path the live session's
|
||||||
|
# own theme switching exercises, online or off.
|
||||||
|
info "Building the desktop generation..."
|
||||||
|
hm_out=""
|
||||||
|
if hm_out=$(nix --extra-experimental-features "nix-command flakes" \
|
||||||
|
build --no-link --print-out-paths \
|
||||||
|
--option substituters "" \
|
||||||
|
"path:$FLAKE_DIR#homeConfigurations.$USERNAME.activationPackage"); then
|
||||||
|
nix --extra-experimental-features "nix-command flakes" \
|
||||||
|
copy --no-check-sigs --to "local?root=/mnt" "$hm_out" \
|
||||||
|
|| hm_out=""
|
||||||
|
fi
|
||||||
|
[[ -n "$hm_out" ]] || warn "live-side desktop build failed — retrying inside the chroot"
|
||||||
# NOT /mnt/tmp: nixos-enter mounts a fresh tmpfs over /tmp inside the
|
# NOT /mnt/tmp: nixos-enter mounts a fresh tmpfs over /tmp inside the
|
||||||
# chroot, which silently vaporizes any script staged there (cost us a
|
# chroot, which silently vaporizes any script staged there (cost us a
|
||||||
# full verification round to find). /root persists into the chroot.
|
# full verification round to find). /root persists into the chroot.
|
||||||
@@ -471,10 +573,15 @@ cat > /mnt/root/nomarchy-hm-activate.sh <<EOF
|
|||||||
set -ex
|
set -ex
|
||||||
exec > /var/log/nomarchy-hm-preactivate.log 2>&1
|
exec > /var/log/nomarchy-hm-preactivate.log 2>&1
|
||||||
export PATH=/run/current-system/sw/bin:\$PATH
|
export PATH=/run/current-system/sw/bin:\$PATH
|
||||||
|
# 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" \
|
out=\$(nix --extra-experimental-features "nix-command flakes" \
|
||||||
build --no-link --print-out-paths \
|
build --no-link --print-out-paths \
|
||||||
--option substituters "" \
|
|
||||||
"path:/home/$USERNAME/.nomarchy#homeConfigurations.$USERNAME.activationPackage")
|
"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/profiles/per-user/$USERNAME
|
||||||
install -d -o "$USERNAME" -g users /nix/var/nix/gcroots/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
|
# activate's profile ops need store access; as the user that means a
|
||||||
|
|||||||
@@ -59,10 +59,27 @@ def die(msg: str) -> "None":
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def notify(body: str) -> None:
|
# All Nomarchy theme notifications share this synchronous tag, so the
|
||||||
if shutil.which("notify-send"):
|
# notification daemon (swaync) REPLACES the previous one in place instead
|
||||||
subprocess.run(["notify-send", "-a", "Nomarchy", "Nomarchy", body],
|
# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast.
|
||||||
capture_output=True)
|
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 ─────────────────────────────────────────────────────
|
# ─── State management ─────────────────────────────────────────────────────
|
||||||
@@ -109,6 +126,29 @@ def deep_merge(base: dict, override: dict) -> dict:
|
|||||||
return out
|
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 ─────────────────────────────────────────────────────
|
# ─── Rebuild dispatch ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
def run_switch() -> None:
|
def run_switch() -> None:
|
||||||
@@ -122,10 +162,13 @@ def run_switch() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
log(f"rebuilding: {' '.join(argv)}")
|
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
|
result = subprocess.run(argv) # stream output to the caller's terminal
|
||||||
if result.returncode != 0:
|
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)")
|
die("rebuild failed (state file already updated; fix and re-run)")
|
||||||
notify("Theme applied ✓")
|
notify("Theme applied ✓")
|
||||||
|
|
||||||
@@ -219,6 +262,7 @@ def cmd_apply(args) -> None:
|
|||||||
state = deep_merge(load_state(), preset)
|
state = deep_merge(load_state(), preset)
|
||||||
write_state(state)
|
write_state(state)
|
||||||
log(f"theme: {state.get('name', args.theme)}")
|
log(f"theme: {state.get('name', args.theme)}")
|
||||||
|
check_fonts(state)
|
||||||
if not args.no_switch:
|
if not args.no_switch:
|
||||||
run_switch()
|
run_switch()
|
||||||
apply_wallpaper(state)
|
apply_wallpaper(state)
|
||||||
@@ -241,6 +285,8 @@ def cmd_set(args) -> None:
|
|||||||
|
|
||||||
write_state(state)
|
write_state(state)
|
||||||
log(f"set {args.path} = {value!r}")
|
log(f"set {args.path} = {value!r}")
|
||||||
|
if args.path.startswith("fonts."):
|
||||||
|
check_fonts(state)
|
||||||
if not args.no_switch:
|
if not args.no_switch:
|
||||||
run_switch()
|
run_switch()
|
||||||
apply_wallpaper(state)
|
apply_wallpaper(state)
|
||||||
|
|||||||
@@ -5,12 +5,136 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# Examples:
|
# ── Overrides (the desktop is on by default; tweak or opt out) ──────
|
||||||
# nomarchy.terminal = "kitty"; # swap the default terminal
|
# nomarchy.terminal = "kitty"; # swap the default terminal
|
||||||
# nomarchy.waybar.enable = false; # bring your own bar
|
# nomarchy.waybar.enable = false; # bring your own bar
|
||||||
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
||||||
|
# nomarchy.keyboard.layout = "de"; # session keyboard (set the matching
|
||||||
|
# # services.xserver.xkb.layout in
|
||||||
|
# # system.nix too; the console + LUKS
|
||||||
|
# # prompt follow it automatically)
|
||||||
|
|
||||||
|
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||||
|
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
||||||
|
# enable = true;
|
||||||
|
# temperature = 4000; # night warmth in K — lower is warmer
|
||||||
|
# sunset = "20:00";
|
||||||
|
# sunrise = "07:00";
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# nomarchy.monitors = [ # declarative per-output layout, hotplug-applied
|
||||||
|
# { name = "eDP-1"; position = "0x0"; }
|
||||||
|
# { name = "HDMI-A-1"; position = "auto-right"; }
|
||||||
|
# ]; # names from `hyprctl devices`
|
||||||
|
#
|
||||||
|
# nomarchy.keyboard.devices = { # a specific keyboard's own layout
|
||||||
|
# "keychron-keychron-k2" = { layout = "de"; };
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# nomarchy.keyboard.layouts = [ "de" "fr" ]; # rofi-prompt for a layout when a
|
||||||
|
# # new keyboard connects + remember it
|
||||||
|
|
||||||
|
# ── Application suite ───────────────────────────────────────────────
|
||||||
|
# A starter complete-workstation set, installed for your user — yours to
|
||||||
|
# curate. Delete a line to slim the machine; uncomment a suggestion (or
|
||||||
|
# add your own) to extend it. The desktop itself — terminal, editor
|
||||||
|
# keybinds, theming — comes from the modules; these are the heavier
|
||||||
|
# standalone apps the distro doesn't impose by default.
|
||||||
|
#
|
||||||
|
# Apps that need system setup beyond a package (Steam's 32-bit stack,
|
||||||
|
# a daemon, group membership, a kernel module) are NOT here — they're
|
||||||
|
# opt-in service toggles in system.nix instead (nomarchy.services.* —
|
||||||
|
# steam, libvirt, obs, docker, gamemode, kdeconnect, adb, wireshark,
|
||||||
|
# ollama, printing).
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
|
libreoffice-fresh # office suite (documents, sheets, slides)
|
||||||
|
vscode # code editor (unfree; allowUnfree is on)
|
||||||
|
gimp # raster image editor
|
||||||
|
inkscape # vector graphics
|
||||||
|
mpv # media player
|
||||||
|
amberol # music player (local library; streaming → spotify below)
|
||||||
|
|
||||||
|
# ── More apps — uncomment to add ─────────────────────────────────
|
||||||
|
# Web browsers
|
||||||
# firefox
|
# firefox
|
||||||
|
# chromium
|
||||||
|
# ungoogled-chromium
|
||||||
|
# brave # (unfree)
|
||||||
|
# google-chrome # (unfree)
|
||||||
|
|
||||||
|
# Communication
|
||||||
|
# signal-desktop
|
||||||
|
# telegram-desktop
|
||||||
|
# element-desktop # Matrix
|
||||||
|
# vesktop # Discord (themeable client)
|
||||||
|
# slack # (unfree)
|
||||||
|
# zoom-us # (unfree)
|
||||||
|
# teams-for-linux # community client (no official Linux app)
|
||||||
|
# thunderbird # email
|
||||||
|
|
||||||
|
# Office & documents
|
||||||
|
# onlyoffice-desktopeditors # stronger MS-Office fidelity
|
||||||
|
# kdePackages.okular # PDF viewer
|
||||||
|
# xournalpp # PDF annotation
|
||||||
|
# pdfarranger
|
||||||
|
# calibre # ebook library
|
||||||
|
# texliveFull # full LaTeX toolchain — multi-GB (texliveMedium is lighter)
|
||||||
|
|
||||||
|
# Notes / research
|
||||||
|
# logseq
|
||||||
|
# obsidian # (unfree)
|
||||||
|
# joplin-desktop
|
||||||
|
# anki-bin # flashcards
|
||||||
|
# zotero # reference manager
|
||||||
|
|
||||||
|
# Code & data
|
||||||
|
# zed-editor
|
||||||
|
# lazygit
|
||||||
|
# dbeaver-bin # database GUI
|
||||||
|
# bruno # API client
|
||||||
|
# distrobox # containerized dev shells
|
||||||
|
|
||||||
|
# Graphics & design
|
||||||
|
# krita # digital painting
|
||||||
|
# blender # 3D
|
||||||
|
# darktable # RAW photo
|
||||||
|
|
||||||
|
# Audio / video
|
||||||
|
# audacity
|
||||||
|
# shotcut # video editor
|
||||||
|
# kdePackages.kdenlive # video editor (pulls in KDE libs)
|
||||||
|
# handbrake # transcoder
|
||||||
|
|
||||||
|
# Media
|
||||||
|
# vlc
|
||||||
|
# spotify # (unfree)
|
||||||
|
# newsflash # RSS reader
|
||||||
|
|
||||||
|
# Gaming (Steam + GameMode are service toggles in system.nix)
|
||||||
|
# lutris
|
||||||
|
# heroic # Epic/GOG launcher
|
||||||
|
# bottles # Wine prefixes
|
||||||
|
# prismlauncher # Minecraft
|
||||||
|
# protonup-qt # Proton-GE manager
|
||||||
|
# mangohud # in-game overlay
|
||||||
|
|
||||||
|
# Files, sync & torrents
|
||||||
|
# nextcloud-client
|
||||||
|
# localsend # AirDrop-like transfer
|
||||||
|
# qbittorrent
|
||||||
|
|
||||||
|
# Security
|
||||||
|
# keepassxc
|
||||||
|
# bitwarden-desktop
|
||||||
|
|
||||||
|
# System
|
||||||
|
# gnome-disk-utility
|
||||||
|
# gparted
|
||||||
|
# baobab # disk usage
|
||||||
|
# wireguard-tools # WireGuard CLI (NetworkManager imports .conf tunnels natively)
|
||||||
|
|
||||||
|
# Local AI (Ollama is a service toggle in system.nix)
|
||||||
|
# lmstudio # (unfree; large closure)
|
||||||
|
# alpaca # GTK Ollama UI
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,41 @@
|
|||||||
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Examples:
|
# ── Overrides (uncomment to change) ─────────────────────────────────
|
||||||
# nomarchy.system.greeter.enable = false; # bring your own login manager
|
# nomarchy.system.greeter.enable = false; # bring your own login manager
|
||||||
# environment.systemPackages = [ pkgs.htop ];
|
# environment.systemPackages = [ pkgs.htop ];
|
||||||
|
|
||||||
|
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||||
|
# nomarchy.system.power = { # active power management (laptops)
|
||||||
|
# enable = true;
|
||||||
|
# backend = "ppd"; # "ppd" (default) or "tlp"
|
||||||
|
# laptop = true; # required by batteryChargeLimit
|
||||||
|
# batteryChargeLimit = 80; # cap charging for longevity
|
||||||
|
# thermal.enable = true; # thermald (Intel CPUs)
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# nomarchy.services.tailscale.enable = true; # mesh VPN — then `sudo tailscale up`
|
||||||
|
# nomarchy.services.syncthing.enable = true; # file sync — GUI at http://127.0.0.1:8384
|
||||||
|
# nomarchy.services.podman.enable = true; # rootless containers (docker → podman)
|
||||||
|
# nomarchy.services.flatpak.enable = true; # Flatpak + the Flathub remote
|
||||||
|
# nomarchy.services.pika.enable = true; # Pika Backup (GUI Borg backups)
|
||||||
|
# nomarchy.services.steam.enable = true; # Steam (32-bit libs, controllers, ports)
|
||||||
|
# nomarchy.services.libvirt.enable = true; # libvirt/KVM + virt-manager GUI
|
||||||
|
# nomarchy.services.obs.enable = true; # OBS Studio + v4l2loopback virtual camera
|
||||||
|
# nomarchy.services.docker.enable = true; # Docker rootful (not alongside podman)
|
||||||
|
# nomarchy.services.kdeconnect.enable = true;# KDE Connect phone integration (opens ports)
|
||||||
|
# nomarchy.services.gamemode.enable = true; # Feral GameMode performance daemon
|
||||||
|
# nomarchy.services.adb.enable = true; # Android adb/fastboot tools
|
||||||
|
# nomarchy.services.wireshark.enable = true; # Wireshark GUI (wireshark group, no root)
|
||||||
|
# nomarchy.services.ollama.enable = true; # local LLM runtime (127.0.0.1:11434)
|
||||||
|
# nomarchy.services.printing.enable = true; # CUPS + Avahi network printer discovery
|
||||||
|
# nomarchy.services.openrgb.enable = true; # RGB peripheral/motherboard lighting daemon
|
||||||
|
# nomarchy.services.restic = { # scheduled (daily) restic backup
|
||||||
|
# enable = true;
|
||||||
|
# repository = "/mnt/backup/restic"; # path or URL (sftp:/b2:/…)
|
||||||
|
# passwordFile = "/etc/nomarchy/restic-password"; # absolute path, NOT in the flake
|
||||||
|
# paths = [ "/home" ];
|
||||||
|
# };
|
||||||
|
|
||||||
system.stateVersion = "26.05";
|
system.stateVersion = "26.05";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#e0af68",
|
"warn": "#e0af68",
|
||||||
"bad": "#f7768e"
|
"bad": "#f7768e"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#32344a",
|
"#32344a",
|
||||||
"#f7768e",
|
"#f7768e",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#df8e1d",
|
"warn": "#df8e1d",
|
||||||
"bad": "#d20f39"
|
"bad": "#d20f39"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#bcc0cc",
|
"#bcc0cc",
|
||||||
"#d20f39",
|
"#d20f39",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#f9e2af",
|
"warn": "#f9e2af",
|
||||||
"bad": "#f38ba8"
|
"bad": "#f38ba8"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#45475a",
|
"#45475a",
|
||||||
"#f38ba8",
|
"#f38ba8",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#E9BB4F",
|
"warn": "#E9BB4F",
|
||||||
"bad": "#ED5B5A"
|
"bad": "#ED5B5A"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#060B1E",
|
"#060B1E",
|
||||||
"#ED5B5A",
|
"#ED5B5A",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#dbbc7f",
|
"warn": "#dbbc7f",
|
||||||
"bad": "#e67e80"
|
"bad": "#e67e80"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#475258",
|
"#475258",
|
||||||
"#e67e80",
|
"#e67e80",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#D0A215",
|
"warn": "#D0A215",
|
||||||
"bad": "#D14D41"
|
"bad": "#D14D41"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#100F0F",
|
"#100F0F",
|
||||||
"#D14D41",
|
"#D14D41",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#d8a657",
|
"warn": "#d8a657",
|
||||||
"bad": "#ea6962"
|
"bad": "#ea6962"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#3c3836",
|
"#3c3836",
|
||||||
"#ea6962",
|
"#ea6962",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#50f7d4",
|
"warn": "#50f7d4",
|
||||||
"bad": "#50f872"
|
"bad": "#50f872"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#0B0C16",
|
"#0B0C16",
|
||||||
"#50f872",
|
"#50f872",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#c0a36e",
|
"warn": "#c0a36e",
|
||||||
"bad": "#c34043"
|
"bad": "#c34043"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "text",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#090618",
|
"#090618",
|
||||||
"#c34043",
|
"#c34043",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#6fa4c9",
|
"warn": "#6fa4c9",
|
||||||
"bad": "#4d86b0"
|
"bad": "#4d86b0"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#1b2d40",
|
"#1b2d40",
|
||||||
"#4d86b0",
|
"#4d86b0",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#b91c1c",
|
"warn": "#b91c1c",
|
||||||
"bad": "#D35F5F"
|
"bad": "#D35F5F"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#333333",
|
"#333333",
|
||||||
"#D35F5F",
|
"#D35F5F",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#b36d43",
|
"warn": "#b36d43",
|
||||||
"bad": "#685742"
|
"bad": "#685742"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#000000",
|
"#000000",
|
||||||
"#685742",
|
"#685742",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#ebcb8b",
|
"warn": "#ebcb8b",
|
||||||
"bad": "#bf616a"
|
"bad": "#bf616a"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#3b4252",
|
"#3b4252",
|
||||||
"#bf616a",
|
"#bf616a",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#459451",
|
"warn": "#459451",
|
||||||
"bad": "#FF5345"
|
"bad": "#FF5345"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#23372B",
|
"#23372B",
|
||||||
"#FF5345",
|
"#FF5345",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#e97b3c",
|
"warn": "#e97b3c",
|
||||||
"bad": "#f85525"
|
"bad": "#f85525"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#00172e",
|
"#00172e",
|
||||||
"#f85525",
|
"#f85525",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#f9cc6c",
|
"warn": "#f9cc6c",
|
||||||
"bad": "#fd6883"
|
"bad": "#fd6883"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#72696a",
|
"#72696a",
|
||||||
"#fd6883",
|
"#fd6883",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#ea9d34",
|
"warn": "#ea9d34",
|
||||||
"bad": "#b4637a"
|
"bad": "#b4637a"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#f2e9e1",
|
"#f2e9e1",
|
||||||
"#b4637a",
|
"#b4637a",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#dfa000",
|
"warn": "#dfa000",
|
||||||
"bad": "#f85552"
|
"bad": "#f85552"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "text",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#5c6a72",
|
"#5c6a72",
|
||||||
"#f85552",
|
"#f85552",
|
||||||
|
|||||||
138
themes/summer-day/rofi.rasi
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Summer Day — rofi whole-swap (themes/<slug>/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%;
|
||||||
|
/* No fixed height — size to content (fixed-num-lines:false) so every
|
||||||
|
* entry shows without scrolling and there's no empty space below. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zebra striping: every other row lifts with a faint light wash over the
|
||||||
|
* dark window (bg0 at ~12% alpha). The selected row below still wins. */
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: #fdf6e31f;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -48,7 +48,10 @@ window#waybar {
|
|||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
#network,
|
#network,
|
||||||
|
#custom-powerprofile,
|
||||||
#battery,
|
#battery,
|
||||||
|
#tray,
|
||||||
|
#custom-notification,
|
||||||
#custom-powermenu {
|
#custom-powermenu {
|
||||||
background-color: @bg0;
|
background-color: @bg0;
|
||||||
color: @fg;
|
color: @fg;
|
||||||
@@ -89,6 +92,10 @@ window#waybar {
|
|||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 21px;
|
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 {
|
#custom-powermenu {
|
||||||
|
|||||||
116
themes/summer-day/waybar.jsonc
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"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", "custom/powerprofile", "battery", "tray", "custom/notification", "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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/launcher": {
|
||||||
|
"interval": "once",
|
||||||
|
"format": "\uf000",
|
||||||
|
"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": "<tt><small>{calendar}</small></tt>"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clock#date": {
|
||||||
|
"format": " {:%A, %B %d, %Y}",
|
||||||
|
"tooltip": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"tray": { "spacing": 8 },
|
||||||
|
|
||||||
|
"custom/powerprofile": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 5,
|
||||||
|
"exec": "nomarchy-powerprofile-status",
|
||||||
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/notification": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"return-type": "json",
|
||||||
|
"exec": "swaync-client -swb",
|
||||||
|
"exec-if": "which swaync-client",
|
||||||
|
"escape": true,
|
||||||
|
"tooltip": true,
|
||||||
|
"on-click": "swaync-client -t -sw",
|
||||||
|
"on-click-right": "swaync-client -d -sw",
|
||||||
|
"format-icons": {
|
||||||
|
"none": "",
|
||||||
|
"notification": "",
|
||||||
|
"dnd-none": "",
|
||||||
|
"dnd-notification": "",
|
||||||
|
"inhibited-none": "",
|
||||||
|
"inhibited-notification": "",
|
||||||
|
"dnd-inhibited-none": "",
|
||||||
|
"dnd-inhibited-notification": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/powermenu": {
|
||||||
|
"format": "",
|
||||||
|
"on-click": "nomarchy-menu power",
|
||||||
|
"tooltip": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#d9bb80",
|
"warn": "#d9bb80",
|
||||||
"bad": "#e68183"
|
"bad": "#e68183"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "text",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#3c474d",
|
"#3c474d",
|
||||||
"#e68183",
|
"#e68183",
|
||||||
|
|||||||
138
themes/summer-night/rofi.rasi
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Summer Night — rofi whole-swap (themes/<slug>/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%;
|
||||||
|
/* No fixed height — size to content (fixed-num-lines:false) so every
|
||||||
|
* entry shows without scrolling and there's no empty space below. */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zebra striping: every other row lifts with a faint dark wash over the
|
||||||
|
* cream window (bg0 at ~12% alpha). The selected row below still wins. */
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: #2d353b1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -53,8 +53,11 @@ window#waybar {
|
|||||||
#clock.date,
|
#clock.date,
|
||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
|
#network,
|
||||||
|
#custom-powerprofile,
|
||||||
#idle_inhibitor,
|
#idle_inhibitor,
|
||||||
#custom-battery,
|
#battery,
|
||||||
|
#custom-notification,
|
||||||
#custom-powermenu {
|
#custom-powermenu {
|
||||||
background-color: @bg0;
|
background-color: @bg0;
|
||||||
color: @fg;
|
color: @fg;
|
||||||
@@ -98,6 +101,8 @@ window#waybar {
|
|||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 21px;
|
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-family: Nomarchy;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
@@ -112,7 +117,7 @@ window#waybar {
|
|||||||
padding-right: 23px;
|
padding-right: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#custom-battery.preservation {
|
#battery.charging {
|
||||||
color: @green;
|
color: @green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
123
themes/summer-night/waybar.jsonc
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"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", "custom/powerprofile", "battery", "tray", "custom/notification", "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"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/nomarchy": {
|
||||||
|
"interval": "once",
|
||||||
|
"format": "\uf000",
|
||||||
|
"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": "<tt><small>{calendar}</small></tt>"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clock#date": {
|
||||||
|
"format": " {:%A, %B %d, %Y}",
|
||||||
|
"tooltip": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"tray": { "spacing": 8 },
|
||||||
|
|
||||||
|
"custom/powerprofile": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 5,
|
||||||
|
"exec": "nomarchy-powerprofile-status",
|
||||||
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/notification": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"return-type": "json",
|
||||||
|
"exec": "swaync-client -swb",
|
||||||
|
"exec-if": "which swaync-client",
|
||||||
|
"escape": true,
|
||||||
|
"tooltip": true,
|
||||||
|
"on-click": "swaync-client -t -sw",
|
||||||
|
"on-click-right": "swaync-client -d -sw",
|
||||||
|
"format-icons": {
|
||||||
|
"none": "",
|
||||||
|
"notification": "",
|
||||||
|
"dnd-none": "",
|
||||||
|
"dnd-notification": "",
|
||||||
|
"inhibited-none": "",
|
||||||
|
"inhibited-notification": "",
|
||||||
|
"dnd-inhibited-none": "",
|
||||||
|
"dnd-inhibited-notification": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/powermenu": {
|
||||||
|
"format": "",
|
||||||
|
"on-click": "nomarchy-menu power",
|
||||||
|
"tooltip": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#e0af68",
|
"warn": "#e0af68",
|
||||||
"bad": "#f7768e"
|
"bad": "#f7768e"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#32344a",
|
"#32344a",
|
||||||
"#f7768e",
|
"#f7768e",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#cecece",
|
"warn": "#cecece",
|
||||||
"bad": "#a4a4a4"
|
"bad": "#a4a4a4"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#0d0d0d",
|
"#0d0d0d",
|
||||||
"#a4a4a4",
|
"#a4a4a4",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
"warn": "#4a4a4a",
|
"warn": "#4a4a4a",
|
||||||
"bad": "#2a2a2a"
|
"bad": "#2a2a2a"
|
||||||
},
|
},
|
||||||
|
"border": {
|
||||||
|
"active": "accent",
|
||||||
|
"inactive": "overlay"
|
||||||
|
},
|
||||||
"ansi": [
|
"ansi": [
|
||||||
"#ffffff",
|
"#ffffff",
|
||||||
"#2a2a2a",
|
"#2a2a2a",
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ if df --output=fstype "$TARGET_DIR" | tail -1 | grep -q tmpfs; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
OVMF=""
|
OVMF="${NOMARCHY_OVMF:-}"
|
||||||
for c in /run/current-system/sw/share/qemu/edk2-x86_64-code.fd \
|
[ -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 \
|
/run/current-system/sw/share/OVMF/OVMF_CODE.fd \
|
||||||
/usr/share/OVMF/OVMF_CODE.fd; do
|
/usr/share/OVMF/OVMF_CODE.fd; do
|
||||||
[ -f "$c" ] && { OVMF="$c"; break; }
|
[ -f "$c" ] && { OVMF="$c"; break; }
|
||||||
|
|||||||