Candela
July 27, 2026 · View on GitHub
Webcam-driven adaptive screen brightness for KDE Plasma on Wayland.
Your laptop has no ambient light sensor, so Plasma cannot adjust brightness to the room. Candela uses the webcam as one: it samples ambient light, maps it through a curve you can tune, and writes the result to PowerDevil — so walking from a sunlit room to a dim one dims the screen without you reaching for the keys, and walking back brightens it again.
It does not touch colour temperature. Night Light keeps that.
What makes it different
It never touches your brightness keys. No scancode remapping, no stealing
Plasma's global shortcut. Candela subscribes to PowerDevil's
brightnessChanged signal and identifies manual adjustments by elimination —
anything it did not write, you did. If the daemon crashes or you uninstall it,
your brightness keys behave exactly as they always did, because they were never
modified.
When you do press a key while automatic control is on, Candela keeps your adjustment, pauses itself for 30 minutes, and tells you why:
Brightness is being managed automatically Your adjustment has been kept. Candela is paused for 30 minutes.
[Resume now][Disable auto]
It compensates for Night Light. Warming a display costs real luminance — attenuating green and blue to reach 3500 K throws away about 20% of the light, so the same backlight genuinely is dimmer in the evening. Candela reads the current temperature from KWin and folds the loss into its target, holding perceived brightness steady across the day.
| Temperature | Luminance | Backlight correction |
|---|---|---|
| 6500 K | 1.000 | — |
| 4500 K | 0.879 | +9.6% |
| 3500 K | 0.795 | +18.0% |
| 2500 K | 0.681 | +32.9% |
This is part of the model, not a slider. It clamps at 100% — at the panel ceiling there is no headroom to correct with, and Candela does not pretend otherwise.
Requirements
- KDE Plasma 6 (verified on 6.6.5, Wayland)
- A webcam
ffmpeg,v4l-utils,python3-dbus,python3-gi,python3-pyqt6
v4l-utils is required, not optional. Webcams auto-expose, and an
auto-exposing camera re-normalises away the very signal being measured — two
readings seconds apart in an unchanged room measured 7.50 and 3.73 here.
Pinning exposure to manual brought the same room to 4.38 / 4.29 / 4.41 / 4.17.
Without v4l2-ctl the daemon appears to work while producing noise.
Install
sudo apt install ./candela_0.1.0_all.deb
systemctl --user enable --now candela
Or from source:
sudo apt install ffmpeg v4l-utils python3-dbus python3-gi python3-pyqt6
git clone https://github.com/danielrosehill/Candela.git && cd Candela
python3 -m candela.daemon --once -v # one reading, applied, then exit
Usage
candelad # run the daemon in the foreground
candelad --once -v # take one reading and apply it
candela # tray icon and control window
Configuration lives at ~/.config/candela/config.toml and is written by the
GUI. The values most worth touching:
| Key | Default | |
|---|---|---|
interval_ac | 120 | Seconds between readings on mains |
interval_battery | 300 | Seconds between readings on battery |
curve_ac | 11 points | Ambient 0–10 mapped to backlight 0–1 |
override_minutes | 30 | How long a manual keypress pauses automation |
shutter_threshold | 0.0 | Fallback blocked-lens cutoff; off by default (see below) |
exposure | 150 | Fixed exposure time once auto is pinned off |
compensation_strength | 0.7 | How much of the Night Light luminance loss to correct |
compensation_strength is below 1.0 on purpose. Correcting the full
photometric loss overshoots, because the eye chromatically adapts to a warm
white and the Helmholtz–Kohlrausch effect makes warm tones read brighter than
their luminance implies.
Why colour temperature is out of scope
The original design had one tool owning both brightness and Kelvin, to avoid two daemons fighting over the screen. Two findings retired that:
- They cannot fight. Backlight is a PWM value behind PowerDevil; colour temperature is the compositor's colour pipeline. No shared resource.
- KWin would not allow it regardless — every temperature property on
org.kde.KWin.NightLightis read-only, and the only writable method ispreview(), which exists for the settings slider and is reverted bystopPreview().
So Night Light keeps Kelvin, and Candela reads it. The GUI shows the current temperature and links straight to the Night Light settings module.
Full introspection output, the paths that no longer exist, and the webcam
findings: docs/kde-plasma-interfaces.md.
Status
Early. The daemon, sensor, curve, compensation, PowerDevil write path and override detection all work and are verified on the reference machine.
Candela ships disabled (enabled = false). The curve is initially
validated, not finished: one confirmation on its sloping section (ambient 3.84
→ 93%, judged correct in the room) and three readings agreeing that ~4.5 and
above should be full backlight. Everything below 3.8 is interpolation.
It is also calibrated on exactly one machine — a ThinkPad E15 Gen 3 with a
Chicony 04f2:b725 camera. A different camera produces a different ambient
scale and needs its own pass, so check it against your own lighting with
--dry-run before enabling.
docs/calibration.md records the raw readings, the
hardware they came from, and a per-claim breakdown of what was measured versus
interpolated versus guessed.
Outstanding:
- No sample below ambient 3.8. The dim end of the curve is pure interpolation, and that is where a wrong curve is most noticeable.
- The GUI has the enable/disable toggle, live status, Kelvin readout and a minimum-brightness control. No draggable curve editor.
- The Debian packaging is scaffolded (
packaging/debian/, systemd user unit) but has never been throughdpkg-buildpackage, and install/purge is untested.
Licence
MIT