From e828a8c274da5003cb259529cd1bcbb8c88d852b Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Wed, 9 Sep 2026 16:56:30 +0900 Subject: [PATCH 1/7] feat(redshift): dim backlight via acpilight/ddcci on day/night cycle Pulls in acpilight (deps/acpilight) as a backward-compatible xbacklight replacement driving brightness through /sys/class/backlight instead of X11 RandR, and wires it to redshift via a period-changed hook so displays dim on the day/night cycle and restore full brightness when redshift is disabled. Works uniformly on a native ACPI panel or, once ddcci-driver-linux is loaded, a DDC/CI-controlled external monitor. --- .gitmodules | 4 ++ README.md | 32 ++++++++++++++ deps/acpilight | 1 + home/.config/redshift/hooks/backlight | 60 +++++++++++++++++++++++++++ home/.local/bin/xbacklight | 1 + mk/build.mk | 13 ++++++ mk/variables.mk | 1 + 7 files changed, 112 insertions(+) create mode 160000 deps/acpilight create mode 100755 home/.config/redshift/hooks/backlight create mode 120000 home/.local/bin/xbacklight diff --git a/.gitmodules b/.gitmodules index 72cbd051e4..1801808079 100644 --- a/.gitmodules +++ b/.gitmodules @@ -646,3 +646,7 @@ [submodule "home/.local/share/nvim/site/pack/default/opt/snacks.nvim"] path = home/.local/share/nvim/site/pack/default/opt/snacks.nvim url = https://github.com/folke/snacks.nvim.git +[submodule "deps/acpilight"] + path = deps/acpilight + url = https://gitlab.com/wavexx/acpilight + ignore = untracked diff --git a/README.md b/README.md index c0c4dde854..44fe2c3876 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,38 @@ On Slackware systems, the following additional dependency is optional: - `sun` +### Backlight control (acpilight) + +[`acpilight`](https://gitlab.com/wavexx/acpilight) (`deps/acpilight`, built like `xwinwrap`) provides a backward-compatible `xbacklight` replacement that controls brightness via `/sys/class/backlight` instead of X11's RandR `Backlight` property, so it works for laptop panels the RandR property was never wired up for, and it's driven by a redshift hook ([`home/.config/redshift/hooks/backlight`](home/.config/redshift/hooks/backlight)) that dims the display along with the day/night cycle. + +Controlling an external/desktop monitor's brightness this way additionally requires the **`ddcci-driver-linux`** kernel module, which exposes DDC/CI-controlled monitors under `/sys/class/backlight/ddcci*` (the same interface as a native panel), so the same hook script works unmodified on both a laptop (native panel) and a desktop (DDC/CI monitor): + +- **Arch Linux**: AUR package `ddcci-driver-linux-dkms` (or `-git` for newer kernels) +- **Slackware**: [`ddcci-driver-linux`](https://slackbuilds.org/repository/15.0/system/ddcci-driver-linux/) on SlackBuilds.org + +Two things the build here deliberately does *not* automate, since they require root and are one-time system setup rather than per-user dotfiles (udev rules in particular are inherently system-wide -- there's no per-user rules directory or per-user udev daemon, so this can't be moved into the unprivileged `home`/stow flow no matter what): + +- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on): + ```sh + /sbin/modprobe ddcci-backlight + + for conn in /sys/class/drm/card0-*/; do + [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue + ddc_link="$conn/ddc" + [ -e "$ddc_link" ] || continue + ddc_bus=$(basename "$(readlink -f "$ddc_link")") + bus_num=${ddc_bus#i2c-} + [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue + echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null + done + ``` +- **Installing acpilight's udev rule** so `xbacklight` can write to `/sys/class/backlight` without root: + ```sh + sudo install -vCDt /etc/udev/rules.d deps/acpilight/90-backlight.rules + sudo udevadm trigger -s backlight -c add + ``` + (also requires your user to be in the `video` group) + ## Installation To build all required components and install the configuration files into the home directory, run the following commands: diff --git a/deps/acpilight b/deps/acpilight new file mode 160000 index 0000000000..3f9c62553f --- /dev/null +++ b/deps/acpilight @@ -0,0 +1 @@ +Subproject commit 3f9c62553f4c34c5e21439ae52f6a88155dbcc3c diff --git a/home/.config/redshift/hooks/backlight b/home/.config/redshift/hooks/backlight new file mode 100755 index 0000000000..c7beea945d --- /dev/null +++ b/home/.config/redshift/hooks/backlight @@ -0,0 +1,60 @@ +#!/bin/sh +# redshift hook: dim the backlight along with the day/night cycle, via acpilight's +# xbacklight (see deps/acpilight, README). Portable across machines: acpilight +# enumerates both native ACPI panels (/sys/class/backlight/) and, once the +# ddcci-driver-linux kernel module is loaded, DDC/CI-controlled external monitors +# (/sys/class/backlight/ddcci*) under the same interface. +# +# Called by redshift as: backlight period-changed +set -u + +brightness_day=70 +brightness_transition=50 +brightness_night=30 +# redshift sets period to "none" (and fires this same period-changed hook) +# when disabled via SIGUSR1/the redshift-gtk tray toggle, alongside resetting +# color settings to neutral -- restore full brightness to match. +brightness_default=100 + +have() { command -v "$1" >/dev/null 2>&1; } + +have xbacklight || exit 0 + +case "${3:-}" in + daytime) brightness=$brightness_day ;; + transition) brightness=$brightness_transition ;; + night) brightness=$brightness_night ;; + none) brightness=$brightness_default ;; + *) exit 0 ;; +esac + +# Only look at actual display backlights, never acpilight's /sys/class/leds +# fallback (keyboard/capslock/etc. LEDs) -- on a machine with no backlight +# device at all, xbacklight's "first controller found" default would otherwise +# silently target one of those instead of a screen and fail or do the wrong +# thing. No backlight device present (e.g. ddcci-driver-linux not loaded +# yet): nothing to do. +backlight_ctrls=$(ls /sys/class/backlight 2>/dev/null) || exit 0 +[ -n "$backlight_ctrls" ] || exit 0 + +# Prefer a ddcci-exposed external monitor (desktop) when present; on machines +# with no ddcci device (e.g. a laptop's native panel only), fall through to +# xbacklight's own default controller, which is safe here since we already +# confirmed at least one /sys/class/backlight entry exists. Matched via +# `case`, not `grep ddcci*`, so it never risks shell pathname expansion +# against files in the current directory. +ctrl= +for name in $backlight_ctrls; do + case "$name" in + ddcci*) + ctrl=$name + break + ;; + esac +done + +if [ -n "$ctrl" ]; then + xbacklight -ctrl "$ctrl" -set "$brightness" +else + xbacklight -set "$brightness" +fi diff --git a/home/.local/bin/xbacklight b/home/.local/bin/xbacklight new file mode 120000 index 0000000000..c3c889e711 --- /dev/null +++ b/home/.local/bin/xbacklight @@ -0,0 +1 @@ +../../../deps/acpilight/xbacklight \ No newline at end of file diff --git a/mk/build.mk b/mk/build.mk index cc64a487ca..3369f8d0bb 100644 --- a/mk/build.mk +++ b/mk/build.mk @@ -45,6 +45,18 @@ $(xwinwrap_target): $(xwinwrap_head_file) $(MAKE) -C $(XWINWRAP_ROOT) xwinwrap: $(xwinwrap_target) +# acpilight's xbacklight is a plain script (no build step); this just ensures the +# submodule is checked out and the script kept executable. The udev rule +# (90-backlight.rules), which grants group `video` write access to +# /sys/class/backlight without setuid/sudo, is a one-time system-level install +# and is NOT handled here -- see README. +.PHONY: acpilight +acpilight_target = $(ACPILIGHT_ROOT)/xbacklight +$(eval $(call git_submodule,acpilight,$(ACPILIGHT_ROOT))) +$(acpilight_target): $(acpilight_head_file) + chmod +x $(acpilight_target) +acpilight: $(acpilight_target) + i3status-config-template := home/.config/i3status/config.template i3status-config := home/.config/i3status/config amd-cpu-temperature-path := /sys/class/hwmon/hwmon2/temp1_input @@ -292,6 +304,7 @@ logrotate: $(logrotate_target) TARGETS = \ mpv-mpris \ xwinwrap \ + acpilight \ ccls \ fzf \ fzy \ diff --git a/mk/variables.mk b/mk/variables.mk index d648befb4f..eed1134cc9 100644 --- a/mk/variables.mk +++ b/mk/variables.mk @@ -21,6 +21,7 @@ FONTS = home/.local/share/fonts MPV_MPRIS_ROOT = $(DEPS)/mpv-mpris XWINWRAP_ROOT = $(DEPS)/xwinwrap +ACPILIGHT_ROOT = $(DEPS)/acpilight CCLS_ROOT = $(DEPS)/ccls ATUIN_ROOT = $(DEPS)/atuin QMK_CLI_ROOT = $(DEPS)/qmk_cli From f1b870a04c9e13b1ac7871f9ea75796ab2999054 Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Wed, 9 Sep 2026 17:06:01 +0900 Subject: [PATCH 2/7] feat(redshift): add daytime gamma warmth and screen-wide brightness brightness-day (RandR's Brightness property, distinct from Gamma) and temp-day apply uniformly across every monitor via software gamma scaling, unlike the hardware-backlight hook which only reaches DDC/CI-capable displays -- so this covers the two LGs that ddcci can't control. Backed off the hook's own brightness_day to 100 now that daytime dimming is handled here, avoiding the two effects compounding on the Philips. --- home/.config/redshift/hooks/backlight | 2 +- home/.config/redshift/redshift.conf | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 home/.config/redshift/redshift.conf diff --git a/home/.config/redshift/hooks/backlight b/home/.config/redshift/hooks/backlight index c7beea945d..257319e805 100755 --- a/home/.config/redshift/hooks/backlight +++ b/home/.config/redshift/hooks/backlight @@ -8,7 +8,7 @@ # Called by redshift as: backlight period-changed set -u -brightness_day=70 +brightness_day=100 brightness_transition=50 brightness_night=30 # redshift sets period to "none" (and fires this same period-changed hook) diff --git a/home/.config/redshift/redshift.conf b/home/.config/redshift/redshift.conf new file mode 100644 index 0000000000..01c0d7361d --- /dev/null +++ b/home/.config/redshift/redshift.conf @@ -0,0 +1,9 @@ +[redshift] +; RandR's Brightness property (distinct from Gamma/color temperature below), +; separate from the hardware-backlight dimming in hooks/backlight -- this +; applies uniformly to every monitor, including ones with no DDC/CI backlight +; control. +brightness-day=0.7 + +; slightly warm daytime color temperature (default neutral is 6500K) +temp-day=5500 From b967bf796e1714240faa7cfc8111a995dc157d6f Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Wed, 9 Sep 2026 19:29:18 +0900 Subject: [PATCH 3/7] fix(redshift): set brightness on every ddcci monitor, not just the first A desktop can have multiple independently-registered ddcci displays (discovered while debugging the two LG monitors on this box). Loop over every ddcci* backlight device instead of stopping at the first match found under /sys/class/backlight. Co-Authored-By: Claude Sonnet 5 --- README.md | 73 +++++++++++++++------------ home/.config/redshift/hooks/backlight | 17 +++---- 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 44fe2c3876..30c5b01acc 100644 --- a/README.md +++ b/README.md @@ -61,37 +61,6 @@ In addition, the following third-party system dependencies are assumed to be pre On Slackware systems, the following additional dependency is optional: - `sun` - -### Backlight control (acpilight) - -[`acpilight`](https://gitlab.com/wavexx/acpilight) (`deps/acpilight`, built like `xwinwrap`) provides a backward-compatible `xbacklight` replacement that controls brightness via `/sys/class/backlight` instead of X11's RandR `Backlight` property, so it works for laptop panels the RandR property was never wired up for, and it's driven by a redshift hook ([`home/.config/redshift/hooks/backlight`](home/.config/redshift/hooks/backlight)) that dims the display along with the day/night cycle. - -Controlling an external/desktop monitor's brightness this way additionally requires the **`ddcci-driver-linux`** kernel module, which exposes DDC/CI-controlled monitors under `/sys/class/backlight/ddcci*` (the same interface as a native panel), so the same hook script works unmodified on both a laptop (native panel) and a desktop (DDC/CI monitor): - -- **Arch Linux**: AUR package `ddcci-driver-linux-dkms` (or `-git` for newer kernels) -- **Slackware**: [`ddcci-driver-linux`](https://slackbuilds.org/repository/15.0/system/ddcci-driver-linux/) on SlackBuilds.org - -Two things the build here deliberately does *not* automate, since they require root and are one-time system setup rather than per-user dotfiles (udev rules in particular are inherently system-wide -- there's no per-user rules directory or per-user udev daemon, so this can't be moved into the unprivileged `home`/stow flow no matter what): - -- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on): - ```sh - /sbin/modprobe ddcci-backlight - - for conn in /sys/class/drm/card0-*/; do - [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue - ddc_link="$conn/ddc" - [ -e "$ddc_link" ] || continue - ddc_bus=$(basename "$(readlink -f "$ddc_link")") - bus_num=${ddc_bus#i2c-} - [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue - echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null - done - ``` -- **Installing acpilight's udev rule** so `xbacklight` can write to `/sys/class/backlight` without root: - ```sh - sudo install -vCDt /etc/udev/rules.d deps/acpilight/90-backlight.rules - sudo udevadm trigger -s backlight -c add - ``` (also requires your user to be in the `video` group) ## Installation @@ -124,3 +93,45 @@ Custom uBlock Origin filters ("My filters") are declared in [`home/.mozilla/nati Notes: - A full browser restart is required to pick up changes to this file. - `toOverwrite` fully replaces the "My filters" pane content on every launch, so any filters added manually through the uBlock UI will be reverted on restart — treat this file as the source of truth and edit it instead. + +### Backlight control (acpilight) + +[`acpilight`](https://gitlab.com/wavexx/acpilight) (`deps/acpilight`, built like `xwinwrap`) provides a backward-compatible `xbacklight` replacement that controls brightness via `/sys/class/backlight` instead of X11's RandR `Backlight` property, so it works for laptop panels the RandR property was never wired up for, and it's driven by a redshift hook ([`home/.config/redshift/hooks/backlight`](home/.config/redshift/hooks/backlight)) that dims the display along with the day/night cycle. + +Controlling an external/desktop monitor's brightness this way additionally requires the **`ddcci-driver-linux`** kernel module, which exposes DDC/CI-controlled monitors under `/sys/class/backlight/ddcci*` (the same interface as a native panel), so the same hook script works unmodified on both a laptop (native panel) and a desktop (DDC/CI monitor): + +- **Arch Linux**: AUR package `ddcci-driver-linux-dkms` (or `-git` for newer kernels) +- **Slackware**: [`ddcci-driver-linux`](https://slackbuilds.org/repository/15.0/system/ddcci-driver-linux/) on SlackBuilds.org + +Two things the build here deliberately does *not* automate, since they require root and are one-time system setup rather than per-user dotfiles (udev rules in particular are inherently system-wide -- there's no per-user rules directory or per-user udev daemon, so this can't be moved into the unprivileged `home`/stow flow no matter what): + +- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on): + ```sh + /sbin/modprobe ddcci-backlight + + # DisplayPort connectors expose the real DDC/CI-capable channel as a direct + # i2c-N subdirectory of the connector itself (the AUX-channel-backed + # adapter); their `ddc` symlink instead points to a separate, more limited + # legacy adapter that can't complete the full DDC/CI handshake. HDMI + # connectors have no such split -- their `ddc` symlink is the only (and + # correct) adapter. + for conn in /sys/class/drm/card0-*/; do + [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue + direct=$(ls -d "$conn"i2c-* 2>/dev/null | head -1) + if [ -n "$direct" ]; then + ddc_bus=$(basename "$direct") + else + ddc_link="$conn/ddc" + [ -e "$ddc_link" ] || continue + ddc_bus=$(basename "$(readlink -f "$ddc_link")") + fi + bus_num=${ddc_bus#i2c-} + [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue + echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null + done + ``` +- **Installing acpilight's udev rule** so `xbacklight` can write to `/sys/class/backlight` without root: + ```sh + sudo install -vCDt /etc/udev/rules.d deps/acpilight/90-backlight.rules + sudo udevadm trigger -s backlight -c add + ``` diff --git a/home/.config/redshift/hooks/backlight b/home/.config/redshift/hooks/backlight index 257319e805..78209e1d1b 100755 --- a/home/.config/redshift/hooks/backlight +++ b/home/.config/redshift/hooks/backlight @@ -37,24 +37,21 @@ esac backlight_ctrls=$(ls /sys/class/backlight 2>/dev/null) || exit 0 [ -n "$backlight_ctrls" ] || exit 0 -# Prefer a ddcci-exposed external monitor (desktop) when present; on machines -# with no ddcci device (e.g. a laptop's native panel only), fall through to +# Set every ddcci-exposed external monitor (a desktop may have several, +# independently registered per i2c bus -- see README); on machines with no +# ddcci device at all (e.g. a laptop's native panel only), fall through to # xbacklight's own default controller, which is safe here since we already # confirmed at least one /sys/class/backlight entry exists. Matched via # `case`, not `grep ddcci*`, so it never risks shell pathname expansion # against files in the current directory. -ctrl= +found_ddcci=0 for name in $backlight_ctrls; do case "$name" in ddcci*) - ctrl=$name - break + xbacklight -ctrl "$name" -set "$brightness" + found_ddcci=1 ;; esac done -if [ -n "$ctrl" ]; then - xbacklight -ctrl "$ctrl" -set "$brightness" -else - xbacklight -set "$brightness" -fi +[ "$found_ddcci" -eq 1 ] || xbacklight -set "$brightness" From b715457b5dd7005960b8a1ea3de7eba387cd594f Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Wed, 9 Sep 2026 19:34:14 +0900 Subject: [PATCH 4/7] fix(redshift): drop gamma-based brightness-day, now that ddcci covers all monitors brightness-day was compensating for the two LGs having no hardware backlight path at all. Now that both register correctly on their AUX i2c bus, real DDC/CI hardware dimming reaches every monitor, which is strictly better than gamma scaling (no color/banding degradation, actual backlight power reduction). Moved the daytime dim target back into the hook's own brightness_day. Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- home/.config/redshift/hooks/backlight | 2 +- home/.config/redshift/redshift.conf | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 30c5b01acc..9c3afd7278 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ In addition, the following third-party system dependencies are assumed to be pre On Slackware systems, the following additional dependency is optional: - `sun` - (also requires your user to be in the `video` group) ## Installation @@ -135,3 +134,4 @@ Two things the build here deliberately does *not* automate, since they require r sudo install -vCDt /etc/udev/rules.d deps/acpilight/90-backlight.rules sudo udevadm trigger -s backlight -c add ``` + (also requires your user to be in the `video` group) diff --git a/home/.config/redshift/hooks/backlight b/home/.config/redshift/hooks/backlight index 78209e1d1b..23c076caad 100755 --- a/home/.config/redshift/hooks/backlight +++ b/home/.config/redshift/hooks/backlight @@ -8,7 +8,7 @@ # Called by redshift as: backlight period-changed set -u -brightness_day=100 +brightness_day=70 brightness_transition=50 brightness_night=30 # redshift sets period to "none" (and fires this same period-changed hook) diff --git a/home/.config/redshift/redshift.conf b/home/.config/redshift/redshift.conf index 01c0d7361d..f34c7c6bf6 100644 --- a/home/.config/redshift/redshift.conf +++ b/home/.config/redshift/redshift.conf @@ -1,9 +1,3 @@ [redshift] -; RandR's Brightness property (distinct from Gamma/color temperature below), -; separate from the hardware-backlight dimming in hooks/backlight -- this -; applies uniformly to every monitor, including ones with no DDC/CI backlight -; control. -brightness-day=0.7 - ; slightly warm daytime color temperature (default neutral is 6500K) temp-day=5500 From e88239e035143af7b987a7bcb317ec7e8a10f8c3 Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Thu, 10 Sep 2026 13:50:47 +0900 Subject: [PATCH 5/7] fix(redshift): background DDC/CI registration loop in rc.local Writing to new_device is a synchronous sysfs call that blocks until the driver's real I2C handshake with that monitor finishes -- doing this for several monitors serially, un-backgrounded, measurably slows down boot. modprobe alone is instant and doesn't need it, only the per-monitor loop does. Co-Authored-By: Claude Sonnet 5 --- README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9c3afd7278..95a3ae1693 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Controlling an external/desktop monitor's brightness this way additionally requi Two things the build here deliberately does *not* automate, since they require root and are one-time system setup rather than per-user dotfiles (udev rules in particular are inherently system-wide -- there's no per-user rules directory or per-user udev daemon, so this can't be moved into the unprivileged `home`/stow flow no matter what): -- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on): +- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on). The loop itself is backgrounded (`( ... ) &`): writing to `new_device` is a synchronous sysfs call that blocks until the driver's real I2C handshake with that monitor finishes, so doing this for several monitors serially and un-backgrounded measurably slows down boot -- `modprobe` alone is instant and doesn't need it, only the per-monitor loop does: ```sh /sbin/modprobe ddcci-backlight @@ -114,20 +114,22 @@ Two things the build here deliberately does *not* automate, since they require r # legacy adapter that can't complete the full DDC/CI handshake. HDMI # connectors have no such split -- their `ddc` symlink is the only (and # correct) adapter. - for conn in /sys/class/drm/card0-*/; do - [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue - direct=$(ls -d "$conn"i2c-* 2>/dev/null | head -1) - if [ -n "$direct" ]; then - ddc_bus=$(basename "$direct") - else - ddc_link="$conn/ddc" - [ -e "$ddc_link" ] || continue - ddc_bus=$(basename "$(readlink -f "$ddc_link")") - fi - bus_num=${ddc_bus#i2c-} - [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue - echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null - done + ( + for conn in /sys/class/drm/card0-*/; do + [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue + direct=$(ls -d "$conn"i2c-* 2>/dev/null | head -1) + if [ -n "$direct" ]; then + ddc_bus=$(basename "$direct") + else + ddc_link="$conn/ddc" + [ -e "$ddc_link" ] || continue + ddc_bus=$(basename "$(readlink -f "$ddc_link")") + fi + bus_num=${ddc_bus#i2c-} + [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue + echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null + done + ) & ``` - **Installing acpilight's udev rule** so `xbacklight` can write to `/sys/class/backlight` without root: ```sh From 8a2ad02eff9ba659fa1ae48ddfa1fde6eaf6a94e Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Thu, 10 Sep 2026 14:16:26 +0900 Subject: [PATCH 6/7] simplify(build): drop redundant chmod +x for acpilight's xbacklight Git already tracks the file's mode as 100755, so a checkout produces an executable script with no chmod needed. Co-Authored-By: Claude Sonnet 5 --- mk/build.mk | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mk/build.mk b/mk/build.mk index 3369f8d0bb..6db15ce4e5 100644 --- a/mk/build.mk +++ b/mk/build.mk @@ -45,16 +45,15 @@ $(xwinwrap_target): $(xwinwrap_head_file) $(MAKE) -C $(XWINWRAP_ROOT) xwinwrap: $(xwinwrap_target) -# acpilight's xbacklight is a plain script (no build step); this just ensures the -# submodule is checked out and the script kept executable. The udev rule -# (90-backlight.rules), which grants group `video` write access to -# /sys/class/backlight without setuid/sudo, is a one-time system-level install -# and is NOT handled here -- see README. +# acpilight's xbacklight is a plain script (no build step); this just ensures +# the submodule is checked out (already executable straight from git, which +# tracks the mode bit). The udev rule (90-backlight.rules), which grants group +# `video` write access to /sys/class/backlight without setuid/sudo, is a +# one-time system-level install and is NOT handled here -- see README. .PHONY: acpilight acpilight_target = $(ACPILIGHT_ROOT)/xbacklight $(eval $(call git_submodule,acpilight,$(ACPILIGHT_ROOT))) $(acpilight_target): $(acpilight_head_file) - chmod +x $(acpilight_target) acpilight: $(acpilight_target) i3status-config-template := home/.config/i3status/config.template From 62a315c839e21e408e2652b036d4640bd4b443de Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Thu, 10 Sep 2026 14:40:21 +0900 Subject: [PATCH 7/7] docs: tighten acpilight/ddcci comments, genericize startup mechanism Trim mk/build.mk's acpilight comment to match the file's terser style (the udev-rule caveat it dropped is still covered in the README). Move the "why backgrounded" explanation from README prose into an inline shell comment next to the code it explains, add DP/HDMI branch labels, and genericize "/etc/rc.d/rc.local" to "your system's startup" for portability to non-Slackware systems. Co-Authored-By: Claude Sonnet 5 --- README.md | 10 ++++++---- mk/build.mk | 6 +----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 95a3ae1693..07348608ff 100644 --- a/README.md +++ b/README.md @@ -95,16 +95,16 @@ Notes: ### Backlight control (acpilight) -[`acpilight`](https://gitlab.com/wavexx/acpilight) (`deps/acpilight`, built like `xwinwrap`) provides a backward-compatible `xbacklight` replacement that controls brightness via `/sys/class/backlight` instead of X11's RandR `Backlight` property, so it works for laptop panels the RandR property was never wired up for, and it's driven by a redshift hook ([`home/.config/redshift/hooks/backlight`](home/.config/redshift/hooks/backlight)) that dims the display along with the day/night cycle. +[`acpilight`](https://gitlab.com/wavexx/acpilight) provides a backward-compatible `xbacklight` replacement that controls brightness via `/sys/class/backlight` instead of X11's RandR `Backlight` property, so it works for laptop panels the RandR property was never wired up for, and it's driven by a redshift hook ([`home/.config/redshift/hooks/backlight`](home/.config/redshift/hooks/backlight)) that dims the display along with the day/night cycle. Controlling an external/desktop monitor's brightness this way additionally requires the **`ddcci-driver-linux`** kernel module, which exposes DDC/CI-controlled monitors under `/sys/class/backlight/ddcci*` (the same interface as a native panel), so the same hook script works unmodified on both a laptop (native panel) and a desktop (DDC/CI monitor): - **Arch Linux**: AUR package `ddcci-driver-linux-dkms` (or `-git` for newer kernels) - **Slackware**: [`ddcci-driver-linux`](https://slackbuilds.org/repository/15.0/system/ddcci-driver-linux/) on SlackBuilds.org -Two things the build here deliberately does *not* automate, since they require root and are one-time system setup rather than per-user dotfiles (udev rules in particular are inherently system-wide -- there's no per-user rules directory or per-user udev daemon, so this can't be moved into the unprivileged `home`/stow flow no matter what): +System settings required: -- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add to `/etc/rc.d/rc.local` (bus resolved dynamically per DRM connector, since i2c adapter numbers aren't guaranteed stable across reboots/driver updates; adjust the `card0-*` glob to match which GPU your monitors are on). The loop itself is backgrounded (`( ... ) &`): writing to `new_device` is a synchronous sysfs call that blocks until the driver's real I2C handshake with that monitor finishes, so doing this for several monitors serially and un-backgrounded measurably slows down boot -- `modprobe` alone is instant and doesn't need it, only the per-monitor loop does: +- **Registering each monitor's DDC/CI device**, since `ddcci-driver-linux` doesn't autodetect monitors on its own -- it needs the main device (address `0x37`) added manually per i2c bus. Add the following snippet to your system's startup (adjust the `card0-*` glob to match which GPU your monitors are on): ```sh /sbin/modprobe ddcci-backlight @@ -119,8 +119,10 @@ Two things the build here deliberately does *not* automate, since they require r [ "$(cat "$conn/status" 2>/dev/null)" = "connected" ] || continue direct=$(ls -d "$conn"i2c-* 2>/dev/null | head -1) if [ -n "$direct" ]; then + # DP ddc_bus=$(basename "$direct") else + # HDMI ddc_link="$conn/ddc" [ -e "$ddc_link" ] || continue ddc_bus=$(basename "$(readlink -f "$ddc_link")") @@ -129,7 +131,7 @@ Two things the build here deliberately does *not* automate, since they require r [ -e "/sys/bus/ddcci/devices/ddcci$bus_num" ] && continue echo ddcci 0x37 > "/sys/bus/i2c/devices/$ddc_bus/new_device" 2>/dev/null done - ) & + ) & # writing to new_device is a synchronous sysfs call that blocks until the driver's real I2C handshake with that monitor finishes, and it's slow ``` - **Installing acpilight's udev rule** so `xbacklight` can write to `/sys/class/backlight` without root: ```sh diff --git a/mk/build.mk b/mk/build.mk index 6db15ce4e5..4c7add1c98 100644 --- a/mk/build.mk +++ b/mk/build.mk @@ -45,14 +45,10 @@ $(xwinwrap_target): $(xwinwrap_head_file) $(MAKE) -C $(XWINWRAP_ROOT) xwinwrap: $(xwinwrap_target) -# acpilight's xbacklight is a plain script (no build step); this just ensures -# the submodule is checked out (already executable straight from git, which -# tracks the mode bit). The udev rule (90-backlight.rules), which grants group -# `video` write access to /sys/class/backlight without setuid/sudo, is a -# one-time system-level install and is NOT handled here -- see README. .PHONY: acpilight acpilight_target = $(ACPILIGHT_ROOT)/xbacklight $(eval $(call git_submodule,acpilight,$(ACPILIGHT_ROOT))) +# no build step - just ensure submodule $(acpilight_target): $(acpilight_head_file) acpilight: $(acpilight_target)