feat(nightlight): geo mode — lat/long-computed sunset via wlsunset
All checks were successful
Check / eval (push) Successful in 4m20s
All checks were successful
Check / eval (push) Successful in 4m20s
Set BOTH nomarchy.nightlight.latitude/.longitude and the night light switches backing unit: wlsunset computes sunrise/sunset from the location daily (the fixed .sunrise/.sunset are ignored; .temperature feeds the night temp). Everything user-facing is unchanged — same nomarchy-nightlight toggle script, Waybar moon, and live-state ExecCondition gate; the unit name is a single let-binding so all three follow together. Declarative-only on purpose: coordinates are machine config like the keyboard layout, so home.nix (commented template example), not a menu writer. checks.nightlight-geo guards the wiring at pure eval: rendered wlsunset ExecStart carries the coords + night temperature, the ExecCondition gate landed on the swapped unit, hyprsunset stays off in geo mode. Verification: V1 — nix flake check --no-build exit 0 (evals the new asserts + option-docs sync); built downstream-template-home (default hyprsunset path unregressed). V3 pending: visible warm shift at the location's night + instant toggle/off-persistence on the swapped unit (HARDWARE-QUEUE § Any machine). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -233,7 +233,7 @@ two tables below are split along exactly that line.
|
||||
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
||||
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
|
||||
| `nomarchy.keyboard.layouts` | `[]` | Extra candidate layouts; when set, a watcher prompts (rofi) for a layout on a newly-connected keyboard and remembers it per-device |
|
||||
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; 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.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; set BOTH `.latitude`/`.longitude` for **geo mode** (wlsunset computes sunrise/sunset from your location; the fixed times are ignored); 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.waybar.enable` | `true` | Nomarchy's Waybar |
|
||||
|
||||
@@ -64,7 +64,6 @@ HARDWARE-QUEUE. Close #76 on PASS.
|
||||
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
|
||||
impermanence, BIOS/legacy boot.
|
||||
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
|
||||
- **Night-light geo mode**: lat/long auto sunset/sunrise (means wlsunset).
|
||||
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
|
||||
- **VPN exit-node richer display** (country/city) (optional).
|
||||
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
|
||||
|
||||
@@ -224,6 +224,16 @@ the **T14s** (webcam case).
|
||||
hibernate — likely no swap is configured. See docs/MIGRATION.md →
|
||||
Enabling hibernation."), **not** a silent no-op. (`swapon` after, if you
|
||||
swapoff'd for the test.)
|
||||
- [ ] **Night-light geo mode (2026-07-11)** — in `home.nix` set
|
||||
`nomarchy.nightlight = { enable = true; latitude = "<lat>";
|
||||
longitude = "<long>"; }` (your real coordinates), `nomarchy-home`,
|
||||
relogin. Then: `systemctl --user status wlsunset` is running (and
|
||||
`hyprsunset` is NOT); after local sunset the screen visibly warms
|
||||
(or test by setting coordinates where it's currently night); the
|
||||
Waybar moon + menu toggle still flip it instantly and an *off*
|
||||
survives relogin (the ExecCondition gate on the swapped unit —
|
||||
the eval check proves the wiring, not the runtime gate). **Pass** =
|
||||
warm shift at the location's night + toggle/persistence intact.
|
||||
|
||||
## AMD dev box only
|
||||
- [ ] **AMD runtime bits** — VA-API (`vainfo` → radeonsi), amd-pstate EPP
|
||||
|
||||
@@ -19,6 +19,24 @@ Template:
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-11 — Night-light geo mode (lat/long → wlsunset)
|
||||
- **Task:** BACKLOG LATER "Night-light geo mode" (human-picked).
|
||||
- **Did:** `nomarchy.nightlight.latitude`/`.longitude` (both set = geo mode):
|
||||
`services.wlsunset` (location-computed sunrise/sunset, `.temperature` →
|
||||
night temp) replaces hyprsunset; the toggle script's `unit=` and the
|
||||
live-state ExecCondition follow via one `unit` let-binding. Template
|
||||
example lines, README row, ROADMAP ✓. New `checks.nightlight-geo`:
|
||||
pure-eval asserts on the rendered units (coords + temp in ExecStart,
|
||||
ExecCondition present, hyprsunset off). Declarative-only (no menu writer —
|
||||
coords are machine config like keyboard layout).
|
||||
- **Verified:** V1 — `nix flake check --no-build` exit 0 (evals the new
|
||||
asserts + option-docs); built `downstream-template-home` (default
|
||||
hyprsunset path unregressed) + the check drv. Caught en route: HM renders
|
||||
`ExecStart` as a list (`toString` needed in eval asserts).
|
||||
- **Pending:** V3 queued (HARDWARE-QUEUE § Any machine): visible warm shift
|
||||
at location night + toggle/persistence on the swapped unit.
|
||||
- **Next suggestion:** wallpapers artifact split (LATER, decided).
|
||||
|
||||
## 2026-07-11 — Sync-sweep rule + grooming + zram user docs (human-requested)
|
||||
- **Task:** Bernardo: groom stale items and codify the check so they don't
|
||||
recur; close the zram user-doc gap.
|
||||
|
||||
@@ -721,10 +721,16 @@ Design/decision records and a running log of shipped work (items marked
|
||||
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).
|
||||
files only). The on-machine check PASSed 2026-07-04 (enable/disable on
|
||||
hardware). **Geo mode shipped 2026-07-11:** set BOTH
|
||||
`nomarchy.nightlight.latitude`/`.longitude` and wlsunset replaces
|
||||
hyprsunset — sunrise/sunset computed from the location daily, the fixed
|
||||
times ignored; same toggle script, moon indicator, and live-state
|
||||
`ExecCondition` gate (the backing unit name is a single let-binding).
|
||||
Declarative-only on purpose (coords are hardware-independent machine
|
||||
config, like the keyboard layout — no menu writer). Guarded by
|
||||
`checks.nightlight-geo` (pure eval over the rendered units); the visible
|
||||
warm shift is hardware-tier, queued.
|
||||
- ✓ **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
|
||||
|
||||
42
flake.nix
42
flake.nix
@@ -550,6 +550,48 @@
|
||||
in
|
||||
assert ok; pkgs.runCommand "nomarchy-display-profiles" { } "touch $out";
|
||||
|
||||
# Night-light geo mode: with BOTH lat/long set the backing unit
|
||||
# flips hyprsunset → wlsunset (location-computed sunrise/sunset)
|
||||
# and the live-state ExecCondition gate survives the swap (the
|
||||
# toggle script's unit= comes from the same let-binding, so the
|
||||
# gate landing on wlsunset covers the selection logic). Pure
|
||||
# eval over the rendered HM units — nothing is built. The
|
||||
# visible warm shift itself stays hardware-tier (HARDWARE-QUEUE),
|
||||
# exactly like the original night-light full cycle.
|
||||
nightlight-geo =
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
hm = home-manager.lib.homeManagerConfiguration {
|
||||
inherit pkgs;
|
||||
modules = [
|
||||
self.homeModules.nomarchy
|
||||
{
|
||||
nomarchy.stateFile = ./templates/downstream/theme-state.json;
|
||||
nomarchy.nightlight = {
|
||||
enable = true;
|
||||
latitude = "52.52";
|
||||
longitude = "13.40";
|
||||
};
|
||||
home = { username = "geo"; homeDirectory = "/home/geo"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
units = hm.config.systemd.user.services;
|
||||
exec = toString units.wlsunset.Service.ExecStart;
|
||||
cond = toString units.wlsunset.Service.ExecCondition;
|
||||
expect = c: msg: lib.assertMsg c "nightlight-geo: ${msg}";
|
||||
ok =
|
||||
expect (lib.hasInfix "52.52" exec && lib.hasInfix "13.40" exec)
|
||||
"wlsunset ExecStart lacks the configured coordinates: ${exec}"
|
||||
&& expect (lib.hasInfix "4000" exec)
|
||||
"wlsunset ExecStart lacks the night temperature: ${exec}"
|
||||
&& expect (lib.hasInfix "should-start" cond)
|
||||
"wlsunset lost the live-state ExecCondition gate"
|
||||
&& expect (!(hm.config.services.hyprsunset.enable or false))
|
||||
"geo mode must not also run hyprsunset";
|
||||
in
|
||||
assert ok; pkgs.runCommand "nomarchy-nightlight-geo" { } "touch $out";
|
||||
|
||||
# The low-battery watcher's crossing logic: one toast per downward
|
||||
# crossing (normal at 25%, critical at 10%), re-armed by charging.
|
||||
# test_power fakes a battery whose capacity/status we script;
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
# schedule (temperature/sunrise/sunset) is tuned via nomarchy.nightlight.* in
|
||||
# home.nix and baked into the unit's time-based `profile`.
|
||||
#
|
||||
# Geo mode: when BOTH nomarchy.nightlight.latitude and .longitude are set,
|
||||
# wlsunset replaces hyprsunset — it computes sunrise/sunset from the
|
||||
# location itself (the fixed .sunrise/.sunset times are ignored). Same
|
||||
# toggle script, same Waybar moon, same live-state ExecCondition gate;
|
||||
# only the unit underneath changes.
|
||||
#
|
||||
# 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
|
||||
@@ -18,12 +24,15 @@ let
|
||||
cfg = config.nomarchy.nightlight;
|
||||
s = config.nomarchy.settings.nightlight;
|
||||
sync = lib.getExe config.nomarchy.package;
|
||||
# Geo mode flips the backing unit; everything user-facing stays the same.
|
||||
geo = cfg.latitude != null && cfg.longitude != null;
|
||||
unit = if geo then "wlsunset.service" else "hyprsunset.service";
|
||||
# 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
|
||||
unit=${unit}
|
||||
# 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
|
||||
@@ -69,7 +78,7 @@ in
|
||||
# home.nix also works as a declarative opt-in.
|
||||
nomarchy.nightlight.enable = lib.mkDefault s.installed;
|
||||
|
||||
services.hyprsunset = lib.mkIf cfg.enable {
|
||||
services.hyprsunset = lib.mkIf (cfg.enable && !geo) {
|
||||
enable = true;
|
||||
settings.profile = [
|
||||
# Daytime: identity = no colour change.
|
||||
@@ -79,10 +88,19 @@ in
|
||||
];
|
||||
};
|
||||
|
||||
# Geo mode: wlsunset owns the schedule — it recomputes sunrise/sunset
|
||||
# from the coordinates daily (no fixed profile to bake).
|
||||
services.wlsunset = lib.mkIf (cfg.enable && geo) {
|
||||
enable = true;
|
||||
latitude = cfg.latitude;
|
||||
longitude = cfg.longitude;
|
||||
temperature.night = 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 =
|
||||
systemd.user.services.${lib.removeSuffix ".service" unit}.Service.ExecCondition =
|
||||
lib.mkIf cfg.enable "${nomarchy-nightlight}/bin/nomarchy-nightlight should-start";
|
||||
|
||||
home.packages = [ nomarchy-nightlight ];
|
||||
|
||||
@@ -192,8 +192,9 @@ in
|
||||
|
||||
nightlight = {
|
||||
enable = lib.mkEnableOption ''
|
||||
a scheduled blue-light filter (hyprsunset): warm at night, no shift
|
||||
by day. Opt-in; tune the temperature + sunrise/sunset below'';
|
||||
a scheduled blue-light filter (hyprsunset; wlsunset in geo mode):
|
||||
warm at night, no shift by day. Opt-in; tune the temperature +
|
||||
sunrise/sunset (or latitude/longitude) below'';
|
||||
|
||||
temperature = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
@@ -206,14 +207,31 @@ in
|
||||
type = lib.types.str;
|
||||
default = "07:00";
|
||||
example = "06:30";
|
||||
description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift.";
|
||||
description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift. Ignored in geo mode.";
|
||||
};
|
||||
|
||||
sunset = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "20:00";
|
||||
example = "21:00";
|
||||
description = "Time (HH:MM) the filter turns ON — warm.";
|
||||
description = "Time (HH:MM) the filter turns ON — warm. Ignored in geo mode.";
|
||||
};
|
||||
|
||||
latitude = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "52.52";
|
||||
description = ''
|
||||
Geo mode: set BOTH latitude and longitude and sunrise/sunset are
|
||||
computed from your location every day (wlsunset replaces
|
||||
hyprsunset; the fixed .sunrise/.sunset times are ignored).'';
|
||||
};
|
||||
|
||||
longitude = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "13.40";
|
||||
description = "Geo mode longitude — set together with latitude.";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
# temperature = 4000; # night warmth in K — lower is warmer
|
||||
# sunset = "20:00";
|
||||
# sunrise = "07:00";
|
||||
# latitude = "52.52"; # geo mode: set BOTH lat/long and sunrise/
|
||||
# longitude = "13.40"; # sunset follow your location (wlsunset);
|
||||
# # the fixed times above are then ignored
|
||||
# };
|
||||
#
|
||||
# nomarchy.updates = { # passive update-awareness indicator + notification
|
||||
|
||||
Reference in New Issue
Block a user