Phaengris.Art hand-drawn arts, city photos, linux / dev stories

Manage Monitor Brightness in Linux With a Shell Script

Managing brightness with monitor controls is a pain. You’re lucky if you have a dedicated button or wheel right on the monitor to do that. Otherwise you have to find that tiny button somewhere under the thing and, well, do with it what you have to do. Repeat for the second monitor if you have any.

Giving that, the idea of using Meta + sound level wheel to regulate the brightness right from the keyboard was unevitable. Yep, you can do that in Linux, but of course in a Linux way, scrathing your head till a suitable bash script is born.

#!/usr/bin/env bash

# brightness isn't changed momentarily, ddcutil needs time to send a command to the monitor
# that's why we better check if there's a command has been sent just a moment ago
# let's use a temp file for that which creation time we can check
if [ -f /tmp/.brightness_sh ]; then
  # date +%s returns the number of seconds since the epoch, see `man date`
  # stat -c %Y returns the last modification time of the file in seconds since the epoch, see `man stat`
  last_run=$(( $(date +%s) - $(stat -c %Y /tmp/.brightness_sh) ))
  if [[ $last_run -lt 2 ]]; then
    echo "The previous call may not be finished yet"
    exit 4
  fi
fi

# update file modification time
touch /tmp/.brightness_sh

# here you list your monitors
# I refer to them by model name because of mine ones are of different brands
# you may want to refer in other ways, by serial number maybe, see `man ddcutil` / "Options for monitor selection"
# the list of available monitors may be obtained with `ddcutil detect`
models=(LC49G95T)
# brightness is feature 10, see `ddcutil capabilities -l LC49G95T`
feature=10
action=$1
# you see ddcutil requires root permissions
# (may be in your system it doesn't; try to run it without sudo)
# and if you don't want to enter password every time, you have several options
# 1. add the following line to /etc/sudoers: yourusername ALL=(ALL) NOPASSWD: /usr/bin/ddcutil
#    (the name of the group depends on your distro, in Fedora it's wheel, in Ubuntu it may be sudo or sudoers)
# 2. add sticky bit to the script: sudo chmod +s /usr/bin/ddcutil
#    (this is a security risk because of after that any user will be able to run ddcutil as a root)
current_value=$(sudo ddcutil getvcp -l ${models[0]} $feature | sed -r "s/^.*current value = \s+([0-9]+).*$/\1/g")
if [[ -z $current_value ]]; then
  echo "Failed to get current brightness level from ddcutil"
  rm /tmp/.brightness_sh
  exit 1
fi

echo "Current brightness is $current_value"
case $action in
  up)
    if [[ $current_value == 100 ]]; then
      # notify-send is a tool to show a fancy desktop notification with a text message
      # it's a part of libnotify, see `man notify-send`
      # most likely it's already installed in your distro
      notify-send "Brightness is at maximum level"
      rm /tmp/.brightness_sh
      exit 2
    fi
    value=$(($current_value+10))
    if [[ $value -gt 100 ]]; then value=100; fi
    ;;
  
  down)
    if [[ $current_value == 0 ]]; then
      notify-send "Brightness is at minimum level"
      rm /tmp/.brightness_sh
      exit 2
    fi
    value=$(($current_value-10))
    if [[ $value -lt 0 ]]; then value=0; fi
    ;;
  
  *)
    echo "Unknown action \"$1\", expected \"up\" or \"down\""
    rm /tmp/.brightness_sh
    exit 3
    ;;
esac

notify-send "Setting brightness to $value"
for model in ${models[@]}; do
  sudo ddcutil setvcp -l $model $feature $value
done  
rm /tmp/.brightness_sh

Investigation story

Any search engine of choice can reveal that ddcutil is the helper here.

ddcutil --help or man ddcutil throws at us a list of commands. “detect” looks promising for a start, let’s try it.

$ sudo ddcutil detect
Display 1
   I2C bus:  /dev/i2c-3
   DRM connector:           card1-HDMI-A-1
   EDID synopsis:
      Mfg id:               SAM - Samsung Electric Company
      Model:                LC49G95T
      Product code:         28754  (0x7052)
      Serial number:        H4ZT902372
      Binary serial number: 1129859672 (0x43584a58)
      Model year:           2245
   VCP version:         2.1

Good, it agrees that I have a monitor. Does it allow us to do something? Using the model name, we can question the available features.

$ sudo ddcutil capabilities -l LC49G95T
...
Feature: 10 (Brightness)
...

It seems we’re on the right track. Let’s dig deeper.

$ sudo ddcutil -l "LC49G95T" getvcp 10
VCP code 0x10 (Brightness                    ): current value =    30, max value =   100

We can get the brightness, can we set it?

$ sudo ddcutil --l LC49G95T setvcp 10 100

Yep, the monitor is definitely much brighter now. Let’s switch it back till we’re not blind. Now we have everything we need to write a script.

Samsung Odyssey G9

For fellow owners of this monitor. If you connected it via DisplayPort, most likely ddcutil detect will make you upset with a disappointing message.

$ sudo ddcutil detect
Invalid display
   I2C bus:  /dev/i2c-4
   DRM connector:           card1-DP-1
   EDID synopsis:
      Mfg id:               SAM - Samsung Electric Company
      Model:                LC49G95T
      Product code:         28755  (0x7053)
      Serial number:        H4ZT902372
      Binary serial number: 1129859672 (0x43584a58)
      Model year:           2245
   DDC communication failed

That’s because this model doesn’t support DDC/CI over DisplayPort.

But there’s a trick to save us. Connect the very same monitor to the very same PC via HDMI (I hope you have a spare HDMI output on your video card).

Your PC will consider it as two outputs. Of course you will use DisplayPort for actual output. You should disable the HDMI output in your desktop environment settings. In KDE it’s done easy:

  • Settings
  • Display and Monitor
  • Display Configuration
  • Choose the HDMI output in the dropdown list
  • Uncheck “Enabled”
  • Apply

And now the magic part of it. Despite of the fact that the HDMI output is disabled for use by the desktop environment, ddcutil detect will find it and commands sent to it will be applied to the monitor!

$ sudo ddcutil detect
Display 1
   I2C bus:  /dev/i2c-3
   DRM connector:           card1-HDMI-A-1
   EDID synopsis:
      Mfg id:               SAM - Samsung Electric Company
      Model:                LC49G95T
      Product code:         28754  (0x7052)
      Serial number:        H4ZT902372
      Binary serial number: 1129859672 (0x43584a58)
      Model year:           2245
   VCP version:         2.1

Invalid display
   I2C bus:  /dev/i2c-4
   DRM connector:           card1-DP-1
   EDID synopsis:
      Mfg id:               SAM - Samsung Electric Company
      Model:                LC49G95T
      Product code:         28755  (0x7053)
      Serial number:        H4ZT902372
      Binary serial number: 1129859672 (0x43584a58)
      Model year:           2245
   DDC communication failed