Last active
January 12, 2021 20:41
-
-
Save ocpu/ac0558f9013cc15dfe1d5a30518e1331 to your computer and use it in GitHub Desktop.
use rofi/dmenu to configure/disconnect/modify a monitor with xrandr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Provides an interactive xrandr experience with dmenu or dmenu | |
# like programs | |
# If the Window Manager requires a restart / reload put the | |
# command here or leave blank | |
RESTART_WM_CMD="i3-msg restart" | |
# Any command that takes a pipe of newline separated options | |
# Such as for example dmenu and rofi -dmenu | |
D_MENU_CMD="rofi -dmenu" | |
# All configurable labels | |
LABEL_MONITOR_AVAILABLE="Configure " | |
LABEL_MONITOR_AVAILABLE_CONFIRM="Activate " | |
LABEL_MONITOR_MONIFY="Modify " | |
LABEL_MONITOR_MONIFY_CONFIRM="Save Settings for " | |
LABEL_MONITOR_OFF="Disconnect " | |
LABEL_CONFIG_RESOLUTION="Resolution: " | |
LABEL_CONFIG_REFRESH_RATE="Refresh Rate: " | |
LABEL_CONFIG_REFRESH_RATE_POSTFIX="hz" | |
LABEL_CONFIG_POSITION="Position " | |
LABEL_CONFIG_DENY="Cancel" | |
LABEL_POSITION_RIGHT_OF="right of " | |
LABEL_POSITION_LEFT_OF="left of " | |
LABEL_POSITION_ABOVE="above " | |
LABEL_POSITION_BELOW="below " | |
LABEL_POSITION_SAME_AS="same as " | |
# -------------------------------------------------------------- # | |
SCRIPT_PATH="`realpath $0`" | |
SCRIPT_FILENAME="`basename $SCRIPT_PATH`" | |
SCRIPT_DIR="`dirname $SCRIPT_PATH`" | |
# Get active and unactivated monitors | |
ACTIVE_MONITORS=($(xrandr --listactivemonitors | tail -n +2 | tr -s ' ' | cut -d ' ' -f 5)) | |
TEMP_MONITORS=($(xrandr | grep -w connected | cut -d ' ' -f 1)) | |
# Delete active monitors from the temp list | |
for MONITOR in "${ACTIVE_MONITORS[@]}"; do | |
for i in "${!TEMP_MONITORS[@]}"; do | |
if [[ ${TEMP_MONITORS[i]} = $MONITOR ]]; then | |
unset 'TEMP_MONITORS[i]' | |
fi | |
done | |
done | |
# Build new monitor array from the temp list | |
for i in "${!TEMP_MONITORS[@]}"; do | |
if test -n "$i"; then | |
MONITORS+=( "${TEMP_MONITORS[i]}" ) | |
fi | |
done | |
# Basic select function that takes a variable amount of args and | |
# passes them to the selection program. | |
# | |
# Usage: select-action ACTION... | |
select-action() { | |
[ "$#" -eq "0" ] && return | |
for action in "$@"; do echo $action; done | $D_MENU_CMD 2> /dev/null | |
} | |
# Get all possible resulutions any monitor accepts. | |
# Usage: get-resolutions MONITOR | |
get-resolutions() { | |
xrandr | awk -v monitor="^$1 connected" '/connected/ {p = 0} $0 ~ monitor {p = 1} p' | tail -n +2 | cut -d ' ' -f 4 | |
} | |
# Get all refresh rate possibilities for a specific resolution on | |
# a monitor. | |
# | |
# Usage: get-update-rates-for-resolution MONITOR RESOLUTION | |
get-update-rates-for-resolution() { | |
local res=`xrandr | awk -v monitor="^$1 connected" '/connected/ {p = 0} $0 ~ monitor {p = 1} p' | grep -w "$2" | sed 's/^ *//;s/ *$//' | sed -n -e "/^$2/p" | sed "s/^$2 *//;s/ *$//"` | |
res="${res/\*}" | |
res="${res/+}" | |
rates=("${res[@]}") | |
for rate in $rates; do | |
echo $rate | |
done | |
} | |
get-relative-position-to-label() { | |
case "$1" in | |
"right-of") echo "$LABEL_POSITION_RIGHT_OF";; | |
"left-of") echo "$LABEL_POSITION_LEFT_OF";; | |
"above") echo "$LABEL_POSITION_ABOVE";; | |
"below") echo "$LABEL_POSITION_BELOW";; | |
"same-as") echo "$LABEL_POSITION_SAME_AS";; | |
*) echo "$LABEL_POSITION_RIGHT_OF";; | |
esac | |
} | |
get-relative-position-from-label() { | |
if test "`echo $LABEL_POSITION_RIGHT_OF | sed 's/^ *| *$//g'`" = "$*"; then echo "right-of"; return; fi | |
if test "`echo $LABEL_POSITION_LEFT_OF | sed 's/^ *| *$//g'`" = "$*"; then echo "left-of"; return; fi | |
if test "`echo $LABEL_POSITION_ABOVE | sed 's/^ *| *$//g'`" = "$*"; then echo "above"; return; fi | |
if test "`echo $LABEL_POSITION_BELOW | sed 's/^ *| *$//g'`" = "$*"; then echo "below"; return; fi | |
if test "`echo $LABEL_POSITION_SAME_AS | sed 's/^ *| *$//g'`" = "$*"; then echo "same-as"; return; fi | |
echo "right-of" | |
} | |
# Starts the interactive selector for monitor resolution, | |
# refresh rate, and monitor position. The position is | |
# a string that starts with either "same-as", "left-of", | |
# "right-of", "above", or "below" and then the monitor the | |
# position would be relative to like "right-of eDP-1". | |
# | |
# If INITIAL_POSITION is left out the selector will not | |
# present an option for the position arg. | |
# | |
# Usage: update-monitor EXECUTE_LABEL INITIAL_RESOLUTION INITIAL_REFRESH_RATE [INITIAL_POSITION] | |
update-monitor() { | |
local EXECUTE_LABEL="$1" | |
local RES="$2" | |
local RATE="$3" | |
read -a POS <<< "$4" | |
local POSITIONS_LIST_LABELS=( | |
"$LABEL_POSITION_SAME_AS" | |
"$LABEL_POSITION_LEFT_OF" | |
"$LABEL_POSITION_RIGHT_OF" | |
"$LABEL_POSITION_ABOVE" | |
"$LABEL_POSITION_BELOW" | |
) | |
local ACTIONS=( | |
"$LABEL_CONFIG_RESOLUTION$RES" | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX" | |
) | |
[ "${#POS[@]}" != "0" ] && ACTIONS+=("$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}` ${POS[1]}") | |
while :; do | |
case `select-action "$EXECUTE_LABEL" "${ACTIONS[@]}" "$LABEL_CONFIG_DENY"` in | |
"$EXECUTE_LABEL") | |
xrandr --output "$MONITOR" --mode "$RES" --rate "$RATE" --${POS[0]} "${POS[1]}" | |
if [ -n "$RESTART_WM_CMD" ]; then | |
sleep 1 | |
$RESTART_WM_CMD | |
fi | |
exit | |
;; | |
"$LABEL_CONFIG_RESOLUTION$RES") | |
local res=`select-action $(get-resolutions $MONITOR)` | |
if test -n "$res"; then | |
RES="$res" | |
RATE=`get-update-rates-for-resolution $MONITOR $RES | head -n1` | |
ACTIONS=( | |
"$LABEL_CONFIG_RESOLUTION$RES" | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX" | |
) | |
[ "${#POS[@]}" != "0" ] && ACTIONS+=("$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}`${POS[1]}") | |
fi | |
;; | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX") | |
local rate=`for rate in $(get-update-rates-for-resolution $MONITOR $RES); do echo "${rate}hz"; done | $D_MENU_CMD 2> /dev/null` | |
if test -n "$rate"; then | |
RATE="${rate%hz}" | |
ACTIONS=( | |
"$LABEL_CONFIG_RESOLUTION$RES" | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX" | |
) | |
[ "${#POS[@]}" != "0" ] && ACTIONS+=("$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}`${POS[1]}") | |
fi | |
;; | |
"$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}`${POS[1]}") | |
local ACTIVE_MONITOR_LIST=("${ACTIVE_MONITORS[@]}") | |
for index in "${!ACTIVE_MONITOR_LIST[@]}"; do | |
if test "${ACTIVE_MONITOR_LIST[index]}" == "$MONITOR"; then | |
unset 'ACTIVE_MONITOR_LIST[index]' | |
fi | |
done | |
local MONITOR_LIST=() | |
for i in "${!ACTIVE_MONITOR_LIST[@]}"; do | |
if test -n "$i"; then | |
MONITOR_LIST+=( "${ACTIVE_MONITOR_LIST[i]}" ) | |
fi | |
done | |
if test "${#MONITOR_LIST[@]}" -ne "0"; then | |
local pos=`select-action "${POSITIONS_LIST_LABELS[@]}"` | |
echo "'$pos' '`get-relative-position-from-label $pos`'" | |
if test -n "$pos"; then | |
if test "${#MONITOR_LIST[@]}" -eq "1"; then | |
POS=("`get-relative-position-from-label $pos`" "${MONITOR_LIST[0]}") | |
ACTIONS=( | |
"$LABEL_CONFIG_RESOLUTION$RES" | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX" | |
"$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}`${POS[1]}" | |
) | |
else | |
local monitor=`select-action "${MONITOR_LIST[@]}"` | |
if test -n "$monitor"; then | |
POS=("`get-relative-position-from-label $pos`" "$monitor") | |
ACTIONS=( | |
"$LABEL_CONFIG_RESOLUTION$RES" | |
"$LABEL_CONFIG_REFRESH_RATE${RATE}$LABEL_CONFIG_REFRESH_RATE_POSTFIX" | |
"$LABEL_CONFIG_POSITION`get-relative-position-to-label ${POS[0]}`${POS[1]}" | |
) | |
fi | |
fi | |
fi | |
fi | |
;; | |
*) exit;; | |
esac | |
done | |
} | |
# Gets the first or specified regex capture group in regex result. | |
# | |
# Source: based on some Stack Overflow answer don't remember. | |
# Usage: echo "text" | regex1 REGEX [CAPTURE_GROUP] | |
function regex1 { gawk "match(\$0,/$1/, ary) {print ary[${2:-1}]}"; } | |
# Tries to figure out the relative position of a monitor to | |
# another. | |
# | |
# Usage: get-monitor-relative-position MONITOR | |
get-monitor-relative-position() { | |
local MONITOR="$1" | |
local MONITOR_ORDER=(`xrandr | grep -P '\b(\d+\+\d+)\b' | regex1 '^([^ ]+)'`) | |
local MONITOR_POSITIONS=(`xrandr | grep -oP '\b(\d+\+\d+)\b'`) | |
local MONITOR_RESOLUTIONS=() | |
for i in "${!MONITOR_ORDER[@]}"; do | |
local resolution=`xrandr | awk -v monitor="^${MONITOR_ORDER[i]} connected" '/connected/ {p = 0} $0 ~ monitor {p = 1} p' | grep '*' | cut -d ' ' -f4` | |
if [[ ${MONITOR_ORDER[i]} = $MONITOR ]]; then | |
local MONITOR_POSITION="${MONITOR_POSITIONS[i]}" | |
local MONITOR_RESOLUTION="${resolution}" | |
unset 'MONITOR_ORDER[i]' | |
unset 'MONITOR_POSITIONS[i]' | |
else | |
local MONITOR_RESOLUTIONS+=("${resolution}") | |
fi | |
done | |
IFS="x" read -a MONITOR_RESOLUTION_PARTS <<< $MONITOR_RESOLUTION | |
IFS="+" read -a MONITOR_POSITION_PARTS <<< $MONITOR_POSITION | |
for i in "${!MONITOR_ORDER[@]}"; do | |
IFS="x" read -a resolutionParts <<< ${MONITOR_RESOLUTIONS[i]} | |
IFS="+" read -a positionParts <<< ${MONITOR_POSITIONS[i]} | |
if test "${MONITOR_POSITION_PARTS[0]}" -eq "${positionParts[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${positionParts[1]}"; then | |
echo "same-as ${MONITOR_ORDER[i]}" | |
return | |
elif test "`expr ${MONITOR_POSITION_PARTS[0]} + ${resolutionParts[0]}`" -eq "${MONITOR_POSITION_PARTS[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${positionParts[1]}"; then | |
echo "left-of ${MONITOR_ORDER[i]}" | |
return | |
elif test "`expr ${MONITOR_POSITION_PARTS[0]} - ${resolutionParts[0]}`" -eq "${MONITOR_POSITION_PARTS[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${positionParts[1]}"; then | |
echo "left-of ${MONITOR_ORDER[i]}" | |
return | |
elif test "`expr ${MONITOR_POSITION_PARTS[0]} + ${resolutionParts[0]}`" -eq "${positionParts[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${positionParts[1]}"; then | |
echo "right-of ${MONITOR_ORDER[i]}" | |
return | |
elif test "`expr ${MONITOR_POSITION_PARTS[0]} - ${resolutionParts[0]}`" -eq "${positionParts[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${positionParts[1]}"; then | |
echo "right-of ${MONITOR_ORDER[i]}" | |
return | |
elif test "${MONITOR_POSITION_PARTS[0]}" -eq "${positionParts[0]}" -a "${MONITOR_POSITION_PARTS[1]}" -eq "${resolutionParts[1]}"; then | |
echo "below ${MONITOR_ORDER[i]}" | |
return | |
elif test "${MONITOR_POSITION_PARTS[0]}" -eq "${positionParts[0]}" -a "`expr ${MONITOR_POSITION_PARTS[1]} + ${resolutionParts[1]}`" -eq "${positionParts[1]}"; then | |
echo "above ${MONITOR_ORDER[i]}" | |
return | |
else | |
continue | |
fi | |
done | |
} | |
ACTIONS=() | |
for monitor in "${MONITORS[@]}"; do | |
ACTIONS+=( "$LABEL_MONITOR_AVAILABLE $monitor" ) | |
done | |
for monitor in "${ACTIVE_MONITORS[@]}"; do | |
ACTIONS+=( "$LABEL_MONITOR_OFF $monitor" ) | |
done | |
for monitor in "${ACTIVE_MONITORS[@]}"; do | |
ACTIONS+=( "$LABEL_MONITOR_MONIFY $monitor" ) | |
done | |
ACTION=`select-action "${ACTIONS[@]}" "$LABEL_CONFIG_DENY"` | |
case $ACTION in | |
$LABEL_MONITOR_AVAILABLE*) | |
MONITOR="${ACTION#$LABEL_MONITOR_AVAILABLE}" | |
RES=`get-resolutions $MONITOR | head -n1` | |
RATE=`get-update-rates-for-resolution $MONITOR $RES | head -n1` | |
# TODO: Save for monitor type | |
POSITIONS_LIST=("same-as" "left-of" "right-of" "above" "below") | |
POS="${POSITIONS_LIST[2]} ${ACTIVE_MONITORS[0]}" | |
update-monitor "$LABEL_MONITOR_AVAILABLE_CONFIRM$MONITOR" "$RES" "$RATE" "$POS" | |
;; | |
$LABEL_MONITOR_OFF*) | |
xrandr --output "${ACTION#$LABEL_MONITOR_OFF}" --off | |
if [ -n "$RESTART_WM_CMD" ]; then | |
sleep 1 | |
$RESTART_WM_CMD | |
fi | |
;; | |
$LABEL_MONITOR_MONIFY*) | |
MONITOR="${ACTION#$LABEL_MONITOR_MONIFY}" | |
RES_RATE=`xrandr | awk -v monitor="^$MONITOR connected" '/connected/ {p = 0} $0 ~ monitor {p = 1} p' | grep '*'` | |
RES=`cat <<< $RES_RATE | cut -d ' ' -f4` | |
read -a array <<< $RES_RATE | |
RATE=`for action in "${array[@]}"; do echo $action; done | grep '*'` | |
RATE="${RATE/\*}" | |
RATE="${RATE/+}" | |
update-monitor "$LABEL_MONITOR_MONIFY_CONFIRM$MONITOR" "$RES" "$RATE" "`get-monitor-relative-position $MONITOR`" | |
;; | |
*) ;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment