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 setup_fp_xrandr_edid() {
103 $XRANDR -q --verbose | awk '
105 / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
106 /^[[:blank:]]+EDID:/ {
109 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
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; }'
132 $DISPER -l | grep '^display '
137 for M in $FP_METHODS; do
139 if [ -n "$FP" ]; then
143 if [ -z "$FP" ]; then
144 echo "Unable to fingerprint display configuration" >&2
147 echo "$FINGERPRINT" | sort
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}')"
155 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
156 # display is connected and has a mode
157 /^[^ ]+ connected [^(]/ {
159 if ($3 == "primary") {
166 if (A[1] A[2] "," A[3] == primary_setup)
169 if (($4 == "left") || ($4 == "right")) {
174 print "pos "A[2]"x"A[3];
179 print "rotate normal";
183 # disconnected or disabled displays
184 /^[^ ]+ (dis)?connected / ||
185 /^[^ ]+ unknown connection / {
192 current_cfg_disper() {
196 common_cfg_xrandr() {
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 / {
207 outputlist=outputlist " " output
208 outputarr[outputarrsize++]=output
210 # disconnected or disabled displays
211 /^[^ ]+ disconnected / ||
212 /^[^ ]+ unknown connection / {
216 # modes available on a screen
218 modelist[$1]=modelist[$1] " " output
221 # find common mode with largest screen area
222 for (m in modelist) {
223 if (modelist[m] == outputlist) {
224 # calculate area of resolution
226 if (wh[1]*wh[2] >= maxdim) {
233 for (i in outputarr) {
234 print "output " outputarr[i];
235 print "mode " maxmode;
238 print "same-as " outputarr[0]
247 $XRANDR -q | awk -v stack="${STACK}" '
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
256 # display is connected
257 /^[^ ]+ connected / {
262 # disconnected or disabled displays
263 /^[^ ]+ disconnected / ||
264 /^[^ ]+ unknown connection / {
268 # modes available on a screen, but pick only the first
270 if (!firstmode) next;
272 # output mode at current virtual desktop pos
274 print "pos " posX "x" posY;
275 # calculate position of next output
277 if (stack == "vertical")
291 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
293 "$PROFILES/$PROFILE/block" "$PROFILE"
298 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
299 echo "Config already loaded"
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 '
322 # Merge with next line if it contains --off
323 s/\n\([^\n]* --off\)/ \1/
329 # Swap lines 1 and 2 if --off is found
330 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
338 /--mode/{ N; s/\n/ /; }
339 ' | xargs -L 1 $XRANDR
348 local CONF="$PROFILES/$PROFILE/config"
349 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
351 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
352 echo " -> Error: Profile '$PROFILE' does not exist." >&2
356 if [ -x "$PROFILES/preswitch" ]; then
357 "$PROFILES/preswitch" "$PROFILE"
359 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
360 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
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
371 if [ $PROFILE = "common" ]; then
372 echo " -> setting largest common mode in cloned mode"
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
383 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
384 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
386 if [ -x "$PROFILES/postswitch" ]; then
387 "$PROFILES/postswitch" "$PROFILE"
393 Usage: $SCRIPTNAME [options]
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
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.
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
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.
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.
420 If xrandr is used, the following virtual configurations are available:
421 ${RESERVED_PROFILE_NAMES}
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
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 ;;
438 --force) FORCE_LOAD=1; shift ;;
439 --fingerprint) setup_fp; exit 0;;
440 --config) current_cfg; exit 0;;
442 *) echo "Error: $1"; exit 1;;
446 CURRENT_SETUP="$(setup_fp)"
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."
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"
460 if [ -n "$LOAD_PROFILE" ]; then
461 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
465 for SETUP_FILE in $PROFILES/*/setup; do
466 if ! [ -e $SETUP_FILE ]; then
469 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
472 if blocked "$PROFILE"; then
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
483 if echo "$FILE_SETUP" | grep -Eq "^[^ ]+ [0-9a-f]{32}$"; then
484 echo -n " (Obsolete fingerprint format. Please update using --save.) "
486 if [ -z "$LEGACY_CURRENT_SETUP" ]; then
487 LEGACY_CURRENT_SETUP="$(echo "$CURRENT_SETUP" | while read DEVICE EDID; do
489 echo -n "${EDID}" | xxd -r -ps | md5sum - | awk '{print $1}'
492 FILE_SETUP="$(echo "$FILE_SETUP" | sort)"
493 if [ "$LEGACY_CURRENT_SETUP" = "$FILE_SETUP" ]; then
494 CURRENT_SETUP="$LEGACY_CURRENT_SETUP"
498 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
500 if [ "$CHANGE_PROFILE" -eq 1 ]; then
501 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
505 # found the profile, exit with success
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"
523 # indent-tabs-mode: t