Add omarchy theme schedule for day/night switching#5779
Open
scottjones wants to merge 10 commits into
Open
Conversation
Swaps themes at sunrise and sunset using sun times computed from the system timezone. Slots are learned from manual theme changes via the theme-set hook, so there's no separate config UI — the existing theme menu drives everything. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caused a Hyprland session crash on resume from suspend: the timer fired its missed-event catchup within 1 second of resume, ran omarchy-theme-set, and the cascade of component restarts during session stabilization brought down UWSM. Hourly fallback OnCalendar still corrects within an hour, which is good enough for theme drift recovery. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Listens to logind's PrepareForSleep D-Bus signal via gdbus monitor. On resume, polls hyprctl until responsive (typically <3s, capped at 10s) then runs apply — so omarchy-theme-set's component restarts don't race with session stabilization. Hourly OnCalendar fallback removed since the schedule is now fully event-driven (sun events, resume, tz change, manual theme change). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After a shutdown longer than the drop-in's 48-hour window, all its OnCalendar entries are in the past and the timer parks in 'elapsed' state with no future fires queued. Two changes fix that: - sleepwatch now runs apply once at its own startup (with the same Hyprland-readiness wait it uses on resume), so the drop-in is regenerated against current dates whenever the session starts. - apply now try-restarts the timer after rewriting the drop-in, to pull it out of 'elapsed' state when daemon-reload alone wouldn't recompute next-fire. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the script exited silently when stdin wasn't a tty (set -euo pipefail + read on EOF). Now it explains how to invoke with an argument instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DST: previously the 'tomorrow' offset was epoch + 86400, which skips a calendar day across a spring-forward boundary (Sat 23:30 EST + 24 h of seconds = Mon 00:30 EDT). Now uses calendar-day arithmetic on the date string. Uninstalled theme: when a configured slot referenced a theme that had been removed, omarchy-theme-set failed and set -e killed apply before the drop-in regeneration ran — schedule silently died and the same broken theme would be retried on every subsequent firing. Now the failure is caught; the schedule keeps running with stale slot, the user gets a stderr message and a notify-send. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Moves record-schedule-slot from default/omarchy/hooks/ (which was a new tree we'd created and nothing reads from) to config/omarchy/hooks/ where existing sample hooks live. Instead of bundling the file into every user's hook dir on upgrade, enable now copies it on opt-in and disable removes it — so users who never enable scheduling never see the hook. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously every apply firing during a broken-theme state sent a fresh -u critical notification — they stack in mako since critical notifications don't auto-dismiss. Now gated by a flag in $XDG_RUNTIME_DIR: at most one notification per broken state. The flag is cleared on the next successful apply so a recurrence after fix re-notifies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
set -euo pipefail + an empty array from mapfile = unbound-variable
crash when ${suntimes[0]} is accessed — which surfaces in the
caller as a generic "Hook failed: …record-schedule-slot" from
omarchy-hook on every theme change. Two fixes:
- record-slot now runs without -e and exits 0 with a stderr
breadcrumb when location or suncalc isn't reachable / returns
nothing. Slot learning is best-effort; don't take down the hook
chain over a transient compute failure.
- apply explicitly checks suncalc output length before indexing
the array, both at the top and inside the drop-in regen loop.
apply still exits 1 on top-level failure (it can't do its job
without sun times), but no longer with a cryptic "unbound
variable" message.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The base timer unit ships with no OnCalendar — entries live in a drop-in written by apply. On a fresh install the drop-in doesn't exist yet, so 'systemctl --user enable --now omarchy-theme-schedule.timer' fails with 'bad unit file setting' and set -euo pipefail then exits the enable script before tzwatch.path, sleepwatch.service, and apply run. User ends up with hook + slots written but services half-installed and has to re-run enable. Reorder: run apply first (writes drop-in, applies theme if needed), then enable+start all three units. apply's own try-restart of the timer no-ops while the timer is still inactive, so the reorder is safe. Caught by v3.8 / non-Mac test that didn't have a leftover drop-in to mask the bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an opt-in, event-driven day/night theme scheduler to Omarchy (omarchy theme schedule {enable,disable,status}), backed by systemd user units and small helper scripts (timezone-derived location + NOAA sunrise/sunset math) and a theme-set hook to “learn” day/night slots from manual theme changes.
Changes:
- Introduces new
omarchy-theme-schedule-*commands (enable/disable/status + apply/location/suncalc/record-slot/sleepwatch helpers). - Adds systemd user units to trigger applies at sunrise/sunset, on resume, and on timezone changes.
- Adds a theme-set hook to persist the current theme into
~/.config/omarchy/current/theme.day/theme.night.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 6 out of 14 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
bin/omarchy-theme-schedule-enable |
Interactive/CLI flow to set day/night slots and enable units. |
bin/omarchy-theme-schedule-disable |
Disables units and removes the learning hook while preserving slots. |
bin/omarchy-theme-schedule-status |
Reports scheduler status, slots, computed phase, and next fire time. |
bin/omarchy-theme-schedule-apply |
Idempotently applies the correct phase theme and regenerates timer OnCalendar drop-in. |
bin/omarchy-theme-schedule-record-slot |
Learns the active phase slot from manual theme changes. |
bin/omarchy-theme-schedule-sleepwatch |
Watches logind resume events and runs apply after compositor readiness. |
bin/omarchy-theme-schedule-location |
Resolves lat/long from timezone (or user override). |
bin/omarchy-theme-schedule-suncalc |
Stdlib-only Python NOAA sunrise/sunset calculator. |
config/systemd/user/omarchy-theme-schedule.service |
Oneshot unit that runs apply. |
config/systemd/user/omarchy-theme-schedule.timer |
Timer unit whose OnCalendar is provided via a generated drop-in. |
config/systemd/user/omarchy-theme-schedule-tzwatch.path |
Path unit to re-run apply when /etc/localtime changes. |
config/systemd/user/omarchy-theme-schedule-sleepwatch.service |
Long-running resume watcher service. |
config/omarchy/hooks/theme-set.d/record-schedule-slot |
Hook invoked by omarchy-theme-set to trigger slot learning. |
test/install-theme-schedule.sh |
Local development installer/uninstaller for testing against an existing install. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
+1 :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automatic day/night theme switching
Adds
omarchy theme schedule {enable,disable,status}for automatic theme swaps at sunrise and sunset. Opt-in; off by default.Up-front honesty: this started as exploration after someone suggested it on X — I didn't have a personal need for it myself, and the original suggester turned out to not be an omarchy user. I built it because the design problem was interesting (event-driven, offline location, "learning" model that needs no settings UI), and validated it against my own day for ~24 hours. Putting it forward in case automatic day/night switching is a common-enough request that you'd consider merging. Equally happy to close it out if it's not a direction you want.
Design
Two slot files at
~/.config/omarchy/current/:The timer being
enabledis the source of truth for "scheduling is on" — no separate flag file (matchesomarchy-battery-monitor's pattern).Sunrise/sunset is computed from the system timezone — no network call, no IP geolocation.
omarchy-theme-schedule-locationreads/usr/share/zoneinfo/zone1970.taband parses the ISO 6709 coordinate for$(readlink /etc/localtime). Users with strong opinions can override with~/.config/omarchy/theme-schedule.locationcontaininglat,long.A small Python NOAA solar-position helper (
omarchy-theme-schedule-suncalc, stdlib only) does the math.No settings UI — the existing theme menu drives everything. When the schedule is enabled, the theme-set hook records every manual change into the active phase's slot. So users keep using
omarchy theme set …as they always have; whatever they wear during the day becomes the day theme, same for night. Even at enable time, the system uses your current theme as one half of the pair and only asks about the other.Four triggers, all event-driven, all idempotent:
omarchy-theme-schedule.timerOnCalendar drop-inomarchy-theme-schedule-sleepwatch.service(gdbus on logind PrepareForSleep)omarchy-theme-schedule-tzwatch.path/etc/localtimechangesomarchy-hook theme-set(config/omarchy/hooks/theme-set.d/record-schedule-slot)omarchy theme setSleepwatch also fires its apply at service startup, which handles the multi-day-shutdown case where all OnCalendar entries in the drop-in are in the past.
applyis fully idempotent: compute phase, read slot, swap only if current ≠ target. Safe to invoke from any trigger.Non-obvious design choices flagged for review
No
Persistent=trueon the timer. First implementation crashed Hyprland on resume — Persistent=true caught up the missed sunrise event within 1 s of resume, ranomarchy-theme-set(which restarts waybar/swaybg/swayosd/terminal/btop/etc.), and the cascade during session stabilization tore down UWSM. Replaced with the sleepwatch service which waits forhyprctl versionto respond before calling apply.No hourly polling. Earlier draft kept
OnCalendar=hourlyas a fallback. Removed in favor of fully event-driven design (sun events + sleepwatch + tzwatch + hook). Trade-off: a multi-day shutdown could leave the drop-in fully stale, but sleepwatch's startup-apply rebuilds it before any timer fires.omarchy-theme-setis called withOMARCHY_THEME_SCHEDULE_TICK=1when invoked by apply, so the hook (which would otherwise record the new theme into the active slot) knows to skip. Same env var is honored by the hook script.No GUI / no settings file for the schedule itself. The flow is
enable→ optionaldisable→ status. Settings are inferred from current theme + slots.Open questions worth surfacing
Files
Auto-routed by the existing
omarchydispatcher — appears asomarchy theme schedule {enable,disable,status}under the existingthemegroup. Internal helpers are markedomarchy:hidden=trueso they don't pollute help output.Validation
Real-world tested across:
omarchy theme schedule …)systemd-analyze verifycleanAlso cross-machine validated on a separate v3.8 / non-Mac install:
omarchy-hookshim needed (v3.8's hook already supports.d/)OnCalendarlives in a drop-in written byapply, soenablehad to be reordered to run apply beforesystemctl --user enable --nowon the timer)omarchy-hookomarchy-theme-schedule-suncalcto return empty output; record-slot logged a breadcrumb to stderr and exited 0 instead of crashing the hook chain)Note on
test/install-theme-schedule.shThat's a local-development helper I used to test against an older installed omarchy (symlinks new bin/ scripts into the live install, copies units into
~/.config/systemd/user/). Happy to drop it from the PR if reviewers prefer; leaving it as a possible reference for someone wanting to iterate on the feature without a full omarchy update cycle.