Omahub
← All plugins
P

Before Sunset

by priard

Switch themes with the sun: one theme for the day half, another for the night half.

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
5971e07
Scanned
3 weeks ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
5971e07
Reviewed
3 weeks ago

The plugin appears to be a legitimate desktop theme/sun-schedule manager. The code is defensive about path handling, avoids following unsafe symlinks, and does not contain obfuscated or credential-stealing behavior. Its broad control over themes, wallpaper, brightness, and volume is expected for its stated purpose.

  • The plugin can modify desktop settings and run external commands, but these actions are consistent with its documented functionality.
  • Some scripts use /tmp and lock files; comments indicate careful handling of symlink and multi-user race conditions, reducing risk.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/priard/omarchy-before-sunset --enable
Appearance #Hyprland #bar #quickshell

Before Sunset

Version 0.7.5 · what changed

Follow the sun: one Omarchy theme through the day, another through the night, each with its own wallpaper and its own answer to whether the top bar should be transparent.

The Before Sunset panel

The name is a loose nod to Linklater's Before Sunset, and to the two films either side of it — between them they cover sunrise, sunset and midnight. This concerns itself with the same three moments, and with considerably less conversation.

Why this exists

Warp's terminal lets you nominate a light theme and a dark theme and then forget about it — the editor follows the system, and each side keeps its own look. I wanted that for the whole desktop, not one application: a bright theme while the sun is up, something calmer after dark, each remembering the wallpaper I picked for it, switching on its own.

Omarchy already has everything needed to do this well. Themes declare whether they are light or dark, omarchy theme set retints the entire desktop in one command, and the shell hosts long-running plugins. What was missing was something to decide when, and somewhere to keep the two choices apart. That is all this plugin is.

It is not the only one

Other people have built versions of this — Darky, Dusk, Theme Scheduler, NightMan, and probably more by the time you read it. Some I found only after starting; some may not have existed yet. Go and look at them: one of them may suit you better, and none of us is going to mind.

That several of these exist is not a problem to be solved. It is the whole point of the arrangement. On this kind of system you can simply write the thing you want, the way you think it ought to work, on the evening you decide you want it — and then leave it out where the next person can take it or ignore it. This one is a mix of what I liked in the others, my own ideas, and a run of stray thoughts about how the day ought to turn over.

<details> <summary><b>The longer answer, if you have a minute</b></summary> <br>

My first Linux was Red Hat 5.0 — the one called Hurricane — in 1997. I was thrilled by it, and stayed thrilled through a long run of distributions after that. Then work happened. Windows for years, then macOS for more years, with the occasional wistful glance over the fence at whatever the Linux world was up to that decade.

Omarchy put me back over the fence, and it turns out I had missed it more than I knew. I have not enjoyed anything this much in a long time: the poking, the reading of other people's shell scripts, the discovery that the thing you assumed was hard-coded is in fact a TOML file you are allowed to edit. It has cost me an unreasonable number of hours lately. I regret none of them.

Before Sunset was meant to be a small thing for my own machine — pick two themes, follow the sun, done by Tuesday. Then I kept asking what else was possible, and it stopped being small. Somewhere along the way it grew opinions about wallpapers, bar transparency, the volume after dark, and the exact colour of the screen at twenty past nine.

This is where it got to. It is here as-is, in case it is useful to someone else. Enjoy.

</details>

What it does

  • Switches themes at sunrise and sunset, computed locally from your coordinates — or at fixed clock times, or from an ambient light sensor if the machine has one.
  • Remembers a background per theme and restores it, so an automatic switch never discards the wallpaper you chose.
  • Remembers whether the top bar should be transparent per theme, because a wallpaper that works behind a transparent bar in one theme rarely does in another.
  • Adopts any theme you pick elsewhere into whichever half of the day is running, so the schedule learns from what you actually do instead of fighting it.

The two slots

The slots are called day and night, not light and dark, and they mean exactly that: which half of the day they cover. Nothing stops you putting a light theme in both — a bright one for daylight and a softer, warmer light theme for the evening is a perfectly reasonable setup, and one the naming would get in the way of if the slots were called "light" and "dark".

Each slot's theme is still labelled with what it reads as, because that is useful to know. It just never constrains the choice.

The day and night slots, each with its theme and wallpaper

One rule

A theme change lands in the slot that is currently in force.

That holds wherever the change comes from — this panel, the theme switcher (Super + Ctrl + Shift + Space), the Omarchy menu, omarchy theme set, a keybinding of your own. Pick a theme during the day and it becomes your day theme; pick one at night and it becomes your night theme. Nothing is reverted behind your back, and nothing quietly expires at the next sunrise.

To step outside the schedule, pin a half. Pinning shows in the panel and stays until you change it, which is the honest version of an override.

Installation

omarchy plugin add https://github.com/priard/omarchy-before-sunset --enable

Then put the widget on the bar from the bar settings, or add it to ~/.config/omarchy/shell.json by hand:

{ "id": "priard.before-sunset" }

The plugin seeds itself on first run: the theme already on screen takes the slot matching its own light/dark reading, and the other slot gets a stock counterpart. It starts working immediately without changing your desktop out from under you.

Updating

omarchy plugin update priard.before-sunset

It fetches, shows you the diff, fast-forwards, and re-validates the manifest — rolling the update back if the new revision does not pass. Run it without an id to be offered every git-managed plugin at once.

Two things it will not do. It will not prompt for credentials, so the repository has to be readable without any: a public repo, or one your git already has a token for. And it will not merge over local edits, because it only ever fast-forwards — if you have been changing the plugin in place, it stops and says so rather than touching your work.

Removal

omarchy plugin remove priard.before-sunset

That is the whole of it for the plugin: it disables it, which drops its entry — slots, schedule, levels and all — out of ~/.config/omarchy/shell.json, then deletes the directory and rescans. No restart, and nothing left in your config.

One file stays, and it is not deleted for you because it holds choices you made: which wallpaper goes with which theme, and which themes wanted a transparent bar.

rm ~/.local/state/omarchy/settings/before-sunset.json

Delete it before reinstalling if you want the plugin to seed itself from scratch rather than pick up where it left off.

If the shell was not running when you removed the plugin, the entry cannot have been dropped from the config and will still be there. This takes it out of the bar layout and the plugins list, wherever it ended up:

jq '(.bar.layout |= map_values(map(select(.id != "priard.before-sunset"))))
    | (.plugins |= map(select(.id != "priard.before-sunset")))' \
  ~/.config/omarchy/shell.json > /tmp/shell.json &&
  mv /tmp/shell.json ~/.config/omarchy/shell.json

One thing genuinely stays changed: the bar's transparent flag is left wherever the schedule last set it, because it is a bar setting rather than a plugin one. Set it back from the bar settings if the last theme's preference was not yours. Your themes and wallpapers stay as they are — the plugin only ever drove Omarchy's own commands, and the one on screen when it left is simply the one you were using.

The panel

Click the bar icon. Right-click pins the other half without opening anything, and pins back to Auto from there.

MODE — Day and Night pin one slot and stop the schedule. Auto follows it.

SCHEDULE (Auto only) — how the half of the day gets decided. Options that cannot work are not offered: Sun needs coordinates, Light sensor needs hardware. A button that quietly does something else is worse than a button that is not there.

Sun draws the day as a strip — night, the lit stretch, night again, with a marker for now — and puts the boundary and offset controls under it, so those settings show their result instead of describing it. Boundary chooses where the day ends: the visible horizon, or one of the three twilights. The two offsets nudge one side without moving the other.

Fixed hours takes a from/to pair.

The strip shades the twilight either side of the day block, out to where the sky runs out of light. That span is the range Boundary can move the turn across, so the control shows its own reach before you touch it. The block itself keeps hard edges: a theme switch is a step, and drawing it as a fade would be drawing something that does not happen.

The sun schedule, with the day drawn as a strip and the boundary and offsets beneath it

Light sensor reads the ambient light sensor and decides from the room rather than the clock — useful under heavy blinds, or in a basement where the sun has no say. See below.

THEMES — one card per slot, showing the two pictures that actually decide how that half of the day looks: the theme's own preview and the exact wallpaper the slot will restore. Under them, what the theme reads as, which slot is in force, and a transparency switch.

Click the theme to open Omarchy's theme picker. Click the wallpaper to pick a different one from that theme's backgrounds. Themes are not required to ship a preview image, so cards for those show the name instead of an empty frame.

Both pickers open with that slot's current choice selected, not the one on screen — those differ whenever you are setting up the half of the day that is not running. Browsing changes nothing, and a choice made for the inactive slot is stored without touching the desktop.

Location

Coordinates come from the location Omarchy already stores for the weather widget, so it only has to be set once — in the Weather panel, or:

omarchy-weather-location --set "Warsaw" 52.22977,21.01178

Changing it there changes the schedule here. Setting latitude and longitude in this plugin's own settings overrides it.

Light sensor

Machines with an ambient light sensor expose it through the kernel's IIO interface, one file per reading. The plugin reads it directly rather than through iio-sensor-proxy, which is not installed everywhere and would be a dependency for something cat already answers. Where no sensor exists the option simply does not appear.

Readings are raw sensor counts scaled by the driver, not calibrated lux. They differ by orders of magnitude between machines, so no default threshold could mean anything. The panel therefore shows the live reading and a Set from now button: put the room in the state you want to be the boundary, press it, and the current reading becomes the threshold.

Two things keep the room from redecorating your desktop every time someone walks past:

  • Hysteresis — a band either side of the threshold where the sensor is given no opinion, so a reading sitting on the line does not oscillate. 15% by default.
  • Hold for — how long a changed reading has to persist before it counts. A hand passing over the sensor is not dusk. 45 seconds by default.

The first reading of a session commits immediately; there is nothing to flap away from yet, and waiting would leave the desktop undecided for no reason.

If sensor mode is selected but no threshold has been set, or the sensor disappears, the schedule falls back to the sun or fixed hours rather than freezing: an unconfigured preference should not take the desktop down with it.

The light sensor, with a live reading and calibration

Warming the screen after dark

Omarchy has a night light toggle: on or off, 4000 K or 6500 K, with nothing in between. This adds the part in between — the screen eases from one to the other across the turn of the day instead of stepping.

Three modes, and they are its own, not the theme's:

  • Off — the plugin does not touch the night light, and Omarchy's toggle behaves exactly as it always has.
  • Auto — follows the day, ramping across each turn.
  • Always — holds the warm temperature around the clock.

Keeping this separate from the theme's mode matters: pinning a theme to Day says something about colours on screen, not about whether your eyes want a warmer picture at midnight. Because it has hours of its own, it keeps them while a theme is pinned — it is the one thing on the schedule that does.

The volume and the brightness have no hours of their own; they only ever ride the schedule. So pinning a half stops them, and the panel dims all three sections to say so. Everything stays adjustable while dimmed — setting the night levels in the middle of the afternoon is the normal way round — and switching back to Auto lands them on the half of the day that is running, without waiting for the next turn.

Its own hours, if you want them

By default the warming follows the same schedule the themes do. It does not have to. Own hours gives it a from/to pair of its own, which is useful when a theme should turn at sunset but the screen has no business warming until the evening proper — and it gives the night light a timetable under the light sensor, which has none.

Choosing the numbers

Kelvin means little until you have seen it on your own screen, so as a rough ladder:

6500 K neutral, what hyprsunset calls identity
5000 K barely shows
4000 K comfortable for an evening — the default, and what Omarchy's own toggle means by "on"
3400 K strong
2700 K most people find this too much for anything but reading

The span defaults to 45 minutes. That puts each minute's move around fifty kelvin, which is below what the eye picks up against a room that is itself getting darker — the whole point being that you never catch it happening. Twenty minutes is noticeable if you are looking; anything under ten is a step with extra stages.

The ramp begins as the day turns over and finishes a span later. If you would rather already be warm by nightfall, Start early moves the beginning forward by that many minutes.

Seeing it

The section draws the whole day as a strip, tinted with the temperature the screen will actually be at each point, with a marker for now. It is generated by the same function that drives hyprsunset, so it cannot show one thing while another happens.

It also answers a question the numbers do not. Switching to auto in the late afternoon can look like it did nothing — and it did nothing, correctly, because sunset has not come yet and the marker is still in the neutral stretch. The strip makes that obvious instead of leaving it to be guessed at, and Start early is right underneath if you would rather it began sooner.

The night light section, with the day tinted by the temperature the screen will be

The icon beside the heading is drawn rather than typed: a braille canvas twelve dots square, which is enough for a disc that reads as round. By day the sun's rays turn slowly — the disc holds still, because what moves is the light coming off it — and only while the panel is open. By night it is the moon, at tonight's real phase, counted from a known new moon through the mean synodic month. It drifts a few hours against the sky and under a day across a century, which nobody can see in twelve dots. The unlit part is drawn as a rim, so a new moon is still a moon rather than an empty square.

A line under the panel heading carries the temperature on screen, in a swatch of that colour, alongside the dim and the volume, so none of the three needs its section opened to be checked. Each level names the half it belongs to — 80% at day while the day is running, 40% at night once the night is — and shows the half that is coming when the one running has no level of its own.

The dim and the volume appear only while they are actually going to move. Pin a half or run on the light sensor and they stand down, so the line drops them rather than promising something nobody is going to do. The night light stays, because it keeps its own hours whatever the mode.

The heading, with what the day is doing in the corner and the temperature, the dim and the volume beneath it

What the day is doing sits in a pill in the corner of the heading — DAY → 19:50 — where it can be read at any moment rather than waited for. The arrow rather than a separator, because the time is not a property of the half that is running: it is where that half is going. The line under the heading is then free to say what the plugin is doing to work that out, and while the panel is open in Auto it cycles through a few of them. Pinned or off it does not cycle: holding a half is a state worth naming once, and a line that kept changing under a pinned desktop would suggest the plugin is up to something it is deliberately not up to.

Sharing the toggle

The ramp drives the same hyprsunset that Omarchy's toggle and bar indicator use, so they see our changes as their own — the indicator calls anything under 6000 K "on". It only re-reads when told to, so it is refreshed after each step; otherwise its icon would lie.

Going the other way: pressing that toggle switches this on or off to match. And because the toggle is a statement about right now, pressing it at a time the schedule disagrees with holds your choice until the day next turns over, rather than being undone a minute later.

Easing the volume down at night

Give the plugin a level and the output volume slides to it when the schedule turns the day over. It is a slide, not a jump, and it shows no on-screen display — nobody pressed a key, so nothing should flash.

The rules are deliberately one-directional. Night lowers and never raises; day, if you enable it, raises and never lowers. A machine you deliberately hushed stays hushed either way. Reach for the volume keys mid-fade and the fade gives way, because someone pressing them has an opinion about the volume that outranks the schedule's.

Only the sun and fixed hours move it, and only when they turn the day over, and only while the schedule is running: pin a half and it stands still until Auto takes over again. The light sensor stops it too — a lamp switched on is not the morning, and a room that brightens should not get louder.

The sink is resolved through omarchy-audio-output-sink, the same helper the volume keys and the audio panel use, so a DSP or combined sink in front of the hardware is honoured rather than bypassed.

The volume section

Dimming the screens at night

The same idea as the volume, pointed at the displays. Give the plugin a night level and a day level and every screen that can be driven moves to them when the schedule turns the day over.

The rules are the same one-directional ones. Night dims and never brightens; day brightens and never dims. A screen you turned down yourself in the middle of the afternoon is still where you left it at dusk, because dusk only ever moves a screen further down. There is a floor of a few percent under every target: a screen at zero is a screen nobody can find the setting on again.

Only the sun and fixed hours move it, and only at the turn of the day. Pinning a half stops it until Auto takes over, and the light sensor never moves it at all.

The slide happens in software

A hardware brightness change is a step: DDC and asdcontrol take one value and the screen is at it. Stepping the whole desktop at dusk is the thing this plugin exists not to do — but writing a hundred intermediate values is worse, because DDC brightness lives in the monitor's own memory and some panels have a finite number of writes in them.

So the slide is drawn in gamma and the hardware moves once. hyprsunset already holds a gamma table for every output — it is what the night light drives — and takes a percentage over the same socket, which costs about four milliseconds and nothing at all in the monitor.

At dusk gamma slides down to the ratio between where a screen is and where it is going, and at the bottom the hardware takes the real value while gamma returns to full. At dawn the two happen together — gamma drops by exactly as much as the hardware rises, so nothing jumps — and then gamma slides back up. Both swaps are ordered so the gap between the two writes reads as a momentary dip rather than a flash: a screen that darkens for a frame is missable, a screen that flares is not.

Gamma is one table for the whole session, with no per-output switch, so with several displays the slide follows the shallowest of them. Nothing is ever darker on the way than it will be at the end, and the deeper targets finish falling in the hardware step, which is the direction they were going anyway.

The fade is driven by the clock rather than by a step count, so it takes the number of seconds it was given whatever the machine costs per write. Reach for the brightness keys while it is running and it stops: you have an opinion about the brightness, which outranks the schedule's. Set the fade to zero, or run without hyprsunset, and the hardware simply steps as it did before.

No notification is sent. This is the one change on the schedule nobody can fail to notice, and announcing it would be telling you what you are looking at.

One pair of numbers, and overrides if you want them

Most setups want one night level and one day level for everything, so that is what the section shows. Per display folds open a row for each display that answered, each starting on the pair above; taking one off the pair starts it on the numbers it already had, so the switch itself never changes what the screen is doing — only what moves it next time.

A display keeps its numbers while it is unplugged. The list is what answered this minute; the file remembers every display that ever did, keyed by its EDID serial rather than its connector, because DP-1 and DP-2 trade places the moment two cables do.

What can and cannot be driven

Three mechanisms, and Omarchy already knows which display wants which: brightnessctl for a laptop's own panel, DDC/CI for most external monitors, and asdcontrol for Apple's. The first two go through omarchy-brightness-display, the same command the brightness keys use.

Apple's displays are driven directly, for the same reason the night light drives hyprsunset rather than Omarchy's toggle: the command cannot do what is needed here. omarchy-brightness-display --monitor DP-2 accepts the monitor name and then hands the work to a helper that ignores it, driving whichever Apple display it detected first. With two of them connected, every write lands on the same panel and the other never moves. Addressing the devices ourselves is the only way both of them dim.

Which Apple display is on which connector, though, is not knowable — a display's EDID serial and its USB serial have nothing in common — so they are listed as displays in their own right rather than being tied to a monitor. The live reading in each row is what tells two identical panels apart.

A display that answers nothing is not listed and not reported: a monitor with no DDC, or one whose firmware refuses the brightness code, is left alone. Where no display answers at all, the section is not drawn, the same way the light sensor is absent on machines that have none.

Backgrounds are remembered per theme

omarchy theme set picks a theme's first background whenever it enters that theme, because the current-background symlink still points into the previous theme's directory and matches nothing in the new one. Left alone, every automatic switch would quietly discard the wallpaper you chose.

So the plugin watches which background is in use, remembers it per theme, and on a switch applies the theme without touching the background before restoring the remembered one. Pick a new wallpaper in the background switcher and that becomes the remembered one from then on.

Only the filename is stored, never a path: the directory a background is served from is staging, replaced wholesale on every theme switch.

Bar transparency is remembered per theme

Each theme gets its own answer to whether the top bar should be transparent, stored beside its background. Toggle it on a slot's row in the panel, or just change it the usual way while a theme is on screen — that is recorded as that theme's preference, the same as a background.

When the wallpaper cannot be painted

A transparent bar is transparent to something. Omarchy already picks the bar text colour by sampling the wallpaper strip behind it, so a light theme over a dark wallpaper gets light text and vice versa — that part needs no help from this plugin.

What breaks the arrangement is a wallpaper that never reaches the screen. Qt renders a few formats itself and gets the rest from imageformat plugins; if the decoder for a format is missing, the desktop paints black while every tool that reads the file — the contrast helper included — sees the real picture and picks the colour that contrasts with that. The bar ends up coloured for an image nobody can see, and a light theme becomes black-on-black.

So before trusting transparency, the plugin checks whether the current wallpaper's format has a decoder on this system. When it does not, transparency is held off and the bar paints the theme's own [bar] background and [bar] text, which is legible by construction. Your preference is captured first and kept: the moment the format can be decoded, transparency comes back on its own.

The check is deliberately one-sided. Reporting a format as decodable when it is not costs an ugly bar until the wallpaper changes; reporting a decodable format as broken would switch off transparency on a perfectly healthy desktop. Every uncertain case — an unfamiliar extension, a Qt layout the check does not recognise — is treated as fine and left alone.

On a stock Omarchy this rarely fires: the base install carries qt6-imageformats, which covers the WebP most themes ship. It earns its keep with themes that bring wallpapers in formats nothing on the system can read, and on installs upgraded from an older Omarchy where that package never arrived.

Configuration

The panel writes these; hand-editing works too. Settings live in this plugin's entry in ~/.config/omarchy/shell.json — the bar layout entry when the widget is on the bar, otherwise an entry in the top-level plugins[] array. That is the same precedence the shell's own updateEntryInline uses.

{
  "id": "priard.before-sunset",
  "dayTheme": "catppuccin-latte",
  "nightTheme": "matte-black",
  "mode": "auto",
  "autoMode": "sun",
  "twilight": "official"
}
Key Default Meaning
dayTheme seeded Theme for the day half.
nightTheme seeded Theme for the night half.
mode "auto" auto follows a schedule, day / night pin one slot, off parks the plugin without unloading it.
autoMode "sun" Which schedule auto follows: sun, fixed, or sensor.
twilight "official" Where the boundary sits: official (the visible horizon, i.e. sunrise and sunset), civil (~30 min later in the evening, earlier in the morning), nautical, astronomical, or a zenith angle in degrees.
sunriseOffsetMinutes 0 Shifts the morning switch. Negative is earlier.
sunsetOffsetMinutes 0 Shifts the evening switch. Negative is earlier.
fixed {"day":"07:00","night":"19:00"} Clock times for autoMode: "fixed", and the fallback during polar day or polar night.
latitude / longitude — Override the shared weather location for this plugin only.
notify false Send a desktop notification on each switch and each adoption.
sensor — {"threshold": 0, "hysteresis": 0.15, "dwellSeconds": 45, "device": ""} for autoMode: "sensor". A threshold of 0 means not calibrated yet; an empty device averages every sensor found.
volume — {"night": 25, "day": null, "fadeSeconds": 20}. Percentages; null leaves that side alone. 0 is a real target, meaning silence.
nightlight — {"mode": "off", "source": "schedule", "fixed": {"day": "07:00", "night": "21:00"}, "day": 6500, "night": 4000, "transitionMinutes": 45, "leadMinutes": 0}. mode is off, auto or on; source is schedule or fixed.

Theme names accept either form: "matte-black" or "Matte Black".

Remembered backgrounds and transparency live outside this entry, in ~/.local/state/omarchy/settings/before-sunset.json: they are state the plugin observes, not configuration you would hand-write.

CLI

omarchy-shell before-sunset status              # JSON: mode, slots, transitions, location, remembered state
omarchy-shell before-sunset toggle              # pin the other half, or hand control back to the schedule
omarchy-shell before-sunset day                 # pin the day slot
omarchy-shell before-sunset night               # pin the night slot
omarchy-shell before-sunset auto                # follow the schedule again
omarchy-shell before-sunset off                 # stand down; day, night or auto starts it again
omarchy-shell before-sunset schedule sun        # what auto follows: sun, fixed, sensor
omarchy-shell before-sunset nightlight always   # the night light: off, auto, always
omarchy-shell before-sunset refresh             # re-evaluate now, the same nudge a resume gives

Each answers with the value it settled on, so a binding can act on the result rather than asking again. schedule and nightlight want one word; hand either an empty string and it reports the mode in force without changing it.

Nothing here sets a number. Temperatures, offsets, thresholds and volumes are chosen in the panel, where you can see what they do to the day while you pick them — a number typed blind at a prompt is the one thing this plugin has no good way to show you.

Bind the toggle to a key in ~/.config/hypr/bindings.lua if you want it on the keyboard rather than the bar.

How it works

Sun.js implements the US Naval Observatory almanac algorithm, cross-checked against an independent NOAA solar-position implementation and agreeing with it to within a minute across solstices, equinoxes and both hemispheres.

The service holds no countdown. Every tick recomputes, from scratch, which half of the day the current instant falls in, and compares that against the theme actually on screen. That is what makes it survive suspend, hibernate, clock changes and daylight saving: a laptop opened after two days asleep corrects itself on the next tick rather than waiting out a timer that never ran.

The tick is monotonic, so it does not run while the machine is suspended, and on its own it would leave a laptop opened in the morning showing last night's theme for the better part of a minute. So the resume is not waited for. logind announces it on the system bus the moment it happens, ahead of the lock screen asking for a password, and the schedule is evaluated there — which is early enough that the right half of the day is already on screen behind the password prompt. It is a shortcut rather than the mechanism: on a machine with no gdbus the tick still corrects everything, just later.

Inside the polar circles there are stretches with no sunrise or sunset at all. The schedule falls back to the fixed clock times so the desktop keeps some rhythm instead of freezing on one theme for weeks.

Light and dark readings come from omarchy-theme-color, the same helper the rest of Omarchy uses, so its precedence (mode, legacy theme_type, a light.mode marker, then background luminance) is honoured rather than reimplemented. Themes shipping only an alacritty.toml have no colors.toml until applied, and are judged on their background with the same rule. This reading is advisory: it labels the slots and seeds them on first run, and never restricts what you can put where.

Layout

manifest.json          plugin metadata
Service.qml            the schedule, adoption, and remembered state
BeforeSunsetPanel.qml     bar icon and settings panel
Sun.js                 sunrise/sunset and the moon's phase, pure functions
Sky.js                 the sun and the moon drawn in braille, pure functions
bin/before-sunset-apply   switch a theme, restoring its remembered background
bin/before-sunset-pick    open the theme picker with a given theme preselected
bin/before-sunset-themes  list installed themes and which side each reads as
bin/before-sunset-bg-state  the current wallpaper, and whether Qt can decode it
bin/before-sunset-bg-pick   pick a wallpaper from any theme's backgrounds
bin/before-sunset-slot      resolve a slot's preview and wallpaper for the panel
bin/before-sunset-sensor    read the ambient light sensor, if there is one
bin/before-sunset-volume    ease the output volume to a level over a few seconds
bin/before-sunset-nightlight  read or set the screen colour temperature
bin/before-sunset-brightness  list the displays that can be dimmed, and dim one
bin/before-sunset-wake      report each resume from suspend, so the theme can catch up at once
bin/before-sunset-transparent  set the bar's transparency, from outside the shell

Requirements and dependencies

A stock Omarchy 4. Nothing to install.

The plugin shells out only to bash, jq, coreutils, and Omarchy's own commands — omarchy-theme-set, omarchy-theme-bg-set, omarchy-theme-color, omarchy-theme-switcher, omarchy-menu-images, omarchy-audio-output-sink, omarchy-notification-send, omarchy-brightness-display, omarchy-bar — plus pactl for the volume fade, hyprctl for the night light and the monitor list, gdbus for the resume announcement, and udevadm to tell two identical displays apart, all part of a base install.

Brightness on Apple's displays goes through asdcontrol, which Omarchy ships in its base packages along with the /etc/sudoers.d rule that lets it run without a password. That rule is Omarchy's own file and grants exactly one command — NOPASSWD: /usr/bin/asdcontrol — which this plugin reads and never installs, edits or removes. Every call passes -n, so where the rule is absent the displays simply do not appear in the list and nothing prompts. It makes no network requests: sunrise and sunset are arithmetic, not an API call.

It listens on the system bus for logind's resume announcement, which is a broadcast signal: received with no privileges, and nothing is sent back.

It reads /sys/bus/iio/devices/ for a light sensor, sets the output volume and the screen colour temperature when you ask it to, and writes to exactly two places: its own entry in ~/.config/omarchy/shell.json, and ~/.local/state/omarchy/settings/before-sunset.json. It changes the bar's transparent flag when a theme's remembered preference or an undecodable wallpaper calls for it.

Development

The version is written in three places and they are expected to agree: manifest.json, the heading at the top of this file, and the newest section of CHANGELOG.md.

Saving a file under ~/.config/omarchy/plugins/ logs a plugin reload, but a running service instance keeps its old code and its IPC target, and a mounted bar widget keeps its old code too. Run omarchy restart shell after editing the QML, before concluding anything about behaviour.

Changelog

See CHANGELOG.md.

License

MIT. See LICENSE.