Banner of smart-brightness-control-in-linux--auto-detect-and-manage-backlight-like-a-pro-17.jpg

Smart Brightness Control in Linux: Auto-Detect and Manage Backlight Like a Pro


Category: Linux

📅 April 28, 2026   |   👁️ Views: 1

Author:   mosaid

Managing screen brightness on Linux sounds trivial… until you realize your system exposes multiple brightness interfaces and only one of them actually works correctly.

I’ve personally run into systems where writing to /sys/class/backlight/acpi_video0/brightness does absolutely nothing, while another hidden interface works perfectly.

So instead of guessing every time, I built a smart brightness script that:

Automatically detects the correct device

Ignores broken or misleading interfaces

Provides verbose debugging output

Supports get / set / increment / decrement


🧠 Understanding the Problem

Linux exposes brightness controls through /sys, typically under:


/sys/class/backlight/
/sys/class/leds/

The issue? You might see something like:


/sys/class/backlight/intel_backlight
/sys/class/backlight/acpi_video0

But only one of them actually controls your screen.

intel_backlight → usually correct (GPU native)

acpi_video0 → often fake or limited

This is why hardcoding paths is a bad idea.


⚙️ The Smart Brightness Script

Here’s the full script I use. It automatically detects the best interface and gives you detailed output when needed.


#!/usr/bin/env bash

set -euo pipefail

STEP=5
VERBOSE=0

log() {
    [[ $VERBOSE -eq 1 ]] && echo "[brightness] $*" >&2
}

percent_to_raw() {
    local percent=$1 max=$2
    echo $(( percent * max / 100 ))
}

raw_to_percent() {
    local val=$1 max=$2
    echo $(( val * 100 / max ))
}

get_candidates() {
    local d

    for d in /sys/class/backlight/*; do
        [[ -d "$d" && -f "$d/brightness" && -f "$d/max_brightness" ]] && echo "$d"
    done

    for d in /sys/class/leds/*; do
        [[ -f "$d/brightness" && -f "$d/max_brightness" ]] && echo "$d"
    done
}

detect_device() {
    mapfile -t candidates < <(get_candidates)

    [[ ${#candidates[@]} -eq 0 ]] && return 1

    log "Found ${#candidates[@]} candidates:"
    for d in "${candidates[@]}"; do
        log "  - $d"
    done

    for d in "${candidates[@]}"; do
        if [[ "$d" == *intel_backlight* || "$d" == *amdgpu* ]]; then
            log "Selected (GPU native): $d"
            echo "$d"
            return
        fi
    done

    for d in "${candidates[@]}"; do
        if [[ "$d" != *acpi_video* ]]; then
            log "Selected (non-ACPI fallback): $d"
            echo "$d"
            return
        fi
    done

    log "Selected (last resort): ${candidates[0]}"
    echo "${candidates[0]}"
}

get_brightness() {
    local dev=$1
    local val max percent

    val=$(<"$dev/brightness")
    max=$(<"$dev/max_brightness")
    percent=$(raw_to_percent "$val" "$max")

    echo "Device: $dev"
    echo "Raw: $val / $max"
    echo "Brightness: ${percent}%"
}

set_brightness() {
    local dev=$1 percent=$2
    local max raw

    max=$(<"$dev/max_brightness")
    raw=$(percent_to_raw "$percent" "$max")

    log "Writing $raw to $dev/brightness (=${percent}%)"
    echo "$raw" | sudo tee "$dev/brightness" >/dev/null

    [[ $VERBOSE -eq 1 ]] && get_brightness "$dev"
}

list_devices() {
    local d val max percent

    mapfile -t candidates < <(get_candidates)

    [[ ${#candidates[@]} -eq 0 ]] && {
        echo "No brightness devices found"
        exit 1
    }

    for d in "${candidates[@]}"; do
        val=$(<"$d/brightness")
        max=$(<"$d/max_brightness")
        percent=$(raw_to_percent "$val" "$max")

        echo "$d"
        echo "  Raw: $val / $max"
        echo "  Brightness: ${percent}%"
        echo
    done
}

main() {
    local cmd="get"

    if [[ "${1:-}" == "-v" ]]; then
        VERBOSE=1
        shift
    fi

    cmd=${1:-get}
    shift || true

    case "$cmd" in
        list)
            list_devices
            return
            ;;
    esac

    local dev
    dev=$(detect_device) || {
        echo "No brightness device found"
        exit 1
    }

    case "$cmd" in
        get)
            get_brightness "$dev"
            ;;
        set)
            [[ $# -lt 1 ]] && { echo "Usage: $0 set <percent>"; exit 1; }
            set_brightness "$dev" "$1"
            ;;
        inc)
            local cur
            cur=$(raw_to_percent "$(<"$dev/brightness")" "$(<"$dev/max_brightness")")
            set_brightness "$dev" $(( cur + STEP ))
            ;;
        dec)
            local cur
            cur=$(raw_to_percent "$(<"$dev/brightness")" "$(<"$dev/max_brightness")")
            set_brightness "$dev" $(( cur - STEP ))
            ;;
        *)
            echo "Usage: $0 [-v] {get|set <n>|inc|dec|list}"
            exit 1
            ;;
    esac
}

main "$@"

⚡ Usage Examples

Check current brightness


./brightness.sh get

Enable verbose debugging


./brightness.sh -v get

Set brightness to 50%


./brightness.sh set 50

Increment / decrement


./brightness.sh inc
./brightness.sh dec

List all detected devices


./brightness.sh list

🔐 Optional: Remove the Need for sudo

You can allow brightness control without sudo using a udev rule:


echo 'ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chmod 666 /sys/class/backlight/%k/brightness"' | sudo tee /etc/udev/rules.d/90-backlight.rules

🧠 Final Thoughts

What I like about this approach is that it’s portable, predictable, and transparent. Instead of relying on desktop environments or external tools, you get direct control over the kernel interface.

And more importantly—you always know which device is actually being used.

If you’re the kind of user who lives in the terminal or runs minimal setups like i3 or sway, this kind of script becomes essential.


🚀 Next Steps

If you want to push this further, here are some ideas:

Bind it to XF86 brightness keys

Add smooth fade transitions

Cache the detected device for faster execution

Add external monitor support via ddcutil

Once you start owning your system at this level, going back to GUI sliders feels… limiting.


← Supercharging Zsh: Practical Functions and Keybindings for Power Users