Brightness control of external monitors with ddcci-backlight

I have a desktop connected to two external monitors. I usually change the brightness and contrast of these monitors multiple times a day and I’d like to have an easy way to change the brightness and contrast.

So far, I’ve mostly been following the answers on this stackexchange question. Using this answer I was able to some scripts to change the brightness and contrast.

However, I’d rather have a slider I can adjust. This other answer states that the ddcci-backlight module should enable the brightness slider:

The DDC/CI kernel module includes a ddcci-backlight module which can integrate most DDC/CI-capable monitors into the kernel’s backlight system ( /sys/class/backlight ). This allows any tool which can use the latter to drive the backlight on a DDC/CI monitor.

The Arch Linux wiki states something similar:

Alternatively, one may use ddcci-driver-linux-dkmsAUR to expose external monitors in sysfs. Then, after loading the ddcci kernel module, one can use any backlight utility.

After some googling I found the ddcci-driver kernel module, which I enabled by including
boot.extraModulePackages = with config.boot.kernelPackages; [ ddcci-driver ];

in my configuration.nix. However, this doesn’t change anything. No slider shows up in the power management, and when I install acpilight and try to change the brightness it seems to have no effect. Also, when I list the devices no monitor seems to show up:

ruben@nixos> sudo xbacklight -l                                                                                   ~
input0::compose
input0::scrolllock
input0::kana
input0::capslock
input0::numlock

I haven’t done anything with kernel modules before and it feels like I’m missing steps here. Is there anyone who got brightness control for external monitors working on NixOS? I can’t imagine that I’m the only one who wants this, yet I can barely find any information on it.

2 Likes

do you know about services.redshift = { enable = true; };
(you can control brightness between day and night)

redshift is about changing the color temperature - not the brightness.

@ruben, if you’re using KDE, it’s possible to do directly from KDE since powerdevil: add DDC support by peterhoeg · Pull Request #88942 · NixOS/nixpkgs · GitHub

2 Likes

Redshift has a brightness adjustment setting but it does not work the way most people might expect. In fact it is a fake brightness adjustment obtained by manipulating the gamma ramps which means that it does not reduce the backlight of the screen. Preferably only use it if your normal backlight adjustment is too coarse-grained

well, how to check which tools is doing it?

Indeed I’m using KDE, so that’s great news, thanks :slight_smile:

Could you elaborate on how I can enable it? From the code you merged to master I would say I should put

services.xserver.desktopManager.plasma5.supportDDC = true;

in my configuration.nix file, but this doesn’t work. Maybe I need a newer version of the KDE (or at least the configuration file). Not sure, I’m not so experienced with NixOS…

services.xserver.desktopManager.plasma5.supportDDC = true;

Yep, that’s it.

Then click “Battery and Brightness” in the tray and you should see a slider for “Display Brightness”.

Please note that if you have multiple monitors, you will be setting the brightness on both.

Thanks, but I get a ‘option does not exist’ error:

ruben@nixos> sudo nixos-rebuild switch                                                               ~
building Nix...
building the system configuration...
error: The option `services.xserver.desktopManager.plasma5.supportDDC' defined in `/etc/nixos/configuration.nix' does not exist.
(use '--show-trace' to show detailed location information)

I guess I somehow need to update something?

Ah, that bit is only in unstable. If you’re on the 20.03 (and want to stay on a release channel), you’ll have to wait for 20.09. Alternatively, you can just switch to unstable which is what I’m doing on on workstations.

1 Like

For me, the issue was that the proprietary nvidia driver doesn’t work correctly with ddcci. I found a way to force the display to be detected by the ddcci driver correctly. But you have to wait until after the i2c device shows up before you can force it, and the kernel module has to be loaded after that. Since I couldn’t find a proper systemd dependency to do this at the right time, I just threw the necessary commands in displayManager.setupCommands.

{ config, pkgs, ... }: {
  boot.extraModulePackages = [config.boot.kernelPackages.ddcci-driver];
  services.xserver.displayManager.setupCommands = ''
    echo 'ddcci 0x37' | tee /sys/bus/i2c/devices/i2c-5/new_device
    ${pkgs.kmod}/bin/modprobe ddcci_backlight
  '';
}

Where i2c-5 is the i2c device for my display. Also, plasma5.supportDDC = true; enables i2c-dev, and note from the Arch wiki:

Using ddcci and i2c-dev simultaneously may result in resource conflicts such as a Device or resource busy error.

3 Likes

Following the instructions from this stackoverflow answer, I ran

sudo nix-channel --add https://nixos.org/channels/nixos-unstable nixos
sudo nixos-rebuild switch --upgrade

I then added the line

services.xserver.desktopManager.plasma5.supportDDC = true;

to my configuration.nix. After running

sudo nixos-rebuild switch

