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:
1️⃣ Install ddcutil
Install it using your package manager:
Ubuntu/Debian:
sudo apt install ddcutil
Fedora:
sudo dnf install ddcutil
Arch Linux:
sudo pacman -S ddcutil
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
🔁 Reboot or log out/in
For the group change to apply, you must:
- Log out and log back in, or
- Reboot your system:
reboot
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
You should see something like:
Display 1
I2C bus: /dev/i2c-6
EDID synopsis: ...
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
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
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.[/]")
6️⃣ Make It Executable and Globally Available
chmod +x monitor
sudo mv monitor /usr/local/bin/
Now just run:
monitor -b 70 -c 60
🔍 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
🧠 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)