#+title: Changing screen brightness #+date: <2022-12-17 Sat 11:33> #+author: thebesttv - [[https://wiki.archlinux.org/title/Backlight][Backlight - ArchWiki]] introduces different ways to adjust screen backlight, for both laptops and external monitors. * Laptop screen Use =xbacklight= from the [[https://archlinux.org/packages/extra/x86_64/xorg-xbacklight/][=xorg-xbacklight=]] package to adjust brightness for laptop screens: #+begin_src bash $ sudo pacman -S xorg-xbacklight #+end_src Control brightness using =-set=, =-inc=, =-dec=: #+begin_src bash $ xbacklight -set 20 # set brightness to 20 $ xbacklight -inc 20 # +20 $ xbacklight -dec 20 # -20 #+end_src By default, brightness gradually fades into the target value---20 steps in 200ms. Set =-steps= to 1 to avoid the fading effect: #+begin_src bash $ xbacklight -steps 1 -dec 10 #+end_src Get current brightness using =-get=: #+begin_src bash $ xbacklight -get 60.000000 $ xbacklight -get | sed 's/\..*//' # remove decimal part 60 #+end_src i3 config: #+begin_src bash bindsym XF86MonBrightnessDown exec --no-startup-id \ xbacklight -steps 1 -dec 10 &&\ notify-send -t 1000 "Laptop brightness $(xbacklight -get | sed 's/\..*//')" bindsym XF86MonBrightnessUp exec --no-startup-id \ xbacklight -steps 1 -inc 10 &&\ notify-send -t 1000 "Laptop brightness $(xbacklight -get | sed 's/\..*//')" #+end_src ** Problem: No outputs have backlight property On a fresh installation of Arch Linux, =xbacklight= may produce error: #+begin_src bash $ xbacklight -set 20 No outputs have backlight property #+end_src Installing =xf86-video-intel= will fix the problem: #+begin_src bash sudo pacman -S xf86-video-intel #+end_src * External monitors [[https://en.wikipedia.org/wiki/Display_Data_Channel][Display Data Channel]] (DDC) allows computer to adjust monitor parameters such as brightness and contrast. [[http://www.ddcutil.com/][=ddcutil=]] is a tool that can control monitors through DDC. - [[https://moverest.xyz/blog/control-display-with-ddc-ci/][Control your display with DDC/CI on Linux]] - [[https://wiki.archlinux.org/title/I2C][I2C - ArchWiki]] ** Install =ddcutil= First, install =ddcutil= and =i2c-tools=: #+begin_src bash $ sudo pacman -S ddcutil i2c-tools #+end_src *** Load I2C module The =i2c-dev= module needs to be loaded manually: #+begin_src bash $ modprobe i2c-dev #+end_src If the module is loaded properly, you should see a list of =/dev/i2c-*= devices: #+begin_src bash $ ls /dev/i2c-* /dev/i2c-0 /dev/i2c-2 /dev/i2c-4 /dev/i2c-6 /dev/i2c-8 /dev/i2c-1 /dev/i2c-3 /dev/i2c-5 /dev/i2c-7 #+end_src To load the module at boot, create =/etc/modules-load.d/i2c-dev.conf= with: #+begin_src text i2c-dev #+end_src *** Add to user group Grant RW access to the =/dev/i2c-*= devices by adding the current user to the =i2c= user group: #+begin_src bash $ sudo usermod $USER -aG i2c #+end_src Reboot to see the effect, or log in to new group for the current shell (the i3 config only works after reboot): #+begin_src bash $ newgrp i2c #+end_src ** Overview Running =ddcutil= on my laptop produces the following warnings: #+begin_src text Unable to open directory /sys/bus/i2c/devices/i2c--1: No such file or directory Device /dev/i2c-255 does not exist. Error = ENOENT(2): No such file or directory /sys/bus/i2c buses without /dev/i2c-N devices: /sys/bus/i2c/devices/i2c-255 Driver i2c_dev must be loaded or builtin See https://www.ddcutil.com/kernel_module #+end_src But the tool functions properly so far, so they can be ignored. All the results shown below will have the warning removed. Detect monitors: #+begin_src bash $ ddcutil detect Invalid display I2C bus: /dev/i2c-4 DRM connector: card0-eDP-1 EDID synopsis: Mfg id: CMN - Chimei Innolux Corporation Model: Product code: 5332 (0x14d4) Serial number: Binary serial number: 0 (0x00000000) Manufacture year: 2017, Week: 48 DDC communication failed This is an eDP laptop display. Laptop displays do not support DDC/CI. Display 1 I2C bus: /dev/i2c-5 DRM connector: card0-DP-1 EDID synopsis: Mfg id: AOC - UNK Model: Q27P2G5 Product code: 9986 (0x2702) Serial number: TAUNAHA006059 Binary serial number: 6059 (0x000017ab) Manufacture year: 2022, Week: 42 VCP version: 2.2 #+end_src Two monitors are detected: 1. =Invalid display=: my laptop's monitor, which, unsurprisingly, does not support DDC/CI. 2. =Display 1=: external AOC monitor, which supports DDC/CI, and is on bus 5 (=/dev/i2c-5=). This is the target screen. Show all VCP Feature Codes that =ddcutil= understands for display 1: #+begin_src bash $ ddcutil -d 1 getvcp known VCP code 0x02 (New control value ): One or more new control values have been saved (0x02) VCP code 0x0b (Color temperature increment ): 100 degree(s) Kelvin VCP code 0x0c (Color temperature request ): 3000 + 35 * (feature 0B color temp increment) degree(s) Kelvin VCP code 0x10 (Brightness ): current value = 50, max value = 100 VCP code 0x12 (Contrast ): current value = 50, max value = 100 VCP code 0x14 (Select color preset ): 6500 K (0x05), Tolerance: Unspecified (0x00) VCP code 0x16 (Video gain: Red ): current value = 50, max value = 100 VCP code 0x18 (Video gain: Green ): current value = 50, max value = 100 VCP code 0x1a (Video gain: Blue ): current value = 50, max value = 100 VCP code 0x1e (Auto setup ): Auto setup not active (sl=0x00) VCP code 0x20 (Horizontal Position (Phase) ): current value = 0, max value = 100 VCP code 0x30 (Vertical Position (Phase) ): current value = 0, max value = 100 VCP code 0x52 (Active control ): Value: 0x00 VCP code 0x60 (Input Source ): DisplayPort-2 (sl=0x10) VCP code 0x62 (Audio speaker volume ): Volume level: 80 (00x50) VCP code 0x6c (Video black level: Red ): current value = 80, max value = 100 VCP code 0x6e (Video black level: Green ): current value = 80, max value = 100 VCP code 0x70 (Video black level: Blue ): current value = 80, max value = 100 VCP code 0x7e (Trapezoid ): Maximum retries exceeded VCP code 0x86 (Display Scaling ): Max image, no aspect ration distortion (sl=0x02) VCP code 0x87 (Sharpness ): current value = 50, max value = 100 VCP code 0xac (Horizontal frequency ): 1093 hz VCP code 0xae (Vertical frequency ): 60.00 hz VCP code 0xb2 (Flat panel sub-pixel layout ): Red/Green/Blue vertical stripe (sl=0x01) VCP code 0xb6 (Display technology type ): LCD (active matrix) (sl=0x03) VCP code 0xc6 (Application enable key ): 0x0040 VCP code 0xc8 (Display controller type ): Mfg: RealTek (sl=0x09), controller number: mh=0x00, ml=0x00, sh=0x00 VCP code 0xc9 (Display firmware level ): 0.1 VCP code 0xca (OSD/Button Control ): OSD disabled, button events enabled (sl=0x01), Host control of power unsupported (sh=0x00) VCP code 0xcc (OSD Language ): Chinese (simplified / Kantai) (sl=0x0d) VCP code 0xd6 (Power mode ): DPM: On, DPMS: Off (sl=0x01) VCP code 0xdc (Display Mode ): Standard/Default mode (sl=0x00) VCP code 0xdf (VCP Version ): 2.2 #+end_src There are a lot of entries. The one we are interested in is the brightness (VCP code =0x10=), currently at 50, the maximum being 100: #+begin_src text VCP code 0x10 (Brightness ): current value = 50, max value = 100 #+end_src ** Changing & querying brightness Use =setvcp= to change brightness in different ways: #+begin_src bash $ ddcutil setvcp 10 25 # set brightness to 25 $ ddcutil setvcp 10 + 5 # brightness +5 $ ddcutil setvcp 10 - 5 # brightness -5 #+end_src Use =getvcp= (or =get=) to obtain current brightness: #+begin_src bash $ ddcutil getvcp 10 # get current brightness VCP code 0x10 (Brightness ): current value = 25, max value = 100 $ ddcutil getvcp --brief 10 # brief output: VCP <CODE> C <CUR> <MAX> VCP 10 C 25 100 $ ddcutil getvcp --brief 10 | cut -d' ' -f4 # only get current value 25 #+end_src ** Speedup The above use of =ddcutil= is simple, yet frustratingly slow: #+begin_src bash $ time sh -c "ddcutil setvcp 10 25 && ddcutil get --brief 10" VCP 10 C 25 100 real 0m1.079s user 0m0.061s sys 0m0.105s #+end_src Simply setting and getting brightness takes a whole 1 second. According to [[https://github.com/rockowitz/ddcutil/issues/240#issuecomment-991381421][rockowitz's comment]]: 1. =ddcutil= examines each =/dev/i2c-*= device on startup, which is a slow process. Specifying the monitor bus number with =--bus= (or =-b=) reduces ~180ms: #+begin_src bash $ time ddcutil setvcp 10 25 real 0m0.580s user 0m0.031s sys 0m0.068s $ time ddcutil --bus=5 setvcp 10 25 real 0m0.404s user 0m0.011s sys 0m0.017s #+end_src However, simply specifying a display number does not reduce time, as I2C devices are still examined to determine which one is used: #+begin_src bash $ time ddcutil --display=1 setvcp 10 25 real 0m0.579s user 0m0.028s sys 0m0.066s #+end_src 2. Most of the elapsed time is spent in pauses mandated by the DDC spec. Use =--sleep-multiplier= to adjust the length of time spent in mandated sleep. For example, =--sleep-multiplier=0.2= multiplies the sleep time by =0.2=: #+begin_src bash $ time ddcutil -b 5 --sleep-multiplier=0.2 --brief get 10 VCP 10 C 25 100 real 0m0.109s user 0m0.018s sys 0m0.008s #+end_src This significantly reduces the overall time, reaching 0.1s. However, reducing sleep time may incur DDC errors, see the [[https://github.com/rockowitz/ddcutil/issues/240#issuecomment-991381421][original comment]] for detail. 3. When using multiple monitors, using =--async= to add some parallelism. To summarize, *specifying bus number* and *reducing sleep time* together reduce the overall time to ~270ms, a 3.7x speedup (the redirection & =tail= command is used to filter out error messages mentioned earlier): #+begin_src bash $ time sh -c \ "ddcutil --bus=5 --sleep-multiplier=0.2 setvcp 10 50 >/dev/null &&\ ddcutil --bus=5 --sleep-multiplier=0.2 --brief getvcp 10 | tail -n1" VCP 10 C 50 100 real 0m0.267s user 0m0.053s sys 0m0.038s #+end_src Finally, the i3 config to ±10 brightness: #+begin_src text bindsym Shift+XF86MonBrightnessDown exec --no-startup-id \ ddcutil --bus=5 --sleep-multiplier=0.2 setvcp 10 - 10 &&\ notify-send -t 1000 "External brightness $(ddcutil --bus=5 --sleep-multiplier=0.2 --brief getvcp 10 | tail -n1 | cut -d' ' -f4)" bindsym Shift+XF86MonBrightnessUp exec --no-startup-id \ ddcutil --bus=5 --sleep-multiplier=0.2 setvcp 10 + 10 &&\ notify-send -t 1000 "External brightness $(ddcutil --bus=5 --sleep-multiplier=0.2 --brief getvcp 10 | tail -n1 | cut -d' ' -f4)" #+end_src