feat: Nomarchy ground-up rewrite on NixOS 26.05
Full replacement of the previous iteration, rebuilt around three ideas:
- Pure evaluation: theme-state.json lives inside the flake and is read
via the nomarchy.stateFile option — no --impure, ever.
- All-Home-Manager theming: `nomarchy-theme-sync apply` writes the JSON
and runs `home-manager switch`; every theme change is one atomic,
rollbackable generation. Wallpaper (swww) is the sole runtime piece.
- Flat, downstream-first layout: modules/{nixos,home} with one
options.nix each, exported as nixosModules/homeModules + overlay +
flake template; system (nixos-rebuild) and desktop (home-manager
switch) rebuild paths are fully split.
Ships 21 themes imported from the previous iteration (palettes,
wallpapers, btop themes, six whole-swap Waybar identities), Stylix for
the GTK/Qt/cursor long tail, a live ISO target with offline theme
switching, and docs/TESTING.md with the QEMU verification workflow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
61
modules/home/btop.nix
Normal file
61
modules/home/btop.nix
Normal file
@@ -0,0 +1,61 @@
|
||||
# btop — themed from per-theme assets, baked at eval time.
|
||||
# A hand-made themes/<slug>/btop.theme asset wins; otherwise a theme is
|
||||
# generated from the palette, so custom themes get a themed btop too.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
c = t.colors;
|
||||
|
||||
asset = config.nomarchy.themesDir + "/${t.slug}/btop.theme";
|
||||
|
||||
generated =
|
||||
let
|
||||
pairs = {
|
||||
main_bg = c.base;
|
||||
main_fg = c.text;
|
||||
title = c.text;
|
||||
hi_fg = c.accent;
|
||||
selected_bg = c.overlay;
|
||||
selected_fg = c.text;
|
||||
inactive_fg = c.muted;
|
||||
graph_text = c.subtext;
|
||||
meter_bg = c.surface;
|
||||
proc_misc = c.accentAlt;
|
||||
cpu_box = c.accent;
|
||||
mem_box = c.good;
|
||||
net_box = c.accentAlt;
|
||||
proc_box = c.surface;
|
||||
div_line = c.overlay;
|
||||
temp_start = c.good;
|
||||
temp_mid = c.warn;
|
||||
temp_end = c.bad;
|
||||
cpu_start = c.good;
|
||||
cpu_mid = c.warn;
|
||||
cpu_end = c.bad;
|
||||
free_start = c.good;
|
||||
used_start = c.warn;
|
||||
download_start = c.accent;
|
||||
upload_start = c.accentAlt;
|
||||
};
|
||||
in
|
||||
lib.concatStringsSep "\n"
|
||||
(lib.mapAttrsToList (k: v: "theme[${k}]=\"${v}\"") pairs) + "\n";
|
||||
in
|
||||
{
|
||||
config = lib.mkIf config.nomarchy.btop.enable {
|
||||
programs.btop = {
|
||||
enable = true;
|
||||
settings = {
|
||||
color_theme = "nomarchy";
|
||||
theme_background = false; # let the terminal's opacity show through
|
||||
vim_keys = true;
|
||||
};
|
||||
};
|
||||
|
||||
xdg.configFile."btop/themes/nomarchy.theme" =
|
||||
if builtins.pathExists asset
|
||||
then { source = asset; }
|
||||
else { text = generated; };
|
||||
};
|
||||
}
|
||||
36
modules/home/default.nix
Normal file
36
modules/home/default.nix
Normal file
@@ -0,0 +1,36 @@
|
||||
# Nomarchy — Home Manager entry point.
|
||||
# Consume this via homeModules.nomarchy (flake.nix), which also pulls in
|
||||
# the stylix home module that stylix.nix configures.
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./options.nix # the nomarchy.* option surface
|
||||
./theme.nix # ingests theme-state.json, owns the live-sync hooks
|
||||
./stylix.nix # GTK/Qt/cursor/fonts from the same JSON
|
||||
./hyprland.nix
|
||||
./waybar.nix
|
||||
./ghostty.nix
|
||||
./btop.nix
|
||||
];
|
||||
|
||||
home.stateVersion = "26.05";
|
||||
|
||||
home.packages = with pkgs; [
|
||||
swww # wallpaper daemon with animated transitions
|
||||
fuzzel # launcher / dmenu (used by the theme picker bind)
|
||||
libnotify
|
||||
];
|
||||
|
||||
home.sessionVariables = {
|
||||
TERMINAL = config.nomarchy.terminal;
|
||||
NIXOS_OZONE_WL = "1"; # Electron/Chromium native Wayland
|
||||
|
||||
# Where the Nomarchy flake (and therefore theme-state.json) lives on
|
||||
# disk. nomarchy-theme-sync writes its state here; rebuilds read from
|
||||
# here. Clone/symlink your flake to this path.
|
||||
NOMARCHY_PATH = "$HOME/.nomarchy";
|
||||
};
|
||||
|
||||
programs.home-manager.enable = true;
|
||||
}
|
||||
41
modules/home/ghostty.nix
Normal file
41
modules/home/ghostty.nix
Normal file
@@ -0,0 +1,41 @@
|
||||
# Ghostty — Nomarchy's default terminal, themed from theme-state.json.
|
||||
# Colors, fonts and the full 16-color ANSI palette are baked from the
|
||||
# JSON at eval time.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
c = t.colors;
|
||||
in
|
||||
{
|
||||
programs.ghostty = lib.mkIf config.nomarchy.ghostty.enable {
|
||||
enable = true;
|
||||
enableBashIntegration = true;
|
||||
|
||||
settings = {
|
||||
# ── Typography (from theme-state.json) ────────────────────────
|
||||
font-family = t.fonts.mono;
|
||||
font-size = t.fonts.size;
|
||||
|
||||
# ── Colors (from theme-state.json) ────────────────────────────
|
||||
background = c.base;
|
||||
foreground = c.text;
|
||||
cursor-color = c.accent;
|
||||
selection-background = c.overlay;
|
||||
selection-foreground = c.text;
|
||||
split-divider-color = c.surface;
|
||||
|
||||
# "N=#rrggbb" entries; Ghostty accepts repeated `palette` keys,
|
||||
# which the HM module renders from this list.
|
||||
palette = lib.imap0 (i: color: "${toString i}=${color}") t.ansi;
|
||||
|
||||
# ── Chrome ────────────────────────────────────────────────────
|
||||
background-opacity = t.ui.terminalOpacity;
|
||||
window-padding-x = 12;
|
||||
window-padding-y = 12;
|
||||
window-decoration = false;
|
||||
gtk-single-instance = true;
|
||||
confirm-close-surface = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
140
modules/home/hyprland.nix
Normal file
140
modules/home/hyprland.nix
Normal file
@@ -0,0 +1,140 @@
|
||||
# Hyprland — fully driven by config.nomarchy.theme (theme-state.json).
|
||||
# Gaps, borders and colors are baked from the JSON at eval time; theme
|
||||
# changes arrive via `home-manager switch`.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
c = t.colors;
|
||||
inherit (config.nomarchy.lib) rgb rgba;
|
||||
|
||||
# SUPER+1..9 / SUPER+SHIFT+1..9 workspace binds, generated.
|
||||
workspaceBinds = builtins.concatLists (builtins.genList
|
||||
(i:
|
||||
let ws = toString (i + 1);
|
||||
in [
|
||||
"$mod, ${ws}, workspace, ${ws}"
|
||||
"$mod SHIFT, ${ws}, movetoworkspace, ${ws}"
|
||||
])
|
||||
9);
|
||||
in
|
||||
{
|
||||
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
||||
enable = true;
|
||||
# The binary and portal come from the NixOS module
|
||||
# (programs.hyprland.enable); HM only manages configuration.
|
||||
package = null;
|
||||
portalPackage = null;
|
||||
|
||||
settings = {
|
||||
"$mod" = "SUPER";
|
||||
"$terminal" = config.nomarchy.terminal;
|
||||
|
||||
monitor = [ ",preferred,auto,1" ];
|
||||
|
||||
exec-once = [
|
||||
"swww-daemon"
|
||||
# Paint the wallpaper as soon as the session is up (waits for
|
||||
# swww-daemon internally).
|
||||
"nomarchy-theme-sync wallpaper"
|
||||
];
|
||||
|
||||
# ── Theme-driven look ──────────────────────────────────────────
|
||||
general = {
|
||||
gaps_in = t.ui.gapsIn;
|
||||
gaps_out = t.ui.gapsOut;
|
||||
border_size = t.ui.borderSize;
|
||||
"col.active_border" = "${rgb c.accent} ${rgb c.accentAlt} 45deg";
|
||||
"col.inactive_border" = rgb c.overlay;
|
||||
layout = "dwindle";
|
||||
resize_on_border = true;
|
||||
};
|
||||
|
||||
decoration = {
|
||||
rounding = t.ui.rounding;
|
||||
active_opacity = t.ui.activeOpacity;
|
||||
inactive_opacity = t.ui.inactiveOpacity;
|
||||
blur = {
|
||||
enabled = t.ui.blur;
|
||||
size = 8;
|
||||
passes = 2;
|
||||
popups = true;
|
||||
};
|
||||
shadow = {
|
||||
enabled = t.ui.shadow;
|
||||
range = 20;
|
||||
render_power = 3;
|
||||
color = rgba c.mantle "aa";
|
||||
};
|
||||
};
|
||||
|
||||
animations = {
|
||||
enabled = true;
|
||||
bezier = [
|
||||
"smooth, 0.25, 0.1, 0.25, 1.0"
|
||||
"snappy, 0.6, 0.0, 0.1, 1.0"
|
||||
];
|
||||
animation = [
|
||||
"windows, 1, 4, snappy, popin 85%"
|
||||
"border, 1, 8, smooth"
|
||||
"fade, 1, 5, smooth"
|
||||
"workspaces, 1, 4, snappy, slidefade 15%"
|
||||
];
|
||||
};
|
||||
|
||||
# ── Behaviour ──────────────────────────────────────────────────
|
||||
input = {
|
||||
kb_layout = "us";
|
||||
follow_mouse = 1;
|
||||
touchpad.natural_scroll = true;
|
||||
};
|
||||
|
||||
dwindle = {
|
||||
pseudotile = true;
|
||||
preserve_split = true;
|
||||
};
|
||||
|
||||
misc = {
|
||||
disable_hyprland_logo = true;
|
||||
force_default_wallpaper = 0;
|
||||
};
|
||||
|
||||
bind = [
|
||||
"$mod, Return, exec, $terminal"
|
||||
"$mod, D, exec, fuzzel"
|
||||
"$mod, Q, killactive"
|
||||
"$mod, F, fullscreen"
|
||||
"$mod, V, togglefloating"
|
||||
"$mod SHIFT, E, exit"
|
||||
|
||||
# Theme picker: fuzzel menu over the presets; apply writes the
|
||||
# state and runs home-manager switch (progress via notify-send).
|
||||
"$mod, T, exec, nomarchy-theme-sync apply \"$(nomarchy-theme-sync list | fuzzel --dmenu --prompt 'theme> ')\""
|
||||
# Cycle the current theme's wallpapers (instant, no rebuild).
|
||||
"$mod SHIFT, T, exec, nomarchy-theme-sync bg next"
|
||||
|
||||
# Focus
|
||||
"$mod, H, movefocus, l"
|
||||
"$mod, L, movefocus, r"
|
||||
"$mod, K, movefocus, u"
|
||||
"$mod, J, movefocus, d"
|
||||
|
||||
# Screenshot region to clipboard
|
||||
", Print, exec, grim -g \"$(slurp)\" - | wl-copy"
|
||||
] ++ workspaceBinds;
|
||||
|
||||
binde = [
|
||||
", XF86AudioRaiseVolume, exec, pamixer -i 5"
|
||||
", XF86AudioLowerVolume, exec, pamixer -d 5"
|
||||
", XF86AudioMute, exec, pamixer -t"
|
||||
", XF86MonBrightnessUp, exec, brightnessctl set +5%"
|
||||
", XF86MonBrightnessDown, exec, brightnessctl set 5%-"
|
||||
];
|
||||
|
||||
bindm = [
|
||||
"$mod, mouse:272, movewindow"
|
||||
"$mod, mouse:273, resizewindow"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
64
modules/home/options.nix
Normal file
64
modules/home/options.nix
Normal file
@@ -0,0 +1,64 @@
|
||||
# User-level `nomarchy.*` options — the full surface downstream users
|
||||
# configure in their home.nix. Kept small on purpose.
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
options.nomarchy = {
|
||||
# ── Required ───────────────────────────────────────────────────
|
||||
stateFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
example = lib.literalExpression "./theme-state.json";
|
||||
description = ''
|
||||
Path to theme-state.json, the single source of truth for all UI
|
||||
configuration. Must live inside your flake (so evaluation stays
|
||||
pure) and be git-tracked. nomarchy-theme-sync writes to the
|
||||
on-disk copy; rebuilds bake it into the generation.
|
||||
'';
|
||||
};
|
||||
|
||||
# ── Preferences ────────────────────────────────────────────────
|
||||
terminal = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ghostty";
|
||||
description = "Terminal emulator command, used by keybinds and $TERMINAL.";
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.nomarchy-theme-sync;
|
||||
defaultText = lib.literalExpression "pkgs.nomarchy-theme-sync";
|
||||
description = "The nomarchy-theme-sync package (provided by overlays.default).";
|
||||
};
|
||||
|
||||
themesDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = ../../themes;
|
||||
defaultText = lib.literalExpression "\"\${nomarchy}/themes\"";
|
||||
description = ''
|
||||
Theme presets/assets directory probed at eval time for per-theme
|
||||
app overrides (<slug>/waybar.css, <slug>/waybar.jsonc). Point it
|
||||
at your own directory to ship custom layouts downstream.
|
||||
'';
|
||||
};
|
||||
|
||||
# ── Component toggles ──────────────────────────────────────────
|
||||
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
|
||||
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
|
||||
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
|
||||
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
|
||||
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
|
||||
|
||||
# ── Computed (read-only) ───────────────────────────────────────
|
||||
theme = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
readOnly = true;
|
||||
description = "The parsed theme state (stateFile merged over defaults).";
|
||||
};
|
||||
|
||||
lib = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
readOnly = true;
|
||||
description = "Color-format helpers shared by the theme consumers.";
|
||||
};
|
||||
};
|
||||
}
|
||||
79
modules/home/stylix.nix
Normal file
79
modules/home/stylix.nix
Normal file
@@ -0,0 +1,79 @@
|
||||
# Stylix — themes the long tail of applications (GTK, Qt, cursors,
|
||||
# fonts) from the same theme-state.json that drives the live engine.
|
||||
#
|
||||
# Division of labour: the hot-reload trio (Hyprland, Waybar, Ghostty)
|
||||
# is owned by the Nomarchy engine and updates instantly; everything
|
||||
# Stylix touches updates on the next home-manager switch. That is why
|
||||
# autoEnable is off and the trio's Stylix targets stay disabled.
|
||||
#
|
||||
# Note: the stylix home module itself is imported by homeModules.nomarchy
|
||||
# in flake.nix (it needs the stylix flake input).
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy;
|
||||
t = cfg.theme;
|
||||
c = t.colors;
|
||||
hex = lib.removePrefix "#";
|
||||
|
||||
# Map the Nomarchy palette onto base16 roles.
|
||||
base16 = {
|
||||
base00 = hex c.base; # default background
|
||||
base01 = hex c.mantle; # darker background (status bars)
|
||||
base02 = hex c.surface; # selection background
|
||||
base03 = hex c.muted; # comments
|
||||
base04 = hex c.subtext; # dark foreground
|
||||
base05 = hex c.text; # default foreground
|
||||
base06 = hex c.text; # light foreground
|
||||
base07 = hex c.text; # lightest foreground
|
||||
base08 = hex c.bad; # red
|
||||
base09 = hex c.warn; # orange
|
||||
base0A = hex c.warn; # yellow
|
||||
base0B = hex c.good; # green
|
||||
base0C = hex (builtins.elemAt t.ansi 6); # cyan
|
||||
base0D = hex c.accent; # blue
|
||||
base0E = hex c.accentAlt;# magenta
|
||||
base0F = hex c.bad; # brown/deprecated
|
||||
};
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.stylix.enable {
|
||||
stylix = {
|
||||
enable = true;
|
||||
autoEnable = false; # explicit targets only — the engine owns the rest
|
||||
polarity = if t.mode == "light" then "light" else "dark";
|
||||
base16Scheme = base16;
|
||||
|
||||
targets = {
|
||||
gtk.enable = true;
|
||||
qt.enable = true;
|
||||
};
|
||||
|
||||
cursor = {
|
||||
name = lib.mkDefault "Bibata-Modern-Classic";
|
||||
package = lib.mkDefault pkgs.bibata-cursors;
|
||||
size = lib.mkDefault 24;
|
||||
};
|
||||
|
||||
fonts = {
|
||||
monospace = {
|
||||
name = t.fonts.mono;
|
||||
package = lib.mkDefault pkgs.nerd-fonts.jetbrains-mono;
|
||||
};
|
||||
sansSerif = {
|
||||
name = t.fonts.ui;
|
||||
package = lib.mkDefault pkgs.inter;
|
||||
};
|
||||
serif = {
|
||||
name = t.fonts.ui;
|
||||
package = lib.mkDefault pkgs.inter;
|
||||
};
|
||||
emoji = {
|
||||
name = "Noto Color Emoji";
|
||||
package = lib.mkDefault pkgs.noto-fonts-color-emoji;
|
||||
};
|
||||
sizes.terminal = t.fonts.size;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
69
modules/home/theme.nix
Normal file
69
modules/home/theme.nix
Normal file
@@ -0,0 +1,69 @@
|
||||
# Nomarchy theming engine.
|
||||
#
|
||||
# nomarchy.stateFile (theme-state.json, inside the consuming flake) is
|
||||
# ingested at evaluation time — pure, because flake files are store
|
||||
# paths — and exposed to every other module as `config.nomarchy.theme`.
|
||||
#
|
||||
# Theme changes are fully Home Manager managed: `nomarchy-theme-sync
|
||||
# apply <theme>` writes the JSON and runs `home-manager switch`, baking
|
||||
# everything (Hyprland, Waybar, Ghostty, btop, Stylix) into one
|
||||
# read-only generation. No runtime patching, no partial states; theme
|
||||
# history is generation history.
|
||||
#
|
||||
# The one runtime exception is the wallpaper (swww is imperative by
|
||||
# nature): applied at session start, after every switch (hook below),
|
||||
# and cycled instantly with `nomarchy-theme-sync bg next`.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy;
|
||||
|
||||
themeState = builtins.fromJSON (builtins.readFile cfg.stateFile);
|
||||
|
||||
# Defaults guarantee evaluation succeeds on a sparse or older state
|
||||
# file (e.g. one written before a schema field was added). The shipped
|
||||
# Tokyo Night preset provides the color/ansi fallbacks; the path is
|
||||
# relative to this module, so it resolves inside Nomarchy's own flake
|
||||
# source even when consumed downstream.
|
||||
preset = builtins.fromJSON (builtins.readFile ../../themes/tokyo-night.json);
|
||||
|
||||
defaults = preset // {
|
||||
fonts = {
|
||||
mono = "JetBrainsMono Nerd Font";
|
||||
ui = "Inter";
|
||||
size = 11;
|
||||
};
|
||||
ui = {
|
||||
gapsIn = 5;
|
||||
gapsOut = 12;
|
||||
borderSize = 2;
|
||||
rounding = 10;
|
||||
activeOpacity = 1.0;
|
||||
inactiveOpacity = 0.95;
|
||||
terminalOpacity = 0.96;
|
||||
blur = true;
|
||||
shadow = true;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
config = {
|
||||
nomarchy.theme = lib.recursiveUpdate defaults themeState;
|
||||
|
||||
nomarchy.lib = {
|
||||
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
|
||||
rgb = c: "rgb(${lib.removePrefix "#" c})";
|
||||
# "#16161e" -> "rgba(16161eaa)" (Hyprland color + hex alpha)
|
||||
rgba = c: alpha: "rgba(${lib.removePrefix "#" c}${alpha})";
|
||||
};
|
||||
|
||||
home.packages = [ cfg.package ];
|
||||
|
||||
# After a switch the new theme's wallpaper should show without
|
||||
# logging out. Best-effort: outside a graphical session swww isn't
|
||||
# running and this is a no-op.
|
||||
home.activation.nomarchyWallpaper = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
run ${lib.getExe cfg.package} --quiet wallpaper || true
|
||||
'';
|
||||
};
|
||||
}
|
||||
155
modules/home/waybar.nix
Normal file
155
modules/home/waybar.nix
Normal file
@@ -0,0 +1,155 @@
|
||||
# Waybar — two-tier theming:
|
||||
#
|
||||
# 1. Default: structure, fonts, geometry AND palette baked from
|
||||
# theme-state.json (colors as GTK named colors, @define-color).
|
||||
#
|
||||
# 2. Whole-swap: themes with their own visual identity ship
|
||||
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
||||
# replace the generated style/layout entirely. Probed at eval time —
|
||||
# pure, it's flake source.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
|
||||
# Per-theme override probe.
|
||||
assetDir = config.nomarchy.themesDir + "/${t.slug}";
|
||||
styleOverride = assetDir + "/waybar.css";
|
||||
configOverride = assetDir + "/waybar.jsonc";
|
||||
hasStyleOverride = builtins.pathExists styleOverride;
|
||||
hasConfigOverride = builtins.pathExists configOverride;
|
||||
|
||||
# The palette as GTK named colors, straight from the JSON.
|
||||
colorDefs = lib.concatStringsSep "\n"
|
||||
(lib.mapAttrsToList (name: value: "@define-color ${name} ${value};") t.colors);
|
||||
|
||||
generatedSettings = {
|
||||
layer = "top";
|
||||
position = "top";
|
||||
height = 34;
|
||||
margin-top = t.ui.gapsOut;
|
||||
margin-left = t.ui.gapsOut;
|
||||
margin-right = t.ui.gapsOut;
|
||||
spacing = 8;
|
||||
|
||||
# waybar re-reads style.css when it changes on disk, so a
|
||||
# home-manager switch restyles the running bar without a restart.
|
||||
reload_style_on_change = true;
|
||||
|
||||
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "battery" ];
|
||||
|
||||
"hyprland/workspaces" = {
|
||||
format = "{icon}";
|
||||
on-click = "activate";
|
||||
};
|
||||
|
||||
"hyprland/window" = {
|
||||
max-length = 48;
|
||||
separate-outputs = true;
|
||||
};
|
||||
|
||||
clock = {
|
||||
format = "{:%H:%M}";
|
||||
format-alt = "{:%A %d %B %Y}";
|
||||
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
||||
};
|
||||
|
||||
pulseaudio = {
|
||||
format = "{icon} {volume}%";
|
||||
format-muted = "";
|
||||
format-icons.default = [ "" "" "" ];
|
||||
on-click = "pamixer -t";
|
||||
};
|
||||
|
||||
network = {
|
||||
format-wifi = " {essid}";
|
||||
format-ethernet = "";
|
||||
format-disconnected = "";
|
||||
tooltip-format = "{ipaddr} via {gwaddr}";
|
||||
};
|
||||
|
||||
cpu.format = " {usage}%";
|
||||
memory.format = " {percentage}%";
|
||||
|
||||
battery = {
|
||||
states = { warning = 25; critical = 10; };
|
||||
format = "{icon} {capacity}%";
|
||||
format-charging = " {capacity}%";
|
||||
format-icons = [ "" "" "" "" "" ];
|
||||
};
|
||||
|
||||
tray.spacing = 8;
|
||||
};
|
||||
|
||||
generatedStyle = ''
|
||||
/* Palette baked from theme-state.json */
|
||||
${colorDefs}
|
||||
|
||||
* {
|
||||
font-family: "${t.fonts.ui}", "${t.fonts.mono}";
|
||||
font-size: ${toString t.fonts.size}pt;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
window#waybar {
|
||||
background: alpha(@base, 0.85);
|
||||
color: @text;
|
||||
border: ${toString t.ui.borderSize}px solid alpha(@accent, 0.4);
|
||||
border-radius: ${toString t.ui.rounding}px;
|
||||
}
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 8px;
|
||||
color: @muted;
|
||||
border-radius: ${toString t.ui.rounding}px;
|
||||
}
|
||||
|
||||
#workspaces button.active {
|
||||
color: @base;
|
||||
background: @accent;
|
||||
}
|
||||
|
||||
#workspaces button.urgent {
|
||||
color: @base;
|
||||
background: @bad;
|
||||
}
|
||||
|
||||
#window {
|
||||
color: @subtext;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#clock {
|
||||
color: @text;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tray, #pulseaudio, #network, #cpu, #memory, #battery {
|
||||
color: @subtext;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#pulseaudio.muted { color: @muted; }
|
||||
#battery.warning { color: @warn; }
|
||||
#battery.critical { color: @bad; }
|
||||
#battery.charging { color: @good; }
|
||||
'';
|
||||
in
|
||||
{
|
||||
programs.waybar = lib.mkIf config.nomarchy.waybar.enable {
|
||||
enable = true;
|
||||
systemd.enable = true; # started/stopped with graphical-session.target
|
||||
|
||||
settings.mainBar =
|
||||
if hasConfigOverride
|
||||
then builtins.fromJSON (builtins.readFile configOverride)
|
||||
else generatedSettings;
|
||||
|
||||
style =
|
||||
if hasStyleOverride
|
||||
then builtins.readFile styleOverride
|
||||
else generatedStyle;
|
||||
};
|
||||
}
|
||||
100
modules/nixos/default.nix
Normal file
100
modules/nixos/default.nix
Normal file
@@ -0,0 +1,100 @@
|
||||
# Nomarchy — reusable system layer (NixOS 26.05).
|
||||
#
|
||||
# This module is the distro: import it from any host (see
|
||||
# nixosModules.nomarchy in flake.nix) and layer your machine specifics
|
||||
# (bootloader, hostname, users, hardware) on top. Host concerns are
|
||||
# deliberately NOT set here. Everything user-facing (Hyprland config,
|
||||
# Waybar, Ghostty, theming) lives in modules/home.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy.system;
|
||||
in
|
||||
{
|
||||
imports = [ ./options.nix ];
|
||||
|
||||
config = {
|
||||
# ── Wayland session: Hyprland ────────────────────────────────────
|
||||
# Installs the binary, registers the session, wires up
|
||||
# xdg-desktop-portal-hyprland. Configuration is Home Manager's job.
|
||||
programs.hyprland.enable = lib.mkDefault true;
|
||||
|
||||
xdg.portal = {
|
||||
enable = lib.mkDefault true;
|
||||
extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers, etc.
|
||||
};
|
||||
|
||||
services.greetd = lib.mkIf cfg.greeter.enable {
|
||||
enable = lib.mkDefault true;
|
||||
settings.default_session = {
|
||||
command = lib.mkDefault "${pkgs.greetd.tuigreet}/bin/tuigreet --time --remember --cmd Hyprland";
|
||||
user = "greeter";
|
||||
};
|
||||
};
|
||||
|
||||
# ── Audio: Pipewire ──────────────────────────────────────────────
|
||||
security.rtkit.enable = lib.mkDefault cfg.audio.enable;
|
||||
services.pulseaudio.enable = lib.mkDefault false;
|
||||
services.pipewire = lib.mkIf cfg.audio.enable {
|
||||
enable = lib.mkDefault true;
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = true;
|
||||
pulse.enable = true;
|
||||
wireplumber.enable = true;
|
||||
};
|
||||
|
||||
# ── Desktop services ─────────────────────────────────────────────
|
||||
security.polkit.enable = lib.mkDefault true;
|
||||
services.gnome.gnome-keyring.enable = lib.mkDefault true;
|
||||
services.dbus.enable = lib.mkDefault true;
|
||||
services.upower.enable = lib.mkDefault true;
|
||||
networking.networkmanager.enable = lib.mkDefault true;
|
||||
|
||||
hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable;
|
||||
services.blueman.enable = lib.mkDefault cfg.bluetooth.enable;
|
||||
|
||||
# ── Fonts ────────────────────────────────────────────────────────
|
||||
fonts = {
|
||||
packages = with pkgs; [
|
||||
nerd-fonts.jetbrains-mono
|
||||
inter
|
||||
noto-fonts
|
||||
noto-fonts-color-emoji
|
||||
];
|
||||
fontconfig.defaultFonts = {
|
||||
monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ];
|
||||
sansSerif = lib.mkDefault [ "Inter" ];
|
||||
emoji = lib.mkDefault [ "Noto Color Emoji" ];
|
||||
};
|
||||
};
|
||||
|
||||
# ── Essential packages ───────────────────────────────────────────
|
||||
environment.systemPackages = with pkgs; [
|
||||
nomarchy-theme-sync # provided by overlays.default
|
||||
git
|
||||
vim
|
||||
wget
|
||||
curl
|
||||
jq
|
||||
brightnessctl
|
||||
playerctl
|
||||
pamixer
|
||||
wl-clipboard
|
||||
grim
|
||||
slurp
|
||||
];
|
||||
|
||||
# ── Nix itself ───────────────────────────────────────────────────
|
||||
nix = {
|
||||
settings = {
|
||||
experimental-features = [ "nix-command" "flakes" ];
|
||||
auto-optimise-store = lib.mkDefault true;
|
||||
};
|
||||
gc = {
|
||||
automatic = lib.mkDefault true;
|
||||
dates = lib.mkDefault "weekly";
|
||||
options = lib.mkDefault "--delete-older-than 14d";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
14
modules/nixos/options.nix
Normal file
14
modules/nixos/options.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
# System-level `nomarchy.system.*` options.
|
||||
#
|
||||
# Deliberately small: only things a downstream user plausibly disagrees
|
||||
# with get a toggle. Everything else in the system module is set with
|
||||
# lib.mkDefault, so plain NixOS options override it natively.
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
options.nomarchy.system = {
|
||||
greeter.enable = lib.mkEnableOption "the greetd/tuigreet login screen" // { default = true; };
|
||||
audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; };
|
||||
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user