#!/bin/sh # # Automatically select a display configuration based on connected devices # # Stefan Tomanek # # How to use: # # Save your current display configuration and setup with: # $ autorandr --save mobile # # Connect an additional display, configure your setup and save it: # $ autorandr --save docked # # Now autorandr can detect which hardware setup is active: # $ autorandr # mobile # docked (detected) # # To automatically reload your setup, just append --change to the command line # # To manually load a profile, you can use the --load option. # # autorandr tries to avoid reloading an identical configuration. To force the # (re)configuration, apply --force. # # To prevent a profile from being loaded, place a script call "block" in its # directory. The script is evaluated before the screen setup is inspected, and # in case of it returning a value of 0 the profile is skipped. This can be used # to query the status of a docking station you are about to leave. # # If no suitable profile can be identified, the current configuration is kept. # To change this behaviour and switch to a fallback configuration, specify # --default # # Another script called "postswitch "can be placed in the directory # ~/.autorandr as well as in all profile directories: The scripts are executed # after a mode switch has taken place and can notify window managers or other # applications about it. # # # While the script uses xrandr by default, calling it by the name "autodisper" # or "auto-disper" forces it to use the "disper" utility, which is useful for # controlling nvidia chipsets. The formats for fingerprinting the current setup # and saving/loading the current configuration are adjusted accordingly. XRANDR=/usr/bin/xrandr DISPER=/usr/bin/disper XDPYINFO=/usr/bin/xdpyinfo PROFILES=~/.autorandr/ CONFIG=~/.autorandr.conf CHANGE_PROFILE=0 FORCE_LOAD=0 DEFAULT_PROFILE="" SAVE_PROFILE="" FP_METHODS="setup_fp_xrandr_edid setup_fp_sysfs_edid" CURRENT_CFG_METHOD="current_cfg_xrandr" LOAD_METHOD="load_cfg_xrandr" SCRIPTNAME="$(basename $0)" # when called as autodisper/auto-disper, we assume different defaults if [ "$SCRIPTNAME" = "auto-disper" ] || [ "$SCRIPTNAME" = "autodisper" ]; then echo "Assuming disper defaults..." >&2 FP_METHODS="setup_fp_disper" CURRENT_CFG_METHOD="current_cfg_disper" LOAD_METHOD="load_cfg_disper" fi if [ -f $CONFIG ]; then echo "Loading configuration from '$CONFIG'" >&2 . $CONFIG fi setup_fp_xrandr_edid() { $XRANDR -q --verbose | awk ' /^[^ ]+ (dis)?connected / { DEV=$1; } $1 ~ /^[a-f0-9]+$/ { ID[DEV] = ID[DEV] $1 } END { for (X in ID) { print X " " ID[X]; } }' } setup_fp_sysfs_edid() { # xrandr triggers the reloading of EDID data $XRANDR -q > /dev/null # hash the EDIDs of all _connected_ devices for P in /sys/class/drm/card*-*/; do # nothing found [ ! -d "$P" ] && continue if grep -q "^connected$" < "${P}status"; then echo -n "$(basename "$P") " md5sum ${P}edid | awk '{print $1}' fi done } setup_fp_disper() { $DISPER -l | grep '^display ' } setup_fp() { local FP=""; for M in $FP_METHODS; do FP="$($M)" if [ -n "$FP" ]; then break fi done if [ -z "$FP" ]; then echo "Unable to fingerprint display configuration" >&2 return fi echo "$FP" } current_cfg_xrandr() { local PRIMARY_SETUP=""; if [ -x "$XDPYINFO" ]; then PRIMARY_SETUP="$($XDPYINFO -ext XINERAMA | awk '/^ head #0:/ {printf $3 $5}')" fi $XRANDR -q | awk -v primary_setup="${PRIMARY_SETUP}" ' # display is connected and has a mode /^[^ ]+ connected [^(]/ { split($3, A, "+"); print "output "$1; print "mode "A[1]; print "pos "A[2]"x"A[3]; if ($4 !~ /^\(/) { print "rotate "$4; } if (A[1] A[2] "," A[3] == primary_setup) print "primary"; next; } # disconnected or disabled displays /^[^ ]+ (dis)?connected / || /^[^ ]+ unknown connection / { print "output "$1; print "off"; next; }' } current_cfg_disper() { $DISPER -p } current_cfg() { $CURRENT_CFG_METHOD; } blocked() { local PROFILE="$1" [ ! -x "$PROFILES/$PROFILE/block" ] && return 1 "$PROFILES/$PROFILE/block" "$PROFILE" } config_equal() { local PROFILE="$1" if [ "$(cat "$PROFILES/$PROFILE/config")" = "$(current_cfg)" ]; then echo "Config already loaded" return 0 else return 1 fi } load_cfg_xrandr() { sed 's!^!--!' "$1" | xargs $XRANDR } load_cfg_disper() { $DISPER -i < "$1" } load() { local PROFILE="$1" local CONF="$PROFILES/$PROFILE/config" if [ -e "$CONF" ] ; then echo " -> loading profile $PROFILE" $LOAD_METHOD "$CONF" [ -x "$PROFILES/$PROFILE/postswitch" ] && \ "$PROFILES/$PROFILE/postswitch" "$PROFILE" [ -x "$PROFILES/postswitch" ] && \ "$PROFILES/postswitch" "$PROFILE" fi } help() { cat < save your current setup to profile -l, --load load profile -d, --default make profile the default profile --force force (re)loading of a profile --fingerprint fingerprint your current hardware setup --config dump your current xrandr setup To prevent a profile from being loaded, place a script call "block" in its directory. The script is evaluated before the screen setup is inspected, and in case of it returning a value of 0 the profile is skipped. This can be used to query the status of a docking station you are about to leave. If no suitable profile can be identified, the current configuration is kept. To change this behaviour and switch to a fallback configuration, specify --default . Another script called "postswitch "can be placed in the directory ~/.autorandr as well as in any profile directories: The scripts are executed after a mode switch has taken place and can notify window managers. When called by the name "autodisper" or "auto-disper", the script uses "disper" instead of "xrandr" to detect, configure and save the display configuration. EOH exit } # process parameters OPTS=$(getopt -n autorandr -o s:l:d:cfh --long change,default:,save:,load:,force,fingerprint,config,help -- "$@") if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi eval set -- "$OPTS" while true; do case "$1" in -c|--change) CHANGE_PROFILE=1; shift ;; -d|--default) DEFAULT_PROFILE="$2"; shift 2 ;; -s|--save) SAVE_PROFILE="$2"; shift 2 ;; -l|--load) LOAD_PROFILE="$2"; shift 2 ;; -h|--help) help ;; --force) FORCE_LOAD=1; shift ;; --fingerprint) setup_fp; exit 0;; --config) current_cfg; exit 0;; --) shift; break ;; *) echo "Error: $1"; exit 1;; esac done CURRENT_SETUP="$(setup_fp)" if [ -n "$SAVE_PROFILE" ]; then echo "Saving current configuration as profile '${SAVE_PROFILE}'" mkdir -p "$PROFILES/$SAVE_PROFILE" echo "$CURRENT_SETUP" > "$PROFILES/$SAVE_PROFILE/setup" $CURRENT_CFG_METHOD > "$PROFILES/$SAVE_PROFILE/config" exit 0 fi if [ -n "$LOAD_PROFILE" ]; then CHANGE_PROFILE=1 FORCE_LOAD=1 load "$LOAD_PROFILE" exit $? fi for SETUP_FILE in $PROFILES/*/setup; do if ! [ -e $SETUP_FILE ]; then break fi PROFILE="$(basename $(dirname "$SETUP_FILE"))" echo -n "$PROFILE" if blocked "$PROFILE"; then echo " (blocked)" continue fi FILE_SETUP="$(cat "$PROFILES/$PROFILE/setup")" if [ "$CURRENT_SETUP" = "$FILE_SETUP" ]; then echo " (detected)" if [ "$CHANGE_PROFILE" -eq 1 ]; then if [ "$FORCE_LOAD" -eq 1 ] || ! config_equal "$PROFILE"; then load "$PROFILE" fi fi # found the profile, exit with success exit 0 else echo "" fi done # we did not find the profile, load default if [ -n "$DEFAULT_PROFILE" ]; then echo "No suitable profile detected, falling back to $DEFAULT_PROFILE" load "$DEFAULT_PROFILE" fi exit 1