3 # Automatically select a display configuration based on connected devices
5 # Stefan Tomanek <stefan.tomanek@wertarbyte.de>
9 # Save your current display configuration and setup with:
10 # $ autorandr --save mobile
12 # Connect an additional display, configure your setup and save it:
13 # $ autorandr --save docked
15 # Now autorandr can detect which hardware setup is active:
20 # To automatically reload your setup, just append --change to the command line
22 # To manually load a profile, you can use the --load <profile> option.
24 # autorandr tries to avoid reloading an identical configuration. To force the
25 # (re)configuration, apply --force.
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.
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
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.
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.
47 XRANDR=/usr/bin/xrandr
48 DISPER=/usr/bin/disper
49 XDPYINFO=/usr/bin/xdpyinfo
51 CONFIG=~/.autorandr.conf
52 RESERVED_PROFILE_NAMES=`cat <<EOF
53 common Clone all connected outputs at the largest common resolution
54 horizontal Stack all connected outputs horizontally at their largest resolution
55 vertical Stack all connected outputs vertically at their largest resolution
63 FP_METHODS="setup_fp_sysfs_edid setup_fp_xrandr_edid"
64 CURRENT_CFG_METHOD="current_cfg_xrandr"
65 LOAD_METHOD="load_cfg_xrandr"
67 SCRIPTNAME="$(basename $0)"
68 # when called as autodisper/auto-disper, we assume different defaults
69 if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then
70 echo "Assuming disper defaults..." >&2
71 FP_METHODS="setup_fp_disper"
72 CURRENT_CFG_METHOD="current_cfg_disper"
73 LOAD_METHOD="load_cfg_disper"
76 if [ -f $CONFIG ]; then
77 echo "Loading configuration from '$CONFIG'" >&2
81 setup_fp_xrandr_edid() {
82 $XRANDR -q --verbose | awk '
84 / (dis)?connected/ { DEVICE=gensub("-([A-Z]-)?", "", "g", $1) " "; }
85 /^[[:blank:]]+EDID:/ {
88 for(getline; /^[[:blank:]]+[0-9a-f]+$/; getline) {
99 setup_fp_sysfs_edid() {
100 which xxd >/dev/null 2>&1 || return
101 $XRANDR -q > /dev/null
102 for DEVICE in /sys/class/drm/card*-*; do
103 [ -e "${DEVICE}/status" ] && grep -q "^connected$" "${DEVICE}/status" || continue
104 echo -n "$(echo "${DEVICE}/edid" | sed -re 's#^.+card[0-9]+-([^/]+).+#\1#; s#-([A-Z]-)?##') "
105 xxd -c 256 -ps "${DEVICE}/edid" | awk 'ORS=""; /.+/ { print; }'
111 $DISPER -l | grep '^display '
116 for M in $FP_METHODS; do
118 if [ -n "$FP" ]; then
122 if [ -z "$FP" ]; then
123 echo "Unable to fingerprint display configuration" >&2
126 echo "$FINGERPRINT" | sort
129 current_cfg_xrandr() {
130 local PRIMARY_SETUP="";
131 if [ -x "$XDPYINFO" ]; then
132 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
134 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
135 # display is connected and has a mode
136 /^[^ ]+ connected [^(]/ {
138 if ($3 == "primary") {
145 if (A[1] A[2] "," A[3] == primary_setup)
148 if (($4 == "left") || ($4 == "right")) {
153 print "pos "A[2]"x"A[3];
158 print "rotate normal";
162 # disconnected or disabled displays
163 /^[^ ]+ (dis)?connected / ||
164 /^[^ ]+ unknown connection / {
171 current_cfg_disper() {
175 common_cfg_xrandr() {
178 # output: current output
179 # outputlist: space sep list of all outputs
180 # outputarr: array of all connected outputs
181 # outputarrsize: number of connected outputs
182 # modelist[800x600]: space sep list of outputs supporting mode
183 # display is connected
184 /^[^ ]+ connected / {
186 outputlist=outputlist " " output
187 outputarr[outputarrsize++]=output
189 # disconnected or disabled displays
190 /^[^ ]+ disconnected / ||
191 /^[^ ]+ unknown connection / {
195 # modes available on a screen
197 modelist[$1]=modelist[$1] " " output
200 # find common mode with largest screen area
201 for (m in modelist) {
202 if (modelist[m] == outputlist) {
203 # calculate area of resolution
205 if (wh[1]*wh[2] >= maxdim) {
212 for (i in outputarr) {
213 print "output " outputarr[i];
214 print "mode " maxmode;
217 print "same-as " outputarr[0]
226 $XRANDR -q | awk -v stack="${STACK}" '
228 # stack: "horizontal" (anything except vertical) or "vertical"
229 # output: current output
230 # firstmode: pick first mode after output
231 # posX,posY: position of the next output
235 # display is connected
236 /^[^ ]+ connected / {
241 # disconnected or disabled displays
242 /^[^ ]+ disconnected / ||
243 /^[^ ]+ unknown connection / {
247 # modes available on a screen, but pick only the first
249 if (!firstmode) next;
251 # output mode at current virtual desktop pos
253 print "pos " posX "x" posY;
254 # calculate position of next output
256 if (stack == "vertical")
270 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
272 "$PROFILES/$PROFILE/block" "$PROFILE"
277 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
278 echo "Config already loaded"
286 # sed 1: Prefix arguments with "--"
287 # sed 2: Merge arguments into one line per output
288 # sed 3: * Merge all --off outputs into the first line
289 # * Place the output with --pos 0x0 on the second line
290 # * Remaining outputs are appended as they appear
291 # * Keep everything in hold buffer until the last line
292 # sed 4: Remove empty lines caused by G and H on empty hold buffer
293 # sed 5: Join lines enabling screens in pairs of two (See https://github.com/phillipberndt/autorandr/pull/6)
294 sed 's/^/--/' "$1" | sed -e '
301 # Merge with next line if it contains --off
302 s/\n\([^\n]* --off\)/ \1/
308 # Swap lines 1 and 2 if --off is found
309 / --off/ s/^\([^\n]*\)\n\([^\n]*\)/\2\n\1/
317 /--mode/{ N; s/\n/ /; }
318 ' | xargs -L 1 $XRANDR
327 local CONF="$PROFILES/$PROFILE/config"
328 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
330 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
331 echo " -> Error: Profile '$PROFILE' does not exist." >&2
335 if [ -x "$PROFILES/preswitch" ]; then
336 "$PROFILES/preswitch" "$PROFILE"
338 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
339 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
342 if [ -f "$CONF" ]; then
343 echo " -> loading profile $PROFILE"
344 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
345 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
350 if [ $PROFILE = "common" ]; then
351 echo " -> setting largest common mode in cloned mode"
353 elif [ $PROFILE = "horizontal" ]; then
354 echo " -> stacking all outputs horizontally at their largest modes"
355 STACK="horizontal" stack_cfg_xrandr
356 elif [ $PROFILE = "vertical" ]; then
357 echo " -> stacking all outputs vertically at their largest modes"
358 STACK="vertical" stack_cfg_xrandr
362 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
363 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
365 if [ -x "$PROFILES/postswitch" ]; then
366 "$PROFILES/postswitch" "$PROFILE"
372 Usage: $SCRIPTNAME [options]
374 -h, --help get this small help
375 -c, --change reload current setup
376 -s, --save <profile> save your current setup to profile <profile>
377 -l, --load <profile> load profile <profile>
378 -d, --default <profile> make profile <profile> the default profile
379 --force force (re)loading of a profile
380 --fingerprint fingerprint your current hardware setup
381 --config dump your current xrandr setup
383 To prevent a profile from being loaded, place a script call "block" in its
384 directory. The script is evaluated before the screen setup is inspected, and
385 in case of it returning a value of 0 the profile is skipped. This can be used
386 to query the status of a docking station you are about to leave.
388 If no suitable profile can be identified, the current configuration is kept.
389 To change this behaviour and switch to a fallback configuration, specify
392 Another script called "postswitch "can be placed in the directory
393 ~/.autorandr as well as in any profile directories: The scripts are executed
394 after a mode switch has taken place and can notify window managers.
396 When called by the name "autodisper" or "auto-disper", the script uses "disper"
397 instead of "xrandr" to detect, configure and save the display configuration.
399 If xrandr is used, the following virtual configurations are available:
400 ${RESERVED_PROFILE_NAMES}
406 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
407 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
412 -c|--change) CHANGE_PROFILE=1; shift ;;
413 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
414 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
415 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
417 --force) FORCE_LOAD=1; shift ;;
418 --fingerprint) setup_fp; exit 0;;
419 --config) current_cfg; exit 0;;
421 *) echo "Error: $1"; exit 1;;
425 CURRENT_SETUP="$(setup_fp)"
427 if [ -n "$SAVE_PROFILE" ]; then
428 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
429 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
432 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
433 mkdir -p "$PROFILES/$SAVE_PROFILE"
434 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
435 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
439 if [ -n "$LOAD_PROFILE" ]; then
440 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
444 for SETUP_FILE in $PROFILES/*/setup; do
445 if ! [ -e $SETUP_FILE ]; then
448 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
451 if blocked "$PROFILE"; then
456 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
457 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
459 if [ "$CHANGE_PROFILE" -eq 1 ]; then
460 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
464 # found the profile, exit with success
471 # we did not find the profile, load default
472 if [ -n "$DEFAULT_PROFILE" ]; then
473 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
474 load "$DEFAULT_PROFILE"
482 # indent-tabs-mode: t