4.2 KiB
Implementation Log
This file documents the brightness/OSD design decisions, failed attempts, and current state.
Current Working Design
The current setup intentionally keeps the brightness path simple:
- A brightness shortcut immediately changes the internal panel with
brightnessctl. - The OSD is shown or updated after the internal change.
- The external monitor follows through
external-brightness-sync.service.
The internal panel is the master. The external monitor is never adjusted independently by wluma.
Why Internal Is The Master
Letting wluma control both the internal backlight and the external DDC/CI monitor independently caused occasional drift between displays.
The cleaner design is:
wlumacontrols only/sys/class/backlight/intel_backlightexternal-brightness-sync.servicereads the internal percentageddcutilsets the external monitor to the same percentage
This keeps one source of truth for automatic brightness.
Native GNOME OSD Attempt
GNOME Shell exposes a DBus method:
org.gnome.Shell.ShowOSD
On GNOME Shell 49.6, external calls to this method failed with:
GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: ShowOSD is not allowed
Because of that, the project uses a small GTK4/libadwaita OSD instead of trying to call GNOME Shell's private OSD API.
GTK OSD Positioning On Wayland
The first GTK OSD version used a normal GTK window. It worked, but GNOME Wayland placed it like a normal window, so it appeared near the center of the screen.
Under Wayland, normal clients cannot reliably choose arbitrary screen coordinates. A top-edge OSD should use the layer-shell protocol.
The project therefore supports gtk4-layer-shell:
sudo dnf install -y gtk4-layer-shell
When available, the OSD uses it for proper top-edge positioning.
Without gtk4-layer-shell, the OSD still works, but GNOME may place it in the normal window position.
OSD Flicker Fix
An early OSD version killed the existing OSD process on every new brightness key press.
That caused a chopped feeling:
key press -> old OSD exits -> new OSD starts
The current OSD instead keeps a running process alive and updates it:
key press -> write new value -> signal running OSD -> reset hide timer
Files used for that:
/tmp/brightness-osd.pid
/tmp/brightness-osd.value
This smooths repeated key presses without delaying the actual brightness change.
Failed Debounce Attempt
There was an attempt to coalesce multiple fast brightness key presses into one final brightness operation.
The attempted flow was:
key press -> store target value -> show target in OSD -> wait for quiet period -> run brightnessctl once
The debounce delay was controlled by:
BRIGHTNESS_APPLY_DEBOUNCE_MS="180"
This was reverted because it made brightness changes feel laggy.
Why It Felt Bad
Brightness keys are expected to affect the panel immediately.
Debouncing the actual brightnessctl call introduced a visible delay:
press keys -> screen does not change immediately -> final jump happens later
That is unpleasant for display brightness because visual feedback matters more than reducing the number of brightness writes.
The external monitor already has some delay because ddcutil is slower than an internal backlight write. Delaying the internal display as well made the whole interaction feel worse.
Lesson
Do not debounce the real brightness change.
Only smooth the OSD:
brightnessctl immediately
OSD update/reuse
external sync follows
Reverted Commit
The debounce attempt was committed and then reverted:
324505b Debounce brightness key bursts
a84963e Revert "Debounce brightness key bursts"
The reverted implementation added:
bin/brightness-step
BRIGHTNESS_APPLY_DEBOUNCE_MS
Those are intentionally not part of the current working design.
Current Recommendation
For brightness controls:
- keep internal brightness immediate
- keep OSD process reusable instead of restarting it
- keep external sync asynchronous
- avoid batching or delaying real brightness changes
This gives the most responsive feel while preserving internal/external sync.