Circadian+ is a small solar-aware controller for the light emitted by a
physical monitor. It uses ddcutil to adjust only
three DDC/CI controls:
| VCP code | Monitor control | Default day | Default night |
|---|---|---|---|
10 |
Brightness | 50 | 5 |
18 |
Green gain | 100 | 0 |
1A |
Blue gain | 100 | 0 |
Red gain is deliberately never written. Circadian+ does not use Hyprsunset, gamma ramps, color-temperature transforms, or a compositor-specific API.
- Sunrise and sunset are calculated locally from latitude and longitude.
- Fixed wakeup and bedtime scheduling is also available.
- Morning and evening transitions interpolate brightness, green, and blue directly between the configured day and night values.
- The default morning transition ends at sunrise.
- The default evening transition starts at sunset.
- Automatic state comes from the current wall clock, so restart, suspend, and missed-event recovery do not depend on saved transition progress.
- A failed
ddcutilupdate remains visible in status and retries promptly. - Each update writes all three controls in one verified
setvcpcommand.
Circadian+ requires Linux, ddcutil 1.3 or newer, a DDC/CI-capable external
monitor, and permission to access its I2C or USB monitor-control interface.
First identify the display and try the three controls directly:
ddcutil detect
ddcutil getvcp --display 1 10 18 1AThe values reported by getvcp include each monitor's maximum. Keep the
configured values within those limits. Some monitors expose green and blue
gain only while an OSD color preset such as Custom or User is selected. A
monitor's advertised capabilities can also be incomplete, so a direct
getvcp test is more useful than relying only on ddcutil capabilities.
git clone https://github.com/sxndmxn/circadian-plus
cd circadian-plus
cargo install --path . --locked
install -Dm644 systemd/circadian-plus.service \
~/.config/systemd/user/circadian-plus.service
install -Dm600 circadian-plus.toml ~/.config/circadian-plus/config.toml
systemctl --user daemon-reload
systemctl --user enable --now circadian-plus.serviceThe Cargo package and installed command are both named circadian-plus.
mode = "auto"
[location]
latitude = 34.05
longitude = -118.24
[monitor]
display = 1
[transition]
duration_minutes = 120
morning_anchor = "end"
evening_anchor = "start"
easing = "smooth"
[day]
brightness = 50
green = 100
blue = 100
[night]
brightness = 5
green = 0
blue = 0
[daemon]
transition_tick_seconds = 5
idle_tick_seconds = 30
retry_seconds = 5
status_file = "$XDG_RUNTIME_DIR/circadian-plus/status.json"mode = "fixed" uses [schedule].wakeup and [schedule].bedtime instead of
[location]. Unknown fields, malformed environment overrides, DDC values
outside 0..=255, and unreadable explicit config paths are errors.
Supported easing values are linear, ease_in, ease_out, ease_in_out,
sine, smooth, smoother, and cubic_bezier(x1,y1,x2,y2).
Environment overrides use the CIRCADIAN_PLUS_ prefix. Run
circadian-plus config to print the validated effective configuration.
circadian-plus preview # calculate automatic levels; change nothing
circadian-plus now # levels successfully applied by the daemon
circadian-plus status # schedule, levels, and last DDC error
circadian-plus set 5 0 0 # hold brightness/green/blue until resume
circadian-plus day # hold the configured day levels
circadian-plus night # hold the configured night levels
circadian-plus pause # freeze the currently applied levels
circadian-plus resume # return to the automatic scheduleAdd --json for machine-readable output. --verbose enables informational
daemon logs; --quiet suppresses them. Commands use a private Unix socket in
$XDG_RUNTIME_DIR/circadian-plus; the daemon also atomically publishes
status.json there for bars and scripts.
cargo fmt --all -- --check
cargo test --all-targets --all-features --locked
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo build --release --locked
cargo audit
git diff --checkMIT