DEV Community

Cover image for No More Monitor Buttons: Control Brightness & Contrast with Your Custom CLI Tool.
Md. Mehedi Hasan Nabil
Md. Mehedi Hasan Nabil

Posted on

No More Monitor Buttons: Control Brightness & Contrast with Your Custom CLI Tool.

Ever wanted a clean way to adjust your external monitor's brightness and contrast from the command line? Let's build a simple yet beautiful Python CLI tool named monitor using the power of ddcutil and rich!

By the end, you’ll be able to do things like:

cli-output-of-the-terminal

cli-change-monitor-brightness-contrast


1️⃣ Install ddcutil

Install it using your package manager:

Ubuntu/Debian:

sudo apt install ddcutil
Enter fullscreen mode Exit fullscreen mode

Fedora:

sudo dnf install ddcutil
Enter fullscreen mode Exit fullscreen mode

Arch Linux:

sudo pacman -S ddcutil
Enter fullscreen mode Exit fullscreen mode

2️⃣ Post-Install: Set Up Permissions

✅ Add your user to the i2c group

This allows ddcutil to access monitor interfaces without needing sudo.

sudo usermod -aG i2c $USER
Enter fullscreen mode Exit fullscreen mode

🔁 Reboot or log out/in

For the group change to apply, you must:

  • Log out and log back in, or
  • Reboot your system:
reboot
Enter fullscreen mode Exit fullscreen mode

If ddcutil detect doesn’t show any monitors, a reboot usually helps.


3️⃣ Verify Your Monitor Is Detected

After logging in again, run:

ddcutil detect
Enter fullscreen mode Exit fullscreen mode

You should see something like:

Display 1
   I2C bus:             /dev/i2c-6
   EDID synopsis:       ...

Enter fullscreen mode Exit fullscreen mode

If you don't see any monitors:

  • Try reconnecting your external monitor
  • Try using sudo ddcutil detect to test access

4️⃣ Install Python & rich

Install rich to render progress bars and pretty output:

pip install rich
Enter fullscreen mode Exit fullscreen mode

5️⃣ Build the CLI with Python

Here’s the full script for monitor. Save this as a file named simply monitor (no .py extension), and make it executable.
run this command:

nano monitor
Enter fullscreen mode Exit fullscreen mode

paste this:

#!/usr/bin/env python3

import sys
import subprocess
import re
from rich.console import Console
from rich.progress import Progress, BarColumn, TextColumn, TaskProgressColumn

console = Console()

# Get value of VCP code (10 = brightness, 12 = contrast)
def get_vcp_value(code):
    try:
        result = subprocess.run(["ddcutil", "getvcp", code], capture_output=True, text=True)
        match = re.search(r'current value =\s*(\d+), max value =\s*(\d+)', result.stdout, re.IGNORECASE)
        if match:
            current = int(match.group(1))
            max_val = int(match.group(2))
            percentage = int(current * 100 / max_val)
            return percentage
    except Exception as e:
        console.print(f"[red]Error getting VCP code {code}: {e}[/]")
    return None

# Set value of VCP code
def set_vcp_value(code, value):
    try:
        subprocess.run(["ddcutil", "setvcp", code, str(value)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return True
    except Exception as e:
        console.print(f"[red]Error setting VCP code {code}: {e}[/]")
        return False

# Display progress bar
def display_progress(value, label="Setting"):
    with Progress(
        TextColumn("🔆 [progress.description]{task.description}"),
        BarColumn(bar_width=40),
        TaskProgressColumn(text_format="[cyan]{task.percentage:>3.0f}%"),
        TextColumn("of [cyan]100% 🔆"),
        expand=False,
    ) as progress:
        task = progress.add_task(label, total=100)
        progress.update(task, completed=value)

# Validate integer input between 0-100
def validate_input(value_str):
    try:
        value = int(value_str)
        if 0 <= value <= 100:
            return value
    except ValueError:
        pass
    return None

# Print usage/help
def print_usage():
    console.print("[bold yellow]Monitor CLI — Adjust Brightness & Contrast[/]\n")
    console.print("Usage:")
    console.print("  monitor --get")
    console.print("  monitor -b [0-100] [-c [0-100]]\n")
    console.print("Examples:")
    console.print("  monitor -b 70")
    console.print("  monitor -b 85 -c 60")
    console.print("  monitor --get")

# Main
if __name__ == "__main__":
    args = sys.argv[1:]
    brightness = None
    contrast = None

    if not args or "--help" in args or "-h" in args:
        print_usage()
        sys.exit(0)

    if "--get" in args:
        b = get_vcp_value("10")  # Brightness
        c = get_vcp_value("12")  # Contrast

        if b is not None:
            console.print(f"\n[bold blue]Current Brightness:[/] {b}%")
            display_progress(b, "Brightness")
        else:
            console.print("[red]❌ Failed to read brightness.[/]")

        if c is not None:
            console.print(f"\n[bold magenta]Current Contrast:[/] {c}%")
            display_progress(c, "Contrast")
        else:
            console.print("[red]❌ Failed to read contrast.[/]")

        sys.exit(0)

    # Parse CLI args
    i = 0
    while i < len(args):
        if args[i] in ("-b", "--brightness") and i + 1 < len(args):
            brightness = validate_input(args[i + 1])
            i += 2
        elif args[i] in ("-c", "--contrast") and i + 1 < len(args):
            contrast = validate_input(args[i + 1])
            i += 2
        else:
            console.print(f"[red]❌ Unknown or invalid argument: {args[i]}[/]")
            print_usage()
            sys.exit(1)

    # Apply brightness
    if brightness is not None:
        if set_vcp_value("10", brightness):
            console.print(f"\n[bold green]✅ Brightness set to {brightness}%[/]")
            display_progress(brightness, "Brightness")
        else:
            console.print("[red]❌ Failed to set brightness.[/]")

    # Apply contrast
    if contrast is not None:
        if set_vcp_value("12", contrast):
            console.print(f"\n[bold green]✅ Contrast set to {contrast}%[/]")
            display_progress(contrast, "Contrast")
        else:
            console.print("[red]❌ Failed to set contrast.[/]")
Enter fullscreen mode Exit fullscreen mode

6️⃣ Make It Executable and Globally Available

chmod +x monitor
sudo mv monitor /usr/local/bin/
Enter fullscreen mode Exit fullscreen mode

Now just run:

monitor -b 70 -c 60
Enter fullscreen mode Exit fullscreen mode

🔍 Examples

monitor --get             # Show current brightness
monitor -b 60             # Set brightness to 60%
monitor -c 45             # Set contrast to 45%
monitor -b 70 -c 50       # Set both
Enter fullscreen mode Exit fullscreen mode

🧠 How It Works

  • VCP 10 = Brightness
  • VCP 12 = Contrast
  • ddcutil talks to your monitor via DDC/CI over I²C

🚀 Bonus Ideas

Once this is working, you can extend it:

  • Add presets: monitor --preset movie
  • Create profiles for day/night
  • Build a Textual GUI on top of it

Top comments (0)