3 # Automatically select a display configuration based on connected devices
5 # Stefan Tomanek <stefan.tomanek@wertarbyte.de>
9 # Save your current display configuration and setup with:
10 # $ autorandr --save mobile
12 # Connect an additional display, configure your setup and save it:
13 # $ autorandr --save docked
15 # Now autorandr can detect which hardware setup is active:
20 # To automatically reload your setup, just append --change to the command line
22 # To manually load a profile, you can use the --load <profile> option.
24 # autorandr tries to avoid reloading an identical configuration. To force the
25 # (re)configuration, apply --force.
27 # To prevent a profile from being loaded, place a script call "block" in its
28 # directory. The script is evaluated before the screen setup is inspected, and
29 # in case of it returning a value of 0 the profile is skipped. This can be used
30 # to query the status of a docking station you are about to leave.
32 # If no suitable profile can be identified, the current configuration is kept.
33 # To change this behaviour and switch to a fallback configuration, specify
36 # Another script called "postswitch "can be placed in the directory
37 # ~/.autorandr as well as in all profile directories: The scripts are executed
38 # after a mode switch has taken place and can notify window managers or other
39 # applications about it.
42 # While the script uses xrandr by default, calling it by the name "autodisper"
43 # or "auto-disper" forces it to use the "disper" utility, which is useful for
44 # controlling nvidia chipsets. The formats for fingerprinting the current setup
45 # and saving/loading the current configuration are adjusted accordingly.
47 XRANDR=/usr/bin/xrandr
48 DISPER=/usr/bin/disper
49 XDPYINFO=/usr/bin/xdpyinfo
50 PROFILES=~/.autorandr/
51 CONFIG=~/.autorandr.conf
52 RESERVED_PROFILE_NAMES=`cat <<EOF
53 common Clone all connected outputs at the largest common resolution
54 horizontal Stack all connected outputs horizontally at their largest resolution
55 vertical Stack all connected outputs vertically at their largest resolution
63 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
64 CURRENT_CFG_METHOD="current_cfg_xrandr"
65 LOAD_METHOD="load_cfg_xrandr"
67 SCRIPTNAME="$(basename $0)"
68 # when called as autodisper/auto-disper, we assume different defaults
69 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
70 echo "Assuming disper defaults..." >&2
71 FP_METHODS="setup_fp_disper"
72 CURRENT_CFG_METHOD="current_cfg_disper"
73 LOAD_METHOD="load_cfg_disper"
76 if [ -f $CONFIG ]; then
77 echo "Loading configuration from '$CONFIG'" >&2
81 setup_fp_xrandr_edid() {
82 $XRANDR -q --verbose | awk '
83 /^[^ ]+ (dis)?connected / { DEV=$1; }
84 $1 ~ /^[a-f0-9]+$/ { ID[DEV] = ID[DEV] $1 }
85 END { for (X in ID) { print X " " ID[X]; } }'
88 setup_fp_sysfs_edid() {
89 # xrandr triggers the reloading of EDID data
90 $XRANDR -q > /dev/null
91 # hash the EDIDs of all _connected_ devices
92 for P in /sys/class/drm/card*-*/; do
94 [ ! -d "$P" ] && continue
95 if grep -q "^connected$" < "${P}status"; then
96 echo -n "$(basename "$P") "
97 md5sum ${P}edid | awk '{print $1}'
103 $DISPER -l | grep '^display '
108 for M in $FP_METHODS; do
110 if [ -n "$FP" ]; then
114 if [ -z "$FP" ]; then
115 echo "Unable to fingerprint display configuration" >&2
121 current_cfg_xrandr() {
122 local PRIMARY_SETUP="";
123 if [ -x "$XDPYINFO" ]; then
124 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
126 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
127 # display is connected and has a mode
128 /^[^ ]+ connected [^(]/ {
130 if ($3 == "primary") {
137 if (A[1] A[2] "," A[3] == primary_setup)
140 if (($4 == "left") || ($4 == "right")) {
145 print "pos "A[2]"x"A[3];
150 print "rotate normal";
154 # disconnected or disabled displays
155 /^[^ ]+ (dis)?connected / ||
156 /^[^ ]+ unknown connection / {
163 current_cfg_disper() {
167 common_cfg_xrandr() {
170 # output: current output
171 # outputlist: space sep list of all outputs
172 # outputarr: array of all connected outputs
173 # outputarrsize: number of connected outputs
174 # modelist[800x600]: space sep list of outputs supporting mode
175 # display is connected
176 /^[^ ]+ connected / {
178 outputlist=outputlist " " output
179 outputarr[outputarrsize++]=output
181 # disconnected or disabled displays
182 /^[^ ]+ disconnected / ||
183 /^[^ ]+ unknown connection / {
187 # modes available on a screen
189 modelist[$1]=modelist[$1] " " output
192 # find common mode with largest screen area
193 for (m in modelist) {
194 if (modelist[m] == outputlist) {
195 # calculate area of resolution
197 if (wh[1]*wh[2] >= maxdim) {
204 for (i in outputarr) {
205 print "output " outputarr[i];
206 print "mode " maxmode;
209 print "same-as " outputarr[0]
218 $XRANDR -q | awk -v stack="${STACK}" '
220 # stack: "horizontal" (anything except vertical) or "vertical"
221 # output: current output
222 # firstmode: pick first mode after output
223 # posX,posY: position of the next output
227 # display is connected
228 /^[^ ]+ connected / {
233 # disconnected or disabled displays
234 /^[^ ]+ disconnected / ||
235 /^[^ ]+ unknown connection / {
239 # modes available on a screen, but pick only the first
241 if (!firstmode) next;
243 # output mode at current virtual desktop pos
245 print "pos " posX "x" posY;
246 # calculate position of next output
248 if (stack == "vertical")
262 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
264 "$PROFILES/$PROFILE/block" "$PROFILE"
269 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
270 echo "Config already loaded"
278 # sed 1: Prefix arguments with "--"
279 # sed 2: Merge arguments into one line per output
280 # sed 3: Merge into two lines, all --off outputs in the first one
281 sed 's/^/--/' "$1" | sed -e '
289 # Merge if next line contains --off
290 s/\n\([^\n]* --off\)/ \1/
296 # Merge if previous line contains --mode
297 s/\(--mode [^\n]*\)\n/\1 /
299 $!d' | xargs -L 1 $XRANDR
308 local CONF="$PROFILES/$PROFILE/config"
309 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
310 [ -f "$CONF" -o -n $IS_VIRTUAL_PROFILE ] || return 1
311 if [ -x "$PROFILES/preswitch" ]; then
312 "$PROFILES/preswitch" "$PROFILE"
314 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
315 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
318 if [ -f "$CONF" ]; then
319 echo " -> loading profile $PROFILE"
320 if [ -n $IS_VIRTUAL_PROFILE ]; then
321 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
326 if [ $PROFILE = "common" ]; then
327 echo " -> setting largest common mode in cloned mode"
329 elif [ $PROFILE = "horizontal" ]; then
330 echo " -> stacking all outputs horizontally at their largest modes"
331 STACK="horizontal" stack_cfg_xrandr
332 elif [ $PROFILE = "vertical" ]; then
333 echo " -> stacking all outputs vertically at their largest modes"
334 STACK="vertical" stack_cfg_xrandr
338 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
339 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
341 if [ -x "$PROFILES/postswitch" ]; then
342 "$PROFILES/postswitch" "$PROFILE"
348 Usage: $SCRIPTNAME [options]
350 -h, --help get this small help
351 -c, --change reload current setup
352 -s, --save <profile> save your current setup to profile <profile>
353 -l, --load <profile> load profile <profile>
354 -d, --default <profile> make profile <profile> the default profile
355 --force force (re)loading of a profile
356 --fingerprint fingerprint your current hardware setup
357 --config dump your current xrandr setup
359 To prevent a profile from being loaded, place a script call "block" in its
360 directory. The script is evaluated before the screen setup is inspected, and
361 in case of it returning a value of 0 the profile is skipped. This can be used
362 to query the status of a docking station you are about to leave.
364 If no suitable profile can be identified, the current configuration is kept.
365 To change this behaviour and switch to a fallback configuration, specify
368 Another script called "postswitch "can be placed in the directory
369 ~/.autorandr as well as in any profile directories: The scripts are executed
370 after a mode switch has taken place and can notify window managers.
372 When called by the name "autodisper" or "auto-disper", the script uses "disper"
373 instead of "xrandr" to detect, configure and save the display configuration.
375 If xrandr is used, the following virtual configurations are available:
376 ${RESERVED_PROFILE_NAMES}
382 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
383 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
388 -c|--change) CHANGE_PROFILE=1; shift ;;
389 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
390 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
391 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
393 --force) FORCE_LOAD=1; shift ;;
394 --fingerprint) setup_fp; exit 0;;
395 --config) current_cfg; exit 0;;
397 *) echo "Error: $1"; exit 1;;
401 CURRENT_SETUP="$(setup_fp)"
403 if [ -n "$SAVE_PROFILE" ]; then
404 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
405 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
408 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
409 mkdir -p "$PROFILES/$SAVE_PROFILE"
410 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
411 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
415 if [ -n "$LOAD_PROFILE" ]; then
416 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
420 for SETUP_FILE in $PROFILES/*/setup; do
421 if ! [ -e $SETUP_FILE ]; then
424 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
427 if blocked "$PROFILE"; then
432 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
433 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
435 if [ "$CHANGE_PROFILE" -eq 1 ]; then
436 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
440 # found the profile, exit with success
447 # we did not find the profile, load default
448 if [ -n "$DEFAULT_PROFILE" ]; then
449 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
450 load "$DEFAULT_PROFILE"
458 # indent-tabs-mode: t