and restarting (I think logging out and in again should work as well), it works! :slight_smile:

@peterhoeg Thank you so much! You have no idea how much time I spent trying to get this working (in fact I gave up several times) :slight_smile:

Do you need the ddcci-driver for things to work in KDE or is that for a different use-case?

No, you’re right; It’s not necessary. I have removed it from my last post.

I’m now using this config on my Gnome setup with an Nvidia card, and it seems to work well:

{ lib, config, pkgs, ... }: {
  boot.extraModulePackages = [config.boot.kernelPackages.ddcci-driver];
  boot.kernelModules = ["i2c-dev" "ddcci_backlight"];

  services.udev.extraRules = ''
    SUBSYSTEM=="i2c-dev", ACTION=="add",\
      ATTR{name}=="NVIDIA i2c adapter*",\
      TAG+="ddcci",\
      TAG+="systemd",\
      ENV{SYSTEMD_WANTS}+="ddcci@$kernel.service"
  '';

  systemd.services."ddcci@" = {
    scriptArgs = "%i";
    script = ''
      echo Trying to attach ddcci to $1
      i=0
      id=$(echo $1 | cut -d "-" -f 2)
      if ${pkgs.ddcutil}/bin/ddcutil getvcp 10 -b $id; then
        echo ddcci 0x37 > /sys/bus/i2c/devices/$1/new_device
      fi
    '';
    serviceConfig.Type = "oneshot";
  };

}
2 Likes

Looks like the ddcci_backlight kernel module is now active by default.

I’m also on Nvidia and weirdly, I have to run the detection forcing (echo ddcci 0x37 > /sys/bus/i2c/devices/$1/new_device) from my user session, and with a substantial delay (around 10s after X comes up) otherwise there is some kind of race condition and the devices never show up in /sys/class/backlight.

->Full config that makes this work on i3 with lots of hacks

I’ve also found in recent nixos-unstable revisions that I have to delay the echo ddcci 0x37 part, so now the script in my service from the comment above looks like this:

echo Trying to attach ddcci to $1
i=0
id=$(echo $1 | cut -d "-" -f 2)
counter=5
while [ $counter -gt 0 ]; do
  if ${pkgs.ddcutil}/bin/ddcutil getvcp 10 -b $id; then
    echo ddcci 0x37 > /sys/bus/i2c/devices/$1/new_device
    break
  fi
  sleep 1
  counter=$((counter - 1))
done

But otherwise, it works the same with the udev rule, so I don’t have to do anything manually other than make sure to give it a second before logging in and starting Gnome (otherwise Gnome doesn’t pick up the backlight that appears after logging in).

1 Like

Love your solution, for some reason I’m not getting any i2c-dev udev events, even with the i2c-dev kernel module loaded :thinking: but I guess that doesn’t really belong in this thread.

For folks who might need it in the future, here’s a bunch of things I used for my AMD GPU. This exposes the monitor under /sys/class/backlight and lets me write to the brightness file without root. I’m not sure whether all the configuration here is absolutely necessary, but it works!

{config, pkgs, ...}: {
  hardware.i2c.enable = true;
  users.users.myusername.extraGroups = [ "i2c" "video" ];
  boot.extraModulePackages = [ config.boot.kernelPackages.ddcci-driver ];
  boot.kernelModules = [ "i2c-dev" "ddcci_backlight" ];
  environment.systemPackages = [ pkgs.ddcutil ];
  services.udev.packages = [ pkgs.ddcutil ];

  services.udev.extraRules = ''
    ACTION=="add", SUBSYSTEM=="i2c-dev", ATTR{name}=="AMDGPU DM*", TAG+="ddcci", TAG+="systemd", ENV{SYSTEMD_WANTS}+="ddcci@$kernel.service"
    ACTION=="add", SUBSYSTEM=="backlight", KERNEL=="ddcci*", RUN+="${pkgs.coreutils-full}/bin/chgrp video /sys/class/backlight/%k/brightness"
    ACTION=="add", SUBSYSTEM=="backlight", KERNEL=="ddcci*", RUN+="${pkgs.coreutils-full}/bin/chmod a+w /sys/class/backlight/%k/brightness"
  '';

  systemd.services."ddcci@" = {
    scriptArgs = "%i";
    script = ''
      echo Trying to attach ddcci to $1
      id=$(echo $1 | cut -d "-" -f 2)
      counter=5
      while [ $counter -gt 0 ]; do
        if ${pkgs.ddcutil}/bin/ddcutil getvcp 10 -b $id; then
          echo ddcci 0x37 > /sys/bus/i2c/devices/$1/new_device
          echo Successfully attached ddcci to $1
          break
        fi
        sleep 1
        counter=$((counter - 1))
      done
    '';
    serviceConfig.Type = "oneshot";
  };
}