]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blob - autorandr
Store refresh rate
[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                 output=$1
130                 print "output "$1;
131                 if ($3 == "primary") {
132                         print $3
133                         split($4, A, "+")
134                         $4=$5
135                 }
136                 else {
137                         split($3, A, "+");
138                         if (A[1] A[2] "," A[3] == primary_setup)
139                                 print "primary";
140                 }
141                 if (($4 == "left") || ($4 == "right")) {
142                         split(A[1], B, "x");
143                         A[1] = B[2]"x"B[1];
144                 }
145                 print "mode "A[1];
146                 print "pos "A[2]"x"A[3];
147                 if ($4 !~ /^\(/) {
148                         print "rotate "$4;
149                 }
150                 else {
151                         print "rotate normal";
152                 }
153                 next;
154         }
155         /  [0-9]+x[0-9]+ .+/ {
156                 if (output) {
157                         for (n=1; n<10; n++) {
158                                 if($n ~ /[0-9]+\.[0-9]+\*/) {
159                                         print "rate " gensub(/(+|\*)/, "", "g", $n);
160                                 }
161                         }
162                 }
163         }
164         # disconnected or disabled displays
165         /^[^ ]+ (dis)?connected / ||
166         /^[^ ]+ unknown connection / {
167                 output=""
168                 print "output "$1;
169                 print "off";
170                 next;
171         }'
172 }
173
174 current_cfg_disper() {
175         $DISPER -p
176 }
177
178 common_cfg_xrandr() {
179         $XRANDR -q | awk '
180         # variables:
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 / {
188             output=$1;
189             outputlist=outputlist " " output
190             outputarr[outputarrsize++]=output
191         }
192         # disconnected or disabled displays
193         /^[^ ]+ disconnected / ||
194         /^[^ ]+ unknown connection / {
195             print "output " $1;
196             print "off";
197         }
198         # modes available on a screen
199         /^   [0-9]+x[0-9]+/ {
200             modelist[$1]=modelist[$1] " " output
201         }
202         END {
203             # find common mode with largest screen area
204             for (m in modelist) {
205                 if (modelist[m] == outputlist) {
206                     # calculate area of resolution
207                     split(m, wh, "x");
208                     if (wh[1]*wh[2] >= maxdim) {
209                         maxdim=wh[1]*wh[2]
210                         maxmode=m
211                     }
212                 }
213             }
214             if (maxmode) {
215                 for (i in outputarr) {
216                     print "output " outputarr[i];
217                     print "mode " maxmode;
218                     print "pos 0x0";
219                     if (i > 0) {
220                         print "same-as " outputarr[0]
221                     }
222                 }
223             }
224         }' \
225                 | load_cfg_xrandr -
226 }
227
228 stack_cfg_xrandr() {
229         $XRANDR -q | awk -v stack="${STACK}" '
230         # variables:
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
235         BEGIN {
236             posX=posY=0
237         }
238         # display is connected
239         /^[^ ]+ connected / {
240             output=$1;
241             print "output " $1;
242             firstmode=1
243         }
244         # disconnected or disabled displays
245         /^[^ ]+ disconnected / ||
246         /^[^ ]+ unknown connection / {
247             print "output " $1;
248             print "off";
249         }
250         # modes available on a screen, but pick only the first
251         /^   [0-9]+x[0-9]+/ {
252             if (!firstmode) next;
253             firstmode=0
254             # output mode at current virtual desktop pos
255             print "mode " $1;
256             print "pos " posX "x" posY;
257             # calculate position of next output
258             split($1, wh, "x");
259             if (stack == "vertical")
260                 posY += wh[2];
261             else
262                 posX += wh[1];
263         }' \
264                 | load_cfg_xrandr -
265 }
266
267 current_cfg() {
268         $CURRENT_CFG_METHOD;
269 }
270
271 blocked() {
272         local PROFILE="$1"
273         [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
274
275         "$PROFILES/$PROFILE/block" "$PROFILE"
276 }
277
278 config_equal() {
279         local PROFILE="$1"
280         if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
281                 echo "Config already loaded"
282                 return 0
283         else
284                 return 1
285         fi
286 }
287
288 load_cfg_xrandr() {
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 '
298                 :START
299                 /\n--output/{P;D}
300                 s/\n/ /
301                 N;bSTART' | sed -e '
302                         / --off/{
303                                 G
304                                 # Merge with next line if it contains --off
305                                 s/\n\([^\n]* --off\)/ \1/
306                                 h
307                                 $!d;b
308                         }
309                         / --pos 0x0/{
310                                 G
311                                 # Swap lines 1 and 2 if --off is found
312                                 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
313                                 h
314                                 $!d;b
315                         }
316                         H
317                         $!d
318                         x' | sed -e '
319                                 /./ !d' | sed -e '
320                                         /--mode/{ N; s/\n/ /; }
321                                 ' | xargs -L 1 $XRANDR
322 }
323
324 load_cfg_disper() {
325         $DISPER -i < "$1"
326 }
327
328 load() {
329         local PROFILE="$1"
330         local CONF="$PROFILES/$PROFILE/config"
331         local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
332
333         if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
334                 echo " -> Error: Profile '$PROFILE' does not exist." >&2
335                 return
336         fi
337
338         if [ -x "$PROFILES/preswitch" ]; then
339                 "$PROFILES/preswitch" "$PROFILE"
340         fi
341         if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
342                 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
343         fi
344
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
349                 fi
350                 $LOAD_METHOD "$CONF"
351         else
352                 # Virtual profiles
353                 if [ $PROFILE = "common" ]; then
354                         echo " -> setting largest common mode in cloned mode"
355                         common_cfg_xrandr
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
362                 fi
363         fi
364
365         if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
366                 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
367         fi
368         if [ -x "$PROFILES/postswitch" ]; then
369                 "$PROFILES/postswitch" "$PROFILE"
370         fi
371 }
372
373 help() {
374         cat <<EOH
375 Usage: $SCRIPTNAME [options]
376
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
385
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.
390
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
393  --default <profile>.
394
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.
398
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.
401
402  If xrandr is used, the following virtual configurations are available:
403 ${RESERVED_PROFILE_NAMES}
404
405 EOH
406         exit
407 }
408 # process parameters
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
411 eval set -- "$OPTS"
412
413 while true; do
414         case "$1" in
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 ;;
419                 -h|--help) help ;;
420                 --force) FORCE_LOAD=1; shift ;;
421                 --fingerprint) setup_fp; exit 0;;
422                 --config) current_cfg; exit 0;;
423                 --) shift; break ;;
424                 *) echo "Error: $1"; exit 1;;
425         esac
426 done
427
428 CURRENT_SETUP="$(setup_fp)"
429
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."
433                 exit 1
434         fi
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"
439         exit 0
440 fi
441
442 if [ -n "$LOAD_PROFILE" ]; then
443         CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
444         exit $?
445 fi
446
447 for SETUP_FILE in $PROFILES/*/setup; do
448         if ! [ -e $SETUP_FILE ]; then
449                 break
450         fi
451         PROFILE="$(basename $(dirname "$SETUP_FILE"))"
452         echo -n "$PROFILE"
453
454         if blocked "$PROFILE"; then
455                 echo " (blocked)"
456                 continue
457         fi
458
459         FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
460         if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
461                 echo " (detected)"
462                 if [ "$CHANGE_PROFILE" -eq 1 ]; then
463                         if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
464                                 load "$PROFILE"
465                         fi
466                 fi
467                 # found the profile, exit with success
468                 exit 0
469         else
470                 echo ""
471         fi
472 done
473
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"
478 fi
479 exit 1
480
481 # Local Variables:
482 # tab-width: 8
483 # sh-basic-offset: 8
484 # sh-indentation: 8
485 # indent-tabs-mode: t
486 # End: