Skip to content

Instantly share code, notes, and snippets.

@gservat
Created April 14, 2019 14:37
Show Gist options
  • Save gservat/09c34d46de353c36c2316e2df79391e7 to your computer and use it in GitHub Desktop.
Save gservat/09c34d46de353c36c2316e2df79391e7 to your computer and use it in GitHub Desktop.
Display profile switcher using nvidia-settings
#!/bin/bash
exec > >(tee -a "/tmp/display-switcher.log") 2>&1
# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this
# separately; see below.
usage() {
cat << HELP >&2
Usage: $0 [option ...] {profile}
OPTIONS:
-h, --help This help message
-v, --verbose Enable verbose mode
-w N, --wait N Seconds to wait before changing profile
-m TEXT, --message TEXT Text to display as a notification
-t SECS, --message-timeout SECS How long (seconds) to display the notification for
-u NAME, --user NAME User to use for X11 notifications
HELP
}
msg() {
if $VERBOSE; then
echo "[$(date)] $1"
fi
}
if ! TEMP=$(getopt -o hvt:u:m:w: --long verbose,help,user:,message:,message-timeout:,wait: -n 'display-switcher' -- "$@"); then
usage
exit 1
fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
VERBOSE=false
WAIT=0
MESSAGE="Switching display profile to %s soon..."
NOTIFY_USER=
NOTIFY_UID=
NOTIFY_ARGS=
ICON="/usr/share/icons/Pop/48x48/devices/gnome-dev-computer.svg"
while true; do
case "$1" in
-v | --verbose ) VERBOSE=true; shift ;;
-w | --wait ) WAIT="$2"; shift 2 ;;
-m | --message ) MESSAGE="$2"; shift 2 ;;
-t | --message-timeout ) NOTIFY_ARGS="${NOTIFY_ARGS} -t ${2}000"; shift 2 ;;
-u | --user )
NOTIFY_USER="$2"
shift 2
if ! NOTIFY_UID="$(id -u ${NOTIFY_USER} 2>/dev/null)"; then
echo "ERROR: Unable to get UID for ${NOTIFY_USER}."
exit 1
fi
;;
-h | --help ) usage; exit 0 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if [[ -z ${NOTIFY_USER} ]]; then
echo "ERROR: You need to specify the username for the authorized X user to use for notifications."
usage
exit 1
fi
NOTIFY_CMD="sudo -u ${NOTIFY_USER} DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${NOTIFY_UID}/bus dunstify -I ${ICON}"
# nvidia-settings -q dpys
BENQ_4K_EDID="DPY-EDID-ead0ac88-15bf-e410-e2a5-8538dcd8450d"
LG_5K_EDID="DPY-EDID-79885171-ff0f-23e3-0566-988d08123861"
NOTEBOOK_EDID="DPY-EDID-703b6c15-6e68-abf3-2744-8a1b0abf4d39"
# nvidia-settings -q CurrentMetaMode | tr '\n' ' '
PROFILE=${1}
case "${PROFILE}" in
laptop-only)
MODE="${NOTEBOOK_EDID}: 3840x2160 @3840x2160 +0+0 {ViewPortIn=3840x2160, ViewPortOut=3840x2160+0+0}"
;;
5k-4k-vertical-laptop)
MODE="${BENQ_4K_EDID}: 3840x2160 @2160x3840 +4096+0 {ViewPortIn=2160x3840, ViewPortOut=3840x2160+0+0, Rotation=90}, ${LG_5K_EDID}: 4096x2304 @4096x2304 +0+0 {ViewPortIn=4096x2304, ViewPortOut=4096x2304+0+0}, ${NOTEBOOK_EDID}: 3840x2160 @3840x2160 +6256+0 {ViewPortIn=3840x2160, ViewPortOut=3840x2160+0+0}"
;;
5k-4k-vertical-no-laptop)
MODE="${BENQ_4K_EDID}: 3840x2160 @2160x3840 +4096+0 {ViewPortIn=2160x3840, ViewPortOut=3840x2160+0+0, Rotation=90}, ${LG_5K_EDID}: 4096x2304 @4096x2304 +0+0 {ViewPortIn=4096x2304, ViewPortOut=4096x2304+0+0}"
;;
*)
usage
echo -e "\\nERROR: You need to specify a valid profile to switch to."
exit 1
;;
esac
msg Starting...
msg "Notify command: ${NOTIFY_CMD}"
FINAL_MSG=$(printf "${MESSAGE}" ${PROFILE})
msg "Sending notification: ${NOTIFY_CMD} ${NOTIFY_ARGS} \"${FINAL_MSG}\""
${NOTIFY_CMD} ${NOTIFY_ARGS} "${FINAL_MSG}"
if [[ ${WAIT} -gt 0 ]]; then
msg "Sleeping for ${WAIT} seconds"
sleep "${WAIT}"
fi
# Finally, run nvidia-settings.
msg "Running: nvidia-settings --assign \"CurrentMetaMode=${MODE}\""
nvidia-settings --assign "CurrentMetaMode=${MODE}"
${NOTIFY_CMD} -t 3000 "Display profile has been switched"
@gservat
Copy link
Author

gservat commented Apr 14, 2019

Background

I've got the following hardware:

  • Lenovo Thinkpad X1 Extreme (running Pop!_OS)
  • Thunderbolt 3 Docking Station
    • LG Ultrafine 5K monitor
    • BenQ BL2711U 4K monitor

The issue that I had was that tools like xrandr (and companions like autorandr/arandr) simply did not work reliably. Sometimes they would configure my screens the way I wanted, but most of the time they spat out errors (e.g. BadValue) and left monitors off, or they did weird stuff like enable panning so the virtual size was much bigger than the monitor size. This meant that if I docked off, there was a pretty good chance that when I docked on again, I'd have to spend a bunch of frustrating minutes playing with xrandr to get my screens set-up correctly again. You can imagine how this can get old and frustrating pretty quickly!

Solution

I found that nvidia-settings was actually reliable in setting up my screens the way I wanted, and you can actually drive nvidia-settings via the CLI. Interestingly enough, it seemed that often enough the displays did not always come up under the same names (e.g. DPY-0 was not always DPY-0... it sometimes came up as DPY-1 for instance), so I switched to using the EDID values, which are guaranteed to be the same.

To use this script, you'll have to remove/add EDID values for your monitors and configure the various display profiles that you want to be able to switch between. In my case, the profiles I've set-up are:

  • laptop-only for when I'm undocked (i.e. no monitors - notebook only)
  • 5k-4k-vertical-laptop for when I want my 5K (landscape) + 4K (portrait) + my notebook display (rarely used)
  • 5k-4k-vertical-no-laptop for when I want my 5K (landscape) + 4K (portrait) but the notebook monitor switched off (used when docked)

To come up with the values for these profiles, you'll want to use nvidia-settings to set-up your screens and once it looks the way you want it to look, simply exit and run nvidia-settings -q CurrentMetaMode. You'll need the text after the ::, which will go into the MODE=X part of the case statement. In this MODE line, you'll need to replace names like DPY-X with their EDID equivalent, which you can find by using nvidia-settings -q dpys (under Has the following names).

The end result is that after docking/undocking a bunch of times, the displays came up reliably how I want them to be set-up.

To automate the switching of display profiles when I dock on/off, I've created /etc/udev/rules.d/70-thunderbolt-dock.rules with the following 2 rules:

ACTION=="remove", SUBSYSTEM=="net", ENV{ID_MODEL}=="ThinkPad_TBT_3_Dock", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/<username>/.Xauthority", RUN+="/home/<username>/bin/display-switcher -u <username> -v -t 5 -w 5 laptop-only"
ACTION=="add", SUBSYSTEM=="usb", ENV{ID_MODEL}=="ThinkPad_TBT_3_Dock", ENV{DISPLAY}=":0", ENV{XAUTHORITY}="/home/<username>/.Xauthority", RUN+="/home/<username>/bin/display-switcher -u <username> -v -t 10 -w 15 5k-4k-vertical-no-laptop"

Hope this saves you some frustration!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment