3 # Automatically select a display configuration based on connected devices
5 # Copyright (c) 2013 Stefan Tomanek <stefan.tomanek@wertarbyte.de>
7 # THIS IS _NOT_ OSS CODE!
13 # Save your current display configuration and setup with:
14 # $ autorandr --save mobile
16 # Connect an additional display, configure your setup and save it:
17 # $ autorandr --save docked
19 # Now autorandr can detect which hardware setup is active:
24 # To automatically reload your setup, just append --change to the command line
26 # To manually load a profile, you can use the --load <profile> option.
28 # autorandr tries to avoid reloading an identical configuration. To force the
29 # (re)configuration, apply --force.
31 # To prevent a profile from being loaded, place a script call "block" in its
32 # directory. The script is evaluated before the screen setup is inspected, and
33 # in case of it returning a value of 0 the profile is skipped. This can be used
34 # to query the status of a docking station you are about to leave.
36 # If no suitable profile can be identified, the current configuration is kept.
37 # To change this behaviour and switch to a fallback configuration, specify
40 # Another script called "postswitch "can be placed in the directory
41 # ~/.autorandr as well as in all profile directories: The scripts are executed
42 # after a mode switch has taken place and can notify window managers or other
43 # applications about it.
46 # While the script uses xrandr by default, calling it by the name "autodisper"
47 # or "auto-disper" forces it to use the "disper" utility, which is useful for
48 # controlling nvidia chipsets. The formats for fingerprinting the current setup
49 # and saving/loading the current configuration are adjusted accordingly.
51 XRANDR=/usr/bin/xrandr
52 DISPER=/usr/bin/disper
53 XDPYINFO=/usr/bin/xdpyinfo
55 CONFIG=~/.autorandr.conf
56 RESERVED_PROFILE_NAMES=`cat <<EOF
57 common Clone all connected outputs at the largest common resolution
58 horizontal Stack all connected outputs horizontally at their largest resolution
59 vertical Stack all connected outputs vertically at their largest resolution
67 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
68 CURRENT_CFG_METHOD="current_cfg_xrandr"
69 LOAD_METHOD="load_cfg_xrandr"
71 SCRIPTNAME="$(basename $0)"
72 # when called as autodisper/auto-disper, we assume different defaults
73 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
74 echo "Assuming disper defaults..." >&2
75 FP_METHODS="setup_fp_disper"
76 CURRENT_CFG_METHOD="current_cfg_disper"
77 LOAD_METHOD="load_cfg_disper"
80 if [ -f $CONFIG ]; then
81 echo "Loading configuration from '$CONFIG'" >&2
85 setup_fp_xrandr_edid() {
86 $XRANDR -q --verbose | awk '
88 / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
89 /^[[:blank:]]+EDID:/ {
92 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
103 setup_fp_sysfs_edid() {
104 which xxd >/dev/null 2>&1 || return
105 $XRANDR -q > /dev/null
106 for DEVICE in /sys/class/drm/card*-*; do
107 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
108 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
109 xxd -c 256 -ps "${DEVICE}/edid" | awk 'ORS=""; /.+/ { print; }'
115 $DISPER -l | grep '^display '
120 for M in $FP_METHODS; do
122 if [ -n "$FP" ]; then
126 if [ -z "$FP" ]; then
127 echo "Unable to fingerprint display configuration" >&2
130 echo "$FINGERPRINT" | sort
133 current_cfg_xrandr() {
134 local PRIMARY_SETUP="";
135 if [ -x "$XDPYINFO" ]; then
136 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
138 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
139 # display is connected and has a mode
140 /^[^ ]+ connected [^(]/ {
142 if ($3 == "primary") {
149 if (A[1] A[2] "," A[3] == primary_setup)
152 if (($4 == "left") || ($4 == "right")) {
157 print "pos "A[2]"x"A[3];
162 print "rotate normal";
166 # disconnected or disabled displays
167 /^[^ ]+ (dis)?connected / ||
168 /^[^ ]+ unknown connection / {
175 current_cfg_disper() {
179 common_cfg_xrandr() {
182 # output: current output
183 # outputlist: space sep list of all outputs
184 # outputarr: array of all connected outputs
185 # outputarrsize: number of connected outputs
186 # modelist[800x600]: space sep list of outputs supporting mode
187 # display is connected
188 /^[^ ]+ connected / {
190 outputlist=outputlist " " output
191 outputarr[outputarrsize++]=output
193 # disconnected or disabled displays
194 /^[^ ]+ disconnected / ||
195 /^[^ ]+ unknown connection / {
199 # modes available on a screen
201 modelist[$1]=modelist[$1] " " output
204 # find common mode with largest screen area
205 for (m in modelist) {
206 if (modelist[m] == outputlist) {
207 # calculate area of resolution
209 if (wh[1]*wh[2] >= maxdim) {
216 for (i in outputarr) {
217 print "output " outputarr[i];
218 print "mode " maxmode;
221 print "same-as " outputarr[0]
230 $XRANDR -q | awk -v stack="${STACK}" '
232 # stack: "horizontal" (anything except vertical) or "vertical"
233 # output: current output
234 # firstmode: pick first mode after output
235 # posX,posY: position of the next output
239 # display is connected
240 /^[^ ]+ connected / {
245 # disconnected or disabled displays
246 /^[^ ]+ disconnected / ||
247 /^[^ ]+ unknown connection / {
251 # modes available on a screen, but pick only the first
253 if (!firstmode) next;
255 # output mode at current virtual desktop pos
257 print "pos " posX "x" posY;
258 # calculate position of next output
260 if (stack == "vertical")
274 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
276 "$PROFILES/$PROFILE/block" "$PROFILE"
281 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
282 echo "Config already loaded"
290 # sed 1: Prefix arguments with "--"
291 # sed 2: Merge arguments into one line per output
292 # sed 3: * Merge all --off outputs into the first line
293 # * Place the output with --pos 0x0 on the second line
294 # * Remaining outputs are appended as they appear
295 # * Keep everything in hold buffer until the last line
296 # sed 4: Remove empty lines caused by G and H on empty hold buffer
297 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
298 sed 's/^/--/' "$1" | sed -e '
305 # Merge with next line if it contains --off
306 s/\n\([^\n]* --off\)/ \1/
312 # Swap lines 1 and 2 if --off is found
313 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
321 /--mode/{ N; s/\n/ /; }
322 ' | xargs -L 1 $XRANDR
331 local CONF="$PROFILES/$PROFILE/config"
332 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
334 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
335 echo " -> Error: Profile '$PROFILE' does not exist." >&2
339 if [ -x "$PROFILES/preswitch" ]; then
340 "$PROFILES/preswitch" "$PROFILE"
342 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
343 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
346 if [ -f "$CONF" ]; then
347 echo " -> loading profile $PROFILE"
348 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
349 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
354 if [ $PROFILE = "common" ]; then
355 echo " -> setting largest common mode in cloned mode"
357 elif [ $PROFILE = "horizontal" ]; then
358 echo " -> stacking all outputs horizontally at their largest modes"
359 STACK="horizontal" stack_cfg_xrandr
360 elif [ $PROFILE = "vertical" ]; then
361 echo " -> stacking all outputs vertically at their largest modes"
362 STACK="vertical" stack_cfg_xrandr
366 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
367 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
369 if [ -x "$PROFILES/postswitch" ]; then
370 "$PROFILES/postswitch" "$PROFILE"
376 Usage: $SCRIPTNAME [options]
378 -h, --help get this small help
379 -c, --change reload current setup
380 -s, --save <profile> save your current setup to profile <profile>
381 -l, --load <profile> load profile <profile>
382 -d, --default <profile> make profile <profile> the default profile
383 --force force (re)loading of a profile
384 --fingerprint fingerprint your current hardware setup
385 --config dump your current xrandr setup
387 To prevent a profile from being loaded, place a script call "block" in its
388 directory. The script is evaluated before the screen setup is inspected, and
389 in case of it returning a value of 0 the profile is skipped. This can be used
390 to query the status of a docking station you are about to leave.
392 If no suitable profile can be identified, the current configuration is kept.
393 To change this behaviour and switch to a fallback configuration, specify
396 Another script called "postswitch "can be placed in the directory
397 ~/.autorandr as well as in any profile directories: The scripts are executed
398 after a mode switch has taken place and can notify window managers.
400 When called by the name "autodisper" or "auto-disper", the script uses "disper"
401 instead of "xrandr" to detect, configure and save the display configuration.
403 If xrandr is used, the following virtual configurations are available:
404 ${RESERVED_PROFILE_NAMES}
410 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
411 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
416 -c|--change) CHANGE_PROFILE=1; shift ;;
417 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
418 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
419 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
421 --force) FORCE_LOAD=1; shift ;;
422 --fingerprint) setup_fp; exit 0;;
423 --config) current_cfg; exit 0;;
425 *) echo "Error: $1"; exit 1;;
429 CURRENT_SETUP="$(setup_fp)"
431 if [ -n "$SAVE_PROFILE" ]; then
432 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
433 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
436 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
437 mkdir -p "$PROFILES/$SAVE_PROFILE"
438 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
439 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
443 if [ -n "$LOAD_PROFILE" ]; then
444 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
448 for SETUP_FILE in $PROFILES/*/setup; do
449 if ! [ -e $SETUP_FILE ]; then
452 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
455 if blocked "$PROFILE"; then
460 # This sed command is for compatibility with old versions that did not try
461 # to normalize device names
462 FILE_SETUP="$(sed -re 's#-([A-Z]-)?##g; s#card[0-9]##;' "$PROFILES/$PROFILE/setup")"
463 # Detect the md5sum in fingerprint files created using the old sysfs fingerprinting
464 # If it is detected, output a warning and calculate the legacy variant of the current
466 if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
467 echo -n " (Obsolete fingerprint format. Please update using --save.) "
469 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
470 LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
472 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
475 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
476 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
477 CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
481 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
483 if [ "$CHANGE_PROFILE" -eq 1 ]; then
484 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
488 # found the profile, exit with success
495 # we did not find the profile, load default
496 if [ -n "$DEFAULT_PROFILE" ]; then
497 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
498 load "$DEFAULT_PROFILE"
506 # indent-tabs-mode: t