mirror of
https://github.com/phillipberndt/autorandr
synced 2025-03-08 13:38:17 +00:00
xRandR (in newer versions?) will output an extra columns for primary outputs. Detect this column and modify the string split and column numbers accordingly.
298 lines
7.7 KiB
Bash
Executable File
298 lines
7.7 KiB
Bash
Executable File
#!/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 [^(]/ {
|
|
print "output "$1;
|
|
if ($3 == "primary") {
|
|
print $3
|
|
split($4, A, "+")
|
|
$4=$5
|
|
}
|
|
else {
|
|
split($3, A, "+");
|
|
if (A[1] A[2] "," A[3] == primary_setup)
|
|
print "primary";
|
|
}
|
|
print "mode "A[1];
|
|
print "pos "A[2]"x"A[3];
|
|
if ($4 !~ /^\(/) {
|
|
print "rotate "$4;
|
|
}
|
|
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
|