Night light is off by default and configured entirely through the menu,
writing into the downstream flake instead of ~/.local/state — so the
choice is git-tracked and reproducible across a clone+rebuild.
Two flags under a new settings.* section of the state file (exposed as
the nomarchy.settings option):
- settings.nightlight.installed — sticky, gates the hyprsunset unit;
nomarchy.nightlight.enable mkDefault-reads it. First enable from the
menu rebuilds to create the unit (the one accepted rebuild).
- settings.nightlight.on — runtime on/off, toggled instantly
(theme-sync set --no-switch + systemctl), no rebuild. An ExecCondition
reads the live flag at session start so an off survives reboot;
splitting it from the sticky flag means a later unrelated rebuild
never undoes an instant-off (no decay).
Drops the ~/.local/state/nomarchy/nightlight-off marker and the
ConditionPathExists hack. theme-sync set now skips the wallpaper
re-apply for non-wallpaper keys, and its rebuild notifications are
generic ("changes" not "theme").
First cut of the broader principle — any user-settable config gets a
menu writer that lands it in the flake; no state outside the checkout.
Docs (README §4, ROADMAP, template) updated to reflect it.
Eval- and round-trip-verified; on-hardware check pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
4.4 KiB
Nix
91 lines
4.4 KiB
Nix
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
|
|
# gamma/temperature tool). Warm at night, identity (no shift) by day; the
|
|
# schedule (temperature/sunrise/sunset) is tuned via nomarchy.nightlight.* in
|
|
# home.nix and baked into the unit's time-based `profile`.
|
|
#
|
|
# Off by default and opt-in. Two git-tracked flags in the state file, both
|
|
# menu-written (no ~/.local/state):
|
|
# 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
|
|
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
|
|
{
|
|
config = {
|
|
# 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;
|
|
settings.profile = [
|
|
# Daytime: identity = no colour change.
|
|
{ time = cfg.sunrise; identity = true; }
|
|
# Night: shift to the warm 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 ];
|
|
};
|
|
}
|