Desktop control of external monitor settings with ddcutil and vdu_controls

I recently sought a way to adjust the brightness of a desktop monitor without having to use its physical controls. This post describes what I discovered and introduces a system-tray GUI that I created to control my monitors. Those primarily interested in the GUI can skip over the first part of the post.

A Brief Intro to DDC/MCCS for Desktop Control of Monitors

Utilities such as xrandr and xset provide some control over display settings, but these utilities can’t change the physical monitors settings such as brightness and contrast (DPMS settings excepted). For example, if a monitor’s backlight brightness is currently set to 13%, using xrandr to increase brightness just squishes the darker parts of the range up toward 13%, which is not the same as using the monitor’s physical controls to increase backlight brightness beyond 13%.

There is a VESA standard for controlling monitor settings directly from a PC. The Data Channel Channel (DDC) standard is part of the spec for external-monitor connectivity. The standard includes the DDC Command Interface (DDC/CI) which is a means for PC’s to pass commands to monitors. Actual DCC/CI commands are defined by the Monitor Control Command Set (MCCS) which includes a set of Virtual Control Panel (VCP) codes that provide read and write access to specific monitor settings. Most monitors made in the last decade have some level of support for DDC/MCCS. Onboard laptop displays don’t implement DDC, so DDC/MCCS is only applicable for externally connected monitors. USB connected external monitors normally support MCCS, but over USB, not DDC.

The ddcutil DDC/MSSC Command Line Tool

There are a few open source DDC utilities. The most practical Linux DDC utility is the command line tool ddcutil. What makes ddcutil practical is that it copes well with the spectrum of DDC and USB MCCS implementations as well as the sometimes unreliable nature of DDC communications. As an added bonus, ddcutil comes pre-packaged for Tumbleweed.

There is one core dependency for ddcutil, DDC is a i2c based protocol, so ddcutil requires the i2c-dev kernel-module. In addition to ensuring i2c-dev is loaded, those using Nvidia’s GPU driver will need to follow some ddcutil documentation to set a reliable i2c-dev speed. Full configuration instructions can be found in the ddctuil man page, the packaged help files, and at ddcutil.com.

When starting out with ddcutil, the first thing to do is to see if it can detect any monitors, for example:


% ddcutil detect --terse
Display 1
   I2C bus:             /dev/i2c-5
   Monitor:             HWP:HP ZR24w:CNT008XXXX

Display 2
   I2C bus:             /dev/i2c-8
   Monitor:             GSM:LG HDR 4K:


The reported display numbers do not correspond to X11 display numbers, they are monitor connection numbers. The numbering may change if a monitor is switched off or unplugged, if that’s a concern, ddcutil may be passed other forms of identification such as the model or serial number.

Once we know what displays are present we can send VCP codes to control the individual monitors. There are a huge number of VCP codes. Not all VCP codes are useful and not all are standardised. The ddcutil capabilities command can be used to discover what VCP codes a monitor claims to support. For example:


% ddcutil --display 2 capabilities
Model: Not specified
MCCS version: 2.1
VCP Features:
   Feature: 02 (New control value)
   Feature: 04 (Restore factory defaults)
   Feature: 05 (Restore factory brightness/contrast defaults)
   Feature: 08 (Restore color defaults)
   Feature: 10 (Brightness)
   Feature: 12 (Contrast)
   Feature: 14 (Select color preset)
      Values:
         05: 6500 K
         08: 9300 K
         0b: User 1
   Feature: 16 (Video gain: Red)
...
   Feature: 62 (Audio speaker volume)
   Feature: 8D (Audio Mute)
   Feature: F4 (manufacturer specific feature)
   Feature: F5 (manufacturer specific feature)
      Values: 00 01 02 (interpretation unavailable)
...

Some monitors are not 100% accurate or complete in their capability claims. I have one monitor that claims to have two DisplayPort input sources, but it physically only has one (changing the input to the imaginary one does nothing).

For safety ddcutil will only allow write access to known/supported codes, mystery manufacturer specific codes are solely read-only. Continuing the example from above, display 2 supports VCP code 10, the code for the brightness control, the monitors real brightness can be retrieved or set as follows:


% ddcutil --display 2 getvcp 10
VCP code 0x10 (Brightness                    ): current value =    50, max value =   100

% ddcutil --display 2 setvcp 10 90

Scripts can be written to streamline their use of particular codes. For example, I use the following brightness altering script to set the brightness on one or more monitors at a time:


