Skip to content

Instantly share code, notes, and snippets.

@kurahaupo
Last active August 26, 2025 00:53
Show Gist options
  • Save kurahaupo/216a37ee0c6b4efaf158 to your computer and use it in GitHub Desktop.
Save kurahaupo/216a37ee0c6b4efaf158 to your computer and use it in GitHub Desktop.
Script using "xrandr" to configure multi-head display
#!/bin/bash
# Devices are identified by a name (output by xrandr) and by an index (the
# ordinal sequence output by xrandr).
#
# CD is the list of screen-numbers for the screens that are connected
# CD[0]==0, assuming the built-in screen is enabled
#
# S is all the information we know about each screen
# keys in S[] -- concatenate the following hierarchical parts:
# 0 (1 2 etc) screen number
# .n dname (device name, usable as DX subscript)
# .e available/enabled
# .h height (after rotation)
# .m modes available on this screen
# (also, without anything concatenated, the number of modes for this screen)
# .0 (1 2 etc) mode number
# .h height of screen in this mode
# .w width of screen in this mode
# .r rotation (list RR, above; multiple of 90° clockwise)
# .uh height *before* rotation
# .uw width *before* rotation
# .w width (after rotation)
# .x position offset
# .y position offset
# DX is the mapping from screen-name to screen-number
# CD is the list of screen-numbers for the screens which are available/enabled
#
# As an example, consider the built-in screen on a laptop, which is typically
# eDP-1 or LVDS1
# Since it's the first one output by xrandr, it is screen number 0.
# It will almost always be enabled, and almost never rotated.
# So:
# DX[LVDS1] or DX[eDP\-1] is 0 -- it's screen #0
# CD[@] includes 0 -- screen #0 is enabled
#
# S[0.e] is true -- it's always available; this might be subsequently set to false to disable it.
# S[0.uw] and S[0.m.0.w] are the physical width of the screen in pixels } the modeline tagged with "+" is the preferred one, and
# S[0.uh] and S[0.m.0.h] are the physical height of the screen in pixels } afaik it's always the first modeline #0
# S[0.r] is 0 -- not rotated
# S[0.h] & S[0.w] are copies of S[0.uh] & S[0.uw] (either respectively or
# swapped, depending on the parity of the rotation)
# S[0.x] and S[0.y] are the offsets within the view-frame;
# for eDP-1 or LVDS1 the offsets start out as 0,0 but if you assign
# negative offsets to the other screens, all the offsets will all be
# adjusted so as to make the minimum x & y offsets across all screens
# both 0.
# If you want mirroring, simply set both screens to the same x & y offset.
shopt -s extglob
die() { echo "$*" >&2 ; exit 1 ; }
readonly true=1 false=0
declare -a CD # connected devices
declare -A S
declare -A DX
declare -a RR=( normal right inverted left ) # rotation descriptions
################################################################################
dry_run=false
make_primary=left
verbose=false
xds=false
xds_min=false
################################################################################
#
# Use «xrandr» to obtain information about available screens
# Need this before we can handle command-line args
#
maxw=8192 maxh=6144 # these are only defaults in case xrandr does not output suitable values
d=-1 m=-1
while
IFS= \
read -r line
do
IFS=$' \t' read -r -a words <<<"$line" # array of words split on whitespace, ignoring leading & trailing whitespace
case $line in
Screen*)
# Grab values from "maximum WWW x HHH" if present
for (( i=${#words[@]}-2 ; i>=0 ; --i )) do
[[ ${words[i]} = maximum && ${words[i+2]} = [x×] ]] && {
((
maxw = words[i+1],
maxh = words[i+3],
1
))
(( verbose )) && printf 'Maximum framebuffer size %d×%d\n' $maxw $maxh
break # found it, don't need to keep scanning
}
done
;;
' '[vh]:*) ;; # extra info about current modeline (ignored)
' '*)
(( d >= 0 )) || continue
w=${words[0]}
w=${w%i} # interleaved
[[ $w = ?*[x×]?* && $w != *[!0-9x×]* && $w != *[x×]*[x×]* ]] || die "invalid modeline '$line' for $d"
h=${w#*[x×]} # NNNxNNN
w=${w%[x×]*}
((
S[$d.m.$m.w] = w,
S[$d.m.$m.h] = h,
S[$d.m] = ++m,
1
))
# Pick the line recommended by xrandr (tagged with a "+")
# Failing that, pick the first one
[[ $line = *+* || -z ${S[$d.uw]} ]] &&
((
S[$d.uw] = w,
S[$d.uh] = h,
1
))
;;
*' connected '*)
dname=${words[0]}
(( DX[$dname] = ++d ))
CD+=( $d )
S[$d.n]=$dname
S[$d.e]=1
(( S[$d.x] = S[$d.y] = 0 ))
m=0
;;
*' '*'connect'*)
dname=${words[0]}
(( DX[$dname] = ++d ))
S[$d.n]=$dname
m=0
;;
esac
done < <(xrandr)
for d in ${CD[@]}
do
if (( S[$d.e] ))
then
printf 'Display #%u is %s using %u×%u (supporting:' "$d" "${S[$d.n]}" "${S[$d.uw]}" "${S[$d.uh]}"
for (( m = 0 ; m < S[$d.m] ; ++m )) do
printf ' %u×%u' "${S[$d.m.$m.w]}" "${S[$d.m.$m.h]}"
done
printf ')\n'
else
printf 'Display #%u is %s (disconnected)\n' "$d" "${S[$d.n]}"
fi
done
[[ ${S[${CD[0]}.n]} = @(LVDS|eDP-)* ]] ||
die "First adaptor #${CD[0]} is ${S[${CD[0]}.n]} rather than eDP* or LVDS*; please check source code in $0, line $LINENO"
# So you're reading this because the error message above was displayed?
#
# The pre-sets all assume that the first enabled device is the
# laptop's built-in screen, so we check here to make sure that's the
# case.
#
# Check the output of xrandr to see if this laptop has named its
# built-in screen something else.
################################################################################
# disable SCREEN...
#
# Each SCREEN can be a name in DX or a sequence index in CD.
disable() {
local p d
for p do
d=${DX[$p]-${CD[p]}}
[[ -n $d && -n ${S[$d.n]} ]] || continue # not an error to disable a nonexistent device
((
S[$d.e] = 0,
1
))
done
}
# rotate SCREEN ρ
#
# Rotate what is shown on SCREEN by ρπ/2 radians (90ρ°) clockwise where ρ is an
# integer (taken mod 4) or one of the symbolic values 'upright', 'clockwise',
# 'inverted' or 'anticlockwise' (only the first letter is used).
#
# SCREEN can be a name in DX or a sequence index in CD.
rotate() {
(( $# == 2 )) || die 'rotate: wrong number of args'
local p=$1 r=${2#-}
local d=${DX[$p]-${CD[p]}}
[[ -n $d && -n ${S[$d.n]} ]] || return #die "rotate: unknown screen number or name «$1»"
if [[ -n $r ]]
then
# Adopt new rotation, if given
# only look at the first letter of the word
r=${r##@(+([0-9])|[ACILRUacilru])}
r=${2%"$r"}
r=${r^^}
[[ -n $r ]] || die "Invalid rotation «$2»"
local U=0 R=1 I=2 L=3 C=1 A=3 # first letters of:
# upright, right, inverted, left,
# clockwise, anticlockwise.
if (( verbose ))
then
local rx=$(( r&3 ))
[[ $r = "$rx" ]] || rx="$r=$rx"
printf 'Rotating screen #%d (%s) to orientation %s (%s)\n' "$d" "${S[$d.n]}" "$rx" "${RR[r&3]}"
fi
(( S[$d.r] = r &= 3 ))
else
# or use existing rotation, if not given
(( r = S[$d.r] &= 3 ))
fi
# If the rotation is 0° or 180° (r is even) then the usable width & height
# are the corresponding device width & height; if the rotation is 90° or
# 270° (r is odd) then they are swapped: the usable width is the device
# height and the usable height is the device width.
((
r &= 1,
S[$d.w] = ( r ? S[$d.uh] : S[$d.uw] ),
S[$d.h] = ( r ? S[$d.uw] : S[$d.uh] ),
1
))
}
# anticlockwise DISPLAY...
# clockwise DISPLAY...
# inverted DISPLAY...
# upright DISPLAY...
#
# Apply rotation to each DISPLAY
anticlockwise() { local d ; for d do rotate "$d" anticlockwise ; done ; }
clockwise() { local d ; for d do rotate "$d" clockwise ; done ; }
inverted() { local d ; for d do rotate "$d" inverted ; done ; }
upright() { local d ; for d do rotate "$d" upright ; done ; }
# Set all available displays initially to upright
upright ${!CD[@]}
# offset SCREEN ±X ±Y
#
# SCREEN is shifted on the logical display surface down by Y and right by X
# SCREEN can be a name in DX or a sequence index in CD.
offset() {
(( $# == 3 )) || die 'offset: wrong number of args'
local p=$1 dx=$2 dy=$3
local d=${DX[$p]-${CD[p]:?"Unknown screen «$1»"}}
[[ -n $d && -n ${S[$d.n]} ]] || die "offset: unknown screen number or name «$1»"
[[ $dx = *% ]] && (( dx = S[$d.w] * ${dx%?} / 100 ))
[[ $dy = *% ]] && (( dy = S[$d.h] * ${dy%?} / 100 ))
((
S[$d.x] += dx,
S[$d.y] += dy,
1
))
((verbose)) &&
printf 'Offset screen #%u (%s) from %d,%d to %d,%d (moved by %+d,%+d)\n' \
"$d" "${S[$d.n]}" \
$(( S[$d.x] - dx )) $(( S[$d.y] - dy )) \
$(( S[$d.x] )) $(( S[$d.y] )) \
$(( dx )) $(( dy ))
}
# position SCREEN1 PREPOSITION SCREEN2
#
# Adjust position of SCREEN1 so that it is positioned adjacent to SCREEN2,
# where PREPOSITION is one of left_of, right_of, above, below
#
# SCREEN1 & SCREEN2 can each be a name in DX or a sequence index in CD.
position()
{
(( $# == 3 )) || die 'position: wrong number of args'
local p=$1 rel=$2 o=$3
local td=${DX[$p]-${CD[p]:?"Unknown screen «$1»"}}
local sd=${DX[$o]-${CD[o]:?"Unknown screen «$3»"}}
[[ -n $td && -n ${S[$td.n]} ]] || die "position: unknown screen number or name «$1»"
[[ -n $sd && -n ${S[$sd.n]} ]] || die "position: unknown screen number or name «$3»"
case $rel in
left_of) (( S[$td.x] = S[$sd.x] - S[$td.w], S[$td.y] = S[$sd.y], 1 )) ;;
right_of) (( S[$td.x] = S[$sd.x] + S[$sd.w], S[$td.y] = S[$sd.y], 1 )) ;;
above) (( S[$td.x] = S[$sd.x], S[$td.y] = S[$sd.y] - S[$td.h], 1 )) ;;
below) (( S[$td.x] = S[$sd.x], S[$td.y] = S[$sd.y] + S[$sd.h], 1 )) ;;
mirror) (( S[$td.x] = S[$sd.x], S[$td.y] = S[$sd.y], 1 )) ;;
*) die "position: invalid preposition «$2»"
esac
((verbose)) &&
printf 'Position screen #%u (%s) at %d,%d %s screen #%u (%s)\n' \
"$td" "${S[$td.n]}" \
$(( S[$td.x] )) $(( S[$td.y] )) \
"${rel//_/ }" \
"$sd" "${S[$sd.n]}"
}
################################################################################
# Disable all screens except built-in screen
# ┌───────┐
# │ 0 │
# └───────┘
setup_solo() {
disable 1 2 3 4 5 6
echo "Using single screen on ${S[${CD[0]}.n]}, layout 'A'"
use_defaults=0
}
# Use only a single external screen, in portrait mode
# ┌─────┐
# │↻ │
# │ │
# │ │
# │ 1 │
# └─────┘
setup_1extp() {
disable 0 2 3 4 5 6
anticlockwise 1
echo "Using only external screen ${S[${CD[1]}.n]}; layout 'Y'"
use_defaults=0
}
# Use only two external screens side by side
# ┌─────────┬─────────┐
# │ │ │
# │ 1 │ 2 │
# └─────────┴─────────┘
setup_2ext() {
disable 0 3 4 5 6
position 1 left_of 2
echo "Using only external screens ${S[${CD[1]}.n]} and ${S[${CD[2]}.n]}; layout 'X'"
use_defaults=0
}
# Use only two external screens side by side, both in portrait mode
# ┌─────┬─────┐
# │↻ │↻ │
# │ │ │
# │ │ │
# │ 1 │ 2 │
# └─────┴─────┘
setup_2extp() {
disable 0 3 4 5 6
anticlockwise 1 2
position 1 left_of 0
echo "Using only external screens ${S[${CD[1]}.n]} and ${S[${CD[2]}.n]}; layout 'X'"
use_defaults=0
}
# Set up for home workstation, with the laptop elevated to right of the
# external screen in upright mode.
# ┌─────────┐▁▁▁▁▁▁▁
# │ │ │
# │ 1 │ 0 │
# └─────────┘▔▔▔▔▔▔▔
setup_home2() {
disable 2 3 4 5 6
position 1 left_of 0
offset 0 0 418
echo "Using dual-screen: external screen with elevated laptop on right; layout 'H2' (home2)"
use_defaults=0
}
# Set up for home workstation, with the laptop elevated to right of the first
# external screen, and the second external screen to its left. Both external
# screens in upright mode.
# ┌─────────┬─────────┐▁▁▁▁▁▁▁
# │ │ │ │
# │ 2 │ 1 │ 0 │
# └─────────┴─────────┘▔▔▔▔▔▔▔
setup_home3() {
disable 3 4 5 6
position 1 left_of 0
position 2 left_of 1
offset 0 0 418
echo "Using triple-screen (home): twin portrait screens with laptop on right; layout 'H3'"
use_defaults=0
}
# Set up for home workstation, with the laptop elevated to right of the
# external screen in portrait mode.
# ┌─────┐
# │↻ ├───────┐
# │ │ 0 │
# │ ├───────┘
# │ 1 │
# └─────┘
setup_home2p() {
disable 2 3 4 5 6
anticlockwise 1
position 0 right_of 1
offset 0 0 418
echo "Using dual-screen: external screen with elevated laptop on right, layout 'H2p' (home2p)"
use_defaults=0
}
# Set up for home workstation, with the laptop elevated to right of the first
# external screen, and the second external screen to its left. Both external
# screens in portrait mode.
# ┌─────┬─────┐
# │↻ │↻ ├───────┐
# │ │ │ 0 │
# │ │ ├───────┘
# │ 2 │ 1 │
# └─────┴─────┘
setup_home3p() {
disable 3 4 5 6
anticlockwise 1 2
position 1 left_of 0
position 2 left_of 1
offset 0 0 418
echo "Using triple-screen: twin portrait screens with laptop on right, layout 'H3p' (home3p)"
use_defaults=0
}
# Set all screens to overlap, bottom-aligned, horizontally centred
# ┌─────────┐
# │ 1 │
# │┌┈┈┈┈┈┈┈┐│
# │┊ 0 ┊│
# └┴───────┴┘
setup_mirror_bc() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -50% -100%
done
offset 0 -50% -100%
echo "Mirroring ${#CD[@]} screens, aligned at bottom, centred horizontally; layout 'Mbc'"
use_defaults=0
}
# Set all screens to overlap, bottom-left-aligned
# ┌─────────┐
# │ 1 │
# ├┈┈┈┈┈┈┈┐ │
# │ 0 ┊ │
# └───────┴─┘
setup_mirror_bl() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" 0 -100%
done
offset 0 0 -100%
echo "Mirroring ${#CD[@]} screens, aligned at bottom-left; layout 'Mbl'"
use_defaults=0
}
# Set all screens to overlap, bottom-right-aligned
# ┌─────────┐
# │ 1 │
# │ ┌┈┈┈┈┈┈┈┤
# │ ┊ 0 │
# └─┴───────┘
setup_mirror_br() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -100% -100%
done
offset 0 -100% 0
echo "Mirroring ${#CD[@]} screens, aligned at bottom-right; layout 'Mbr'"
use_defaults=0
}
# Set all screens to overlap, horizontally & vertically centred
# ┌─────────┐
# │┌┈┈┈┈┈┈┈┐│
# │┊ 0,1 ┊│
# │└┈┈┈┈┈┈┈┘│
# └─────────┘
setup_mirror_mc() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -50% -50%
done
offset 0 -50% -50%
echo "Mirroring ${#CD[@]} screens, centred horizontally & vertically; layout 'Mmc'"
use_defaults=0
}
# Set all screens to overlap, left-aligned, vertically centred
# ┌─────────┐
# ├┈┈┈┈┈┈┈┐ │
# │ 0 ┊1│
# ├┈┈┈┈┈┈┈┘ │
# └─────────┘
setup_mirror_ml() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" 0 -50%
done
offset 0 0 -50%
echo "Mirroring ${#CD[@]} screens, aligned at left, centred vertically; layout 'Mml'"
use_defaults=0
}
# Set all screens to overlap, right-aligned, vertically centred
# ┌─────────┐
# │ ┌┈┈┈┈┈┈┈┤
# │1┊ 0 │
# │ └┈┈┈┈┈┈┈┤
# └─────────┘
setup_mirror_mr() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -100% -50%
done
offset 0 -100% -50%
echo "Mirroring ${#CD[@]} screens, aligned at right, centred vertically; layout 'Mmr'"
use_defaults=0
}
# Set all screens to overlap, top-aligned, horizontally centred
# ┌┬───────┬┐
# │┊ 0 ┊│
# │└┈┈┈┈┈┈┈┘│
# │ 1 │
# └─────────┘
setup_mirror_tc() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -50% 0
done
offset 0 -50% 0
echo "Mirroring ${#CD[@]} screens, aligned at top, centred horizontally; layout 'Mtc'"
use_defaults=0
}
# Set all screens to overlap, top-left-aligned
# ┌───────┬─┐
# │ 0 ┊ │
# ├┈┈┈┈┈┈┈┘ │
# │ 1 │
# └─────────┘
setup_mirror_tl() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" 0 0
done
offset 0 0 0
echo "Mirroring ${#CD[@]} screens, aligned at top-left; layout 'Mtl'"
use_defaults=0
}
# Set all screens to overlap, top-right-aligned
# ┌─┬───────┐
# │ ┊ 0 │
# │ └┈┈┈┈┈┈┈┤
# │ 1 │
# └─────────┘
setup_mirror_tr() {
for ((d=1;d<=${#CD[@]};++d)) do
position "$d" mirror 0
offset "$d" -100% 0
done
offset 0 -100% 0
echo "Mirroring ${#CD[@]} screens, aligned at top-right; layout 'Mtr'"
use_defaults=0
}
# Set up for a general-purpose twin screen, with the external screen (any) to
# right of the built-in screen, and with their bottoms level.
# ┌─────────┐
# ┌───────┤ │
# │ 0 │ 1 │
# └───────┴─────────┘
setup_twin() {
disable 2 3 4 5 6
position 1 right_of 0
offset 0 0 -100%
offset 1 0 -100%
echo "Using dual-screen with laptop left of external screen, bottoms level; layout 'T' (twin)"
use_defaults=0
}
# Set up for office docking station: two external screens side-by-side and
# bottom-aligned, and the internal screen below and evenly spaced between them.
# ┌─────────┐
# │ ├───────┐
# │ 1 │ 2 │
# └─────┬───┴───┬───┘
# │ 0 │
# └───────┘
setup_office() {
(( ${#CD[@]} >= 3 )) || die 'setup_office: not enough screens'
upright 0 1 2
position 1 left_of 2
offset 1 0 -100%
offset 2 0 -100%
position 0 below 2
offset 0 -50% 0
echo "Using dual-screen, layout 'Z' (office) with elevated external screen on right"
use_defaults=0
}
# Projector (first external screen) above built-in screen
# ┌─────────┐
# │ │
# │ 1 │
# └┬───────┬┘
# │ 0 │
# └───────┘
setup_projector() {
dnum1=${CD[1]:?'second screen not connected'}
((
S[${CD[1]}.e] = 1,
S[${CD[1]}.y] = S[${CD[0]}.y]-S[${CD[1]}.h],
S[${CD[1]}.x] = 0,
1
))
echo "Using dual-screen with projector above laptop, layout 'P'"
use_defaults=0
}
# Built-in to right of external but overlapping by 400px, and down 560px
# ┌─────────┐
# │ ┌┈┼─────┐
# │ 1 ┊ ┊ 0 │
# └───────┴─┴─────┘
setup_demo5() {
dnum1=${CD[1]:?'second screen not connected'}
disable 2 3 4 5 6
position 1 right_of 0
offset 1 -400 +560
echo "Using example overlapping dual-screen, layout 'Q'"
use_defaults=0
}
# turn off built-in screen and only use first external screen
# ┌─────────┐
# │ │
# │ 1 │
# └─────────┘
setup_extonly() {
dnum1=${CD[1]:?'second screen not connected'}
disable 0 2 3 4 5 6
echo "Using only external ${CD[1]} screen, layout 'E'"
use_defaults=0
}
show_list () {
cat <<-EndOfList
LIST
EndOfList
declare -n var
for var in RR S DX CD
do
printf '%s:\n' "${!var}"
for i in "${!var[@]}"
do
printf '\t%-23s %s\n' "$i" "${var[$i]}"
done
done
exit
}
show_help () {
cat <<-EndOfHelp
Usage: ${0##*/} [INIT] [PRESET] [ROTATION] [PRIMARY]
INIT is one of:
init re-initialise driver
xds interactive set-up (xfce4-display-settings)
PRESET is one of:
A, solo arrange layout as "solo"
E, ext arrange layout as "extonly"
H, home arrange layout as "home3p"
H2, home2 arrange layout as "home2"
H2p, home2p arrange layout as "home2p"
H3, home3 arrange layout as "home3"
H3p, home3p arrange layout as "home3p"
M, mirror arrange layout as "mirror"
P, proj arrange layout as "projector"
Q, plus arrange layout as "demo5"
T, twin arrange layout as "twin"
X, 2ext arrange layout as "2ext" - twin external screens without built-in screen
X, 2ext arrange layout as "2ext"
Y, 1ext arrange layout as "1ext" - single external screen without built-in screen
Z, office arrange layout as "office"
ROTATION is one of:
a[NUM], rotate-clockwise=DISPLAY use physical left edge of selected screen as its logical top
l[NUM], rotate-left=DISPLAY
c[NUM], rotate-anticlockwise=DISPLAY use physical right edge of selected screen as its logical top
r[NUM], rotate-upright=DISPLAY
i[NUM], rotate-right=DISPLAY use physical bottom edge of selected screen as its logical top
u[NUM], rotate-inverted=DISPLAY use physical top edge of selected screen as its logical top
NB: clockwise/anticlockwise denotes how the logical screen moves on the
physical screen; if the physical screen is rotated, nominate the reverse
direction to have the display upright.
PRIMARY is one of:
p[NUM] assign selected screen as primary
primary=NUM
primary=NAME
p{l,r,t,b} select primary screen based on layout position
primary={left,right,top,bottom}
If [NUM] is omitted, select screen 0
EndOfHelp
exit
}
comma_split() {
local arg=$1
local opts=$-
set -f # set -o noglob
shift
local IFS=, # split on comma
set -- "$@" $arg
set +f -$opts
"$@"
}
# swap screens
# re-arrange the order of CD[@]
swap_screens () {
local i j=$1 k
k=${DX[$j]}
[[ $k ]] && for ((j=0 ; j<${#CD[@]} && CD[j] != k ; ++j)) do :; done
local t=${CD[j]}
shift
(( i=j ))
for j do
k=${DX[$j]}
[[ $k ]] && for ((j=0 ; j<${#CD[@]} && CD[j] != k ; ++j)) do :; done
CD[i]=${CD[j]}
(( i=j ))
done
CD[i]=$t
(( verbose )) &&
printf 're-ordered CD\n' &&
declare -p CD
}
use_defaults=1
while (($#)) ; do
case ${1#"${1%%[^-]*}"} in
# Info
(l|list) show_list ;;
(help|usage) show_help ;;
# Driver initialisation
(init) xds_min=true ;;
(xds) xds=true ;;
# Preset groups
(A | solo) setup_solo ;;
(E | ext) setup_extonly ;;
(H | home) setup_home3p ;;
(H2 | home2) setup_home2 ;;
(H2p| home2p) setup_home2p ;;
(H3 | home3) setup_home3 ;;
(H3p| home3p) setup_home3p ;;
(M | mirror) setup_mirror_tl ;;
(Mbc) setup_mirror_bc ;;
(Mbl) setup_mirror_bl ;;
(Mbr) setup_mirror_br ;;
(Mmc) setup_mirror_mc ;;
(Mml) setup_mirror_ml ;;
(Mmr) setup_mirror_mr ;;
(Mtc) setup_mirror_tc ;;
(Mtl) setup_mirror_tl ;;
(Mtr) setup_mirror_tr ;;
(P | proj) setup_projector ;;
(Q | plus) setup_demo5 ;;
(T | twin) setup_twin ;;
(X | 2ext) setup_2extp ;; # twin external screens without built-in screen
(Y | 1ext) setup_1extp ;; # single external screen without built-in screen
(Z | office) setup_office ;;
# Swap order of screens
(swap=*,*) comma_split "${1#*swap=}" swap_screens ;;
(s+([0-9,])) comma_split "${1#s}" swap_screens ;;
# Rotate individual screens
(u*([0-9,])) comma_split "${1#?}" upright ;;
([cr]*([0-9,])) comma_split "${1#?}" clockwise ;;
(i*([0-9,])) comma_split "${1#?}" inverted ;;
([al]*([0-9,])) comma_split "${1#?}" anticlockwise ;;
# (?(r*-)*=@(upright|clockwise|inverted|anticlockwise)) p=${1#r*-} ; "${p#*=}" "${p%%=*}" ;;
(?(r*([otae])-)@(upright|clockwise|inverted|anticlockwise)=*)
p=${1#r*-}
comma_split "${p#*=}" "${p%%=*}"
;;
# choose which screen is "primary" and thus holds the window manager's menu bar etc
(p[0-9]) make_primary=${CD[${1#p}]} ;;
(pb|primary=bottom) make_primary=bottom ;;
(pl|primary=left) make_primary=left ;;
(primary=[0-9]) make_primary=${1##*=} ;;
(primary=[0-9]) make_primary=${1##*=} ;;
(pr|primary=right) make_primary=right ;;
(pt|primary=top) make_primary=top ;;
(p|primary=first) make_primary=${CD[0]} ;;
# debugging
(n|dryrun|dry-run|no-act) dry_run=true ;;
(notdryrun|not-dry-run|act) dry_run=false ;;
(v|verbose) verbose=true ;;
(q|quiet) verbose=false ;;
(*)
echo >&2 "Invalid option '$1'; usage: $0 [home|SHORTCUT] [init|xds] [dryrun] [v|q] [--primary={NUM|top|bottom|left|right] [--rotate-{SCREENNAME}={upright|clockwise|inverted|anticlockwise}]"
exit 64 ;;
esac
shift
done
# If there was no instruction on the command line, examine whatever devices are
# attached and make a best-effort guess at what the user wants.
if ((use_defaults))
then
(( verbose )) &&
declare -p CD
case ${#CD[@]} in
(0) exit ;;
(1)
echo USING 1 monitor: \
${S[${CD[0]}.n]}:${S[${CD[0]}.w]}×${S[${CD[0]}.h]}
setup_solo ;;
(2)
echo USING 2 monitors: \
${S[${CD[0]}.n]}:${S[${CD[0]}.w]}×${S[${CD[0]}.h]} \
${S[${CD[1]}.n]}:${S[${CD[1]}.w]}×${S[${CD[1]}.h]}
case ${S[${CD[1]}.n]}:${S[${CD[1]}.w]}×${S[${CD[1]}.h]} in
(HDMI-1:1920×1080) setup_home2p ;;
(DP-1:1920×1080) setup_home2p ;;
(*) setup_twin ;;
esac ;;
(3)
echo USING 3 monitors: \
${S[${CD[0]}.n]}:${S[${CD[0]}.w]}×${S[${CD[0]}.h]} \
${S[${CD[1]}.n]}:${S[${CD[1]}.w]}×${S[${CD[1]}.h]} \
${S[${CD[2]}.n]}:${S[${CD[2]}.w]}×${S[${CD[2]}.h]}
case ${S[${CD[1]}.n]}:${S[${CD[1]}.w]}×${S[${CD[1]}.h]},${S[${CD[2]}.n]}:${S[${CD[2]}.w]}×${S[${CD[2]}.h]} in
(DP-1-1:1920×1080,DP-1-2:1600×900)
setup_office ;;
(DP-1-1:1600×900,DP-1-2:1920×1080)
swap_screens 1 2
setup_office ;;
(*) setup_home3p ;;
esac ;;
(*)
die "Cannot guess for more than 3 screens"
esac
fi
# Find the x & y offsets of the extreme edges of all screens.
bw=0 # right-most
bh=0 # bottom-most
ox=$maxw # left-most
oy=$maxh # top-most
for d in "${DX[@]}"
do
# skip disabled screens
# find leftmost & topmost
# adjust overall width & height to encompass all screens
(( S[$d.e] &&
(
ox > (q = S[$d.x]) && (ox = q),
oy > (q = S[$d.y]) && (oy = q),
bw < (q = S[$d.x]+S[$d.w]) && (bw = q),
bh < (q = S[$d.y]+S[$d.h]) && (bh = q)
),
1
))
done
# Keeping their relative positions, move all the screens so that the x-offset
# of the leftmost screen and the y-offset of the topmost screen are both zero.
for d in "${DX[@]}"
do
(( S[$d.e] )) || continue # skip disabled screens
(( S[$d.x] -= ox,
S[$d.y] -= oy ))
done
((
bw -= ox, bw > maxw && (bw = maxw),
bh -= oy, bh > maxh && (bh = maxh),
1
))
((bw && bh)) || die "zero-sized screen"
xrandr_args=( --fb "$bw"x"$bh" ) # xrandr args, to be determined
primary_done=0
for dname in ${!DX[@]}
do
d="${DX[$dname]}"
[[ $dname = ${S[$d.n]} ]] || die "Mismatch of name for screen #$d between '$dname' and '${S[$d.n]}'"
# Prepare xrandr args for Device, Mode, Rotation, and Position (or OFF)
xrandr_args+=( --output $dname )
if (( ! S[$d.e] )) || [[ -z ${S[$d.n]} ]]
then
#
# The keys in the DX hash are a superset of the S[$d.n] values, since the
# latter are only defined when the corresponding screen is physically
# connected and enabled.
#
# Turn off any unused screens
xrandr_args+=( --off )
continue # skip everything else for a disabled screen
fi
(( (uw = S[$d.uw]) &&
(uh = S[$d.uh]) )) || die "Screen #$d (${S[$d.n]}) missing recommended geometry"
xrandr_args+=(
--mode "$uw"x"$uh"
--rotate "${RR[${S[$d.r]:-0}&3]}"
--pos ${S[$d.x]:-0}x${S[$d.y]:-0}
)
# Choose a "primary" screen (where the menu appears)
if case $make_primary in
(left) ((S[$d.x] == 0)) ;;
(top) ((S[$d.y] == 0)) ;;
(right) ((S[$d.x]+S[$d.w] == bw)) ;; # (right) ((S[$d.x] != 0)) ;;
(bottom) ((S[$d.y]+S[$d.h] == bh)) ;; # (bottom) ((S[$d.y] != 0)) ;;
([0-9]) ((d == make_primary)) ;;
(*) false ;;
esac &&
((! primary_done++))
then
xrandr_args+=( --preferred )
fi
done
if $dry_run ; then vv() { echo "$@"; }
declare -p DX CD S
elif $verbose ; then vv() { echo "$@"; "$@"; }
xrandr_args+=( --verbose )
else vv() { "$@"; }
fi
(( xds_min )) && vv xfce4-display-settings --minimal
vv xrandr "${xrandr_args[@]}"
(( xds )) && vv xfce4-display-settings
exit
@jglotzer
Copy link

jglotzer commented Apr 2, 2016

When I ran this I found that unless I changed (on line 59)

(( maxw = words[i+1], maxh = words[i+3] ))

to

(( maxw = ${words[i+1]}, maxh = ${words[i+3]} ))

bash itself segfaulted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment