Skip to content

Add ddcutil fallback for brightness on desktops without backlight device#5282

Open
felipe3dfx wants to merge 1 commit intobasecamp:devfrom
felipe3dfx:feat/brightness-ddc-fallback
Open

Add ddcutil fallback for brightness on desktops without backlight device#5282
felipe3dfx wants to merge 1 commit intobasecamp:devfrom
felipe3dfx:feat/brightness-ddc-fallback

Conversation

@felipe3dfx
Copy link
Copy Markdown
Contributor

@felipe3dfx felipe3dfx commented Apr 12, 2026

Summary

XF86MonBrightnessDown/Up silently fails on systems without /sys/class/backlight — desktops, NUCs, mini-PCs, and laptops where the driver doesn't register a backlight device (common on NVIDIA and some AMD hybrid setups — see #5067). This PR extends omarchy-brightness-display with a DDC/CI fallback via ddcutil, so the same keybindings that work on laptops also work on external monitors, out of the box.

Zero user setup: the Arch ddcutil package already ships modules-load.d/ddcutil.conf (auto-loads i2c-dev at boot) and a 60-ddcutil-i2c.rules udev rule that grants uaccess to display-class i2c buses. This PR leans on that — the migration just installs the package and kicks the module + udev for users whose current session was already running.

What's in the PR

Script (bin/omarchy-brightness-display)

  • A third branch after the existing ACPI and Apple paths: iterate ddcutil-detected i2c buses, read/write VCP 0x10, step all monitors together, report a unified percent to swayosd.
  • Bus + state cache in $XDG_RUNTIME_DIR (300s TTL) so repeats skip the ~1 s ddcutil detect and the per-monitor getvcp round-trip.
  • Blocking flock (not -n) serializes key repeats through the cache read-modify-write. -n silently drops taps during the cold path and users feel it as "one of my three presses didn't register". The debounce worker does not inherit the lock (9>&-).
  • Debounced setvcp — rapid taps coalesce into a single setvcp per gesture via a timestamp + atomic target-file + background worker (250 ms window). This is the same pattern KDE PowerDevil uses (D8626), for the same reasons:
    • the OSD stays in sync instead of the monitor chasing stale values over slow i2c,
    • writes to the monitor's NVRAM (limited write cycles per ddcutil docs) are minimized.
  • --sleep-multiplier 0.04 --noverify on setvcp for responsive interactive use.
  • Bash 5+ EPOCHSECONDS / EPOCHREALTIME builtins — no date forks on the hot path.

Package + migration

  • ddcutil added to install/omarchy-base.packages (alphabetical).
  • migrations/1776070000.sh — upgrade path for existing users:
    • omarchy-pkg-add ddcutil if missing.
    • modprobe i2c-dev so the current session can use DDC without a reboot.
    • udevadm control --reload-rules && udevadm trigger --subsystem-match=i2c-dev so the upstream package's uaccess rule applies to already-enumerated i2c buses in the active seat.
    • set -e so a failing omarchy-pkg-add or modprobe doesn't get silently marked as applied by omarchy-migrate.

No fresh-install hook is needed: pacman running during install drops modules-load.d/ddcutil.conf, which systemd-modules-load.service picks up on the first boot into the installed system, and the bundled udev rule processes all i2c buses on that same boot — so first login comes up with /dev/i2c-* already ACL-tagged for the active seat user.

Measurements

BenQ EX2780Q + Dell U2415, NVIDIA proprietary on Hyprland:

Path Time
Warm (cached bus + state, debounced) 21–22 ms
Cold (TTL expired, full ddcutil detect) ~750 ms, once per 5-min window
getvcp physical floor, BenQ, multiplier 0.04 465 ms
setvcp physical floor, BenQ, multiplier 0.04 487 ms

The 300–500 ms per VCP op is physical DDC/CI wire time — that is why the cache + debounce pattern matters: it keeps that time off the hot path.

Tested

  • Multi-monitor DDC step together, unified percent to swayosd: verified live.
  • Rapid Fn+F1 burst (10 taps): no drops, single setvcp per gesture, monitor slews to the final value once: verified live.
  • Cold cache (rm /run/user/$UID/omarchy-ddc-*): re-detects buses, re-reads state, one slow tap then warm path: verified live.
  • Apple ASdControl path and native backlight path: inspection only — the new branch runs only after both short-circuit, no behavior change on laptops.

Review iteration

An earlier revision of this PR shipped its own default/udev/ddcutil-i2c.rules + an i2c group + install/config/ddcutil-i2c.sh that copied the rule and added the user to the group (requiring a logout). The Copilot review on install/config/ddcutil-i2c.sh and migrations/1776070000.sh pushed on the i2c-dev module loading question, and digging into that surfaced that the Arch ddcutil package already ships both a modules-load.d entry and its own 60-ddcutil-i2c.rules with TAG+="uaccess" — which is a strictly better approach than the group-based one (no logout, handled by systemd-logind, filtered to display-class buses via ATTRS{class}=="0x03*"). This revision leans on that instead. Net result: 3 files + 140 lines instead of 6 files + 169 lines, and no more logout notice.

Related

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a DDC/CI (ddcutil) fallback to omarchy-brightness-display so brightness keybindings work on systems without a /sys/class/backlight device (e.g., desktops/external monitors), and wires up install + migration hooks to grant i2c device access.

Changes:

  • Extend bin/omarchy-brightness-display with a DDC/CI fallback, including caching + debounced setvcp.
  • Add ddcutil to base packages and introduce an install hook to create the i2c group + install a udev rule.
  • Add a migration to apply the same udev/group setup for existing installs.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
bin/omarchy-brightness-display Adds DDC/CI fallback path with caching, locking, unified OSD reporting, and debounced writes.
default/udev/ddcutil-i2c.rules New udev rule to grant group access to /dev/i2c-* devices.
install/omarchy-base.packages Installs ddcutil by default.
install/config/ddcutil-i2c.sh Fresh-install hook to create i2c group, add user, and install udev rule.
install/config/all.sh Runs the new ddcutil-i2c.sh hook during install.
migrations/1776070000.sh Upgrade migration to install ddcutil and apply udev/group access for existing users.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread default/udev/ddcutil-i2c.rules Outdated
Comment thread migrations/1776070000.sh Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install/config/ddcutil-i2c.sh Outdated
Comment thread migrations/1776070000.sh Outdated
@felipe3dfx felipe3dfx force-pushed the feat/brightness-ddc-fallback branch from ab1183c to 220874f Compare April 12, 2026 03:53
@felipe3dfx felipe3dfx marked this pull request as draft April 12, 2026 03:58
@felipe3dfx felipe3dfx requested a review from Copilot April 12, 2026 03:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@felipe3dfx felipe3dfx marked this pull request as ready for review April 12, 2026 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants