Initial Fedora brightness automation setup

This commit is contained in:
jan
2026-04-25 13:20:58 +02:00
commit dc932b8f64
12 changed files with 1092 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
__pycache__/
*.py[cod]
.DS_Store

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Jan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

152
README.md Normal file
View File

@@ -0,0 +1,152 @@
# Fedora Brightness Automation Buttons OSD
Small Fedora/GNOME brightness setup for laptops with an internal Linux backlight and an external DDC/CI monitor.
The internal display is the master. Brightness keys change the internal panel, show a compact OSD, and a user service mirrors the same percentage to the external monitor with `ddcutil`.
This avoids drift from letting two outputs auto-adjust independently.
## Features
- Internal display controlled with `brightnessctl`
- External monitor mirrored with `ddcutil` VCP code `10`
- Compact GTK4/libadwaita OSD for custom brightness shortcuts
- Optional Wayland top-edge positioning with `gtk4-layer-shell`
- systemd user service for continuous external sync
- Configurable backlight device, DDC bus, monitor model, step size, and sync interval
- Optional wluma config where only the internal display is managed
## Tested Setup
- Fedora 43
- GNOME Shell 49
- Internal panel: `intel_backlight`
- External monitor: DDC/CI via `ddcutil`
Other Fedora/GNOME setups should work if `brightnessctl` and `ddcutil` can control the displays.
## Dependencies
```bash
sudo dnf install -y brightnessctl ddcutil python3-gobject gtk4 libadwaita gtk4-layer-shell
```
`gtk4-layer-shell` is recommended on GNOME Wayland. Without it, GNOME may place the OSD like a normal GTK window instead of pinning it at the top edge.
If your `ddcutil` command needs sudo, configure passwordless sudo for the specific command you use. Example:
```text
jan ALL=(root) NOPASSWD: /usr/bin/ddcutil
```
Use a stricter sudoers rule if you prefer limiting arguments.
## Quick Install
```bash
git clone https://gitea.diehanis.de/Jan/Fedora_Brightness_Automation_Buttons_OSD.git
cd Fedora_Brightness_Automation_Buttons_OSD
./install.sh
```
The installer writes:
```text
~/.local/bin/brightness-osd
~/.local/bin/brightness-up-all
~/.local/bin/brightness-down-all
~/.local/bin/brightness-sync-hg342pcb
~/.config/systemd/user/external-brightness-sync.service
~/.config/brightness-automation/env
```
Then set your GNOME custom keyboard shortcuts to:
```text
/home/YOUR_USER/.local/bin/brightness-down-all
/home/YOUR_USER/.local/bin/brightness-up-all
```
Use the full path in GNOME Settings.
## Configuration
Edit:
```text
~/.config/brightness-automation/env
```
Default config:
```bash
BRIGHTNESS_BACKLIGHT_DEVICE="intel_backlight"
BRIGHTNESS_SYNC_BACKLIGHT="/sys/class/backlight/intel_backlight"
BRIGHTNESS_STEP="10"
BRIGHTNESS_DDCUTIL_MODEL="HG342PCB"
BRIGHTNESS_DDCUTIL_DISPLAY="1"
BRIGHTNESS_DDCUTIL_BUS="16"
BRIGHTNESS_SYNC_INTERVAL="1"
BRIGHTNESS_SYNC_MIN_PERCENT="1"
BRIGHTNESS_OSD_VISIBLE_MS="1400"
```
After changing DDC or sync values:
```bash
systemctl --user restart external-brightness-sync.service
```
## Finding Your Values
Backlight device:
```bash
brightnessctl -l
ls /sys/class/backlight
```
External monitor:
```bash
sudo ddcutil detect
sudo ddcutil getvcp 10
```
If `ddcutil detect` reports a bus like `/dev/i2c-16`, set:
```bash
BRIGHTNESS_DDCUTIL_BUS="16"
```
## wluma
If you use `wluma`, let it manage only the internal display. The external monitor should follow through the sync service.
Optional install of the example wluma config:
```bash
INSTALL_WLUMA_CONFIG=1 ./install.sh
systemctl --user restart wluma.service
```
Example config is in:
```text
wluma/config.toml
```
## Status
```bash
systemctl --user status external-brightness-sync.service
journalctl --user -u external-brightness-sync.service -n 50 --no-pager
```
## Uninstall
```bash
./uninstall.sh
```
The uninstall script keeps `~/.config/brightness-automation/env` so local settings are not destroyed.

14
bin/brightness-down-all Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIG="${BRIGHTNESS_AUTOMATION_CONFIG:-$HOME/.config/brightness-automation/env}"
if [[ -f "$CONFIG" ]]; then
# shellcheck disable=SC1090
source "$CONFIG"
fi
BACKLIGHT_DEVICE="${BRIGHTNESS_BACKLIGHT_DEVICE:-intel_backlight}"
STEP="${BRIGHTNESS_STEP:-10}"
brightnessctl -q -d "$BACKLIGHT_DEVICE" set "${STEP}%-"
"$HOME/.local/bin/brightness-osd" || true

204
bin/brightness-osd Executable file
View File

@@ -0,0 +1,204 @@
#!/usr/bin/env python3
import os
import signal
import sys
import time
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Adw, Gdk, GLib, Gtk
try:
gi.require_version("Gtk4LayerShell", "1.0")
from gi.repository import Gtk4LayerShell
except (ImportError, ValueError):
Gtk4LayerShell = None
BACKLIGHT_PATH = os.environ.get(
"BRIGHTNESS_OSD_BACKLIGHT", "/sys/class/backlight/intel_backlight"
)
PID_FILE = os.environ.get("BRIGHTNESS_OSD_PID", "/tmp/brightness-osd.pid")
VALUE_FILE = os.environ.get("BRIGHTNESS_OSD_VALUE", "/tmp/brightness-osd.value")
VISIBLE_MS = int(os.environ.get("BRIGHTNESS_OSD_VISIBLE_MS", "1400"))
def read_percent():
if len(sys.argv) > 1:
return max(0, min(100, int(round(float(sys.argv[1])))))
with open(os.path.join(BACKLIGHT_PATH, "brightness"), encoding="utf-8") as handle:
current = int(handle.read().strip())
with open(os.path.join(BACKLIGHT_PATH, "max_brightness"), encoding="utf-8") as handle:
maximum = int(handle.read().strip())
return max(0, min(100, round(current * 100 / maximum)))
def read_existing_pid():
try:
with open(PID_FILE, encoding="utf-8") as handle:
return int(handle.read().strip())
except (FileNotFoundError, ValueError):
return None
def process_exists(pid):
try:
os.kill(pid, 0)
return True
except ProcessLookupError:
return False
except PermissionError:
return True
def signal_existing(percent):
pid = None
for _attempt in range(5):
pid = read_existing_pid()
if pid and pid != os.getpid() and process_exists(pid):
break
time.sleep(0.03)
else:
return False
with open(VALUE_FILE, "w", encoding="utf-8") as handle:
handle.write(str(percent))
try:
os.kill(pid, signal.SIGUSR1)
return True
except ProcessLookupError:
return False
class BrightnessOsd(Adw.Application):
def __init__(self, percent):
super().__init__(application_id="local.brightness.osd")
self.percent = percent
self.label = None
self.bar = None
self.timeout_id = None
def update_percent(self, percent):
self.percent = max(0, min(100, percent))
if self.label:
self.label.set_label(f"{self.percent}%")
if self.bar:
self.bar.set_value(self.percent)
if self.timeout_id:
GLib.source_remove(self.timeout_id)
self.timeout_id = GLib.timeout_add(VISIBLE_MS, self.quit)
def handle_refresh(self, *_args):
try:
with open(VALUE_FILE, encoding="utf-8") as handle:
percent = int(handle.read().strip())
except (FileNotFoundError, ValueError):
percent = read_percent()
GLib.idle_add(self.update_percent, percent)
def do_activate(self):
with open(PID_FILE, "w", encoding="utf-8") as handle:
handle.write(str(os.getpid()))
window = Gtk.ApplicationWindow(application=self)
window.set_title("Brightness")
window.set_decorated(False)
window.set_resizable(False)
window.set_default_size(190, 58)
window.set_opacity(0.92)
window.set_hide_on_close(True)
if Gtk4LayerShell is not None:
Gtk4LayerShell.init_for_window(window)
Gtk4LayerShell.set_layer(window, Gtk4LayerShell.Layer.OVERLAY)
Gtk4LayerShell.set_anchor(window, Gtk4LayerShell.Edge.TOP, True)
Gtk4LayerShell.set_margin(window, Gtk4LayerShell.Edge.TOP, 28)
Gtk4LayerShell.set_keyboard_mode(window, Gtk4LayerShell.KeyboardMode.NONE)
css = Gtk.CssProvider()
css.load_from_data(
b"""
window {
background: transparent;
}
.osd {
background: alpha(@window_fg_color, 0.88);
color: @window_bg_color;
border-radius: 15px;
padding: 10px 14px;
box-shadow: 0 10px 34px alpha(black, 0.34);
}
.osd image {
color: @window_bg_color;
}
.percent {
font-size: 15px;
font-weight: 700;
}
levelbar trough {
min-height: 6px;
border-radius: 999px;
background: alpha(@window_bg_color, 0.24);
}
levelbar block.filled {
min-height: 6px;
border-radius: 999px;
background: @window_bg_color;
}
"""
)
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(), css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
box.add_css_class("osd")
box.set_halign(Gtk.Align.CENTER)
box.set_valign(Gtk.Align.START)
box.set_margin_top(28)
icon = Gtk.Image.new_from_icon_name("display-brightness-symbolic")
icon.set_pixel_size(22)
self.label = Gtk.Label(label=f"{self.percent}%")
self.label.add_css_class("percent")
self.label.set_width_chars(4)
self.bar = Gtk.LevelBar.new_for_interval(0, 100)
self.bar.set_value(self.percent)
self.bar.set_size_request(96, -1)
self.bar.set_valign(Gtk.Align.CENTER)
box.append(icon)
box.append(self.bar)
box.append(self.label)
window.set_child(box)
signal.signal(signal.SIGUSR1, self.handle_refresh)
window.present()
self.timeout_id = GLib.timeout_add(VISIBLE_MS, self.quit)
def main():
percent = read_percent()
if signal_existing(percent):
return 0
with open(PID_FILE, "w", encoding="utf-8") as handle:
handle.write(str(os.getpid()))
app = BrightnessOsd(percent)
return app.run([])
if __name__ == "__main__":
raise SystemExit(main())

65
bin/brightness-sync-hg342pcb Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIG="${BRIGHTNESS_AUTOMATION_CONFIG:-$HOME/.config/brightness-automation/env}"
if [[ -f "$CONFIG" ]]; then
# shellcheck disable=SC1090
source "$CONFIG"
fi
MODEL="${BRIGHTNESS_DDCUTIL_MODEL:-HG342PCB}"
DISPLAY_NUM="${BRIGHTNESS_DDCUTIL_DISPLAY:-1}"
BUS_NUM="${BRIGHTNESS_DDCUTIL_BUS:-16}"
BACKLIGHT_PATH="${BRIGHTNESS_SYNC_BACKLIGHT:-/sys/class/backlight/intel_backlight}"
INTERVAL="${BRIGHTNESS_SYNC_INTERVAL:-1}"
MIN_PERCENT="${BRIGHTNESS_SYNC_MIN_PERCENT:-1}"
brightness_percent() {
local current max percent
current="$(<"${BACKLIGHT_PATH}/brightness")"
max="$(<"${BACKLIGHT_PATH}/max_brightness")"
percent="$(( (current * 100 + max / 2) / max ))"
if (( percent < MIN_PERCENT )); then
percent="$MIN_PERCENT"
elif (( percent > 100 )); then
percent=100
fi
printf '%s\n' "$percent"
}
run_ddcutil() {
if sudo -n ddcutil --bus "$BUS_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --bus "$BUS_NUM" "$@"
return
fi
if sudo -n ddcutil --display "$DISPLAY_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --display "$DISPLAY_NUM" "$@"
return
fi
if sudo -n ddcutil --model "$MODEL" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --model "$MODEL" "$@"
return
fi
echo "External ${MODEL} was not reachable via sudo -n ddcutil." >&2
return 1
}
last_percent=""
while true; do
percent="$(brightness_percent)"
if [[ "$percent" != "$last_percent" ]]; then
if run_ddcutil setvcp 10 "$percent" >/dev/null; then
last_percent="$percent"
fi
fi
sleep "$INTERVAL"
done

14
bin/brightness-up-all Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIG="${BRIGHTNESS_AUTOMATION_CONFIG:-$HOME/.config/brightness-automation/env}"
if [[ -f "$CONFIG" ]]; then
# shellcheck disable=SC1090
source "$CONFIG"
fi
BACKLIGHT_DEVICE="${BRIGHTNESS_BACKLIGHT_DEVICE:-intel_backlight}"
STEP="${BRIGHTNESS_STEP:-10}"
brightnessctl -q -d "$BACKLIGHT_DEVICE" set "+${STEP}%"
"$HOME/.local/bin/brightness-osd" || true

View File

@@ -0,0 +1,522 @@
# HP Firefly G11 unter Fedora finaler sauberer Stand
## Ziel
Saubere gemeinsame Helligkeitssteuerung für:
- internes Display über `intel_backlight`
- externen Monitor über `ddcutil`
sowie automatische Helligkeitsregelung über den vorhandenen Ambient-Light-Sensor mit `wluma`.
Finale Logik:
- **internes Display ist Master**
- `wluma` regelt nur noch `intel_backlight`
- der externe Monitor wird automatisch auf denselben Prozentwert nachgezogen
- die manuellen Helligkeitstasten zeigen ein GNOME/Adwaita-passendes OSD
- dadurch können intern und extern nicht mehr durch zwei unabhängige `wluma`-Regelungen auseinanderlaufen
---
## Finaler Ist-Zustand
### Was aktuell funktioniert
- Das interne Display hängt als normales Backlight unter:
- `intel_backlight`
- Der externe Monitor `HG342PCB` am Thunderbolt-Dock ist per `ddcutil` erreichbar.
- Die gemeinsamen Button-Skripte regeln jetzt:
- intern per `brightnessctl`
- zeigen ein lokales GTK/libadwaita-OSD
- extern folgt automatisch über `external-brightness-sync.service`
- `wluma.service` läuft als User-Service nach dem Login.
- `wluma` nutzt nur noch das interne `intel_backlight`.
- `external-brightness-sync.service` spiegelt den internen Prozentwert automatisch auf den externen Monitor per `ddcutil`.
### Aktueller Nachweis
```bash
systemctl --user status wluma.service
systemctl --user status external-brightness-sync.service
brightnessctl -l
sudo ddcutil detect
sudo ddcutil getvcp 10
```
Erwarteter sauberer Zustand:
in `brightnessctl -l`:
- `intel_backlight`
und in `sudo ddcutil detect`:
- `Display 1`
- `I2C bus: /dev/i2c-16`
- `Model: HG342PCB`
---
## Warum dieser Stand der richtige ist
### 1. Internes und externes Display laufen jetzt über zwei stabile Wege
Das interne Panel läuft weiter über `intel_backlight`.
Der externe Monitor wird über das Thunderbolt-Dock direkt per `ddcutil` angesprochen.
Damit ist die gemeinsame Steuerung wieder stabil, auch wenn intern und extern technisch über unterschiedliche Wege laufen.
Wichtig:
Es gibt nur noch eine automatische Regelinstanz. `wluma` regelt das interne Panel, der Sync-Dienst übernimmt die Spiegelung nach extern.
### 2. Das Thunderbolt-Dock reicht DDC/CI sauber durch, `ddcci_backlight` war hier aber nicht der richtige Weg
Mit dem neuen Dock ist der Monitor nachweislich über `ddcutil` auf `card1-DP-5` / `/dev/i2c-16` erreichbar.
Die frühere `ddcci3`-Lösung über `/sys/class/backlight` war für dieses Setup dagegen nicht stabil nutzbar.
Der saubere Endstand ist deshalb:
- **intern: `brightnessctl`**
- **extern: `ddcutil`**
- **kein aggressives Hotplug-Refresh über `ddcci`**
### 3. GNOME Auto Brightness war nicht funktionsfähig
Der Ambient-Light-Sensor funktioniert eindeutig, aber GNOME hat die Helligkeit sichtbar nicht geregelt.
Deshalb ist `wluma` jetzt die saubere automatische Lösung auf Basis des Sensors.
---
## Aktive Komponenten
### Als User aktiv
#### wluma-Service
```text
~/.config/systemd/user/wluma.service
```
#### wluma-Konfiguration
```text
~/.config/wluma/config.toml
```
#### Gemeinsame Helligkeitsskripte
```text
~/.local/bin/brightness-osd
~/.local/bin/brightness-ddcutil-hg342pcb
~/.local/bin/brightness-down-all
~/.local/bin/brightness-up-all
```
#### Externer Sync-Dienst
```text
~/.local/bin/brightness-sync-hg342pcb
~/.config/systemd/user/external-brightness-sync.service
```
---
## Finaler Inhalt der wichtigen Dateien
### 1. `~/.local/bin/brightness-ddcutil-hg342pcb`
```bash
#!/usr/bin/env bash
set -euo pipefail
MODEL="${BRIGHTNESS_DDCUTIL_MODEL:-HG342PCB}"
STEP="${BRIGHTNESS_DDCUTIL_STEP:-10}"
DISPLAY_NUM="${BRIGHTNESS_DDCUTIL_DISPLAY:-1}"
BUS_NUM="${BRIGHTNESS_DDCUTIL_BUS:-16}"
if [ "$#" -ne 1 ]; then
echo "Usage: $(basename "$0") up|down" >&2
exit 2
fi
case "$1" in
up) delta="+" ;;
down) delta="-" ;;
*) echo "Usage: $(basename "$0") up|down" >&2; exit 2 ;;
esac
run_ddcutil() {
if sudo -n ddcutil --bus "$BUS_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --bus "$BUS_NUM" "$@"
return
fi
if sudo -n ddcutil --display "$DISPLAY_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --display "$DISPLAY_NUM" "$@"
return
fi
if sudo -n ddcutil --model "$MODEL" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --model "$MODEL" "$@"
return
fi
if ddcutil --bus "$BUS_NUM" getvcp 10 >/dev/null 2>&1; then
ddcutil --bus "$BUS_NUM" "$@"
return
fi
if ddcutil --display "$DISPLAY_NUM" getvcp 10 >/dev/null 2>&1; then
ddcutil --display "$DISPLAY_NUM" "$@"
return
fi
if ddcutil --model "$MODEL" getvcp 10 >/dev/null 2>&1; then
ddcutil --model "$MODEL" "$@"
return
fi
echo "External HG342PCB was not reachable via ddcutil on bus ${BUS_NUM}." >&2
exit 1
}
run_ddcutil setvcp 10 "$delta" "$STEP" >/dev/null
```
### 2. `~/.local/bin/brightness-down-all`
```bash
#!/usr/bin/env bash
set -euo pipefail
brightnessctl -q -d intel_backlight set 10%-
/home/jan/.local/bin/brightness-osd || true
```
### 3. `~/.local/bin/brightness-up-all`
```bash
#!/usr/bin/env bash
set -euo pipefail
brightnessctl -q -d intel_backlight set +10%
/home/jan/.local/bin/brightness-osd || true
```
### 4. `~/.local/bin/brightness-osd`
```python
#!/usr/bin/env python3
```
Dieses Skript zeigt ein kleines GTK4/libadwaita-OSD am oberen Bildschirmrand mit:
- Adwaita-Icon `display-brightness-symbolic`
- Prozentwert der internen Master-Helligkeit
- Fortschrittsbalken
Das OSD ist einzeilig und kompakt. Wenn mehrere Tastendrücke schnell hintereinander kommen, wird der laufende OSD-Prozess aktualisiert und der Ausblend-Timer verlängert; dadurch verschwindet es nicht mehr zwischen zwei Helligkeitsänderungen.
Für echte Positionierung am oberen Bildschirmrand unter GNOME Wayland wird dieses Fedora-Paket benötigt:
```bash
sudo dnf install -y gtk4-layer-shell
```
Ohne `gtk4-layer-shell` darf GNOME/Wayland ein normales GTK-Fenster nicht zuverlässig oben positionieren; dann fällt das Skript auf die normale GTK-Fensterposition zurück.
### 5. `~/.config/wluma/config.toml`
```toml
[als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" }
[[output.backlight]]
name = "eDP-1"
path = "/sys/class/backlight/intel_backlight"
capturer = "none"
```
### 6. `~/.local/bin/brightness-sync-hg342pcb`
```bash
#!/usr/bin/env bash
set -euo pipefail
MODEL="${BRIGHTNESS_DDCUTIL_MODEL:-HG342PCB}"
DISPLAY_NUM="${BRIGHTNESS_DDCUTIL_DISPLAY:-1}"
BUS_NUM="${BRIGHTNESS_DDCUTIL_BUS:-16}"
BACKLIGHT_PATH="${BRIGHTNESS_SYNC_BACKLIGHT:-/sys/class/backlight/intel_backlight}"
INTERVAL="${BRIGHTNESS_SYNC_INTERVAL:-1}"
MIN_PERCENT="${BRIGHTNESS_SYNC_MIN_PERCENT:-1}"
brightness_percent() {
local current max percent
current="$(<"${BACKLIGHT_PATH}/brightness")"
max="$(<"${BACKLIGHT_PATH}/max_brightness")"
percent="$(( (current * 100 + max / 2) / max ))"
if (( percent < MIN_PERCENT )); then
percent="$MIN_PERCENT"
elif (( percent > 100 )); then
percent=100
fi
printf '%s\n' "$percent"
}
run_ddcutil() {
if sudo -n ddcutil --bus "$BUS_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --bus "$BUS_NUM" "$@"
return
fi
if sudo -n ddcutil --display "$DISPLAY_NUM" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --display "$DISPLAY_NUM" "$@"
return
fi
if sudo -n ddcutil --model "$MODEL" getvcp 10 >/dev/null 2>&1; then
sudo -n ddcutil --model "$MODEL" "$@"
return
fi
echo "External ${MODEL} was not reachable via sudo -n ddcutil." >&2
return 1
}
last_percent=""
while true; do
percent="$(brightness_percent)"
if [[ "$percent" != "$last_percent" ]]; then
if run_ddcutil setvcp 10 "$percent" >/dev/null; then
last_percent="$percent"
fi
fi
sleep "$INTERVAL"
done
```
### 7. `~/.config/systemd/user/wluma.service`
```ini
[Unit]
Description=Adjust screen brightness automatically with wluma
PartOf=graphical-session.target
After=graphical-session.target
[Service]
ExecStart=/usr/bin/wluma
Restart=always
PrivateNetwork=true
PrivateMounts=false
[Install]
WantedBy=graphical-session.target
```
### 8. `~/.config/systemd/user/external-brightness-sync.service`
```ini
[Unit]
Description=Mirror internal panel brightness to external HG342PCB
PartOf=graphical-session.target
After=graphical-session.target wluma.service
[Service]
ExecStart=%h/.local/bin/brightness-sync-hg342pcb
Restart=always
RestartSec=2
[Install]
WantedBy=graphical-session.target
```
---
## Dienste finaler Soll-Zustand
### Aktiv
```bash
systemctl --user status wluma.service
systemctl --user status external-brightness-sync.service
```
Erwartet:
- `wluma.service`: `active (running)`
- `external-brightness-sync.service`: `active (running)`
### Nicht mehr Teil der Lösung
Diese Komponenten gehören **nicht** mehr zur finalen Lösung:
- `ddcci-attach.service`
- `/usr/local/sbin/ddcci-attach-hg342pcb`
- `ddcci-refresh.service`
- `/usr/local/sbin/ddcci-refresh`
- `/etc/udev/rules.d/99-ddcci-hotplug.rules`
Der Grund:
Mit dem Thunderbolt-Dock funktioniert die externe Steuerung sauber über `ddcutil`; die `ddcci`-Backlight-Variante wird dafür nicht mehr benötigt.
---
## Cleanup alter / falscher Komponenten
### Falls noch vorhanden: `ddcci-refresh` entfernen
```bash
sudo systemctl disable --now ddcci-refresh.service 2>/dev/null || true
sudo rm -f /etc/systemd/system/ddcci-refresh.service
sudo rm -f /usr/local/sbin/ddcci-refresh
sudo rm -f /etc/udev/rules.d/99-ddcci-hotplug.rules
sudo systemctl daemon-reload
sudo udevadm control --reload-rules
```
### Alte `ddcci`-Komponenten können entfernt werden
Die ältere `ddcci`-/`ddcci_backlight`-Variante ist **nicht mehr der finale Stand**.
Final ist jetzt die Hybrid-Variante über:
- `intel_backlight` für intern
- `ddcutil` für extern
---
## Bedienung im Alltag
### Manuell beide Displays heller / dunkler
```bash
/home/jan/.local/bin/brightness-down-all
/home/jan/.local/bin/brightness-up-all
```
### Reaktionsverhalten
Das interne Display reagiert praktisch sofort.
Der externe Monitor reagiert typischerweise leicht verzögert, oft ungefähr nach einer Sekunde.
Das ist bei `ddcutil` normal, weil zuerst DDC/CI-Kommunikation mit dem Monitor über das Dock stattfindet.
Etwas schneller könnte es eventuell mit `--noverify` werden, das ist aber weniger robust und daher aktuell nicht Teil des finalen Stands.
### GNOME-Shortcuts
Wenn dafür Benutzerkürzel in GNOME gesetzt werden, immer den **vollen Pfad** verwenden:
```text
/home/jan/.local/bin/brightness-down-all
/home/jan/.local/bin/brightness-up-all
```
Nicht `~/.local/bin/...`, weil das im GNOME-Shortcut-Feld unzuverlässig sein kann.
---
## wluma und externer Sync aktueller realer Zustand
### Wichtig
`wluma` läuft automatisch nach dem Login und steuert nur das interne Display:
```bash
systemctl --user status wluma.service
```
Der externe Monitor wird durch diesen User-Service nachgezogen:
```bash
systemctl --user status external-brightness-sync.service
```
### Verhalten
`wluma` nutzt weiter den adaptiven Lernmodus, aber nur auf `intel_backlight`.
Der externe Sync-Dienst liest einmal pro Sekunde den internen Prozentwert aus:
```text
/sys/class/backlight/intel_backlight/brightness
/sys/class/backlight/intel_backlight/max_brightness
```
und setzt denselben Prozentwert extern per:
```bash
sudo -n ddcutil --bus 16 setvcp 10 <prozent>
```
Das bedeutet:
- `wluma` entscheidet die Helligkeit nur einmal, am internen Panel
- extern folgt automatisch
- manuelle Änderungen über die gemeinsamen Skripte bleiben ebenfalls synchron
- bei einem Replug des externen Monitors kann ein `systemctl --user restart external-brightness-sync.service` sinnvoll sein
### Praktische Folge
Wenn du möchtest, dass `wluma` besser wird, dann im Alltag in verschiedenen Lichtsituationen die Helligkeit mit deinen gemeinsamen Skripten nachregeln. Daraus lernt `wluma` die interne Master-Helligkeit; extern wird automatisch mitgezogen.
---
## Sensorstatus
Der Ambient-Light-Sensor funktioniert sauber.
Nachgewiesen durch:
- `monitor-sensor` zeigt deutliche Lux-Änderungen
- `gsettings get org.gnome.settings-daemon.plugins.power ambient-enabled` war `true`
- `iio-sensor-proxy` läuft
Das Problem lag daher **nicht** am Sensor, sondern an GNOME Auto Brightness.
---
## Was bei Problemen sinnvoll ist
### 1. Prüfen, ob intern und extern erreichbar sind
```bash
ls -l /sys/class/backlight
brightnessctl -l
sudo ddcutil --bus 16 getvcp 10
```
### 2. Dienste prüfen
```bash
systemctl --user status wluma.service
systemctl --user status external-brightness-sync.service
```
### 3. Dienste neu starten
```bash
systemctl --user restart wluma.service
systemctl --user restart external-brightness-sync.service
```
---
## Was aktuell bewusst **nicht** gemacht wird
- kein aggressives `delete_device` / `new_device` im laufenden Betrieb
- keine Hotplug-Automatik mit `ddcci-refresh`
- keine udev-Regel, die `ddcci` ständig neu erzeugt
- kein Verlassen auf GNOME Auto Brightness
- keine unabhängige `wluma`-Regelung für den externen Monitor
Der Grund ist reine Stabilität:
Interner Master plus externer DDC/CI-Sync vermeidet Drift zwischen den Displays.
---
## Kurzfazit
### Final sauber gelöst
- intern läuft über `intel_backlight`
- extern läuft über `ddcutil` auf Bus 16
- `wluma` läuft automatisch nach dem Login und steuert nur intern
- `external-brightness-sync.service` zieht den externen Monitor automatisch nach
- der ALS-Sensor funktioniert und wird von `wluma` genutzt
### Final bewusst verworfen
- externes Display direkt von `wluma` regeln lassen
- `ddcci-refresh`
- Hotplug-Delete/Recreate-Automatik
- GNOME Auto Brightness als eigentliche Endlösung
---
## Schnellbefehle
### Status
```bash
systemctl --user status wluma.service
systemctl --user status external-brightness-sync.service
ls /sys/class/backlight
brightnessctl -l
sudo ddcutil --bus 16 getvcp 10
```
### Manuelle gemeinsame Steuerung
```bash
/home/jan/.local/bin/brightness-down-all
/home/jan/.local/bin/brightness-up-all
```
### Dienste neu starten
```bash
systemctl --user restart wluma.service
systemctl --user restart external-brightness-sync.service
```

59
install.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKLIGHT_DEVICE="${BRIGHTNESS_BACKLIGHT_DEVICE:-intel_backlight}"
BACKLIGHT_PATH="${BRIGHTNESS_SYNC_BACKLIGHT:-/sys/class/backlight/${BACKLIGHT_DEVICE}}"
DDC_MODEL="${BRIGHTNESS_DDCUTIL_MODEL:-HG342PCB}"
DDC_DISPLAY="${BRIGHTNESS_DDCUTIL_DISPLAY:-1}"
DDC_BUS="${BRIGHTNESS_DDCUTIL_BUS:-16}"
STEP="${BRIGHTNESS_STEP:-10}"
INTERVAL="${BRIGHTNESS_SYNC_INTERVAL:-1}"
MIN_PERCENT="${BRIGHTNESS_SYNC_MIN_PERCENT:-1}"
install -d "$HOME/.local/bin"
install -d "$HOME/.config/systemd/user"
install -d "$HOME/.config/brightness-automation"
install -m 0755 "$ROOT/bin/brightness-osd" "$HOME/.local/bin/brightness-osd"
install -m 0755 "$ROOT/bin/brightness-up-all" "$HOME/.local/bin/brightness-up-all"
install -m 0755 "$ROOT/bin/brightness-down-all" "$HOME/.local/bin/brightness-down-all"
install -m 0755 "$ROOT/bin/brightness-sync-hg342pcb" "$HOME/.local/bin/brightness-sync-hg342pcb"
install -m 0644 "$ROOT/systemd/user/external-brightness-sync.service" "$HOME/.config/systemd/user/external-brightness-sync.service"
if [[ ! -f "$HOME/.config/brightness-automation/env" ]]; then
cat >"$HOME/.config/brightness-automation/env" <<EOF
BRIGHTNESS_BACKLIGHT_DEVICE="$BACKLIGHT_DEVICE"
BRIGHTNESS_SYNC_BACKLIGHT="$BACKLIGHT_PATH"
BRIGHTNESS_STEP="$STEP"
BRIGHTNESS_DDCUTIL_MODEL="$DDC_MODEL"
BRIGHTNESS_DDCUTIL_DISPLAY="$DDC_DISPLAY"
BRIGHTNESS_DDCUTIL_BUS="$DDC_BUS"
BRIGHTNESS_SYNC_INTERVAL="$INTERVAL"
BRIGHTNESS_SYNC_MIN_PERCENT="$MIN_PERCENT"
BRIGHTNESS_OSD_VISIBLE_MS="1400"
EOF
fi
if [[ "${INSTALL_WLUMA_CONFIG:-0}" == "1" ]]; then
install -d "$HOME/.config/wluma"
install -m 0644 "$ROOT/wluma/config.toml" "$HOME/.config/wluma/config.toml"
fi
systemctl --user daemon-reload
systemctl --user enable --now external-brightness-sync.service
cat <<EOF
Installed.
Shortcuts:
$HOME/.local/bin/brightness-down-all
$HOME/.local/bin/brightness-up-all
Config:
$HOME/.config/brightness-automation/env
Status:
systemctl --user status external-brightness-sync.service
EOF

View File

@@ -0,0 +1,13 @@
[Unit]
Description=Mirror internal panel brightness to external HG342PCB
PartOf=graphical-session.target
After=graphical-session.target wluma.service
[Service]
EnvironmentFile=-%h/.config/brightness-automation/env
ExecStart=%h/.local/bin/brightness-sync-hg342pcb
Restart=always
RestartSec=2
[Install]
WantedBy=graphical-session.target

17
uninstall.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
systemctl --user disable --now external-brightness-sync.service 2>/dev/null || true
rm -f "$HOME/.config/systemd/user/external-brightness-sync.service"
rm -f "$HOME/.local/bin/brightness-osd"
rm -f "$HOME/.local/bin/brightness-up-all"
rm -f "$HOME/.local/bin/brightness-down-all"
rm -f "$HOME/.local/bin/brightness-sync-hg342pcb"
systemctl --user daemon-reload
cat <<EOF
Uninstalled binaries and user service.
Kept user config:
$HOME/.config/brightness-automation/env
EOF

8
wluma/config.toml Normal file
View File

@@ -0,0 +1,8 @@
[als.iio]
path = "/sys/bus/iio/devices"
thresholds = { 0 = "night", 20 = "dark", 80 = "dim", 250 = "normal", 500 = "bright", 800 = "outdoors" }
[[output.backlight]]
name = "eDP-1"
path = "/sys/class/backlight/intel_backlight"
capturer = "none"