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.

Toby Speight
87.9k14 gold badges104 silver badges325 bronze badges
asked Nov 26, 2018 at 18:40
\$\endgroup\$

1 Answer 1

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
answered Dec 25, 2018 at 12:06
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.