Adjust brightness with xrandr and cron job

EDIT Thanks to pa4080 I added one line to the script below and now it works great. I don't exactly understand how, oh well.

I would like to make a cron job to adjust my brightness at different hours of the day. After doing some googling and trial and error I wrote the following bash script that works well:

#!/bin/bash
export DISPLAY=$(w $(id -un) | awk 'NF > 7 && $2 ~ /tty[0-9]+/ {print $3; exit}')

H=$(date +%H)

if (( 00 <= 10#$H && 10#$H < 07 )); then
    xrandr --output HDMI-1 --brightness .3 && xrandr --output HDMI-2 --brightness .3 && xrandr --output HDMI-3 --brightness .3
elif (( 07 <= 10#$H && 10#$H < 10 )); then
    xrandr --output HDMI-1 --brightness .5 && xrandr --output HDMI-2 --brightness .5 && xrandr --output HDMI-3 --brightness .5
elif (( 10 <= 10#$H && 10#$H < 19 )); then
    xrandr --output HDMI-1 --brightness .7 && xrandr --output HDMI-2 --brightness .7 && xrandr --output HDMI-3 --brightness .7
elif (( 19 <= 10#$H && 10#$H < 22 )); then
    xrandr --output HDMI-1 --brightness .5 && xrandr --output HDMI-2 --brightness .5 && xrandr --output HDMI-3 --brightness .5
elif (( 22 <= 10#$H && 10#$H < 23 )); then
    xrandr --output HDMI-1 --brightness .3 && xrandr --output HDMI-2 --brightness .3 && xrandr --output HDMI-3 --brightness .3
else
    echo "Error"
fi

Then I used crontab -e to add the following line:

0 * * * * /home/piney/screendimmer.sh

The cronjob is triggered but the script doesn't run. What am I doing wrong?


Solution 1:

Cron provides limited set of environment variables by default[1]. To get xrandr to work through a Cron job, you should export[2] the value of the current user's $DISPLAY variable[3]. To do that add the follow line to the beginning of your script (or add it within the crontab file[4]):

export DISPLAY=$(w $(id -un) | awk 'NF > 7 && $2 ~ /tty[0-9]+/ {print $3; exit}')

References:

  • Crontab and C program that should be executed into a terminal window

  • How to programmatically find the current value of DISPLAY when DISPLAY is unset?


I liked the idea and already implemented it in my system. Here is my version of the above script:

#!/bin/bash

# While the user is not logged in == until the $DISPLAY variable is unset or empty
unset DISPLAY
while [ -z "$DISPLAY" ] || [ "$DISPLAY" == "" ]; do
        DISPLAY=$(w "$(id -un)" | awk 'NF > 7 && $2 ~ /tty[0-9]+/ {print $3; exit}' 2>/dev/null)
        if [ "$DISPLAY" == "" ]; then sleep 30; else export DISPLAY="$DISPLAY"; fi
done

brightness(){
        # Get the list of the active monitors automatically
        # To set this list manually use: OUT=( VGA-1 HDMI-1 HDMI-2 HDMI-3 )
        OUT=$(xrandr --listactivemonitors | awk 'NR!=1{print " "$NF" "}')
        # Adjust the brightness level for each monitor
        for current in "${OUT[@]}"; do xrandr --output "${current// /}" --brightness "$1"; done
}

if [ -z "${1+x}" ]; then  # If the scrip is called from Cron or CLI without an argument: 'brightness'
        H=$(date +%-H)
        if   ((  0 <= "$H" && "$H" <  7 )); then brightness ".5"
        elif ((  7 <= "$H" && "$H" < 10 )); then brightness ".6"
        elif (( 10 <= "$H" && "$H" < 19 )); then brightness ".7"
        elif (( 19 <= "$H" && "$H" < 22 )); then brightness ".6"
        elif (( 22 <= "$H" && "$H" < 24 )); then brightness ".5"
        else echo "Error"
        fi
else brightness "$1"    # If the scipt is called with an additional argument: 'brightness "<value>"'
fi
  • The script is able to get the list of the active monitors automatically. I've tested it with two monitors.

  • Nice idea is to place the executable file[5] in /usr/local/bin, thus it will be available also as shell command. Let's assume it is called brightness.

  • The script is able to use an arguments, which will override the default brightness values, for example: brightness .9.

  • While /usr/local/bin is not listed in the crontab's $PATH variable[1] [4] [6], the Cron jobs should use the full path:

    @hourly /usr/local/bin/brightness
    
  • Probably the @reboot Cron jobs will not work with the current version of the script[7].

Solution 2:

Instead of writing cron jobs to manually change your display's brightness, you might want to have a look at redshift, a program that can do exactly this. It can be set up to track daylight at your location, and change both your display's brightness and color temperature to better match natural light.

Image illustrating the effect of redshift

Its main selling point is changing the color temperature (i.e., shifting the color more towards the red, which is where the name comes from), but it can also adjust brightness. You could configure it to do just brightness, if that's what you want.

The main advantage over the manual solution is that redshift changes color/brightness gradually, matched to the current daily cycle of your location, rather than in steps as with your cron approach. You can also switch the effect on/off rather easily; sending the process SIGUSR1 will toggle the effect. I made a keybinding that does killall -USR1 redshift to make this easily accessible.

There is another program of similar functionality called f.lux, which also supports Windows and MacOS and seems quite popular. I have no experience with it though; in particular I'm not entirely sure if it can change brightness in addition to color temperature.