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 [^(]/ {
131 if ($3 == "primary") {
138 if (A[1] A[2] "," A[3] == primary_setup)
141 if (($4 == "left") || ($4 == "right")) {
146 print "pos "A[2]"x"A[3];
151 print "rotate normal";
155 / [0-9]+x[0-9]+ .+/ {
157 for (n=1; n<10; n++) {
158 if($n ~ /[0-9]+\.[0-9]+\*/) {
159 print "rate " gensub(/(+|\*)/, "", "g", $n);
164 # disconnected or disabled displays
165 /^[^ ]+ (dis)?connected / ||
166 /^[^ ]+ unknown connection / {
174 current_cfg_disper() {
178 common_cfg_xrandr() {
181 # output: current output
182 # outputlist: space sep list of all outputs
183 # outputarr: array of all connected outputs
184 # outputarrsize: number of connected outputs
185 # modelist[800x600]: space sep list of outputs supporting mode
186 # display is connected
187 /^[^ ]+ connected / {
189 outputlist=outputlist " " output
190 outputarr[outputarrsize++]=output
192 # disconnected or disabled displays
193 /^[^ ]+ disconnected / ||
194 /^[^ ]+ unknown connection / {
198 # modes available on a screen
200 modelist[$1]=modelist[$1] " " output
203 # find common mode with largest screen area
204 for (m in modelist) {
205 if (modelist[m] == outputlist) {
206 # calculate area of resolution
208 if (wh[1]*wh[2] >= maxdim) {
215 for (i in outputarr) {
216 print "output " outputarr[i];
217 print "mode " maxmode;
220 print "same-as " outputarr[0]
229 $XRANDR -q | awk -v stack="${STACK}" '
231 # stack: "horizontal" (anything except vertical) or "vertical"
232 # output: current output
233 # firstmode: pick first mode after output
234 # posX,posY: position of the next output
238 # display is connected
239 /^[^ ]+ connected / {
244 # disconnected or disabled displays
245 /^[^ ]+ disconnected / ||
246 /^[^ ]+ unknown connection / {
250 # modes available on a screen, but pick only the first
252 if (!firstmode) next;
254 # output mode at current virtual desktop pos
256 print "pos " posX "x" posY;
257 # calculate position of next output
259 if (stack == "vertical")
273 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
275 "$PROFILES/$PROFILE/block" "$PROFILE"
280 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
281 echo "Config already loaded"
289 # sed 1: Prefix arguments with "--"
290 # sed 2: Merge arguments into one line per output
291 # sed 3: * Merge all --off outputs into the first line
292 # * Place the output with --pos 0x0 on the second line
293 # * Remaining outputs are appended as they appear
294 # * Keep everything in hold buffer until the last line
295 # sed 4: Remove empty lines caused by G and H on empty hold buffer
296 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
297 sed 's/^/--/' "$1" | sed -e '
304 # Merge with next line if it contains --off
305 s/\n\([^\n]* --off\)/ \1/
311 # Swap lines 1 and 2 if --off is found
312 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
320 /--mode/{ N; s/\n/ /; }
321 ' | xargs -L 1 $XRANDR
330 local CONF="$PROFILES/$PROFILE/config"
331 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
333 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
334 echo " -> Error: Profile '$PROFILE' does not exist." >&2
338 if [ -x "$PROFILES/preswitch" ]; then
339 "$PROFILES/preswitch" "$PROFILE"
341 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
342 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
345 if [ -f "$CONF" ]; then
346 echo " -> loading profile $PROFILE"
347 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
348 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
353 if [ $PROFILE = "common" ]; then
354 echo " -> setting largest common mode in cloned mode"
356 elif [ $PROFILE = "horizontal" ]; then
357 echo " -> stacking all outputs horizontally at their largest modes"
358 STACK="horizontal" stack_cfg_xrandr
359 elif [ $PROFILE = "vertical" ]; then
360 echo " -> stacking all outputs vertically at their largest modes"
361 STACK="vertical" stack_cfg_xrandr
365 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
366 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
368 if [ -x "$PROFILES/postswitch" ]; then
369 "$PROFILES/postswitch" "$PROFILE"
375 Usage: $SCRIPTNAME [options]
377 -h, --help get this small help
378 -c, --change reload current setup
379 -s, --save <profile> save your current setup to profile <profile>
380 -l, --load <profile> load profile <profile>
381 -d, --default <profile> make profile <profile> the default profile
382 --force force (re)loading of a profile
383 --fingerprint fingerprint your current hardware setup
384 --config dump your current xrandr setup
386 To prevent a profile from being loaded, place a script call "block" in its
387 directory. The script is evaluated before the screen setup is inspected, and
388 in case of it returning a value of 0 the profile is skipped. This can be used
389 to query the status of a docking station you are about to leave.
391 If no suitable profile can be identified, the current configuration is kept.
392 To change this behaviour and switch to a fallback configuration, specify
395 Another script called "postswitch "can be placed in the directory
396 ~/.autorandr as well as in any profile directories: The scripts are executed
397 after a mode switch has taken place and can notify window managers.
399 When called by the name "autodisper" or "auto-disper", the script uses "disper"
400 instead of "xrandr" to detect, configure and save the display configuration.
402 If xrandr is used, the following virtual configurations are available:
403 ${RESERVED_PROFILE_NAMES}
409 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
410 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
415 -c|--change) CHANGE_PROFILE=1; shift ;;
416 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
417 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
418 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
420 --force) FORCE_LOAD=1; shift ;;
421 --fingerprint) setup_fp; exit 0;;
422 --config) current_cfg; exit 0;;
424 *) echo "Error: $1"; exit 1;;
428 CURRENT_SETUP="$(setup_fp)"
430 if [ -n "$SAVE_PROFILE" ]; then
431 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
432 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
435 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
436 mkdir -p "$PROFILES/$SAVE_PROFILE"
437 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
438 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
442 if [ -n "$LOAD_PROFILE" ]; then
443 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
447 for SETUP_FILE in $PROFILES/*/setup; do
448 if ! [ -e $SETUP_FILE ]; then
451 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
454 if blocked "$PROFILE"; then
459 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
460 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
462 if [ "$CHANGE_PROFILE" -eq 1 ]; then
463 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
467 # found the profile, exit with success
474 # we did not find the profile, load default
475 if [ -n "$DEFAULT_PROFILE" ]; then
476 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
477 load "$DEFAULT_PROFILE"
485 # indent-tabs-mode: t