Reach DDC on GPUs that are not "VGA", and stop warning about an empty dock

ddcutil's udev rule grants the seated user the GPU's i2c buses only when the
PCI class is 0x030000. An AMD iGPU that is not the primary boot display says
0x038000, so on the Framework every DDC bus stayed root-only. Ship the same
grant for the class the hardware actually reports; change-settings installs it.

And two conflations in the probe: an undocked laptop reported its normal state
as an error, and doctor collapsed every error into "No accessible DDC/CI bus".
Nothing external connected is now a clean empty -- doctor's unconfigured path
-- and a real failure surfaces the probe's own words, because an unreadable
bus and a monitor with DDC/CI off in its menu have different fixes.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
This commit is contained in:
Gabriel Brown
2026-08-23 10:32:17 -04:00
parent 36fdd4e076
commit 6510fdda0f
3 changed files with 29 additions and 2 deletions
@@ -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
+4 -1
View File
@@ -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.")