Files

160 lines
6.3 KiB
Nix

{self, ...}: {
# Hyprland desktop. The compositor config itself (hyprland.lua + ahmza/*.lua,
# hyprlock, hypridle, hyprsunset) is managed via stow (dotfiles: hypr package).
# Here we enable Hyprland and install the runtime tools that config + the
# helper scripts in the `bin` package expect on PATH.
flake.nixosModules.hyprland = {pkgs, ...}: let
modules = with self.nixosModules; [
audio
systemTheme
fuzzel
ghostty
waybar
quickshell
screenshot
];
in {
imports = modules;
programs.hyprland = {
enable = true;
withUWSM = true;
};
# hypr = compositor config, dunst = notifications, wal = pywal templates,
# bin = helper scripts (swp/launch-waybar/passmenu) referenced by keybinds.
ahmza.stow.packages = ["hypr" "dunst" "wal" "bin"];
services.displayManager.defaultSession = "hyprland-uwsm";
services.xserver.enable = true;
security.polkit.enable = true;
# Removable-media automounting.
#
# udiskie is only a tray frontend to the udisks2 D-Bus daemon, so the daemon
# has to be enabled or udiskie runs but can never mount anything. Note this
# covers removable media *only*: udisks2 reports the internal SATA disks as
# HintSystem=true, so udiskie leaves them alone. Permanently attached drives
# are declared in the host's disks.nix instead.
services.udisks2.enable = true;
# Session daemons run as systemd user units bound to graphical-session.target
# (which UWSM manages properly) rather than from the hypr autostart, so they
# start and stop with the session instead of leaking across logins.
#
# hypridle and hyprsunset both ship their own unit, but in
# $out/share/systemd/user — NOT $out/lib/systemd/user. That distinction is
# why they need redeclaring rather than just enabling:
#
# * `systemd.packages = [pkgs.hypridle]` would collect nothing. nixpkgs'
# generateUnits globs only $pkg/etc/systemd/user and $pkg/lib/systemd/user.
# * The units are nonetheless *visible* to the user manager, via
# environment.pathsToLink -> /run/current-system/sw/share/systemd/user,
# which is on the search path because XDG_DATA_DIRS ends with
# /run/current-system/sw/share. But visible is not enabled: nothing
# creates the WantedBy symlink, so they sit unstarted.
# * `systemd.user.services.<n>.wantedBy` alone would not help either — with
# no other keys NixOS synthesises a *replacement* unit with an empty
# [Service] section, shadowing upstream's via /etc/systemd/user (which
# takes precedence over $XDG_DATA_DIRS).
#
# So the full definition below is deliberate. It is close to upstream's but
# not identical: hyprsunset's `Requires=graphical-session.target` is dropped
# on purpose (PartOf already propagates the stop, while Requires would also
# make starting hyprsunset by hand pull up the whole session target), and
# hypridle gains an explicit `path` because its config shells out.
systemd.user.services = {
udiskie = {
description = "udiskie removable media automounter";
partOf = ["graphical-session.target"];
after = ["graphical-session.target"];
wantedBy = ["graphical-session.target"];
serviceConfig = {
Type = "simple";
# -s = smart tray: the icon appears only while removable media is present.
ExecStart = "${pkgs.udiskie}/bin/udiskie -s";
Restart = "on-failure";
};
};
hypridle = {
description = "Hyprland idle daemon";
documentation = ["https://wiki.hyprland.org/Hypr-Ecosystem/hypridle"];
partOf = ["graphical-session.target"];
after = ["graphical-session.target"];
wantedBy = ["graphical-session.target"];
# ~/.config/hypr/hypridle.conf calls out to these: hyprlock (lock_cmd),
# pidof (the `pidof hyprlock ||` guard), brightnessctl (backlight
# listener), hyprctl (dpms), loginctl + systemctl (sleep/lock),
# notify-send (unlock_cmd).
#
# MAINTENANCE HAZARD: that config lives in a *different* repo (the stow'd
# `hypr` package in the dotfiles repo), so this list can silently drift
# out of sync with it. A binary used there but missing here fails as a
# bare "command not found" inside hypridle, and Restart=on-failure will
# NOT catch it because hypridle itself stays up — the idle action just
# quietly stops working. Add any new binary to both places.
path = with pkgs; [
hyprlock
hyprland
brightnessctl
libnotify
procps
systemd
];
unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY";
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.hypridle}/bin/hypridle";
Restart = "on-failure";
};
};
hyprsunset = {
description = "Hyprland blue-light filter";
documentation = ["https://wiki.hyprland.org/Hypr-Ecosystem/hyprsunset"];
partOf = ["graphical-session.target"];
after = ["graphical-session.target"];
wantedBy = ["graphical-session.target"];
unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY";
serviceConfig = {
Type = "simple";
ExecStart = "${pkgs.hyprsunset}/bin/hyprsunset";
Restart = "on-failure";
# Intentionally only on hyprsunset, not hypridle/udiskie: upstream's
# hyprsunset.service sets Slice=session.slice and upstream's
# hypridle.service does not. Not an oversight — don't "fix" it.
Slice = "session.slice";
};
};
};
environment.systemPackages = with pkgs; [
# hypr* ecosystem (keybinds / hypridle / autostart)
hyprlock
hypridle
hyprsunset
hyprpicker
# launcher / file manager / browser (vars.lua: Picker/FileManager/Browser)
kdePackages.dolphin
firefox
# wallpaper daemon used by the `swp` script + autostart (awww-daemon)
awww
# scripting deps for bin/ helpers (swp uses jq + hyprctl)
jq
# display / applets referenced by autostart
xrandr
networkmanagerapplet
blueman
udiskie
brightnessctl
# clipboard
cliphist
# theming
gsettings-desktop-schemas
dunst
pywal16
];
};
}