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
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 all --off outputs into the first line
281 # * Place the output with --pos 0x0 on the second line
282 # * Remaining outputs are appended as they appear
283 # * Keep everything in hold buffer until the last line
284 # sed 4: Remove empty lines caused by G and H on empty hold buffer
285 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
286 sed 's/^/--/' "$1" | sed -e '
293 # Merge with next line if it contains --off
294 s/\n\([^\n]* --off\)/ \1/
300 # Swap lines 1 and 2 if --off is found
301 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
309 /--mode/{ N; s/\n/ /; }
310 ' | xargs -L 1 $XRANDR
319 local CONF="$PROFILES/$PROFILE/config"
320 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
322 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
323 echo " -> Error: Profile '$PROFILE' does not exist." >&2
327 if [ -x "$PROFILES/preswitch" ]; then
328 "$PROFILES/preswitch" "$PROFILE"
330 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
331 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
334 if [ -f "$CONF" ]; then
335 echo " -> loading profile $PROFILE"
336 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
337 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
342 if [ $PROFILE = "common" ]; then
343 echo " -> setting largest common mode in cloned mode"
345 elif [ $PROFILE = "horizontal" ]; then
346 echo " -> stacking all outputs horizontally at their largest modes"
347 STACK="horizontal" stack_cfg_xrandr
348 elif [ $PROFILE = "vertical" ]; then
349 echo " -> stacking all outputs vertically at their largest modes"
350 STACK="vertical" stack_cfg_xrandr
354 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
355 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
357 if [ -x "$PROFILES/postswitch" ]; then
358 "$PROFILES/postswitch" "$PROFILE"
364 Usage: $SCRIPTNAME [options]
366 -h, --help get this small help
367 -c, --change reload current setup
368 -s, --save <profile> save your current setup to profile <profile>
369 -l, --load <profile> load profile <profile>
370 -d, --default <profile> make profile <profile> the default profile
371 --force force (re)loading of a profile
372 --fingerprint fingerprint your current hardware setup
373 --config dump your current xrandr setup
375 To prevent a profile from being loaded, place a script call "block" in its
376 directory. The script is evaluated before the screen setup is inspected, and
377 in case of it returning a value of 0 the profile is skipped. This can be used
378 to query the status of a docking station you are about to leave.
380 If no suitable profile can be identified, the current configuration is kept.
381 To change this behaviour and switch to a fallback configuration, specify
384 Another script called "postswitch "can be placed in the directory
385 ~/.autorandr as well as in any profile directories: The scripts are executed
386 after a mode switch has taken place and can notify window managers.
388 When called by the name "autodisper" or "auto-disper", the script uses "disper"
389 instead of "xrandr" to detect, configure and save the display configuration.
391 If xrandr is used, the following virtual configurations are available:
392 ${RESERVED_PROFILE_NAMES}
398 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
399 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
404 -c|--change) CHANGE_PROFILE=1; shift ;;
405 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
406 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
407 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
409 --force) FORCE_LOAD=1; shift ;;
410 --fingerprint) setup_fp; exit 0;;
411 --config) current_cfg; exit 0;;
413 *) echo "Error: $1"; exit 1;;
417 CURRENT_SETUP="$(setup_fp)"
419 if [ -n "$SAVE_PROFILE" ]; then
420 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
421 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
424 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
425 mkdir -p "$PROFILES/$SAVE_PROFILE"
426 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
427 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
431 if [ -n "$LOAD_PROFILE" ]; then
432 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
436 for SETUP_FILE in $PROFILES/*/setup; do
437 if ! [ -e $SETUP_FILE ]; then
440 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
443 if blocked "$PROFILE"; then
448 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
449 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
451 if [ "$CHANGE_PROFILE" -eq 1 ]; then
452 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
456 # found the profile, exit with success
463 # we did not find the profile, load default
464 if [ -n "$DEFAULT_PROFILE" ]; then
465 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
466 load "$DEFAULT_PROFILE"
474 # indent-tabs-mode: t