]> git.donarmstrong.com Git - deb_pkgs/autorandr.git/blob - autorandr
Inform users about virtual configurations, prevent name collisions, prefer existing...
[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 Set all connected outputs to the largest common resolution in cloned mode
54 EOF`
55
56 CHANGE_PROFILE=0
57 FORCE_LOAD=0
58 DEFAULT_PROFILE=""
59 SAVE_PROFILE=""
60
61 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
62 CURRENT_CFG_METHOD="current_cfg_xrandr"
63 LOAD_METHOD="load_cfg_xrandr"
64
65 SCRIPTNAME="$(basename $0)"
66 # when called as autodisper/auto-disper, we assume different defaults
67 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
68         echo "Assuming disper defaults..." >&2
69         FP_METHODS="setup_fp_disper"
70         CURRENT_CFG_METHOD="current_cfg_disper"
71         LOAD_METHOD="load_cfg_disper"
72 fi
73
74 if [ -f $CONFIG ]; then
75         echo "Loading configuration from '$CONFIG'" >&2
76         . $CONFIG
77 fi
78
79 setup_fp_xrandr_edid() {
80         $XRANDR -q --verbose | awk '
81         /^[^ ]+ (dis)?connected / { DEV=$1; }
82         $1 ~ /^[a-f0-9]+$/ { ID[DEV] = ID[DEV] $1 }
83         END { for (X in ID) { print X " " ID[X]; } }'
84 }
85
86 setup_fp_sysfs_edid() {
87         # xrandr triggers the reloading of EDID data
88         $XRANDR -q > /dev/null
89         # hash the EDIDs of all _connected_ devices
90         for P in /sys/class/drm/card*-*/; do
91                 # nothing found
92                 [ ! -d "$P" ] && continue
93                 if grep -q "^connected$" < "${P}status"; then
94                         echo -n "$(basename "$P") "
95                         md5sum ${P}edid | awk '{print $1}'
96                 fi
97         done
98 }
99
100 setup_fp_disper() {
101         $DISPER -l | grep '^display '
102 }
103
104 setup_fp() {
105         local FP="";
106         for M in $FP_METHODS; do
107                 FP="$($M)"
108                 if [ -n "$FP" ]; then
109                         break
110                 fi
111         done
112         if [ -z "$FP" ]; then
113                 echo "Unable to fingerprint display configuration" >&2
114                 return
115         fi
116         echo "$FP"
117 }
118
119 current_cfg_xrandr() {
120         local PRIMARY_SETUP="";
121         if [ -x "$XDPYINFO" ]; then
122                 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^  head #0:/ {printf $3 $5}')"
123         fi
124         $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
125         # display is connected and has a mode
126         /^[^ ]+ connected [^(]/ {
127                 print "output "$1;
128                 if ($3 == "primary") {
129                         print $3
130                         split($4, A, "+")
131                         $4=$5
132                 }
133                 else {
134                         split($3, A, "+");
135                         if (A[1] A[2] "," A[3] == primary_setup)
136                                 print "primary";
137                 }
138                 if (($4 == "left") || ($4 == "right")) {
139                         split(A[1], B, "x");
140                         A[1] = B[2]"x"B[1];
141                 }
142                 print "mode "A[1];
143                 print "pos "A[2]"x"A[3];
144                 if ($4 !~ /^\(/) {
145                         print "rotate "$4;
146                 }
147                 else {
148                         print "rotate normal";
149                 }
150                 next;
151         }
152         # disconnected or disabled displays
153         /^[^ ]+ (dis)?connected / ||
154         /^[^ ]+ unknown connection / {
155                 print "output "$1;
156                 print "off";
157                 next;
158         }'
159 }
160
161 current_cfg_disper() {
162         $DISPER -p
163 }
164
165 common_cfg_xrandr() {
166         $XRANDR -q | awk '
167         # variables:
168         #   output: current output
169         #   outputlist: space sep list of all outputs
170         #   outputarr: array of all connected outputs
171         #   outputarrsize: number of connected outputs
172         #   modelist[800x600]: space sep list of outputs supporting mode
173         # display is connected
174         /^[^ ]+ connected / {
175             output=$1;
176             outputlist=outputlist " " output
177             outputarr[outputarrsize++]=output
178         }
179         # disconnected or disabled displays
180         /^[^ ]+ disconnected / ||
181         /^[^ ]+ unknown connection / {
182             print "output " $1;
183             print "off";
184         }
185         # modes available on a screen
186         /^   [0-9]+x[0-9]+/ {
187             modelist[$1]=modelist[$1] " " output
188         }
189         END {
190             # find common mode with largest screen area
191             for (m in modelist) {
192                 if (modelist[m] == outputlist) {
193                     # calculate area of resolution
194                     split(m, wh, "x");
195                     if (wh[1]*wh[2] >= maxdim) {
196                         maxdim=wh[1]*wh[2]
197                         maxmode=m
198                     }
199                 }
200             }
201             if (maxmode) {
202                 for (i in outputarr) {
203                     print "output " outputarr[i];
204                     print "mode " maxmode;
205                     print "pos 0x0";
206                     if (i > 0) {
207                         print "same-as " outputarr[0]
208                     }
209                 }
210             }
211         }' \
212                 | load_cfg_xrandr -
213 }
214
215 current_cfg() {
216         $CURRENT_CFG_METHOD;
217 }
218
219 blocked() {
220         local PROFILE="$1"
221         [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
222
223         "$PROFILES/$PROFILE/block" "$PROFILE"
224 }
225
226 config_equal() {
227         local PROFILE="$1"
228         if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
229                 echo "Config already loaded"
230                 return 0
231         else
232                 return 1
233         fi
234 }
235
236 load_cfg_xrandr() {
237         # sed 1: Prefix arguments with "--"
238         # sed 2: Merge arguments into one line per output
239         # sed 3: Merge into two lines, all --off outputs in the first one
240         sed 's/^/--/' "$1" | sed -e '
241                 :START
242                 /\n--output/{P;D}
243                 s/\n/ /
244                 N;bSTART' | sed -e '
245                         ### First line
246                         / --off/{
247                                 G
248                                 # Merge if next line contains --off
249                                 s/\n\([^\n]* --off\)/ \1/
250                                 h
251                                 $!d;b
252                         }
253                         ### Last line
254                         H;x
255                         # Merge if previous line contains --mode
256                         s/\(--mode [^\n]*\)\n/\1 /
257                         h
258                         $!d' | xargs -L 1 $XRANDR
259 }
260
261 load_cfg_disper() {
262         $DISPER -i < "$1"
263 }
264
265 load() {
266         local PROFILE="$1"
267         local CONF="$PROFILES/$PROFILE/config"
268         local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -q " $PROFILE   "`
269         [ -f "$CONF" -o -n $IS_VIRTUAL_PROFILE ] || return 1
270         if [ -x "$PROFILES/preswitch" ]; then
271                 "$PROFILES/preswitch" "$PROFILE"
272         fi
273         if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
274                 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
275         fi
276
277         if [ -f "$CONF" ]; then
278                 echo " -> loading profile $PROFILE"
279                 if [ -n $IS_VIRTUAL_PROFILE ]; then
280                         echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
281                 fi
282                 $LOAD_METHOD "$CONF"
283         else
284                 # Virtual profiles
285                 if [ $PROFILE = "common" ]; then
286                         echo " -> setting largest common mode"
287                         common_cfg_xrandr
288                 fi
289         fi
290
291         if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
292                 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
293         fi
294         if [ -x "$PROFILES/postswitch" ]; then
295                 "$PROFILES/postswitch" "$PROFILE"
296         fi
297 }
298
299 help() {
300         cat <<EOH
301 Usage: $SCRIPTNAME [options]
302
303 -h, --help              get this small help
304 -c, --change            reload current setup
305 -s, --save <profile>    save your current setup to profile <profile>
306 -l, --load <profile>    load profile <profile>
307 -d, --default <profile> make profile <profile> the default profile
308 --force                 force (re)loading of a profile
309 --fingerprint           fingerprint your current hardware setup
310 --config                dump your current xrandr setup
311
312  To prevent a profile from being loaded, place a script call "block" in its
313  directory. The script is evaluated before the screen setup is inspected, and
314  in case of it returning a value of 0 the profile is skipped. This can be used
315  to query the status of a docking station you are about to leave.
316
317  If no suitable profile can be identified, the current configuration is kept.
318  To change this behaviour and switch to a fallback configuration, specify
319  --default <profile>.
320
321  Another script called "postswitch "can be placed in the directory
322  ~/.autorandr as well as in any profile directories: The scripts are executed
323  after a mode switch has taken place and can notify window managers.
324
325  When called by the name "autodisper" or "auto-disper", the script uses "disper"
326  instead of "xrandr" to detect, configure and save the display configuration.
327
328  If xrandr is used, the following virtual configurations are available:
329  ${RESERVED_PROFILE_NAMES}
330
331 EOH
332         exit
333 }
334 # process parameters
335 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
336 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
337 eval set -- "$OPTS"
338
339 while true; do
340         case "$1" in
341                 -c|--change) CHANGE_PROFILE=1; shift ;;
342                 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
343                 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
344                 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
345                 -h|--help) help ;;
346                 --force) FORCE_LOAD=1; shift ;;
347                 --fingerprint) setup_fp; exit 0;;
348                 --config) current_cfg; exit 0;;
349                 --) shift; break ;;
350                 *) echo "Error: $1"; exit 1;;
351         esac
352 done
353
354 CURRENT_SETUP="$(setup_fp)"
355
356 if [ -n "$SAVE_PROFILE" ]; then
357         if echo "$RESERVED_PROFILE_NAMES" | grep -q " $SAVE_PROFILE     "; then
358                 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
359                 exit 1
360         fi
361         echo "Saving current configuration as profile '${SAVE_PROFILE}'"
362         mkdir -p "$PROFILES/$SAVE_PROFILE"
363         echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
364         $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
365         exit 0
366 fi
367
368 if [ -n "$LOAD_PROFILE" ]; then
369         CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
370         exit $?
371 fi
372
373 for SETUP_FILE in $PROFILES/*/setup; do
374         if ! [ -e $SETUP_FILE ]; then
375                 break
376         fi
377         PROFILE="$(basename $(dirname "$SETUP_FILE"))"
378         echo -n "$PROFILE"
379
380         if blocked "$PROFILE"; then
381                 echo " (blocked)"
382                 continue
383         fi
384
385         FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
386         if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
387                 echo " (detected)"
388                 if [ "$CHANGE_PROFILE" -eq 1 ]; then
389                         if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
390                                 load "$PROFILE"
391                         fi
392                 fi
393                 # found the profile, exit with success
394                 exit 0
395         else
396                 echo ""
397         fi
398 done
399
400 # we did not find the profile, load default
401 if [ -n "$DEFAULT_PROFILE" ]; then
402         echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
403         load "$DEFAULT_PROFILE"
404 fi
405 exit 1
406
407 # Local Variables:
408 # tab-width: 8
409 # sh-basic-offset: 8
410 # sh-indentation: 8
411 # indent-tabs-mode: t
412 # End: