diff --git a/config/copy/etc/udev/rules.d/61-panama-ddc-i2c.rules b/config/copy/etc/udev/rules.d/61-panama-ddc-i2c.rules new file mode 100644 index 0000000..fed7cb2 --- /dev/null +++ b/config/copy/etc/udev/rules.d/61-panama-ddc-i2c.rules @@ -0,0 +1,9 @@ +# External monitor brightness (DDC/CI) on GPUs that are not "VGA". +# +# ddcutil ships 60-ddcutil-i2c.rules, which grants the seated user access to +# the GPU's i2c buses -- but only when the GPU's PCI class is 0x030000 (VGA +# compatible controller). An AMD iGPU that is not the primary boot display +# enumerates as 0x038000 (Display controller) instead, so every DDC bus it +# exposes stays root-only and the Brightness service reads EACCES. Same grant, +# broadened to the class that hardware actually reports. +SUBSYSTEM=="i2c-dev", KERNEL=="i2c-[0-9]*", ATTRS{class}=="0x038000", TAG+="uaccess" diff --git a/config/dot/quickshell/scripts/panama-brightness b/config/dot/quickshell/scripts/panama-brightness index 11a5f72..3c1ab5c 100755 --- a/config/dot/quickshell/scripts/panama-brightness +++ b/config/dot/quickshell/scripts/panama-brightness @@ -93,7 +93,7 @@ bus_for_connector() { cmd_list() { has_accessible_bus || emit_error 'no I2C bus is accessible. ddcutil ships a udev rule that grants this, but only to devices created after it was installed. Run: sudo udevadm control --reload-rules && sudo udevadm trigger --subsystem-match=i2c-dev --subsystem-match=drm' - local rows=() connector bus value path + local rows=() externals=0 connector bus value path for path in "$DRM_ROOT"/card*-*; do [[ -e "$path/ddc" ]] || continue [[ "$(cat "$path/status" 2>/dev/null)" == "connected" ]] || continue @@ -102,6 +102,14 @@ cmd_list() { connector="$(basename "$path")" connector="${connector#card*-}" + # Internal panels (eDP, LVDS, DSI) use the backlight, never DDC/CI. + # Counting only external connectors lets the empty result below say + # whether anything is even plugged in. + case "$connector" in + eDP-*|LVDS-*|DSI-*) ;; + *) externals=$((externals + 1)) ;; + esac + bus="$(bus_for_connector "$path")" [[ "$bus" =~ ^[0-9]+$ ]] || continue @@ -119,6 +127,13 @@ cmd_list() { done if [[ ${#rows[@]} -eq 0 ]]; then + # Nothing external is connected at all: the normal state of an + # undocked laptop, not a problem to warn about. The error path is + # reserved for a monitor that is present but will not talk. + if (( externals == 0 )); then + printf '{"displays":[],"error":""}\n' + return 0 + fi emit_error 'no connected monitor reports DDC/CI brightness. Some panels implement it only when "DDC/CI" is enabled in their on-screen menu.' fi diff --git a/config/dot/quickshell/scripts/panama-doctor b/config/dot/quickshell/scripts/panama-doctor index 8e57770..2756818 100755 --- a/config/dot/quickshell/scripts/panama-doctor +++ b/config/dot/quickshell/scripts/panama-doctor @@ -533,7 +533,10 @@ def check_brightness(config: DoctorConfig) -> Check: except (json.JSONDecodeError, KeyError, TypeError, ValueError): return Check("input.brightness", "input-media", "External monitor brightness", "warning", "DDC/CI probe returned an invalid result.", instructions) if error: - return Check("input.brightness", "input-media", "External monitor brightness", "warning", "No accessible DDC/CI bus.", instructions) + # The probe says what is actually wrong -- an unreadable bus and a + # monitor with DDC/CI switched off in its menu are different problems + # with different fixes, and one hardcoded string here hid that. + return Check("input.brightness", "input-media", "External monitor brightness", "warning", error.rstrip(".") + ".", instructions) if not displays: return Check("input.brightness", "input-media", "External monitor brightness", "unconfigured", "No DDC/CI display is configured.") return Check("input.brightness", "input-media", "External monitor brightness", "ok", f"{len(displays)} DDC/CI display{'s' if len(displays) != 1 else ''} available.")