Automatic brightness adjustment based on screen contents and ALS
Automatic brightness adjustment based on screen contents and ALS
A tool for Wayland compositors to automatically adjust screen brightness based on the screen contents and amount of ambient light around you.
With the default capturer = "auto", wluma tries supported Wayland protocols followed by PipeWire sources once during startup. If none works, it uses ambient light only for the rest of the process lifetime. Once a protocol produces usable frames, wluma keeps using and reconnecting only that protocol after interruptions. See the "Configuration" section below for details.
The list of supported protocols:
ext-image-copy-capture-v1 - the newest protocol that potentially is (or will be) supported by any modern Wayland desktop environment.ext-image-capture-source-v1 and linux-dmabuf-v1 protocols to be supported as well.wlr-screencopy-unstable-v1 - supported by any wlroots-based compositors (e.g. sway), as well as Hyprland.linux-dmabuf-v1 protocol to be supported as well.wlr-export-dmabuf-unstable-v1 - supported by any wlroots-based compositors (e.g. sway).The app will automatically brighten the screen when you are looking at a dark window (such as a fullscreen terminal) and darken the screen when you are looking at a bright window (such as web browser). The algorithm takes into consideration the amount of ambient light around you, so the same window can be brighter during the day than during the night.
With permission of Lumen's author (the project that inspired me to create this app), I'm reusing a demo GIF:
Simply launch wluma and continue adjusting your screen brightness as you usually do - the app will learn your preferences.
wluma will not do anything on the first launch! You have to adjust the brightness by hand a few times, in different environment and/or with different screen contents, that way wluma will learn your preferences and only then it will begin to automatically change your screen brightness for you.
Changing brightness, seeing status and controlling the running daemon can be also done through CLI commands, run wluma help to see all available options.
The app has minimal impact on system resources and battery life even though it is able to monitor screen contents several times a second. This is achieved by importing DMA-BUF screen buffers and doing computations on GPU using Vulkan API.
Use one of the available packages and methods below:
$PATH.wluma.service if you want to run it as a systemd-service - it can be placed e.g. in ~/.config/systemd/user/.90-wluma-backlight.rules too, if you want to give wluma direct driver access for the fastest performance (see "Permissions" section below) - it can be placed e.g. in /etc/udev/rules.d/.If you want to build the app yourself, make sure you use latest stable Rust, otherwise you might get compilation errors! Using rustup is perhaps the easiest. Ubuntu needs the following dependencies: sudo apt-get -y install v4l-utils libv4l-dev libudev-dev libvulkan-dev libdbus-1-dev libpipewire-0.3-dev.
Then simply run cargo build --locked --release and the binary will be placed into ./target/release/wluma.
In order to access backlight devices, wluma must either:
90-wluma-backlight.rules udev rule, add your user to the video group and reboot (fastest, most likely a requirement for smooth transitions)elogind or systemd-logind (they provide a safe interface for unprivileged users to control device's brightness through dbus, no configuration necessary)root (not recommended)The configuration is entirely optional, as everything is auto-detected to the best extent possible. To make any changes described below, create $XDG_CONFIG_HOME/wluma/config.toml and set the relevant overrides.
When ALS configuration is omitted, wluma uses an external ALS whenever $XDG_RUNTIME_DIR/wluma/als.sock is a Unix socket, otherwise it uses an available IIO ambient light sensor, then the Apple SMC sensor of an Intel MacBook, or continues without one. This selection is updated while wluma is running as sources appear and disappear. For IIO it first tries iio-sensor-proxy over the system D-Bus and then direct IIO discovery under /sys/bus/iio/devices.
Explicit [als.external], [als.iio], [als.applesmc], [als.webcam], [als.time] and [als.none] sections override automatic selection. The IIO path enables direct polling from a different sysfs directory when iio-sensor-proxy is unavailable.
[als.iio]
path = "/sys/bus/iio/devices"Intel MacBooks expose their ambient light sensor through the applesmc platform driver instead of IIO, as /sys/devices/platform/applesmc./light. wluma uses the brighter sensor reading on the lux scale. The path is optional; when omitted, the first applesmc.* device with a light file is used:
[als.applesmc]
path = "/sys/devices/platform/applesmc.768/light"An external ALS is a Unix stream socket server that sends one non-negative decimal value per line. It should send values as soon as they become available and at least once every two seconds, including when unchanged. The path defaults to $XDG_RUNTIME_DIR/wluma/als.sock and the scale defaults to lux:
[als.external]
path = "/run/user/1000/home-assistant/als.sock"
scale = "linear"The lux scale accepts illuminance values without an upper bound. The linear scale accepts normalized values from 0 to 100. The socket is detected whether it exists before wluma starts or appears later.
Webcam ALS reports perceived camera-frame lightness from 0 to 100. Set video to the zero-based V4L2 device number, such as 0 for /dev/video0:
[als.webcam]
video = 0Time ALS uses a circular, linearly interpolated schedule of synthetic light levels. Hours must be from 0 to 23 and levels from 0 to 100:
[als.time]
levels = { 0 = 0, 7 = 10, 9 = 40, 12 = 70, 16 = 50, 19 = 10, 21 = 0 }To explicitly disable ambient-light input, use:
[als.none]The adaptive predictor remembers your adjustments and applies them across the full range of ambient light and screen content, including conditions you have not encountered before. It reproduces learned preferences when possible and balances conflicting preferences instead of discarding them.
After two minutes of inactivity, wluma additionally reduces displays to 30% on top of their predicted brightness, and turns keyboard backlights off. These defaults can be changed globally or per power source:
[idle]
enabled = true
timeout = 120
brightness = 30
[idle.ac]
timeout = 300
[idle.battery]
brightness = 20Timeouts are in seconds. Omitted AC and battery settings inherit the global values; set enabled = false globally or in either profile to disable it. Wluma uses UPower to detect the current power source. When activity resumes, the idle reduction is removed from the latest prediction.
Idle detection uses ext-idle-notify-v1, which honors compositor idle inhibitors, with GNOME Mutter's IdleMonitor as a fallback. Disable other automatic idle dimming to avoid conflicts. On GNOME:
gsettings set org.gnome.settings-daemon.plugins.power idle-dim falseThe entire output section is optional. Wluma continuously discovers connected DRM outputs, associates internal panels with /sys/class/backlight devices and external monitors with DDC using their EDID, and automatically selects a screen capturer. Automatically discovered outputs and keyboards are started and stopped as they appear and disappear, and display capture and brightness access are suspended while a display is powered down. For external monitors, installing ddcutil-service is recommended: wluma automatically prefers its persistent libddcutil D-Bus service and falls back to raw DDC access when it is unavailable. DDC is known to often be problematic, so also consider trying ddcci-driver-linux if you can.
To override the capturer, predictor or gamma control for a discovered output, use the detected brightness type and DRM connector name reported with RUST_LOG=debug:
[[output.ddcutil]]
name = "HDMI-A-3"
capturer = "none"Explicit output.backlight and output.ddcutil entries remain supported. The path of a backlight entry is optional when its name matches a discovered connector. When set, it must point to a sysfs backlight device directory such as /sys/class/backlight/intel_backlight. Supplying a path or DDC identifier overrides the discovered value. enabled, capturer, vulkan_device, predictor and gamma are available for both output types. To exclude a discovered output, set enabled = false:
[[output.ddcutil]]
name = "DP-1"
enabled = falseThe name field identifies the Wayland output and is matched as a substring against descriptions containing model, manufacturer and serial number (like eDP-1 'Sharp Corporation 0x14A8 0x00000000' (eDP-1)). Automatically discovered output names use the unambiguous DRM connector name.
For output.ddcutil, if the identifier that works for brightness control is not the same substring that your Wayland compositor exposes for screen capture, set identifier. This is common for external DDC monitors: name should match the compositor output description such as HDMI-A-3, while identifier may need to be a serial number for DDC.
Tip: run wluma with RUST_LOG=debug to see how your outputs are being identified, so that you can choose an appropriate name and identifier configuration values.
The capturer field will determine how screen contents will be captured. Currently supported values are auto, wayland, pipewire and none (ignores screen contents and predicts brightness only based on ALS). The default value auto tries supported Wayland protocols first, then PipeWire sources, once during startup. If none works, wluma uses ALS alone for the rest of the process lifetime. Once a protocol successfully produces frames, it remains selected and only that protocol is reconnected after interruptions; brightness prediction pauses until capture recovers rather than switching protocols or assuming zero screen luma. The value wayland chooses among Wayland protocols in the same way, but you can force a specific one with ext-image-copy-capture-v1, wlr-screencopy-unstable-v1 or wlr-export-dmabuf-unstable-v1. The value pipewire similarly chooses among PipeWire sources, and you can force a specific source with zkde-screencast-unstable-v1, gnome-mutter-screencast or xdg-desktop-portal-screencast. Unlike auto, an explicitly selected family or protocol exits with an error if no capture protocol becomes available during startup.
Tip: run wluma with RUST_LOG=debug and capturer="auto" to see which protocols are supported and
No open issues yet, or sync has not completed.