|
@@ -0,0 +1,290 @@
|
|
|
+#!/bin/sh
|
|
|
+#
|
|
|
+# Automatically select a display configuration based on connected devices
|
|
|
+#
|
|
|
+# Stefan Tomanek <stefan.tomanek@wertarbyte.de>
|
|
|
+#
|
|
|
+# 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 <profile> 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 <profile>
|
|
|
+#
|
|
|
+# 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 <<EOH
|
|
|
+Usage: $SCRIPTNAME [options]
|
|
|
+
|
|
|
+-h, --help get this small help
|
|
|
+-c, --change reload current setup
|
|
|
+-s, --save <profile> save your current setup to profile <profile>
|
|
|
+-l, --load <profile> load profile <profile>
|
|
|
+-d, --default <profile> make profile <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 <profile>.
|
|
|
+
|
|
|
+ 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
|