Compare commits
26 Commits
v1
...
86802f244e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86802f244e | ||
|
|
f3325385c1 | ||
|
|
aed41793f8 | ||
|
|
685126ab47 | ||
|
|
c8d0b09044 | ||
|
|
c2f90c7d0a | ||
|
|
5747dc9839 | ||
|
|
d1344712b8 | ||
|
|
37204f5f45 | ||
| 97b5944dc1 | |||
| 0c483f9512 | |||
| dfb57c2e34 | |||
| 995810927d | |||
| e1cf190dd2 | |||
|
|
70334e68bb | ||
|
|
6a4af69b0f | ||
|
|
d9466d6555 | ||
|
|
0e42763aea | ||
|
|
a6d6860054 | ||
|
|
5c43a93285 | ||
|
|
09c308b93c | ||
|
|
bdf20f2d8e | ||
|
|
c57d26864e | ||
|
|
019fdfc8bb | ||
|
|
c2281dbc61 | ||
|
|
b4fe52261b |
40
README.md
@@ -182,6 +182,13 @@ behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
|
|||||||
assignment wins, and bind/exec-once lists concatenate. Full guide with
|
assignment wins, and bind/exec-once lists concatenate. Full guide with
|
||||||
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||||||
|
|
||||||
|
**Where each option goes.** `nomarchy.system.*`, `nomarchy.hardware.*`, and
|
||||||
|
`nomarchy.services.*` are NixOS options — set them in `system.nix`. Everything
|
||||||
|
else under `nomarchy.*` is a Home Manager option — set it in `home.nix`. The
|
||||||
|
two tables below are split along exactly that line.
|
||||||
|
|
||||||
|
**`home.nix`** (Home Manager — the desktop):
|
||||||
|
|
||||||
| Option | Default | Purpose |
|
| Option | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
|
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
|
||||||
@@ -190,7 +197,8 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
|||||||
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
| `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.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.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.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; off by default — enable it from the menu (System › Night light; the first enable rebuilds), then toggle on/off instantly from the menu or the Waybar moon indicator (writes `settings.nightlight.on` in your flake, no rebuild) and it survives reboot, so it stays reproducible |
|
||||||
|
| `nomarchy.updates.enable` | `false` | Passive update awareness: a background check (`.interval`, default daily) that flags when flake inputs (nixpkgs, the Nomarchy input, …) are behind upstream — and, with Flatpak on, when apps have updates (`.flatpak`) — via a Waybar indicator + notification. Never changes anything; click the indicator to run the upgrade flow |
|
||||||
| `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.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
|
||||||
@@ -207,6 +215,11 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
|||||||
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
|
| `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.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 |
|
||||||
|
|
||||||
|
**`system.nix`** (NixOS — the machine):
|
||||||
|
|
||||||
|
| Option | Default | Purpose |
|
||||||
|
|---|---|---|
|
||||||
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
||||||
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
||||||
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
||||||
@@ -219,6 +232,14 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
|||||||
| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present |
|
| `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.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.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` |
|
||||||
|
| `nomarchy.hardware.intel.enable` | `false` | Intel enablement above nixos-hardware (GuC/HuC via `i915.enable_guc=3`); the installer sets it on an Intel CPU/GPU |
|
||||||
|
| `nomarchy.hardware.intel.computeRuntime` | `false` | Opt-in: Intel GPU compute — OpenCL/Level-Zero (`intel-compute-runtime`) + oneVPL (`vpl-gpu-rt`) |
|
||||||
|
| `nomarchy.hardware.amd.enable` | `false` | AMD enablement above nixos-hardware (amd-pstate EPP + radeonsi VA-API); installer-set on an AMD CPU/GPU |
|
||||||
|
| `nomarchy.hardware.amd.rocm.enable` | `false` | Opt-in: ROCm HIP/OpenCL GPU compute (multi-GB); pair with `.gfxOverride` (e.g. `"11.0.0"`) for an unlisted iGPU |
|
||||||
|
| `nomarchy.hardware.fingerprint.enable` | `false` | fprintd for a detected fingerprint reader (installer-set); enroll with `fprintd-enroll` |
|
||||||
|
| `nomarchy.hardware.fingerprint.pam` | `false` | Opt-in: use the fingerprint for login + sudo (PAM) |
|
||||||
|
| `nomarchy.hardware.npu.enable` | `false` | Opt-in/experimental: load the on-die NPU driver (`amdxdna`/`intel_vpu`); userspace runtime is BYO |
|
||||||
|
| `nomarchy.hardware.latestKernel` | `false` | Opt-in: ship `linuxPackages_latest` instead of the default kernel — for very new hardware whose drivers landed recently |
|
||||||
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN (then `sudo tailscale up`) |
|
| `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.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.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
|
||||||
@@ -271,6 +292,23 @@ imperative; nothing in Nix consumes the path): applied at session start and
|
|||||||
after every switch via a tiny activation hook, cycled instantly with
|
after every switch via a tiny activation hook, cycled instantly with
|
||||||
`bg next`.
|
`bg next`.
|
||||||
|
|
||||||
|
### Config the menu writes (not just themes)
|
||||||
|
|
||||||
|
The in-flake-state model isn't only for appearance. **Feature toggles you flip
|
||||||
|
from the menu are written into a `settings.*` section of the *same* state file**
|
||||||
|
— git-tracked, reproducible, never stashed in `~/.local/state`. The menu is just
|
||||||
|
an ergonomic writer for your flake, so version-controlling your downstream
|
||||||
|
reproduces the machine, settings and all. Where a toggle can take effect without
|
||||||
|
a rebuild it does: the menu writes the key (`--no-switch`) and flips the running
|
||||||
|
service, which reads the *live* flake state at session start, so the choice is
|
||||||
|
both instant and survives reboot.
|
||||||
|
|
||||||
|
**Night light** is the first to use this — enable it from the menu (System ›
|
||||||
|
Night light; the first enable rebuilds to install hyprsunset), then on/off is
|
||||||
|
instant. Internally it's two keys: `settings.nightlight.installed` (sticky —
|
||||||
|
gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the
|
||||||
|
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
|
||||||
|
|
||||||
### Per-theme app assets (`themes/<slug>/`)
|
### Per-theme app assets (`themes/<slug>/`)
|
||||||
|
|
||||||
Recoloring covers 95% of theming; the rest is one optional assets directory
|
Recoloring covers 95% of theming; the rest is one optional assets directory
|
||||||
|
|||||||
311
docs/ROADMAP.md
@@ -51,6 +51,11 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
one-off — any new feature that earns a menu entry must be placed in the
|
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
|
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.
|
direct `SUPER+CTRL+<mnemonic>` bind and self-gating as applicable.
|
||||||
|
- ✓ **Back everywhere:** every list menu now ends with a `↩ Back` entry that
|
||||||
|
returns one level up (power/theme/power-profile/clipboard/files/capture,
|
||||||
|
not just Tools/System), so you never have to Esc out and reopen. A `back`
|
||||||
|
helper + a shared `BACK` label keep it uniform; matched exactly so it can't
|
||||||
|
collide with clipboard/filename content. Esc still quits instantly.
|
||||||
- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the
|
- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the
|
||||||
expression blind (result only in the *next* menu's `-mesg`) and `qalc -t`
|
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-
|
misparsed common phrasings (`15% of 200` → `rem(15, 1 B)`, the natural-
|
||||||
@@ -67,25 +72,130 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
launcher, and yazi (SUPER+E) already covers real browsing.
|
launcher, and yazi (SUPER+E) already covers real browsing.
|
||||||
- **More menu modules from rofi tools:** the script-based counterparts
|
- **More menu modules from rofi tools:** the script-based counterparts
|
||||||
(run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate
|
(run via `rofi -dmenu`, like the hand-rolled modules), each a deliberate
|
||||||
replacement of an existing flow: **rofi-network-manager** (a keyboard
|
replacement of an existing flow.
|
||||||
wifi/VPN picker vs today's `nmtui`-in-terminal `network`), **rofi-rbw /
|
- ✓ **Network** (`networkmanager_dmenu`): a native rofi wifi/VPN picker
|
||||||
rofi-pass** (a secrets module — Bitwarden via rbw, or `pass` — pairs with
|
replacing the old `nmtui`-in-terminal `network` flow. Configured (xdg
|
||||||
`keys.nix`), and **rofi-pulse-select** (an audio sink/source switcher).
|
`networkmanager-dmenu/config.ini`) to drive rofi with `rofi_highlight`
|
||||||
Decide per-module whether it earns replacing the current path.
|
for connected/available rows; editing a connection drops to nmtui in the
|
||||||
|
terminal. Inherits the generated theme like every other module.
|
||||||
|
- ✓ **Audio** (`rofi-pulse-select`): a PipeWire/pulse sink/source switcher
|
||||||
|
under System → Audio (Output/Input), self-gated on the pulse socket.
|
||||||
|
- Deferred: **rofi-rbw / rofi-pass** secrets module — no Bitwarden/`pass`
|
||||||
|
setup here today (secrets are gpg/ssh + gnome-keyring, see `keys.nix`),
|
||||||
|
so it'd mean adopting a new secret manager. Revisit if that changes.
|
||||||
|
- Also: menu search is now **case-insensitive fuzzy** (`matching = fuzzy`,
|
||||||
|
`sorting-method = fzf`) across every module + the launcher.
|
||||||
|
- ✓ **Printer setup menu module:** a `nomarchy-menu printers` entry in the
|
||||||
|
**System** submenu opens **system-config-printer** (the CUPS admin GUI —
|
||||||
|
discovery, drivers/PPDs, options, test page), mirroring the bluetooth/blueman
|
||||||
|
pattern: the printing service ships the package (`environment.systemPackages`)
|
||||||
|
and the menu execs it, self-gated on the `system-config-printer` binary so
|
||||||
|
the entry appears only when `nomarchy.services.printing` is on. No direct
|
||||||
|
`SUPER+CTRL` bind — printer setup is a rare one-off, not a frequent utility
|
||||||
|
(left out deliberately; easy to add). Chose the GUI over a rofi-native
|
||||||
|
`lpadmin` flow (driver/PPD picking is impractical in rofi) and the CUPS web
|
||||||
|
UI (an unthemed browser page). Validated: the generated menu script's
|
||||||
|
`bash -n` build check passes, and an eval confirms printing-on puts
|
||||||
|
system-config-printer in `systemPackages`. Pending an on-machine check.
|
||||||
|
- **VPN setup & management menu:** a dedicated **System → VPN** flow that goes
|
||||||
|
past today's Network module (`networkmanager_dmenu` only connects/disconnects
|
||||||
|
*existing* VPN connections) to a guided **setup + management** surface across
|
||||||
|
the three common kinds:
|
||||||
|
- **WireGuard:** import a `.conf` into NetworkManager (`nmcli connection import
|
||||||
|
type wireguard file …` — NM handles wg tunnels natively, no extra plugin)
|
||||||
|
and toggle it up/down.
|
||||||
|
- **OpenVPN:** import an `.ovpn` (`nmcli connection import type openvpn file …`),
|
||||||
|
which needs the `networkmanager-openvpn` plugin shipped — gate the import
|
||||||
|
entry on it (the wireguard path has no such dependency).
|
||||||
|
- **Tailscale:** `tailscale up/down`, status, and **exit-node** selection — it
|
||||||
|
lives outside NetworkManager, so the menu drives the `tailscale` CLI
|
||||||
|
directly, self-gated on `nomarchy.services.tailscale` (already an opt-in
|
||||||
|
service) like the other hardware-gated System entries.
|
||||||
|
Shape: a `nomarchy-menu vpn` rofi submenu under **System** (Connect/Disconnect
|
||||||
|
with each connection's live up/down state like the wifi picker · Import config
|
||||||
|
via the existing Files/`fd` picker pattern · the Tailscale block when present),
|
||||||
|
ending in `↩ Back`. A self-gating Waybar `custom/vpn` indicator (shield glyph
|
||||||
|
while a tunnel or Tailscale is up) for parity with the other status modules,
|
||||||
|
added to the summer whole-swaps too. Secrets stay in the connection manager's
|
||||||
|
own store (NetworkManager / Tailscale state) — no new secret manager (cf. the
|
||||||
|
deferred rofi-rbw/pass note above). Open question: how much in-menu *creation*
|
||||||
|
(vs import) to support — a from-scratch WireGuard keypair/peer editor is a lot
|
||||||
|
of rofi surface, so likely import-first, deferring creation to
|
||||||
|
`nm-connection-editor`. Complements the existing Network item rather than
|
||||||
|
replacing it.
|
||||||
- **Theme parity with legacy:** summer-day/night now carry their legacy
|
- **Theme parity with legacy:** summer-day/night now carry their legacy
|
||||||
bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script
|
bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script
|
||||||
modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3,
|
modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3,
|
||||||
logo button opens nomarchy-menu); the other four identity themes are
|
logo button opens nomarchy-menu); the other four identity themes are
|
||||||
palette recolors and already match. Remaining: a visual pass over all
|
palette recolors and already match. Remaining: a visual pass over all
|
||||||
six on the live ISO
|
six on the live ISO
|
||||||
- **Per-theme rofi identity:** the `themes/<slug>/rofi.rasi` whole-swap
|
- ✓ **Per-theme rofi identity:** the `themes/<slug>/rofi.rasi` whole-swap
|
||||||
ships, and summer-day/night carry their legacy designs (inverted window,
|
ships, and all six identity themes now carry a designed `.rasi`. summer-day/
|
||||||
green inputbar, yellow bottom-border). Remaining: author `.rasi`
|
night keep their legacy ports (inverted window, green inputbar, yellow
|
||||||
identities for the other four ported themes if/when they want one (the
|
bottom-border); the other four were authored from each theme's character
|
||||||
generated palette theme is the default and looks fine)
|
(no legacy layout to port — their waybar whole-swaps are palette-only):
|
||||||
- **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB
|
**nord** a soft rounded "frost panel" (frost border, brighter-frost
|
||||||
re-copy on every state write is the main eval tax), then pre-built theme
|
selection, aurora-purple prompt); **retro-82** a sharp CRT terminal (square
|
||||||
variants if still needed
|
corners, amber-on-navy, a teal scanline underline, mono); **lumon** a
|
||||||
|
clinical cyan "bezel" (thick cyan frame, a *framed*-not-filled readout
|
||||||
|
inputbar, mono); **kanagawa** ink-and-paper (warm washi-paper frame, a
|
||||||
|
deeper ink-well inputbar, crystal-blue wave reserved for the selection).
|
||||||
|
Each is self-contained (it replaces the generated theme) and keeps the
|
||||||
|
element structure the theme-grid picker's per-invocation `-theme-str`
|
||||||
|
layers onto; all four parse clean under `rofi -dump-theme`. The generated
|
||||||
|
palette theme stays the default for the other 15 presets. Remaining: a
|
||||||
|
visual pass over the four on hardware (the parse check confirms syntax, not
|
||||||
|
aesthetics).
|
||||||
|
- ✓ **Visual theme picker (preview thumbnails):** `nomarchy-menu theme` is now
|
||||||
|
a rofi **icon grid of real desktop previews** instead of a plain-text list —
|
||||||
|
each theme a screenshot of its themed desktop (waybar + floating terminal),
|
||||||
|
pretty name beneath, **grouped dark-first then light** in one scrollable grid
|
||||||
|
(the previews make the mode obvious, so no light/dark submenu split), the
|
||||||
|
active theme marked `✓`, ending in `↩ Back`. The grid, the Name→slug map and
|
||||||
|
the active mark are all **generated at eval time** from the preset JSONs in
|
||||||
|
`rofi.nix` (`builtins.readDir` + `fromJSON`); "active" is just `t.slug`, since
|
||||||
|
every switch rebuilds the menu. Grid layout via a per-invocation `-theme-str`
|
||||||
|
(`listview { columns: 3; flow: horizontal; }` so Down scrolls row-by-row, +
|
||||||
|
vertical `element` cards, name centred below). **Sizing gotcha (learned the
|
||||||
|
hard way):** rofi's `element-icon` `size` is a **single value → a square
|
||||||
|
cell** (a two-value `WxH` is silently collapsed), and the icon is *contained*
|
||||||
|
in that square. So a 16:9 preview letterboxes (theme-coloured bands top/
|
||||||
|
bottom), and a cell can't be shorter-than-square without the bands returning.
|
||||||
|
The fix: a build-time imagemagick step **centre-crops each preview to a
|
||||||
|
square** so it fills the cell edge-to-edge; the window width is derived so a
|
||||||
|
column is exactly the icon side (no slack margins). One knob, `themeGridIconW`
|
||||||
|
(240px), drives both icon and window. **Workflow:** Bernardo captures previews
|
||||||
|
on real hardware and commits them as `themes/<slug>/preview.png`, **already
|
||||||
|
downscaled to 480×270** (~2.4 MB total for all 21, vs ~28 MB full-res) — the
|
||||||
|
source stays 16:9 and untouched; only the *displayed* thumb is squared at
|
||||||
|
build, so the crop is reversible. Graceful fallback when a theme has no
|
||||||
|
`preview.png`: a plain-name row, so it degrades cleanly.
|
||||||
|
(A headless VM-render route — `runNixOSTest` + software-GL Hyprland
|
||||||
|
(`LIBGL_ALWAYS_SOFTWARE` on virtio-gpu) + `machine.screenshot()` QMP
|
||||||
|
framebuffer dump — was prototyped 2026-06-19 and **works** (themed waybar
|
||||||
|
rendered + captured); real-hardware capture was chosen for fidelity +
|
||||||
|
simplicity, with the VM route as a documented fallback if hand-capturing all
|
||||||
|
themes gets tedious.)
|
||||||
|
- **Faster switches:** move `backgrounds/` out of the flake source. Diagnosis
|
||||||
|
(confirmed): `themes/` is 86 MB and `backgrounds/` is **all** of it — the
|
||||||
|
palette JSONs + per-theme overrides are only ~208 KB, and the wallpapers are
|
||||||
|
**never read at Nix eval** (only the Python `swww` path uses them). But
|
||||||
|
`theme-state.json` is git-tracked, so every `apply` rewrites it → the flake
|
||||||
|
tree changes → Nix re-copies the whole 86 MB source before `home-manager
|
||||||
|
switch` can evaluate. You pay an 86 MB copy to change a 1 KB file.
|
||||||
|
**Decided approach (deferred — don't want the extra moving part yet):**
|
||||||
|
Option 1, a **separate pinned wallpapers artifact** — a `Nomarchy-wallpapers`
|
||||||
|
repo or release tarball, pulled once via a flake input / `fetchurl` (pinned
|
||||||
|
by hash → content-addressed, never re-copied on a state write), with
|
||||||
|
`nomarchy-theme-sync` reading wallpapers from that stable store path (the
|
||||||
|
`NOMARCHY_DEFAULT_THEMES` env hook already anticipates external theme
|
||||||
|
assets). Keeps eval **pure**; the live ISO still bakes them in (fetched at
|
||||||
|
build). A state write then re-copies only ~208 KB. Rejected alternative:
|
||||||
|
moving `theme-state.json` out to `~/.config` + `--impure` eval (one repo, no
|
||||||
|
second artifact, but trades away the in-tree-pinned-state reproducibility).
|
||||||
|
Follow-on if `home-manager switch` itself is still the bottleneck after the
|
||||||
|
copy is gone: **pre-built theme variants** (build each theme's generation
|
||||||
|
ahead of time so a switch just activates a cached one).
|
||||||
- Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since
|
- Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since
|
||||||
v1: `nomarchy.system.plymouth.*`, background tinted from the state file)
|
v1: `nomarchy.system.plymouth.*`, background tinted from the state file)
|
||||||
- Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy
|
- Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy
|
||||||
@@ -263,6 +373,31 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
Complements nixos-hardware (model quirks) rather than replacing it; keep
|
Complements nixos-hardware (model quirks) rather than replacing it; keep
|
||||||
the detection in the installer's `hardware-db` so an installed machine
|
the detection in the installer's `hardware-db` so an installed machine
|
||||||
bakes the right defaults, all overridable through `nomarchy.hardware.*`.
|
bakes the right defaults, all overridable through `nomarchy.hardware.*`.
|
||||||
|
- ✓ **Mechanism + broad seed shipped** (`modules/nixos/hardware.nix`): a
|
||||||
|
vendor-keyed `nomarchy.hardware.*` surface (`intel`, `amd`, `fingerprint`,
|
||||||
|
`npu`), `hardware-db.sh` extended to detect Intel/AMD, a fingerprint reader
|
||||||
|
(libfprint USB vendor IDs) and an NPU (Intel VPU / AMD XDNA PCI IDs), and
|
||||||
|
the installer bakes the matching toggles into `system.nix` — safe defaults
|
||||||
|
active (GuC/HuC, amd-pstate, AMD VA-API env, fprintd), heavy/experimental
|
||||||
|
opt-ins commented (Intel compute-runtime, ROCm, fingerprint PAM, NPU
|
||||||
|
driver). Audited against the nixos-hardware commons so it only fills the
|
||||||
|
gap above them — microcode / the media-driver VA-API stack / weekly fstrim
|
||||||
|
already come from `common-cpu-*` / `common-gpu-*` / `common-pc-ssd`.
|
||||||
|
Exercised live on the dev machine's detection (an AMD Ryzen-AI laptop: AMD
|
||||||
|
+ fingerprint + NPU all detected) and a toggles-on build (`amd_pstate=active`
|
||||||
|
+ `i915.enable_guc=3` in kernel-params, `fprintd.service` present).
|
||||||
|
**Remaining: on-hardware verification of the runtime bits (AMD/NPU/Intel
|
||||||
|
GPU-compute) — none are testable in CI or on the Intel Latitudes for the
|
||||||
|
AMD paths.** SSD TRIM is intentionally left to `common-pc-ssd`.
|
||||||
|
- ✓ **Hardening (driver-gen + kernel awareness):** `intel.guc` emits the
|
||||||
|
*i915* param, so the installer turns it off when the GPU is on the newer
|
||||||
|
`xe` driver (Lunar Lake / Battlemage / Panther Lake — GuC is default-on
|
||||||
|
there); the NPU detector matches the PCI *accelerator class* (not just a
|
||||||
|
device-ID list) so new gens are caught without a code change; and a
|
||||||
|
`nomarchy.hardware.latestKernel` escape hatch (+ a build-time warning when
|
||||||
|
`npu.enable` predates the shipped kernel) covers very-new hardware whose
|
||||||
|
drivers only just landed. A config-assertion VM test guards the wiring
|
||||||
|
(`checks.hardware-toggles`: kernel cmdline + fprintd + PAM, booted in a VM).
|
||||||
Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
|
Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
|
||||||
- **GPU compute — ROCm:** the 780M is an RDNA3 iGPU (gfx1103) ROCm doesn't
|
- **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
|
officially list, so it needs `HSA_OVERRIDE_GFX_VERSION=11.0.0`. Multi-GB
|
||||||
@@ -353,10 +488,60 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
|
warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
|
||||||
drives the HM `services.hyprsunset` with two time-based profiles
|
drives the HM `services.hyprsunset` with two time-based profiles
|
||||||
(`sunrise` → identity, `sunset` → `temperature`), so hyprsunset handles the
|
(`sunrise` → identity, `sunset` → `temperature`), so hyprsunset handles the
|
||||||
schedule and the on-login state. Needs an on-hardware check that hyprsunset
|
schedule and the on-login state. Active-profile-at-session-start was
|
||||||
applies the active profile at session start. Remaining (optional): a menu +
|
confirmed on hardware (2026-06-18). ✓ **Menu + Waybar toggle (opt-in,
|
||||||
Waybar toggle to force it on/off, and geo (lat/long) auto sunset/sunrise
|
instant, in-flake state):** two git-tracked keys in the state file, both
|
||||||
(would mean wlsunset, which schedules by location).
|
menu-written (exposed via the new `nomarchy.settings` option) —
|
||||||
|
`settings.nightlight.installed` (**sticky**: gates the hyprsunset unit;
|
||||||
|
`nomarchy.nightlight.enable` `mkDefault`-reads it) and
|
||||||
|
`settings.nightlight.on` (runtime on/off). **Off by default.** The *first*
|
||||||
|
enable from the menu writes `installed` and rebuilds to create the unit (the
|
||||||
|
one accepted rebuild); **every toggle after is instant** —
|
||||||
|
`nomarchy-theme-sync set settings.nightlight.on … --no-switch` (atomic +
|
||||||
|
`git add -N`, no rebuild) plus a `systemctl` start/stop. Splitting the sticky
|
||||||
|
flag from the on/off is what avoids a **decay** bug: an instant-off writes
|
||||||
|
only `on`, so an unrelated later rebuild (e.g. `sys-update`) never drops the
|
||||||
|
unit and "on" stays instant. Persistence across logout/reboot comes from an
|
||||||
|
`ExecCondition` on the unit (`nomarchy-nightlight should-start`) that reads the
|
||||||
|
**live** working-tree on/off at start time — *not* the eval-frozen store copy
|
||||||
|
— so an off survives a reboot with no rebuild; a later rebuild bakes the same
|
||||||
|
value. No more `~/.local/state` marker, no marker `ConditionPathExists`. The
|
||||||
|
`nomarchy-menu` System-submenu entry is always shown (current on/off) so the
|
||||||
|
feature can be enabled from the menu; the self-gating Waybar
|
||||||
|
`custom/nightlight` indicator shows the moon while running and hides otherwise.
|
||||||
|
This is the first cut of a broader principle — **any user-settable config
|
||||||
|
gets a menu writer that lands it in the downstream flake; no state lives
|
||||||
|
outside the checkout** (Phase 1 night-light + Phase 2 the per-device
|
||||||
|
keyboard-layout memory below are done — both now in the git-tracked state
|
||||||
|
file; next target: opt-in auto-commit of the flake on each mutation, owned
|
||||||
|
files only). Remaining (optional): geo (lat/long) auto
|
||||||
|
sunset/sunrise (would mean wlsunset, which schedules by location). Pending an
|
||||||
|
on-machine check (first enable rebuilds + comes on; later toggles instant; off
|
||||||
|
persists across reboot via ExecCondition; stopping hyprsunset restores gamma).
|
||||||
|
- ✓ **Automatic timezone (location-following clock):** `nomarchy.system.autoTimezone`
|
||||||
|
(opt-in, off by default) — geoclue + `services.automatic-timezoned` drive
|
||||||
|
`/etc/localtime` from your location, so travelling to another zone updates the
|
||||||
|
Waybar clock on its own. **Menu-driven, in-flake state** (the night-light /
|
||||||
|
keyboard philosophy): the flag is `settings.autoTimezone` in theme-state.json,
|
||||||
|
git-tracked; the System-menu entry (`nomarchy-menu autotimezone` →
|
||||||
|
`nomarchy-autotimezone`) writes it and rebuilds. **Not instant like
|
||||||
|
night-light** — it's a *system* service (not a user unit you can start/stop),
|
||||||
|
so the toggle drives a `sudo nixos-rebuild` (bakes the service + the
|
||||||
|
`time.timeZone` override) plus a `home-manager switch` (the Waybar-refresh
|
||||||
|
watcher), both off the one flag. **time.timeZone handling:** a runtime zone
|
||||||
|
needs `/etc/localtime` writable; automatic-timezoned sets `time.timeZone = null`
|
||||||
|
itself, but the installer's static value would collide (a hard eval error), so
|
||||||
|
the module forces it null (`mkForce`) over the installer's value when enabled,
|
||||||
|
and reverts to it when disabled. **Live clock refresh:** Waybar's clock module
|
||||||
|
captures the zone at construction, so `modules/home/timezone.nix` runs a tiny
|
||||||
|
user service that watches timedate1's change signal and reloads Waybar
|
||||||
|
(SIGUSR2) on a real zone change (also catches a manual `timedatectl
|
||||||
|
set-timezone`). Eval-verified both ways (on: geoclue+daemon on, tz forced null
|
||||||
|
over a static installer value, watcher present; off: static tz intact, no
|
||||||
|
service/watcher). **Pending an on-hardware check** — geoclue detection and the
|
||||||
|
SIGUSR2 clock refresh aren't testable in CI (if SIGUSR2 proves insufficient,
|
||||||
|
fall back to restarting waybar). Remaining (optional): a Waybar tooltip line
|
||||||
|
showing the detected zone.
|
||||||
- **Keyboard layouts (per-device + switching):**
|
- **Keyboard layouts (per-device + switching):**
|
||||||
- ✓ **Per-device declarative layout:** `nomarchy.keyboard.devices`
|
- ✓ **Per-device declarative layout:** `nomarchy.keyboard.devices`
|
||||||
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
|
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
|
||||||
@@ -372,15 +557,29 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
watch` daemon runs (exec-once): it polls `hyprctl devices`, and when a
|
watch` daemon runs (exec-once): it polls `hyprctl devices`, and when a
|
||||||
keyboard connects *after* login that isn't in `keyboard.devices` and
|
keyboard connects *after* login that isn't in `keyboard.devices` and
|
||||||
hasn't been chosen before, it pops a rofi layout picker, applies the
|
hasn't been chosen before, it pops a rofi layout picker, applies the
|
||||||
choice with `hyprctl switchxkblayout` (an index into the candidate set,
|
choice as a **per-device** `hyprctl keyword device[<name>]:kb_layout` (the
|
||||||
which `input.kb_layout` carries), and remembers it per-device in
|
runtime twin of the declarative `device{}` blocks — so it isolates that
|
||||||
`~/.local/state/nomarchy/keyboard-layouts` — re-applied silently on later
|
one keyboard and never touches the built-in board), and remembers it. The
|
||||||
reconnects. The boot-time set (incl. the built-in keyboard) is never
|
boot-time set (incl. the built-in keyboard) is never prompted. The
|
||||||
prompted. The runtime-remember complement to the declarative
|
runtime-remember complement to the declarative `keyboard.devices`.
|
||||||
`keyboard.devices`; a stateful runtime piece by design. **Pending an
|
**Picker verified on hardware (2026-06-18)**; the first cut applied the
|
||||||
on-hardware test** (hotplug isn't verifiable in CI). Remaining (bonus):
|
choice with `switchxkblayout` (a global layout-index flip) and merged the
|
||||||
offer to write a choice into `keyboard.devices` so it graduates to the
|
candidate pool into `input.kb_layout`, so a selection leaked onto the
|
||||||
reproducible config.
|
laptop keyboard and stuck after unplug — fixed by the per-device keyword +
|
||||||
|
keeping the pool out of the session layout.
|
||||||
|
- ✓ **In-flake state + graduation (2026-06-23):** the remembered picks moved
|
||||||
|
out of `~/.local/state/nomarchy/keyboard-layouts` into the git-tracked
|
||||||
|
state file (`settings.keyboard.devices`, a device-name → layout map) —
|
||||||
|
Phase 2 of the in-flake-state principle, the same path night-light took.
|
||||||
|
The watcher reads the **live** working tree (a pick is honoured at once)
|
||||||
|
and writes instantly with `--no-switch` (no rebuild), merging the whole map
|
||||||
|
back at the dot-free parent path so a device name containing a dot can't
|
||||||
|
corrupt the dotted set-path. Each remembered device **graduates** into
|
||||||
|
`nomarchy.keyboard.devices` on the next rebuild (a generated Hyprland
|
||||||
|
`device{}` block, `mkDefault` so a hand-written entry still wins), after
|
||||||
|
which the watcher sees it as declared and steps back. Eval-verified
|
||||||
|
(graduation + precedence) and the writer round-trips; **still needs the
|
||||||
|
on-hardware hotplug re-verify** (the picker path isn't testable in CI).
|
||||||
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
|
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
|
||||||
`grp:` options) for switching layouts on one keyboard; add the
|
`grp:` options) for switching layouts on one keyboard; add the
|
||||||
`hyprland/language` module to the summer whole-swap themes for parity
|
`hyprland/language` module to the summer whole-swap themes for parity
|
||||||
@@ -410,15 +609,57 @@ how to override it. Items marked ✓ are shipped.
|
|||||||
artifact and will crash on hardware too). The menu wiring is correct and
|
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 app is installed, but the GUI doesn't open. The snapshot *backend*
|
||||||
(the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning
|
(the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning
|
||||||
its libbtrfsutil, or wait for an upstream bump; meanwhile the menu entry
|
its libbtrfsutil, or wait for an upstream bump. ✓ **Fallback shipped:**
|
||||||
is a no-op when the binary crashes. Fallback if it lingers: a rofi-based
|
the menu now launches `nomarchy-snapshots` (system-side, gated on
|
||||||
snapshot menu (no btrfs-assistant dependency).
|
`nomarchy.system.snapper`) instead — a keyboard-driven snapper
|
||||||
- Remaining (optional): a keyboard-driven rofi browse for quick glances;
|
browser/restore with no btrfs-assistant dependency. snapper is root-only
|
||||||
boot-from-snapshot needs a systemd-boot equivalent of grub-btrfs.
|
here (no `ALLOW_USERS`) and rofi can't run as root under Wayland, so it
|
||||||
- **Update awareness:** updates are manual today (`sys-update`/`home-update`);
|
runs in a terminal via `sudo` (one password prompt) and uses `fzf` to pick:
|
||||||
add a Waybar indicator / notification when flake inputs are stale or a new
|
browse/diff (read-only), restore files (`undochange`), or roll back (root
|
||||||
nixpkgs rev is available (optionally with a pending-change count). Augments,
|
config only) — each behind a typed-`yes` confirmation, which is *safer*
|
||||||
never replaces, the explicit rebuild flow.
|
than the fat-fingerable one-click rofi rollback this entry originally
|
||||||
|
avoided. btrfs-assistant stays installed for when nixpkgs fixes it.
|
||||||
|
**Pending an on-machine check of the restore/rollback paths.**
|
||||||
|
- Remaining (optional): boot-from-snapshot needs a systemd-boot equivalent
|
||||||
|
of grub-btrfs.
|
||||||
|
- ✓ **Update awareness:** `nomarchy.updates.enable` (opt-in, `modules/home/
|
||||||
|
updates.nix`) — a `nomarchy-updates` checker on a systemd user timer
|
||||||
|
(`.interval`, default daily) compares each **direct** branch-tracking flake
|
||||||
|
input (nixpkgs, the Nomarchy input, home-manager, stylix … via `git
|
||||||
|
ls-remote` vs the locked rev; offline → skipped, no false alarm) and, when
|
||||||
|
the `flatpak` CLI is present (`.flatpak`), counts Flatpak updates. A
|
||||||
|
self-gating Waybar `custom/updates` indicator (hidden until something's
|
||||||
|
available; accent ` N`, signal-refreshed) + a notification that fires only
|
||||||
|
when the count *grows* (so a daily timer never nags). Click → the upgrade
|
||||||
|
flow in a terminal (`sys-update` / `flatpak update`, each confirmed). Purely
|
||||||
|
passive — it never changes anything, augmenting the explicit rebuild flow.
|
||||||
|
Indicator is in the generated bar and both summer whole-swaps. **Pending an
|
||||||
|
on-machine check** (the ls-remote/notify/timer path needs a live session).
|
||||||
|
- **Automated upstream lock bumps (maintainer CI):** the upstream twin of the
|
||||||
|
user-side checker above — automate advancing Nomarchy's *own* `flake.lock`,
|
||||||
|
which is the entire delivery channel (a downstream takes a single input,
|
||||||
|
`?ref=v1`, so nixpkgs/home-manager/stylix/nixos-hardware reach it
|
||||||
|
*transitively pinned* through that lock; users can't bump them independently).
|
||||||
|
**Tiered by what CI can't do:** the hardware-QA gate (the Latitude 5410 + the
|
||||||
|
AMD dev box — AMD/NPU/Intel-GPU-compute runtime bits aren't testable in CI or
|
||||||
|
on the Intel Latitudes for the AMD paths) can't be automated, so a bot drives
|
||||||
|
`main` and a human still promotes `main → v1`. A scheduled job (Forgejo
|
||||||
|
Actions, or Renovate's Nix manager opening a reviewable lock-diff PR) runs
|
||||||
|
`nix flake update` → `nix flake check` + the existing `runNixOSTest` suite +
|
||||||
|
builds `system.build.toplevel`, and on green lands on `main` — which nobody
|
||||||
|
tracks, so a bad bump costs a debug session, not a user outage; `v1` stays
|
||||||
|
the manual on-hardware gate (it only ever fast-forwards, never force-pushed,
|
||||||
|
or a user's locked rev can vanish from history). **Cadence weekly**
|
||||||
|
(release-26.05 doesn't move fast; nightly is just churn), and `nix flake
|
||||||
|
update` stays *within* the pinned release branches — no surprise major bump
|
||||||
|
(a 26.11 jump is a deliberate `v2`, hand-edited in flake.nix). Side benefit:
|
||||||
|
shrinks the security-patch latency the single-input model otherwise imposes
|
||||||
|
(users are gated on the maintainer for nixpkgs CVEs), since a fix is usually
|
||||||
|
already eval/build/VM-tested and one hardware promotion away from `v1` —
|
||||||
|
worth fast-laning lock-only/security bumps ahead of feature batches. The repo
|
||||||
|
has **no CI today** (manual `nix flake update` only). Explicitly *not* a
|
||||||
|
binary cache: compile-from-source is a deliberate values call (Gentoo-style),
|
||||||
|
so this automates the *config/lock* channel, not artifact distribution.
|
||||||
- **"nomarchy" control center:** a single TUI/GUI front-end over the common
|
- **"nomarchy" control center:** a single TUI/GUI front-end over the common
|
||||||
toggles — theme, power profile, opt-in services, display, DND — built on
|
toggles — theme, power profile, opt-in services, display, DND — built on
|
||||||
the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already
|
the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already
|
||||||
|
|||||||
25
flake.nix
@@ -134,6 +134,31 @@
|
|||||||
downstream.nixosConfigurations.default.config.system.build.toplevel;
|
downstream.nixosConfigurations.default.config.system.build.toplevel;
|
||||||
downstream-template-home =
|
downstream-template-home =
|
||||||
downstream.homeConfigurations.me.activationPackage;
|
downstream.homeConfigurations.me.activationPackage;
|
||||||
|
|
||||||
|
# Config-level VM assertions for the nomarchy.hardware.* toggles —
|
||||||
|
# what they SET (kernel cmdline, fprintd, PAM). Real hardware
|
||||||
|
# behaviour (firmware/driver/device) needs bare metal and is out of
|
||||||
|
# scope here. Imports only hardware.nix, so the VM stays minimal.
|
||||||
|
hardware-toggles = pkgs.testers.runNixOSTest {
|
||||||
|
name = "nomarchy-hardware-toggles";
|
||||||
|
nodes.machine = { ... }: {
|
||||||
|
imports = [ ./modules/nixos/hardware.nix ];
|
||||||
|
nomarchy.hardware = {
|
||||||
|
intel.enable = true;
|
||||||
|
amd.enable = true;
|
||||||
|
fingerprint = { enable = true; pam = true; };
|
||||||
|
npu.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
cmdline = machine.succeed("cat /proc/cmdline")
|
||||||
|
assert "amd_pstate=active" in cmdline, cmdline
|
||||||
|
assert "i915.enable_guc=3" in cmdline, cmdline
|
||||||
|
machine.succeed("systemctl cat fprintd.service")
|
||||||
|
machine.succeed("grep -q pam_fprintd /etc/pam.d/sudo")
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# ─── Reference host ────────────────────────────────────────────
|
# ─── Reference host ────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Nomarchy — Home Manager entry point.
|
# Nomarchy — Home Manager entry point.
|
||||||
# Consume this via homeModules.nomarchy (flake.nix), which also pulls in
|
# Consume this via homeModules.nomarchy (flake.nix), which also pulls in
|
||||||
# the stylix home module that stylix.nix configures.
|
# the stylix home module that stylix.nix configures.
|
||||||
{ config, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
./yazi.nix # flagship TUI file manager, themed + plugins
|
./yazi.nix # flagship TUI file manager, themed + plugins
|
||||||
./osd.nix # swayosd volume/brightness OSD, themed
|
./osd.nix # swayosd volume/brightness OSD, themed
|
||||||
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||||
|
./timezone.nix # keep the Waybar clock in step with auto-timezone changes
|
||||||
|
./updates.nix # passive update-awareness indicator + notification, opt-in
|
||||||
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
||||||
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
||||||
./fastfetch.nix # system info with the themed Nomarchy logo
|
./fastfetch.nix # system info with the themed Nomarchy logo
|
||||||
@@ -32,6 +34,16 @@
|
|||||||
services.network-manager-applet.enable = true;
|
services.network-manager-applet.enable = true;
|
||||||
xsession.preferStatusNotifierItems = true;
|
xsession.preferStatusNotifierItems = true;
|
||||||
|
|
||||||
|
# Standard XDG user directories (Downloads, Documents, Pictures, Music,
|
||||||
|
# Videos, Desktop, Public, Templates): written to user-dirs.dirs so file
|
||||||
|
# pickers/browsers resolve them, and created on activation so a fresh
|
||||||
|
# install lands with them present (not just on first app use). mkDefault
|
||||||
|
# so a downstream home.nix can flip it off or remap individual paths.
|
||||||
|
xdg.userDirs = {
|
||||||
|
enable = lib.mkDefault true;
|
||||||
|
createDirectories = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
|
||||||
home.stateVersion = "26.05";
|
home.stateVersion = "26.05";
|
||||||
|
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ let
|
|||||||
c = t.colors;
|
c = t.colors;
|
||||||
inherit (config.nomarchy.lib) rgb rgba;
|
inherit (config.nomarchy.lib) rgb rgba;
|
||||||
|
|
||||||
|
# nomarchy-theme-sync — the in-flake state writer the keyboard watcher uses
|
||||||
|
# to remember per-device layouts (same path night-light's toggle takes).
|
||||||
|
sync = lib.getExe config.nomarchy.package;
|
||||||
|
|
||||||
# swayosd's `--input-volume mute-toggle` draws the OSD but never flips the
|
# swayosd's `--input-volume mute-toggle` draws the OSD but never flips the
|
||||||
# source mute (verified on hardware: wpctl toggles the state, swayosd's
|
# 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
|
# input-mute path is a no-op — unlike its working output-mute path). Do the
|
||||||
@@ -55,10 +59,14 @@ let
|
|||||||
++ lib.optional (m.extra != "") m.extra
|
++ lib.optional (m.extra != "") m.extra
|
||||||
);
|
);
|
||||||
|
|
||||||
# Keyboard layout candidates: the primary nomarchy.keyboard.layout (split on
|
# Candidate layouts offered by the new-keyboard picker: the session
|
||||||
# commas) plus any nomarchy.keyboard.layouts. Drives input.kb_layout so all
|
# layout(s) (nomarchy.keyboard.layout, comma-split for a multi-layout
|
||||||
# of them are switchable, and feeds the watcher's picker.
|
# session) plus the extra nomarchy.keyboard.layouts pool. The pool is
|
||||||
kbLayouts = lib.unique (
|
# deliberately NOT merged into input.kb_layout — those candidates are for
|
||||||
|
# *external* keyboards and get applied per-device by the watcher's apply().
|
||||||
|
# Loading them onto the session keyboard is what let a global switch flip
|
||||||
|
# the built-in board to the wrong layout.
|
||||||
|
pickerLayouts = lib.unique (
|
||||||
(lib.splitString "," config.nomarchy.keyboard.layout)
|
(lib.splitString "," config.nomarchy.keyboard.layout)
|
||||||
++ config.nomarchy.keyboard.layouts
|
++ config.nomarchy.keyboard.layouts
|
||||||
);
|
);
|
||||||
@@ -68,21 +76,41 @@ let
|
|||||||
# keyboard.devices, not already remembered), ask for a layout and persist it
|
# keyboard.devices, not already remembered), ask for a layout and persist it
|
||||||
# per-device, re-applying silently on later reconnects. Stateful runtime
|
# per-device, re-applying silently on later reconnects. Stateful runtime
|
||||||
# piece — the complement to the declarative keyboard.devices. Reliable
|
# piece — the complement to the declarative keyboard.devices. Reliable
|
||||||
# primitives only: poll hyprctl devices, apply via switchxkblayout (an index
|
# primitives only: poll hyprctl devices, apply with a per-device
|
||||||
# into kbLayouts). NOTE: needs an on-hardware test (hotplug isn't verifiable
|
# `device[<name>]:kb_layout` keyword — the runtime twin of the declarative
|
||||||
# in CI).
|
# device blocks, so it isolates that one keyboard and never disturbs the
|
||||||
|
# built-in board (switchxkblayout flipped the *shared* layout, which leaked
|
||||||
|
# onto the laptop and stuck after unplug).
|
||||||
|
#
|
||||||
|
# The remembered choices live in the git-tracked in-flake state
|
||||||
|
# (settings.keyboard.devices), NOT ~/.local/state — read live from the
|
||||||
|
# working tree (so a pick this session is honoured at once) and written
|
||||||
|
# instantly with `--no-switch` (no rebuild). A later rebuild graduates them
|
||||||
|
# into nomarchy.keyboard.devices (generated device{} blocks), after which the
|
||||||
|
# watcher treats them as declared and steps back. NOTE: needs an on-hardware
|
||||||
|
# test (hotplug isn't verifiable in CI).
|
||||||
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
|
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
|
||||||
set -u
|
set -u
|
||||||
layouts="${lib.concatStringsSep " " kbLayouts}"
|
layouts="${lib.concatStringsSep " " pickerLayouts}"
|
||||||
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
|
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
|
||||||
state="''${XDG_STATE_HOME:-$HOME/.local/state}/nomarchy/keyboard-layouts"
|
sync=${sync}
|
||||||
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() { hyprctl keyword "device[$1]:kb_layout" "$2" >/dev/null 2>&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'; }
|
keyboards() { hyprctl devices -j | jq -r '.keyboards[].name'; }
|
||||||
is_declared() { case " $declared " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
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"; }
|
|
||||||
|
# The remembered map (device-name -> layout) from the LIVE in-flake state;
|
||||||
|
# an absent key (sparse state) reads as an empty object.
|
||||||
|
saved_map() { "$sync" get settings.keyboard.devices 2>/dev/null || echo '{}'; }
|
||||||
|
saved_for() { saved_map | jq -r --arg k "$1" '.[$k] // empty'; }
|
||||||
|
|
||||||
|
# Persist a pick INSTANTLY: merge it into the map and write the whole object
|
||||||
|
# back at the dot-free parent path, so a device name containing a dot can't
|
||||||
|
# corrupt the dotted set-path. --no-switch = write only, no rebuild.
|
||||||
|
remember() {
|
||||||
|
map=$(saved_map | jq -c --arg k "$1" --arg v "$2" '. + {($k): $v}') || return
|
||||||
|
"$sync" --quiet set settings.keyboard.devices "$map" --no-switch
|
||||||
|
}
|
||||||
|
|
||||||
# Startup: re-apply remembered layouts, never prompt — so the built-in
|
# Startup: re-apply remembered layouts, never prompt — so the built-in
|
||||||
# keyboard just stays on the session default.
|
# keyboard just stays on the session default.
|
||||||
@@ -106,7 +134,7 @@ let
|
|||||||
apply "$kb" "$s"
|
apply "$kb" "$s"
|
||||||
else
|
else
|
||||||
choice=$(printf '%s\n' $layouts | rofi -dmenu -p "Layout · $kb")
|
choice=$(printf '%s\n' $layouts | rofi -dmenu -p "Layout · $kb")
|
||||||
[ -n "$choice" ] && { printf '%s=%s\n' "$kb" "$choice" >> "$state"; apply "$kb" "$choice"; }
|
[ -n "$choice" ] && { remember "$kb" "$choice"; apply "$kb" "$choice"; }
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
prev="$cur"
|
prev="$cur"
|
||||||
@@ -146,7 +174,13 @@ in
|
|||||||
# 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";
|
# Waybar is launched here rather than as a systemd user service: bound
|
||||||
|
# to graphical-session.target the unit raced Hyprland's IPC on relogin
|
||||||
|
# and landed in `failed`; exec-once only fires once the compositor is
|
||||||
|
# up. Reloaded on theme switch via SIGUSR2 (nomarchy-theme-sync). See
|
||||||
|
# waybar.nix.
|
||||||
|
] ++ lib.optional config.nomarchy.waybar.enable "waybar"
|
||||||
|
++ 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:
|
# These flow from theme-state.json and stay at NORMAL priority:
|
||||||
@@ -201,8 +235,10 @@ in
|
|||||||
|
|
||||||
input = {
|
input = {
|
||||||
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
||||||
# option (the installer writes it; it also drives tty/LUKS).
|
# option (the installer writes it; it also drives tty/LUKS). Only the
|
||||||
kb_layout = lib.concatStringsSep "," kbLayouts;
|
# session layout(s) land here; the keyboard.layouts pool is applied
|
||||||
|
# per-device to external boards, never onto the session keyboard.
|
||||||
|
kb_layout = config.nomarchy.keyboard.layout;
|
||||||
kb_variant = config.nomarchy.keyboard.variant;
|
kb_variant = config.nomarchy.keyboard.variant;
|
||||||
follow_mouse = lib.mkDefault 1;
|
follow_mouse = lib.mkDefault 1;
|
||||||
touchpad.natural_scroll = lib.mkDefault true;
|
touchpad.natural_scroll = lib.mkDefault true;
|
||||||
@@ -266,11 +302,24 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Runtime-remembered per-device layouts (settings.keyboard.devices, written
|
||||||
|
# by the watcher into the in-flake state) graduate into the declarative
|
||||||
|
# keyboard.devices: each becomes a generated Hyprland device{} block —
|
||||||
|
# reproducible and applied natively on hotplug — after which the watcher sees
|
||||||
|
# it as declared and steps back. mkDefault so a hand-written
|
||||||
|
# keyboard.devices.<name> in home.nix still wins for the same keyboard.
|
||||||
|
nomarchy.keyboard.devices = lib.mapAttrs
|
||||||
|
(_name: layout: { layout = lib.mkDefault layout; })
|
||||||
|
config.nomarchy.settings.keyboard.devices;
|
||||||
|
|
||||||
# nwg-displays: interactive monitor arranger (applies live via hyprctl and
|
# nwg-displays: interactive monitor arranger (applies live via hyprctl and
|
||||||
# writes a config file). A helper to find values for nomarchy.monitors —
|
# 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
|
# the declarative config stays the source of truth (we don't source its
|
||||||
# output). Gated on its toggle; pointless without the Hyprland session.
|
# output). Gated on its toggle; pointless without the Hyprland session.
|
||||||
home.packages = lib.optionals
|
home.packages =
|
||||||
(config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
lib.optionals (config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
|
||||||
[ pkgs.nwg-displays ];
|
[ pkgs.nwg-displays ]
|
||||||
|
# The new-keyboard watcher on PATH (when enabled) so it's discoverable and
|
||||||
|
# runnable by name for debugging — Hyprland still exec-once's it by store path.
|
||||||
|
++ lib.optional (config.nomarchy.hyprland.enable && kbAutoSwitch) keyboardWatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,75 @@
|
|||||||
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
|
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
|
||||||
# gamma/temperature tool). Warm at night, identity (no shift) by day;
|
# gamma/temperature tool). Warm at night, identity (no shift) by day; the
|
||||||
# hyprsunset's time-based `profile` entries handle the schedule and pick the
|
# schedule (temperature/sunrise/sunset) is tuned via nomarchy.nightlight.* in
|
||||||
# right state on session start. Opt-in via nomarchy.nightlight.enable.
|
# home.nix and baked into the unit's time-based `profile`.
|
||||||
#
|
#
|
||||||
# The hyprsunset HM service module is provided by home-manager; this only
|
# Off by default and opt-in. Two git-tracked flags in the state file, both
|
||||||
# configures it. Override anything with plain services.hyprsunset.* options.
|
# menu-written (no ~/.local/state):
|
||||||
{ config, lib, ... }:
|
# settings.nightlight.installed — does the hyprsunset unit exist? Sticky; the
|
||||||
|
# option mkDefault-reads it, so the FIRST enable from the menu rebuilds (to
|
||||||
|
# create the unit) and an instant-off is never undone by a later rebuild.
|
||||||
|
# settings.nightlight.on — runtime on/off. Toggled INSTANTLY (write + systemctl,
|
||||||
|
# no rebuild); read by the unit's ExecCondition (should-start) at session
|
||||||
|
# start so the choice survives logout/reboot via the *live* state, not the
|
||||||
|
# eval-frozen store copy. A later rebuild bakes the same value (no divergence).
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.nomarchy.nightlight;
|
cfg = config.nomarchy.nightlight;
|
||||||
|
s = config.nomarchy.settings.nightlight;
|
||||||
|
sync = lib.getExe config.nomarchy.package;
|
||||||
|
# Runtime-on default for when the `on` key hasn't been written yet (e.g. right
|
||||||
|
# after the first enable). Baked at eval; only used when the live key is absent.
|
||||||
|
onDefault = lib.boolToString s.on;
|
||||||
|
|
||||||
|
nomarchy-nightlight = pkgs.writeShellScriptBin "nomarchy-nightlight" ''
|
||||||
|
unit=hyprsunset.service
|
||||||
|
# Instant runtime on/off: write the in-flake state WITHOUT a rebuild.
|
||||||
|
write_on() { ${sync} --quiet set settings.nightlight.on "$1" --no-switch; }
|
||||||
|
# First enable: mark the feature installed and REBUILD to create the unit
|
||||||
|
# (the one rebuild we accept; every toggle after is instant).
|
||||||
|
install_feature() { ${sync} --quiet set settings.nightlight.installed true; }
|
||||||
|
# Read the LIVE working-tree on/off (~/.nomarchy via $NOMARCHY_PATH), not the
|
||||||
|
# store copy baked into this generation; fall back to the eval-time default
|
||||||
|
# when absent. Normalise Python's True/False bool rendering.
|
||||||
|
is_on() {
|
||||||
|
v=$(${sync} get settings.nightlight.on 2>/dev/null) || v=${onDefault}
|
||||||
|
case "$v" in true|True) return 0 ;; *) return 1 ;; esac
|
||||||
|
}
|
||||||
|
installed() { systemctl --user cat "$unit" >/dev/null 2>&1; }
|
||||||
|
start() { systemctl --user start "$unit" 2>/dev/null || true; }
|
||||||
|
stop() { systemctl --user stop "$unit" 2>/dev/null || true; }
|
||||||
|
case "''${1:-toggle}" in
|
||||||
|
should-start) is_on ;; # ExecCondition gate (login/reboot)
|
||||||
|
status)
|
||||||
|
# Waybar (polls every 3s): moon while running; print nothing otherwise so
|
||||||
|
# the module self-hides — enable / re-enable from the System menu.
|
||||||
|
systemctl --user is-active --quiet "$unit" \
|
||||||
|
&& printf '{"text":"","tooltip":"Night light on — warm on schedule (click to disable)","class":"on"}\n'
|
||||||
|
exit 0 ;;
|
||||||
|
on)
|
||||||
|
if installed; then write_on true; start; else install_feature; start; fi ;;
|
||||||
|
off) write_on false; stop ;;
|
||||||
|
toggle)
|
||||||
|
if systemctl --user is-active --quiet "$unit"; then
|
||||||
|
write_on false; stop # on -> off (instant)
|
||||||
|
elif installed; then
|
||||||
|
write_on true; start # installed, off -> on (instant)
|
||||||
|
else
|
||||||
|
install_feature; start # first enable (rebuilds)
|
||||||
|
fi ;;
|
||||||
|
*) echo "usage: nomarchy-nightlight [toggle|status|on|off|should-start]" >&2; exit 64 ;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = lib.mkIf cfg.enable {
|
config = {
|
||||||
services.hyprsunset = {
|
# Unit presence tracks the sticky `installed` flag the menu writes (first
|
||||||
|
# enable rebuilds). mkDefault so a hand-set nomarchy.nightlight.enable in
|
||||||
|
# home.nix also works as a declarative opt-in.
|
||||||
|
nomarchy.nightlight.enable = lib.mkDefault s.installed;
|
||||||
|
|
||||||
|
services.hyprsunset = lib.mkIf cfg.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings.profile = [
|
settings.profile = [
|
||||||
# Daytime: identity = no colour change.
|
# Daytime: identity = no colour change.
|
||||||
@@ -21,5 +78,13 @@ in
|
|||||||
{ time = cfg.sunset; temperature = cfg.temperature; }
|
{ time = cfg.sunset; temperature = cfg.temperature; }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Gate the unit on the LIVE on/off state at start time (login/reboot), not
|
||||||
|
# at eval time — so a menu toggle (written without a rebuild) is honoured on
|
||||||
|
# the next session. A failed condition skips the unit (inactive, not failed).
|
||||||
|
systemd.user.services.hyprsunset.Service.ExecCondition =
|
||||||
|
lib.mkIf cfg.enable "${nomarchy-nightlight}/bin/nomarchy-nightlight should-start";
|
||||||
|
|
||||||
|
home.packages = [ nomarchy-nightlight ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,10 +105,14 @@ in
|
|||||||
picker. When non-empty, a small watcher runs in the session: when a
|
picker. When non-empty, a small watcher runs in the session: when a
|
||||||
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
||||||
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
||||||
the primary nomarchy.keyboard.layout), applies the choice, and
|
the primary nomarchy.keyboard.layout), applies the choice to that
|
||||||
remembers it per-device (~/.local/state) — re-applying automatically
|
keyboard only (a per-device kb_layout, so the built-in board is left
|
||||||
on later reconnects. The runtime-remember complement to the
|
alone), and remembers it in the git-tracked in-flake state
|
||||||
declarative keyboard.devices; a stateful runtime piece by design.
|
(settings.keyboard.devices, not ~/.local/state) — re-applied
|
||||||
|
automatically on later reconnects and across reboots. Each remembered
|
||||||
|
choice graduates into nomarchy.keyboard.devices on the next rebuild
|
||||||
|
(a generated device block). The runtime-remember complement to the
|
||||||
|
declarative keyboard.devices.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +139,9 @@ in
|
|||||||
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
||||||
keyboard that's physically a different layout than the laptop's
|
keyboard that's physically a different layout than the laptop's
|
||||||
built-in one. Hyprland applies it automatically whenever that
|
built-in one. Hyprland applies it automatically whenever that
|
||||||
keyboard is connected.
|
keyboard is connected. The interactive watcher
|
||||||
|
(nomarchy.keyboard.layouts) also writes its remembered picks here on
|
||||||
|
the next rebuild, so a runtime choice graduates into reproducible config.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,6 +190,32 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updates = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
passive update awareness: a background check (systemd user timer) that
|
||||||
|
compares the flake's locked inputs (nixpkgs, the Nomarchy input, …)
|
||||||
|
against upstream and — when Flatpak is enabled — counts Flatpak
|
||||||
|
updates, surfacing a Waybar indicator + a notification when something
|
||||||
|
is available. It never changes anything; you still run sys-update /
|
||||||
|
home-update / flatpak update yourself'';
|
||||||
|
|
||||||
|
interval = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "daily";
|
||||||
|
example = "6h";
|
||||||
|
description = "How often to check, as a systemd OnCalendar expression.";
|
||||||
|
};
|
||||||
|
|
||||||
|
flatpak = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Also count available Flatpak updates when the `flatpak` CLI is
|
||||||
|
present (i.e. nomarchy.services.flatpak is on). No effect otherwise.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
monitors = lib.mkOption {
|
monitors = lib.mkOption {
|
||||||
type = lib.types.listOf monitorType;
|
type = lib.types.listOf monitorType;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
@@ -228,6 +260,17 @@ in
|
|||||||
description = "The parsed theme state (stateFile merged over defaults).";
|
description = "The parsed theme state (stateFile merged over defaults).";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
readOnly = true;
|
||||||
|
description = ''
|
||||||
|
Parsed feature settings — the `settings` section of the state file,
|
||||||
|
what the menu/Waybar toggles write (e.g. settings.nightlight.enable).
|
||||||
|
Feature options mkDefault-read from here, so a menu toggle lands in the
|
||||||
|
in-flake state (git-tracked, reproducible) rather than ~/.local/state.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
lib = lib.mkOption {
|
lib = lib.mkOption {
|
||||||
type = lib.types.attrs;
|
type = lib.types.attrs;
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
|
|||||||
@@ -19,6 +19,92 @@ let
|
|||||||
rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi";
|
rasiOverride = cfg.themesDir + "/${t.slug}/rofi.rasi";
|
||||||
hasRasiOverride = builtins.pathExists rasiOverride;
|
hasRasiOverride = builtins.pathExists rasiOverride;
|
||||||
|
|
||||||
|
# ── Visual theme picker ──────────────────────────────────────────────
|
||||||
|
# Every preset (themes/<slug>.json) is read at eval time to build a grid
|
||||||
|
# of real desktop previews. Grouped dark-first then light — the previews
|
||||||
|
# make the mode obvious at a glance, so no light/dark submenu split — and
|
||||||
|
# the active theme is marked ✓. "Active" is just t.slug: every switch
|
||||||
|
# rebuilds this script, so the baked-in mark always tracks the live theme.
|
||||||
|
themeFiles = lib.filterAttrs
|
||||||
|
(n: ty: ty == "regular" && lib.hasSuffix ".json" n)
|
||||||
|
(builtins.readDir cfg.themesDir);
|
||||||
|
themeData = lib.mapAttrsToList (fname: _:
|
||||||
|
let
|
||||||
|
j = builtins.fromJSON (builtins.readFile (cfg.themesDir + "/${fname}"));
|
||||||
|
slug = j.slug or (lib.removeSuffix ".json" fname);
|
||||||
|
in {
|
||||||
|
inherit slug;
|
||||||
|
name = j.name or slug;
|
||||||
|
mode = j.mode or "dark";
|
||||||
|
hasPreview = builtins.pathExists (cfg.themesDir + "/${slug}/preview.png");
|
||||||
|
}) themeFiles;
|
||||||
|
|
||||||
|
byName = lib.sort (a: b: a.name < b.name);
|
||||||
|
orderedThemes =
|
||||||
|
byName (lib.filter (th: th.mode != "light") themeData) # dark group
|
||||||
|
++ byName (lib.filter (th: th.mode == "light") themeData); # then light
|
||||||
|
|
||||||
|
# rofi's element-icon cells are SQUARE, so a 16:9 preview would letterbox
|
||||||
|
# (theme-coloured bands top/bottom). Build-time crop each committed 480×270
|
||||||
|
# preview to a centred square so it fills the cell edge-to-edge. The source
|
||||||
|
# preview.png stays 480×270 (untouched) — only the displayed thumb is square.
|
||||||
|
themeThumbs = pkgs.runCommand "nomarchy-theme-thumbs"
|
||||||
|
{ nativeBuildInputs = [ pkgs.imagemagick ]; } ''
|
||||||
|
mkdir -p $out
|
||||||
|
${lib.concatMapStringsSep "\n"
|
||||||
|
(th: lib.optionalString th.hasPreview ''
|
||||||
|
magick ${cfg.themesDir + "/${th.slug}/preview.png"} \
|
||||||
|
-resize 360x360^ -gravity center -extent 360x360 -strip $out/${th.slug}.png
|
||||||
|
'')
|
||||||
|
orderedThemes}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# One grid row per theme (square thumb + name, ✓ on the active one), and a
|
||||||
|
# Name→slug map so the picked label resolves back to the preset that
|
||||||
|
# nomarchy-theme-sync applies (pretty names ≠ slugs, hence the map). Themes
|
||||||
|
# with no preview degrade to a plain-name row.
|
||||||
|
themeRows = lib.concatMapStringsSep "\n" (th:
|
||||||
|
let label = th.name + lib.optionalString (th.slug == t.slug) " ✓";
|
||||||
|
in if th.hasPreview
|
||||||
|
then "row ${lib.escapeShellArg label} ${themeThumbs}/${th.slug}.png"
|
||||||
|
else "printf '%s\\n' ${lib.escapeShellArg label}")
|
||||||
|
orderedThemes;
|
||||||
|
themeSlugMap = lib.concatMapStringsSep "\n" (th:
|
||||||
|
" [${lib.escapeShellArg th.name}]=${lib.escapeShellArg th.slug}")
|
||||||
|
orderedThemes;
|
||||||
|
|
||||||
|
# Per-invocation grid layout (cards: a 16:9 preview above a centered name),
|
||||||
|
# overriding the single-column list theme just for this menu.
|
||||||
|
# · The icon box is sized 16:9 (rofi 2.0 takes a two-value `size`) to
|
||||||
|
# match the 480×270 previews exactly — they fill it with no letterbox.
|
||||||
|
# · The window is sized to the *content* (3 × icon + paddings), not a
|
||||||
|
# percentage, so a column is no wider than its card — otherwise the
|
||||||
|
# selection highlight balloons out around the image.
|
||||||
|
# · flow: horizontal lays cards out row-by-row (rofi's default Vertical
|
||||||
|
# fills column-by-column, so Down at a column's foot jumped to the top
|
||||||
|
# of the next column instead of scrolling the page).
|
||||||
|
# Grid dial — the previews are as big as the icon px allows; screen *height*
|
||||||
|
# caps it, so showing 2 rows (not 3) lets each card grow a lot. Scrolling
|
||||||
|
# reaches the rest. Tune themeGridIconW for size; the window resizes to fit.
|
||||||
|
# rofi's element-icon `size` is a SINGLE value (the manual only documents
|
||||||
|
# one) — a two-value "WxH" is silently collapsed, which is why the preview
|
||||||
|
# used to render tiny inside a too-wide column. So: size = the card *width*,
|
||||||
|
# rofi fits the 16:9 image tight to it (no square letterbox), and the window
|
||||||
|
# is sized so a column is exactly that width — the preview fills the cell.
|
||||||
|
themeGridCols = 3;
|
||||||
|
themeGridLines = 3;
|
||||||
|
themeGridIconW = 240; # square preview card side (px) — the size knob
|
||||||
|
themeGridPad = 0; # margin around the preview inside its cell (px)
|
||||||
|
themeGridGap = 8; # gap between cards (px)
|
||||||
|
themeGridThemeStr = lib.escapeShellArg (lib.concatStringsSep " " [
|
||||||
|
# window width = cards + their padding + inter-card gaps + chrome (~20px)
|
||||||
|
"window { width: ${toString (themeGridCols * (themeGridIconW + 2 * themeGridPad) + (themeGridCols - 1) * themeGridGap + 20)}px; }"
|
||||||
|
"listview { columns: ${toString themeGridCols}; lines: ${toString themeGridLines}; spacing: ${toString themeGridGap}px; flow: horizontal; }"
|
||||||
|
"element { orientation: vertical; padding: ${toString themeGridPad}px; spacing: 2px; }"
|
||||||
|
"element-icon { size: ${toString themeGridIconW}px; }"
|
||||||
|
"element-text { horizontal-align: 0.5; }"
|
||||||
|
]);
|
||||||
|
|
||||||
# Keybindings cheatsheet (SUPER+? → the `keybinds` menu module). Built
|
# Keybindings cheatsheet (SUPER+? → the `keybinds` menu module). Built
|
||||||
# from the SAME ./keybinds.nix that hyprland.nix binds, so it can never
|
# 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
|
# drift from the live shortcuts. "$mod" reads as SUPER; rows are padded
|
||||||
@@ -64,6 +150,14 @@ let
|
|||||||
# rofi has show-icons (on globally; passed explicitly on the icon menus).
|
# rofi has show-icons (on globally; passed explicitly on the icon menus).
|
||||||
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
|
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
|
||||||
|
|
||||||
|
# General menu convention: every list menu ends with a "↩ Back" entry that
|
||||||
|
# returns one level up (so you never have to Esc out and reopen). `back`
|
||||||
|
# emits it with an icon; plain pick-lists append the bare label instead.
|
||||||
|
# Always matched EXACTLY ("↩ Back") so it can't collide with clipboard or
|
||||||
|
# filename content. The arrow + go-previous icon read as "go back".
|
||||||
|
BACK="↩ Back"
|
||||||
|
back() { row "$BACK" go-previous; }
|
||||||
|
|
||||||
case "''${1:-}" in
|
case "''${1:-}" in
|
||||||
power)
|
power)
|
||||||
choice=$( {
|
choice=$( {
|
||||||
@@ -73,8 +167,10 @@ let
|
|||||||
row "Hibernate" system-hibernate
|
row "Hibernate" system-hibernate
|
||||||
row "Reboot" system-reboot
|
row "Reboot" system-reboot
|
||||||
row "Shutdown" system-shutdown
|
row "Shutdown" system-shutdown
|
||||||
|
back
|
||||||
} | rofi -dmenu -show-icons -p Power) || exit 0
|
} | rofi -dmenu -show-icons -p Power) || exit 0
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
|
"$BACK") exec "$0" ;;
|
||||||
*Lock) loginctl lock-session ;;
|
*Lock) loginctl lock-session ;;
|
||||||
*Logout) hyprctl dispatch exit ;;
|
*Logout) hyprctl dispatch exit ;;
|
||||||
*Suspend) systemctl suspend ;;
|
*Suspend) systemctl suspend ;;
|
||||||
@@ -92,17 +188,30 @@ let
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
cur=$(powerprofilesctl get 2>/dev/null)
|
cur=$(powerprofilesctl get 2>/dev/null)
|
||||||
choice=$(powerprofilesctl list 2>/dev/null \
|
choice=$( { powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p'; printf '%s\n' "$BACK"; } \
|
||||||
| sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \
|
|
||||||
| rofi -dmenu -p "profile (now: $cur)") || exit 0
|
| rofi -dmenu -p "profile (now: $cur)") || exit 0
|
||||||
|
[ "$choice" = "$BACK" ] && exec "$0" system
|
||||||
[ -n "$choice" ] && powerprofilesctl set "$choice" ;;
|
[ -n "$choice" ] && powerprofilesctl set "$choice" ;;
|
||||||
|
|
||||||
theme)
|
theme)
|
||||||
choice=$(nomarchy-theme-sync list | rofi -dmenu -p theme) || exit 0
|
# Visual picker: a grid of real desktop previews (rows + Name→slug
|
||||||
[ -n "$choice" ] && exec nomarchy-theme-sync apply "$choice" ;;
|
# map generated from the presets at build time). Pick a card → apply
|
||||||
|
# that slug. The grid layout is a per-invocation -theme-str override.
|
||||||
|
declare -A THEME_SLUG=(
|
||||||
|
${themeSlugMap}
|
||||||
|
)
|
||||||
|
choice=$( {
|
||||||
|
${themeRows}
|
||||||
|
back
|
||||||
|
} | rofi -dmenu -show-icons -p Theme -theme-str ${themeGridThemeStr}) || exit 0
|
||||||
|
[ "$choice" = "$BACK" ] && exec "$0"
|
||||||
|
choice="''${choice% ✓}" # drop the active marker if present
|
||||||
|
slug="''${THEME_SLUG[$choice]:-}"
|
||||||
|
[ -n "$slug" ] && exec nomarchy-theme-sync apply "$slug" ;;
|
||||||
|
|
||||||
clipboard)
|
clipboard)
|
||||||
sel=$(cliphist list | rofi -dmenu -p clip) || exit 0
|
sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi -dmenu -p clip) || exit 0
|
||||||
|
[ "$sel" = "$BACK" ] && exec "$0" tools
|
||||||
printf '%s' "$sel" | cliphist decode | wl-copy ;;
|
printf '%s' "$sel" | cliphist decode | wl-copy ;;
|
||||||
|
|
||||||
calc)
|
calc)
|
||||||
@@ -114,10 +223,10 @@ let
|
|||||||
-calc-command "echo -n '{result}' | wl-copy" ;;
|
-calc-command "echo -n '{result}' | wl-copy" ;;
|
||||||
|
|
||||||
files)
|
files)
|
||||||
sel=$(fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
|
sel=$( { fd . "$HOME" --type f --hidden --exclude .git --exclude .cache 2>/dev/null \
|
||||||
| head -n 50000 \
|
| head -n 50000 | sed "s|^$HOME/||"; printf '%s\n' "$BACK"; } \
|
||||||
| sed "s|^$HOME/||" \
|
|
||||||
| rofi -dmenu -p file) || exit 0
|
| rofi -dmenu -p file) || exit 0
|
||||||
|
[ "$sel" = "$BACK" ] && exec "$0" tools
|
||||||
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
|
[ -n "$sel" ] && exec xdg-open "$HOME/$sel" ;;
|
||||||
|
|
||||||
emoji)
|
emoji)
|
||||||
@@ -132,23 +241,52 @@ let
|
|||||||
exec xdg-open "https://www.google.com/search?q=$(urlencode "$q")" ;;
|
exec xdg-open "https://www.google.com/search?q=$(urlencode "$q")" ;;
|
||||||
|
|
||||||
network)
|
network)
|
||||||
# nmtui in a terminal (NetworkManager is the system network stack).
|
# Native rofi wifi/VPN picker (networkmanager_dmenu) over the system
|
||||||
exec ${cfg.terminal} -e nmtui ;;
|
# NetworkManager — replaces the old nmtui-in-a-terminal flow. Reads
|
||||||
|
# its config from xdg.configFile below (told to drive rofi).
|
||||||
|
exec networkmanager_dmenu ;;
|
||||||
|
|
||||||
bluetooth)
|
bluetooth)
|
||||||
# blueman-manager GUI (services.blueman.enable, system-side).
|
# blueman-manager GUI (services.blueman.enable, system-side).
|
||||||
exec blueman-manager ;;
|
exec blueman-manager ;;
|
||||||
|
|
||||||
|
printers)
|
||||||
|
# system-config-printer GUI (the CUPS admin app), installed by
|
||||||
|
# nomarchy.services.printing. Self-gated in the System menu; guard
|
||||||
|
# here too in case it's invoked directly.
|
||||||
|
command -v system-config-printer >/dev/null 2>&1 \
|
||||||
|
&& exec system-config-printer
|
||||||
|
notify-send "Printers" "Not available (nomarchy.services.printing off?)."; exit 0 ;;
|
||||||
|
|
||||||
|
audio)
|
||||||
|
# PipeWire (pulse) sink/source switcher via rofi-pulse-select. Self-
|
||||||
|
# gated in the System menu on the pulse socket; guarded here too.
|
||||||
|
if [ ! -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ]; then
|
||||||
|
notify-send "Audio" "No PipeWire/PulseAudio socket (nomarchy.audio off?)."; exit 0
|
||||||
|
fi
|
||||||
|
choice=$( {
|
||||||
|
row "Output device" audio-volume-high
|
||||||
|
row "Input device" audio-input-microphone
|
||||||
|
back
|
||||||
|
} | rofi -dmenu -show-icons -p Audio) || exit 0
|
||||||
|
case "$choice" in
|
||||||
|
"$BACK") exec "$0" system ;;
|
||||||
|
*Output*) exec rofi-pulse-select sink ;;
|
||||||
|
*Input*) exec rofi-pulse-select source ;;
|
||||||
|
esac ;;
|
||||||
|
|
||||||
capture)
|
capture)
|
||||||
choice=$( {
|
choice=$( {
|
||||||
row "Region → clipboard" applets-screenshooter
|
row "Region → clipboard" applets-screenshooter
|
||||||
row "Region → file" applets-screenshooter
|
row "Region → file" applets-screenshooter
|
||||||
row "Full screen → clipboard" camera-photo
|
row "Full screen → clipboard" camera-photo
|
||||||
row "Full screen → file" camera-photo
|
row "Full screen → file" camera-photo
|
||||||
|
back
|
||||||
} | rofi -dmenu -show-icons -p Capture) || exit 0
|
} | rofi -dmenu -show-icons -p Capture) || exit 0
|
||||||
dir="$HOME/Pictures/Screenshots"
|
dir="$HOME/Pictures/Screenshots"
|
||||||
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
|
"$BACK") exec "$0" tools ;;
|
||||||
*"Region → clipboard"*) grim -g "$(slurp)" - | wl-copy ;;
|
*"Region → clipboard"*) grim -g "$(slurp)" - | wl-copy ;;
|
||||||
*"Region → file"*) mkdir -p "$dir"; grim -g "$(slurp)" "$file" \
|
*"Region → file"*) mkdir -p "$dir"; grim -g "$(slurp)" "$file" \
|
||||||
&& notify-send "Screenshot saved" "$file" ;;
|
&& notify-send "Screenshot saved" "$file" ;;
|
||||||
@@ -178,13 +316,31 @@ let
|
|||||||
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
|
|
||||||
|
nightlight)
|
||||||
|
# Force the scheduled blue-light filter on/off for the session by
|
||||||
|
# starting/stopping hyprsunset (nomarchy-nightlight, from nightlight.nix).
|
||||||
|
# Self-gated in the menu; guard here too in case it's invoked directly.
|
||||||
|
command -v nomarchy-nightlight >/dev/null 2>&1 \
|
||||||
|
&& exec nomarchy-nightlight toggle
|
||||||
|
notify-send "Night light" "Not available (nomarchy.nightlight off?)."; exit 0 ;;
|
||||||
|
|
||||||
|
autotimezone)
|
||||||
|
# Toggle automatic timezone detection (geoclue + automatic-timezoned).
|
||||||
|
# A SYSTEM service, so flipping it writes the in-flake flag and runs a
|
||||||
|
# system rebuild (sudo) + a home switch — in a terminal, like Snapshots.
|
||||||
|
command -v nomarchy-autotimezone >/dev/null 2>&1 \
|
||||||
|
|| { notify-send "Auto timezone" "Unavailable on this machine."; exit 0; }
|
||||||
|
exec ${cfg.terminal} -e nomarchy-autotimezone toggle ;;
|
||||||
|
|
||||||
snapshot)
|
snapshot)
|
||||||
# btrfs-assistant: snapshot browse / diff / restore / rollback over
|
# btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil ABI), so launch
|
||||||
# snapper, elevating via polkit. Shipped system-side with
|
# the keyboard-driven snapper browser instead: a terminal running it
|
||||||
# nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
|
# via sudo (snapper is root-only here — one password prompt), with
|
||||||
command -v btrfs-assistant >/dev/null 2>&1 \
|
# browse/diff, restore (undochange) and rollback, each confirmed.
|
||||||
|| { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
|
# Self-gated to nomarchy.system.snapper, so it no-ops elsewhere.
|
||||||
exec btrfs-assistant ;;
|
command -v nomarchy-snapshots >/dev/null 2>&1 \
|
||||||
|
|| { notify-send "Snapshots" "Snapshot tools unavailable (nomarchy.system.snapper off?)."; exit 0; }
|
||||||
|
exec ${cfg.terminal} -e sudo nomarchy-snapshots ;;
|
||||||
|
|
||||||
tools)
|
tools)
|
||||||
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
||||||
@@ -197,9 +353,10 @@ let
|
|||||||
row "Web search" system-search
|
row "Web search" system-search
|
||||||
row "Capture" applets-screenshooter
|
row "Capture" applets-screenshooter
|
||||||
row "Ask Claude" internet-chat
|
row "Ask Claude" internet-chat
|
||||||
row "Back" go-previous
|
back
|
||||||
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
|
"$BACK") exec "$0" ;;
|
||||||
*Calc*) exec "$0" calc ;;
|
*Calc*) exec "$0" calc ;;
|
||||||
*Clipboard*) exec "$0" clipboard ;;
|
*Clipboard*) exec "$0" clipboard ;;
|
||||||
*Emoji*) exec "$0" emoji ;;
|
*Emoji*) exec "$0" emoji ;;
|
||||||
@@ -207,7 +364,6 @@ let
|
|||||||
*Web*) exec "$0" web ;;
|
*Web*) exec "$0" web ;;
|
||||||
*Capture*) exec "$0" capture ;;
|
*Capture*) exec "$0" capture ;;
|
||||||
*Ask*) exec "$0" ask ;;
|
*Ask*) exec "$0" ask ;;
|
||||||
*Back*) exec "$0" ;;
|
|
||||||
esac ;;
|
esac ;;
|
||||||
|
|
||||||
system)
|
system)
|
||||||
@@ -218,20 +374,35 @@ let
|
|||||||
choice=$( {
|
choice=$( {
|
||||||
row "Network" network-wireless
|
row "Network" network-wireless
|
||||||
row "Bluetooth" bluetooth
|
row "Bluetooth" bluetooth
|
||||||
|
[ -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ] \
|
||||||
|
&& row "Audio" audio-volume-high
|
||||||
|
command -v system-config-printer >/dev/null 2>&1 && row "Printers" printer
|
||||||
row "Do Not Disturb" notification-disabled
|
row "Do Not Disturb" notification-disabled
|
||||||
command -v btrfs-assistant >/dev/null 2>&1 && row "Snapshots" timeshift
|
if systemctl --user is-active --quiet hyprsunset.service 2>/dev/null
|
||||||
|
then row "Night light (on)" weather-clear-night
|
||||||
|
else row "Night light (off)" weather-clear-night
|
||||||
|
fi
|
||||||
|
if [ "$(nomarchy-theme-sync get settings.autoTimezone 2>/dev/null)" = true ]
|
||||||
|
then row "Auto timezone (on)" preferences-system-time
|
||||||
|
else row "Auto timezone (off)" preferences-system-time
|
||||||
|
fi
|
||||||
|
command -v nomarchy-snapshots >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||||
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||||
row "Power profile" preferences-system-power
|
row "Power profile" preferences-system-power
|
||||||
fi
|
fi
|
||||||
row "Back" go-previous
|
back
|
||||||
} | rofi -dmenu -show-icons -p System) || exit 0
|
} | rofi -dmenu -show-icons -p System) || exit 0
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
|
"$BACK") exec "$0" ;;
|
||||||
*Network*) exec "$0" network ;;
|
*Network*) exec "$0" network ;;
|
||||||
*Bluetooth*) exec "$0" bluetooth ;;
|
*Bluetooth*) exec "$0" bluetooth ;;
|
||||||
|
*Audio*) exec "$0" audio ;;
|
||||||
|
*Printers*) exec "$0" printers ;;
|
||||||
*"Do Not Disturb"*) exec "$0" dnd ;;
|
*"Do Not Disturb"*) exec "$0" dnd ;;
|
||||||
|
*"Night light"*) exec "$0" nightlight ;;
|
||||||
|
*"Auto timezone"*) exec "$0" autotimezone ;;
|
||||||
*Snapshots*) exec "$0" snapshot ;;
|
*Snapshots*) exec "$0" snapshot ;;
|
||||||
*"Power profile"*) exec "$0" power-profile ;;
|
*"Power profile"*) exec "$0" power-profile ;;
|
||||||
*Back*) exec "$0" ;;
|
|
||||||
esac ;;
|
esac ;;
|
||||||
|
|
||||||
"")
|
"")
|
||||||
@@ -256,7 +427,7 @@ let
|
|||||||
esac ;;
|
esac ;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask|dnd|snapshot]" >&2
|
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|audio|printers|capture|keybinds|ask|dnd|nightlight|autotimezone|snapshot]" >&2
|
||||||
exit 64 ;;
|
exit 64 ;;
|
||||||
esac
|
esac
|
||||||
'';
|
'';
|
||||||
@@ -265,11 +436,34 @@ in
|
|||||||
config = lib.mkIf cfg.rofi.enable {
|
config = lib.mkIf cfg.rofi.enable {
|
||||||
home.packages = [
|
home.packages = [
|
||||||
nomarchy-menu
|
nomarchy-menu
|
||||||
pkgs.fd # files module (fuzzy search over $HOME)
|
pkgs.fd # files module (fuzzy search over $HOME)
|
||||||
pkgs.xdg-utils # xdg-open for the files + web modules
|
pkgs.xdg-utils # xdg-open for the files + web modules
|
||||||
pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm)
|
pkgs.nodejs # npx, for the Ask Claude module (claude-code from npm)
|
||||||
|
pkgs.networkmanager_dmenu # network module: rofi wifi/VPN picker
|
||||||
|
pkgs.rofi-pulse-select # audio module: sink/source switcher
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# networkmanager_dmenu drives rofi (not bare dmenu) and uses rofi's
|
||||||
|
# active/urgent row styling for the connected/available networks, so it
|
||||||
|
# inherits the generated theme like every other menu module. Editing a
|
||||||
|
# connection drops to nmtui in the configured terminal. force: the tool
|
||||||
|
# writes a default config on first run, so an unmanaged file may already
|
||||||
|
# sit at this path — we fully own it, so overwrite rather than abort.
|
||||||
|
xdg.configFile."networkmanager-dmenu/config.ini" = {
|
||||||
|
force = true;
|
||||||
|
text = ''
|
||||||
|
[dmenu]
|
||||||
|
dmenu_command = rofi
|
||||||
|
rofi_highlight = True
|
||||||
|
compact = False
|
||||||
|
wifi_chars = ▂▄▆█
|
||||||
|
|
||||||
|
[editor]
|
||||||
|
terminal = ${cfg.terminal}
|
||||||
|
gui_if_available = False
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
programs.rofi = {
|
programs.rofi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
terminal = cfg.terminal;
|
terminal = cfg.terminal;
|
||||||
@@ -292,6 +486,14 @@ in
|
|||||||
# reserving `lines` (8) — so a 6-entry menu doesn't leave empty space,
|
# reserving `lines` (8) — so a 6-entry menu doesn't leave empty space,
|
||||||
# while the app launcher still fills + scrolls past `lines`.
|
# while the app launcher still fills + scrolls past `lines`.
|
||||||
fixed-num-lines = false;
|
fixed-num-lines = false;
|
||||||
|
# Search: case-insensitive fuzzy across every menu + the launcher, so
|
||||||
|
# "system" finds "System" and "fzr" finds "Firefox". fzf sorting ranks
|
||||||
|
# the closest match to the top (the per-keystroke modules — calc/emoji
|
||||||
|
# — opt out per-invocation with -no-sort).
|
||||||
|
matching = "fuzzy";
|
||||||
|
case-sensitive = false;
|
||||||
|
sort = true;
|
||||||
|
sorting-method = "fzf";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Whole-swap themes bring their own rofi.rasi; otherwise the theme
|
# Whole-swap themes bring their own rofi.rasi; otherwise the theme
|
||||||
|
|||||||
@@ -54,6 +54,23 @@ let
|
|||||||
# tone for kanagawa/summer-*). Each preset declares its own so a theme
|
# tone for kanagawa/summer-*). Each preset declares its own so a theme
|
||||||
# switch always replaces it (deep_merge would otherwise leave it stuck).
|
# switch always replaces it (deep_merge would otherwise leave it stuck).
|
||||||
border = { active = "accent"; inactive = "overlay"; };
|
border = { active = "accent"; inactive = "overlay"; };
|
||||||
|
|
||||||
|
# Non-appearance feature settings the menu/watchers write into this same
|
||||||
|
# in-flake state. nomarchy.nightlight: `installed` (sticky — gates the unit,
|
||||||
|
# so the first enable rebuilds) and `on` (instant runtime on/off).
|
||||||
|
# settings.keyboard.devices: per-device layouts the new-keyboard watcher
|
||||||
|
# remembers (device-name -> XKB layout), git-tracked instead of
|
||||||
|
# ~/.local/state; they graduate into nomarchy.keyboard.devices on the next
|
||||||
|
# rebuild. Defaulted so a sparse/older state file still evaluates;
|
||||||
|
# nomarchy.settings exposes them.
|
||||||
|
settings = {
|
||||||
|
nightlight = { installed = false; on = true; };
|
||||||
|
keyboard.devices = { };
|
||||||
|
# Automatic timezone detection (nomarchy.system.autoTimezone): a system
|
||||||
|
# service, but the flag lives here so both sides read one source — the
|
||||||
|
# home side gates the Waybar-refresh watcher (timezone.nix) on it.
|
||||||
|
autoTimezone = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
parsed = lib.recursiveUpdate defaults themeState;
|
parsed = lib.recursiveUpdate defaults themeState;
|
||||||
@@ -78,6 +95,11 @@ in
|
|||||||
config = {
|
config = {
|
||||||
nomarchy.theme = parsed // { inherit iconTheme border; };
|
nomarchy.theme = parsed // { inherit iconTheme border; };
|
||||||
|
|
||||||
|
# Feature toggles the menu writes (settings.nightlight.enable, …), exposed
|
||||||
|
# alongside the appearance state. Feature modules mkDefault-read from here
|
||||||
|
# so a menu toggle lands in the flake instead of in ~/.local/state.
|
||||||
|
nomarchy.settings = parsed.settings;
|
||||||
|
|
||||||
nomarchy.lib = {
|
nomarchy.lib = {
|
||||||
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
|
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
|
||||||
rgb = c: "rgb(${lib.removePrefix "#" c})";
|
rgb = c: "rgb(${lib.removePrefix "#" c})";
|
||||||
|
|||||||
44
modules/home/timezone.nix
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Auto-timezone, home side: keep the Waybar clock in step with the system
|
||||||
|
# timezone. Waybar's clock module captures the zone once at construction, so a
|
||||||
|
# runtime timezone change (automatic-timezoned, nomarchy.system.autoTimezone)
|
||||||
|
# would NOT show until a relogin. A tiny watcher subscribes to timedate1's
|
||||||
|
# change signal and reloads Waybar (SIGUSR2 = the same reload theme-sync uses),
|
||||||
|
# so the clock follows your location live. Also catches a manual
|
||||||
|
# `timedatectl set-timezone`.
|
||||||
|
#
|
||||||
|
# Gated on the same in-flake flag the system side reads (settings.autoTimezone,
|
||||||
|
# exposed via nomarchy.settings) — so it only runs when the feature is on. The
|
||||||
|
# menu toggle (nomarchy-autotimezone) rebuilds both sides off that one flag.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy;
|
||||||
|
enabled = cfg.waybar.enable && (cfg.settings.autoTimezone or false);
|
||||||
|
|
||||||
|
tzWatch = pkgs.writeShellScript "nomarchy-tz-watch" ''
|
||||||
|
last=$(${pkgs.systemd}/bin/timedatectl show -p Timezone --value 2>/dev/null || true)
|
||||||
|
${pkgs.dbus}/bin/dbus-monitor --system \
|
||||||
|
"type='signal',interface='org.freedesktop.DBus.Properties',path='/org/freedesktop/timedate1',member='PropertiesChanged'" \
|
||||||
|
2>/dev/null |
|
||||||
|
while read -r _; do
|
||||||
|
cur=$(${pkgs.systemd}/bin/timedatectl show -p Timezone --value 2>/dev/null || true)
|
||||||
|
[ "$cur" = "$last" ] && continue
|
||||||
|
last=$cur
|
||||||
|
${pkgs.procps}/bin/pkill -SIGUSR2 -x waybar 2>/dev/null || true
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.user.services.nomarchy-tz-watch = lib.mkIf enabled {
|
||||||
|
Unit = {
|
||||||
|
Description = "Reload Waybar on timezone change (auto-timezone)";
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
After = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${tzWatch}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
141
modules/home/updates.nix
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Update awareness (opt-in, nomarchy.updates.enable) — a passive background
|
||||||
|
# check that surfaces a Waybar indicator + a notification when updates are
|
||||||
|
# available, without ever changing anything (you still run sys-update /
|
||||||
|
# home-update / flatpak update). It counts:
|
||||||
|
# • flake inputs whose locked rev is behind upstream (nixpkgs, the Nomarchy
|
||||||
|
# input, home-manager …) — via `git ls-remote` on each branch-tracking
|
||||||
|
# github/git input in flake.lock; offline → skipped, never a false alarm.
|
||||||
|
# • Flatpak updates, when the `flatpak` CLI is present (services.flatpak on).
|
||||||
|
#
|
||||||
|
# nomarchy-updates is always on PATH and self-gates (status prints nothing
|
||||||
|
# until the timer has found something), so the Waybar module — generated and
|
||||||
|
# whole-swap — can exec it by name even when the feature is off.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy.updates;
|
||||||
|
|
||||||
|
nomarchy-updates = pkgs.writeShellScriptBin "nomarchy-updates" ''
|
||||||
|
set -u
|
||||||
|
# System + user profiles, so flatpak / sys-update resolve from a timer-run
|
||||||
|
# service too (build-time tools below use absolute store paths regardless).
|
||||||
|
export PATH="$PATH:/run/current-system/sw/bin:/etc/profiles/per-user/$USER/bin"
|
||||||
|
GIT=${pkgs.git}/bin/git
|
||||||
|
JQ=${pkgs.jq}/bin/jq
|
||||||
|
cache="''${XDG_CACHE_HOME:-$HOME/.cache}/nomarchy"
|
||||||
|
state="$cache/updates.json"
|
||||||
|
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
||||||
|
mkdir -p "$cache"
|
||||||
|
|
||||||
|
count_nix() {
|
||||||
|
local lock="$flake/flake.lock" n=0 name kind owner repo url ref locked giturl up
|
||||||
|
[ -f "$lock" ] || { echo 0; return; }
|
||||||
|
while IFS=$'\t' read -r name kind owner repo url ref locked; do
|
||||||
|
[ -n "$locked" ] || continue
|
||||||
|
case "$kind" in
|
||||||
|
github) giturl="https://github.com/$owner/$repo" ;;
|
||||||
|
git) giturl="$url" ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
up=$("$GIT" ls-remote "$giturl" "$ref" 2>/dev/null | ${pkgs.gawk}/bin/awk 'NR==1{print $1}')
|
||||||
|
[ -n "$up" ] || continue # offline / unknown → skip (no false alarm)
|
||||||
|
[ "$up" != "$locked" ] && n=$((n + 1))
|
||||||
|
done < <(
|
||||||
|
# Only the flake's DIRECT inputs (root.inputs) — not the transitive
|
||||||
|
# closure — so a deep dependency bump doesn't nag as an "update".
|
||||||
|
"$JQ" -r '
|
||||||
|
.nodes as $nodes
|
||||||
|
| ($nodes.root.inputs | [ .[] | if type == "array" then .[0] else . end ]) as $direct
|
||||||
|
| $nodes | to_entries[]
|
||||||
|
| .key as $k | .value as $v
|
||||||
|
| select($direct | index($k))
|
||||||
|
| select(($v.original.type? == "github") or ($v.original.type? == "git"))
|
||||||
|
| select(($v.original.rev? // "") == "") # branch-tracking only
|
||||||
|
| [ $k, $v.original.type,
|
||||||
|
($v.original.owner? // ""), ($v.original.repo? // ""),
|
||||||
|
($v.original.url? // ""), ($v.original.ref? // "HEAD"),
|
||||||
|
($v.locked.rev? // "") ] | @tsv
|
||||||
|
' "$lock"
|
||||||
|
)
|
||||||
|
echo "$n"
|
||||||
|
}
|
||||||
|
|
||||||
|
count_flatpak() {
|
||||||
|
${lib.optionalString cfg.flatpak ''
|
||||||
|
if command -v flatpak >/dev/null 2>&1; then
|
||||||
|
flatpak remote-ls --updates --columns=application 2>/dev/null | ${pkgs.gnugrep}/bin/grep -c . || true
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_bar() { ${pkgs.procps}/bin/pkill -RTMIN+9 -x waybar 2>/dev/null || true; }
|
||||||
|
|
||||||
|
case "''${1:-status}" in
|
||||||
|
check)
|
||||||
|
nix=$(count_nix); fp=$(count_flatpak); total=$((nix + fp))
|
||||||
|
prev=$("$JQ" -r '.total // 0' "$state" 2>/dev/null || echo 0)
|
||||||
|
printf '{"nix":%d,"flatpak":%d,"total":%d,"ts":%d}\n' \
|
||||||
|
"$nix" "$fp" "$total" "$(${pkgs.coreutils}/bin/date +%s)" > "$state"
|
||||||
|
# Notify only when NEW updates appear, so a daily timer doesn't nag.
|
||||||
|
if [ "$total" -gt 0 ] && [ "$total" -gt "$prev" ]; then
|
||||||
|
msg="$nix flake input(s)"
|
||||||
|
[ "$fp" -gt 0 ] && msg="$msg · $fp Flatpak(s)"
|
||||||
|
${pkgs.libnotify}/bin/notify-send -a Nomarchy "Updates available" \
|
||||||
|
"$msg — click the bar icon, or run sys-update."
|
||||||
|
fi
|
||||||
|
refresh_bar ;;
|
||||||
|
status)
|
||||||
|
total=$("$JQ" -r '.total // 0' "$state" 2>/dev/null || echo 0)
|
||||||
|
[ "$total" -gt 0 ] 2>/dev/null || exit 0 # up to date / unchecked → hide
|
||||||
|
nix=$("$JQ" -r '.nix // 0' "$state"); fp=$("$JQ" -r '.flatpak // 0' "$state")
|
||||||
|
tip="Updates available"
|
||||||
|
[ "$nix" -gt 0 ] && tip="$tip\n• $nix flake input(s) — sys-update"
|
||||||
|
[ "$fp" -gt 0 ] && tip="$tip\n• $fp Flatpak(s) — flatpak update"
|
||||||
|
printf '{"text":" %d","tooltip":"%s","class":"available"}\n' "$total" "$tip" ;;
|
||||||
|
upgrade)
|
||||||
|
echo "Checking…"; "$0" check
|
||||||
|
nix=$("$JQ" -r '.nix // 0' "$state" 2>/dev/null || echo 0)
|
||||||
|
fp=$("$JQ" -r '.flatpak // 0' "$state" 2>/dev/null || echo 0)
|
||||||
|
echo "Pending: $nix flake input(s), $fp Flatpak(s)."
|
||||||
|
if [ "$nix" -gt 0 ] && command -v sys-update >/dev/null 2>&1; then
|
||||||
|
read -rp "Run sys-update (flake update + system rebuild)? [y/N] " a
|
||||||
|
[ "$a" = y ] && sys-update
|
||||||
|
fi
|
||||||
|
if [ "$fp" -gt 0 ] && command -v flatpak >/dev/null 2>&1; then
|
||||||
|
read -rp "Run flatpak update? [y/N] " a
|
||||||
|
[ "$a" = y ] && flatpak update
|
||||||
|
fi
|
||||||
|
"$0" check
|
||||||
|
echo "Done — press enter."; read -r _ || true ;;
|
||||||
|
*) echo "usage: nomarchy-updates [check|status|upgrade]" >&2; exit 64 ;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkMerge [
|
||||||
|
# Always on PATH so the Waybar module (incl. the static whole-swap themes)
|
||||||
|
# can exec it; it self-gates at runtime.
|
||||||
|
{ home.packages = [ nomarchy-updates ]; }
|
||||||
|
|
||||||
|
(lib.mkIf cfg.enable {
|
||||||
|
systemd.user.services.nomarchy-updates = {
|
||||||
|
Unit.Description = "Check for Nomarchy / nixpkgs / Flatpak updates";
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${nomarchy-updates}/bin/nomarchy-updates check";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
systemd.user.timers.nomarchy-updates = {
|
||||||
|
Unit.Description = "Periodic update-awareness check";
|
||||||
|
Timer = {
|
||||||
|
OnStartupSec = "2min";
|
||||||
|
OnCalendar = cfg.interval;
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "timers.target" ];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -77,9 +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" "custom/powerprofile" ]
|
modules-right = [ "tray" "pulseaudio" "cpu" "memory" "custom/powerprofile" "custom/nightlight" ]
|
||||||
++ lib.optional showLanguage "hyprland/language"
|
++ lib.optional showLanguage "hyprland/language"
|
||||||
++ [ "battery" "custom/notification" ];
|
++ [ "battery" "custom/updates" "custom/notification" ];
|
||||||
|
|
||||||
"hyprland/workspaces" = {
|
"hyprland/workspaces" = {
|
||||||
format = "{icon}";
|
format = "{icon}";
|
||||||
@@ -111,15 +111,8 @@ let
|
|||||||
on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
on-click = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
||||||
};
|
};
|
||||||
|
|
||||||
network = {
|
# No network module: nm-applet lives in the tray (the GUI path), so the
|
||||||
format-wifi = " {essid}";
|
# bar's wifi/ethernet indicator would just duplicate it.
|
||||||
format-ethernet = "";
|
|
||||||
format-disconnected = "";
|
|
||||||
tooltip-format = "{ipaddr} via {gwaddr}";
|
|
||||||
# nm-applet sits in the tray for the GUI path; this is the
|
|
||||||
# keyboard-friendly one.
|
|
||||||
on-click = "${config.nomarchy.terminal} -e nmtui";
|
|
||||||
};
|
|
||||||
|
|
||||||
cpu.format = " {usage}%";
|
cpu.format = " {usage}%";
|
||||||
memory.format = " {percentage}%";
|
memory.format = " {percentage}%";
|
||||||
@@ -138,6 +131,30 @@ let
|
|||||||
on-click = "nomarchy-powerprofile-cycle";
|
on-click = "nomarchy-powerprofile-cycle";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Night-light (hyprsunset) indicator. Self-gates: the moon shows only while
|
||||||
|
# the schedule runs; otherwise the status helper prints nothing => hidden
|
||||||
|
# (enable / re-enable from the System menu). Click toggles instantly — writes
|
||||||
|
# the in-flake on/off (settings.nightlight.on, no rebuild) and flips the unit
|
||||||
|
# with systemctl, so the choice lands in the flake and survives reboot.
|
||||||
|
"custom/nightlight" = {
|
||||||
|
exec = "nomarchy-nightlight status";
|
||||||
|
return-type = "json";
|
||||||
|
interval = 3;
|
||||||
|
on-click = "nomarchy-nightlight toggle";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Update awareness. Self-gates: hidden unless nomarchy.updates is enabled
|
||||||
|
# AND the periodic check found something (the helper prints nothing then).
|
||||||
|
# signal 9 lets the checker refresh it instantly; click opens the upgrade
|
||||||
|
# flow in a terminal.
|
||||||
|
"custom/updates" = {
|
||||||
|
exec = "nomarchy-updates status";
|
||||||
|
return-type = "json";
|
||||||
|
interval = 1800;
|
||||||
|
signal = 9;
|
||||||
|
on-click = "${config.nomarchy.terminal} -e nomarchy-updates upgrade";
|
||||||
|
};
|
||||||
|
|
||||||
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
||||||
# (text/tooltip/class) on every change, so it tracks count and DND with
|
# (text/tooltip/class) on every change, so it tracks count and DND with
|
||||||
# no polling. Left-click toggles the panel; right-click toggles DND.
|
# no polling. Left-click toggles the panel; right-click toggles DND.
|
||||||
@@ -209,11 +226,21 @@ let
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #language, #battery, #custom-notification {
|
#tray, #pulseaudio, #cpu, #memory, #custom-powerprofile, #custom-nightlight, #custom-updates, #language, #battery, #custom-notification {
|
||||||
color: @subtext;
|
color: @subtext;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Night-light active → warm tone, matching the filter it represents. */
|
||||||
|
#custom-nightlight.on { color: @warn; }
|
||||||
|
|
||||||
|
/* Updates pending → accent, to draw the eye. */
|
||||||
|
#custom-updates.available { color: @accent; }
|
||||||
|
|
||||||
|
/* The power-profile speedometer glyph renders small in its em box —
|
||||||
|
size it up so it reads at a glance like the other indicators. */
|
||||||
|
#custom-powerprofile { font-size: ${toString (t.fonts.size + 3)}pt; }
|
||||||
|
|
||||||
/* notifications waiting → accent; Do-Not-Disturb → muted bell-off */
|
/* notifications waiting → accent; Do-Not-Disturb → muted bell-off */
|
||||||
#custom-notification.notification { color: @accent; }
|
#custom-notification.notification { color: @accent; }
|
||||||
#custom-notification.dnd-none,
|
#custom-notification.dnd-none,
|
||||||
@@ -228,7 +255,14 @@ in
|
|||||||
{
|
{
|
||||||
programs.waybar = lib.mkIf config.nomarchy.waybar.enable {
|
programs.waybar = lib.mkIf config.nomarchy.waybar.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
systemd.enable = true; # started/stopped with graphical-session.target
|
# Launched from Hyprland's exec-once (hyprland.nix), NOT a systemd user
|
||||||
|
# service. Bound to graphical-session.target the unit raced Hyprland's IPC
|
||||||
|
# on a warm relogin — it started before the socket was up, exited, landed
|
||||||
|
# in `failed`, and was never retried, so the bar vanished. exec-once only
|
||||||
|
# fires once Hyprland is up, dodging the race; theme switches reload the
|
||||||
|
# running bar via SIGUSR2 (nomarchy-theme-sync). No uwsm here to manage the
|
||||||
|
# session target, so we don't depend on its lifecycle.
|
||||||
|
systemd.enable = false;
|
||||||
|
|
||||||
# mkDefault so downstream can replace the whole bar config/style with
|
# mkDefault so downstream can replace the whole bar config/style with
|
||||||
# a plain home.nix assignment. For per-theme identity, prefer the
|
# a plain home.nix assignment. For per-theme identity, prefer the
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ let
|
|||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ];
|
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ];
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
# The safe half of distro branding: distroName flows into
|
# The safe half of distro branding: distroName flows into
|
||||||
@@ -69,6 +69,11 @@ in
|
|||||||
console.earlySetup = lib.mkDefault true;
|
console.earlySetup = lib.mkDefault true;
|
||||||
boot.initrd.systemd.enable = lib.mkDefault true;
|
boot.initrd.systemd.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
# Nomarchy roots are BTRFS, not ZFS, so adopt the 26.11 default early and
|
||||||
|
# silence the eval warning the old `true` default emits. mkDefault, so a
|
||||||
|
# genuine ZFS-root downstream can still force it back on.
|
||||||
|
boot.zfs.forceImportRoot = lib.mkDefault false;
|
||||||
|
|
||||||
# ── 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.
|
||||||
@@ -320,8 +325,69 @@ in
|
|||||||
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
||||||
'')
|
'')
|
||||||
# The desktop snapshot manager (browse / diff / restore / rollback over
|
# The desktop snapshot manager (browse / diff / restore / rollback over
|
||||||
# snapper, elevating via polkit) — what `nomarchy-menu snapshot` launches.
|
# snapper, elevating via polkit). Kept for when nixpkgs fixes its crash.
|
||||||
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant;
|
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant
|
||||||
|
# Keyboard-driven snapper browser/restore — the fallback `nomarchy-menu
|
||||||
|
# snapshot` actually launches while btrfs-assistant 2.2 segfaults on 26.05
|
||||||
|
# (libbtrfsutil ABI). Runs as root (snapper is root-only here; the menu
|
||||||
|
# opens it in a terminal via sudo, one password prompt), fzf to pick, with
|
||||||
|
# browse/diff (read-only) and typed-`yes` confirmation before any write.
|
||||||
|
++ lib.optional cfg.snapper.enable (pkgs.writeShellApplication {
|
||||||
|
name = "nomarchy-snapshots";
|
||||||
|
runtimeInputs = with pkgs; [ snapper fzf gawk gnugrep less coreutils systemd ];
|
||||||
|
text = ''
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "nomarchy-snapshots must run as root (snapper needs it) — use sudo." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t configs < <(snapper list-configs | awk 'NR>2 {print $1}' | grep .)
|
||||||
|
if [ "''${#configs[@]}" -eq 0 ]; then
|
||||||
|
echo "No snapper configs found." >&2; exit 1
|
||||||
|
elif [ "''${#configs[@]}" -eq 1 ]; then
|
||||||
|
config="''${configs[0]}"
|
||||||
|
else
|
||||||
|
config=$(printf '%s\n' "''${configs[@]}" | fzf --prompt="snapper config> ") || exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while :; do
|
||||||
|
snap=$(snapper -c "$config" list \
|
||||||
|
| fzf --header-lines=2 --prompt="[$config] pick a snapshot (Esc quits)> ") || exit 0
|
||||||
|
num=$(awk '{print $1}' <<<"$snap")
|
||||||
|
case "$num" in ""|*[!0-9]*) continue ;; esac
|
||||||
|
|
||||||
|
action=$(printf '%s\n' \
|
||||||
|
"Browse changes since #$num (read-only)" \
|
||||||
|
"Restore changed files to #$num (undochange)" \
|
||||||
|
"Roll the system back to #$num (reboot)" \
|
||||||
|
"↩ Back to the snapshot list" \
|
||||||
|
| fzf --prompt="snapshot #$num> ") || exit 0
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
Browse*)
|
||||||
|
snapper -c "$config" status "$num..0" | less -R || true ;;
|
||||||
|
Restore*)
|
||||||
|
read -rp "Revert files in '$config' to snapshot #$num? Type yes to confirm: " ans || continue
|
||||||
|
if [ "$ans" = yes ]; then
|
||||||
|
snapper -c "$config" undochange "$num..0"
|
||||||
|
echo "Files restored. Press enter."; read -r _ || true
|
||||||
|
fi ;;
|
||||||
|
Roll*)
|
||||||
|
if [ "$config" != root ]; then
|
||||||
|
echo "Rollback applies to the 'root' config only; use Restore for '$config'. Press enter."
|
||||||
|
read -r _ || true
|
||||||
|
else
|
||||||
|
read -rp "Roll the SYSTEM back to #$num and REBOOT now? Type yes to confirm: " ans || continue
|
||||||
|
if [ "$ans" = yes ]; then
|
||||||
|
snapper -c root rollback "$num"
|
||||||
|
echo "Rolled back — rebooting…"; systemctl reboot
|
||||||
|
fi
|
||||||
|
fi ;;
|
||||||
|
*) continue ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
# Don't let boot entries fill the ESP over the years.
|
# Don't let boot entries fill the ESP over the years.
|
||||||
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
|
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
|
||||||
|
|||||||
195
modules/nixos/hardware.nix
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# Hardware enablement beyond nixos-hardware.
|
||||||
|
#
|
||||||
|
# The nixos-hardware "common-*" profiles the installer selects cover the
|
||||||
|
# BASICS (microcode, the Intel/AMD VA-API media stack, weekly fstrim), and
|
||||||
|
# power.nix adds thermald + power-profiles-daemon. This module fills the GAP
|
||||||
|
# above those: broadly-beneficial bits that default ON when the installer
|
||||||
|
# detects the vendor (opt-OUT), and heavier/experimental bits behind opt-IN
|
||||||
|
# toggles. The installer's hardware-db.sh probes what's present and writes the
|
||||||
|
# matching nomarchy.hardware.* into the generated system.nix.
|
||||||
|
#
|
||||||
|
# Audited against the commons so we don't double-set: we add GuC/HuC, the
|
||||||
|
# amd-pstate governor, the AMD VA-API env, GPU-compute runtimes, fprintd, and
|
||||||
|
# the NPU driver — none of which the commons turn on.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy.hardware;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.nomarchy.hardware = {
|
||||||
|
intel = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
Intel CPU/GPU enablement (the installer turns this on when it detects
|
||||||
|
an Intel CPU or GPU). Complements nixos-hardware's common-gpu-intel'';
|
||||||
|
|
||||||
|
guc = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = cfg.intel.enable;
|
||||||
|
defaultText = lib.literalExpression "config.nomarchy.hardware.intel.enable";
|
||||||
|
description = ''
|
||||||
|
Load the GPU's GuC/HuC firmware via the i915 param
|
||||||
|
(i915.enable_guc=3) — better power management and HuC-accelerated
|
||||||
|
media. On by default with intel.enable. NOTE: this is the *i915*
|
||||||
|
driver's param; the newer `xe` driver (Lunar Lake / Battlemage /
|
||||||
|
Panther Lake and other recent Xe GPUs) enables GuC by default and
|
||||||
|
ignores it, so the installer turns this off on xe-driver hardware.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
computeRuntime = lib.mkEnableOption ''
|
||||||
|
Intel GPU compute: the OpenCL / Level Zero (intel-compute-runtime) and
|
||||||
|
oneVPL (vpl-gpu-rt) runtimes for GPU compute and transcode. Opt-in (a
|
||||||
|
few hundred MB) — the Intel counterpart to AMD ROCm'';
|
||||||
|
};
|
||||||
|
|
||||||
|
amd = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
AMD CPU/GPU enablement (installer-set on an AMD CPU or GPU).
|
||||||
|
Complements nixos-hardware's common-cpu-amd / common-gpu-amd'';
|
||||||
|
|
||||||
|
pstate = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = cfg.amd.enable;
|
||||||
|
defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable";
|
||||||
|
description = ''
|
||||||
|
Use the amd-pstate EPP driver (amd_pstate=active) — the modern Zen
|
||||||
|
power/perf governor that power-profiles-daemon drives per profile.
|
||||||
|
On by default with amd.enable (broadly beneficial on Zen 2+).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
vaapi = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = cfg.amd.enable;
|
||||||
|
defaultText = lib.literalExpression "config.nomarchy.hardware.amd.enable";
|
||||||
|
description = ''
|
||||||
|
Point VA-API at mesa's radeonsi (LIBVA_DRIVER_NAME=radeonsi) for
|
||||||
|
hardware video decode/encode. On by default with amd.enable.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
rocm = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
AMD ROCm: the HIP / OpenCL GPU-compute stack (multi-GB closure).
|
||||||
|
Opt-in — unlocks GPU PyTorch / Ollama on Radeon'';
|
||||||
|
|
||||||
|
gfxOverride = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
example = "11.0.0";
|
||||||
|
description = ''
|
||||||
|
HSA_OVERRIDE_GFX_VERSION for GPUs ROCm doesn't officially list
|
||||||
|
(e.g. an RDNA3 780M iGPU, gfx1103, needs "11.0.0"). Empty = no
|
||||||
|
override.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fingerprint = {
|
||||||
|
enable = lib.mkEnableOption ''
|
||||||
|
a fingerprint reader via fprintd (the installer turns this on when it
|
||||||
|
detects a known reader). Enroll with `fprintd-enroll`'';
|
||||||
|
|
||||||
|
pam = lib.mkEnableOption ''
|
||||||
|
using the fingerprint for login and sudo (PAM). Opt-in — password-only
|
||||||
|
stays the default for the cautious; enroll a finger first'';
|
||||||
|
};
|
||||||
|
|
||||||
|
npu.enable = lib.mkEnableOption ''
|
||||||
|
the on-die NPU (AI accelerator) kernel driver — amdxdna on AMD (Ryzen
|
||||||
|
AI), intel_vpu on Intel (Core Ultra and newer). Opt-in and experimental:
|
||||||
|
this loads the in-kernel driver only; the userspace runtime (AMD XRT /
|
||||||
|
oneAPI Level Zero NPU) is yours to add. Needs a recent kernel (see
|
||||||
|
latestKernel)'';
|
||||||
|
|
||||||
|
latestKernel = lib.mkEnableOption ''
|
||||||
|
the latest mainline kernel (pkgs.linuxPackages_latest) instead of the
|
||||||
|
distro default — for very new hardware whose drivers (a fresh NPU, the
|
||||||
|
`xe` GPU driver, new-platform enablement) only landed recently. Off by
|
||||||
|
default; the default kernel already carries amd-pstate and amdxdna (6.14+)'';
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
# ── Intel ──────────────────────────────────────────────────────────
|
||||||
|
(lib.mkIf cfg.intel.guc {
|
||||||
|
boot.kernelParams = [ "i915.enable_guc=3" ];
|
||||||
|
})
|
||||||
|
(lib.mkIf cfg.intel.computeRuntime {
|
||||||
|
hardware.graphics.extraPackages = with pkgs; [ intel-compute-runtime vpl-gpu-rt ];
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── AMD ────────────────────────────────────────────────────────────
|
||||||
|
(lib.mkIf cfg.amd.pstate {
|
||||||
|
boot.kernelParams = [ "amd_pstate=active" ];
|
||||||
|
})
|
||||||
|
(lib.mkIf cfg.amd.vaapi {
|
||||||
|
# radeonsi itself comes from mesa (via common-gpu-amd); this just steers
|
||||||
|
# libva at it. mkDefault so a hand-set value or another module wins.
|
||||||
|
environment.sessionVariables.LIBVA_DRIVER_NAME = lib.mkDefault "radeonsi";
|
||||||
|
hardware.graphics.extraPackages = [ pkgs.libva ];
|
||||||
|
})
|
||||||
|
(lib.mkIf cfg.amd.rocm.enable {
|
||||||
|
hardware.graphics.extraPackages = [ pkgs.rocmPackages.clr pkgs.rocmPackages.clr.icd ];
|
||||||
|
environment.sessionVariables = lib.optionalAttrs (cfg.amd.rocm.gfxOverride != "") {
|
||||||
|
HSA_OVERRIDE_GFX_VERSION = cfg.amd.rocm.gfxOverride;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Fingerprint ────────────────────────────────────────────────────
|
||||||
|
(lib.mkIf cfg.fingerprint.enable {
|
||||||
|
services.fprintd.enable = true;
|
||||||
|
})
|
||||||
|
(lib.mkIf (cfg.fingerprint.enable && cfg.fingerprint.pam) {
|
||||||
|
security.pam.services.login.fprintAuth = true;
|
||||||
|
security.pam.services.sudo.fprintAuth = true;
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Newest kernel for very-new hardware (opt-in escape hatch) ──────
|
||||||
|
(lib.mkIf cfg.latestKernel {
|
||||||
|
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── NPU (in-kernel driver only; userspace runtime is BYO) ──────────
|
||||||
|
# The driver has to actually be in the running kernel — warn (don't fail)
|
||||||
|
# when it predates the shipped one, pointing at latestKernel.
|
||||||
|
(lib.mkIf (cfg.npu.enable && cfg.amd.enable) {
|
||||||
|
boot.kernelModules = [ "amdxdna" ];
|
||||||
|
warnings = lib.optional
|
||||||
|
(!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.14")
|
||||||
|
''
|
||||||
|
nomarchy.hardware.npu: the amdxdna driver needs kernel >= 6.14, but
|
||||||
|
this config ships ${config.boot.kernelPackages.kernel.version}. Set
|
||||||
|
nomarchy.hardware.latestKernel = true.'';
|
||||||
|
})
|
||||||
|
(lib.mkIf (cfg.npu.enable && cfg.intel.enable) {
|
||||||
|
boot.kernelModules = [ "intel_vpu" ];
|
||||||
|
warnings = lib.optional
|
||||||
|
(!lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.11")
|
||||||
|
''
|
||||||
|
nomarchy.hardware.npu: the intel_vpu driver (especially for newer
|
||||||
|
NPUs) wants a recent kernel, but this config ships
|
||||||
|
${config.boot.kernelPackages.kernel.version}. Consider
|
||||||
|
nomarchy.hardware.latestKernel = true.'';
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── Sanity ─────────────────────────────────────────────────────────
|
||||||
|
{
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.amd.rocm.enable -> cfg.amd.enable;
|
||||||
|
message = "nomarchy.hardware.amd.rocm.enable needs nomarchy.hardware.amd.enable.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.intel.computeRuntime -> cfg.intel.enable;
|
||||||
|
message = "nomarchy.hardware.intel.computeRuntime needs nomarchy.hardware.intel.enable.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = cfg.npu.enable -> (cfg.amd.enable || cfg.intel.enable);
|
||||||
|
message = "nomarchy.hardware.npu.enable needs a detected Intel or AMD platform.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -46,6 +46,16 @@
|
|||||||
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; };
|
||||||
|
|
||||||
|
autoTimezone.enable = lib.mkEnableOption ''
|
||||||
|
automatic timezone detection (geoclue + automatic-timezoned): the
|
||||||
|
system timezone — and so the Waybar clock — follows your location, so
|
||||||
|
travelling to another zone updates the time on its own. Off by default
|
||||||
|
(it's a location service and needs the network); toggle it from the
|
||||||
|
System menu, which lands the choice in the in-flake state file. Enabling
|
||||||
|
it unsets the static time.timeZone for you (a runtime timezone needs
|
||||||
|
/etc/localtime writable), so the menu toggle drives a system rebuild''
|
||||||
|
// { default = false; };
|
||||||
|
|
||||||
snapper.enable = lib.mkEnableOption ''
|
snapper.enable = lib.mkEnableOption ''
|
||||||
hourly/daily BTRFS timeline snapshots of / via snapper, plus the
|
hourly/daily BTRFS timeline snapshots of / via snapper, plus the
|
||||||
`nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the
|
`nixos-rebuild-snap` pre-rebuild-snapshot helper. No-op unless the
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ in
|
|||||||
|
|
||||||
printing.enable = lib.mkEnableOption ''
|
printing.enable = lib.mkEnableOption ''
|
||||||
CUPS printing with Avahi/mDNS, so network printers are auto-discovered
|
CUPS printing with Avahi/mDNS, so network printers are auto-discovered
|
||||||
(add vendor drivers via `services.printing.drivers`)'';
|
(add vendor drivers via `services.printing.drivers`); the menu's
|
||||||
|
System ▸ Printers entry opens the system-config-printer GUI'';
|
||||||
|
|
||||||
openrgb.enable = lib.mkEnableOption ''
|
openrgb.enable = lib.mkEnableOption ''
|
||||||
the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
|
the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
|
||||||
@@ -246,6 +247,9 @@ in
|
|||||||
nssmdns4 = true;
|
nssmdns4 = true;
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
};
|
};
|
||||||
|
# The CUPS admin GUI — the menu's System ▸ Printers entry execs it
|
||||||
|
# (self-gated on this binary), so it ships with the printing service.
|
||||||
|
environment.systemPackages = [ pkgs.system-config-printer ];
|
||||||
})
|
})
|
||||||
|
|
||||||
(lib.mkIf cfg.openrgb.enable {
|
(lib.mkIf cfg.openrgb.enable {
|
||||||
|
|||||||
87
modules/nixos/timezone.nix
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Automatic timezone detection (opt-in) — the system timezone, and so the
|
||||||
|
# Waybar clock, follows your location, so travelling to another zone updates
|
||||||
|
# the time on its own. Geoclue feeds `automatic-timezoned`, which drives
|
||||||
|
# /etc/localtime at runtime.
|
||||||
|
#
|
||||||
|
# In-flake state, menu-driven (the keyboard/night-light philosophy): the on/off
|
||||||
|
# flag lives in the same theme-state.json under `settings.autoTimezone`
|
||||||
|
# (git-tracked, reproducible), written by the System-menu toggle
|
||||||
|
# (nomarchy-autotimezone). Because this is a SYSTEM service — not a user unit it
|
||||||
|
# can start/stop instantly like night-light — the toggle drives a system rebuild
|
||||||
|
# (plus a home switch for the Waybar-refresh watcher in timezone.nix home-side).
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.nomarchy.system;
|
||||||
|
|
||||||
|
# Read the same state file the rest of the system side uses (Plymouth too),
|
||||||
|
# wired by lib.mkFlake. The flag defaults off when the file is absent/sparse.
|
||||||
|
state =
|
||||||
|
if cfg.stateFile != null
|
||||||
|
then builtins.fromJSON (builtins.readFile cfg.stateFile)
|
||||||
|
else { };
|
||||||
|
stateEnabled = (state.settings or { }).autoTimezone or false;
|
||||||
|
|
||||||
|
sync = lib.getExe pkgs.nomarchy-theme-sync;
|
||||||
|
|
||||||
|
# Menu/CLI toggle. Runs as the normal user (it owns the flake checkout +
|
||||||
|
# writes the state); sudos only the system switch, like sys-update. Writes
|
||||||
|
# the in-flake flag, then rebuilds: the system rebuild bakes the service +
|
||||||
|
# the time.timeZone override, the home switch installs/removes the Waybar
|
||||||
|
# refresh watcher — both read the same flag we just wrote.
|
||||||
|
nomarchy-autotimezone = pkgs.writeShellScriptBin "nomarchy-autotimezone" ''
|
||||||
|
set -e
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
echo "nomarchy-autotimezone: run as your normal user (it sudos the rebuild itself)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
||||||
|
|
||||||
|
cur=$(${sync} get settings.autoTimezone 2>/dev/null) || cur=false
|
||||||
|
case "''${1:-toggle}" in
|
||||||
|
on) new=true ;;
|
||||||
|
off) new=false ;;
|
||||||
|
toggle) case "$cur" in true|True) new=false ;; *) new=true ;; esac ;;
|
||||||
|
status) echo "$cur"; exit 0 ;;
|
||||||
|
*) echo "usage: nomarchy-autotimezone [toggle|on|off|status]" >&2; exit 64 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
${sync} --quiet set settings.autoTimezone "$new" --no-switch
|
||||||
|
|
||||||
|
if [ "$new" = true ]; then
|
||||||
|
notify-send "Auto timezone" "Enabling — rebuilding the system…" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
notify-send "Auto timezone" "Disabling — rebuilding the system…" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo nixos-rebuild switch --flake "$flake#default"
|
||||||
|
home-manager switch --flake "$flake"
|
||||||
|
|
||||||
|
if [ "$new" = true ]; then
|
||||||
|
notify-send "Auto timezone on" "The clock now follows your location." 2>/dev/null || true
|
||||||
|
else
|
||||||
|
notify-send "Auto timezone off" "Back to the fixed timezone in system.nix." 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkMerge [
|
||||||
|
{
|
||||||
|
# Shipped unconditionally so the menu can enable the feature even while
|
||||||
|
# it's off. Track the in-flake flag; mkDefault so a hand-set
|
||||||
|
# nomarchy.system.autoTimezone.enable in system.nix still wins.
|
||||||
|
environment.systemPackages = [ nomarchy-autotimezone ];
|
||||||
|
nomarchy.system.autoTimezone.enable = lib.mkDefault stateEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
(lib.mkIf cfg.autoTimezone.enable {
|
||||||
|
services.geoclue2.enable = true;
|
||||||
|
services.automatic-timezoned.enable = true;
|
||||||
|
# A runtime timezone needs /etc/localtime writable. automatic-timezoned
|
||||||
|
# sets time.timeZone = null itself, but the installer writes a static
|
||||||
|
# value at normal priority, which would collide (a hard eval error) —
|
||||||
|
# mkForce null overrides both and resolves cleanly.
|
||||||
|
time.timeZone = lib.mkForce null;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -112,6 +112,7 @@ HARDWARE_DB=(
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
nomarchy_detect_hw() {
|
nomarchy_detect_hw() {
|
||||||
local sys_vendor product_name cpu_vendor
|
local sys_vendor product_name cpu_vendor
|
||||||
|
local nvidia=0 amdgpu=0 intelgpu=0
|
||||||
sys_vendor=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo "")
|
sys_vendor=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null || echo "")
|
||||||
product_name=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "")
|
product_name=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "")
|
||||||
cpu_vendor=$(lscpu 2>/dev/null | awk -F: '/Vendor ID/{gsub(/ /,"",$2); print $2; exit}')
|
cpu_vendor=$(lscpu 2>/dev/null | awk -F: '/Vendor ID/{gsub(/ /,"",$2); print $2; exit}')
|
||||||
@@ -126,7 +127,7 @@ nomarchy_detect_hw() {
|
|||||||
|
|
||||||
# GPU (lspci may list several; report all)
|
# GPU (lspci may list several; report all)
|
||||||
if command -v lspci >/dev/null 2>&1; then
|
if command -v lspci >/dev/null 2>&1; then
|
||||||
local gpu_line nvidia=0 amdgpu=0 intelgpu=0
|
local gpu_line
|
||||||
while IFS= read -r gpu_line; do
|
while IFS= read -r gpu_line; do
|
||||||
case "$gpu_line" in
|
case "$gpu_line" in
|
||||||
*"[10de:"*|*"NVIDIA"*) nvidia=1 ;;
|
*"[10de:"*|*"NVIDIA"*) nvidia=1 ;;
|
||||||
@@ -140,6 +141,67 @@ nomarchy_detect_hw() {
|
|||||||
(( intelgpu )) && { echo "MODULE common-gpu-intel"; echo "DETAIL gpu: Intel"; detected=1; }
|
(( intelgpu )) && { echo "MODULE common-gpu-intel"; echo "DETAIL gpu: Intel"; detected=1; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── nomarchy.hardware.* enablement — the gap ABOVE the nixos-hardware
|
||||||
|
# commons (GuC/HuC, amd-pstate, the AMD VA-API env, GPU-compute runtimes,
|
||||||
|
# fprintd, the NPU driver). Emitted as NOMARCHY lines the installer turns
|
||||||
|
# into nomarchy.hardware.* in system.nix; safe bits active, heavy opt-ins
|
||||||
|
# commented.
|
||||||
|
if [[ "$cpu_vendor" == "GenuineIntel" || $intelgpu -eq 1 ]]; then
|
||||||
|
echo "NOMARCHY hardware.intel.enable=true"
|
||||||
|
echo "DETAIL nomarchy.hardware.intel: GuC/HuC on; GPU-compute runtime opt-in"
|
||||||
|
# i915.enable_guc applies to the i915 driver only. The newer `xe` driver
|
||||||
|
# (Lunar Lake / Battlemage / Panther Lake, recent Xe GPUs) enables GuC by
|
||||||
|
# default and ignores the param — so turn the toggle off when xe is bound.
|
||||||
|
if lspci -k 2>/dev/null | grep -qiE 'driver in use: xe\b'; then
|
||||||
|
echo "NOMARCHY hardware.intel.guc=false"
|
||||||
|
echo "DETAIL Intel GPU on the xe driver → GuC default-on (i915 param skipped)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "$cpu_vendor" == "AuthenticAMD" || $amdgpu -eq 1 ]]; then
|
||||||
|
echo "NOMARCHY hardware.amd.enable=true"
|
||||||
|
echo "DETAIL nomarchy.hardware.amd: amd-pstate + VA-API on; ROCm opt-in"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fingerprint reader — libfprint's common USB vendor IDs (Goodix,
|
||||||
|
# Synaptics/Validity, Elan, EgisTec, Upek, AuthenTec, FocalTech, NB).
|
||||||
|
local have_fp=0
|
||||||
|
if command -v lsusb >/dev/null 2>&1; then
|
||||||
|
local vid
|
||||||
|
for vid in 27c6 06cb 138a 04f3 1c7a 147e 08ff 2808 1fae; do
|
||||||
|
lsusb 2>/dev/null | grep -qiE "ID ${vid}:" && { have_fp=1; break; }
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local f
|
||||||
|
for f in /sys/bus/usb/devices/*/idVendor; do
|
||||||
|
[[ -e "$f" ]] || continue
|
||||||
|
case "$(cat "$f" 2>/dev/null)" in
|
||||||
|
27c6|06cb|138a|04f3|1c7a|147e|08ff|2808|1fae) have_fp=1; break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [[ $have_fp -eq 1 ]]; then
|
||||||
|
echo "NOMARCHY hardware.fingerprint.enable=true"
|
||||||
|
echo "DETAIL fingerprint reader detected → fprintd (PAM login/sudo opt-in)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# NPU (detect-only → a commented, experimental opt-in). Match the PCI
|
||||||
|
# "Processing accelerators" class [1200] (or accelerator keywords) and
|
||||||
|
# attribute by vendor — future-proof vs a per-device-ID list, so new gens
|
||||||
|
# (e.g. Panther Lake) are caught without a code change. Known IDs kept for
|
||||||
|
# reference: Intel VPU 8086:{7d1d,643e,ad1d,b03e}, AMD XDNA 1022:1502.
|
||||||
|
if command -v lspci >/dev/null 2>&1; then
|
||||||
|
local npu_line
|
||||||
|
npu_line=$(lspci -nn 2>/dev/null \
|
||||||
|
| grep -iE '\[1200\]|processing accelerat|neural|\[8086:(7d1d|643e|ad1d|b03e)\]|\[1022:1502\]' \
|
||||||
|
| head -1)
|
||||||
|
case "$npu_line" in
|
||||||
|
*"[8086:"*|*Intel*)
|
||||||
|
echo "NOMARCHY-NPU intel"; echo "DETAIL Intel NPU detected (opt-in, experimental)" ;;
|
||||||
|
*"[1022:"*|*"Advanced Micro Devices"*|*AMD*)
|
||||||
|
echo "NOMARCHY-NPU amd"; echo "DETAIL AMD XDNA NPU detected (opt-in, experimental)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# Chassis (glob test, not compgen — nixpkgs' non-interactive bash
|
# Chassis (glob test, not compgen — nixpkgs' non-interactive bash
|
||||||
# is built without the completion builtins)
|
# is built without the completion builtins)
|
||||||
local bats=(/sys/class/power_supply/BAT*)
|
local bats=(/sys/class/power_supply/BAT*)
|
||||||
|
|||||||
@@ -54,6 +54,15 @@ if [[ $EUID -ne 0 ]]; then
|
|||||||
exec sudo --preserve-env "$0" "$@"
|
exec sudo --preserve-env "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# `sudo --preserve-env` (needed to carry the NOMARCHY_* vars) also drags in
|
||||||
|
# the live session user's HOME=/home/nomarchy. Root-run `nix` calls below
|
||||||
|
# would then scribble an eval cache + .nix-defexpr into /home/nomarchy —
|
||||||
|
# and the in-chroot one lands on the TARGET disk as a stray, orphaned
|
||||||
|
# /home/nomarchy (no such user on the installed system). Pin root's own
|
||||||
|
# HOME so every root nix invocation stays in /root; the user activation
|
||||||
|
# sets HOME=/home/$USERNAME explicitly and is unaffected.
|
||||||
|
export HOME=/root
|
||||||
|
|
||||||
header "Nomarchy installer" "NixOS, themed and ready to go."
|
header "Nomarchy installer" "NixOS, themed and ready to go."
|
||||||
|
|
||||||
[[ -d /sys/firmware/efi ]] \
|
[[ -d /sys/firmware/efi ]] \
|
||||||
@@ -210,6 +219,8 @@ section "Hardware detection"
|
|||||||
source "$SHARE/hardware-db.sh"
|
source "$SHARE/hardware-db.sh"
|
||||||
|
|
||||||
HW_PROFILES=()
|
HW_PROFILES=()
|
||||||
|
HW_NOMARCHY=() # NOMARCHY hardware.* assignments from detection
|
||||||
|
NPU_VENDOR="" # "intel" | "amd" if an NPU was detected (commented opt-in)
|
||||||
hw_mode="${NOMARCHY_HW:-auto}"
|
hw_mode="${NOMARCHY_HW:-auto}"
|
||||||
if [[ "$hw_mode" == "none" ]]; then
|
if [[ "$hw_mode" == "none" ]]; then
|
||||||
info "Hardware profiles skipped."
|
info "Hardware profiles skipped."
|
||||||
@@ -220,8 +231,10 @@ else
|
|||||||
if [[ -n "$detection" ]]; then
|
if [[ -n "$detection" ]]; then
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
case "$line" in
|
case "$line" in
|
||||||
MODULE\ *) HW_PROFILES+=("${line#MODULE }") ;;
|
MODULE\ *) HW_PROFILES+=("${line#MODULE }") ;;
|
||||||
DETAIL\ *) info "→ ${line#DETAIL }" ;;
|
NOMARCHY-NPU\ *) NPU_VENDOR="${line#NOMARCHY-NPU }" ;;
|
||||||
|
NOMARCHY\ *) HW_NOMARCHY+=("${line#NOMARCHY }") ;;
|
||||||
|
DETAIL\ *) info "→ ${line#DETAIL }" ;;
|
||||||
esac
|
esac
|
||||||
done <<< "$detection"
|
done <<< "$detection"
|
||||||
fi
|
fi
|
||||||
@@ -428,6 +441,57 @@ NIX
|
|||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Hardware enablement (nomarchy.hardware.*): what hardware-db.sh detected
|
||||||
|
# above the nixos-hardware commons. Safe defaults active; the heavier or
|
||||||
|
# experimental opt-ins written commented for the user to flip on.
|
||||||
|
HARDWARE_CONFIG=""
|
||||||
|
if [[ ${#HW_NOMARCHY[@]} -gt 0 || -n "$NPU_VENDOR" ]]; then
|
||||||
|
has_intel=0; has_amd=0; has_fp=0; intel_guc_off=0
|
||||||
|
if [[ ${#HW_NOMARCHY[@]} -gt 0 ]]; then
|
||||||
|
for nm in "${HW_NOMARCHY[@]}"; do
|
||||||
|
case "$nm" in
|
||||||
|
hardware.intel.enable=true) has_intel=1 ;;
|
||||||
|
hardware.intel.guc=false) intel_guc_off=1 ;;
|
||||||
|
hardware.amd.enable=true) has_amd=1 ;;
|
||||||
|
hardware.fingerprint.enable=true) has_fp=1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
hw_lines=" # Hardware enablement (auto-detected). Safe defaults are active;
|
||||||
|
# the heavier opt-ins are commented — uncomment to turn them on."
|
||||||
|
if [[ $has_intel -eq 1 ]]; then
|
||||||
|
hw_lines+="
|
||||||
|
nomarchy.hardware.intel.enable = true; # GuC/HuC firmware (i915.enable_guc=3)"
|
||||||
|
if [[ $intel_guc_off -eq 1 ]]; then
|
||||||
|
hw_lines+="
|
||||||
|
nomarchy.hardware.intel.guc = false; # GPU on the xe driver → GuC is default-on"
|
||||||
|
fi
|
||||||
|
hw_lines+="
|
||||||
|
# nomarchy.hardware.intel.computeRuntime = true; # OpenCL/oneVPL GPU compute (opt-in)"
|
||||||
|
fi
|
||||||
|
if [[ $has_amd -eq 1 ]]; then
|
||||||
|
hw_lines+="
|
||||||
|
nomarchy.hardware.amd.enable = true; # amd-pstate EPP + radeonsi VA-API
|
||||||
|
# nomarchy.hardware.amd.rocm.enable = true; # ROCm GPU compute (multi-GB, opt-in)
|
||||||
|
# nomarchy.hardware.amd.rocm.gfxOverride = \"\"; # e.g. \"11.0.0\" for an unlisted iGPU"
|
||||||
|
fi
|
||||||
|
if [[ $has_fp -eq 1 ]]; then
|
||||||
|
hw_lines+="
|
||||||
|
nomarchy.hardware.fingerprint.enable = true; # fprintd (enroll: fprintd-enroll)
|
||||||
|
# nomarchy.hardware.fingerprint.pam = true; # use it for login + sudo (opt-in)"
|
||||||
|
fi
|
||||||
|
if [[ -n "$NPU_VENDOR" ]]; then
|
||||||
|
hw_lines+="
|
||||||
|
# nomarchy.hardware.npu.enable = true; # $NPU_VENDOR NPU driver (experimental; userspace runtime BYO)
|
||||||
|
# nomarchy.hardware.latestKernel = true; # newest kernel if the NPU driver isn't in the shipped one"
|
||||||
|
fi
|
||||||
|
HARDWARE_CONFIG=$(cat <<NIX
|
||||||
|
|
||||||
|
$hw_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
|
||||||
@@ -459,7 +523,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$POWER_CONFIG$RESUME_CONFIG
|
$AUTOLOGIN_CONFIG$POWER_CONFIG$HARDWARE_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;
|
||||||
|
|
||||||
@@ -573,6 +637,8 @@ 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
|
||||||
|
# Keep root's nix state in /root, not a stray /home/nomarchy on the target.
|
||||||
|
export HOME=/root
|
||||||
# Normally pre-built in the live env and copied over; the in-chroot
|
# Normally pre-built in the live env and copied over; the in-chroot
|
||||||
# build (default substituters — the live-side build already proved the
|
# build (default substituters — the live-side build already proved the
|
||||||
# no-network case) is a last-resort fallback.
|
# no-network case) is a last-resort fallback.
|
||||||
|
|||||||
@@ -165,12 +165,17 @@ def run_switch() -> None:
|
|||||||
# Persistent (timeout 0): stays up for the whole rebuild — replaced
|
# Persistent (timeout 0): stays up for the whole rebuild — replaced
|
||||||
# in place by the success/failure notification below — so a multi-
|
# in place by the success/failure notification below — so a multi-
|
||||||
# minute switch never looks like it silently failed.
|
# minute switch never looks like it silently failed.
|
||||||
notify("Applying theme — rebuilding the desktop…", persistent=True)
|
notify("Applying changes — 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", urgency="critical")
|
notify("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("Changes applied ✓")
|
||||||
|
# Waybar runs from Hyprland's exec-once (not a systemd unit HM would restart
|
||||||
|
# on switch), so nudge the running bar to re-read its freshly rebuilt
|
||||||
|
# config/style. SIGUSR2 = reload; harmless no-op if waybar isn't running.
|
||||||
|
if shutil.which("pkill"):
|
||||||
|
subprocess.run(["pkill", "--signal", "SIGUSR2", "-x", "waybar"], check=False)
|
||||||
|
|
||||||
|
|
||||||
# ─── Theme assets (wallpapers) ────────────────────────────────────────────
|
# ─── Theme assets (wallpapers) ────────────────────────────────────────────
|
||||||
@@ -289,7 +294,11 @@ def cmd_set(args) -> None:
|
|||||||
check_fonts(state)
|
check_fonts(state)
|
||||||
if not args.no_switch:
|
if not args.no_switch:
|
||||||
run_switch()
|
run_switch()
|
||||||
apply_wallpaper(state)
|
# Only wallpaper/theme keys change what swww shows; skip the re-apply
|
||||||
|
# (and its transition) for unrelated keys like ui.* or settings.* so a
|
||||||
|
# gaps tweak or a feature toggle doesn't flash the background.
|
||||||
|
if args.path.split(".")[0] in ("wallpaper", "slug"):
|
||||||
|
apply_wallpaper(state)
|
||||||
|
|
||||||
|
|
||||||
def cmd_get(args) -> None:
|
def cmd_get(args) -> None:
|
||||||
|
|||||||
@@ -16,12 +16,19 @@
|
|||||||
|
|
||||||
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||||
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
||||||
# enable = true;
|
# enable = true; # declarative opt-in (or just enable it from
|
||||||
|
# # the menu: System › Night light)
|
||||||
# temperature = 4000; # night warmth in K — lower is warmer
|
# temperature = 4000; # night warmth in K — lower is warmer
|
||||||
# sunset = "20:00";
|
# sunset = "20:00";
|
||||||
# sunrise = "07:00";
|
# sunrise = "07:00";
|
||||||
# };
|
# };
|
||||||
#
|
#
|
||||||
|
# nomarchy.updates = { # passive update-awareness indicator + notification
|
||||||
|
# enable = true;
|
||||||
|
# interval = "daily"; # how often to check (systemd OnCalendar)
|
||||||
|
# flatpak = true; # also count Flatpak updates when flatpak is on
|
||||||
|
# };
|
||||||
|
#
|
||||||
# nomarchy.monitors = [ # declarative per-output layout, hotplug-applied
|
# nomarchy.monitors = [ # declarative per-output layout, hotplug-applied
|
||||||
# { name = "eDP-1"; position = "0x0"; }
|
# { name = "eDP-1"; position = "0x0"; }
|
||||||
# { name = "HDMI-A-1"; position = "auto-right"; }
|
# { name = "HDMI-A-1"; position = "auto-right"; }
|
||||||
@@ -31,8 +38,9 @@
|
|||||||
# "keychron-keychron-k2" = { layout = "de"; };
|
# "keychron-keychron-k2" = { layout = "de"; };
|
||||||
# };
|
# };
|
||||||
#
|
#
|
||||||
# nomarchy.keyboard.layouts = [ "de" "fr" ]; # rofi-prompt for a layout when a
|
# nomarchy.keyboard.layouts = [ "de" "fr" ]; # rofi-prompt for a layout when a new
|
||||||
# # new keyboard connects + remember it
|
# # keyboard connects + remember it in the
|
||||||
|
# # flake state (graduates to .devices above)
|
||||||
|
|
||||||
# ── Application suite ───────────────────────────────────────────────
|
# ── Application suite ───────────────────────────────────────────────
|
||||||
# A starter complete-workstation set, installed for your user — yours to
|
# A starter complete-workstation set, installed for your user — yours to
|
||||||
|
|||||||
@@ -31,6 +31,23 @@
|
|||||||
# thermal.enable = true; # thermald (Intel CPUs)
|
# thermal.enable = true; # thermald (Intel CPUs)
|
||||||
# };
|
# };
|
||||||
#
|
#
|
||||||
|
# nomarchy.system.autoTimezone.enable = true; # clock follows your location
|
||||||
|
# # (geoclue); travelling updates the
|
||||||
|
# # time on its own. Unsets time.timeZone
|
||||||
|
# # above. Easier toggled from System menu.
|
||||||
|
#
|
||||||
|
# nomarchy.hardware = { # enablement above nixos-hardware (installer-detected)
|
||||||
|
# intel.enable = true; # GuC/HuC firmware; installer sets this on Intel
|
||||||
|
# intel.computeRuntime = true; # OpenCL/oneVPL GPU compute (opt-in)
|
||||||
|
# amd.enable = true; # amd-pstate + radeonsi VA-API; installer-set on AMD
|
||||||
|
# amd.rocm.enable = true; # ROCm GPU compute (multi-GB, opt-in)
|
||||||
|
# amd.rocm.gfxOverride = "11.0.0"; # HSA override for an unlisted iGPU
|
||||||
|
# fingerprint.enable = true; # fprintd; installer-set when a reader is detected
|
||||||
|
# fingerprint.pam = true; # use the fingerprint for login + sudo
|
||||||
|
# npu.enable = true; # on-die NPU driver (experimental; userspace runtime BYO)
|
||||||
|
# latestKernel = true; # newest kernel for very-new hardware (drivers not yet in the default)
|
||||||
|
# };
|
||||||
|
#
|
||||||
# nomarchy.services.tailscale.enable = true; # mesh VPN — then `sudo tailscale up`
|
# 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.syncthing.enable = true; # file sync — GUI at http://127.0.0.1:8384
|
||||||
# nomarchy.services.podman.enable = true; # rootless containers (docker → podman)
|
# nomarchy.services.podman.enable = true; # rootless containers (docker → podman)
|
||||||
|
|||||||
@@ -55,5 +55,6 @@
|
|||||||
"terminalOpacity": 0.96,
|
"terminalOpacity": 0.96,
|
||||||
"blur": true,
|
"blur": true,
|
||||||
"shadow": true
|
"shadow": true
|
||||||
}
|
},
|
||||||
|
"settings": {}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
themes/catppuccin-latte/preview.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
themes/catppuccin/preview.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
themes/ethereal/preview.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
themes/everforest/preview.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
themes/flexoki-light/preview.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
themes/gruvbox/preview.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
themes/hackerman/preview.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
themes/kanagawa/preview.png
Normal file
|
After Width: | Height: | Size: 229 KiB |
107
themes/kanagawa/rofi.rasi
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Kanagawa — rofi whole-swap (themes/<slug>/rofi.rasi).
|
||||||
|
* Ink-and-paper identity: a sumi-ink canvas inside a warm washi-paper frame
|
||||||
|
* (the same text-toned border the theme uses on windows — the only warm
|
||||||
|
* frame of the four), the inputbar a deeper ink well, and the crystal-blue
|
||||||
|
* wave reserved for the selected row. Soft rounding, an unhurried feel.
|
||||||
|
* The `configuration {}` block is omitted on purpose — modi/terminal/
|
||||||
|
* show-icons come from modules/home/rofi.nix.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
sumi: #1f1f28; /* sumi ink — the canvas */
|
||||||
|
well: #090618; /* deeper ink — inputbar + zebra */
|
||||||
|
paper: #dcd7ba; /* washi paper — text + frame */
|
||||||
|
parch: #c8c093; /* parchment — the prompt */
|
||||||
|
grey: #727169;
|
||||||
|
wave: #7e9cd8; /* crystal-blue wave — the selection */
|
||||||
|
|
||||||
|
font: "JetBrainsMono Nerd Font 14";
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @paper;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: @sumi;
|
||||||
|
border: 2px;
|
||||||
|
border-color: @paper; /* the warm paper frame (kanagawa's border tone) */
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 40%;
|
||||||
|
location: center;
|
||||||
|
anchor: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainbox {
|
||||||
|
background-color: transparent;
|
||||||
|
children: [ inputbar, message, listview ];
|
||||||
|
spacing: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputbar {
|
||||||
|
children: [ prompt, entry ];
|
||||||
|
background-color: @well; /* the ink well */
|
||||||
|
text-color: @paper;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
spacing: 8px;
|
||||||
|
margin: 0px 0px 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt { text-color: @parch; }
|
||||||
|
|
||||||
|
entry {
|
||||||
|
text-color: @paper;
|
||||||
|
cursor: text;
|
||||||
|
placeholder: "search…";
|
||||||
|
placeholder-color: @grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview {
|
||||||
|
background-color: transparent;
|
||||||
|
columns: 1;
|
||||||
|
lines: 8;
|
||||||
|
dynamic: true;
|
||||||
|
scrollbar: false;
|
||||||
|
spacing: 4px;
|
||||||
|
padding: 8px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @paper;
|
||||||
|
orientation: horizontal;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
spacing: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: @well;
|
||||||
|
}
|
||||||
|
element selected.normal {
|
||||||
|
background-color: @wave;
|
||||||
|
text-color: @sumi;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
size: 36px;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
element-text {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: inherit;
|
||||||
|
highlight: bold;
|
||||||
|
vertical-align: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message { padding: 8px 0px 0px 0px; }
|
||||||
|
textbox {
|
||||||
|
background-color: @well;
|
||||||
|
text-color: @paper;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar { width: 0px; }
|
||||||
BIN
themes/lumon/preview.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
110
themes/lumon/rofi.rasi
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Lumon — rofi whole-swap (themes/<slug>/rofi.rasi).
|
||||||
|
* Clinical-terminal identity (the MDR readout): a thick cyan bezel around a
|
||||||
|
* cold teal screen, the inputbar FRAMED rather than filled (a data field,
|
||||||
|
* not a button), near-white headings, cyan selection. Faintly softened
|
||||||
|
* corners keep it boxy without going razor-sharp like Retro 82.
|
||||||
|
* The `configuration {}` block is omitted on purpose — modi/terminal/
|
||||||
|
* show-icons come from modules/home/rofi.nix.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
base: #16242d;
|
||||||
|
surface: #1b2d40;
|
||||||
|
overlay: #304860;
|
||||||
|
text: #d6e2ee;
|
||||||
|
ice: #f2fcff; /* near-white — the prompt */
|
||||||
|
cyan: #8bc9eb; /* the Lumon glow — bezel + select */
|
||||||
|
|
||||||
|
font: "JetBrainsMono Nerd Font 13";
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: @base;
|
||||||
|
border: 3px; /* a screen bezel */
|
||||||
|
border-color: @cyan;
|
||||||
|
border-radius: 4px; /* boxy, faintly softened */
|
||||||
|
width: 40%;
|
||||||
|
location: center;
|
||||||
|
anchor: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainbox {
|
||||||
|
background-color: transparent;
|
||||||
|
children: [ inputbar, message, listview ];
|
||||||
|
spacing: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Framed, not filled — a readout field rather than a button. */
|
||||||
|
inputbar {
|
||||||
|
children: [ prompt, entry ];
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
border: 1px;
|
||||||
|
border-color: @overlay;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
spacing: 8px;
|
||||||
|
margin: 0px 0px 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt { text-color: @ice; }
|
||||||
|
|
||||||
|
entry {
|
||||||
|
text-color: @text;
|
||||||
|
cursor: text;
|
||||||
|
placeholder: "refine…";
|
||||||
|
placeholder-color: @overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview {
|
||||||
|
background-color: transparent;
|
||||||
|
columns: 1;
|
||||||
|
lines: 8;
|
||||||
|
dynamic: true;
|
||||||
|
scrollbar: false;
|
||||||
|
spacing: 4px;
|
||||||
|
padding: 8px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
orientation: horizontal;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
spacing: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: @surface;
|
||||||
|
}
|
||||||
|
element selected.normal {
|
||||||
|
background-color: @cyan;
|
||||||
|
text-color: @base;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
size: 36px;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
element-text {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: inherit;
|
||||||
|
highlight: bold;
|
||||||
|
vertical-align: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message { padding: 8px 0px 0px 0px; }
|
||||||
|
textbox {
|
||||||
|
background-color: @surface;
|
||||||
|
text-color: @text;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar { width: 0px; }
|
||||||
BIN
themes/matte-black/preview.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
themes/miasma/preview.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
themes/nord/preview.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
110
themes/nord/rofi.rasi
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Nord — rofi whole-swap (themes/<slug>/rofi.rasi).
|
||||||
|
* Frost identity: a soft, rounded "frost panel". The window wears a calm
|
||||||
|
* frost border; the selected row lifts to the brighter frost, and the
|
||||||
|
* prompt picks up the aurora purple for the one point of warmth.
|
||||||
|
* The `configuration {}` block is omitted on purpose — modi/terminal/
|
||||||
|
* show-icons come from modules/home/rofi.nix. Element structure mirrors
|
||||||
|
* the generated theme so the theme-grid picker's per-invocation
|
||||||
|
* -theme-str (vertical cards) still lays out correctly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
base: #2e3440;
|
||||||
|
surface: #3b4252;
|
||||||
|
overlay: #4c566a;
|
||||||
|
text: #d8dee9;
|
||||||
|
frost1: #81a1c1; /* calm frost — the border */
|
||||||
|
frost2: #88c0d0; /* bright frost — the selection */
|
||||||
|
aurora: #b48ead; /* the single accent pop — prompt */
|
||||||
|
|
||||||
|
font: "JetBrainsMono Nerd Font 13";
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: @base;
|
||||||
|
border: 2px;
|
||||||
|
border-color: @frost1;
|
||||||
|
border-radius: 14px; /* Nord is soft and rounded */
|
||||||
|
width: 40%;
|
||||||
|
location: center;
|
||||||
|
anchor: center;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainbox {
|
||||||
|
background-color: transparent;
|
||||||
|
children: [ inputbar, message, listview ];
|
||||||
|
spacing: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputbar {
|
||||||
|
children: [ prompt, entry ];
|
||||||
|
background-color: @surface;
|
||||||
|
text-color: @text;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
spacing: 8px;
|
||||||
|
margin: 0px 0px 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt { text-color: @aurora; }
|
||||||
|
|
||||||
|
entry {
|
||||||
|
text-color: @text;
|
||||||
|
cursor: text;
|
||||||
|
placeholder: "Search…";
|
||||||
|
placeholder-color: @overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview {
|
||||||
|
background-color: transparent;
|
||||||
|
columns: 1;
|
||||||
|
lines: 8;
|
||||||
|
dynamic: true;
|
||||||
|
scrollbar: false;
|
||||||
|
spacing: 4px;
|
||||||
|
padding: 8px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @text;
|
||||||
|
orientation: horizontal;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
spacing: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Faint frost-grey zebra; the selected row below still wins. */
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: @surface;
|
||||||
|
}
|
||||||
|
element selected.normal {
|
||||||
|
background-color: @frost2;
|
||||||
|
text-color: @base;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
size: 36px;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
element-text {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: inherit;
|
||||||
|
highlight: bold;
|
||||||
|
vertical-align: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message { padding: 8px 0px 0px 0px; }
|
||||||
|
textbox {
|
||||||
|
background-color: @surface;
|
||||||
|
text-color: @text;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar { width: 0px; }
|
||||||
BIN
themes/osaka-jade/preview.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
themes/retro-82/preview.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
109
themes/retro-82/rofi.rasi
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Nomarchy Retro 82 — rofi whole-swap (themes/<slug>/rofi.rasi).
|
||||||
|
* CRT-terminal identity: SQUARE corners (the giveaway against every rounded
|
||||||
|
* theme), amber-on-navy phosphor, a monospace face. The window wears a hard
|
||||||
|
* amber frame; the inputbar sits over a teal underline (a stray scanline),
|
||||||
|
* and the prompt + selected row burn amber.
|
||||||
|
* The `configuration {}` block is omitted on purpose — modi/terminal/
|
||||||
|
* show-icons come from modules/home/rofi.nix.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
navy: #05182e;
|
||||||
|
panel: #00172e;
|
||||||
|
overlay: #134e5a;
|
||||||
|
sand: #f6dcac; /* phosphor cream — the foreground */
|
||||||
|
teal: #3f8f8a; /* the underline scanline */
|
||||||
|
amber: #faa968; /* the glow — frame, prompt, select */
|
||||||
|
|
||||||
|
font: "JetBrainsMono Nerd Font 13";
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @sand;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
background-color: @navy;
|
||||||
|
border: 2px;
|
||||||
|
border-color: @amber;
|
||||||
|
border-radius: 0px; /* sharp: this is a terminal */
|
||||||
|
width: 40%;
|
||||||
|
location: center;
|
||||||
|
anchor: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainbox {
|
||||||
|
background-color: transparent;
|
||||||
|
children: [ inputbar, message, listview ];
|
||||||
|
spacing: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputbar {
|
||||||
|
children: [ prompt, entry ];
|
||||||
|
background-color: @panel;
|
||||||
|
text-color: @sand;
|
||||||
|
border-bottom: 2px; /* a teal scanline under the field */
|
||||||
|
border-color: @teal;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
spacing: 8px;
|
||||||
|
margin: 0px 0px 8px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt { text-color: @amber; }
|
||||||
|
|
||||||
|
entry {
|
||||||
|
text-color: @sand;
|
||||||
|
cursor: text;
|
||||||
|
placeholder: "search…";
|
||||||
|
placeholder-color: @overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
listview {
|
||||||
|
background-color: transparent;
|
||||||
|
columns: 1;
|
||||||
|
lines: 8;
|
||||||
|
dynamic: true;
|
||||||
|
scrollbar: false;
|
||||||
|
spacing: 2px;
|
||||||
|
padding: 8px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: @sand;
|
||||||
|
orientation: horizontal;
|
||||||
|
border-radius: 0px; /* square rows too */
|
||||||
|
padding: 10px 14px;
|
||||||
|
spacing: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
element alternate.normal {
|
||||||
|
background-color: @panel;
|
||||||
|
}
|
||||||
|
element selected.normal {
|
||||||
|
background-color: @amber;
|
||||||
|
text-color: @navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
element-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
size: 36px;
|
||||||
|
cursor: inherit;
|
||||||
|
}
|
||||||
|
element-text {
|
||||||
|
background-color: transparent;
|
||||||
|
text-color: inherit;
|
||||||
|
highlight: bold;
|
||||||
|
vertical-align: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message { padding: 8px 0px 0px 0px; }
|
||||||
|
textbox {
|
||||||
|
background-color: @panel;
|
||||||
|
text-color: @sand;
|
||||||
|
border-radius: 0px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollbar { width: 0px; }
|
||||||
BIN
themes/ristretto/preview.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
themes/rose-pine/preview.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
themes/summer-day/preview.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
@@ -47,8 +47,9 @@ window#waybar {
|
|||||||
#clock-date,
|
#clock-date,
|
||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
#network,
|
|
||||||
#custom-powerprofile,
|
#custom-powerprofile,
|
||||||
|
#custom-nightlight,
|
||||||
|
#custom-updates,
|
||||||
#battery,
|
#battery,
|
||||||
#tray,
|
#tray,
|
||||||
#custom-notification,
|
#custom-notification,
|
||||||
@@ -71,6 +72,11 @@ window#waybar {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The power-profile speedometer glyph renders small in its em box. */
|
||||||
|
#custom-powerprofile {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#workspaces button.active {
|
#workspaces button.active {
|
||||||
background-color: @blue;
|
background-color: @blue;
|
||||||
color: @bg0;
|
color: @bg0;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
"modules-left": ["custom/launcher", "clock", "clock#date"],
|
"modules-left": ["custom/launcher", "clock", "clock#date"],
|
||||||
"modules-center": ["hyprland/workspaces"],
|
"modules-center": ["hyprland/workspaces"],
|
||||||
"modules-right": ["pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
|
"modules-right": ["pulseaudio", "custom/powerprofile", "custom/nightlight", "custom/updates", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||||
|
|
||||||
"hyprland/workspaces": {
|
"hyprland/workspaces": {
|
||||||
"disable-scroll": true,
|
"disable-scroll": true,
|
||||||
@@ -46,14 +46,6 @@
|
|||||||
"tooltip-format": "Playing at {volume}%"
|
"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": {
|
"battery": {
|
||||||
"interval": 60,
|
"interval": 60,
|
||||||
"states": { "warning": 30, "critical": 15 },
|
"states": { "warning": 30, "critical": 15 },
|
||||||
@@ -87,6 +79,21 @@
|
|||||||
"on-click": "nomarchy-powerprofile-cycle"
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"custom/nightlight": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 3,
|
||||||
|
"exec": "nomarchy-nightlight status",
|
||||||
|
"on-click": "nomarchy-nightlight toggle"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/updates": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 1800,
|
||||||
|
"signal": 9,
|
||||||
|
"exec": "nomarchy-updates status",
|
||||||
|
"on-click": "sh -c '$TERMINAL -e nomarchy-updates upgrade'"
|
||||||
|
},
|
||||||
|
|
||||||
"custom/notification": {
|
"custom/notification": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
|
|||||||
BIN
themes/summer-night/preview.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
@@ -53,8 +53,9 @@ window#waybar {
|
|||||||
#clock.date,
|
#clock.date,
|
||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
#network,
|
|
||||||
#custom-powerprofile,
|
#custom-powerprofile,
|
||||||
|
#custom-nightlight,
|
||||||
|
#custom-updates,
|
||||||
#idle_inhibitor,
|
#idle_inhibitor,
|
||||||
#battery,
|
#battery,
|
||||||
#custom-notification,
|
#custom-notification,
|
||||||
@@ -75,6 +76,11 @@ window#waybar {
|
|||||||
color: @fg;
|
color: @fg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The power-profile speedometer glyph renders small in its em box. */
|
||||||
|
#custom-powerprofile {
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
#tray > .needs-attention {
|
#tray > .needs-attention {
|
||||||
color: @green;
|
color: @green;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
"modules-left": ["custom/nomarchy", "clock", "clock#date"],
|
"modules-left": ["custom/nomarchy", "clock", "clock#date"],
|
||||||
"modules-center": ["hyprland/workspaces"],
|
"modules-center": ["hyprland/workspaces"],
|
||||||
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
|
"modules-right": ["idle_inhibitor", "pulseaudio", "custom/powerprofile", "custom/nightlight", "custom/updates", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||||
|
|
||||||
"hyprland/workspaces": {
|
"hyprland/workspaces": {
|
||||||
"disable-scroll": true,
|
"disable-scroll": true,
|
||||||
@@ -55,14 +55,6 @@
|
|||||||
"tooltip-format": "{desc} | {volume}%"
|
"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": {
|
"battery": {
|
||||||
"interval": 30,
|
"interval": 30,
|
||||||
"states": { "warning": 25, "critical": 10 },
|
"states": { "warning": 25, "critical": 10 },
|
||||||
@@ -94,6 +86,21 @@
|
|||||||
"on-click": "nomarchy-powerprofile-cycle"
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"custom/nightlight": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 3,
|
||||||
|
"exec": "nomarchy-nightlight status",
|
||||||
|
"on-click": "nomarchy-nightlight toggle"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/updates": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 1800,
|
||||||
|
"signal": 9,
|
||||||
|
"exec": "nomarchy-updates status",
|
||||||
|
"on-click": "sh -c '$TERMINAL -e nomarchy-updates upgrade'"
|
||||||
|
},
|
||||||
|
|
||||||
"custom/notification": {
|
"custom/notification": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
|
|||||||
BIN
themes/tokyo-night/preview.png
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
themes/vantablack/preview.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
themes/white/preview.png
Normal file
|
After Width: | Height: | Size: 49 KiB |