]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blob - autorandr
Revert "Removed non-trivial code by Stefan Tomanek"
[deb_pkgs/autorandr.git] / autorandr
1 #!/bin/sh
2 #
3 # Automatically select a display configuration based on connected devices
4 #
5 # Copyright (c) 2013 Stefan Tomanek <stefan.tomanek@wertarbyte.de>
6 #
7 #
8 #
9 # THE FOLLOWING LICENCE AGREEMENT IS PRELIMINARY AND INVALID UNTIL ISSUE #7 AT
10 #  https://github.com/phillipberndt/autorandr/issues/7
11 # HAS BEEN RESOLVED!
12 #
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 #
26 #
27 #
28 # How to use:
29 #
30 # Save your current display configuration and setup with:
31 #  $ autorandr --save mobile
32 #
33 # Connect an additional display, configure your setup and save it:
34 #  $ autorandr --save docked
35 #
36 # Now autorandr can detect which hardware setup is active:
37 #  $ autorandr
38 #    mobile
39 #    docked (detected)
40 #
41 # To automatically reload your setup, just append --change to the command line
42 #
43 # To manually load a profile, you can use the --load <profile> option.
44 #
45 # autorandr tries to avoid reloading an identical configuration. To force the
46 # (re)configuration, apply --force.
47 #
48 # To prevent a profile from being loaded, place a script call "block" in its
49 # directory. The script is evaluated before the screen setup is inspected, and
50 # in case of it returning a value of 0 the profile is skipped. This can be used
51 # to query the status of a docking station you are about to leave.
52 #
53 # If no suitable profile can be identified, the current configuration is kept.
54 # To change this behaviour and switch to a fallback configuration, specify
55 # --default <profile>
56 #
57 # Another script called "postswitch "can be placed in the directory
58 # ~/.autorandr as well as in all profile directories: The scripts are executed
59 # after a mode switch has taken place and can notify window managers or other
60 # applications about it.
61 #
62 #
63 # While the script uses xrandr by default, calling it by the name "autodisper"
64 # or "auto-disper" forces it to use the "disper" utility, which is useful for
65 # controlling nvidia chipsets. The formats for fingerprinting the current setup
66 # and saving/loading the current configuration are adjusted accordingly.
67
68 XRANDR=/usr/bin/xrandr
69 DISPER=/usr/bin/disper
70 XDPYINFO=/usr/bin/xdpyinfo
71 PROFILES=~/.autorandr
72 CONFIG=~/.autorandr.conf
73 RESERVED_PROFILE_NAMES=`cat <<EOF
74  common     Clone all connected outputs at the largest common resolution
75  horizontal Stack all connected outputs horizontally at their largest resolution
76  vertical   Stack all connected outputs vertically at their largest resolution
77 EOF`
78
79 CHANGE_PROFILE=0
80 FORCE_LOAD=0
81 DEFAULT_PROFILE=""
82 SAVE_PROFILE=""
83
84 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
85 CURRENT_CFG_METHOD="current_cfg_xrandr"
86 LOAD_METHOD="load_cfg_xrandr"
87
88 SCRIPTNAME="$(basename $0)"
89 # when called as autodisper/auto-disper, we assume different defaults
90 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
91         echo "Assuming disper defaults..." >&2
92         FP_METHODS="setup_fp_disper"
93         CURRENT_CFG_METHOD="current_cfg_disper"
94         LOAD_METHOD="load_cfg_disper"
95 fi
96
97 if [ -f $CONFIG ]; then
98         echo "Loading configuration from '$CONFIG'" >&2
99         . $CONFIG
100 fi
101
102 setup_fp_xrandr_edid() {
103         $XRANDR -q --verbose | awk '
104         ORS="";
105         / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
106         /^[[:blank:]]+EDID:/ {
107                 print DEVICE
108                 DEVICE=""
109                 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
110                         print $1;
111                 }
112                 print "\n";
113         }
114         END {
115                 print "\n";
116         }
117         '
118 }
119
120 setup_fp_sysfs_edid() {
121         which xxd >/dev/null 2>&1 || return
122         $XRANDR -q > /dev/null
123         for DEVICE in /sys/class/drm/card*-*; do
124                 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
125                 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
126                         xxd -c 256 -ps "${DEVICE}/edid" | awk 'ORS=""; /.+/ { print; }'
127                 echo
128         done
129 }
130
131 setup_fp_disper() {
132         $DISPER -l | grep '^display '
133 }
134
135 setup_fp() {
136         local FP="";
137         for M in $FP_METHODS; do
138                 FP="$($M)"
139                 if [ -n "$FP" ]; then
140                         break
141                 fi
142         done
143         if [ -z "$FP" ]; then
144                 echo "Unable to fingerprint display configuration" >&2
145                 return
146         fi
147         echo "$FINGERPRINT" | sort
148 }
149
150 current_cfg_xrandr() {
151         local PRIMARY_SETUP="";
152         if [ -x "$XDPYINFO" ]; then
153                 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^  head #0:/ {printf $3 $5}')"
154         fi
155         $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
156         # display is connected and has a mode
157         /^[^ ]+ connected [^(]/ {
158                 print "output "$1;
159                 if ($3 == "primary") {
160                         print $3
161                         split($4, A, "+")
162                         $4=$5
163                 }
164                 else {
165                         split($3, A, "+");
166                         if (A[1] A[2] "," A[3] == primary_setup)
167                                 print "primary";
168                 }
169                 if (($4 == "left") || ($4 == "right")) {
170                         split(A[1], B, "x");
171                         A[1] = B[2]"x"B[1];
172                 }
173                 print "mode "A[1];
174                 print "pos "A[2]"x"A[3];
175                 if ($4 !~ /^\(/) {
176                         print "rotate "$4;
177                 }
178                 else {
179                         print "rotate normal";
180                 }
181                 next;
182         }
183         # disconnected or disabled displays
184         /^[^ ]+ (dis)?connected / ||
185         /^[^ ]+ unknown connection / {
186                 print "output "$1;
187                 print "off";
188                 next;
189         }'
190 }
191
192 current_cfg_disper() {
193         $DISPER -p
194 }
195
196 common_cfg_xrandr() {
197         $XRANDR -q | awk '
198         # variables:
199         #   output: current output
200         #   outputlist: space sep list of all outputs
201         #   outputarr: array of all connected outputs
202         #   outputarrsize: number of connected outputs
203         #   modelist[800x600]: space sep list of outputs supporting mode
204         # display is connected
205         /^[^ ]+ connected / {
206             output=$1;
207             outputlist=outputlist " " output
208             outputarr[outputarrsize++]=output
209         }
210         # disconnected or disabled displays
211         /^[^ ]+ disconnected / ||
212         /^[^ ]+ unknown connection / {
213             print "output " $1;
214             print "off";
215         }
216         # modes available on a screen
217         /^   [0-9]+x[0-9]+/ {
218             modelist[$1]=modelist[$1] " " output
219         }
220         END {
221             # find common mode with largest screen area
222             for (m in modelist) {
223                 if (modelist[m] == outputlist) {
224                     # calculate area of resolution
225                     split(m, wh, "x");
226                     if (wh[1]*wh[2] >= maxdim) {
227                         maxdim=wh[1]*wh[2]
228                         maxmode=m
229                     }
230                 }
231             }
232             if (maxmode) {
233                 for (i in outputarr) {
234                     print "output " outputarr[i];
235                     print "mode " maxmode;
236                     print "pos 0x0";
237                     if (i > 0) {
238                         print "same-as " outputarr[0]
239                     }
240                 }
241             }
242         }' \
243                 | load_cfg_xrandr -
244 }
245
246 stack_cfg_xrandr() {
247         $XRANDR -q | awk -v stack="${STACK}" '
248         # variables:
249         #   stack: "horizontal" (anything except vertical) or "vertical"
250         #   output: current output
251         #   firstmode: pick first mode after output
252         #   posX,posY: position of the next output
253         BEGIN {
254             posX=posY=0
255         }
256         # display is connected
257         /^[^ ]+ connected / {
258             output=$1;
259             print "output " $1;
260             firstmode=1
261         }
262         # disconnected or disabled displays
263         /^[^ ]+ disconnected / ||
264         /^[^ ]+ unknown connection / {
265             print "output " $1;
266             print "off";
267         }
268         # modes available on a screen, but pick only the first
269         /^   [0-9]+x[0-9]+/ {
270             if (!firstmode) next;
271             firstmode=0
272             # output mode at current virtual desktop pos
273             print "mode " $1;
274             print "pos " posX "x" posY;
275             # calculate position of next output
276             split($1, wh, "x");
277             if (stack == "vertical")
278                 posY += wh[2];
279             else
280                 posX += wh[1];
281         }' \
282                 | load_cfg_xrandr -
283 }
284
285 current_cfg() {
286         $CURRENT_CFG_METHOD;
287 }
288
289 blocked() {
290         local PROFILE="$1"
291         [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
292
293         "$PROFILES/$PROFILE/block" "$PROFILE"
294 }
295
296 config_equal() {
297         local PROFILE="$1"
298         if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
299                 echo "Config already loaded"
300                 return 0
301         else
302                 return 1
303         fi
304 }
305
306 load_cfg_xrandr() {
307         # sed 1: Prefix arguments with "--"
308         # sed 2: Merge arguments into one line per output
309         # sed 3:  * Merge all --off outputs into the first line
310         #         * Place the output with --pos 0x0 on the second line
311         #         * Remaining outputs are appended as they appear
312         #         * Keep everything in hold buffer until the last line
313         # sed 4: Remove empty lines caused by G and H on empty hold buffer
314         # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
315         sed 's/^/--/' "$1" | sed -e '
316                 :START
317                 /\n--output/{P;D}
318                 s/\n/ /
319                 N;bSTART' | sed -e '
320                         / --off/{
321                                 G
322                                 # Merge with next line if it contains --off
323                                 s/\n\([^\n]* --off\)/ \1/
324                                 h
325                                 $!d;b
326                         }
327                         / --pos 0x0/{
328                                 G
329                                 # Swap lines 1 and 2 if --off is found
330                                 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
331                                 h
332                                 $!d;b
333                         }
334                         H
335                         $!d
336                         x' | sed -e '
337                                 /./ !d' | sed -e '
338                                         /--mode/{ N; s/\n/ /; }
339                                 ' | xargs -L 1 $XRANDR
340 }
341
342 load_cfg_disper() {
343         $DISPER -i < "$1"
344 }
345
346 load() {
347         local PROFILE="$1"
348         local CONF="$PROFILES/$PROFILE/config"
349         local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
350
351         if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
352                 echo " -> Error: Profile '$PROFILE' does not exist." >&2
353                 return
354         fi
355
356         if [ -x "$PROFILES/preswitch" ]; then
357                 "$PROFILES/preswitch" "$PROFILE"
358         fi
359         if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
360                 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
361         fi
362
363         if [ -f "$CONF" ]; then
364                 echo " -> loading profile $PROFILE"
365                 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
366                         echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
367                 fi
368                 $LOAD_METHOD "$CONF"
369         else
370                 # Virtual profiles
371                 if [ $PROFILE = "common" ]; then
372                         echo " -> setting largest common mode in cloned mode"
373                         common_cfg_xrandr
374                 elif [ $PROFILE = "horizontal" ]; then
375                         echo " -> stacking all outputs horizontally at their largest modes"
376                         STACK="horizontal" stack_cfg_xrandr
377                 elif [ $PROFILE = "vertical" ]; then
378                         echo " -> stacking all outputs vertically at their largest modes"
379                         STACK="vertical" stack_cfg_xrandr
380                 fi
381         fi
382
383         if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
384                 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
385         fi
386         if [ -x "$PROFILES/postswitch" ]; then
387                 "$PROFILES/postswitch" "$PROFILE"
388         fi
389 }
390
391 help() {
392         cat <<EOH
393 Usage: $SCRIPTNAME [options]
394
395 -h, --help              get this small help
396 -c, --change            reload current setup
397 -s, --save <profile>    save your current setup to profile <profile>
398 -l, --load <profile>    load profile <profile>
399 -d, --default <profile> make profile <profile> the default profile
400 --force                 force (re)loading of a profile
401 --fingerprint           fingerprint your current hardware setup
402 --config                dump your current xrandr setup
403
404  To prevent a profile from being loaded, place a script call "block" in its
405  directory. The script is evaluated before the screen setup is inspected, and
406  in case of it returning a value of 0 the profile is skipped. This can be used
407  to query the status of a docking station you are about to leave.
408
409  If no suitable profile can be identified, the current configuration is kept.
410  To change this behaviour and switch to a fallback configuration, specify
411  --default <profile>.
412
413  Another script called "postswitch "can be placed in the directory
414  ~/.autorandr as well as in any profile directories: The scripts are executed
415  after a mode switch has taken place and can notify window managers.
416
417  When called by the name "autodisper" or "auto-disper", the script uses "disper"
418  instead of "xrandr" to detect, configure and save the display configuration.
419
420  If xrandr is used, the following virtual configurations are available:
421 ${RESERVED_PROFILE_NAMES}
422
423 EOH
424         exit
425 }
426 # process parameters
427 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
428 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
429 eval set -- "$OPTS"
430
431 while true; do
432         case "$1" in
433                 -c|--change) CHANGE_PROFILE=1; shift ;;
434                 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
435                 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
436                 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
437                 -h|--help) help ;;
438                 --force) FORCE_LOAD=1; shift ;;
439                 --fingerprint) setup_fp; exit 0;;
440                 --config) current_cfg; exit 0;;
441                 --) shift; break ;;
442                 *) echo "Error: $1"; exit 1;;
443         esac
444 done
445
446 CURRENT_SETUP="$(setup_fp)"
447
448 if [ -n "$SAVE_PROFILE" ]; then
449         if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
450                 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
451                 exit 1
452         fi
453         echo "Saving current configuration as profile '${SAVE_PROFILE}'"
454         mkdir -p "$PROFILES/$SAVE_PROFILE"
455         echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
456         $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
457         exit 0
458 fi
459
460 if [ -n "$LOAD_PROFILE" ]; then
461         CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
462         exit $?
463 fi
464
465 for SETUP_FILE in $PROFILES/*/setup; do
466         if ! [ -e $SETUP_FILE ]; then
467                 break
468         fi
469         PROFILE="$(basename $(dirname "$SETUP_FILE"))"
470         echo -n "$PROFILE"
471
472         if blocked "$PROFILE"; then
473                 echo " (blocked)"
474                 continue
475         fi
476
477         # This sed command is for compatibility with old versions that did not try
478         # to normalize device names
479         FILE_SETUP="$(sed -re 's#-([A-Z]-)?##g; s#card[0-9]##;' "$PROFILES/$PROFILE/setup")"
480         # Detect the md5sum in fingerprint files created using the old sysfs fingerprinting
481         # If it is detected, output a warning and calculate the legacy variant of the current
482         # setup.
483         if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
484                 echo -n " (Obsolete fingerprint format. Please update using --save.) "
485
486                 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
487                         LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
488                                 echo -n "${DEVICE} "
489                                 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
490                         done)"
491                 fi
492                 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
493                 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
494                         CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
495                 fi
496         fi
497
498         if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
499                 echo " (detected)"
500                 if [ "$CHANGE_PROFILE" -eq 1 ]; then
501                         if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
502                                 load "$PROFILE"
503                         fi
504                 fi
505                 # found the profile, exit with success
506                 exit 0
507         else
508                 echo ""
509         fi
510 done
511
512 # we did not find the profile, load default
513 if [ -n "$DEFAULT_PROFILE" ]; then
514         echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
515         load "$DEFAULT_PROFILE"
516 fi
517 exit 1
518
519 # Local Variables:
520 # tab-width: 8
521 # sh-basic-offset: 8
522 # sh-indentation: 8
523 # indent-tabs-mode: t
524 # End: