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

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →