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 '
83 /^[^ ]+ (dis)?connected / { DEV=$1; }
84 $1 ~ /^[a-f0-9]+$/ { ID[DEV] = ID[DEV] $1 }
85 END { for (X in ID) { print X " " ID[X]; } }'
88 setup_fp_sysfs_edid() {
89 # xrandr triggers the reloading of EDID data
90 $XRANDR -q > /dev/null
91 # hash the EDIDs of all _connected_ devices
92 for P in /sys/class/drm/card*-*/; do
94 [ ! -d "$P" ] && continue
95 if grep -q "^connected$" < "${P}status"; then
96 echo -n "$(basename "$P") "
97 md5sum ${P}edid | awk '{print $1}'
103 $DISPER -l | grep '^display '
108 for M in $FP_METHODS; do
110 if [ -n "$FP" ]; then
114 if [ -z "$FP" ]; then
115 echo "Unable to fingerprint display configuration" >&2
121 current_cfg_xrandr() {
122 local PRIMARY_SETUP="";
123 if [ -x "$XDPYINFO" ]; then
124 PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')"
126 $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" '
127 # display is connected and has a mode
128 /^[^ ]+ connected [^(]/ {
130 if ($3 == "primary") {
137 if (A[1] A[2] "," A[3] == primary_setup)
140 if (($4 == "left") || ($4 == "right")) {
145 print "pos "A[2]"x"A[3];
150 print "rotate normal";
154 # disconnected or disabled displays
155 /^[^ ]+ (dis)?connected / ||
156 /^[^ ]+ unknown connection / {
163 current_cfg_disper() {
167 common_cfg_xrandr() {
170 # output: current output
171 # outputlist: space sep list of all outputs
172 # outputarr: array of all connected outputs
173 # outputarrsize: number of connected outputs
174 # modelist[800x600]: space sep list of outputs supporting mode
175 # display is connected
176 /^[^ ]+ connected / {
178 outputlist=outputlist " " output
179 outputarr[outputarrsize++]=output
181 # disconnected or disabled displays
182 /^[^ ]+ disconnected / ||
183 /^[^ ]+ unknown connection / {
187 # modes available on a screen
189 modelist[$1]=modelist[$1] " " output
192 # find common mode with largest screen area
193 for (m in modelist) {
194 if (modelist[m] == outputlist) {
195 # calculate area of resolution
197 if (wh[1]*wh[2] >= maxdim) {
204 for (i in outputarr) {
205 print "output " outputarr[i];
206 print "mode " maxmode;
209 print "same-as " outputarr[0]
218 $XRANDR -q | awk -v stack="${STACK}" '
220 # stack: "horizontal" (anything except vertical) or "vertical"
221 # output: current output
222 # firstmode: pick first mode after output
223 # posX,posY: position of the next output
227 # display is connected
228 /^[^ ]+ connected / {
233 # disconnected or disabled displays
234 /^[^ ]+ disconnected / ||
235 /^[^ ]+ unknown connection / {
239 # modes available on a screen, but pick only the first
241 if (!firstmode) next;
243 # output mode at current virtual desktop pos
245 print "pos " posX "x" posY;
246 # calculate position of next output
248 if (stack == "vertical")
262 [ ! -x "$PROFILES/$PROFILE/block" ] && return 1
264 "$PROFILES/$PROFILE/block" "$PROFILE"
269 if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then
270 echo "Config already loaded"
278 # sed 1: Prefix arguments with "--"
279 # sed 2: Merge arguments into one line per output
280 # sed 3: Merge into two lines, all --off outputs in the first one
281 sed 's/^/--/' "$1" | sed -e '
289 # Merge if next line contains --off
290 s/\n\([^\n]* --off\)/ \1/
296 # Merge if previous line contains --mode
297 s/\(--mode [^\n]*\)\n/\1 /
299 $!d' | xargs -L 1 $XRANDR
308 local CONF="$PROFILES/$PROFILE/config"
309 local IS_VIRTUAL_PROFILE=`echo "$RESERVED_PROFILE_NAMES" | grep -c "^ $PROFILE "`
311 if [ ! -f "$CONF" -a $IS_VIRTUAL_PROFILE -eq 0 ]; then
312 echo " -> Error: Profile '$PROFILE' does not exist." >&2
316 if [ -x "$PROFILES/preswitch" ]; then
317 "$PROFILES/preswitch" "$PROFILE"
319 if [ -x "$PROFILES/$PROFILE/preswitch" ]; then
320 "$PROFILES/$PROFILE/preswitch" "$PROFILE"
323 if [ -f "$CONF" ]; then
324 echo " -> loading profile $PROFILE"
325 if [ $IS_VIRTUAL_PROFILE -ne 0 ]; then
326 echo " -> Warning: Existing profile overrides virtual profile with same name" >&2
331 if [ $PROFILE = "common" ]; then
332 echo " -> setting largest common mode in cloned mode"
334 elif [ $PROFILE = "horizontal" ]; then
335 echo " -> stacking all outputs horizontally at their largest modes"
336 STACK="horizontal" stack_cfg_xrandr
337 elif [ $PROFILE = "vertical" ]; then
338 echo " -> stacking all outputs vertically at their largest modes"
339 STACK="vertical" stack_cfg_xrandr
343 if [ -x "$PROFILES/$PROFILE/postswitch" ]; then
344 "$PROFILES/$PROFILE/postswitch" "$PROFILE"
346 if [ -x "$PROFILES/postswitch" ]; then
347 "$PROFILES/postswitch" "$PROFILE"
353 Usage: $SCRIPTNAME [options]
355 -h, --help get this small help
356 -c, --change reload current setup
357 -s, --save <profile> save your current setup to profile <profile>
358 -l, --load <profile> load profile <profile>
359 -d, --default <profile> make profile <profile> the default profile
360 --force force (re)loading of a profile
361 --fingerprint fingerprint your current hardware setup
362 --config dump your current xrandr setup
364 To prevent a profile from being loaded, place a script call "block" in its
365 directory. The script is evaluated before the screen setup is inspected, and
366 in case of it returning a value of 0 the profile is skipped. This can be used
367 to query the status of a docking station you are about to leave.
369 If no suitable profile can be identified, the current configuration is kept.
370 To change this behaviour and switch to a fallback configuration, specify
373 Another script called "postswitch "can be placed in the directory
374 ~/.autorandr as well as in any profile directories: The scripts are executed
375 after a mode switch has taken place and can notify window managers.
377 When called by the name "autodisper" or "auto-disper", the script uses "disper"
378 instead of "xrandr" to detect, configure and save the display configuration.
380 If xrandr is used, the following virtual configurations are available:
381 ${RESERVED_PROFILE_NAMES}
387 OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@")
388 if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
393 -c|--change) CHANGE_PROFILE=1; shift ;;
394 -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;;
395 -s|--save) SAVE_PROFILE="$2"; shift 2 ;;
396 -l|--load) LOAD_PROFILE="$2"; shift 2 ;;
398 --force) FORCE_LOAD=1; shift ;;
399 --fingerprint) setup_fp; exit 0;;
400 --config) current_cfg; exit 0;;
402 *) echo "Error: $1"; exit 1;;
406 CURRENT_SETUP="$(setup_fp)"
408 if [ -n "$SAVE_PROFILE" ]; then
409 if echo "$RESERVED_PROFILE_NAMES" | grep -q "^ $SAVE_PROFILE "; then
410 echo "Cannot save current configuration as profile '${SAVE_PROFILE}': This configuration name is a reserved virtual configuration."
413 echo "Saving current configuration as profile '${SAVE_PROFILE}'"
414 mkdir -p "$PROFILES/$SAVE_PROFILE"
415 echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup"
416 $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config"
420 if [ -n "$LOAD_PROFILE" ]; then
421 CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE"
425 for SETUP_FILE in $PROFILES/*/setup; do
426 if ! [ -e $SETUP_FILE ]; then
429 PROFILE="$(basename $(dirname "$SETUP_FILE"))"
432 if blocked "$PROFILE"; then
437 FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")"
438 if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then
440 if [ "$CHANGE_PROFILE" -eq 1 ]; then
441 if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then
445 # found the profile, exit with success
452 # we did not find the profile, load default
453 if [ -n "$DEFAULT_PROFILE" ]; then
454 echo "No suitable profile detected, falling back to $DEFAULT_PROFILE"
455 load "$DEFAULT_PROFILE"
463 # indent-tabs-mode: t