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.
1 Answer 1
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:
- hardcoded array of monitors by order of preference, allowing wildcards
- hardcoded assoc. array of screen resolutions for each preference
- hardcoded assoc. array of options templates for each possible configuration
- construct array of actual available monitor names from (1) and xrandr output
- construct array of resolutions from (2) and (4)
- get options template from (3) and (4)
- 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