diff --git a/Bin/brigthness.sh b/Bin/brigthness.sh new file mode 100755 index 0000000..d133302 --- /dev/null +++ b/Bin/brigthness.sh @@ -0,0 +1,223 @@ +#!/bin/bash +# +# Bash script to get/set monitor brightness. +# This script acts as a smart wrapper for 'ddcutil' and the Linux sysfs backlight interface. +# +# It automatically determines whether to use ddcutil (for external monitors) +# or the /sys/class/backlight interface (for internal displays like eDP/LVDS). +# + +# --- Configuration --- +readonly CACHE_PATH="/tmp/ddcutil_detect_cache.txt" +readonly CACHE_TTL=3600 # Cache duration in seconds (1 hour) + +# --- Helper Functions --- + +# Prints an error message to stderr and exits with code 1. +# Usage: fail "Error message" +fail() { + echo "Error: $1" >&2 + exit 1 +} + +# Checks if a monitor name corresponds to an internal display. +# Usage: is_internal "monitor_name" +# Returns 0 (true) if internal, 1 (false) otherwise. +is_internal() { + local monitor_name="$1" + # eDP (embedded DisplayPort) and LVDS are common for laptop panels. + if [[ "$monitor_name" == "eDP"* || "$monitor_name" == "LVDS"* ]]; then + return 0 # Success (is internal) + else + return 1 # Failure (is not internal) + fi +} + +# Finds the corresponding backlight device in /sys/class/backlight for a given +# connector name (e.g., "DP-1"). +# It echoes the device name (e.g., "intel_backlight") on success. +# Usage: get_sysfs_backlight_device "connector_name" +get_sysfs_backlight_device() { + local target_connector="$1" + + for entry in /sys/class/backlight/*; do + # Ensure it's a valid directory entry + [ -d "$entry" ] || continue + + local device_name + device_name=$(basename "$entry") + + # Prioritize nvidia devices if found + if [[ "$device_name" == "nvidia_"* ]]; then + echo "$device_name" + return 0 + fi + + # Follow the symlink to find the graphics card connector + local real_path + if [ -L "$entry/device" ]; then + real_path=$(readlink -f "$entry/device") + # Extract the connector name from a path like .../card0-DP-1/... + # This regex finds "card" + digits + "-", and captures what follows. + if [[ "$real_path" =~ card[0-9]+-([^/]+) ]]; then + local path_connector="${BASH_REMATCH[1]}" + if [[ "$path_connector" == "$target_connector" ]]; then + echo "$device_name" + return 0 + fi + fi + fi + done + + return 1 # Not found +} + +# Retrieves the output of `ddcutil detect`, using a cache to avoid +# repeated slow calls. Echoes the command output. +# Usage: get_ddcutil_detect_output +get_ddcutil_detect_output() { + local use_cache=true + if [ ! -f "$CACHE_PATH" ]; then + use_cache=false + else + local now + now=$(date +%s) + local mtime + mtime=$(stat -c %Y "$CACHE_PATH") + local age=$((now - mtime)) + if (( age > CACHE_TTL )); then + use_cache=false + fi + fi + + if [[ "$use_cache" == true ]]; then + cat "$CACHE_PATH" + else + # Run the command, tee the output to the cache file, and also return it + ddcutil detect 2>/dev/null | tee "$CACHE_PATH" + fi +} + +# Parses the output of `ddcutil detect` to find the display index (e.g., 1) +# for a given monitor connector name (e.g., "DP-1"). +# Echoes the display index on success. +# Usage: get_ddc_index_for_monitor "monitor_name" +get_ddc_index_for_monitor() { + local target_monitor="$1" + local detect_output + detect_output=$(get_ddcutil_detect_output) + + # Check if ddcutil command failed or produced no output + if [ -z "$detect_output" ]; then + return 1 + fi + + local current_display="" + # Use process substitution and a while loop to read line-by-line + # This avoids issues with subshells and variable scope. + while IFS= read -r line; do + # Find lines like "Display 1" and store the number + if [[ "$line" =~ ^Display[[:space:]]+([0-9]+) ]]; then + current_display="${BASH_REMATCH[1]}" + continue + fi + + # Once we have a display number, look for its connector info + if [ -n "$current_display" ]; then + # Look for lines like "DRM connector: card0-DP-1" + if [[ "$line" =~ DRM_?connector:.*card[0-9]+-(${target_monitor}) ]]; then + echo "$current_display" + return 0 + fi + fi + done <<< "$detect_output" + + return 1 # Not found +} + +# --- Main Logic --- + +main() { + # Check for correct number of arguments + if [[ "$#" -lt 2 ]]; then + echo "-1" + exit 1 + fi + + local cmd="$1" + local mon="$2" + local val="${3:-}" # Default to empty if not provided + + if is_internal "$mon"; then + # --- Handle Internal Display (via /sys/class/backlight) --- + local backlight_device + backlight_device=$(get_sysfs_backlight_device "$mon") + if [ -z "$backlight_device" ]; then + echo "-1" # Error: Could not find backlight device + exit 1 + fi + + local brightness_file="/sys/class/backlight/$backlight_device/brightness" + local max_brightness_file="/sys/class/backlight/$backlight_device/max_brightness" + + if [ "$cmd" == "get" ]; then + local current_b + current_b=$(cat "$brightness_file") + local max_b + max_b=$(cat "$max_brightness_file") + # Perform integer arithmetic to scale to 0-100 + echo $((current_b * 100 / max_b)) + + elif [ "$cmd" == "set" ]; then + [[ "$#" -lt 3 ]] && echo "-1" && exit 1 + local max_b + max_b=$(cat "$max_brightness_file") + # Scale the 0-100 value to the device's raw value + local raw_brightness=$((val * max_b / 100)) + # NOTE: Writing here may require root privileges or specific udev rules. + if ! echo "$raw_brightness" > "$brightness_file" 2>/dev/null; then + echo "-1" # Error: Permission denied or other write error + exit 1 + fi + echo "$val" # Echo back the set value on success + else + echo "-1" # Invalid command + exit 1 + fi + + else + # --- Handle External Display (via ddcutil) --- + local display_index + display_index=$(get_ddc_index_for_monitor "$mon") + if [ -z "$display_index" ]; then + echo "-1" # Error: Could not find DDC display index + exit 1 + fi + + if [ "$cmd" == "get" ]; then + # Call ddcutil, parse for "current value = X," + local output + output=$(ddcutil --display "$display_index" getvcp 10 2>/dev/null) + if [[ "$output" =~ current[[:space:]]+value[[:space:]]*=[[:space:]]*([0-9]+) ]]; then + echo "${BASH_REMATCH[1]}" + else + echo "-1" # Error: Could not parse brightness from ddcutil + exit 1 + fi + elif [ "$cmd" == "set" ]; then + [[ "$#" -lt 3 ]] && echo "-1" && exit 1 + if ddcutil --display "$display_index" setvcp 10 "$val" >/dev/null 2>&1; then + echo "$val" # Echo back the set value on success + else + echo "-1" # Error: ddcutil command failed + exit 1 + fi + else + echo "-1" # Invalid command + exit 1 + fi + fi +} + +# Run the main function with all script arguments +main "$@" \ No newline at end of file