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.

TemperatureLuminanceBacklight correction
6500 K1.000
4500 K0.879+9.6%
3500 K0.795+18.0%
2500 K0.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:

KeyDefault
interval_ac120Seconds between readings on mains
interval_battery300Seconds between readings on battery
curve_ac11 pointsAmbient 0–10 mapped to backlight 0–1
override_minutes30How long a manual keypress pauses automation
shutter_threshold0.0Fallback blocked-lens cutoff; off by default (see below)
exposure150Fixed exposure time once auto is pinned off
compensation_strength0.7How 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:

  1. They cannot fight. Backlight is a PWM value behind PowerDevil; colour temperature is the compositor's colour pipeline. No shared resource.
  2. KWin would not allow it regardless — every temperature property on org.kde.KWin.NightLight is read-only, and the only writable method is preview(), which exists for the settings slider and is reverted by stopPreview().

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 through dpkg-buildpackage, and install/purge is untested.

Licence

MIT