A comprehensive solution for controlling brightness on multiple monitors in Hyprland, with per-monitor Waybar widgets and keyboard shortcuts.
- 🖥️ Per-Monitor Control: Each Waybar instance shows and controls its own monitor's brightness
- ⌨️ Keyboard Shortcuts: Control focused monitor brightness with Right Ctrl + Arrow keys
- 🖱️ Waybar Widget: Scroll to adjust, click to set 100% or 0%
- 🔄 Smart Detection: Automatically handles different monitor types:
- External monitors via DDC/CI (ddcutil)
- Laptop displays via brightnessctl
- Unsupported monitors show "N/A"
- ⚡ Fast Updates: Async DDC/CI commands prevent UI lag
Install the following packages on Arch Linux:
sudo pacman -S ddcutil i2c-tools brightnessctl- ddcutil: Controls external monitors via DDC/CI protocol
- i2c-tools: I2C device utilities
- brightnessctl: Controls laptop backlight
# Load the module immediately
sudo modprobe i2c-dev
# Make it load on boot
echo "i2c-dev" | sudo tee /etc/modules-load.d/i2c.conf# Create i2c group (if it doesn't exist)
getent group i2c || sudo groupadd i2c
# Add your user to the i2c group
sudo usermod -aG i2c $USER
# Create udev rule for i2c device permissions
sudo sh -c 'echo "KERNEL==\"i2c-[0-9]*\", GROUP=\"i2c\", MODE=\"0660\"" > /etc/udev/rules.d/99-i2c.rules'
# Reload udev rules
sudo udevadm control --reload-rules && sudo udevadm triggerAfter installing ddcutil, check which monitors support DDC/CI:
ddcutil detectExample output:
Display 1
I2C bus: /dev/i2c-14
DRM_connector: card1-DP-7
EDID synopsis:
Mfg id: GSM - Goldstar Company Ltd (LG)
Model: LG ULTRAWIDE
Product code: 30655 (0x77bf)
VCP version: 2.1
Invalid display
I2C bus: /dev/i2c-9
DRM_connector: card1-eDP-1
This is a laptop display. Laptop displays do not support DDC/CI.
Note the display numbers and connector names (e.g., DP-7, eDP-1).
Create ~/.config/waybar/scripts/brightness-per-monitor.sh:
#!/bin/bash
# --- CONFIGURATION ---
STEP=5
STATE_FILE_PREFIX="/tmp/waybar_brightness"
# ---------------------
# Get monitor name - either from argument, env var, or focused monitor
get_monitor_name() {
if [ -n "$1" ]; then
echo "$1"
elif [ -n "$WAYBAR_OUTPUT_NAME" ]; then
echo "$WAYBAR_OUTPUT_NAME"
else
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
fi
}
# Get ddcutil display number for a monitor
get_ddcutil_display() {
local monitor_name="$1"
case "$monitor_name" in
"DP-7")
echo "1" # LG ULTRAWIDE - ADJUST THIS FOR YOUR SETUP
;;
# Add more monitors here as needed:
# "DP-4")
# echo "2"
# ;;
*)
echo "" # No DDC/CI support
;;
esac
}
# Get state file for this monitor
get_state_file() {
local monitor="$1"
echo "${STATE_FILE_PREFIX}_${monitor}.tmp"
}
# Function to send DDC/CI command in background
set_brightness_ddcutil() {
local display_num="$1"
local brightness="$2"
pkill -f "ddcutil.*setvcp 10"
(ddcutil --display "$display_num" setvcp 10 "$brightness") &
}
# Function to set laptop brightness
set_brightness_laptop() {
local brightness="$1"
brightnessctl set "${brightness}%" > /dev/null 2>&1
}
# Get current brightness for a specific monitor
get_current_brightness() {
local monitor="$1"
local state_file=$(get_state_file "$monitor")
local display_num=$(get_ddcutil_display "$monitor")
if [ "$monitor" = "eDP-1" ]; then
# Laptop display
if [ ! -f "$state_file" ]; then
local current=$(brightnessctl get 2>/dev/null || echo "0")
local max=$(brightnessctl max 2>/dev/null || echo "1")
local percent=$((current * 100 / max))
echo "$percent" > "$state_file"
fi
cat "$state_file"
elif [ -n "$display_num" ]; then
# External monitor with DDC/CI
if [ ! -f "$state_file" ]; then
local brightness=$(ddcutil --display "$display_num" getvcp 10 -t 2>/dev/null | awk '{print $4}' || echo "100")
echo "$brightness" > "$state_file"
fi
cat "$state_file"
else
echo "?"
fi
}
# Set brightness for a specific monitor
set_brightness() {
local monitor="$1"
local new_brightness="$2"
local state_file=$(get_state_file "$monitor")
local display_num=$(get_ddcutil_display "$monitor")
if [ "$monitor" = "eDP-1" ]; then
# Laptop display
echo "$new_brightness" > "$state_file"
set_brightness_laptop "$new_brightness"
elif [ -n "$display_num" ]; then
# External monitor with DDC/CI
echo "$new_brightness" > "$state_file"
set_brightness_ddcutil "$display_num" "$new_brightness"
else
# Unsupported monitor
return
fi
# Signal all waybar instances
pkill -RTMIN+8 waybar
}
# Parse command - format: "command [monitor]"
# If monitor not specified, use WAYBAR_OUTPUT_NAME or focused monitor
COMMAND="$1"
MONITOR=$(get_monitor_name "$2")
current=$(get_current_brightness "$MONITOR")
case "$COMMAND" in
"get")
echo " $current"
;;
"up")
if [ "$current" = "?" ]; then
echo " N/A"
else
new_brightness=$((current + STEP > 100 ? 100 : current + STEP))
if [ "$current" -ne "$new_brightness" ]; then
set_brightness "$MONITOR" "$new_brightness"
fi
fi
;;
"down")
if [ "$current" = "?" ]; then
echo " N/A"
else
new_brightness=$((current - STEP < 0 ? 0 : current - STEP))
if [ "$current" -ne "$new_brightness" ]; then
set_brightness "$MONITOR" "$new_brightness"
fi
fi
;;
"right_click")
if [ "$current" != "?" ]; then
new_brightness=0
set_brightness "$MONITOR" "$new_brightness"
fi
;;
"left_click")
if [ "$current" != "?" ]; then
new_brightness=100
set_brightness "$MONITOR" "$new_brightness"
fi
;;
esacMake it executable:
chmod +x ~/.config/waybar/scripts/brightness-per-monitor.shget_ddcutil_display() function to match your monitor setup. Use the display numbers and connector names from ddcutil detect.
Add this to your ~/.config/waybar/config.jsonc:
"custom/brightness": {
"format": "☀ {}%",
"signal": 8,
"exec": "~/.config/waybar/scripts/brightness-per-monitor.sh get",
"on-scroll-up": "~/.config/waybar/scripts/brightness-per-monitor.sh up",
"on-scroll-down": "~/.config/waybar/scripts/brightness-per-monitor.sh down",
"on-click": "~/.config/waybar/scripts/brightness-per-monitor.sh left_click",
"on-click-right": "~/.config/waybar/scripts/brightness-per-monitor.sh right_click",
"tooltip-format": "Monitor Brightness\\nScroll: adjust\\nLeft click: 100%\\nRight click: 0%"
}Reload Waybar:
pkill -SIGUSR2 waybarAdd to your ~/.config/hypr/bindings.conf:
# Monitor brightness control (controls focused monitor)
bindeld = RCTRL, Up, Brightness up, exec, ~/.config/waybar/scripts/brightness-per-monitor.sh up
bindeld = RCTRL, Down, Brightness down, exec, ~/.config/waybar/scripts/brightness-per-monitor.sh down
Reload Hyprland:
hyprctl reload- Right Ctrl + Up Arrow: Increase brightness of focused monitor by 5%
- Right Ctrl + Down Arrow: Decrease brightness of focused monitor by 5%
Each Waybar instance (one per monitor) shows the brightness for its specific monitor:
- Scroll Up/Down: Adjust brightness by 5%
- Left Click: Set to 100%
- Right Click: Set to 0%
You can also control brightness from the command line:
# Get brightness for current focused monitor
~/.config/waybar/scripts/brightness-per-monitor.sh get
# Adjust brightness for focused monitor
~/.config/waybar/scripts/brightness-per-monitor.sh up
~/.config/waybar/scripts/brightness-per-monitor.sh down
# Control specific monitor
~/.config/waybar/scripts/brightness-per-monitor.sh up DP-7
~/.config/waybar/scripts/brightness-per-monitor.sh down eDP-1Edit the STEP variable in brightness-per-monitor.sh:
STEP=5 # Change to 10 for larger stepsIf you have multiple external monitors, add them to the get_ddcutil_display() function:
get_ddcutil_display() {
local monitor_name="$1"
case "$monitor_name" in
"DP-7")
echo "1" # First external monitor
;;
"DP-4")
echo "2" # Second external monitor
;;
"HDMI-A-1")
echo "3" # HDMI monitor
;;
*)
echo "" # No DDC/CI support
;;
esac
}Find your display numbers with:
ddcutil detect# Check if i2c devices exist
ls -la /dev/i2c-*
# Verify i2c module is loaded
lsmod | grep i2c
# Check group membership
groups $USER# Check if you're in the i2c group
groups | grep i2c
# If not, add yourself and log out/in
sudo usermod -aG i2c $USERSome monitors may need to enable DDC/CI in their OSD (On-Screen Display) settings. Look for options like:
- DDC/CI
- External Control
- PC Communication
This is normal for DDC/CI. The script runs ddcutil commands in the background to prevent UI lag. The Waybar display updates immediately using cached values.
# Get current brightness
ddcutil --display 1 getvcp 10
# Set brightness to 50%
ddcutil --display 1 setvcp 10 50-
Monitor Detection: The script uses
WAYBAR_OUTPUT_NAME(set by Waybar for each instance) or queries the focused monitor viahyprctl -
Per-Monitor State: Each monitor's brightness is cached in
/tmp/waybar_brightness_<monitor>.tmpfor fast reads -
Backend Selection:
- Laptop displays (eDP-1) use
brightnessctl - External monitors use
ddcutilwith DDC/CI - Unsupported monitors show "?"
- Laptop displays (eDP-1) use
-
Async Updates: DDC/CI commands run in the background to prevent blocking. The UI updates immediately from the cache, while the actual hardware change happens asynchronously.
-
Waybar Signals: Uses RTMIN+8 signal to refresh all Waybar instances after brightness changes
This configuration is provided as-is for personal use. Feel free to modify and share.
Based on the brightness control gist by negoro26, adapted for per-monitor control in Hyprland with Waybar.