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 's/^/--/' "$1" | sed -e '
292 # Merge with next line if it contains --off
293 s/\n\([^\n]* --off\)/ \1/
299 # Swap lines 1 and 2 if --off is found
300 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
307 /./ !d' | xargs -L 1 $XRANDR
316 local CONF="$PROFILES/$PROFILE/config"
317 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
319 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
320 echo " -> Error: Profile '$PROFILE' does not exist." >&2
324 if [ -x "$PROFILES/preswitch" ]; then
325 "$PROFILES/preswitch" "$PROFILE"
327 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
328 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
331 if [ -f "$CONF" ]; then
332 echo " -> loading profile $PROFILE"
333 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
334 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
339 if [ $PROFILE = "common" ]; then
340 echo " -> setting largest common mode in cloned mode"
342 elif [ $PROFILE = "horizontal" ]; then
343 echo " -> stacking all outputs horizontally at their largest modes"
344 STACK="horizontal" stack_cfg_xrandr
345 elif [ $PROFILE = "vertical" ]; then
346 echo " -> stacking all outputs vertically at their largest modes"
347 STACK="vertical" stack_cfg_xrandr
351 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
352 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
354 if [ -x "$PROFILES/postswitch" ]; then
355 "$PROFILES/postswitch" "$PROFILE"
361 Usage: $SCRIPTNAME [options]
363 -h, --help get this small help
364 -c, --change reload current setup
365 -s, --save <profile> save your current setup to profile <profile>
366 -l, --load <profile> load profile <profile>
367 -d, --default <profile> make profile <profile> the default profile
368 --force force (re)loading of a profile
369 --fingerprint fingerprint your current hardware setup
370 --config dump your current xrandr setup
372 To prevent a profile from being loaded, place a script call "block" in its
373 directory. The script is evaluated before the screen setup is inspected, and
374 in case of it returning a value of 0 the profile is skipped. This can be used
375 to query the status of a docking station you are about to leave.
377 If no suitable profile can be identified, the current configuration is kept.
378 To change this behaviour and switch to a fallback configuration, specify
381 Another script called "postswitch "can be placed in the directory
382 ~/.autorandr as well as in any profile directories: The scripts are executed
383 after a mode switch has taken place and can notify window managers.
385 When called by the name "autodisper" or "auto-disper", the script uses "disper"
386 instead of "xrandr" to detect, configure and save the display configuration.
388 If xrandr is used, the following virtual configurations are available:
389 ${RESERVED_PROFILE_NAMES}
395 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
396 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
401 -c|--change) CHANGE_PROFILE=1; shift ;;
402 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
403 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
404 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
406 --force) FORCE_LOAD=1; shift ;;
407 --fingerprint) setup_fp; exit 0;;
408 --config) current_cfg; exit 0;;
410 *) echo "Error: $1"; exit 1;;
414 CURRENT_SETUP="$(setup_fp)"
416 if [ -n "$SAVE_PROFILE" ]; then
417 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
418 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
421 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
422 mkdir -p "$PROFILES/$SAVE_PROFILE"
423 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
424 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
428 if [ -n "$LOAD_PROFILE" ]; then
429 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
433 for SETUP_FILE in $PROFILES/*/setup; do
434 if ! [ -e $SETUP_FILE ]; then
437 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
440 if blocked "$PROFILE"; then
445 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
446 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
448 if [ "$CHANGE_PROFILE" -eq 1 ]; then
449 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
453 # found the profile, exit with success
460 # we did not find the profile, load default
461 if [ -n "$DEFAULT_PROFILE" ]; then
462 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
463 load "$DEFAULT_PROFILE"
471 # indent-tabs-mode: t