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

It's a quite old protocol that is well supported across many different devices.

I'm currently using it to:

  • Adjust brightness of my two monitors depending on how the room is lighted
  • Switch minotor inputs between the VM-dedicated graphics card and the host graphics card, replacing the need of a very expensive KVM switch that can WQHD @144Hz.

How

Required packages

You must install ddcutil package. 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, you should see some files in /dev/i2c-*.

Allow the user to use DDC

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

/etc/udev/rules.d/45-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

Identify your monitor info

How to address your monitor

There are multiple ways to address your monitor, like display number (--display), model name (--model), serial number (--sn), i2c bus ID (--bus).

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

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

Which features can be controlled

  • Get feature value: ddcutil --bus=0 getvcp $FEAT_ID
  • Set feature value: ddcutil --bus=0 setvcp $FEAT_ID $VALUE
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

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

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