- Why Use autorandr?
- Prerequisites (Raspberry Pi 4B Specific)
- Installation on openSUSE Tumbleweed
- Basic Configuration: Creating and Managing Profiles
- Applying Configurations
- Automation (Recommended)
- Setting a Default Fallback Profile
- Advanced Usage
- Troubleshooting
- Alternative: Hardware HDMI EDID Emulator
- Resources
- A.
monitor-info.sh
(Bash script to collect monitor data) - B. “Improved Temporary Xrandr configuration script”
#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 (Recommended)
For the most convenient experience, configure autorandr to react automatically whenever you connect or disconnect a display:
-
Using systemd Service: This is generally the most robust and manageable method on modern Linux systems like openSUSE Tumbleweed. The autorandr.service unit, when enabled, typically integrates with udev to monitor for display-related hardware events (specifically from the Direct Rendering Manager or DRM subsystem). Upon detecting such an event (e.g., an HDMI cable being plugged in), systemd activates the service, which in turn usually runs autorandr --change to apply the appropriate profile.
# Enable the service to start on boot and start it immediately
sudo systemctl enable --now autorandr.service
# You can check its status later with: systemctl status autorandr.service -
Using udev Rules: autorandr packages often include udev rules (e.g., in /lib/udev/rules.d/) that directly trigger autorandr --change when specific kernel events related to display hardware occur. While this works, managing services through systemd often provides better control, logging, and dependency management. If you installed manually or suspect the rules aren’t active, you might need to reload them:
sudo udevadm control --reload-rules && sudo udevadm triggerWith either automation method properly configured, plugging in a known projector or monitor should automatically result in your saved configuration being applied, although there might be a brief delay (a few seconds) while autorandr detects the display and applies the profile.
#Setting a Default Fallback Profile
It’s highly recommended to define a “fallback” or “default” profile. This profile will be automatically applied by autorandr --change (and thus by the automated services) if you connect a display configuration that doesn’t closely match any of your specifically saved profiles. The primary purpose is to prevent being left with an unusable or awkward display state (like a very low resolution, incorrect mirroring, or only the laptop’s internal display active when an external one is connected but unrecognized).
-
Choose and Save a Safe Profile: Configure your display(s) to a very common and widely supported resolution, like 1920x1080@60Hz or perhaps 1280x720@60Hz, which most monitors and projectors should handle without issue. Save this configuration:
# Example: Configure for 1920x1080 manually first, then save
autorandr --save fallback-1080p -
Set as Default: Tell autorandr to use this profile as the default:
autorandr --default fallback-1080pNow, when connecting an unknown display, autorandr will attempt to apply this safe configuration, maximizing the chances of getting a usable picture.
#Advanced Usage
autorandr offers features beyond basic profile switching:
-
Wildcard EDID Matching: Sometimes you might have several projectors or monitors of the same model series. Their EDIDs might be very similar but differ slightly (e.g., in serial number fields). To create a single profile that matches all of them, you can edit the config file within the profile directory (e.g., ~/.config/autorandr/classroom-projectors/config). Find the line(s) specifying the EDID for the relevant output(s) and replace the differing parts (or less critical parts) with an asterisk (*).
Example snippet from ~/.config/autorandr/some_profile/config:*
output HDMI-1
# edid 00ffffffffffff001e6d[…] # Original specific EDID
edid 00ffffffffffff001e6d* # Matches any EDID starting with this prefixCaution: Be careful not to make the wildcard too broad, or it might incorrectly match unintended displays.
- Hook Scripts: You can automate actions that should occur whenever a specific profile is loaded or unloaded. Create executable scripts (e.g., using bash or python) named preswitch (runs before switching to this profile), postswitch (runs after switching to this profile), predetect (runs before detection), or postdetect (runs after detection) inside a profile’s directory (~/.config/autorandr/<profile_name>/) or the global config directory (~/.config/autorandr/). Common uses for postswitch include:
- Setting a specific desktop wallpaper: feh --bg-scale /path/to/wallpaper.jpg
- Restarting desktop panels if they don’t resize correctly: xfce4-panel -r
- Changing the default audio output sink.
Remember to make the scripts executable: chmod +x ~/.config/autorandr/<profile_name>/postswitch.
- Forcing Matches (–match-edid): In rare troubleshooting scenarios where display properties other than EDID might be causing incorrect profile matching, you can experiment with options like autorandr --change --match-edid to force matching based primarily or solely on the EDID information. Consult man autorandr for details.
#Troubleshooting
If autorandr doesn’t behave as expected:
-
Check Detected Profiles & Scores: See what autorandr currently detects and how well it matches known profiles. The output shows detected profiles and their calculated match scores.
autorandr --detected -
Use Debug Mode: This provides highly detailed output about the detection process, including EDIDs read, profiles considered, matching scores, and the exact xrandr commands being generated and executed. This is invaluable for diagnosing why a specific profile isn’t being selected.
autorandr --change --debug -
Check System Logs: Look for errors or warnings related to the graphics driver (DRM) or EDID processing in the system journal. This can reveal underlying hardware or driver issues.
# View live logs (press Ctrl+C to stop)
journalctl -f | grep -i -E “drm|edid|autorandr”
# View logs from the current boot
journalctl -b | grep -i -E “drm|edid|autorandr” - Ensure System Updates: Reiterate the importance of sudo zypper dup to ensure you have the latest kernel, graphics drivers, and potentially autorandr fixes.
- Check Physical Connections: Sometimes the simplest solution is overlooked. Ensure HDMI or DisplayPort cables are securely plugged in at both the Raspberry Pi and the display ends. Try a different cable if problems persist.
#Alternative: Hardware HDMI EDID Emulator
For displays that consistently cause problems due to missing, corrupt, or non-standard EDID information, a hardware HDMI EDID Emulator (also known as an EDID ghost or dummy plug) can be a viable workaround. This small device plugs into an HDMI port on the Pi and contains a chip pre-programmed with standard EDID data (e.g., for a 1080p monitor). The Raspberry Pi’s operating system reads the EDID from the emulator instead of the actual connected display. This effectively forces the Pi to “see” a standard display, often resolving issues with problematic hardware like some older projectors or KVM switches. While it provides consistency, it lacks the dynamic flexibility of autorandr – it forces one specific configuration regardless of what display (if any) is actually connected downstream. It’s a targeted solution for specific problematic hardware, not a replacement for general-purpose profile management.
#Resources
- autorandr GitHub Repository: The primary source for code, detailed documentation (README), and reporting issues. Check the Issues page here for existing bug reports or troubleshooting discussions. https://github.com/phillipberndt/autorandr
- openSUSE Build Service Package: Link to the specific package page for the OBS repository, useful for checking versions and build status. https://build.opensuse.org/package/show/home:phillipberndt/autorandr
By carefully configuring autorandr and leveraging its automation features, you can significantly streamline the process of using your Raspberry Pi 4B with diverse display setups, making your transitions between classroom, office, and other locations much smoother and less prone to technical interruptions.
#bash script to collect monitor data on Debian
• Installs all required tools (including optional edid-decode).
• Detects every DRM connector (VGA, HDMI, DP, DVI, etc.), whether EDID-capable or not.
• Enumerates connected and disconnected outputs via xrandr.
• Logs EDID (sysfs, get-edid, DDC/CI), general hardware info (hwinfo, inxi, lshw), and udev data.
• Provides a summary and guidance on extracting modeline parameters.
• Offers an optional CVT-based modeline generator/applicator.
Save as ~/monitor-info.sh
, then:
chmod +x ~/monitor-info.sh
sudo ~/monitor-info.sh
Script:
#!/usr/bin/env bash
#
# monitor-info.sh
# Collect comprehensive monitor info for xrandr configuration on Debian Bullseye+.
# Usage: sudo bash ~/monitor-info.sh
#
set -euo pipefail
# ------------------------------------------------------------
# Function: ensure_root
# Abort if not running as root.
# ------------------------------------------------------------
ensure_root() {
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Must be run as root." >&2
exit 1
fi
}
# ------------------------------------------------------------
# Function: install_tools
# Installs required packages in one apt command.
# ------------------------------------------------------------
install_tools() {
apt update
apt install -y \
read-edid \
ddcutil \
hwinfo \
inxi \
lshw \
x11-xserver-utils \
edid-decode || true # optional
}
# ------------------------------------------------------------
# Function: setup_outdir
# Creates a timestamped output directory.
# ------------------------------------------------------------
setup_outdir() {
OUTDIR="${HOME}/monitor-info-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUTDIR"
echo "Output directory: $OUTDIR"
}
# ------------------------------------------------------------
# Function: log_cmd
# Runs a command, logs stdout/stderr to a file, does not exit on error.
# Usage: log_cmd logfile command [args...]
# ------------------------------------------------------------
log_cmd() {
local logfile="$1"; shift
{
echo "===== $(date '+%F %T') : $* ====="
"$@" 2>&1 || echo "(ERROR: '$*' failed with exit code $?)"
echo
} >>"$OUTDIR/$logfile"
}
# ------------------------------------------------------------
# Function: detect_connectors
# Populates arrays: DRM_ALL (all connectors), DRM_EDID (with edid),
# XRANDR_CONNECTED, XRANDR_ALL.
# ------------------------------------------------------------
detect_connectors() {
# DRM: all dirs under /sys/class/drm matching *-*
for path in /sys/class/drm/*-*; do
[[ -e "$path" ]] || continue
local name=$(basename "$path")
DRM_ALL+=("$name")
[[ -r "$path/edid" ]] && DRM_EDID+=("$name")
done
# xrandr: all outputs and connected ones
while IFS= read -r line; do
local out=$(awk '/ connected/{print $1} / disconnected/{print $1}' <<<"$line")
[[ -n "$out" ]] && XRANDR_ALL+=("$out")
[[ $line == *" connected"* ]] && XRANDR_CONNECTED+=("$out")
done < <(xrandr 2>/dev/null)
# Summary print
{
echo "All DRM connectors: ${DRM_ALL[*]:-none}"
echo "DRM connectors with EDID: ${DRM_EDID[*]:-none}"
echo "xrandr outputs: ${XRANDR_ALL[*]:-none}"
echo "Connected outputs: ${XRANDR_CONNECTED[*]:-none}"
} | tee "$OUTDIR/summary.txt"
}
# ------------------------------------------------------------
# Function: collect_sysfs_edid
# Parses EDID from sysfs for connectors in DRM_EDID.
# ------------------------------------------------------------
collect_sysfs_edid() {
for CON in "${DRM_EDID[@]}"; do
log_cmd "edid_sysfs_${CON}.log" parse-edid <"/sys/class/drm/${CON}/edid"
done
}
# ------------------------------------------------------------
# Function: collect_getedid
# Parses EDID via get-edid (ISA bus fallback).
# ------------------------------------------------------------
collect_getedid() {
log_cmd "edid_getedid.log" get-edid | parse-edid
}
# ------------------------------------------------------------
# Function: collect_ddc
# Runs ddcutil detect, get-edid, and capabilities once.
# ------------------------------------------------------------
collect_ddc() {
log_cmd "ddcutil_detect.log" ddcutil detect
log_cmd "ddcutil_edid.log" ddcutil get-edid | parse-edid
log_cmd "ddcutil_caps.log" ddcutil capabilities
}
# ------------------------------------------------------------
# Function: collect_general_info
# Captures xrandr verbose, hwinfo, inxi, lshw.
# ------------------------------------------------------------
collect_general_info() {
log_cmd "xrandr_verbose.log" xrandr --verbose
log_cmd "hwinfo_monitor.log" hwinfo --monitor
log_cmd "inxi_Gxx.log" inxi -Gxx
log_cmd "lshw_display.log" lshw -C display
}
# ------------------------------------------------------------
# Function: collect_udev
# Gathers udevadm info for all DRM_ALL connectors.
# ------------------------------------------------------------
collect_udev() {
for CON in "${DRM_ALL[@]}"; do
local path="/sys/class/drm/${CON}"
log_cmd "udevadm_${CON}.log" udevadm info --query=all --path="$(readlink -f "$path")"
done
}
# ------------------------------------------------------------
# Function: generate_cvt
# Prompts user, generates and optionally applies a CVT modeline.
# ------------------------------------------------------------
generate_cvt() {
read -rp "Enter width height refresh (e.g. 1920 1080 60), or ENTER to skip: " W H R
if [[ -n "$W$H$R" ]]; then
local MODELINE
MODELINE=$(cvt "$W" "$H" "$R" 2>/dev/null | tail -n1)
if [[ -n "$MODELINE" ]]; then
local NAME=$(awk '{print $2}' <<<"$MODELINE")
echo "Modeline: $MODELINE" | tee -a "$OUTDIR/cvt_modeline.log"
local TARGET=${XRANDR_CONNECTED[0]:-}
if [[ -n "$TARGET" ]]; then
xrandr --newmode $MODELINE \
&& xrandr --addmode "$TARGET" "$NAME" \
&& echo "Applied to $TARGET; use 'xrandr --output $TARGET --mode $NAME'"
else
echo "No connected output to apply mode; saved under cvt_modeline.log."
fi
else
echo "ERROR: Failed to generate modeline." | tee -a "$OUTDIR/cvt_modeline.log"
fi
fi
}
# ------------------- Main Execution -------------------
ensure_root
install_tools
# Arrays for connectors
declare -a DRM_ALL DRM_EDID XRANDR_ALL XRANDR_CONNECTED
setup_outdir
detect_connectors
collect_sysfs_edid
collect_getedid
collect_ddc
collect_general_info
collect_udev
generate_cvt
echo
echo "Done. Review logs and summary.txt in $OUTDIR."
echo "To craft an xrandr modeline manually, inspect 'Detailed Timing Descriptors' in the EDID logs."
Next Steps:
- Open
$OUTDIR/summary.txt
to see detected connectors and outputs. - Inspect
edid_sysfs_*.log
andedid_getedid.log
for Detailed Timing Descriptors (pixel clock, hsync/vsync, porches). - Enclose those values within this
bash
script: ``` #!/bin/bash
#Improved Temporary Xrandr configuration script
#These settings will only apply to the current X session and
#will not persist after a reboot or X server restart.
#Ensure this script is run from within an active X Window System session.
#
#This script now checks the connection status of HDMI-1 and DP-1
#before attempting to configure them, based on live ‘xrandr’ output.
#If DP-1 is the only connected monitor, it attempts to set screen DPI
#based on its physical dimensions.
#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 echo “Warning: bc command not found. DPI calculation for DP-1 will be skipped.” echo “Please install ‘bc’ if you want to enable automatic DPI setting.” else BC_AVAILABLE=true fi
echo “Applying temporary xrandr settings…” echo “This script will check current display connection statuses.” echo “Verify your display identifiers (e.g., HDMI-1, DP-1) by running ‘xrandr’ in a terminal.”
#Define mode names and modelines
MODE_NAME_1=”1152x864_60.00” # For DP-1 MODELINE_1=”81.75 1152 1216 1336 1520 864 867 871 897 -hsync +vsync” MODE_1_WIDTH_PX=1152 MODE_1_HEIGHT_PX=864
MODE_NAME_2=”2560x1080_60.00” # For HDMI-1 MODELINE_2=”230.00 2560 2720 2992 3424 1080 1083 1093 1120 -hsync +vsync”
#Define output names
OUTPUT_DP=”DP-1” # DisplayPort output OUTPUT_HDMI=”HDMI-1” # HDMI output
#Physical dimensions for DP-1 (in mm)
DP_WIDTH_MM=419 DP_HEIGHT_MM=236
#— Define New Modes (always attempt this) —
echo “” echo “Defining new mode: $MODE_NAME_1” xrandr –newmode “$MODE_NAME_1” $MODELINE_1 if [ $? -ne 0 ]; then echo “Warning: Could not define mode $MODE_NAME_1.” echo “Possible issues: mode already exists (use ‘xrandr’ to check), incorrect modeline parameters, or xrandr limitations.” fi
echo “Defining new mode: $MODE_NAME_2” xrandr –newmode “$MODE_NAME_2” $MODELINE_2 if [ $? -ne 0 ]; then echo “Warning: Could not define mode $MODE_NAME_2.” echo “Possible issues: mode already exists (use ‘xrandr’ to check), incorrect modeline parameters, or xrandr limitations.” fi
#— Check current connection status of displays —
echo “” echo “Checking current display connection status…” IS_DP_CONNECTED=false if xrandr | grep -q “^${OUTPUT_DP} connected”; then IS_DP_CONNECTED=true echo “- $OUTPUT_DP is detected as connected.” else echo “- $OUTPUT_DP is detected as disconnected.” fi
IS_HDMI_CONNECTED=false if xrandr | grep -q “^${OUTPUT_HDMI} connected”; then IS_HDMI_CONNECTED=true echo “- $OUTPUT_HDMI is detected as connected.” else echo “- $OUTPUT_HDMI is detected as disconnected.” fi
#— Initial Positioning Command (if both displays are connected) —
echo “” if $IS_HDMI_CONNECTED && $IS_DP_CONNECTED; then echo “Attempting to set $OUTPUT_HDMI –auto –above $OUTPUT_DP…” xrandr –output “$OUTPUT_HDMI” –auto –above “$OUTPUT_DP” if [ $? -ne 0 ]; then echo “Warning: Could not set $OUTPUT_HDMI –auto –above $OUTPUT_DP.” else echo “Successfully attempted to position $OUTPUT_HDMI above $OUTPUT_DP.” fi elif ! $IS_HDMI_CONNECTED && $IS_DP_CONNECTED; then echo “Skipping initial positioning: $OUTPUT_HDMI is disconnected. $OUTPUT_DP is primary.” elif $IS_HDMI_CONNECTED && ! $IS_DP_CONNECTED; then echo “Skipping initial positioning: $OUTPUT_DP is disconnected. $OUTPUT_HDMI is primary.” else echo “Skipping initial positioning: Both $OUTPUT_HDMI and $OUTPUT_DP appear to be disconnected.” fi
#— Configure DP Output ($OUTPUT_DP) —
echo “” if $IS_DP_CONNECTED; then echo “Configuring $OUTPUT_DP…” echo “Adding mode $MODE_NAME_1 to $OUTPUT_DP” xrandr –addmode “$OUTPUT_DP” “$MODE_NAME_1” if [ $? -ne 0 ]; then echo “Error adding mode $MODE_NAME_1 to $OUTPUT_DP.” echo “Ensure $OUTPUT_DP is truly connected, the mode was defined, and supports this mode.” else echo “Setting $OUTPUT_DP to mode $MODE_NAME_1” xrandr –output “$OUTPUT_DP” –mode “$MODE_NAME_1” if [ $? -ne 0 ]; then echo “Error setting $OUTPUT_DP to mode $MODE_NAME_1.” else # If DP-1 is the only connected monitor, try to set screen DPI if ! $IS_HDMI_CONNECTED && $BC_AVAILABLE; then echo “” echo “DP-1 ($OUTPUT_DP) appears to be the only connected display and ‘bc’ is available.” echo “Calculating DPI for screen based on DP-1 physical dimensions ($DP_WIDTH_MM mm x $DP_HEIGHT_MM mm) and mode $MODE_NAME_1 ($MODE_1_WIDTH_PX x $MODE_1_HEIGHT_PX).”
# Calculate DPI_H and DPI_V using bc for floating point arithmetic
# scale=2 sets precision for bc output
# Ensure dimensions are not zero to prevent division by zero, though hardcoded here
if [ "$DP_WIDTH_MM" -gt 0 ] && [ "$DP_HEIGHT_MM" -gt 0 ]; then
DPI_H_CALC=$(bc -l <<< "scale=2; $MODE_1_WIDTH_PX / ($DP_WIDTH_MM / 25.4)")
DPI_V_CALC=$(bc -l <<< "scale=2; $MODE_1_HEIGHT_PX / ($DP_HEIGHT_MM / 25.4)")
# Check if bc calculations were successful and returned valid numbers
if [[ -n "$DPI_H_CALC" && -n "$DPI_V_CALC" && "$DPI_H_CALC" =~ ^[0-9]+([.][0-9]+)?$ && "$DPI_V_CALC" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
# Calculate average DPI and round to nearest integer
AVG_DPI=$(printf "%.0f" "$(bc -l <<< "($DPI_H_CALC + $DPI_V_CALC) / 2")")
echo "Calculated average DPI: $AVG_DPI (H: $DPI_H_CALC, V: $DPI_V_CALC)"
echo "Attempting to set screen DPI to $AVG_DPI..."
# Note: 'xrandr --dpi' typically applies to the entire X screen.
xrandr --dpi "$AVG_DPI"
if [ $? -ne 0 ]; then
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."
else
echo "Screen DPI successfully set to $AVG_DPI. This may improve font rendering."
fi
else
echo "Warning: DPI calculation failed or produced non-numeric results. Skipping DPI setting."
echo "DPI_H_CALC raw output: '$DPI_H_CALC'"
echo "DPI_V_CALC raw output: '$DPI_V_CALC'"
fi
else
echo "Warning: DP-1 physical dimensions (DP_WIDTH_MM or DP_HEIGHT_MM) are zero. Cannot calculate DPI."
fi
elif ! $IS_HDMI_CONNECTED && ! $BC_AVAILABLE; then
echo "DP-1 ($OUTPUT_DP) is the only display, but 'bc' is not available. Skipping DPI calculation."
fi
fi
fi else
echo "Skipping configuration for $OUTPUT_DP as it is disconnected." fi
#— Configure HDMI Output ($OUTPUT_HDMI) —
echo “” if $IS_HDMI_CONNECTED; then echo “Configuring $OUTPUT_HDMI…” echo “Adding mode $MODE_NAME_2 to $OUTPUT_HDMI” xrandr –addmode “$OUTPUT_HDMI” “$MODE_NAME_2” if [ $? -ne 0 ]; then echo “Error adding mode $MODE_NAME_2 to $OUTPUT_HDMI.” echo “Ensure $OUTPUT_HDMI is truly connected, the mode was defined, and supports this mode.” else echo “Setting $OUTPUT_HDMI to mode $MODE_NAME_2” xrandr –output “$OUTPUT_HDMI” –mode “$MODE_NAME_2” if [ $? -ne 0 ]; then echo “Error setting $OUTPUT_HDMI to mode $MODE_NAME_2.” fi fi else echo “Skipping configuration for $OUTPUT_HDMI as it is disconnected.” fi
echo “” echo “Temporary xrandr settings applied (or attempted for connected displays).” echo “Current screen configuration (relevant connected displays):” xrandr | grep “ connected”
echo “” echo “If you encounter issues, please check:” echo “1. Your display output names ($OUTPUT_DP, $OUTPUT_HDMI) are correct (use ‘xrandr’ to verify).” echo “2. Your displays are properly connected and powered on.” echo “3. The modelines 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’.”
# setting up as a system service
This process involves several stages:
1. **One-Time System Preparation:** Installing necessary software and performing initial configurations.
2. **Understanding and Using the Provided Scripts:** Detailing the roles of `monitor-info.sh` and the "Improved Temporary Xrandr configuration script."
3. **Configuring `autorandr`:** Manually defining display profiles for your different setups, incorporating logic from the provided scripts.
4. **Automating `autorandr`:** Ensuring `autorandr` applies these profiles automatically when an X session starts and responds to hardware changes.
5. **Advanced `autorandr` Features and Troubleshooting.**
Here's a comprehensive, step-by-step guide:
## Phase 1: One-Time System Preparation
You'll need to run these commands once as root to prepare your system. You can save this as a script (e.g., `debian_display_setup_prep.sh`) and execute it, or run commands individually.
```bash
#!/bin/bash
#
# debian_display_setup_prep.sh
# One-time preparation script for display management on Debian Bullseye.
# Run this script as root: sudo bash debian_display_setup_prep.sh
#
set -euo pipefail
echo ">>> Starting one-time system preparation for display management..."
# Ensure running as root
if [[ $EUID -ne 0 ]]; then
echo "ERROR: This script must be run as root." >&2
exit 1
fi
echo ">>> Updating package lists..."
apt update
echo ">>> Installing autorandr..."
# autorandr is in Debian Bullseye repositories.
# Debian's package typically includes necessary Python dependencies.
# python3-pip is included as the blog post mentions it and it can be useful.
apt install -y autorandr python3-pip
echo ">>> Installing tools for display information gathering and configuration..."
# These tools are used by monitor-info.sh or for manual xrandr setup.
# 'bc' is needed for DPI calculations if you use that part of the example script.
apt install -y read-edid ddcutil hwinfo inxi lshw x11-xserver-utils edid-decode bc
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"
# Consider uncommenting the following to perform a full upgrade now:
# apt full-upgrade -y
echo ">>> GPU Memory Configuration (Conditional - Primarily for Raspberry Pi or similar SBCs)..."
# If your Debian Bullseye system is a Raspberry Pi, ensure sufficient GPU memory.
# Edit /boot/config.txt (NOT /boot/efi/extraconfig.txt).
# Example: Add or modify 'gpu_mem=256' or 'gpu_mem=512'.
# A reboot is required for this change.
# This step is generally not applicable for standard PCs with dedicated GPUs.
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..."
# The Debian autorandr package includes autorandr.service (for udev/DRM events like hotplug)
# and often autorandr-resume.service (for system resume).
# Enabling these provides robust, system-level display management.
if systemctl list-unit-files | grep -q autorandr.service; then
systemctl enable --now autorandr.service
echo "autorandr.service has been enabled and started."
# Also enable the resume service if it exists
if systemctl list-unit-files | grep -q autorandr-resume.service; then
systemctl enable --now autorandr-resume.service
echo "autorandr-resume.service has been enabled and started."
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
echo ">>> One-time system preparation script finished."
echo "Please REBOOT if you made changes like GPU memory configuration."
echo "Next, understand the provided scripts and then manually configure autorandr profiles."
To use this script:
- Save it as
debian_display_setup_prep.sh
. - Make it executable:
chmod +x debian_display_setup_prep.sh
. - Run it as root:
sudo ./debian_display_setup_prep.sh
. - Reboot if you made changes to critical system configurations like GPU memory.
#Phase 2: Understanding and Using the Provided Scripts
Your original query included two scripts from a blog post. Here’s how they fit into the setup:
#A. monitor-info.sh
(Bash script to collect monitor data)
- Purpose: This script is a diagnostic and information-gathering tool. It is not meant to run automatically every time an X session starts. Its purpose is to help you understand your display hardware, especially when configuring new or problematic monitors. The data it gathers (EDID, timings) is crucial for crafting manual
xrandr
commands if needed. - “Enclosing” this script:
- The “One-Time System Preparation Script” (Phase 1) already installed all the command-line tools
monitor-info.sh
uses. - Below is the script content. Save it to a file in your home directory (e.g.,
~/monitor-info.sh
). - Make it executable:
chmod +x ~/monitor-info.sh
. - Run it manually with
sudo ~/monitor-info.sh
when you need to diagnose a display setup before creating anautorandr
profile for it.
- The “One-Time System Preparation Script” (Phase 1) already installed all the command-line tools
#!/usr/bin/env bash
#
# monitor-info.sh
# Collect comprehensive monitor info for xrandr configuration on Debian Bullseye+.
# Usage: sudo bash ~/monitor-info.sh
#
set -euo pipefail
# ------------------------------------------------------------
# Function: ensure_root
# Abort if not running as root.
# ------------------------------------------------------------
ensure_root() {
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Must be run as root." >&2
exit 1
fi
}
# ------------------------------------------------------------
# Function: install_tools_explicit_check
# Checks if tools are installed (they should be by the prep script).
# ------------------------------------------------------------
install_tools_explicit_check() {
local missing_pkgs=""
for pkg in read-edid ddcutil hwinfo inxi lshw x11-xserver-utils edid-decode; 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 the main preparation script or 'sudo apt install $missing_pkgs'" >&2
# Optionally, exit here if you want to be strict: exit 1
fi
}
# ------------------------------------------------------------
# Function: setup_outdir
# Creates a timestamped output directory.
# ------------------------------------------------------------
setup_outdir() {
# Try to create in user's home directory if sudo is used from a user context
REAL_USER=$(logname 2>/dev/null || echo "$SUDO_USER")
USER_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
if [[ -n "$USER_HOME" && -d "$USER_HOME" ]]; then
OUTDIR_BASE="$USER_HOME"
else
OUTDIR_BASE="/root" # Fallback to /root if user home not found
fi
OUTDIR="${OUTDIR_BASE}/monitor-info-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUTDIR"
# If run as root, ensure the user can access it if OUTDIR_BASE was user's home
if [[ -n "$SUDO_USER" && "$OUTDIR_BASE" == "$USER_HOME" ]]; then
chown -R "$SUDO_USER:$SUDO_USER" "$OUTDIR" || true
fi
echo "Output directory: $OUTDIR"
}
# ------------------------------------------------------------
# Function: log_cmd
# Runs a command, logs stdout/stderr to a file, does not exit on error.
# Usage: log_cmd logfile command [args...]
# ------------------------------------------------------------
log_cmd() {
local logfile="$1"; shift
{
echo "===== $(date '+%F %T') : $* ====="
"$@" 2>&1 || echo "(ERROR: '$*' failed with exit code $?)"
echo
} >>"$OUTDIR/$logfile"
}
# ------------------------------------------------------------
# Function: detect_connectors
# Populates arrays: DRM_ALL (all connectors), DRM_EDID (with edid),
# XRANDR_CONNECTED, XRANDR_ALL.
# ------------------------------------------------------------
detect_connectors() {
DRM_ALL=()
DRM_EDID=()
XRANDR_ALL=()
XRANDR_CONNECTED=()
# DRM: all dirs under /sys/class/drm matching card*-* (more specific)
for card_path in /sys/class/drm/card*; do
if [[ -d "$card_path" ]]; then
for path in "$card_path"/*; do
if [[ -d "$path" && $(basename "$path") =~ ^[A-Z]+-[0-9]+$|^[a-z]+-[0-9]+$ ]]; then # Matches like HDMI-A-1 or DP-1
local name=$(basename "$path")
DRM_ALL+=("$name")
[[ -r "$path/edid" && -s "$path/edid" ]] && DRM_EDID+=("$name") # Check if EDID file is readable and not empty
fi
done
fi
done
# xrandr: all outputs and connected ones
# Ensure X is running for xrandr, otherwise skip
if command -v xrandr &> /dev/null && xhost >/dev/null 2>&1; then
while IFS= read -r line; do
local 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 "$OUTDIR/summary.txt"
fi
# Summary print
{
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 "$OUTDIR/summary.txt"
}
# ------------------------------------------------------------
# Function: collect_sysfs_edid
# Parses EDID from sysfs for connectors in DRM_EDID.
# ------------------------------------------------------------
collect_sysfs_edid() {
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."
return
fi
local parser_cmd
parser_cmd=$(command -v edid-decode || command -v parse-edid)
for CON_BASENAME in "${DRM_EDID[@]}"; do
# Find full path for CON_BASENAME under /sys/class/drm/card*
local edid_path=""
for card_path in /sys/class/drm/card*; do
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
log_cmd "edid_sysfs_${CON_BASENAME}.log" "$parser_cmd" <"$edid_path"
else
echo "Could not read EDID for $CON_BASENAME from $edid_path" >> "$OUTDIR/summary.txt"
fi
done
}
# ------------------------------------------------------------
# Function: collect_getedid
# Parses EDID via get-edid (ISA bus fallback, part of read-edid package).
# ------------------------------------------------------------
collect_getedid() {
if ! command -v get-edid &> /dev/null; then
echo "Skipping get-edid: command not found."
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."
return
fi
local parser_cmd
parser_cmd=$(command -v edid-decode || command -v parse-edid)
# get-edid often needs I2C modules loaded, like i2c-dev
# Ensure modules are loaded, if possible (might require reboot or manual modprobe)
if ! lsmod | grep -q "i2c_dev"; then
echo "INFO: i2c_dev module not loaded. get-edid might fail. Consider 'sudo modprobe i2c_dev'."
fi
log_cmd "edid_getedid.log" bash -c "get-edid 2>/dev/null | $parser_cmd"
}
# ------------------------------------------------------------
# Function: collect_ddc
# Runs ddcutil detect and other commands.
# ------------------------------------------------------------
collect_ddc() {
if ! command -v ddcutil &> /dev/null; then
echo "Skipping ddcutil: command not found."
return
fi
# ddcutil often needs I2C modules loaded, like i2c-dev
if ! lsmod | grep -q "i2c_dev"; then
echo "INFO: i2c_dev module not loaded. ddcutil might fail. Consider 'sudo modprobe i2c_dev'."
fi
log_cmd "ddcutil_detect.log" ddcutil detect --verbose
# The following might be too much or redundant if sysfs EDID works well
# log_cmd "ddcutil_edid.log" ddcutil get-edid --verbose | edid-decode # or parse-edid
# log_cmd "ddcutil_caps.log" ddcutil capabilities --verbose
}
# ------------------------------------------------------------
# Function: collect_general_info
# Captures xrandr verbose, hwinfo, inxi, lshw.
# ------------------------------------------------------------
collect_general_info() {
if command -v xrandr &> /dev/null && xhost >/dev/null 2>&1; then
log_cmd "xrandr_verbose.log" xrandr --verbose
fi
command -v hwinfo &> /dev/null && log_cmd "hwinfo_monitor.log" hwinfo --monitor --verbose
command -v inxi &> /dev/null && log_cmd "inxi_Gxx.log" inxi -Gxx --display
command -v lshw &> /dev/null && log_cmd "lshw_display.log" lshw -C display -sanitize
}
# ------------------------------------------------------------
# Function: collect_udev
# Gathers udevadm info for all DRM_ALL connectors.
# ------------------------------------------------------------
collect_udev() {
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" ]]; then
log_cmd "udevadm_${CON_BASENAME}.log" udevadm info --query=all --path="$(readlink -f "$sys_path")"
fi
done
}
# ------------------------------------------------------------
# Function: generate_cvt_interactive
# Prompts user, generates and optionally applies a CVT modeline.
# (Note: applying modes should be done carefully and typically within an X session)
# ------------------------------------------------------------
generate_cvt_interactive() {
if ! command -v cvt &> /dev/null; then
echo "Skipping CVT modeline generation: cvt command not found (part of x11-xserver-utils)."
return
fi
if ! command -v xrandr &> /dev/null || ! xhost >/dev/null 2>&1; then
echo "Skipping CVT modeline application: xrandr not available or X session not active."
read -rp "Enter width height refresh (e.g. 1920 1080 60) to generate modeline, or ENTER to skip: " W H R
if [[ -n "$W" && -n "$H" && -n "$R" ]]; then
local MODELINE
MODELINE=$(cvt "$W" "$H" "$R" 2>/dev/null | grep Modeline | sed 's/Modeline //')
if [[ -n "$MODELINE" ]]; then
echo "Generated Modeline: $MODELINE" | tee -a "$OUTDIR/cvt_modeline.log"
else
echo "ERROR: Failed to generate modeline with cvt $W $H $R." | tee -a "$OUTDIR/cvt_modeline.log"
fi
fi
return
fi
read -rp "Enter width height refresh (e.g. 1920 1080 60), or ENTER to skip CVT modeline generation: " W H R
if [[ -n "$W" && -n "$H" && -n "$R" ]]; then
local MODELINE_FULL MODELINE_PARAMS NAME
MODELINE_FULL=$(cvt "$W" "$H" "$R" 2>/dev/null | grep Modeline)
if [[ -n "$MODELINE_FULL" ]]; then
MODELINE_PARAMS=$(echo "$MODELINE_FULL" | sed 's/Modeline //; s/^"[^"]*" //') # Remove "Modeline" and the name part
NAME=$(echo "$MODELINE_FULL" | awk '{print $2}' | tr -d '"') # Extract the mode name
echo "Generated Modeline: $MODELINE_FULL" | tee -a "$OUTDIR/cvt_modeline.log"
echo "Parameters for xrandr --newmode: $NAME $MODELINE_PARAMS" | tee -a "$OUTDIR/cvt_modeline.log"
local TARGET=${XRANDR_CONNECTED[0]:-} # Use first connected output as example
if [[ -n "$TARGET" ]]; then
read -rp "Attempt to apply this to $TARGET (requires X session)? (y/N): " APPLY_CVT
if [[ "$APPLY_CVT" =~ ^[Yy]$ ]]; then
echo "Applying: xrandr --newmode $NAME $MODELINE_PARAMS" | tee -a "$OUTDIR/cvt_modeline.log"
xrandr --newmode "$NAME" $MODELINE_PARAMS
if [[ $? -eq 0 ]]; then
echo "Applying: xrandr --addmode $TARGET $NAME" | tee -a "$OUTDIR/cvt_modeline.log"
xrandr --addmode "$TARGET" "$NAME"
if [[ $? -eq 0 ]]; then
echo "To activate, run: xrandr --output $TARGET --mode $NAME" | tee -a "$OUTDIR/cvt_modeline.log"
read -rp "Attempt to set mode $NAME on $TARGET now? (y/N): " SET_MODE
if [[ "$SET_MODE" =~ ^[Yy]$ ]]; then
xrandr --output "$TARGET" --mode "$NAME"
echo "Mode set attempt completed." | tee -a "$OUTDIR/cvt_modeline.log"
fi
else
echo "ERROR: xrandr --addmode failed." | tee -a "$OUTDIR/cvt_modeline.log"
fi
else
echo "ERROR: xrandr --newmode failed (mode might already exist or be invalid)." | tee -a "$OUTDIR/cvt_modeline.log"
fi
fi
else
echo "No connected xrandr output detected to suggest application." | tee -a "$OUTDIR/cvt_modeline.log"
fi
else
echo "ERROR: Failed to generate modeline with cvt $W $H $R." | tee -a "$OUTDIR/cvt_modeline.log"
fi
fi
}
# ------------------- Main Execution -------------------
ensure_root
install_tools_explicit_check # Verify tools, prep script should have installed them
# Arrays for connectors
declare -a DRM_ALL DRM_EDID XRANDR_ALL XRANDR_CONNECTED
setup_outdir
detect_connectors # Call this after setup_outdir so summary.txt goes into $OUTDIR
collect_sysfs_edid
collect_getedid
collect_ddc
collect_general_info
collect_udev
generate_cvt_interactive
echo
echo "Done. Review logs and summary.txt in $OUTDIR."
echo "To craft an xrandr modeline manually, inspect 'Detailed Timing Descriptors' or 'Established Timings' in the EDID logs (edid_sysfs_*.log or edid_getedid.log)."
#B. “Improved Temporary Xrandr configuration script”
- Purpose: This script provides a template of
xrandr
commands for a specific dual-monitor setup (DP-1 and HDMI-1) including custom modelines and DPI settings. - “Enclosing” this script:
- It is not meant to be run automatically on every X session start if you are using
autorandr
as the primary display manager. Doing so could conflict withautorandr
. - Instead, its
xrandr
commands and logic should be used as a reference or template when you are manually configuring your displays once before saving anautorandr
profile (Phase 3, Step 2). - The DPI setting part is more dynamic and, if desired for a specific
autorandr
profile, should be placed into anautorandr
hook script (see Phase 3, Step 7). - Below is the script content for your reference during manual configuration:
- It is not meant to be run automatically on every X session start if you are using
#!/bin/bash
# This is the "Improved Temporary Xrandr configuration script" from the blog post.
# Use its xrandr commands as a TEMPLATE when manually setting up your displays
# BEFORE saving an autorandr profile.
# Do NOT run this script directly on every X startup if using autorandr.
# The DPI calculation part can be adapted into an autorandr postswitch hook.
# 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
echo "Warning: bc command not found. DPI calculation for DP-1 will be skipped."
echo "Please install 'bc' if you want to enable automatic DPI setting."
else
BC_AVAILABLE=true
fi
echo "Applying temporary xrandr settings (REFERENCE SCRIPT)..."
echo "This script will check current display connection statuses."
echo "Verify your display identifiers (e.g., HDMI-1, DP-1) by running 'xrandr' in a terminal."
# Define mode names and modelines
MODE_NAME_1="1152x864_60.00" # For DP-1
MODELINE_1="81.75 1152 1216 1336 1520 864 867 871 897 -hsync +vsync"
MODE_1_WIDTH_PX=1152
MODE_1_HEIGHT_PX=864
MODE_NAME_2="2560x1080_60.00" # For HDMI-1
MODELINE_2="230.00 2560 2720 2992 3424 1080 1083 1093 1120 -hsync +vsync"
# Define output names
OUTPUT_DP="DP-1" # DisplayPort output
OUTPUT_HDMI="HDMI-1" # HDMI output
# Physical dimensions for DP-1 (in mm) - FOR DPI CALCULATION
DP_WIDTH_MM=419
DP_HEIGHT_MM=236
# --- Define New Modes (always attempt this if using these modes) ---
echo ""
echo "Defining new mode: $MODE_NAME_1"
xrandr --newmode "$MODE_NAME_1" $MODELINE_1
if [ $? -ne 0 ]; then
echo "Warning: Could not define mode $MODE_NAME_1."
echo "Possible issues: mode already exists (use 'xrandr' to check), incorrect modeline parameters, or xrandr limitations."
fi
echo "Defining new mode: $MODE_NAME_2"
xrandr --newmode "$MODE_NAME_2" $MODELINE_2
if [ $? -ne 0 ]; then
echo "Warning: Could not define mode $MODE_NAME_2."
fi
# --- Check current connection status of displays ---
echo ""
echo "Checking current display connection status..."
IS_DP_CONNECTED=false
if xrandr | grep -q "^${OUTPUT_DP} connected"; then
IS_DP_CONNECTED=true
echo "- $OUTPUT_DP is detected as connected."
else
echo "- $OUTPUT_DP is detected as disconnected."
fi
IS_HDMI_CONNECTED=false
if xrandr | grep -q "^${OUTPUT_HDMI} connected"; then
IS_HDMI_CONNECTED=true
echo "- $OUTPUT_HDMI is detected as connected."
else
echo "- $OUTPUT_HDMI is detected as disconnected."
fi
# --- Initial Positioning Command (if both displays are connected) ---
# Example: xrandr --output HDMI-1 --auto --above DP-1
# This needs to be adapted to your desired layout.
echo ""
if $IS_HDMI_CONNECTED && $IS_DP_CONNECTED; then
echo "Attempting to set $OUTPUT_HDMI --auto --above $OUTPUT_DP..."
# Replace with your desired layout command, e.g.:
# xrandr --output "$OUTPUT_HDMI" --mode "$MODE_NAME_2" --output "$OUTPUT_DP" --mode "$MODE_NAME_1" --primary --right-of "$OUTPUT_HDMI"
xrandr --output "$OUTPUT_HDMI" --auto --above "$OUTPUT_DP" # Example from script
if [ $? -ne 0 ]; then
echo "Warning: Could not set initial positioning."
else
echo "Successfully attempted to position $OUTPUT_HDMI above $OUTPUT_DP."
fi
elif ! $IS_HDMI_CONNECTED && $IS_DP_CONNECTED; then
echo "Skipping initial positioning: $OUTPUT_HDMI is disconnected. $OUTPUT_DP is primary."
xrandr --output "$OUTPUT_DP" --mode "$MODE_NAME_1" --primary --auto
elif $IS_HDMI_CONNECTED && ! $IS_DP_CONNECTED; then
echo "Skipping initial positioning: $OUTPUT_DP is disconnected. $OUTPUT_HDMI is primary."
xrandr --output "$OUTPUT_HDMI" --mode "$MODE_NAME_2" --primary --auto
else
echo "Skipping initial positioning: Both $OUTPUT_HDMI and $OUTPUT_DP appear to be disconnected."
fi
# --- Configure DP Output ($OUTPUT_DP) ---
echo ""
if $IS_DP_CONNECTED; then
echo "Configuring $OUTPUT_DP..."
echo "Adding mode $MODE_NAME_1 to $OUTPUT_DP"
xrandr --addmode "$OUTPUT_DP" "$MODE_NAME_1"
if [ $? -ne 0 ]; then
echo "Error adding mode $MODE_NAME_1 to $OUTPUT_DP."
else
echo "Setting $OUTPUT_DP to mode $MODE_NAME_1"
# This command would typically be part of the comprehensive layout command above
# xrandr --output "$OUTPUT_DP" --mode "$MODE_NAME_1"
# (Potentially redundant if already set in positioning)
if [ $? -ne 0 ]; then
echo "Error setting $OUTPUT_DP to mode $MODE_NAME_1."
else
# If DP-1 is the only connected monitor, try to set screen DPI
if ! $IS_HDMI_CONNECTED && $BC_AVAILABLE; then
echo ""
echo "DP-1 ($OUTPUT_DP) appears to be the only connected display and 'bc' is available."
echo "Calculating DPI for screen based on DP-1 physical dimensions ($DP_WIDTH_MM mm x $DP_HEIGHT_MM mm) and mode $MODE_NAME_1 ($MODE_1_WIDTH_PX x $MODE_1_HEIGHT_PX)."
if [ "$DP_WIDTH_MM" -gt 0 ] && [ "$DP_HEIGHT_MM" -gt 0 ]; then
DPI_H_CALC=$(bc -l <<< "scale=2; $MODE_1_WIDTH_PX / ($DP_WIDTH_MM / 25.4)")
DPI_V_CALC=$(bc -l <<< "scale=2; $MODE_1_HEIGHT_PX / ($DP_HEIGHT_MM / 25.4)")
if [[ -n "$DPI_H_CALC" && -n "$DPI_V_CALC" && "$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: $AVG_DPI (H: $DPI_H_CALC, V: $DPI_V_CALC)"
echo "Attempting to set screen DPI to $AVG_DPI..."
xrandr --dpi "$AVG_DPI"
if [ $? -ne 0 ]; then
echo "Warning: Could not set screen DPI to $AVG_DPI."
else
echo "Screen DPI successfully set to $AVG_DPI."
fi
else
echo "Warning: DPI calculation failed or produced non-numeric results."
fi
else
echo "Warning: DP-1 physical dimensions are zero. Cannot calculate DPI."
fi
elif ! $IS_HDMI_CONNECTED && ! $BC_AVAILABLE; then
echo "DP-1 ($OUTPUT_DP) is the only display, but 'bc' is not available. Skipping DPI calculation."
fi
fi
fi
else
echo "Skipping configuration for $OUTPUT_DP as it is disconnected."
fi
# --- Configure HDMI Output ($OUTPUT_HDMI) ---
echo ""
if $IS_HDMI_CONNECTED; then
echo "Configuring $OUTPUT_HDMI..."
echo "Adding mode $MODE_NAME_2 to $OUTPUT_HDMI"
xrandr --addmode "$OUTPUT_HDMI" "$MODE_NAME_2"
if [ $? -ne 0 ]; then
echo "Error adding mode $MODE_NAME_2 to $OUTPUT_HDMI."
else
echo "Setting $OUTPUT_HDMI to mode $MODE_NAME_2"
# This command would typically be part of the comprehensive layout command above
# xrandr --output "$OUTPUT_HDMI" --mode "$MODE_NAME_2"
# (Potentially redundant if already set in positioning)
if [ $? -ne 0 ]; then
echo "Error setting $OUTPUT_HDMI to mode $MODE_NAME_2."
fi
fi
else
echo "Skipping configuration for $OUTPUT_HDMI as it is disconnected."
fi
echo ""
echo "Temporary xrandr settings (REFERENCE SCRIPT) applied (or attempted for connected displays)."
echo "Current screen configuration (relevant connected displays):"
xrandr | grep " connected"
#Phase 3: Configuring autorandr
(Manual Steps for Each User and Setup)
autorandr
works by saving your current display configuration into a “profile.” This needs to be done by the user (root or normal user) within their own X session for each unique display setup.
Steps to create profiles:
- Connect Displays: Attach your specific combination of monitors, projectors, etc.
- Manually Configure Displays: Use your desktop environment’s display settings tool or manual
xrandr
commands in a terminal to arrange your displays exactly as desired.- Refer to the
xrandr
commands in the “Improved Temporary Xrandr configuration script” above as a template for defining modelines (xrandr --newmode ...
), adding modes (xrandr --addmode ...
), and setting outputs (xrandr --output <name> --mode <mode> --pos <XxY> --primary --rotate <normal|left|right|inverted> --output <other_name> ...
). - For example, to set up two monitors,
HDMI-1
(2560x1080) to the left ofDP-1
(1920x1080, primary):# (Assuming modelines for these resolutions are already supported or added via --newmode) xrandr --output HDMI-1 --mode 2560x1080 --pos 0x0 --rotate normal \ --output DP-1 --mode 1920x1080 --pos 2560x0 --rotate normal --primary
- Refer to the
- Save the
autorandr
Profile: Once displays are perfectly configured, save this state:autorandr --save <profile_name>
Use descriptive names (e.g.,
office_dual_monitors
,lecture_projector_1024x768
). - Profile Locations and User Context:
- Profiles are saved in
~/.config/autorandr/
(e.g.,/root/.config/autorandr/
for root,/home/youruser/.config/autorandr/
foryouruser
). - For common profiles accessible by both root and normal users (if no user-specific one matches): You can manually copy or create profile directories in
/etc/xdg/autorandr/
.autorandr
checks user-specific paths first. This is useful if root and a normal user often encounter the same display setups.
- Profiles are saved in
- Repeat for Other Setups: Repeat steps 1-3 for every different display configuration.
- List and Set Default Profile:
autorandr --list # Review saved profiles # Configure a very safe, common display setup (e.g., single monitor 1920x1080) # Then save it and set it as default: autorandr --save fallback_safe_1080p autorandr --default fallback_safe_1080p
This default profile is crucial if an unknown display setup is detected.
- Implementing DPI Settings via Hooks (Optional):
If you want the DPI calculation from the “Improved Temporary Xrandr configuration script” to apply when a specific
autorandr
profile is loaded, create apostswitch
hook script within that profile’s directory.-
For a profile named
my_specific_setup
(directory:~/.config/autorandr/my_specific_setup/
or/etc/xdg/autorandr/my_specific_setup/
), create an executable script:.../my_specific_setup/postswitch
#!/bin/sh # # postswitch hook for profile 'my_specific_setup' to set DPI. # IMPORTANT: Customize all variables below (OUTPUT_NAME, MODE_WIDTH_PX, etc.) # to match the specifics of THIS 'my_specific_setup' profile. # # For debugging hooks, you can use logger or write to a temp file: # echo "Running postswitch for my_specific_setup at $(date)" >> /tmp/autorandr_hook_debug.log if ! command -v bc > /dev/null 2>&1; then # echo "postswitch_dpi: bc command not found, skipping DPI." >> /tmp/autorandr_hook_debug.log exit 0 fi # --- START CUSTOMIZATION for 'my_specific_setup' profile --- OUTPUT_NAME="DP-1" # The actual output name (e.g., DP-1, HDMI-A-0) for this profile's main display MODE_WIDTH_PX=1152 # Horizontal resolution of OUTPUT_NAME in this profile MODE_HEIGHT_PX=864 # Vertical resolution of OUTPUT_NAME in this profile PHYS_WIDTH_MM=419 # Physical width in mm of the monitor connected to OUTPUT_NAME PHYS_HEIGHT_MM=236 # Physical height in mm of the monitor connected to OUTPUT_NAME # --- END CUSTOMIZATION --- # Check if the relevant display is configured as expected by autorandr for this profile # This grep is a basic check; more robust checks might be needed for complex setups. if xrandr | grep -q "^${OUTPUT_NAME} connected.*${MODE_WIDTH_PX}x${MODE_HEIGHT_PX}"; then if [ "$PHYS_WIDTH_MM" -gt 0 ] && [ "$PHYS_HEIGHT_MM" -gt 0 ]; then DPI_H_CALC=$(bc -l <<< "scale=2; $MODE_WIDTH_PX / ($PHYS_WIDTH_MM / 25.4)") DPI_V_CALC=$(bc -l <<< "scale=2; $MODE_HEIGHT_PX / ($PHYS_HEIGHT_MM / 25.4)") if [[ -n "$DPI_H_CALC" && -n "$DPI_V_CALC" && "$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 "postswitch_dpi: Setting screen DPI to $AVG_DPI for $OUTPUT_NAME." >> /tmp/autorandr_hook_debug.log xrandr --dpi "$AVG_DPI" # else # echo "postswitch_dpi: DPI calculation failed." >> /tmp/autorandr_hook_debug.log fi # else # echo "postswitch_dpi: Physical dimensions for $OUTPUT_NAME are zero." >> /tmp/autorandr_hook_debug.log fi # else # echo "postswitch_dpi: Expected display $OUTPUT_NAME not found or not in expected mode." >> /tmp/autorandr_hook_debug.log fi exit 0
-
Make the hook script executable:
chmod +x .../my_specific_setup/postswitch
. Repeat for other profiles if needed, customizing variables each time.
-
#Phase 4: Automating autorandr
This ensures autorandr
acts on display changes and at X session startup.
-
Systemd Service (for hotplugging and system events): The
autorandr.service
(enabled in Phase 1) handles display changes detected byudev
(e.g., plugging/unplugging monitors). This works system-wide, even outside or during an X session. It’s the primary mechanism for dynamic changes. -
X Session Startup Script (for
startx
): To explicitly triggerautorandr
when an X session is initiated viastartx
(by root or any normal user), create: File:/etc/X11/Xsession.d/50-autorandr-load-profile
#!/bin/sh # # /etc/X11/Xsession.d/50-autorandr-load-profile # Load autorandr profile on X session start (for startx users). # This script runs as the user starting the X session. # if command -v xrandr >/dev/null 2>&1 && command -v autorandr >/dev/null 2>&1; then # --change: detects displays, loads best profile. # --batch: implies --force, suppresses xrandr output unless error. Good for scripts. autorandr --change --batch # For debugging, you could log: # LOG_DIR="/tmp/autorandr_logs" # mkdir -p "$LOG_DIR" # echo "Xsession autorandr trigger for $(whoami) at $(date)" >> "$LOG_DIR/xsession_trigger.log" # autorandr --change --debug >> "$LOG_DIR/autorandr_xsession_$(whoami)_$(date +%Y%m%d-%H%M%S).log" 2>&1 fi exit 0
- Activate: Save and make executable:
sudo chmod +x /etc/X11/Xsession.d/50-autorandr-load-profile
. - Interplay: The
autorandr.service
provides continuous monitoring. This Xsession script ensures that whenstartx
is explicitly run,autorandr
evaluates the display situation at that specific moment, respecting the current user’s profiles.
- Activate: Save and make executable:
#Phase 5: Advanced autorandr
Features and Troubleshooting
- Advanced Features (from blog post):
- Wildcard EDID Matching: Edit
config
file in profile dirs (e.g.,~/.config/autorandr/<profile>/config
) to use*
in EDID strings for flexibility with similar monitors. - Other Hook Scripts:
preswitch
,predetect
,postdetect
for more automation (e.g., restarting panels, changing audio sinks). Rememberchmod +x
for all hook scripts.
- Wildcard EDID Matching: Edit
- Troubleshooting (from blog post):
autorandr --detected
: See current detection and scores.autorandr --change --debug
: Verbose output for manual diagnosis in an X session.journalctl -f | grep -i -E "drm|edid|autorandr"
: Live system logs.journalctl -b | grep -i -E "drm|edid|autorandr"
: Logs from current boot.- Ensure system is updated:
sudo apt update && sudo apt full-upgrade -y
. - Check physical cable connections.
By following these phases, you’ll have a robust system where autorandr
manages your display configurations automatically when displays are connected/disconnected or when you start an X session with startx
, leveraging the specific configurations and logic from the provided scripts. Remember to adapt profile names and hook script details to your exact hardware and preferences.