]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blob - autorandr
Merge remote-tracking branch 'bingmann/master'
[deb_pkgs/autorandr.git] / autorandr
1 #!/bin/sh
2 #
3 # Automatically select a display configuration based on connected devices
4 #
5 # Stefan Tomanek <stefan.tomanek@wertarbyte.de>
6 #
7 # How to use:
8 #
9 # Save your current display configuration and setup with:
10 #  $ autorandr --save mobile
11 #
12 # Connect an additional display, configure your setup and save it:
13 #  $ autorandr --save docked
14 #
15 # Now autorandr can detect which hardware setup is active:
16 #  $ autorandr
17 #    mobile
18 #    docked (detected)
19 #
20 # To automatically reload your setup, just append --change to the command line
21 #
22 # To manually load a profile, you can use the --load <profile> option.
23 #
24 # autorandr tries to avoid reloading an identical configuration. To force the
25 # (re)configuration, apply --force.
26 #
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.
31 #
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
34 # --default <profile>
35 #
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.
40 #
41 #
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.
46
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
56 EOF`
57
58 CHANGE_PROFILE=0
59 FORCE_LOAD=0
60 DEFAULT_PROFILE=""
61 SAVE_PROFILE=""
62
63 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
64 CURRENT_CFG_METHOD="current_cfg_xrandr"
65 LOAD_METHOD="load_cfg_xrandr"
66
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"
74 fi
75
76 if [ -f $CONFIG ]; then
77         echo "Loading configuration from '$CONFIG'" >&2
78         . $CONFIG
79 fi
80
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]; } }'
86 }
87
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
93                 # nothing found
94                 [ ! -d "$P" ] && continue
95                 if grep -q "^connected$" < "${P}status"; then
96                         echo -n "$(basename "$P") "
97                         md5sum ${P}edid | awk '{print $1}'
98                 fi
99         done
100 }
101
102 setup_fp_disper() {
103         $DISPER -l | grep '^display '
104 }
105
106 setup_fp() {
107         local FP="";
108         for M in $FP_METHODS; do
109                 FP="$($M)"
110                 if [ -n "$FP" ]; then
111                         break
112                 fi
113         done
114         if [ -z "$FP" ]; then
115                 echo "Unable to fingerprint display configuration" >&2
116                 return
117         fi
118         echo "$FP"
119 }
120
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}')"
125         fi
126         $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
127         # display is connected and has a mode
128         /^[^ ]+ connected [^(]/ {
129                 print "output "$1;
130                 if ($3 == "primary") {
131                         print $3
132                         split($4, A, "+")
133                         $4=$5
134                 }
135                 else {
136                         split($3, A, "+");
137                         if (A[1] A[2] "," A[3] == primary_setup)
138                                 print "primary";
139                 }
140                 if (($4 == "left") || ($4 == "right")) {
141                         split(A[1], B, "x");
142                         A[1] = B[2]"x"B[1];
143                 }
144                 print "mode "A[1];
145                 print "pos "A[2]"x"A[3];
146                 if ($4 !~ /^\(/) {
147                         print "rotate "$4;
148                 }
149                 else {
150                         print "rotate normal";
151                 }
152                 next;
153         }
154         # disconnected or disabled displays
155         /^[^ ]+ (dis)?connected / ||
156         /^[^ ]+ unknown connection / {
157                 print "output "$1;
158                 print "off";
159                 next;
160         }'
161 }
162
163 current_cfg_disper() {
164         $DISPER -p
165 }
166
167 common_cfg_xrandr() {
168         $XRANDR -q | awk '
169         # variables:
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 / {
177             output=$1;
178             outputlist=outputlist " " output
179             outputarr[outputarrsize++]=output
180         }
181         # disconnected or disabled displays
182         /^[^ ]+ disconnected / ||
183         /^[^ ]+ unknown connection / {
184             print "output " $1;
185             print "off";
186         }
187         # modes available on a screen
188         /^   [0-9]+x[0-9]+/ {
189             modelist[$1]=modelist[$1] " " output
190         }
191         END {
192             # find common mode with largest screen area
193             for (m in modelist) {
194                 if (modelist[m] == outputlist) {
195                     # calculate area of resolution
196                     split(m, wh, "x");
197                     if (wh[1]*wh[2] >= maxdim) {
198                         maxdim=wh[1]*wh[2]
199                         maxmode=m
200                     }
201                 }
202             }
203             if (maxmode) {
204                 for (i in outputarr) {
205                     print "output " outputarr[i];
206                     print "mode " maxmode;
207                     print "pos 0x0";
208                     if (i > 0) {
209                         print "same-as " outputarr[0]
210                     }
211                 }
212             }
213         }' \
214                 | load_cfg_xrandr -
215 }
216
217 stack_cfg_xrandr() {
218         $XRANDR -q | awk -v stack="${STACK}" '
219         # variables:
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
224         BEGIN {
225             posX=posY=0
226         }
227         # display is connected
228         /^[^ ]+ connected / {
229             output=$1;
230             print "output " $1;
231             firstmode=1
232         }
233         # disconnected or disabled displays
234         /^[^ ]+ disconnected / ||
235         /^[^ ]+ unknown connection / {
236             print "output " $1;
237             print "off";
238         }
239         # modes available on a screen, but pick only the first
240         /^   [0-9]+x[0-9]+/ {
241             if (!firstmode) next;
242             firstmode=0
243             # output mode at current virtual desktop pos
244             print "mode " $1;
245             print "pos " posX "x" posY;
246             # calculate position of next output
247             split($1, wh, "x");
248             if (stack == "vertical")
249                 posY += wh[2];
250             else
251                 posX += wh[1];
252         }' \
253                 | load_cfg_xrandr -
254 }
255
256 current_cfg() {
257         $CURRENT_CFG_METHOD;
258 }
259
260 blocked() {
261         local PROFILE="$1"
262         [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
263
264         "$PROFILES/$PROFILE/block" "$PROFILE"
265 }
266
267 config_equal() {
268         local PROFILE="$1"
269         if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
270                 echo "Config already loaded"
271                 return 0
272         else
273                 return 1
274         fi
275 }
276
277 load_cfg_xrandr() {
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 '
282                 :START
283                 /\n--output/{P;D}
284                 s/\n/ /
285                 N;bSTART' | sed -e '
286                         ### First line
287                         / --off/{
288                                 G
289                                 # Merge if next line contains --off
290                                 s/\n\([^\n]* --off\)/ \1/
291                                 h
292                                 $!d;b
293                         }
294                         ### Last line
295                         H;x
296                         # Merge if previous line contains --mode
297                         s/\(--mode [^\n]*\)\n/\1 /
298                         h
299                         $!d' | xargs -L 1 $XRANDR
300 }
301
302 load_cfg_disper() {
303         $DISPER -i < "$1"
304 }
305
306 load() {
307         local PROFILE="$1"
308         local CONF="$PROFILES/$PROFILE/config"
309         local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
310
311         if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE == 0 ]; then
312                 echo " -> Error: Profile '$PROFILE' does not exist." >&2
313                 return
314         fi
315
316         if [ -x "$PROFILES/preswitch" ]; then
317                 "$PROFILES/preswitch" "$PROFILE"
318         fi
319         if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
320                 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
321         fi
322
323         if [ -f "$CONF" ]; then
324                 echo " -> loading profile $PROFILE"
325                 if [ $IS_VIRTUAL_PROFILE != 0 ]; then
326                         echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
327                 fi
328                 $LOAD_METHOD "$CONF"
329         else
330                 # Virtual profiles
331                 if [ $PROFILE = "common" ]; then
332                         echo " -> setting largest common mode in cloned mode"
333                         common_cfg_xrandr
334                 elif [ $PROFILE = "horizontal" ]; then
335                         echo " -> stacking all outputs horizontally at their largest modes"
336                         STACK="horizontal" stack_cfg_xrandr
337                 elif [ $PROFILE = "vertical" ]; then
338                         echo " -> stacking all outputs vertically at their largest modes"
339                         STACK="vertical" stack_cfg_xrandr
340                 fi
341         fi
342
343         if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
344                 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
345         fi
346         if [ -x "$PROFILES/postswitch" ]; then
347                 "$PROFILES/postswitch" "$PROFILE"
348         fi
349 }
350
351 help() {
352         cat <<EOH
353 Usage: $SCRIPTNAME [options]
354
355 -h, --help              get this small help
356 -c, --change            reload current setup
357 -s, --save <profile>    save your current setup to profile <profile>
358 -l, --load <profile>    load profile <profile>
359 -d, --default <profile> make profile <profile> the default profile
360 --force                 force (re)loading of a profile
361 --fingerprint           fingerprint your current hardware setup
362 --config                dump your current xrandr setup
363
364  To prevent a profile from being loaded, place a script call "block" in its
365  directory. The script is evaluated before the screen setup is inspected, and
366  in case of it returning a value of 0 the profile is skipped. This can be used
367  to query the status of a docking station you are about to leave.
368
369  If no suitable profile can be identified, the current configuration is kept.
370  To change this behaviour and switch to a fallback configuration, specify
371  --default <profile>.
372
373  Another script called "postswitch "can be placed in the directory
374  ~/.autorandr as well as in any profile directories: The scripts are executed
375  after a mode switch has taken place and can notify window managers.
376
377  When called by the name "autodisper" or "auto-disper", the script uses "disper"
378  instead of "xrandr" to detect, configure and save the display configuration.
379
380  If xrandr is used, the following virtual configurations are available:
381 ${RESERVED_PROFILE_NAMES}
382
383 EOH
384         exit
385 }
386 # process parameters
387 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
388 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
389 eval set -- "$OPTS"
390
391 while true; do
392         case "$1" in
393                 -c|--change) CHANGE_PROFILE=1; shift ;;
394                 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
395                 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
396                 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
397                 -h|--help) help ;;
398                 --force) FORCE_LOAD=1; shift ;;
399                 --fingerprint) setup_fp; exit 0;;
400                 --config) current_cfg; exit 0;;
401                 --) shift; break ;;
402                 *) echo "Error: $1"; exit 1;;
403         esac
404 done
405
406 CURRENT_SETUP="$(setup_fp)"
407
408 if [ -n "$SAVE_PROFILE" ]; then
409         if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
410                 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
411                 exit 1
412         fi
413         echo "Saving current configuration as profile '${SAVE_PROFILE}'"
414         mkdir -p "$PROFILES/$SAVE_PROFILE"
415         echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
416         $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
417         exit 0
418 fi
419
420 if [ -n "$LOAD_PROFILE" ]; then
421         CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
422         exit $?
423 fi
424
425 for SETUP_FILE in $PROFILES/*/setup; do
426         if ! [ -e $SETUP_FILE ]; then
427                 break
428         fi
429         PROFILE="$(basename $(dirname "$SETUP_FILE"))"
430         echo -n "$PROFILE"
431
432         if blocked "$PROFILE"; then
433                 echo " (blocked)"
434                 continue
435         fi
436
437         FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
438         if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
439                 echo " (detected)"
440                 if [ "$CHANGE_PROFILE" -eq 1 ]; then
441                         if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
442                                 load "$PROFILE"
443                         fi
444                 fi
445                 # found the profile, exit with success
446                 exit 0
447         else
448                 echo ""
449         fi
450 done
451
452 # we did not find the profile, load default
453 if [ -n "$DEFAULT_PROFILE" ]; then
454         echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
455         load "$DEFAULT_PROFILE"
456 fi
457 exit 1
458
459 # Local Variables:
460 # tab-width: 8
461 # sh-basic-offset: 8
462 # sh-indentation: 8
463 # indent-tabs-mode: t
464 # End: