]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blob - autorandr
Detect and process legacy fingerprint (setup) files
[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         ORS="";
84         / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
85         /^[[:blank:]]+EDID:/ {
86                 print DEVICE
87                 DEVICE=""
88                 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
89                         print $1;
90                 }
91                 print "\n";
92         }
93         END {
94                 print "\n";
95         }
96         '
97 }
98
99 setup_fp_sysfs_edid() {
100         which xxd >/dev/null 2>&1 || return
101         $XRANDR -q > /dev/null
102         for DEVICE in /sys/class/drm/card*-*; do
103                 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
104                 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
105                         xxd -c 256 -ps "${DEVICE}/edid" | awk 'ORS=""; /.+/ { print; }'
106                 echo
107         done
108 }
109
110 setup_fp_disper() {
111         $DISPER -l | grep '^display '
112 }
113
114 setup_fp() {
115         local FP="";
116         for M in $FP_METHODS; do
117                 FP="$($M)"
118                 if [ -n "$FP" ]; then
119                         break
120                 fi
121         done
122         if [ -z "$FP" ]; then
123                 echo "Unable to fingerprint display configuration" >&2
124                 return
125         fi
126         echo "$FINGERPRINT" | sort
127 }
128
129 current_cfg_xrandr() {
130         local PRIMARY_SETUP="";
131         if [ -x "$XDPYINFO" ]; then
132                 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^  head #0:/ {printf $3 $5}')"
133         fi
134         $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
135         # display is connected and has a mode
136         /^[^ ]+ connected [^(]/ {
137                 print "output "$1;
138                 if ($3 == "primary") {
139                         print $3
140                         split($4, A, "+")
141                         $4=$5
142                 }
143                 else {
144                         split($3, A, "+");
145                         if (A[1] A[2] "," A[3] == primary_setup)
146                                 print "primary";
147                 }
148                 if (($4 == "left") || ($4 == "right")) {
149                         split(A[1], B, "x");
150                         A[1] = B[2]"x"B[1];
151                 }
152                 print "mode "A[1];
153                 print "pos "A[2]"x"A[3];
154                 if ($4 !~ /^\(/) {
155                         print "rotate "$4;
156                 }
157                 else {
158                         print "rotate normal";
159                 }
160                 next;
161         }
162         # disconnected or disabled displays
163         /^[^ ]+ (dis)?connected / ||
164         /^[^ ]+ unknown connection / {
165                 print "output "$1;
166                 print "off";
167                 next;
168         }'
169 }
170
171 current_cfg_disper() {
172         $DISPER -p
173 }
174
175 common_cfg_xrandr() {
176         $XRANDR -q | awk '
177         # variables:
178         #   output: current output
179         #   outputlist: space sep list of all outputs
180         #   outputarr: array of all connected outputs
181         #   outputarrsize: number of connected outputs
182         #   modelist[800x600]: space sep list of outputs supporting mode
183         # display is connected
184         /^[^ ]+ connected / {
185             output=$1;
186             outputlist=outputlist " " output
187             outputarr[outputarrsize++]=output
188         }
189         # disconnected or disabled displays
190         /^[^ ]+ disconnected / ||
191         /^[^ ]+ unknown connection / {
192             print "output " $1;
193             print "off";
194         }
195         # modes available on a screen
196         /^   [0-9]+x[0-9]+/ {
197             modelist[$1]=modelist[$1] " " output
198         }
199         END {
200             # find common mode with largest screen area
201             for (m in modelist) {
202                 if (modelist[m] == outputlist) {
203                     # calculate area of resolution
204                     split(m, wh, "x");
205                     if (wh[1]*wh[2] >= maxdim) {
206                         maxdim=wh[1]*wh[2]
207                         maxmode=m
208                     }
209                 }
210             }
211             if (maxmode) {
212                 for (i in outputarr) {
213                     print "output " outputarr[i];
214                     print "mode " maxmode;
215                     print "pos 0x0";
216                     if (i > 0) {
217                         print "same-as " outputarr[0]
218                     }
219                 }
220             }
221         }' \
222                 | load_cfg_xrandr -
223 }
224
225 stack_cfg_xrandr() {
226         $XRANDR -q | awk -v stack="${STACK}" '
227         # variables:
228         #   stack: "horizontal" (anything except vertical) or "vertical"
229         #   output: current output
230         #   firstmode: pick first mode after output
231         #   posX,posY: position of the next output
232         BEGIN {
233             posX=posY=0
234         }
235         # display is connected
236         /^[^ ]+ connected / {
237             output=$1;
238             print "output " $1;
239             firstmode=1
240         }
241         # disconnected or disabled displays
242         /^[^ ]+ disconnected / ||
243         /^[^ ]+ unknown connection / {
244             print "output " $1;
245             print "off";
246         }
247         # modes available on a screen, but pick only the first
248         /^   [0-9]+x[0-9]+/ {
249             if (!firstmode) next;
250             firstmode=0
251             # output mode at current virtual desktop pos
252             print "mode " $1;
253             print "pos " posX "x" posY;
254             # calculate position of next output
255             split($1, wh, "x");
256             if (stack == "vertical")
257                 posY += wh[2];
258             else
259                 posX += wh[1];
260         }' \
261                 | load_cfg_xrandr -
262 }
263
264 current_cfg() {
265         $CURRENT_CFG_METHOD;
266 }
267
268 blocked() {
269         local PROFILE="$1"
270         [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
271
272         "$PROFILES/$PROFILE/block" "$PROFILE"
273 }
274
275 config_equal() {
276         local PROFILE="$1"
277         if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
278                 echo "Config already loaded"
279                 return 0
280         else
281                 return 1
282         fi
283 }
284
285 load_cfg_xrandr() {
286         # sed 1: Prefix arguments with "--"
287         # sed 2: Merge arguments into one line per output
288         # sed 3:  * Merge all --off outputs into the first line
289         #         * Place the output with --pos 0x0 on the second line
290         #         * Remaining outputs are appended as they appear
291         #         * Keep everything in hold buffer until the last line
292         # sed 4: Remove empty lines caused by G and H on empty hold buffer
293         # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
294         sed 's/^/--/' "$1" | sed -e '
295                 :START
296                 /\n--output/{P;D}
297                 s/\n/ /
298                 N;bSTART' | sed -e '
299                         / --off/{
300                                 G
301                                 # Merge with next line if it contains --off
302                                 s/\n\([^\n]* --off\)/ \1/
303                                 h
304                                 $!d;b
305                         }
306                         / --pos 0x0/{
307                                 G
308                                 # Swap lines 1 and 2 if --off is found
309                                 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
310                                 h
311                                 $!d;b
312                         }
313                         H
314                         $!d
315                         x' | sed -e '
316                                 /./ !d' | sed -e '
317                                         /--mode/{ N; s/\n/ /; }
318                                 ' | xargs -L 1 $XRANDR
319 }
320
321 load_cfg_disper() {
322         $DISPER -i < "$1"
323 }
324
325 load() {
326         local PROFILE="$1"
327         local CONF="$PROFILES/$PROFILE/config"
328         local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
329
330         if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
331                 echo " -> Error: Profile '$PROFILE' does not exist." >&2
332                 return
333         fi
334
335         if [ -x "$PROFILES/preswitch" ]; then
336                 "$PROFILES/preswitch" "$PROFILE"
337         fi
338         if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
339                 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
340         fi
341
342         if [ -f "$CONF" ]; then
343                 echo " -> loading profile $PROFILE"
344                 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
345                         echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
346                 fi
347                 $LOAD_METHOD "$CONF"
348         else
349                 # Virtual profiles
350                 if [ $PROFILE = "common" ]; then
351                         echo " -> setting largest common mode in cloned mode"
352                         common_cfg_xrandr
353                 elif [ $PROFILE = "horizontal" ]; then
354                         echo " -> stacking all outputs horizontally at their largest modes"
355                         STACK="horizontal" stack_cfg_xrandr
356                 elif [ $PROFILE = "vertical" ]; then
357                         echo " -> stacking all outputs vertically at their largest modes"
358                         STACK="vertical" stack_cfg_xrandr
359                 fi
360         fi
361
362         if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
363                 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
364         fi
365         if [ -x "$PROFILES/postswitch" ]; then
366                 "$PROFILES/postswitch" "$PROFILE"
367         fi
368 }
369
370 help() {
371         cat <<EOH
372 Usage: $SCRIPTNAME [options]
373
374 -h, --help              get this small help
375 -c, --change            reload current setup
376 -s, --save <profile>    save your current setup to profile <profile>
377 -l, --load <profile>    load profile <profile>
378 -d, --default <profile> make profile <profile> the default profile
379 --force                 force (re)loading of a profile
380 --fingerprint           fingerprint your current hardware setup
381 --config                dump your current xrandr setup
382
383  To prevent a profile from being loaded, place a script call "block" in its
384  directory. The script is evaluated before the screen setup is inspected, and
385  in case of it returning a value of 0 the profile is skipped. This can be used
386  to query the status of a docking station you are about to leave.
387
388  If no suitable profile can be identified, the current configuration is kept.
389  To change this behaviour and switch to a fallback configuration, specify
390  --default <profile>.
391
392  Another script called "postswitch "can be placed in the directory
393  ~/.autorandr as well as in any profile directories: The scripts are executed
394  after a mode switch has taken place and can notify window managers.
395
396  When called by the name "autodisper" or "auto-disper", the script uses "disper"
397  instead of "xrandr" to detect, configure and save the display configuration.
398
399  If xrandr is used, the following virtual configurations are available:
400 ${RESERVED_PROFILE_NAMES}
401
402 EOH
403         exit
404 }
405 # process parameters
406 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
407 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
408 eval set -- "$OPTS"
409
410 while true; do
411         case "$1" in
412                 -c|--change) CHANGE_PROFILE=1; shift ;;
413                 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
414                 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
415                 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
416                 -h|--help) help ;;
417                 --force) FORCE_LOAD=1; shift ;;
418                 --fingerprint) setup_fp; exit 0;;
419                 --config) current_cfg; exit 0;;
420                 --) shift; break ;;
421                 *) echo "Error: $1"; exit 1;;
422         esac
423 done
424
425 CURRENT_SETUP="$(setup_fp)"
426
427 if [ -n "$SAVE_PROFILE" ]; then
428         if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
429                 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
430                 exit 1
431         fi
432         echo "Saving current configuration as profile '${SAVE_PROFILE}'"
433         mkdir -p "$PROFILES/$SAVE_PROFILE"
434         echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
435         $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
436         exit 0
437 fi
438
439 if [ -n "$LOAD_PROFILE" ]; then
440         CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
441         exit $?
442 fi
443
444 for SETUP_FILE in $PROFILES/*/setup; do
445         if ! [ -e $SETUP_FILE ]; then
446                 break
447         fi
448         PROFILE="$(basename $(dirname "$SETUP_FILE"))"
449         echo -n "$PROFILE"
450
451         if blocked "$PROFILE"; then
452                 echo " (blocked)"
453                 continue
454         fi
455
456         # This sed command is for compatibility with old versions that did not try
457         # to normalize device names
458         FILE_SETUP="$(sed -re 's#-([A-Z]-)?##g; s#card[0-9]##;' "$PROFILES/$PROFILE/setup")"
459         # Detect the md5sum in fingerprint files created using the old sysfs fingerprinting
460         # If it is detected, output a warning and calculate the legacy variant of the current
461         # setup.
462         if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
463                 echo -n " (Obsolete fingerprint format. Please update using --save.) "
464
465                 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
466                         LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
467                                 echo -n "${DEVICE} "
468                                 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
469                         done)"
470                 fi
471                 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
472                 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
473                         CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
474                 fi
475         fi
476
477         if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
478                 echo " (detected)"
479                 if [ "$CHANGE_PROFILE" -eq 1 ]; then
480                         if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
481                                 load "$PROFILE"
482                         fi
483                 fi
484                 # found the profile, exit with success
485                 exit 0
486         else
487                 echo ""
488         fi
489 done
490
491 # we did not find the profile, load default
492 if [ -n "$DEFAULT_PROFILE" ]; then
493         echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
494         load "$DEFAULT_PROFILE"
495 fi
496 exit 1
497
498 # Local Variables:
499 # tab-width: 8
500 # sh-basic-offset: 8
501 # sh-indentation: 8
502 # indent-tabs-mode: t
503 # End: