- Why Use autorandr?
- Prerequisites (Raspberry Pi 4B Specific)
- Installation on openSUSE Tumbleweed
- Basic Configuration: Creating and Managing Profiles
- Applying Configurations
- Automation
#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:
-
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 -
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 -
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 -
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:
- Connect: Physically connect your Raspberry Pi to the specific combination of displays (e.g., a single classroom projector, your dual monitors at the office).
- 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.
-
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-1080pIt’s a good idea to back up this directory periodically, especially if you have complex or finely-tuned profiles.
- 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 --changeBehind 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