#!/bin/bash
# Content of $HOME/bin/brightness
# Reads parameters: displayId newBrightness [displayId newBrightness...]
while  $# -ge 2 ]
do

    ddc_display_id="$1"
    new_brightness="$2"

    shift;shift

    old_brightness=$(ddcutil --brief --display $ddc_display_id getvcp 10 | awk '{print $4}')

    if  $new_brightness -ne $old_brightness ]
    then
        echo "INFO: $ddc_display_id setvcp 10 $new_brightness"
        ddcutil --display $ddc_display_id setvcp 10 $new_brightness
    else
        echo "INFO: $ddc_display_id already set to $new_brightness"
    fi
done

In my case I normally have two monitors connected, monitors 1 and 2, I can use the above script to set them to 80% and 90% brightness as follows:


        /home/michael/bin/brightness 1 80 2 90

Commands and scripts such as the above can be hooked into the desktop start menu by creating KDE/Gnome desktop files in an application menu or the favourites menu. For example, my kickoff menu includes a couple of favourites for brightening and dimming my monitors for daytime and nighttime, and further favourite for monitor DPMS suspend:


The desktop entry files for the above can be constructed by right mousing on the kicker icon, selecting to edit applications, selecting an appropriate menu, and adding a new item. Alternatively .desktop files can be manually created in $HOME/.local/share/applications. My $HOME/.local/share/applications/bright\ display.desktop file contains:


[Desktop Entry]
Comment=
Exec=/home/michael/bin/brightness 1 80 2 90
GenericName=Brighten all monitors
Icon=high-brightness
Name=Brighten Display
NoDisplay=false
Path$e]=
StartupNotify=false
Terminal=0
TerminalOptions=
Type=Application
X-KDE-SubstituteUID=false
X-KDE-Username=

The file for dimming is also the same, only the brightness values and Name are different. The script associated with the favorite for Suspend Displays, doesn’t require ddcutils, loginctl lock-session and xset dpms force suspend is all that is needed.

A System-tray GUI for ddcutil - vdu_controls

As an exercise in Qt Python scripting I created vdu_controls, a system tray app with access to a subset of MCCS controls. Here are a few screenshots of vdu_controls in various configurations:

  1. *vdu_controls --show brightness --show audio-volume
    https://raw.githubusercontent.com/digitaltrails/vdu_controls/master/screen-shots/Screenshot_Small-227.png
    *vdu_controls with all useful controls activated (additional “less useful” controls can be activated via the command line)
    https://raw.githubusercontent.com/digitaltrails/vdu_controls/master/screen-shots/Screenshot_Large-330.png
  2. vdu_controls --no-splash --system-tray
    https://raw.githubusercontent.com/digitaltrails/vdu_controls/master/screen-shots/Screenshot_tray-200.png

The code for vdu_controls is available on github as well as a detailed README.md and man page. The script was developed on Tumbleweed using the default python3.8 with the additional zypper installs of python38-qt5 and ddcutil.

I tried to write vdu_controls to be as self contained as possible. If ddcutil and python38-qt5 are installed, a copy of the vdu_controls.py script is all that is needed. Supporting icons and a default splash-screen image are embedded inside the script. The script can optionally self install itself into $HOME/bin along with creating an appropriate .desktop menu file. For development all that is needed is an editor and the python3 command. I have added all the normal python source-hierarchy boilerplate for documentation and builds, but only as a learning exercise, none of that is necessary (it’s overkill for one self-contained script).

Possibilities for automatically adjusting brightness according to the ambient light level

One other idea I toyed with was to use an old webcam to measure the ambient brightness and then adjust my monitors automatically as conditions changed. I used ffmpeg and ImageMagick to try and measure the ambient light level from a webcam image, for example:


# Capture one frame
ffmpeg -y -s 1024x768 -i /dev/video0 -frames 1 $HOME/tmp/out.jpg 2>>$logfile
# Resize to one pixel and extract the pixel (max) value, then use variuous substitutions to extract just the numberic value.
ambient=$(convert $HOME/tmp/out.jpg -colorspace gray -resize 1x1 -evaluate-sequence Max -format "%[fx:100*mean]" info: 2>/dev/nulll)
ambient=$(echo $ambient | sed 's/.].*//')
echo INFO: camera ambient maximum $ambient

That didn’t work too well because the camera had built-in automatic exposure, if the ambient light level dropped, the camera increased the exposure. I could roughly determine between night and day, but if clouds moved across the sun my heuristics could get fooled. Perhaps better camera positioning, a fixed image target, some blinkers, or a frosted lens cover might have helped. If I had some better light-metering hardware, some form of automatic brightness adjustment would be quite achievable.

Final thoughts

I hope this post up might help if you need to control a monitor without reaching for it’s physical controls, or if you need to climb the learning curve on Python or PyQt, or if you’d just like to add some desktop favourites of your own.

While we’re on monitor related issues I have another howto post on KDE multiple monitors with different resolutions. If you want to combine a new 4K monitor with older non-4k monitors for an X11 desktop, that post might be worth a look.

BTW, Sanford Rockowitz, the author of ddcutil is also working on a comprehensive GUI, but I think smaller/lighter GUI scripts might still be useful for places such as the system tray.

1 Like

There is now an openSUSE Build Service community package for vdu_controls for Tumbleweed, Leap and Fedora at https://software.opensuse.org/package/vdu_controls

The built packages include the following major features added since my original post:

  • A named presets facility for switching between a set of VDU configurations such as night
    , day, photography, and video. - A default settings file and VDU specific settings files.
  • GUI Settings editor with a tab for default settings and a tab for each VDU.
  • All settings and presets files are now INI files for ease of editing.
  • Grayscale calibration chart to assist with adjustments.

The build service packaging mainly adds additional files such as a man-page, separate licence file as well as installing those for all users (under /usr/…). It’s still possible to download the very latest python script from github and use that directly.

The vdu_controls github and OpenSUSE builds have been updated the v1.5.3.

This version adds support for light/dark desktop theme changes, the icon colors will change according to the set theme, both at startup and while running.

I’ve released 1.5.4 to address a couple of issues with first time use: Fails on first run due to missing config file · Issue #6 · digitaltrails/vdu_controls · GitHub. The symptoms were errors on first startup when no config files exist. Existing users with established config files would not have noticed.

Github and OpenSUSE builds have been updated.

Thanks to some suggestions and contributions from Matthew Coleman (https://github.com/crashmatt) I’m releasing version 1.5.5.

https://software.opensuse.org/package/vdu_controls

Changes include:

  • Prevent app-exit when running from the tray - by Matthew Coleman. This happens for some desktops, but not my own Tumbleweed KDE desktop where Qt appears to be tray aware, so KDE users may not notice any difference.
  • Fixed the handling of CNC (Complex Non-Continuous) VCP value-types shown as combo-boxes, such as VCP-code 14 Color-Presets.
  • Recover gracefully from unexpected NC/CNC values (due to incorrect metadata being supplied by the VDU hardware).
  • Default to only enabling brightness and contrast, the ones users are most likely to actually want (most VDU’s reliably support these two, plus startup is faster).
  • Enable automatic restart at login via KDE/X11 desktop session management (borrowed code from https://forums.opensuse.org/showthread.php/561962-Jouno-a-different-way-of-tracking-system-activity).
  • Save/restore Qt window state/geometry across restarts - the main window should now stay where you put it.

If you have put in place anything to start vdu_controls at login, this may no longer be necessary. It depends on which desktop you use, but on my KDE desktop vdu_controls will now be restored automatically if it was running at logout/shutdown. On the odd occasion KDE may fail to restart all the apps that were running, but this doesn’t happen very often.

Version 1.5.7:

  • Check if a system-tray is available before applying system-tray-enabled. For those who use the same login for different desktops (KDE, knome, openbox, …)

https://software.opensuse.org/package/vdu_controls

https://github.com/digitaltrails/vdu_controls

v1.6.3

This release is partly aimed at assisting those affected by issue #18 (https://github.com/digitaltrails/vdu_controls/issues/18), which relates to running vdu_controls in the system tray extension (https://github.com/ubuntu/gnome-shell-extension-appindicator/releases/tag/v42) of the recently released Gnome 42. For the moment it’s best not to use vdu_controls in the system tray of Gnome 42. Users of KDE, Deepin, and Gnome prior to 42 are not affected by the issue.

The changes:

  • Added a hamburger menu as an obvious alternate to the right-mouse button for accessing the context menu.
  • Minor cosmetic UI changes: cleaned up the bottom button and progress bar, better sizing on first use.

There have been some other minor releases since the last post here concerning v1.5.7, those interested can see https://github.com/digitaltrails/vdu_controls/releases for details.

Link to unofficial OpenSUSE rpms:https://software.opensuse.org/package/vdu_controls

Github:

https://github.com/digitaltrails/vdu_controls

Screenshot:

https://user-images.githubusercontent.com/5510901/162596342-1f96123c-67c0-4fe9-b326-5ddf220327af.png

A small update to deal with some feedback I’ve received:

  • A fix for Plasma on Wayland - on autostart at login, wait for system tray to become available. This is not an issue for X11 users: on X11, the application is automatically restored if it was running at logout/shutdown, and on X11, the system tray is already available at the time the application is restored.
  • Enable HiDPI icons to fix the blurred toolbar icons on desktops displays scaled to greater than 100% (for the bottom toolbar).

Link to unofficial OpenSUSE rpms:https://software.opensuse.org/package/vdu_controls

Github:

https://github.com/digitaltrails/vdu_controls

A new release of VDU controls is in software.opensuse.org, it rolls up changes in v1.6.5 through 1.6.7https://software.opensuse.org/package/vdu_controls
https://github.com/digitaltrails/vdu_controls

v1.6.5 included more error handling when a monitors capabilities cannot be obtained.

v1.6.6 and v1.6.7 improves the gnome experience when running from the gnome system tray. The system tray behaviour now follows gnome (gnome 42.2 Manjaro) conventions:

  • Under gnome, when running from the tray, there is no obvious way to reach the main window (the unobvious way is to use the middle-mouse click). When running from the gnome tray I’ve added an Control-Panel menu item to provide an obvious way of bringing up the main window.
  • Under gnome, when running from the tray, if the tray context-menu is used to open a minor window, such as the help window, closing the minor window will terminate the application. The current workaround is that if the tray context-menu is used to open a minor window, the main window will also open - this prevents the closing the minor window from terminating the application.

https://user-images.githubusercontent.com/5510901/173207967-e9079d87-5e62-4d22-a0d6-86c2f7ae60b7.png](https://user-images.githubusercontent.com/5510901/173207967-e9079d87-5e62-4d22-a0d6-86c2f7ae60b7.png)

When not running from the tray, all desktops (KDE, deepin, gnome) behave the same.

This release is focused on improving the stability when faced with invalid or unstable values returned by ddcutil.

  • Cope better with invalid slider values caused by a monitor being too slow or too busy when responding.
  • During login/startup wait for the number of detected monitors to stabilise (during login monitors may be busy and fail to correctly respond to ddcutil).

To help with diagnosing stability issues while logging in, messages written to stdout may also be forwarded to the python syslog library (controlled by the new syslog setting). On openSUSE python syslog will forward these messages to the systemd journal.

https://software.opensuse.org/package/vdu_controls
https://github.com/digitaltrails/vdu_controls

(RPM builds have also been corrected to depend on python3 rather than python3.8.)

Available as a community RPM at https://software.opensuse.org/package/vdu_controls?search_term=vdu_controls
Or directly from github: https://github.com/digitaltrails/vdu_controls

Release notes: https://github.com/digitaltrails/vdu_controls/releases/tag/v1.7.0):
This version introduces many enhancements to the preset functionality as well as a more robust VDU identification for a multi-monitor desktops.

The following screenshot has been extracted from the release-notes and illustrates the main Preset enhancements (see the release-notes for explanations):

https://user-images.githubusercontent.com/5510901/185301922-194ff9ca-dc05-43c1-b541-273a385acc6f.png](https://user-images.githubusercontent.com/5510901/185301922-194ff9ca-dc05-43c1-b541-273a385acc6f.png)

Available as a community RPM at https://software.opensuse.org/packag…m=vdu_controls
Or directly from github: https://github.com/digitaltrails/vdu_controls

Release notes: (https://github.com/digitaltrails/vdu…ses/tag/v1.8.0):
This is a new-feature release. Preset activation may now be triggered by solar elevation. The idea being to automatically change presets according to the prevailing illuminance, such as dawn or dusk, or the sun rising above the surrounding terrain. The time of which these presets should trigger varies as the seasons change, hence the use of the solar elevation.

https://user-images.githubusercontent.com/5510901/200098047-975198f9-6eea-4fcb-9cc1-6b869778400d.png](https://user-images.githubusercontent.com/5510901/200098047-975198f9-6eea-4fcb-9cc1-6b869778400d.png)

To assign a trigger, use the Preset Dialog to set a preset’s solar-elevation. A solar elevation may range from -19 degrees in the eastern sky (morning/ascending) to -19 degrees in the western sky (afternoon/descending), with a maximum nearing 90 degrees at midday.

If a preset has an elevation, it will be triggered each day at a time calculated by using the latitude and longitude specified by in the vdu-controls-globals location option.

By choosing an appropriate solar-elevation a preset may be confined to specific times of the year. For example, a preset with a positive solar elevation will not trigger at mid-winter in the Arctic circle (because the sun never gets that high). Such a preset may always be manually selected regardless of its specified solar elevations.

On any given day, the user may temporarily override any trigger, in which case the trigger is suspended until the following day. For example, a user might choose to disable a trigger intended for the brightest part of the day if the day is particularly dull,

At startup vdu_controls will restore the most recent preset that would have been triggered for this day (if any). For example, say a user has vdu_controls set to run at login, and they’ve also set a preset to trigger at dawn, but they don’t actually log in until just after dawn, the overdue dawn preset will be triggered at login.

I’ve released vdu_controls v1.8.2 - a monitor/VDU control panel. Full release notes are available in the usual location: https://github.com/digitaltrails/vdu_controls/releases/tag/v1.8.2

Community packages: https://software.opensuse.org/package/vdu_controls
Source: https://github.com/digitaltrails/vdu_controls

Highlights:

  • Weather restrictions - presets scheduled by solar-elevation can be subject to optional weather-requirements. Weather requirements will be checked against the weather reported by https://wttr.in.
  • Improved solar elevation trigger behaviour when monitors are offline.
  • Language translations, with sample alpha-level translations included for Danish (da_DK), French (fr_FR), and German (de_DE).

https://user-images.githubusercontent.com/5510901/204110819-7baf0862-7ec4-43c2-b6ec-94f064f6fbf0.png

vdu_controls v1.9 has been released:

Downloads:

vdu_controls is available as a community package for Tumbleweed, Leap and Fedora at:

https://software.opensuse.org/package/vdu_controls

Or as an Arch AUR package from:
https://aur.archlinux.org/packages/vdu_controls (may take a day or so to update)

Release Notes:

  • 1.9.0
    • Bug fixes and speedy performance improvements:
      • Speed up initialization and refresh by combining multiple ddcutil getvcp requests.
      • Stop executing a getvcp precheck before each setvcp.
      • Fix repeat-initialisation bug in Context-Menu Refresh.
      • Fix Settings Dialog text field validation, some errors were invisibly ignored.
      • Fix Settings Dialog Settings Enable VCP Codes, they had stopped working.
      • Fix the monitor specific sleep multipliers, they were not always being used.
      • Treat all monitor detection situations as needing time to stabilise (helps in disconnect situations).
      • Fix event handling so that tablet+pen input works on the main window.
      • Default to a sleep-multiplier of 1.0 to support a wider range of monitors out of the box.
    • V1.9.0 drops support for converting from v1.6.* config and preset files. To convert
      from v1.6.* and earlier versions, follow these steps to download and run v1.8.3:
       % wget https://github.com/digitaltrails/vdu_controls/blob/v1.8.3/vdu_controls.py
       % python3 vdu_controls.py
      
      Alternatively, start fresh by moving or removing the old configs from $HOME/.config/vdu_controls.

Bugs and Suggestions:

If you encounter a bug or issue, or wish to make a suggestion, you’re most welcome to raise it here or on the github issues page.

Moved from forums feedback to applications category.

OK - but it seems to bury the post.

@mchnz I see a couple of options, push it to a development project (eg utilities), then onto Factory, and/or create an openSUSE Wiki page about the application?

@malcolmlewis I looked into the process of getting it into factory a while back by using the Build Service Submit Package. I didn’t get very far. If I recall correctly, at some point I got and error (perhaps an email?) that seemed to imply someone already involved with factory had to sponsor it. So I gave up. If you have any links to the process to follow, they’d be much appreciated.

Michael

@mchnz all you have to do is 1. submit to a development repo, I suggest utilities as a new package and request to be the maintainer, then once accepted you can 2. forward to factory, this takes a bit longer as it has to go through legal review etc.

@mchnz It does need a clean up though (run spec-cleaner over it), and also a changes file and remove the old tarballs. Do you use osc or just the web ui?