DDC/CI monitor control on Linux

DDC/CI is a set of protocols for controlling monitor features like brightness, contrast, color temperature, input source, ... over the display cable (VGA, DVI, HDMI, Display port, ...).

The protocol is fairly old (1998) and nowadays most devices support it.

I'm currently using it to:

  • Adjust the brightness of my two monitors depending on my room lighting.
  • Switch the input source of my monitors for different devices (my laptop, a tower PC and a virtual machine with dedicated GPU). The big advantage here is that you don't need to buy an expensive KVM switch to support 4K displays / high refresh rates.

How

Required packages

You must install the ddcutil package for your Linux distribution. The official website contains extensive information for troubleshooting.

Kernel modules

Ddcutil connects to your screen over an I2C connection, and requires the i2c-dev kernel module to be loaded.

You can load the module at runtime using sudo modprobe i2c-dev.

To make it persistent across reboots, you need to add the module to /etc/modules-load.d/i2c-dev.conf:

i2c-dev

Once the module is loaded, some files should appear in /dev/i2c-*.

Allow the user to use DDC

By default the i2c dev files are owned by root, preventing other users to control them. One solution to allow your user to control DDC without using sudo is to add a custom udev rule:

/etc/udev/rules.d/99-ddcutil-i2c.rules

KERNEL=="i2c-[0-9]*", GROUP="your-user", MODE="0660", PROGRAM="/usr/bin/ddcutil --bus=%n getvcp 0x10"

This rule automatically detects which i2c devices are DDC-capable, and allows members of the group "your-user" to control the file.

You can reload udev rules without rebooting by executing sudo udevadm trigger

If you have multiple users, you can create a new group and add your user to the group:

groupadd ddc
usermod -aG ddc $USER

Note: ddcutil --bus=%n getvcp 0x10 is used to get the current brightness of the monitor. This only work with the assumption that all monitors supporting DDC/CI control can be queried for their brightness, which is likely to be true for the immense majority of them.

Identify your monitor info

How to address your monitor

The following command queries general information on your connected displays:

ddcutil detect
# You should see entries like:
# Display 1
#    I2C bus:             /dev/i2c-0
#    EDID synopsis:
#       Mfg id:           DEL
#       Model:            DELL U2419H
#       Serial number:    833L1N6
#       Manufacture year: 2018
#       EDID version:     1.4
#    VCP version:         2.1

There are multiple methods to address your monitor:

  • By display number using --display
  • By model name using --model
  • By serial number using --sn
  • By i2c bus ID using --bus

The bus ID method is way faster than the others, but may be unreliable if your hardware changes often.

Which features can be controlled

The following command queries which display features can be get/set for a given monitor:

ddcutil capabilities --bus=0
# You should see something like:
# MCCS version: 2.1
# Commands:
#    Command: 01 (VCP Request)
#    Command: 02 (VCP Response)
#    Command: 03 (VCP Set)
#    Command: 07 (Timing Request)
#    Command: 0c (Save Settings)
#    Command: e3 (Capabilities Reply)
#    Command: f3 (Capabilities Request)
# VCP Features:
#    Feature: 10 (Luminosity)
#    Feature: 12 (Contrast)
#    Feature: 14 (Select color preset)
#       Values:
#          04: 5000 K
#          05: 6500 K
#          06: 7500 K
#          08: 9300 K
#          09: 10000 K
#          0b: User 1
#          0c: User 2
#    Feature: 16 (Video gain: Red)
#    Feature: 18 (Video gain: Green)
#    Feature: 1A (Video gain: Blue)
#    Feature: 60 (Input Source)
#       Values:
#          0f: DisplayPort-1
#          11: HDMI-1
#    Feature: AA (Screen Orientation)
#       Values:
#          01: 0 degrees
#          02: 90 degrees
#          03: 180 degrees
#          04: 270 degrees

You can get/set those features using:

  • Get: ddcutil --bus=0 getvcp $FEAT_ID
  • Set: ddcutil --bus=0 setvcp $FEAT_ID $VALUE

Script examples

Change brightness

ddc-setbrightness

#!/bin/bash
# Usage: ddc-setbrightness 50
ddcutil --bus=0 setvcp 10 "$1" &
ddcutil --bus=1 setvcp 10 "$1" &
wait

Since DDC commands can be slow to execute (especially without --bus addressing), it is best to run them in parallel and wait for completion.

Switch input sources

Very useful when you need to change input sources very often, and don't have a dedicated button on the monitor (or for automating it).

ddc-switch-inputs

#!/bin/bash
# Usage: ddc-switch-inputs 1
case "$1" in
   1 )
      # Config 1: Main PC
      OUT=("0x0f" "0x20")
      ;;
   2 )
      # Config 2: Virtual machine
      OUT=("0x11" "0x21")
      ;;
   * )
      echo "Unknown input '$1'"
      exit 1
      ;;
esac

ddcutil --bus=0 setvcp 60 ${OUT[0]} &
ddcutil --bus=1 setvcp 60 ${OUT[1]} &
wait

Reduce eyestrain

ddc-daylight

#!/bin/bash
# Usage: ddc-daylight night
case "$1" in
   "day" )
      BRIGHTNESS=100
      TEMPERATURE=0x09
      ;;
   "evening" | "morning" )
      BRIGHTNESS=60
      TEMPERATURE=0x06
      ;;
   "night" )
      BRIGHTNESS=30
      TEMPERATURE=0x04
      ;;
   "dark" )
      BRIGHTNESS=0
      TEMPERATURE=0x04
      ;;
   * )
      echo "Unknown time of day '$1'"
      exit 1
      ;;
esac

ddcutil --bus=0 setvcp 10 $BRIGHTNESS &
ddcutil --bus=1 setvcp 10 $BRIGHTNESS &
ddcutil --bus=0 setvcp 14 $TEMPERATURE &
ddcutil --bus=1 setvcp 14 $TEMPERATURE &
wait

Updates:
2022-01-13: Replaced i2c-[0-9]+ with i2c-[0-9]* since udev doesn't support the + extension. Thanks Hendrik W. !