Display Backlight & Keyboard Illumination Bash Script

Okay, so this might be a bit rough, it still has TODO’s in it, as I just threw this together to get the panel backlight and keyboard illumination to work together in a way I thought seemed really logical. That said, it may seem totally illogical for your own use-cases, but that’s what is great about scripts; they’re easy to hack on! It does fall back to xbacklight, which is for the display backlight only – so run it from the terminal first to make sure you have it configured correctly.

You might be happy using this from the terminal, but you’ll probably prefer to add it to your window manager configuration, mapped to some pair of hot-keys.

To use this you’ll probably have to change some (or all) of the USER CONFIG section, but if you have those correct the rest *should* just work. There are some other optional parameters throughout the file that you can tweak, and I tried to explain what they do as verbosely as possible.

If you have any ways to improve/streamline this script feel free to make suggestions! I’m certainly not a Bash guru and I’m always happy to learn something new!

#!/bin/bash
#
# Backlight Utility // bl-util.sh
# Copyleft 2019, by Adam Gaskins <self@adamgaskins.com>
#
# Description:
#   Just a simple backlight control utility...
#
#   If you need help finding the /sys path to your backlight device:
#       First try looking for a display backlight in:
#           /sys/class/backiight/
#       If nothing is there try running:
#           find /sys/devices -type f -name 'brightness'
#   That should give you at least a handful of possible locations to where both 
#   your display & keyboard backlight devices might be located (but also some 
#   invalid ones too, so be careful).
#
#   See the README for a painfully detailed explanation of the rationale behind 
#   the way this script functions.
#
# Usage Example:
#   ./bl-util.sh +
#   ./bl-util.sh -
#
# TODO:
#   - If brightness to set is with x % near min or max just set the min or max. 
#     It's annoying to see it set to to 98% and then the change to 100% is not 
#     even perceptible, or worse it ends up at 1 or 2% and is basically at the 
#     lowest brightness, but you have to press it again just to turn to off the 
#     keyboard BL
#   - add option to attempt to auto find the display and keyboard backlight, 
#     device and path and have all settings be optional.

##############################
######## USER CONFIG #########

## Path to display's backlight control in /sys/...
# Note: This is likely correct already
SYS_DISPLAY_PATH='/sys/class/backlight'
# NOTE: the above /sys/class path is preferable to the /sys/devices variant 
# (below), especially if your system has discreet video (i.e. Optimus) that 
# might use a different video card at times! Only list the device in the 
# specific manner shown below if you you have no other option.
#SYS_DISPLAY_PATH='/sys/devices/pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1'

## Name of display's backlight device in $SYS_DISPLAY_PATH
# Note: If using Intel video this could be correct
SYS_DISPLAY_DEVICE='intel_backlight'

## Path to the keyboard's backlight control in /sys/... (this may be correct)
SYS_KEYBOARD_PATH='/sys/class/leds'

## Name of keyboard backlight device in $SYS_KEYBOARD_PATH (you will probably 
## have to replace this)
SYS_KEYBOARD_DEVICE='dell::kbd_backlight'

##### END OF USER CONFIG #####
##############################

n=/dev/null # reusable redirect abbreviation

# Use notifications if possible, otherwise just echo
# TODO: detect if run from terminal inside an X session and use echo
if command -v notify-send >$n && echo $DISPLAY | grep '\:[0-9]' >$n; then
    notify="$(command -v notify-send) --urgency=low --app-name=$0 $0"
    # if available, use canberra to play a bell chime
#    if command -v canberra-gtk-play >$n; then
#        notify="$(command -v canberra-gtk-play) -i bell -d $0; ${notify}"
#    fi
else
    notify="echo $0: "
fi

if [ ! -d ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE} ]; then
    ${notify} "Invalid display path ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}! Aborting."
    exit 1
fi
if [ ! -r ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/actual_brightness ]; then
    ${notify} "Cannot read ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/actual_brightness! Aborting."
    exit 1
fi

# Make sure the script is not already running (avoids launching lots of 
# instances when, for example, the brightness key is held down).
processes=$(ps -x | grep $0 | grep -v grep | wc -l)
if [ $processes -gt 3 ]; then
    echo $(ps -x | grep $0 | grep -v grep | wc -l)
    exit 1
fi

# TODO: is there a better way? this is iffy...
# Check that a command has been given
if [[ $1 == '+' || $1 == '-' ]]; then 
    direction=$1
else
    ${notify} "The script expects a '+' or '-' character as an argument!"
    exit 10
fi


### Display Settings ###

## Length of fade effect (in milliseconds) [integer]
# Tip: If your keyboard backlight fades then it looks very nice to match that 
# time closely here. 200ms seems to be about right on my Inspiron 7573.
fade_time_ms=200 # must be > 33

# CUSTOM_BL_CMD can be a user-defined string used to set brightness. This can be 
# any command that takes an integer value between the min & brightness values 
# (as defined in /sys/<path_to_device/). Just _remember_ to replace a real value 
# within the command with a '%s' (without the single quotes) so that this script 
# can insert the brightness values in to your command! You can, however, just 
# leave it empty and let the script try it's own methods.
# Example: CUSTOM_BL_CMD='pkexec xfpm-power-backlight-helper --set-brightness %s'
CUSTOM_BL_CMD=''

# if provided, use CUSTOM_BL_CMD without question
if [[ $CUSTOM_BL_CMD = *"%s"* ]]; then
    display_set_cmd=${CUSTOM_BL_CMD}
# otherwise we try the echo method...
elif [ -w ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/brightness ]; then
    display_set_cmd='echo %s > ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/brightness'
# ...or fall back to xbacklight, if available
elif command -v xbacklight >$n; then
    echo "Cannot write to ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/brightness, \
falling back to 'xbacklight' method. This likely means the keyboard backlight will \
not be set!"
    display_set_cmd='xbacklight -set $(( ( (%s * 10000 / 7500) + 99 ) / 100 ))'
# if neither method works just abort
else
    ${notify} "Cannot write to ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/brightness \
or find the 'xbacklight' utility on this system! Aborting."
    exit 1
fi

# get display backlight values
display_cur=$(cat ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/actual_brightness)
display_max=$(cat ${SYS_DISPLAY_PATH}/${SYS_DISPLAY_DEVICE}/max_brightness)
display_min=1

## Base delta [integer]
# Base delta effects brightness in a non-linear way so that we have smaller 
# changes at low brightness and larger changes at higher brightness. Since our 
# eyes tend to be more sensitive to changes at lower light levels, and less so 
# at higher brightness levels, this value can make the transitions at these 
# opposing extremes appear more consistent.
display_delta_base=5 # values between 4 and 12 give reasonable results
display_delta_applied=$(( ((${display_delta_base} * ${display_max}) + 50) / 100 ))
# this might have rounding errors here, break this up...
display_delta=$(( (${display_delta_applied} * ${display_cur}) / \
    (($display_max + 1) / 2) + $display_delta_applied ))

# calculate what the display brightness will end up being so we can set the 
# keyboard backlight accordingly. We only need to know this before hand because 
# some KB backlights have their own fade routine, so we need to set that before 
# we set the display brightness fade routine to have them both occur at the same 
# time.
display_new=$((${display_cur} ${direction} $display_delta))

# calculate length of each step (steps_ms), number of stops needed (steps), and 
# how much to advance the brightness each step (brightness_step)
fade_step_ms=$(( ((${fade_time_ms} * 3) + 50) / 100 )) # ensures ~30fps for fade
fade_steps=$(( ( ${fade_time_ms} + (($fade_step_ms+1) / 2) ) / $fade_step_ms ))
brightness_step=$(( ( $display_delta + (($fade_steps+1) / 2) ) / $fade_steps  ))


### Keyboard Settings ###
# Note: In the 'bl-util.readme', under the Keyboard Backlight Tips section, 
# there are some ideas to help in deciding how to configure the following three 
# variables!
 
## Enable/disable keyboard backlight control [bool]
# Note: This just disables its control via this script, it does not enable or 
# disable the KB backlight itself!
kb_control=true

## Turn backlight off if display brightness goes above percentage value [int]
# Rationale: Turns off backlight when bright ambient light (i.e. bright room, or 
# sunlight) would render the backlight moot (see the README for details). This 
# is relative to display brightness, so a value of 60 would turn KB backlight 
# off when display brightness goes above 60%, and turns it back on if display 
# brightness drops just below this value. 100 effectively disables this feature!
kb_cutoff=35

## Turn off keyboard backlight when display brightness is at lowest value [bool]
# Rationale: If you're running the display down to the absolute dimmest value 
# you might be trying to minimize the light you're emitting and there fore might 
# prefer not to have the keyboard lit up. Further more many keyboard backlights 
# cannot dim too much, so you may reach a point where the keyboard brightness is 
# much brighter than your display, which is both annoying and makes it difficult 
# for your eyes to adjust.
kb_allow_zero=true

## This is the command used to set keyboard backlight brightness
# This can be any command that takes an integer value. Simply replace a real 
# value within the command with a '%s'.
kb_set_cmd='echo %s > ${SYS_KEYBOARD_PATH}/${SYS_KEYBOARD_DEVICE}/brightness'

# get keyboard backlight values
kb_cur=$(cat ${SYS_KEYBOARD_PATH}/${SYS_KEYBOARD_DEVICE}/brightness)
kb_max=$(cat ${SYS_KEYBOARD_PATH}/${SYS_KEYBOARD_DEVICE}/max_brightness)

# this is the display_max with kb_cutoff applied
display_ceiling=$(( (${kb_cutoff} * ${display_max} + 50) / 100 ))

# calculate the new keyboard brightness (+ 99 rounds up, + 50 rounds correctly, 
# remove it to round down)
kb_new=$(( ((($display_new * 100 / $display_ceiling) * ${kb_max}) + 99 ) / 100 ))


### Apply Settings ###

## Set keyboard backlight
if [ $kb_control -a \
     -w ${SYS_KEYBOARD_PATH}/${SYS_KEYBOARD_DEVICE}/brightness -a \
     $kb_new -ne ${kb_cur} ]; then
    if [ $kb_new -eq 0 ]; then # check for allow_zero option
        $kb_allow_zero && value=0 || value=1
        cmd=$(printf "${kb_set_cmd}" $value)
        set -f; eval $cmd; set +f # allows us to eval multiplication (* char)
        test $value -eq 0 && ${notify} "Keyboard backlight has been turned off"
        test $value -eq 1 && ${notify} "Keyboard backlight brightness has been adjusted"
    elif [ ${display_new} -lt $display_ceiling ];then # adjust backlight
        cmd=$(printf "${kb_set_cmd}" $kb_new)
        set -f; eval $cmd; set +f
        ${notify} "Keyboard backlight brightness has been adjusted"
    else # display_cur is over cutoff value, turn kb backlight off
        cmd=$(printf "${kb_set_cmd}" 0)
        set -f; eval $cmd; set +f
        ${notify} "Keyboard backlight has been turned off"
    fi
fi

## Set display brightness
step=0
while [ $step -le $fade_steps ]; do
    sleep ${fade_step_ms}e-3

    display_cur=$(( ${display_cur} ${direction} $brightness_step ))

    if [ ${display_cur} -ge ${display_max} ]; then
        cmd=$(printf "${display_set_cmd}" ${display_max})
        set -f; eval $cmd; set +f
        ${notify} "The backlight is currently at the maximum brightness!"
        break;
    elif [ ${display_cur} -le ${display_min} ]; then
        cmd=$(printf "${display_set_cmd}" ${display_min})
        set -f; eval $cmd; set +f
        ${notify} "The backlight has reached the minimum brightness!"
        break;
    fi

    cmd=$(printf "${display_set_cmd}" ${display_cur})
    set -f; eval $cmd; set +f

    step=$(( $step + 1 ))
done

# make sure we can't get erroneous percent values in the notification
if [ ${display_cur} -gt ${display_max} ]; then
    display_cur=$display_max
elif [ ${display_cur} -lt ${display_min} ]; then
    display_cur=$display_min
fi

${notify} "Current brightness set to $(( ${display_cur} * 100 / ${display_max} ))%"

This is just a hastily made README for the script. You probably don’t need it but I’ll include it here anyways.

==Keyboard Backlight Usage Tips==

On my laptop the keyboard backlight uses nearly a whopping 1.5 watts of power at 
it's highest brightness, and almost one full watt at it's first and "lowest" 
brightness setting! This might not sound like much, but newer laptops are using 
less and less power, so this starts to seem like a pretty big piece of the 
battery pie chart considering this same laptop (Dell Inspiron Model: 7375) 
only uses about ~6 watts total power when idle! That translates in to nearly 
fifty minutes of battery life PER STEP! Aside from "off", there are just two 
steps (low or high) on this particular machine, but that still comes out to 
around ~1.7 hours of battery time that are lost when I use full backlight 
brightness! Now, keep in mind that this laptop gets nine hours of battery life 
under light load, so that isn't a huge percentage, but even if your battery life 
is only three hours, and assuming similar power consumption from the keyboard 
backlight (a big assumption, but...) that could still translate to roughly half 
an hour of battery time lost to just powering the keyboard backlight! If your 
watching a movie using hardware acceleration and left the keyboard backlight on 
then that could be the difference in seeing the ending, or not! So take a moment 
to consider your usage patterns and set the keyboard backlight variables 
appropriately! You might not want to disable the backlight at zero display 
brightness, but you probably don't need it on all the time and can still set the 
cutoff value to something conservative, say 60-80% (which would nix the kb 
backlight at display brightness above that percentage) so that you don't run 
it needlessly outside, or in bright rooms!


==bl-util Notes==

Folks who haven't had to think much about display brightness often think that 
that brighter values in darker rooms & dimmer values in brighter rooms makes 
sense... the reality is quite the opposite. We want our screens brighter in 
bright rooms (or sunlight) because of the way our eyes adjust to deal with them 
relative to our surroundings. In a bright room or out in the sun our eyes 
dilate to let in less light. This is also why bright sunlight makes you squint, 
it's like our fall-back mechanism when pupil dilation isn't enough to 
compensate. So it turns out that we actually need brighter back-lighting in 
brighter settings. Likewise, in darker settings our eyes are trying to let in as 
much light as they can, so a bright screen can be very harsh to look at in a 
dark room where our eyes are trying to adjust to dim surroundings and deal with 
the bright screen at the same time - although we tend to deal with this 
scenarios better than a dim screen in bright sunlight, and I'm sure some of you 
reading this are old enough to remember phones and laptops that couldn't even 
been seen outside on a sunny day! In that case the light from our screen just 
wasn't enough to compete with the ambient light around us, so our dilated 
pupils just couldn't even see the light radiating from those old devices. I 
assure you, the light was indeed there... but as Einstein said, it's all 
relative - and relative to the sun a bright display isn't so bright. This line 
of reasoning is also what I'm using to deal with the screen & keyboard backlight 
settings in this script. Typically display backlights have a large range of 
values (anywhere from around a dozen levels to many thousands of levels of 
brightness) but keyboard backlights tend to simply have an on/off settings, or 
maybe some very small number of brightness levels. Even my brand new Inspiron 
only has only three settings: off/low/high! [Of course some fancy keyboards have 
many brightness levels, even RGB and other features/effects, but I'm mostly 
targeting laptops and their built-in keyboards here]. Despite all this, I'm 
certain there are some laptop keyboards that have a higher ranges of brightness 
values, similar to a display backlight, and this is why I'm doing some extra 
work here to make sure we can handle those sorts of keyboards properly, rather 
than just making them behave as if they just had two or three levels of 
brightness, like mine does.

In my mind the KB backlight does have a slightly different ideal optimal 
behavior than the display backlight. I still believe it should be at its 
brightest when the display is brightest (with a caveat that I'll get to in a 
moment), and it should dim when the display is dimmed - generally speaking. 
However, with a display backlight you never really want to have the backlight 
turned off all together, as there's no real scenario in which it's even very 
readable that way, but with a keyboard the backlight can be off and you can 
still read the key's printed characters assuming there's enough ambient light. 
With this in mind it seems logical to turn off the backlight completely when 
there is an abundance of ambient light, and, short of using a light-sensor or 
web-cam to measure that, we can make assumptions on ambient light levels based on 
the users brightness setting. For example, if the screen backlight is set to 75% 
or higher, then it's safe to assume we have a lot of ambient light, either a 
very bright room or outdoors in the day-light, and so we can probably just turn 
off the KB backlight in this situation. This situation, where we kill the KB 
backlight in favor using ambient light to read the KB is a special case because 
the keyboard is still visible when we have adequate lighting. Having the ability 
to cut off the backlight when it is washed out by ambient light isn't that 
noticeable (after all, you can't really see that it's lit in bright light), 
however if your outside where this scenario is most likely then you will be more 
appreciative of the small but not completely insignificant power savings, since 
you are probably running on battery power in this situation!

That said, there does come a point where we still have some ambient light but 
not enough to adequately illuminate the keys without some help. Now we will want 
the keyboard lit up to make our display and keys closer in brightness. 
Basically, this is where ambient light become inadequate to illuminate the keys 
sufficiently and our keyboard's backlight needs to be at its maximum brightness 
level. Remember, just like with the display, the more ambient light we have the 
brighter the backlight needs to be for it to be of benefit. It's just that there 
is this scenario where a bright room or sunlight are of more benefit than the 
backlight itself! It is only below this point where we begin to make the KB 
backlight behave much like the display backlight in that it should get dimmer as 
the ambient light gets lower.

To sum it up, this is all to ensure that our screen, our keyboard and our 
environmental ambient light are all at similar levels so as to reduce eye strain 
and other issues caused by improper and highly contrasted light sources within 
our field of vision! I hope this all makes sense, as I am probably 
over-complicating it, but I think it's more likely that it's just not as simple 
as it seems to be at first glance.

So let's say I'm working at a very minimal display brightness in a dark room - 
with this logic we would have our KB backlight on but set at (or near) it's 
lowest brightness value (we don't want the KB BL brighter than our screen, that 
would just be distracting and uncomfortable) and as we raise our screen 
brightness the KB backlight should also get brighter - but remember that we will 
get to a point where the screen brightness will need to continue rising and our 
keyboard backlight becomes useless in the brighter ambient light, so it gets 
cuts off as it becomes an unneeded waste of power


==Software Design Note==

It would seemingly make sense to do the keyboard backlight changes within 
the same loop that changes the backlight display, however my KB backlight 
automatically does a fade effect lasting ~200ms, and since it has only two 
brightness levels this is obviously hard-coded and not something one can easily 
override (maybe even impossible from software). I might still move the KB 
control to the loop eventually, once I learn more about how other keyboard 
backlights work (not to mention the possibilities/complexities of having RGB 
keys, but that's a topic for another time). So right now I'm just going to stick 
with setting the keyboard backlight in one step before the display brightness 
(so the kb fade effect happens while our software display fade runs). If you 
have a keyboard backlight with more than a couple of brightness levels then 
please contact me and send me the the output of all the relevant /sys/... files, 
I'll certainly add the custom fade capability for KB BLs if I learn that systems 
out there can actually make use of it! That said, I am still implementing this 
in a way that will take advantage of such KB backlights, they just wont utilize 
the fade effect unless it is hard-coded to do so (like mine and likely many 
others indeed do).

==Final Thoughts==

There is one other school of thought regarding brightness, an alternative to the 
logic of brighter backlight in a brighter environment, dimmer backlight in a 
darker environment. I'll call it the Graphic Designer's Profile. Now, I'm not 
implementing it in this script, as it would basically require the thing to do a 
full monitor calibration, and there's already tools out there for that (not to 
mention I'm not even sure my concept of gamma is 100% accurate!). Anyways, the 
differences are that graphic designers want optimal deep blacks and bright 
whites, and accuracy across the color spectrum - and for most displays there is 
an optimal brightness where these settings become as good they can possibly get 
for a particular display. Anyways, I'm not going to delve in to all of this too 
deeply (not that I understand it all too deeply anyhow), but suffice it to say 
that my script doesn't take this in to account. If this your concern you need 
software that helps you calibrate your display and then you need to leave this 
settings along. I have used some hardware devices that service this purpose, 
even one that supposedly calibrated for ambient lighting changes using a USB 
sensor - and none of these devices worked as good as the software calibration 
tools that rely on you picking the best options from a series of questions, just 
using your own eyes. They're similar to how an eye doctor prescribes glasses 
where they flip the lens back and forth and ask you which is best. So yeah, if 
your hard core in to graphics design you might want to go that route instead. 
You could still use my tool for adjustments and just be aware of what brightness 
level it is that you calibrated it, so you can make sure to return to that 
later.


==Ideas for the future==

**USING SCREEN EVENTS TO CONTROL KEYBOARD BACKLIGHT**
I know that an event is triggered when the keyboard is needed, such as when you 
click/touch inside a text-box that requires typing, and this would bring up an 
on-screen keyboard in the even that a convertible device is in tablet mode. I 
want to figure this out more and use those events to enable/disable the 
keyboard backlight. There would be other times it should be on, but this would 
be a great method to reduce it's power usage - and from the section Keyboard 
Backlight Usage Tips we do know that the kb backlight wastes a significant 
amount of power!

**A BETTER DYNAMIC BRIGHTNESS CONTROL**
I'd also like to take a stab at the concept of adjusting brightness based on 
screen content. For example, lets say you have your quad of dark terminals on 
one desktop, then you switch to your browser window and suddenly the screen is 
painfully bright and mostly white! We could average the screen values to adjust 
the brightness here, and many have already implemented this sort of thing - that 
said, I've yet to see it done well, or in a non-distracting sort of way. My Dell 
even has something like this build in, but it's horrible and everyone I've met 
with this feature hates it... and I met them because they were asking 'why?' how 
to disable it! It isn't the most intuitive thing to find in the BIOS. There's 
also some software, even available on Linux, to do this, but I also haven't had 
great luck there. I won't name names, but either the packages were just harder 
to setup and use than they needed to be (or I'm just dumb)... or, again, they 
just worked in a distracting and undesirable manner. So yeah, if anyone else 
things this sounds nice (assuming it was done well) or you have some ideas to do 
it well, then please let me know!

**THAT'S IT...** ...but if you have ideas let me know! I'm not trying to compete 
with large projects, but rather I'm trying to make something that is small & 
simple to use, and has minimal dependencies, but I'd still like it to work 
intelligently and be feature rich! Speak up if you have ideas and if I see a 
pattern I'll implement them... or at least try to do so!


==Contact Info== My name is Adam Gaskins. I'm not a great Bash scripter, so if 
you have some tips then by all means let me know! My email is 
self@adamgaskins.com. I hope you find this tool useful!

Leave a Reply