Bash: A Simple Script for Changing Display Brightness with XRANDR

I run two external displays on my laptop; the brightness keys on the laptop don't affect them. I could probably find an app or extension for Xubuntu to handle dimming the displays, but since I spend a lot of time on the command line, it's just as easy to dim them from there with the help of XRANDR and a simple Bash script. This post outlines how to set the script up. I'll also cover an optional "night mode" that reduces blue gamma from the display.

If you're on Xubuntu, Ubuntu, or just about any other flavor of Linux, you're probably using the X server to handle your display configurations. That should also mean that you have XRANDR available on the command line. XRANDR can change display configurations in any number of ways, but today I wanna look at it for controlling brightness. Here's the deal, though: I don't wanna have to memorize yet another command line syntax just to dim my monitors. Whichever way XRANDR handles the dimming, I'm gonna move it into a Bash script and alias it so I can adjust brightness on the fly!

Outlining the Bash Script

Basically, I want flexibility in how bright or how dim the monitors go--the command line equivalent of a slider. I'll basically need two things for my script, then:

  1. A way to call the script (i.e., an alias), and...
  2. A scale: basically a one to ten value that I can pass to XRANDR as a variable and adjust brightness accordingly.

Luckily, neither of these is rocket science. XRANDR already adjusts brightness when given a numerical value: 1.00 being full brightness and 0.00 being pitch black. To minimize keystrokes, I'll need to do some conversion between decimals and whole numbers. Otherwise, the script should be pretty simple.

After I go through the logistics of dimming, I'll also cover how to add a "night mode" that reduces blue gamma from the display. This "option" can be passed to XRANDR as a second variable.

Step 1: Getting to Know XRANDR

First thing's first: let's look at how to dim a monitor with XRANDR. The syntax should look like this:

// [displayID] is self explanatory; [X.XX] is the decimal value for brightness 
$ xrandr --output [displayID] --brightness [X.XX]

That's not too complicated, but it's definitely more than I'd like to type each time I adjust my brightness.

In order to get started, I'll first need to get the Display ID's for my two external monitors. You can gather this information in one of two ways. The first, is to simply type xrandr in your terminal. The output will be pretty verbose--highlighting monitor info for both connected and disconnected displays (regardless of whether the monitor is actually in use). Personally, I have a third monitor (my built in laptop screen) that's "connected" but not in use (I disable it whenever the externals are connected). Since my external monitors are the only ones in use, a better way to get their ID's might be like this:

// to show only monitors that are ACTIVE 
$ xrandr --listactivemonitors

This should give you a simplified output. Something, more or less, like this:

output of xrandr --listactivemonitors
Viola!

You can see that I have two monitors "active"-- each with a respective ID: HDMI-1 and DP-2. If, then, I wanted to change the brightness on both monitors, I would need to do some serious keyboard surfing. Let's say I want to reduce the brightness on both monitors by 50%; I'd basically have to type that xrandr brightness command twice:

// as the lettering on my keyboard slowly wears away... 
$ xrandr --output HDMI-1 --brightness 0.5 && xrandr --output DP-2 --brightness 0.5

If I were using my laptop display as well, my hair would start greying before I ever got the brightness changed on all three screens. That's just not gonna do...

Step 2: Moving to a Bash Script

Create a script called dimmer.sh. Be sure to include a hash-bang (#! /bin/bash), and get ready for some really light logic. Here's the script at a glance--nothing too special:

#! /bin/bash  
if [ "$1" = 10 ]; then
  percent="1"
else
  percent="0.$1"
fi

xrandr --output HDMI-1 --brightness "$percent" && xrandr --output DP-2 --brightness "$percent"

The first bit of logic (the if statement) is meant to prep a variable that I can pass to XRANDR as an integer. To save key strokes, I'd really like to set my brightness scale using a whole number. For example, for a 50% reduction in brightness, I'd prefer not to type "0.5"; rather, I just wanna hit "5". Using whole numbers, the lowest brightness will be "1" (i.e., "0.1") and the highest brightness will be "10" (i.e., "1.0"). I need to do some translation, though, in order to change my whole-number value into the decimal value that XRANDR expects. In my script, that value gets stored in the $1 variable. You'll probably notice that the $1 variable isn't defined in the script; rather, the it gets defined when I call the script from the command line--like this: $ dimmer 5. "dimmer" is the alias I'll create for the script; "5" gets stored in the $1 variable.

The easiest way to convert from whole to decimal is to just tack on "0." to whatever the whole value is. This is the starting point for the if logic up at the top of the script--i.e., take whatever value $1 has, tack "0." onto it, and store it in a new variable called percent. By that logic, however, a value of "10" (full brightness) would get converted to "0.10" (lowest brightness). So, I need to make an exception for when $1 carries a value of "10". This is the first step in the if logic. If $1 is equal to "10", then the percent variable equals "1". For all other values, the percent variable will equal $1 with a "0." tacked onto the front. The rest is pretty self explanatory.

Optional: Adding a "Night Mode"

XRANDR can also adjust the RGB gamma of your display. Personally, I basically only ever need to dim the light on my displays when it's dark (either for late night or early morning work--don't you just LOVE pushing major updates at midnight?). So, having the ability to cut down on blue light is also handy. Here's what the basic syntax for that looks like:

// gamma is defined with three values: R, G & B. Each value is expressed as a decimal between 0.0 and 1.0 
$ xrandr --output [displayID] --gamma [X.X:X.X:X.X]

Since I really only ever want to cut down on blue light, the only gamma value I would need to mess with is that last one. I might shave just a little off the green too--in order keep things looking more or less even--i.e.:

$ xrandr --output [displayID] --gamma 1.0:0.95:0.85

Before you commit to anything in your script, it's probably best to play around with the values on the command line until you find something you like.

The next big question is how to integrate the gamma change into the script. Personally, I don't want to mess around with passing different gamma values. I want one option that will just toggle the blue gamma down to a set value. I can do this by passing a second inline variable to the script when I call it on the command line--like this: $ dimmer 5 night. This value of "night" will automatically get stored in a variable named $2. I can use some more if-logic to say: if $2 equals "night", set a given gamma value; otherwise, leave the gamma as is. Here's what that looks like added to my existing script.

#! /bin/bash  
if [ "$1" = 10 ]; then
  percent="1"
else
  percent="0.$1"
fi

xrandr --output HDMI-1 --brightness "$percent" && xrandr --output DP-2 --brightness "$percent"

if [ "$2" = night ]; then
  xrandr --output DP-2 --brightness "$percent" --gamma 1.0:0.95:0.85 && xrandr --output HDMI-1 --brightness "$percent" --gamma 1.0:0.95:0.85
else
  xrandr --output DP-2 --brightness "$percent" --gamma 1.0:1.0:1.0 && xrandr --output HDMI-1 --brightness "$percent" --gamma 1.0:1.0:1.0
fi

With this logic, in order to reset the gamma, I can just leave out the "night" option the next time I run the script.

Time to Alias the Script!

The last step is, of course, to alias the script. Head to your .bash_aliases and add the following:

alias dimmer='~/pathto/dimmer.sh'

Source your .bashrc file. If you haven't already, make sure the script is executable and you should be good to go!