autorandr and bash script to collect and setup monitor data for xrandr

Slug: xrandr

55542 characters 6243 words

#Why Use autorandr?

autorandr is a highly effective, Python-based utility specifically designed to automate and simplify the management of multiple display configurations on Linux systems. Its core strength lies in its ability to automatically detect which displays (like projectors, external monitors, or ultrawide screens) are currently connected to your system and then apply a pre-saved configuration (“profile”) that matches that specific hardware setup. This eliminates the need to manually execute commands with xrandr (a command-line tool for configuring display settings) every time you connect or disconnect a display.

This automation is particularly beneficial in your scenario as a professor using a Raspberry Pi 4B with openSUSE Tumbleweed. Imagine moving between different lecture halls, each potentially having a projector with a unique native resolution (1024x768, 1280x800, etc.), then returning to your office to connect to an ultrawide monitor (e.g., 3440x1440), and perhaps later connecting to a standard monitor in a departmental meeting room. Without autorandr, each transition would require manual reconfiguration. With autorandr, once you’ve saved a profile for each setup, the system adapts automatically upon connection. Furthermore, on a modern system like openSUSE Tumbleweed, autorandr integrates seamlessly with the systemd init system. This means it can leverage udev events (the system’s way of detecting hardware changes like plugging in a monitor) to trigger the profile switching automatically in the background, offering a truly hands-off experience after the initial setup.

Key Benefits Elaborated:

  • Automatic Detection & Application: autorandr intelligently identifies connected displays, often using their unique EDID information, and compares this against its library of saved profiles to find the best match, applying it instantly.
  • Ease of Use: The “save once, use anywhere” philosophy drastically reduces complexity. Instead of remembering complex xrandr commands or navigating display settings repeatedly, you perform the setup once per unique display combination and save it with a memorable name.
  • Flexibility: It excels at managing numerous distinct profiles. You can have profiles for single displays, dual displays in different arrangements (extended desktop, mirrored), displays with specific resolutions or refresh rates, covering virtually any common scenario you encounter.
  • Robustness: It gracefully handles situations where display EDID information might be missing, corrupt, or ambiguous – common issues with older projectors or certain adapters. Instead of failing outright or requiring manual intervention like raw xrandr might, autorandr can use other detected properties or fall back to a predefined default profile, ensuring you usually get a usable display state.

#Prerequisites (Raspberry Pi 4B Specific)

Before diving into autorandr installation, optimizing your Raspberry Pi 4B environment is crucial for smooth display operation, especially given its shared memory architecture:

  • GPU Memory (gpu_mem): The Raspberry Pi dynamically allocates system RAM between the CPU and the VideoCore GPU. Insufficient memory allocated to the GPU can lead to various graphical issues, such as visual glitches, screen tearing, an inability to drive displays at their native (especially high) resolutions (like 4K), or even completely blank screens. While the default allocation might be sufficient for basic desktop use, connecting multiple monitors or high-resolution displays often requires more. Check your current allocation and consider increasing it if you face issues. You can adjust this by editing the /boot/efi/extraconfig.txt file (the path might vary slightly depending on the exact Tumbleweed Pi image setup) and adding or modifying a line like gpu_mem=256 or gpu_mem=512 (allocating 256MB or 512MB respectively). A reboot is required for this change to be applied by the system.
  • Firmware & System Updates: The Raspberry Pi’s firmware and the Linux kernel’s graphics drivers (like V3D DRM) are continually updated to improve hardware compatibility, fix bugs, and enhance performance. These updates often include improved handling of display detection protocols like EDID. Keeping your openSUSE Tumbleweed system fully updated is the best way to ensure you have the latest fixes and broadest compatibility. Use the standard Tumbleweed update command, sudo zypper dup (which performs a full system upgrade). Regularly running this ensures you benefit from the latest improvements relevant to display handling.

#Installation on openSUSE Tumbleweed

You have several avenues to install autorandr on your system:

  1. Check Standard Repositories (Try First): OpenSUSE Tumbleweed might already include autorandr in its main repositories. You can check its availability and install it if found:
    # Check if the package exists
    zypper info autorandr
    # If available, install it
    sudo zypper refresh
    sudo zypper install autorandr

  2. Recommended Method (openSUSE Build Service - OBS): The autorandr author often maintains a more up-to-date version in a dedicated OBS repository. This is generally the preferred method if the package isn’t in the main repos or if you need the latest features/fixes:
    # Add the repository
    sudo zypper addrepo https://download.opensuse.org/repositories/home:phillipberndt/openSUSE_Tumbleweed/home:phillipberndt.repo
    # Refresh repository metadata
    sudo zypper refresh
    # Install autorandr from the new repo
    sudo zypper install autorandr

  3. Alternative Method (pip): You can install autorandr using Python’s package installer, pip. However, be aware that this method might install it only for the current user, might not integrate as seamlessly with system-wide services like systemd/udev, and might require manual handling of non-Python dependencies. It can also lead to conflicts if system packages also provide parts of the dependencies.
    # Ensure pip is installed, then install autorandr
    sudo zypper install python3-pip
    sudo pip install autorandr

  4. Alternative Method (From Source): For developers or those needing the absolute latest code, you can clone the autorandr Git repository and install it manually. This typically requires development tools like gcc and make, and you’ll need to manage dependencies yourself. Consult the README file in the repository for specific instructions.
    # Example (dependencies might vary)
    sudo zypper install git make python3-devel
    git clone https://github.com/phillipberndt/autorandr.git
    cd autorandr
    sudo make install

#Basic Configuration: Creating and Managing Profiles

The fundamental workflow for using autorandr revolves around capturing the state of your display setup for each unique configuration you use:

  1. Connect: Physically connect your Raspberry Pi to the specific combination of displays (e.g., a single classroom projector, your dual monitors at the office).
  2. Configure Manually (Once): Use your preferred method to arrange the displays exactly how you want them for this specific setup. This could be:
    • Desktop Environment Tools: Use the graphical display settings panel provided by your desktop environment (e.g., xfce4-display-settings in Xfce, the ‘Displays’ panel in GNOME Settings). Here you can typically enable/disable monitors, set resolutions, refresh rates, orientation, and define primary displays and relative positions in an extended desktop.
    • Manual xrandr Commands: For more fine-grained control or scripting, use xrandr directly in the terminal. For example: xrandr --output HDMI-1 --mode 1920x1080 --primary --output DisplayPort-1 --mode 2560x1440 --rotate left --right-of HDMI-1.
  3. Save Profile: Once the displays are configured correctly, save this entire state as an autorandr profile using a descriptive name. Choosing a consistent naming convention can be helpful, e.g., location-displaytype-resolution or setup_description.
    # Example for a specific lecture hall projector
    autorandr --save lecturehallB-projector-1280x800

    # Example for your office ultrawide setup
    autorandr --save office-ultrawide-3440x1440

    # Example for a standard 1080p monitor used for testing
    autorandr --save lab-monitor-1080p

    It’s a good idea to back up this directory periodically, especially if you have complex or finely-tuned profiles.

  4. List Profiles: To review the profiles you have saved:
    autorandr --list

#Applying Configurations

  • Manual Application: You can manually trigger autorandr to detect the currently connected displays and apply the best-matching profile from your saved library. This is useful for testing or if automation isn’t set up.
    autorandr --change

    Behind the scenes, autorandr --change performs several steps: it detects all connected displays and their properties (like EDID, which is data that allows a display to communicate its capabilities to the graphics card), compares this information against the data stored in each of your saved profiles, calculates a “match score” for each profile based on how well it fits the current hardware, and then automatically executes the xrandr commands stored within the highest-scoring profile (if the score exceeds a certain threshold). If no profile matches well enough, it might load the designated default profile or leave the configuration unchanged, depending on your setup.

#Automation

#!/usr/bin/env bash # # debian_display_setup.sh # A comprehensive script to guide through display setup and management on Debian Bullseye, # integrating the logic of five specialized scripts. # # IMPORTANT USAGE NOTES: # - Options marked (MUST RUN SCRIPT WITH SUDO) require you to invoke this entire script # using 'sudo bash debian_display_setup.sh'. # - Options marked (RUN IN X SESSION) must be run as your normal user from a terminal # emulator within your active graphical desktop session. # - Options marked (NEEDS ROOT FOR FULL DETAILS) will provide more comprehensive information # if the script is run with 'sudo', but may offer limited functionality otherwise. # set -euo pipefail # --- Global Variables --- MONITOR_INFO_OUTDIR="" CUSTOM_XRANDR_SCRIPT_PATH="${HOME}/my_custom_display_config.sh" # User's editable script CONFIG_DIR="${HOME}/Desktop/01-document/dotfiles/debian_display_master" PREP_DONE_FLAG="${CONFIG_DIR}/system_prep_done.flag" # --- Utility Functions --- ensure_config_dir() { mkdir -p "$CONFIG_DIR" if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then # If root created it due to sudo, chown to original user chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$CONFIG_DIR" 2>/dev/null || true fi } # --- Stage 1: System Preparation (from bash5.sh) --- run_system_preparation() { echo echo "--- Stage 1: One-Time System Preparation ---" echo "This step installs necessary packages and configures autorandr services." if [[ $EUID -ne 0 ]]; then echo "ERROR: This step MUST be run with root privileges." echo "Please exit and re-run the entire script using: sudo bash $0" return 1 fi if [[ -f "$PREP_DONE_FLAG" ]]; then read -rp "System preparation appears to have been run before (marker at $PREP_DONE_FLAG). Run again? (y/N): " confirm_rerun if [[ ! "$confirm_rerun" =~ ^[Yy]$ ]]; then echo "Skipping system preparation." # Ensure services are at least attempted to be started if they exist and prep was done if systemctl list-unit-files | grep -q autorandr.service && ! systemctl is-active --quiet autorandr.service; then echo "Attempting to start autorandr.service..." systemctl start autorandr.service || echo "Warning: Failed to start autorandr.service." fi if systemctl list-unit-files | grep -q autorandr-resume.service && ! systemctl is-active --quiet autorandr-resume.service; then echo "Attempting to start autorandr-resume.service..." systemctl start autorandr-resume.service || echo "Warning: Failed to start autorandr-resume.service." fi return 0 fi fi echo ">>> Starting one-time system preparation for display management..." echo ">>> Updating package lists (apt update)..." if ! apt update; then echo "ERROR: 'apt update' failed. Please check your internet connection and package sources." return 1 fi echo ">>> Installing autorandr and python3-pip..." if ! apt install -y autorandr python3-pip; then echo "ERROR: Failed to install autorandr or python3-pip." return 1 fi echo ">>> Installing tools for display information gathering and configuration..." echo " (read-edid ddcutil hwinfo inxi lshw x11-xserver-utils edid-decode bc)" if ! apt install -y read-edid ddcutil hwinfo inxi lshw x11-xserver-utils edid-decode bc; then echo "ERROR: Failed to install one or more display utility packages." return 1 fi echo ">>> System Updates Recommendation..." echo " It's highly recommended to keep your system updated." echo " You can do this by running: sudo apt update && sudo apt full-upgrade -y" echo ">>> GPU Memory Configuration (Conditional - Primarily for Raspberry Pi or similar SBCs)..." echo " If using a Raspberry Pi, check/adjust GPU memory in /boot/config.txt if needed (e.g., gpu_mem=256)." echo ">>> Enabling and starting autorandr systemd services..." if systemctl list-unit-files | grep -q autorandr.service; then if systemctl enable --now autorandr.service; then echo " autorandr.service has been enabled and started." else echo " WARNING: Failed to enable/start autorandr.service." fi if systemctl list-unit-files | grep -q autorandr-resume.service; then if systemctl enable --now autorandr-resume.service; then echo " autorandr-resume.service has been enabled and started." else echo " WARNING: Failed to enable/start autorandr-resume.service." fi fi else echo " WARNING: autorandr.service not found. This is unexpected for the Debian Bullseye package." echo " Automatic hotplug detection via systemd might not function as described." fi ensure_config_dir # Ensure ~/Desktop/01-document/dotfiles/debian_display_master exists date > "$PREP_DONE_FLAG" # Create/update the flag file echo " System preparation completion marker set in $PREP_DONE_FLAG" echo ">>> One-time system preparation script finished." echo " Please REBOOT if you made changes like GPU memory configuration or if prompted by package installations." echo " Next, proceed to 'Collect Monitor Information'." return 0 } # --- Stage 2: Collect Monitor Information (from bash2.sh) --- run_monitor_info_collection() { echo echo "--- Stage 2: Collect Monitor Information ---" echo "This step gathers detailed information about your connected monitors." echo "Full details (e.g., from ddcutil, get-edid) require running this script with 'sudo'." # --- Helper functions from bash2.sh, scoped locally --- _mi_check_privileges_for_tool() { local tool_name="$1" if [[ $EUID -ne 0 ]]; then echo "INFO: '$tool_name' provides more details or requires root. Running with limited privileges or skipping." return 1 # False (not root) fi return 0 # True (is root) } _mi_install_tools_explicit_check() { local missing_pkgs="" for pkg in read-edid ddcutil hwinfo inxi lshw x11-xserver-utils edid-decode bc; do if ! dpkg -s "$pkg" &> /dev/null; then missing_pkgs="$missing_pkgs $pkg" fi done if [[ -n "$missing_pkgs" ]]; then echo "WARNING: Some required packages are missing:$missing_pkgs" >&2 echo " Please run 'Stage 1: System Preparation' or 'sudo apt install$missing_pkgs'" >&2 return 1 fi return 0 } local CURRENT_OUTDIR # Local to this function call _mi_setup_outdir() { local REAL_USER_EFFECTIVE NON_ROOT_HOME OUTDIR_BASE # Determine the non-root user if sudo was used if [[ $EUID -eq 0 && -n "$SUDO_USER" && "$SUDO_USER" != "root" ]]; then REAL_USER_EFFECTIVE="$SUDO_USER" else REAL_USER_EFFECTIVE=$(whoami) # Current effective user fi NON_ROOT_HOME=$(getent passwd "$REAL_USER_EFFECTIVE" | cut -d: -f6) if [[ -n "$NON_ROOT_HOME" && -d "$NON_ROOT_HOME" ]]; then OUTDIR_BASE="$NON_ROOT_HOME" else # Fallback if home not found or current user is root without SUDO_USER context OUTDIR_BASE="/tmp" echo "Warning: Could not determine a standard user home directory. Using $OUTDIR_BASE." fi CURRENT_OUTDIR="${OUTDIR_BASE}/monitor-info-$(date +%Y%m%d-%H%M%S)" if mkdir -p "$CURRENT_OUTDIR"; then echo " Monitor information output directory: $CURRENT_OUTDIR" MONITOR_INFO_OUTDIR="$CURRENT_OUTDIR" # Assign to global # Ensure the original user can access it if created by root in their home if [[ $EUID -eq 0 && -n "$SUDO_USER" && "$OUTDIR_BASE" == "$(getent passwd "$SUDO_USER" | cut -d: -f6)" ]]; then chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$CURRENT_OUTDIR" || echo " Warning: Could not chown $CURRENT_OUTDIR to $SUDO_USER" fi # Create summary.txt initially so tee -a works correctly touch "$MONITOR_INFO_OUTDIR/summary.txt" if [[ $EUID -eq 0 && -n "$SUDO_USER" ]]; then chown "$SUDO_USER:$(id -gn "$SUDO_USER")" "$MONITOR_INFO_OUTDIR/summary.txt" 2>/dev/null || true fi else echo "ERROR: Could not create output directory $CURRENT_OUTDIR. Please check permissions." MONITOR_INFO_OUTDIR="" return 1 fi return 0 } _mi_log_cmd() { local logfile="$1"; shift if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then echo "ERROR (log_cmd): MONITOR_INFO_OUTDIR not set."; return 1; fi { echo "===== $(date '+%F %T') : $* =====" "$@" 2>&1 || echo "(ERROR: '$*' failed with exit code $?)" echo } >>"$MONITOR_INFO_OUTDIR/$logfile" } local -a DRM_ALL DRM_EDID XRANDR_ALL XRANDR_CONNECTED # Local to this function call _mi_detect_connectors() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi DRM_ALL=() DRM_EDID=() XRANDR_ALL=() XRANDR_CONNECTED=() # Regex for connector names like HDMI-A-1, DP-1, eDP-1, DVI-D-1, LVDS-0 etc. # Matches: Prefix(letters) - Suffix(letters/numbers) - Number OR Prefix(letters) - Number local connector_regex='^[a-zA-Z]+(-[a-zA-Z0-9]+)*-[0-9]+$' for card_path in /sys/class/drm/card*; do if [[ -d "$card_path" ]]; then for path_in_card in "$card_path"/*; do local name name=$(basename "$path_in_card") if [[ -d "$path_in_card" && "$name" =~ $connector_regex ]]; then DRM_ALL+=("$name") if [[ -r "$path_in_card/edid" && -s "$path_in_card/edid" ]]; then DRM_EDID+=("$name") fi fi done fi done if command -v xrandr &> /dev/null && xhost >/dev/null 2>&1; then while IFS= read -r line; do local out out=$(awk '/ connected/{print $1} / disconnected/{print $1}' <<<"$line") [[ -n "$out" ]] && XRANDR_ALL+=("$out") [[ $line == *" connected"* ]] && XRANDR_CONNECTED+=("$out") done < <(xrandr 2>/dev/null) else echo "NOTE: xrandr queries skipped (X server not accessible or xrandr not found)." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" fi { echo "All DRM connectors found: ${DRM_ALL[*]:-none}" echo "DRM connectors with readable EDID: ${DRM_EDID[*]:-none}" echo "xrandr outputs (if X session active): ${XRANDR_ALL[*]:-none}" echo "Connected outputs via xrandr (if X session active): ${XRANDR_CONNECTED[*]:-none}" } | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" } _mi_collect_sysfs_edid() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi if ! command -v parse-edid &> /dev/null && ! command -v edid-decode &> /dev/null; then echo "Skipping sysfs EDID parsing: neither parse-edid nor edid-decode found." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi local parser_cmd parser_cmd=$(command -v edid-decode || command -v parse-edid) for CON_BASENAME in "${DRM_EDID[@]}"; do local edid_path="" for card_path in /sys/class/drm/card*; do # Find the card parent if [[ -e "$card_path/$CON_BASENAME/edid" ]]; then edid_path="$card_path/$CON_BASENAME/edid" break fi done if [[ -n "$edid_path" && -r "$edid_path" ]]; then _mi_log_cmd "edid_sysfs_${CON_BASENAME}.log" "$parser_cmd" <"$edid_path" else echo "Could not read sysfs EDID for $CON_BASENAME (path: $edid_path or not found)" | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" fi done } _mi_collect_getedid() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi if ! _mi_check_privileges_for_tool "get-edid"; then return; fi if ! command -v get-edid &> /dev/null; then echo "Skipping get-edid: command not found." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi if ! command -v parse-edid &> /dev/null && ! command -v edid-decode &> /dev/null; then echo "Skipping get-edid parsing: no EDID parser found." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi local parser_cmd parser_cmd=$(command -v edid-decode || command -v parse-edid) if ! lsmod | grep -q "i2c_dev"; then echo "INFO: i2c_dev module not loaded. get-edid might fail. Consider 'sudo modprobe i2c_dev'." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" fi _mi_log_cmd "edid_getedid.log" bash -c "get-edid 2>/dev/null | $parser_cmd" } _mi_collect_ddc() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi if ! _mi_check_privileges_for_tool "ddcutil"; then return; fi if ! command -v ddcutil &> /dev/null; then echo "Skipping ddcutil: command not found." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi if ! lsmod | grep -q "i2c_dev"; then echo "INFO: i2c_dev module not loaded. ddcutil might fail. Consider 'sudo modprobe i2c_dev'." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" fi _mi_log_cmd "ddcutil_detect.log" ddcutil detect --verbose } _mi_collect_general_info() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi if command -v xrandr &> /dev/null && xhost >/dev/null 2>&1; then _mi_log_cmd "xrandr_verbose.log" xrandr --verbose fi # hwinfo, inxi, lshw can provide more with root local sudo_prefix="" if [[ $EUID -ne 0 ]]; then echo "INFO: hwinfo, inxi, lshw provide more details with root privileges." # Not prompting for sudo here, rely on script being run with sudo if full details desired. else sudo_prefix="" # Already root fi command -v hwinfo &> /dev/null && _mi_log_cmd "hwinfo_monitor.log" ${sudo_prefix} hwinfo --monitor --verbose command -v inxi &> /dev/null && _mi_log_cmd "inxi_Gxx.log" ${sudo_prefix} inxi -Gxx --display command -v lshw &> /dev/null && _mi_log_cmd "lshw_display.log" ${sudo_prefix} lshw -C display -sanitize } _mi_collect_udev() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi for CON_BASENAME in "${DRM_ALL[@]}"; do local sys_path="" for card_path in /sys/class/drm/card*; do if [[ -d "$card_path/$CON_BASENAME" ]]; then sys_path="$card_path/$CON_BASENAME" break fi done if [[ -n "$sys_path" && -e "$sys_path" ]]; then _mi_log_cmd "udevadm_${CON_BASENAME}.log" udevadm info --query=all --path="$(readlink -f "$sys_path")" fi done } _mi_generate_cvt_interactive() { if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then return 1; fi if ! command -v cvt &> /dev/null; then echo "Skipping CVT modeline generation: cvt command not found (part of x11-xserver-utils)." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi local W H R echo read -rp "Enter 'width height refresh' (e.g. 1920 1080 60) to generate a CVT modeline, or ENTER to skip: " W H R if [[ -z "$W" || -z "$H" || -z "$R" ]]; then echo "Skipping CVT modeline generation by user request." return fi if ! [[ "$W" =~ ^[0-9]+$ && "$H" =~ ^[0-9]+$ && "$R" =~ ^[0-9]+([.][0-9]+)?$ ]]; then echo "Invalid input for width, height, or refresh. Please use numbers." | tee -a "$MONITOR_INFO_OUTDIR/summary.txt" return fi local MODELINE_FULL MODELINE_PARAMS NAME MODELINE_FULL=$(cvt "$W" "$H" "$R" 2>/dev/null | grep Modeline) if [[ -n "$MODELINE_FULL" ]]; then # Extract the part after "Modeline " MODELINE_PARAMS_WITH_NAME=$(echo "$MODELINE_FULL" | sed 's/Modeline //') # Extract just the name (e.g., "1920x1080_60.00") NAME=$(echo "$MODELINE_PARAMS_WITH_NAME" | awk '{print $1}' | tr -d '"') # Extract parameters after the name MODELINE_PARAMS=$(echo "$MODELINE_PARAMS_WITH_NAME" | sed 's/^"[^"]*" //') echo "Generated Modeline: $MODELINE_FULL" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" echo " Mode Name for xrandr: $NAME" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" echo " Parameters for xrandr --newmode: $MODELINE_PARAMS" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" echo "This modeline string can be used in your custom Xrandr script (Stage 3)." # Optional immediate test if in X session if command -v xrandr &> /dev/null && xhost >/dev/null 2>&1; then local TARGET if [[ ${#XRANDR_CONNECTED[@]} -gt 0 ]]; then TARGET=${XRANDR_CONNECTED[0]}; fi if [[ -n "$TARGET" ]]; then read -rp "Attempt to temporarily apply this new mode '$NAME' to '$TARGET' for testing? (y/N): " APPLY_CVT if [[ "$APPLY_CVT" =~ ^[Yy]$ ]]; then echo "Applying: xrandr --newmode \"$NAME\" $MODELINE_PARAMS" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" if xrandr --newmode "$NAME" $MODELINE_PARAMS; then echo "Applying: xrandr --addmode \"$TARGET\" \"$NAME\"" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" if xrandr --addmode "$TARGET" "$NAME"; then echo "Mode '$NAME' added to '$TARGET'. To activate it now (temporarily): xrandr --output \"$TARGET\" --mode \"$NAME\"" | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" else echo "ERROR: xrandr --addmode failed." | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log"; fi else echo "ERROR: xrandr --newmode failed (mode might already exist or be invalid)." | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log"; fi fi fi else echo " (X session not active or xrandr not found for immediate mode application test)." fi else echo "ERROR: Failed to generate modeline with cvt $W $H $R." | tee -a "$MONITOR_INFO_OUTDIR/cvt_modeline.log" fi } # --- Main execution for monitor info collection --- if ! _mi_install_tools_explicit_check; then echo "Monitor info collection cannot proceed due to missing tools." return 1 fi if ! _mi_setup_outdir; then # Sets MONITOR_INFO_OUTDIR return 1 # Error message already printed by _mi_setup_outdir fi echo " Collecting information. This may take a few moments..." _mi_detect_connectors _mi_collect_sysfs_edid _mi_collect_getedid _mi_collect_ddc _mi_collect_general_info _mi_collect_udev _mi_generate_cvt_interactive echo echo " Monitor information collection Done." echo " Review logs and summary.txt in: $MONITOR_INFO_OUTDIR" echo " Key information for the next steps (connector names, modelines) can be found there." return 0 } # --- Stage 3: Create/Edit Custom Xrandr Configuration Script (from bash4.sh template) --- edit_xrandr_template_config() { echo echo "--- Stage 3: Create/Edit Custom Xrandr Configuration Script ---" echo "This step will help you create a custom script to configure your displays." echo "It uses a template based on 'bash4.sh' (a reference script)." echo "Your custom script will be saved at: $CUSTOM_XRANDR_SCRIPT_PATH" if [[ -z "$MONITOR_INFO_OUTDIR" ]]; then echo "Warning: Monitor information doesn't seem to have been collected in this session." echo " You may need to run 'Collect Monitor Information' first to get necessary details." read -rp "Proceed to create/edit template anyway? (y/N): " proceed_warn if [[ ! "$proceed_warn" =~ ^[Yy]$ ]]; then return fi else echo " Refer to the information collected in: $MONITOR_INFO_OUTDIR" fi if [[ ! -f "$CUSTOM_XRANDR_SCRIPT_PATH" ]]; then echo " Creating template script at $CUSTOM_XRANDR_SCRIPT_PATH..." # Heredoc for bash4.sh content, adapted cat > "$CUSTOM_XRANDR_SCRIPT_PATH" << 'EOF_XRANDR_CONFIG_TEMPLATE' #!/bin/bash # Custom Xrandr Configuration Script # EDIT THIS FILE with your specific monitor outputs, modelines, and layout. # Use information from the 'monitor-info' collection step (Stage 2 of the master script). # # This script is intended to be run from an active X Window System session as your normal user. # These settings will only apply to the current X session. # # After testing, you can use these commands to create an autorandr profile. # Check if xrandr command is available if ! command -v xrandr &> /dev/null; then echo "Error: xrandr command not found. Please ensure it is installed and in your PATH." exit 1 fi # Check if bc command is available (for DPI calculations) BC_AVAILABLE=false if command -v bc &> /dev/null; then BC_AVAILABLE=true else echo "Warning: bc command not found. DPI calculation will be skipped if enabled." fi echo "Applying custom temporary xrandr settings..." echo "Verify your display identifiers (e.g., HDMI-1, DP-1) by running 'xrandr' in a terminal if unsure." # --- USER CONFIGURATION SECTION --- # TODO: Replace these with your actual values based on 'monitor-info' output and desired setup. # Example for Display 1 (e.g., your primary laptop screen or main desktop monitor) OUTPUT_1_NAME="eDP-1" # e.g., DP-1, eDP-1, LVDS-1. Check 'xrandr' or monitor-info logs. MODE_1_NAME="1920x1080" # e.g., 1920x1080_60.00 or just 1920x1080 if a standard mode. # Get from monitor-info or 'xrandr' output for connected monitors. # If using a custom CVT modeline: # MODELINE_1_PARAMS="173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync" # Paste parameters after mode name MODELINE_1_PARAMS="" # Leave empty if using a standard, already known mode. # Physical dimensions & DPI for OUTPUT_1 (Optional) OUTPUT_1_PHYS_WIDTH_MM=344 # Physical width in mm (e.g., 344 for a 15.6" 16:9 display) OUTPUT_1_PHYS_HEIGHT_MM=193 # Physical height in mm (e.g., 193 for a 15.6" 16:9 display) OUTPUT_1_MODE_WIDTH_PX=1920 # Pixel width for MODE_1_NAME (e.g., 1920) OUTPUT_1_MODE_HEIGHT_PX=1080 # Pixel height for MODE_1_NAME (e.g., 1080) OUTPUT_1_CALCULATE_DPI=false # Set to true to attempt DPI calculation for this. # Example for Display 2 (e.g., an external HDMI monitor) - uncomment and configure if you have one # OUTPUT_2_NAME="HDMI-1" # MODE_2_NAME="1920x1080" # MODELINE_2_PARAMS="" # OUTPUT_2_PHYS_WIDTH_MM=527 # OUTPUT_2_PHYS_HEIGHT_MM=296 # OUTPUT_2_MODE_WIDTH_PX=1920 # OUTPUT_2_MODE_HEIGHT_PX=1080 # OUTPUT_2_CALCULATE_DPI=false # --- Layout Command --- # TODO: Customize this xrandr command thoroughly! This defines how your monitors are arranged. # Refer to 'man xrandr' for options like --pos, --left-of, --right-of, --above, --below, --primary, --rotate. # Example 1: Single monitor (Output 1 is primary) # XRANDR_CMD_ARGS=(--output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary --auto) # Example 2: Dual monitor - Output 2 (HDMI) right of Output 1 (eDP), Output 1 is primary # Ensure OUTPUT_2_NAME and MODE_2_NAME are set above if using this. # XRANDR_CMD_ARGS=( # --output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary # --output "$OUTPUT_2_NAME" --mode "$MODE_2_NAME" --right-of "$OUTPUT_1_NAME" --auto # ) # Example 3: Mirroring Output 1 to Output 2 # XRANDR_CMD_ARGS=( # --output "$OUTPUT_1_NAME" --mode "$MODE_1_NAME" --primary # --output "$OUTPUT_2_NAME" --mode "$MODE_1_NAME" --same-as "$OUTPUT_1_NAME" # ) # Default placeholder: Output 1, auto mode, primary. MUST BE EDITED FOR YOUR SETUP. XRANDR_CMD_ARGS=(--output "$OUTPUT_1_NAME" --auto --primary) # --- END OF USER CONFIGURATION SECTION --- # --- Script Logic (Generally no need to edit below this line) --- # Define New Modes (if modelines are provided) echo "" if [[ -n "$OUTPUT_1_NAME" && -n "$MODE_1_NAME" && -n "$MODELINE_1_PARAMS" ]]; then echo "Defining new mode for $OUTPUT_1_NAME: $MODE_1_NAME" xrandr --newmode "$MODE_1_NAME" $MODELINE_1_PARAMS if [ $? -ne 0 ]; then echo "Warning: Could not define mode $MODE_1_NAME. It might already exist or modeline is invalid." fi fi if [[ -n "${OUTPUT_2_NAME:-}" && -n "${MODE_2_NAME:-}" && -n "${MODELINE_2_PARAMS:-}" ]]; then echo "Defining new mode for $OUTPUT_2_NAME: $MODE_2_NAME" xrandr --newmode "$MODE_2_NAME" $MODELINE_2_PARAMS if [ $? -ne 0 ]; then echo "Warning: Could not define mode $MODE_2_NAME. It might already exist or modeline is invalid." fi fi # Check current connection status of configured displays echo "" echo "Checking configured display connection status..." IS_OUTPUT_1_CONNECTED=false if [[ -n "$OUTPUT_1_NAME" ]] && xrandr | grep -q "^${OUTPUT_1_NAME}\s*connected"; then IS_OUTPUT_1_CONNECTED=true echo "- $OUTPUT_1_NAME is detected as connected." else if [[ -n "$OUTPUT_1_NAME" ]]; then echo "- $OUTPUT_1_NAME is NOT configured or detected as disconnected."; fi fi IS_OUTPUT_2_CONNECTED=false if [[ -n "${OUTPUT_2_NAME:-}" ]] && xrandr | grep -q "^${OUTPUT_2_NAME}\s*connected"; then IS_OUTPUT_2_CONNECTED=true echo "- $OUTPUT_2_NAME is detected as connected." else if [[ -n "${OUTPUT_2_NAME:-}" ]]; then echo "- $OUTPUT_2_NAME is NOT configured or detected as disconnected."; fi fi # Add modes to outputs (if defined and modelines were used) if [[ -n "$OUTPUT_1_NAME" && -n "$MODE_1_NAME" && -n "$MODELINE_1_PARAMS" && "$IS_OUTPUT_1_CONNECTED" == "true" ]]; then echo "Adding mode $MODE_1_NAME to $OUTPUT_1_NAME" xrandr --addmode "$OUTPUT_1_NAME" "$MODE_1_NAME" if [ $? -ne 0 ]; then echo "Warning: Could not add mode $MODE_1_NAME to $OUTPUT_1_NAME."; fi fi if [[ -n "${OUTPUT_2_NAME:-}" && -n "${MODE_2_NAME:-}" && -n "${MODELINE_2_PARAMS:-}" && "$IS_OUTPUT_2_CONNECTED" == "true" ]]; then echo "Adding mode $MODE_2_NAME to $OUTPUT_2_NAME" xrandr --addmode "$OUTPUT_2_NAME" "$MODE_2_NAME" if [ $? -ne 0 ]; then echo "Warning: Could not add mode $MODE_2_NAME to $OUTPUT_2_NAME."; fi fi # Apply Main Layout Command echo "" echo "Attempting to apply layout: xrandr ${XRANDR_CMD_ARGS[*]}" if xrandr "${XRANDR_CMD_ARGS[@]}"; then echo "Successfully applied layout." else echo "ERROR: Failed to apply layout command: xrandr ${XRANDR_CMD_ARGS[*]}" echo "Please check your XRANDR_CMD_ARGS, output names, and modes." exit 1 fi # DPI Calculation TARGET_DPI_OUTPUT_NAME="" TARGET_DPI_MODE_WIDTH_PX=0 TARGET_DPI_MODE_HEIGHT_PX=0 TARGET_DPI_PHYS_WIDTH_MM=0 TARGET_DPI_PHYS_HEIGHT_MM=0 if [[ "$OUTPUT_1_CALCULATE_DPI" == "true" && "$IS_OUTPUT_1_CONNECTED" == "true" ]]; then TARGET_DPI_OUTPUT_NAME="$OUTPUT_1_NAME" TARGET_DPI_MODE_WIDTH_PX=$OUTPUT_1_MODE_WIDTH_PX TARGET_DPI_MODE_HEIGHT_PX=$OUTPUT_1_MODE_HEIGHT_PX TARGET_DPI_PHYS_WIDTH_MM=$OUTPUT_1_PHYS_WIDTH_MM TARGET_DPI_PHYS_HEIGHT_MM=$OUTPUT_1_PHYS_HEIGHT_MM elif [[ "${OUTPUT_2_CALCULATE_DPI:-false}" == "true" && "$IS_OUTPUT_2_CONNECTED" == "true" ]]; then TARGET_DPI_OUTPUT_NAME="$OUTPUT_2_NAME" TARGET_DPI_MODE_WIDTH_PX=$OUTPUT_2_MODE_WIDTH_PX TARGET_DPI_MODE_HEIGHT_PX=$OUTPUT_2_MODE_HEIGHT_PX TARGET_DPI_PHYS_WIDTH_MM=$OUTPUT_2_PHYS_WIDTH_MM TARGET_DPI_PHYS_HEIGHT_MM=$OUTPUT_2_PHYS_HEIGHT_MM fi if [[ -n "$TARGET_DPI_OUTPUT_NAME" && "$BC_AVAILABLE" == "true" ]]; then echo "" echo "Attempting DPI calculation for $TARGET_DPI_OUTPUT_NAME." if [ "$TARGET_DPI_PHYS_WIDTH_MM" -gt 0 ] && [ "$TARGET_DPI_PHYS_HEIGHT_MM" -gt 0 ] && \ [ "$TARGET_DPI_MODE_WIDTH_PX" -gt 0 ] && [ "$TARGET_DPI_MODE_HEIGHT_PX" -gt 0 ]; then DPI_H_CALC=$(bc -l <<< "scale=2; $TARGET_DPI_MODE_WIDTH_PX / ($TARGET_DPI_PHYS_WIDTH_MM / 25.4)") DPI_V_CALC=$(bc -l <<< "scale=2; $TARGET_DPI_MODE_HEIGHT_PX / ($TARGET_DPI_PHYS_HEIGHT_MM / 25.4)") if [[ "$DPI_H_CALC" =~ ^[0-9]+([.][0-9]+)?$ && "$DPI_V_CALC" =~ ^[0-9]+([.][0-9]+)?$ ]]; then AVG_DPI=$(printf "%.0f" "$(bc -l <<< "($DPI_H_CALC + $DPI_V_CALC) / 2")") echo "Calculated average DPI for $TARGET_DPI_OUTPUT_NAME: $AVG_DPI (H: $DPI_H_CALC, V: $DPI_V_CALC)" echo "Attempting to set screen DPI to $AVG_DPI..." if xrandr --dpi "$AVG_DPI"; then echo "Screen DPI successfully set to $AVG_DPI. This may improve font rendering." else echo "Warning: Could not set screen DPI to $AVG_DPI. Your X server or driver might not support this, or the value might be out of range." fi else echo "Warning: DPI calculation failed or produced non-numeric results (H: $DPI_H_CALC, V: $DPI_V_CALC). Skipping DPI setting." fi else echo "Warning: Physical dimensions or mode resolution for $TARGET_DPI_OUTPUT_NAME are zero or invalid. Cannot calculate DPI." fi elif [[ -n "$TARGET_DPI_OUTPUT_NAME" && "$BC_AVAILABLE" == "false" ]]; then echo "DPI calculation for $TARGET_DPI_OUTPUT_NAME skipped: 'bc' command not available." fi echo "" echo "Custom temporary xrandr settings applied (or attempted)." echo "Current screen configuration (relevant connected displays):" xrandr | grep " connected" || echo "(xrandr found no connected displays or xrandr command failed)" echo "" echo "If this configuration is correct, you can save it as an autorandr profile (Stage 5 of master script)." echo "If you encounter issues, please check:" echo "1. Your display output names and modes in the USER CONFIGURATION SECTION are correct." echo "2. Your displays are properly connected and powered on." echo "3. The modelines (if used) are compatible with your hardware." echo "4. Review any error messages above from xrandr." echo "5. If DPI was set, verify with 'xdpyinfo | grep resolution'." exit 0 EOF_XRANDR_CONFIG_TEMPLATE chmod +x "$CUSTOM_XRANDR_SCRIPT_PATH" else echo " Existing script found at $CUSTOM_XRANDR_SCRIPT_PATH." fi echo "" echo " Please EDIT the script '$CUSTOM_XRANDR_SCRIPT_PATH' with your specific display settings." echo " You will need to set:" echo " - OUTPUT_1_NAME, MODE_1_NAME, MODELINE_1_PARAMS (if using custom modeline)" echo " - Physical dimensions (e.g., OUTPUT_1_PHYS_WIDTH_MM) and OUTPUT_1_CALCULATE_DPI if you want DPI calculation." echo " - Configure OUTPUT_2 variables if you have a second display." echo " - Most importantly, customize the XRANDR_CMD_ARGS array with your full layout command." echo "" if [[ -n "$MONITOR_INFO_OUTDIR" ]]; then echo " Refer to monitor information collected in: $MONITOR_INFO_OUTDIR" fi read -rp "Would you like to open '$CUSTOM_XRANDR_SCRIPT_PATH' with 'nano' now? (y/N): " edit_now if [[ "$edit_now" =~ ^[Yy]$ ]]; then if command -v nano &> /dev/null; then nano "$CUSTOM_XRANDR_SCRIPT_PATH" else echo "'nano' not found. Please edit the file manually using your preferred editor (e.g., vim, gedit, code)." fi else echo "Please edit '$CUSTOM_XRANDR_SCRIPT_PATH' manually." fi echo " After editing, proceed to 'Test Custom Xrandr Configuration'." } # --- Stage 4: Test Custom Xrandr Configuration (runs the edited script) --- test_custom_xrandr_config() { echo echo "--- Stage 4: Test Custom Xrandr Configuration ---" echo "This step executes your custom script to apply display settings temporarily." if [[ ! -f "$CUSTOM_XRANDR_SCRIPT_PATH" ]]; then echo "Custom configuration script '$CUSTOM_XRANDR_SCRIPT_PATH' not found." echo " Please run 'Stage 3: Create/Edit Custom Xrandr Configuration Script' first." return 1 fi if [[ -z "$DISPLAY" ]]; then echo "ERROR: No X session detected (DISPLAY variable is not set)." echo " This step MUST be run from within an active X Window System session" echo " (e.g., from a terminal emulator in your desktop environment) as your normal user." return 1 fi if ! xhost >/dev/null 2>&1; then # A simple check to see if X server is accessible echo "ERROR: Cannot connect to X server. Ensure you are in an active X session." return 1 fi if [[ $EUID -eq 0 ]]; then echo "WARNING: It's recommended to run this test as your normal desktop user, not as root." read -rp "Continue as root anyway? (y/N): " continue_root_test if [[ ! "$continue_root_test" =~ ^[Yy]$ ]]; then return 1; fi fi echo " You are about to execute the script: $CUSTOM_XRANDR_SCRIPT_PATH" echo " This will attempt to change your current display settings." echo " Ensure you have saved any important work." echo " Know how to recover if the display becomes unusable:" echo " - Switch to a TTY (Ctrl+Alt+F2 through F6)." echo " - Log in, then you can try 'sudo systemctl restart display-manager' or 'sudo reboot'." echo " - Or, from TTY, try 'export DISPLAY=:0; xrandr --auto' (may need to find correct DISPLAY)." read -rp "Proceed with testing? (y/N): " confirm_test if [[ ! "$confirm_test" =~ ^[Yy]$ ]]; then echo "Testing aborted." return fi echo " Executing '$CUSTOM_XRANDR_SCRIPT_PATH'..." if bash "$CUSTOM_XRANDR_SCRIPT_PATH"; then echo " Custom Xrandr script executed successfully." else echo " Custom Xrandr script executed with an error (exit code $?)." fi echo "" echo " Test execution finished." echo " If the display configuration is as expected, you can proceed to save it as an autorandr profile." echo " If not, re-edit '$CUSTOM_XRANDR_SCRIPT_PATH' and test again." } # --- Stage 5: Autorandr Profile Management --- save_autorandr_profile() { echo echo "--- Stage 5a: Save Current Configuration as Autorandr Profile ---" echo "This saves your current Xrandr display configuration as an autorandr profile." if [[ -z "$DISPLAY" ]]; then echo "ERROR: No X session detected. Must be run from an active X session."; return 1; fi if [[ $EUID -eq 0 ]]; then echo "WARNING: Saving autorandr profiles is typically done as the desktop user."; fi if ! command -v autorandr &> /dev/null; then echo "ERROR: autorandr command not found. Please run Stage 1 (System Preparation)." return 1 fi echo " Ensure your displays are configured exactly as you want them" echo " (e.g., after a successful test in Stage 4)." local current_profile current_profile=$(autorandr --current 2>/dev/null) if [[ -n "$current_profile" ]]; then echo " Current detected autorandr profile: $current_profile" else echo " No specific autorandr profile currently detected as active (or multiple match)." fi read -rp "Enter a name for this new autorandr profile (e.g., 'home_dual_monitor', 'laptop_only'): " profile_name if [[ -z "$profile_name" ]]; then echo "No profile name entered. Aborting save." return fi # Sanitize profile name (basic: replace spaces and special chars with underscore) profile_name=$(echo "$profile_name" | tr -s ' /\\:&?' '_') echo " Saving current configuration as profile: '$profile_name'..." if autorandr --save "$profile_name"; then echo " Profile '$profile_name' saved successfully." echo " Autorandr should now automatically apply this profile when this display setup is detected" echo " (assuming autorandr services are running from Stage 1)." else echo "ERROR: Failed to save autorandr profile '$profile_name'." fi } view_autorandr_profiles() { echo echo "--- Stage 5b: View Autorandr Profiles ---" if ! command -v autorandr &> /dev/null; then echo "ERROR: autorandr command not found. Please run Stage 1 (System Preparation)." return 1 fi echo " Available autorandr profiles:" autorandr --list local current_profile current_profile=$(autorandr --current 2>/dev/null) if [[ -n "$current_profile" ]]; then echo " Currently active/detected profile(s): $current_profile" fi } load_autorandr_profile() { echo echo "--- Stage 5c: Load Autorandr Profile ---" echo "This attempts to load a saved autorandr profile." if [[ -z "$DISPLAY" ]]; then echo "ERROR: No X session detected. Must be run from an active X session."; return 1; fi if [[ $EUID -eq 0 ]]; then echo "WARNING: Loading autorandr profiles is typically done as the desktop user."; fi if ! command -v autorandr &> /dev/null; then echo "ERROR: autorandr command not found. Please run Stage 1 (System Preparation)." return 1 fi echo " Available profiles:" autorandr --list read -rp "Enter the name of the profile to load: " profile_to_load if [[ -z "$profile_to_load" ]]; then echo "No profile name entered. Aborting." return fi echo " Attempting to load profile '$profile_to_load'..." # Using --change is often preferred as it only applies if the detected setup matches the profile. # --load or --force --load will apply it regardless. if autorandr --change "$profile_to_load"; then echo " Profile '$profile_to_load' loaded/applied (if it matched current hardware or was forced by --change)." echo " If it didn't change, it might be already active or not match. Try 'autorandr --force --load $profile_to_load'." else echo "ERROR: Failed to load/apply autorandr profile '$profile_to_load' with --change." echo " Try 'autorandr --load $profile_to_load' or 'autorandr --force --load $profile_to_load'." fi } # --- Main Menu --- main_menu() { ensure_config_dir # Create ~/Desktop/01-document/dotfiles/debian_display_master if it doesn't exist echo echo "Debian Bullseye Display Setup & Management Utility" echo "==================================================" echo "IMPORTANT: Read script header and option notes for SUDO/X Session requirements." echo PS3="Please choose an option: " options=( "1. Run ONE-TIME System Preparation (MUST RUN SCRIPT WITH SUDO)" "2. Collect Monitor Information (NEEDS ROOT FOR FULL DETAILS)" "3. Create/Edit Custom Xrandr Configuration Script (RUN AS USER)" "4. Test Custom Xrandr Configuration (RUN IN X SESSION AS USER)" "5. Save Current Configuration as Autorandr Profile (RUN IN X SESSION AS USER)" "6. View Autorandr Profiles (RUN AS USER)" "7. Load Autorandr Profile (RUN IN X SESSION AS USER)" "8. Exit" ) COLUMNS=1 # For select formatting select optk in "${!options[@]}"; do # Iterate over keys for robust option handling local opt_text=${options[$optk]} local opt_num=$((optk + 1)) case $opt_num in 1) run_system_preparation ;; 2) run_monitor_info_collection ;; 3) edit_xrandr_template_config ;; 4) test_custom_xrandr_config ;; 5) save_autorandr_profile ;; 6) view_autorandr_profiles ;; 7) load_autorandr_profile ;; 8) echo "Exiting."; exit 0 ;; *) echo "Invalid option $REPLY. Please choose a number from 1 to 8.";; esac # Prompt to continue after each action echo read -rp "Press Enter to return to the menu..." # Re-display menu echo echo "Debian Bullseye Display Setup & Management Utility" echo "==================================================" echo "IMPORTANT: Read script header and option notes for SUDO/X Session requirements." echo done } # --- Script Entry Point --- main_menu exit 0
URL: https://ib.bsb.br/xrandr