Managing brightness across multiple external monitors on Linux can be surprisingly tedious. Unlike laptops with built-in brightness controls, external monitors require DDC/CI (Display Data Channel Command Interface) communication over I2C. This is where
ddcutil comes in—but out of the box, it's verbose and requires specifying which monitor you want to control every time.In this post, I'll walk through how I went from typing long commands for each monitor to a seamless two-keybind solution that automatically adjusts the brightness of whichever monitor I'm currently focused on.
The Setup
I have three displays:
- ASUS monitor on HDMI-A-1 (
/dev/i2c-1)
- AGON monitor on DP-3 (
/dev/i2c-11)
- MSI laptop display on eDP-1 (doesn't support DDC/CI)
Stage 1: Basic ddcutil Commands
First, I needed to understand how to control brightness with ddcutil. The basic commands are straightforward:
# Check current brightness ddcutil --bus 1 getvcp 10 # ASUS ddcutil --bus 11 getvcp 10 # AGON # Set brightness (0-100) ddcutil --bus 1 setvcp 10 50 # ASUS to 50% ddcutil --bus 11 setvcp 10 75 # AGON to 75%
Stage 2: Incremental Adjustments
Setting absolute brightness values isn't intuitive. I wanted to increment and decrement brightness like I would on a laptop. Since ddcutil doesn't support relative changes natively, I needed a script.
Here's
~/bin/brightness-adjust:#!/bin/bash BUS=$1 CHANGE=$2 if [ -z "$BUS" ] || [ -z "$CHANGE" ]; then echo "Usage: $0 <bus> <+/- amount>" echo "Example: $0 1 +10" exit 1 fi # Get current brightness CURRENT=$(ddcutil --bus $BUS getvcp 10 --terse | cut -d' ' -f4) # Calculate new brightness NEW=$((CURRENT + CHANGE)) # Clamp to 0-100 range if [ $NEW -lt 0 ]; then NEW=0; fi if [ $NEW -gt 100 ]; then NEW=100; fi # Set new brightness ddcutil --bus $BUS --sleep-multiplier 0.1 setvcp 10 $NEW echo "Monitor $BUS: $CURRENT -> $NEW"
Now I could do:
brightness-adjust 1 +10 # Increase ASUS by 10% brightness-adjust 11 -15 # Decrease AGON by 15%
Much better! But I still had to remember which bus number corresponded to which monitor.
Stage 3: Hyprland Integration
I use Hyprland as my compositor, so I wanted keyboard shortcuts. My first attempt looked like this in
.config/hypr/bindings.conf:bind = SUPER, F1, exec, ~/bin/brightness-adjust 1 -10 bind = SUPER, F2, exec, ~/bin/brightness-adjust 1 +10 bind = SUPER, F3, exec, ~/bin/brightness-adjust 11 -10 bind = SUPER, F4, exec, ~/bin/brightness-adjust 11 +10
This worked, but required four keybinds and I had to remember which keys controlled which monitor. There had to be a better way.
Stage 4: Context-Aware Brightness Control
The breakthrough was realizing Hyprland tracks which monitor is currently focused. I could query this information and automatically adjust the right monitor!
Here's the final
~/bin/brightness-adjust-focused script:#!/bin/bash CHANGE=$1 if [ -z "$CHANGE" ]; then echo "Usage: $0 <+/- amount>" exit 1 fi # Get the focused monitor name MONITOR=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name') # Map monitor names to I2C bus numbers or handle laptop display case $MONITOR in "HDMI-A-1") BUS=1 # ASUS ;; "DP-3") BUS=11 # AGON ;; "eDP-1") # Use brightnessctl for laptop display if command -v brightnessctl &> /dev/null; then if [ $CHANGE -lt 0 ]; then # Decrement: remove the minus sign brightnessctl set "${CHANGE#-}%-" else # Increment brightnessctl set "${CHANGE}%+" fi echo "Laptop display brightness adjusted by ${CHANGE}%" else echo "brightnessctl not found. Install it with: sudo pacman -S brightnessctl" exit 1 fi exit 0 ;; *) echo "Unknown monitor: $MONITOR" exit 1 ;; esac # Get current brightness for external monitors CURRENT=$(ddcutil --bus $BUS getvcp 10 --terse | cut -d' ' -f4) # Calculate new brightness NEW=$((CURRENT + CHANGE)) # Clamp to 0-100 range if [ $NEW -lt 0 ]; then NEW=0; fi if [ $NEW -gt 100 ]; then NEW=100; fi # Set new brightness ddcutil --bus $BUS --sleep-multiplier 0.1 setvcp 10 $NEW echo "Monitor $MONITOR (bus $BUS): $CURRENT -> $NEW"
The magic is in
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name', which returns the name of the currently focused monitor.As a bonus, I added support for my laptop's built-in display using
brightnessctl, since laptop displays don't support DDC/CI.Final Hyprland Configuration
Now my bindings are beautifully simple:
bind = SUPER, F1, exec, ~/bin/brightness-adjust-focused -10 bind = SUPER, F2, exec, ~/bin/brightness-adjust-focused +10
Two keybinds. That's it. They work on all three displays, automatically adjusting whichever monitor I'm currently looking at.
The Result
What started as verbose commands like:
ddcutil --bus 1 setvcp 10 50
Became a seamless experience where I just press
SUPER + F1 or SUPER + F2, and the monitor I'm focused on gets brighter or dimmer. The script even handles my laptop display differently, using the appropriate backlight controls.Requirements
To replicate this setup, you'll need:
ddcutilfor external monitor control
brightnessctlfor laptop display control (optional)
- A Wayland compositor that reports focused monitors (like Hyprland)
jqfor parsing JSON from Hyprland
Install on Arch:
sudo pacman -S ddcutil brightnessctl jq
Conclusion
This journey from manual commands to context-aware automation showcases the beauty of Linux customization. With a bit of scripting and understanding of your tools, you can build workflows that feel native and intuitive—even when working with protocols that weren't designed for it.
Now my brightness controls work just as seamlessly as they do on any laptop, across all my monitors, with zero mental overhead about which monitor I'm adjusting. That's the power of taking control of your desktop environment.
