3 # Automatically select a display configuration based on connected devices
5 # Copyright (c) 2013 Stefan Tomanek <stefan.tomanek@wertarbyte.de>
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # Save your current display configuration and setup with:
25 # $ autorandr --save mobile
27 # Connect an additional display, configure your setup and save it:
28 # $ autorandr --save docked
30 # Now autorandr can detect which hardware setup is active:
35 # To automatically reload your setup, just append --change to the command line
37 # To manually load a profile, you can use the --load <profile> option.
39 # autorandr tries to avoid reloading an identical configuration. To force the
40 # (re)configuration, apply --force.
42 # To prevent a profile from being loaded, place a script call "block" in its
43 # directory. The script is evaluated before the screen setup is inspected, and
44 # in case of it returning a value of 0 the profile is skipped. This can be used
45 # to query the status of a docking station you are about to leave.
47 # If no suitable profile can be identified, the current configuration is kept.
48 # To change this behaviour and switch to a fallback configuration, specify
51 # Another script called "postswitch "can be placed in the directory
52 # ~/.autorandr as well as in all profile directories: The scripts are executed
53 # after a mode switch has taken place and can notify window managers or other
54 # applications about it.
57 # While the script uses xrandr by default, calling it by the name "autodisper"
58 # or "auto-disper" forces it to use the "disper" utility, which is useful for
59 # controlling nvidia chipsets. The formats for fingerprinting the current setup
60 # and saving/loading the current configuration are adjusted accordingly.
62 XRANDR=/usr/bin/xrandr
63 DISPER=/usr/bin/disper
64 XDPYINFO=/usr/bin/xdpyinfo
66 CONFIG=~/.autorandr.conf
67 RESERVED_PROFILE_NAMES=`cat <<EOF
68 common Clone all connected outputs at the largest common resolution
69 horizontal Stack all connected outputs horizontally at their largest resolution
70 vertical Stack all connected outputs vertically at their largest resolution
78 FP_METHODS="setup_fp_xrandr_edid setup_fp_sysfs_edid"
79 CURRENT_CFG_METHOD="current_cfg_xrandr"
80 LOAD_METHOD="load_cfg_xrandr"
82 SCRIPTNAME="$(basename $0)"
83 # when called as autodisper/auto-disper, we assume different defaults
84 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
85 echo "Assuming disper defaults..." >&2
86 FP_METHODS="setup_fp_disper"
87 CURRENT_CFG_METHOD="current_cfg_disper"
88 LOAD_METHOD="load_cfg_disper"
91 if [ -f $CONFIG ]; then
92 echo "Loading configuration from '$CONFIG'" >&2
96 if ! which xxd 2>&1 >/dev/null; then
98 # xxd replacement for systems without vim. Ugly, but the only simple
99 # version that both Python 2 and 3 understand that I could come up with.
100 # awk can only do one direction, it has no ord() function.
101 if [ "$1" = "-r" ]; then
102 python -c "import binascii, sys; getattr(sys.stdout, 'buffer', sys.stdout).write(binascii.unhexlify(getattr(sys.stdin, 'buffer', sys.stdin).read()))"
104 python -c "import binascii, sys; getattr(sys.stdout, 'buffer', sys.stdout).write(binascii.hexlify(getattr(sys.stdin, 'buffer', sys.stdin).read()))"
110 setup_fp_xrandr_edid() {
111 $XRANDR -q --verbose | awk '
113 / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
114 /^[[:blank:]]+EDID:/ {
117 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
128 setup_fp_sysfs_edid() {
129 $XRANDR -q > /dev/null
130 for DEVICE in /sys/class/drm/card*-*; do
131 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
132 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
133 cat "${DEVICE}/edid" | xxd -c 256 -ps | awk 'ORS=""; /.+/ { print; }'
139 $DISPER -l | grep '^display '
144 for M in $FP_METHODS; do
146 if [ -n "$FP" ]; then
150 if [ -z "$FP" ]; then
151 echo "Unable to fingerprint display configuration" >&2
157 current_cfg_xrandr() {
158 local PRIMARY_SETUP="";
159 if [ -x "$XDPYINFO" ]; then
160 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
162 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
163 # display is connected and has a mode
164 /^[^ ]+ connected [^(]/ {
167 if ($3 == "primary") {
174 if (A[1] A[2] "," A[3] == primary_setup)
177 if (($4 == "left") || ($4 == "right")) {
182 print "pos "A[2]"x"A[3];
187 print "rotate normal";
191 / [0-9]+x[0-9]+ .+/ {
193 for (n=1; n<10; n++) {
194 if($n ~ /[0-9]+\.[0-9]+\*/) {
195 print "rate " gensub(/(+|\*)/, "", "g", $n);
200 # disconnected or disabled displays
201 /^[^ ]+ (dis)?connected / ||
202 /^[^ ]+ unknown connection / {
210 current_cfg_disper() {
214 common_cfg_xrandr() {
217 # output: current output
218 # outputlist: space sep list of all outputs
219 # outputarr: array of all connected outputs
220 # outputarrsize: number of connected outputs
221 # modelist[800x600]: space sep list of outputs supporting mode
222 # display is connected
223 /^[^ ]+ connected / {
225 outputlist=outputlist " " output
226 outputarr[outputarrsize++]=output
228 # disconnected or disabled displays
229 /^[^ ]+ disconnected / ||
230 /^[^ ]+ unknown connection / {
234 # modes available on a screen
236 modelist[$1]=modelist[$1] " " output
239 # find common mode with largest screen area
240 for (m in modelist) {
241 if (modelist[m] == outputlist) {
242 # calculate area of resolution
244 if (wh[1]*wh[2] >= maxdim) {
251 for (i in outputarr) {
252 print "output " outputarr[i];
253 print "mode " maxmode;
256 print "same-as " outputarr[0]
265 $XRANDR -q | awk -v stack="${STACK}" '
267 # stack: "horizontal" (anything except vertical) or "vertical"
268 # output: current output
269 # firstmode: pick first mode after output
270 # posX,posY: position of the next output
274 # display is connected
275 /^[^ ]+ connected / {
280 # disconnected or disabled displays
281 /^[^ ]+ disconnected / ||
282 /^[^ ]+ unknown connection / {
286 # modes available on a screen, but pick only the first
288 if (!firstmode) next;
290 # output mode at current virtual desktop pos
292 print "pos " posX "x" posY;
293 # calculate position of next output
295 if (stack == "vertical")
309 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
311 "$PROFILES/$PROFILE/block" "$PROFILE"
316 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
317 echo "Config already loaded"
325 # sed 1: Prefix arguments with "--"
326 # sed 2: Merge arguments into one line per output
327 # sed 3: * Merge all --off outputs into the first line
328 # * Place the output with --pos 0x0 on the second line
329 # * Remaining outputs are appended as they appear
330 # * Keep everything in hold buffer until the last line
331 # sed 4: Remove empty lines caused by G and H on empty hold buffer
332 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
333 sed 's/^/--/' "$1" | sed -e '
340 # Merge with next line if it contains --off
341 s/\n\([^\n]* --off\)/ \1/
347 # Swap lines 1 and 2 if --off is found
348 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
356 /--mode/{ N; s/\n/ /; }
357 ' | xargs -L 1 $XRANDR
366 local CONF="$PROFILES/$PROFILE/config"
367 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
369 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
370 echo " -> Error: Profile '$PROFILE' does not exist." >&2
374 if [ -x "$PROFILES/preswitch" ]; then
375 "$PROFILES/preswitch" "$PROFILE"
377 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
378 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
381 if [ -f "$CONF" ]; then
382 echo " -> loading profile $PROFILE"
383 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
384 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
389 if [ $PROFILE = "common" ]; then
390 echo " -> setting largest common mode in cloned mode"
392 elif [ $PROFILE = "horizontal" ]; then
393 echo " -> stacking all outputs horizontally at their largest modes"
394 STACK="horizontal" stack_cfg_xrandr
395 elif [ $PROFILE = "vertical" ]; then
396 echo " -> stacking all outputs vertically at their largest modes"
397 STACK="vertical" stack_cfg_xrandr
401 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
402 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
404 if [ -x "$PROFILES/postswitch" ]; then
405 "$PROFILES/postswitch" "$PROFILE"
411 Usage: $SCRIPTNAME [options]
413 -h, --help get this small help
414 -c, --change reload current setup
415 -s, --save <profile> save your current setup to profile <profile>
416 -l, --load <profile> load profile <profile>
417 -d, --default <profile> make profile <profile> the default profile
418 --force force (re)loading of a profile
419 --fingerprint fingerprint your current hardware setup
420 --config dump your current xrandr setup
422 To prevent a profile from being loaded, place a script call "block" in its
423 directory. The script is evaluated before the screen setup is inspected, and
424 in case of it returning a value of 0 the profile is skipped. This can be used
425 to query the status of a docking station you are about to leave.
427 If no suitable profile can be identified, the current configuration is kept.
428 To change this behaviour and switch to a fallback configuration, specify
431 Another script called "postswitch "can be placed in the directory
432 ~/.autorandr as well as in any profile directories: The scripts are executed
433 after a mode switch has taken place and can notify window managers.
435 When called by the name "autodisper" or "auto-disper", the script uses "disper"
436 instead of "xrandr" to detect, configure and save the display configuration.
438 If xrandr is used, the following virtual configurations are available:
439 ${RESERVED_PROFILE_NAMES}
445 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
446 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
451 -c|--change) CHANGE_PROFILE=1; shift ;;
452 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
453 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
454 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
456 --force) FORCE_LOAD=1; shift ;;
457 --fingerprint) setup_fp; exit 0;;
458 --config) current_cfg; exit 0;;
460 *) echo "Error: $1"; exit 1;;
464 CURRENT_SETUP="$(setup_fp)"
466 if [ -n "$SAVE_PROFILE" ]; then
467 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
468 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
471 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
472 mkdir -p "$PROFILES/$SAVE_PROFILE"
473 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
474 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
478 if [ -n "$LOAD_PROFILE" ]; then
479 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
483 for SETUP_FILE in $PROFILES/*/setup; do
484 if ! [ -e $SETUP_FILE ]; then
487 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
490 if blocked "$PROFILE"; then
495 # This sed command is for compatibility with old versions that did not try
496 # to normalize device names
497 FILE_SETUP="$(sed -re 's#-([A-Z]-)?##g; s#card[0-9]##;' "$PROFILES/$PROFILE/setup")"
498 # Detect the md5sum in fingerprint files created using the old sysfs fingerprinting
499 # If it is detected, output a warning and calculate the legacy variant of the current
501 if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
502 echo -n " (Obsolete fingerprint format. Please update using --save.) "
504 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
505 LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
507 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
510 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
511 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
512 CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
516 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
518 if [ "$CHANGE_PROFILE" -eq 1 ]; then
519 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
523 # found the profile, exit with success
530 # we did not find the profile, load default
531 if [ -n "$DEFAULT_PROFILE" ]; then
532 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
533 load "$DEFAULT_PROFILE"
541 # indent-tabs-mode: t