3
\$\begingroup\$

My main computer is a laptop which I run i3wm atop Arch Linux. As such, my monitor situation sometimes changes, so I wanted to code a script to configure my monitors based on which ones are connected to the system. The script runs on startup of i3wm.

As for the monitors, sometimes I may not have an external display connected, sometimes I may have an HDMI and a DP display connected, and sometimes I may be at another location with an HDMI and DP display connected but the DP appears as a different output.

The monitors are as follows:

  • eDP-1 - Internal Display.
  • HDMI-2 - Either of my secondary displays, which have the same resolution and placement
  • DP-1 or DP-1-8 - Either of my primary displays, which are the same monitor and placement, but appear as different outputs.

The code is as follows:

#!/usr/bin/env bash

# The xRandR names of my monitors, including the internal laptop monitor / display
readonly MON_INTERNAL='eDP-1'
readonly MON1='DP-1'
readonly MON1_FALLBACK='DP-1-8'
readonly MON2='HDMI-2'

# The resolutiond of the given xRandR monitors / displays. NOTE: $MON1 and $MON1_FALLBACK are the same display, so only one res is needed
readonly MON_INTERNAL_RES='1920x1080'
readonly MON1_RES='2560x1440'
readonly MON2_RES='1680x1050'

main_mon=''
sec_mon=''

# Store a count of how many monitors are connected
mon_count=$(xrandr -q | grep -w 'connected' | wc -l)

# Configure the monitors via xRandR
config_monitors() {
    if [[ "$#" -eq "2" ]]; then
        xrandr --output $1 --primary --mode $2 --rotate normal --pos 0x0
    elif [[ "$#" -eq "4" ]]; then
        xrandr --output $MON_INTERNAL --off --output $1 --mode $2 --pos 1680x0 --right-of $3 --output $3 --mode $4 --pos 0x0 --left-of $1
    fi
}

# Determine which main monitor is available
if [[ $mon_count -gt 1 ]]; then
    # The name of which main monitor is connected (either $MON1 or $MON1_FALLBACK)
    main_mon=$(xrandr -q | grep -w 'connected'| grep "^$MON1\|^$MON1_FALLBACK" | awk '{ print $1 }')
else # fallback to laptop display $MON_INTERNAL because the hardcoded displays aren't connected
    main_mon=$MON_INTERNAL
fi

# Determine whether the secondary HDMI monitor, $MON2 is connected
if [[ $mon_count -gt 1 ]] && [[ $(xrandr -q | grep $MON2 | awk '{ print $2 }') -eq connected ]]; then
    sec_mon=$MON2
fi

# Configure both external monitors if they're set or use the internal display
# TODO: Actual fallback logic for when HDMI display is connected but not the primary DP-x..
if [[ -n $main_mon ]] && [[ -n $sec_mon ]]; then
    config_monitors "$main_mon" "$MON1_RES" "$sec_mon" "$MON2_RES"
else
    config_monitors "$MON_INTERNAL" "$MON_INTERNAL_RES"
fi

Any suggestions on how I could improve the functionality and/or readability of this script are greatly appreciated. This is my first true foray into writing a Bash script that actually serves a usable real-world purpose to me.

\$\endgroup\$
3
\$\begingroup\$

for a problem like this, it helps to break down the possible states and map out what you want to do for each, and then construct a data model that lets you minimize duplication.

Here is my approach to your problem:

  1. hardcoded array of monitors by order of preference, allowing wildcards
  2. hardcoded assoc. array of screen resolutions for each preference
  3. hardcoded assoc. array of options templates for each possible configuration
  4. construct array of actual available monitor names from (1) and xrandr output
  5. construct array of resolutions from (2) and (4)
  6. get options template from (3) and (4)
  7. fill template using (4) and (5)

__

#!/usr/bin/env bash

# each PRIORITY entry must have matching entry in MODE; $displays will be sorted in priority order
declare -ar PRIORITY=( "DP-1*" HDMI-2 eDP-1 )
declare -Ar MODE=(
        [eDP-1]=1920x1080
        [DP-1*]=2560x1440
        [HDMI-2]=1680x1050
    )

# options corresponding to each possible config.  sorted in PRIORITY order.  
# left hand side is matched against space-separated list of actual monitor labels from xrandr
# template values like <D2> are zero-based
declare -Ar OPTS=(
            [DP-1* HDMI-2 eDP-1]='--output <D2> --off --output <D0> --mode <M0> --pos 1680x0 --right-of <D2> --output <D1> --mode <M1> --pos 0x0 --left-of <D0>'
            [HDMI-2 eDP-1]='--output <D1> --off --output <D0> --mode <M0> --pos 0x0'
            [eDP-1*]='--output <D0> --primary --mode <M0> --rotate normal --pos 0x0'
        )

declare -ar ALL_CONNECTED=( $( { xrandr -q || exit 1; } | awk '$2 == "connected" {print $1}' ) )


[[ ${#ALL_CONNECTED[@]} = 0 ]] && {
    echo no monitors connected
    exit 1
}

declare -a displays=()
declare -a modes=()

# populate displays and modes in preference order from ALL_CONNECTED 
for (( i=0; i<${#PRIORITY[@]}; i++ )); do
    for (( j=0; j<${#ALL_CONNECTED[@]}; j++ )); do
        if [[ ${ALL_CONNECTED[$j]} == ${PRIORITY[$i]} ]]; then
            displays+=( ${ALL_CONNECTED[$j]} )
            modes+=( ${MODE[${PRIORITY[$i]}]} )
            break
        fi
    done
done

echo "
ALL_CONNECTED: ${ALL_CONNECTED[@]}
displays: ${displays[@]}
modes: ${modes[@]}
"

for i in "${!OPTS[@]}"; do
    if [[ "${displays[@]}" == $i ]]; then
        opts=${OPTS[$i]}
        opts=${opts//<M/\$\{modes\[}            
    opts=${opts//<D/\$\{displays\[}
        opts=${opts//>/\]\}}
        set -x
        xrandr $( eval echo $opts )
        exit $?
    fi
done
echo "no OPT setting found for connected display combination of ${ALL_CONNECTED[@]} [ ${displays[@]} ]"
exit 1
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.