Skip to content

Instantly share code, notes, and snippets.

@mathieu-b
Created June 25, 2024 23:03
Show Gist options
  • Save mathieu-b/f7a13a260380ac2ffe8e7808cca9c0a4 to your computer and use it in GitHub Desktop.
Save mathieu-b/f7a13a260380ac2ffe8e7808cca9c0a4 to your computer and use it in GitHub Desktop.
Control the brightness of external displays via ddcutil on Linux
#!/usr/bin/env bash
#==============================================================================
# Script for setting the brightness of two displays using ddcutil.
# You could store this script at ~/.local/bin/brightness
# (with execute permissions, and ensuring that ~/.local/bin is in your PATH)
# and then call it from a keybinding, launcher, or directly from CLI.
# Example usages:
# brightness +10 (increase brightness by 10%)
# brightness -10 (decrease brightness by 10%)
# brightness 50 (set brightness to 50%)
# brightness 100 (set brightness to 100%)
#
# Author: Mathieu Bosi, 2024/06/26
# Based on: https://moverest.xyz/blog/control-display-with-ddc-ci/configure
#==============================================================================
set -eu
if ! command -v ddcutil &> /dev/null; then
echo "ddcutil is not installed. Please install it and configure your machine as explained here:"
echo "https://moverest.xyz/blog/control-display-with-ddc-ci/configure"
exit 1
fi
if [[ $# -ne 1 ]]; then
echo "Usage: $0 [+,-]<brightness_percent>"
exit 1
fi
brightness="$1"
# Ensure that there is a space between the sign and the number, as required by ddcutil:
brightness="${brightness//+/+ }"
brightness="${brightness//-/- }"
echo "Brightness parameter for ddcutil: '$brightness'"
readonly DDC_BRIGHTNESS_CONTROL_ID=10
function set_display_brightness() {
local display_number="$1"
local brightness="$2"
local retries_left=2
echo "Setting brightness for display $display_number to '${brightness}' ..."
until ddcutil --display "${display_number}" setvcp "${DDC_BRIGHTNESS_CONTROL_ID}" ${brightness} ; do
[[ $retries_left -le 0 ]] && break
echo "Error setting brightness for display $display_number, will retry $retries_left more times ..."
retries_left=$((retries_left - 1))
sleep 0.1
done
if [[ $retries_left -le 0 ]]; then
echo "Failed to set brightness for display $display_number"
else
echo "Brightness for display $display_number set to $brightness percent"
fi
}
for display_number in 1 2 ; do
set_display_brightness "${display_number}" "${brightness}" &
done
wait
@mathieu-b
Copy link
Author

mathieu-b commented Jun 26, 2024

Updated version that auto-detects all connected displays that support the Brightness control:

#!/usr/bin/env bash

#==============================================================================
# Script for setting the brightness of all connected displays that support brightness control using ddcutil.
# You could store this script at ~/.local/bin/brightness
# (with execute permissions, and ensuring that ~/.local/bin is in your PATH) 
# and then call it from a keybinding, launcher, or directly from CLI.
# Example usages:
#   brightness +10     (increase brightness by 10%)
#   brightness -10     (decrease brightness by 10%)
#   brightness 50      (set brightness to 50%)
#   brightness 100     (set brightness to 100%)
#   brightness rescan  (rescan i2c buses for all monitors)
#
# Author: Mathieu Bosi, 2024/06/26
# Based on:
# - https://moverest.xyz/blog/control-display-with-ddc-ci/configure
# - https://www.reddit.com/r/linux/comments/zs8l7t/comment/j18mem8/
#==============================================================================

set -eu

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

readonly I2C_BUSES_CACHE_FILE_PATH="${SCRIPT_DIR}/monitor-i2c-buses"
readonly VCP_BRIGHTNESS_CONTROL_ID=10


errors_count=0


if ! command -v ddcutil &> /dev/null; then
    echo "❌ ddcutil is not installed. Please install it and configure your machine as explained here:"
    echo "https://moverest.xyz/blog/control-display-with-ddc-ci/configure"
    ((errors_count++))
fi

if ! groups | grep -q "\bi2c\b"; then
    echo "❌ You are not in the i2c group. Please add yourself to the i2c group and log out and back in."
    echo "You can do this by running the following command:"
    echo "sudo usermod -aG i2c $USER"
    ((errors_count++))
else
    echo "✅ You are in the i2c group."
fi


if [[ $# -ne 1 ]]; then
    echo "Usage: $0 [+,-]<brightness_percent>"
    ((errors_count++))
fi


if [[ $errors_count -gt 0 ]]; then
    echo "Exiting due to errors."
    exit 1
fi


if [[ "$1" == "rescan" ]] ; then
  if rm -fv "${I2C_BUSES_CACHE_FILE_PATH}" ; then
    echo "Deleted i2c buses cache file at '${I2C_BUSES_CACHE_FILE_PATH}'"
  else
    echo "⚠️ Failed to delete i2c buses cache file at '${I2C_BUSES_CACHE_FILE_PATH}'"
  fi
fi


if [[ ! -f "${I2C_BUSES_CACHE_FILE_PATH}" ]]; then
  echo "Detecting i2c buses for all monitors ..."

  i2c_busses_ids="$(ddcutil detect | grep -o "i2c.*" | cut -f2- -d- | tr '\n' ' ')"

  for i2c_bus_id in ${i2c_busses_ids} ; do
    echo "-- Detected I2C bus with ID $i2c_bus_id. Testing that I2C bus is functional ..."
    if ddcutil --bus "${i2c_bus_id}" getvcp "${VCP_BRIGHTNESS_CONTROL_ID}" ; then
      echo "✅   ... I2C bus $i2c_bus_id brightness control is functional"
      echo "$i2c_bus_id" >> "${I2C_BUSES_CACHE_FILE_PATH}"
    else
      echo "⚠️    ... I2C bus $i2c_bus_id brightness control is not functional: it will not be used / cached."
    fi
    echo
  done
  
else
  echo "Using cached i2c buses IDs at '${I2C_BUSES_CACHE_FILE_PATH}' for all monitors:"
  cat "${I2C_BUSES_CACHE_FILE_PATH}"
  echo "To rescan i2c buses, run '$0 rescan'"
fi

if [[ "$1" == "rescan" ]] ; then
  echo "Rescan complete, exiting."
  exit 0
fi


brightness="$1"

# Ensure that there is a space between the sign and the number, as required by ddcutil:
brightness="${brightness//+/+ }"
brightness="${brightness//-/- }"

echo "Brightness parameter for ddcutil: '$brightness'"

function set_display_brightness() {
    local display_i2c_bus_number="$1"
    local brightness="$2"

    local retries_left=2

    echo "Setting brightness for display on I2C bus $display_i2c_bus_number to '${brightness}' ..."

    # shellcheck disable=SC2086
    until ddcutil --bus "${display_i2c_bus_number}" setvcp "${VCP_BRIGHTNESS_CONTROL_ID}" ${brightness} ; do      
      [[ $retries_left -le 0 ]] && break 
      echo "Error setting brightness for display on I2C bus $display_i2c_bus_number, will retry $retries_left more times ..."      
      retries_left=$((retries_left - 1))      
      sleep 0.5
    done

    if [[ $retries_left -le 0 ]]; then
      echo "❌ Failed to set brightness for display on I2C bus $display_i2c_bus_number."
    else
      echo "✅ Brightness for display on I2C bus $display_i2c_bus_number set to $brightness percent."
    fi
}



while read -r display_i2c_bus_number; do
  # NOTE: it looks like trying to parallelize this with & / wait results in frequent failures
  # when ussing the ddcutil '--bus' option, so we set brightness sequentially:
  set_display_brightness "${display_i2c_bus_number}" "${brightness}"
done < "${I2C_BUSES_CACHE_FILE_PATH}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment