3 # Automatically select a display configuration based on connected devices
5 # Copyright (c) 2013 Stefan Tomanek <stefan.tomanek@wertarbyte.de>
9 # THE FOLLOWING LICENCE AGREEMENT IS PRELIMINARY AND INVALID UNTIL ISSUE #7 AT
10 # https://github.com/phillipberndt/autorandr/issues/7
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.
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.
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/>.
30 # Save your current display configuration and setup with:
31 # $ autorandr --save mobile
33 # Connect an additional display, configure your setup and save it:
34 # $ autorandr --save docked
36 # Now autorandr can detect which hardware setup is active:
41 # To automatically reload your setup, just append --change to the command line
43 # To manually load a profile, you can use the --load <profile> option.
45 # autorandr tries to avoid reloading an identical configuration. To force the
46 # (re)configuration, apply --force.
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.
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
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.
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.
68 XRANDR=/usr/bin/xrandr
69 DISPER=/usr/bin/disper
70 XDPYINFO=/usr/bin/xdpyinfo
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
84 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
85 CURRENT_CFG_METHOD="current_cfg_xrandr"
86 LOAD_METHOD="load_cfg_xrandr"
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"
97 if [ -f $CONFIG ]; then
98 echo "Loading configuration from '$CONFIG'" >&2
102 if ! which xxd 2>&1 >/dev/null; then
104 # xxd replacement for systems without vim. Ugly, but the only simple
105 # version that both Python 2 and 3 understand that I could come up with.
106 # awk can only do one direction, it has no ord() function.
107 if [ "$1" = "-r" ]; then
108 python -c "import binascii, sys; getattr(sys.stdout, 'buffer', sys.stdout).write(binascii.unhexlify(getattr(sys.stdin, 'buffer', sys.stdin).read()))"
110 python -c "import binascii, sys; getattr(sys.stdout, 'buffer', sys.stdout).write(binascii.hexlify(getattr(sys.stdin, 'buffer', sys.stdin).read()))"
116 setup_fp_xrandr_edid() {
117 $XRANDR -q --verbose | awk '
119 / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
120 /^[[:blank:]]+EDID:/ {
123 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
134 setup_fp_sysfs_edid() {
135 $XRANDR -q > /dev/null
136 for DEVICE in /sys/class/drm/card*-*; do
137 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
138 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
139 cat "${DEVICE}/edid" | xxd -c 256 -ps | awk 'ORS=""; /.+/ { print; }'
145 $DISPER -l | grep '^display '
150 for M in $FP_METHODS; do
152 if [ -n "$FP" ]; then
156 if [ -z "$FP" ]; then
157 echo "Unable to fingerprint display configuration" >&2
163 current_cfg_xrandr() {
164 local PRIMARY_SETUP="";
165 if [ -x "$XDPYINFO" ]; then
166 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
168 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
169 # display is connected and has a mode
170 /^[^ ]+ connected [^(]/ {
172 if ($3 == "primary") {
179 if (A[1] A[2] "," A[3] == primary_setup)
182 if (($4 == "left") || ($4 == "right")) {
187 print "pos "A[2]"x"A[3];
192 print "rotate normal";
196 # disconnected or disabled displays
197 /^[^ ]+ (dis)?connected / ||
198 /^[^ ]+ unknown connection / {
205 current_cfg_disper() {
209 common_cfg_xrandr() {
212 # output: current output
213 # outputlist: space sep list of all outputs
214 # outputarr: array of all connected outputs
215 # outputarrsize: number of connected outputs
216 # modelist[800x600]: space sep list of outputs supporting mode
217 # display is connected
218 /^[^ ]+ connected / {
220 outputlist=outputlist " " output
221 outputarr[outputarrsize++]=output
223 # disconnected or disabled displays
224 /^[^ ]+ disconnected / ||
225 /^[^ ]+ unknown connection / {
229 # modes available on a screen
231 modelist[$1]=modelist[$1] " " output
234 # find common mode with largest screen area
235 for (m in modelist) {
236 if (modelist[m] == outputlist) {
237 # calculate area of resolution
239 if (wh[1]*wh[2] >= maxdim) {
246 for (i in outputarr) {
247 print "output " outputarr[i];
248 print "mode " maxmode;
251 print "same-as " outputarr[0]
260 $XRANDR -q | awk -v stack="${STACK}" '
262 # stack: "horizontal" (anything except vertical) or "vertical"
263 # output: current output
264 # firstmode: pick first mode after output
265 # posX,posY: position of the next output
269 # display is connected
270 /^[^ ]+ connected / {
275 # disconnected or disabled displays
276 /^[^ ]+ disconnected / ||
277 /^[^ ]+ unknown connection / {
281 # modes available on a screen, but pick only the first
283 if (!firstmode) next;
285 # output mode at current virtual desktop pos
287 print "pos " posX "x" posY;
288 # calculate position of next output
290 if (stack == "vertical")
304 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
306 "$PROFILES/$PROFILE/block" "$PROFILE"
311 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
312 echo "Config already loaded"
320 # sed 1: Prefix arguments with "--"
321 # sed 2: Merge arguments into one line per output
322 # sed 3: * Merge all --off outputs into the first line
323 # * Place the output with --pos 0x0 on the second line
324 # * Remaining outputs are appended as they appear
325 # * Keep everything in hold buffer until the last line
326 # sed 4: Remove empty lines caused by G and H on empty hold buffer
327 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
328 sed 's/^/--/' "$1" | sed -e '
335 # Merge with next line if it contains --off
336 s/\n\([^\n]* --off\)/ \1/
342 # Swap lines 1 and 2 if --off is found
343 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
351 /--mode/{ N; s/\n/ /; }
352 ' | xargs -L 1 $XRANDR
361 local CONF="$PROFILES/$PROFILE/config"
362 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
364 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
365 echo " -> Error: Profile '$PROFILE' does not exist." >&2
369 if [ -x "$PROFILES/preswitch" ]; then
370 "$PROFILES/preswitch" "$PROFILE"
372 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
373 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
376 if [ -f "$CONF" ]; then
377 echo " -> loading profile $PROFILE"
378 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
379 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
384 if [ $PROFILE = "common" ]; then
385 echo " -> setting largest common mode in cloned mode"
387 elif [ $PROFILE = "horizontal" ]; then
388 echo " -> stacking all outputs horizontally at their largest modes"
389 STACK="horizontal" stack_cfg_xrandr
390 elif [ $PROFILE = "vertical" ]; then
391 echo " -> stacking all outputs vertically at their largest modes"
392 STACK="vertical" stack_cfg_xrandr
396 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
397 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
399 if [ -x "$PROFILES/postswitch" ]; then
400 "$PROFILES/postswitch" "$PROFILE"
406 Usage: $SCRIPTNAME [options]
408 -h, --help get this small help
409 -c, --change reload current setup
410 -s, --save <profile> save your current setup to profile <profile>
411 -l, --load <profile> load profile <profile>
412 -d, --default <profile> make profile <profile> the default profile
413 --force force (re)loading of a profile
414 --fingerprint fingerprint your current hardware setup
415 --config dump your current xrandr setup
417 To prevent a profile from being loaded, place a script call "block" in its
418 directory. The script is evaluated before the screen setup is inspected, and
419 in case of it returning a value of 0 the profile is skipped. This can be used
420 to query the status of a docking station you are about to leave.
422 If no suitable profile can be identified, the current configuration is kept.
423 To change this behaviour and switch to a fallback configuration, specify
426 Another script called "postswitch "can be placed in the directory
427 ~/.autorandr as well as in any profile directories: The scripts are executed
428 after a mode switch has taken place and can notify window managers.
430 When called by the name "autodisper" or "auto-disper", the script uses "disper"
431 instead of "xrandr" to detect, configure and save the display configuration.
433 If xrandr is used, the following virtual configurations are available:
434 ${RESERVED_PROFILE_NAMES}
440 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
441 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
446 -c|--change) CHANGE_PROFILE=1; shift ;;
447 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
448 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
449 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
451 --force) FORCE_LOAD=1; shift ;;
452 --fingerprint) setup_fp; exit 0;;
453 --config) current_cfg; exit 0;;
455 *) echo "Error: $1"; exit 1;;
459 CURRENT_SETUP="$(setup_fp)"
461 if [ -n "$SAVE_PROFILE" ]; then
462 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
463 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
466 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
467 mkdir -p "$PROFILES/$SAVE_PROFILE"
468 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
469 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
473 if [ -n "$LOAD_PROFILE" ]; then
474 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
478 for SETUP_FILE in $PROFILES/*/setup; do
479 if ! [ -e $SETUP_FILE ]; then
482 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
485 if blocked "$PROFILE"; then
490 # This sed command is for compatibility with old versions that did not try
491 # to normalize device names
492 FILE_SETUP="$(sed -re 's#-([A-Z]-)?##g; s#card[0-9]##;' "$PROFILES/$PROFILE/setup")"
493 # Detect the md5sum in fingerprint files created using the old sysfs fingerprinting
494 # If it is detected, output a warning and calculate the legacy variant of the current
496 if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
497 echo -n " (Obsolete fingerprint format. Please update using --save.) "
499 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
500 LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
502 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
505 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
506 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
507 CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
511 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
513 if [ "$CHANGE_PROFILE" -eq 1 ]; then
514 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
518 # found the profile, exit with success
525 # we did not find the profile, load default
526 if [ -n "$DEFAULT_PROFILE" ]; then
527 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
528 load "$DEFAULT_PROFILE"
536 # indent-tabs-mode: t