#!/usr/bin/zsh # # Setup outputs with xrandr as a backend and dmenu as a frontend # # external commands: cat, cut, dmenu, grep, sed, xrandr # Released under GPLv2 # typeset XRANDR_TXT # readonly; stdout of running xrandr without any options typeset -A OUTPUT_CONFIGURED # key=connected output name typeset -a DISCONNECT_OUTPUTS typeset OPT_MODE='auto' typeset OPT_ROTATION='normal' typeset -ir EXIT_CLEAN=7 typeset -ir ERR_BAD_ARGS=257 typeset -ir ERR_BAD_SELECTION=258 typeset -ir ERR_NO_OPTIONS=259 if ! command -v cat &>/dev/null; then echo 'coreutils seem to be missing. You'\''re gonna have a bad time.' >&2 exit 255 elif ! command -v grep &>/dev/null; then echo 'grep seems to be missing. You'\''re gonna have a bad time.' >&2 exit 255 elif ! command -v xrandr &>/dev/null; then echo 'Ran xrandr-dmenu without xrandr? You'\''re gonna have a bad time.' >&2 exit 255 elif ! command -v dmenu &>/dev/null; then echo 'Ran xrandr-dmenu without dmenu? You'\''re gonna have a bad time.' >&2 exit 255 elif ! xset q &>/dev/null; then echo 'Woah there, cowboy! You need to run this from inside X!' >&2 exit 1 fi XRANDR_TXT="$(xrandr)" || exit $? function () { typeset opt typeset output typeset help_msg="usage: xrandr-dmenu [options] Setup X outputs position, mode, etc Input 'exit' or press 'ESC' at any point to cancel. Options: -m Select mode for outputs -M Use current mode for outputs -r Select rotation for outputs -R Use current rotation for outputs" while getopts mMrRh opt; do case "${opt}" in ('m') OPT_MODE='change' ;; ('M') OPT_MODE='no_change' ;; ('r') OPT_ROTATION='change' ;; ('R') OPT_ROTATION='no_change' ;; ('h') echo "${help_msg}" exit 0 ;; ('?') echo "${help_msg}" exit 1 ;; esac done for output in $(grep ' connected' <<< "${XRANDR_TXT}" | cut -d ' ' -f 1); do OUTPUT_CONFIGURED[${output}]='false' done for output ($(grep ' disconnected' <<< "${XRANDR_TXT}" | cut -d ' ' -f 1)) { DISCONNECTED_OUTPUTS+=("${output}") } } "$@" typeset -r XRANDR_TXT typeset -r OPT_MODE typeset -r OPT_ROTATION typeset -r DISCONNECTED_OUTPUTS function main() { typeset prompt typeset menu typeset output typeset mode typeset rotation typeset position typeset xrandr_cmd # set primary output prompt='Select primary output:' output="$(menu_select "${prompt}" ${(k)=OUTPUT_CONFIGURED})" [ "$output" = "eDP1" ] && bail 1 position='--primary' mode="$(select_mode ${output})" || bail $? rotation="$(select_rotation ${output})" || bail $? OUTPUT_CONFIGURED[${output}]='true' xrandr_cmd="xrandr --output ${output} ${position} ${rotation} ${mode}" # set additional outputs prompt='Select next output:' while ! all_outputs_configured; do menu="$(list_unconfigured_outputs)" output="$(menu_select ${prompt} ${=menu})" || bail $? position="$(select_position ${output})" || bail $? if [[ "${position}" != '--off' ]]; then mode="$(select_mode ${output})" || bail $? rotation="$(select_rotation ${output})" || bail $? fi OUTPUT_CONFIGURED[${output}]='true' xrandr_cmd+=" --output ${output} ${position} ${rotation} ${mode}" done if [ "$(pidof x11vnc)" ]; then killvnc="$(printf "no\nyes" | dmenu -p "Would you like to kill existing vnc display?")" if [ "$killvnc" = "yes" ]; then xrandr --output HDMI2 --off killall -9 x11vnc vnc="$(printf "no\nyes" | dmenu -p "Would you like to connect to your ipad?")" fi else vnc="$(printf "no\nyes" | dmenu -p "Would you like to connect to your ipad?")" fi if [ "$vnc" = "yes" ]; then $HOME/.local/bin/ipad_xrandr.sh pidof x11vnc && notify-send -t 60000 -a " Network info" "$(echo -e "wlan0: $wlan\neth0: $eth\nRunning on port 5900")" fi # forcibly '--off' disconnected outputs for output in ${DISCONNECTED_OUTPUTS}; do xrandr_cmd+=" --output ${output} --off" done # do the deed if ! ${=xrandr_cmd}; then echo "Failed to execute xrandr command:\n${xrandr_cmd}" bail 255 fi } ################################################################################ # Uses dmenu to select the position of a given output relative to an already # configured output. --same-as and --off are considered a position. # Prints in the form of xrandr option (eg, '--right-of DP1') to stdout # Global variables: # ERR_BAD_ARG # Arguments: # $1=name of output to configure # Returns: # ERR_BAD_ARG for no $1 ################################################################################ function select_position() { [[ -z $1 ]] && return "${ERR_BAD_ARG}" typeset output typeset prompt typeset -a menu typeset anchor typeset position typeset selection output="$1" prompt="Select position of ${output}:" for anchor in $(list_configured_outputs); do for position in 'left of' 'right of' 'above' 'below' 'same as'; do menu+=("${position} ${anchor}") done done menu+=('off') selection="$(menu_select "${prompt}" ${menu})" || return $? case "${selection[(w)1]}" in (left|right|same) echo "--${selection/ /-}" ;; (above|below|mirror|off) echo "--${selection}" ;; esac } ################################################################################ # Uses dmenu to display the detected mode options for a given output and lets # the user select a mode to use. Prints choice in xrandr option format # (eg, '--mode 800x600' or '--auto') to stdout # Global variables: # XRANDR_TXT # OPT_MODE # ERR_BAD_ARGS # Arguments: # $1 - name of which output we are working with # Returns: # ERR_BAD_ARGS ################################################################################ function select_mode() { [[ -z $1 ]] && return "${ERR_BAD_ARGS}" typeset output typeset prompt typeset menu typeset selection output="$1" prompt="Select mode for ${output}:" if [[ "${OPT_MODE}" == 'auto' ]]; then echo '--auto' elif [[ "${OPT_MODE}" == 'no_change' ]]; then echo '' else # TODO: make this not ugly. A better sed should negate the need for cut/grep menu="$(echo \"${XRANDR_TXT}\" \ | sed -n '/^'${output}' /,/^[^ ]/ s/ * //p' \ | cut -d ' ' -f 1 \ | grep x \ | cat <(echo auto) -)" selection="$(menu_select "${prompt}" ${=menu})" || return $? if [[ 'auto' == "${selection}" ]]; then echo '--auto' else echo "--mode ${selection}" fi fi } function select_rotation() { [[ -z $1 ]] && return "${ERR_BAD_ARGS}" typeset menu typeset prompt prompt="Select rotation of $1:" menu=('normal' 'inverted' 'left' 'right') if [[ "${OPT_ROTATION}" == 'normal' ]]; then echo '--rotate normal' elif [[ "${OPT_ROTATION}" == 'no_change' ]]; then echo '' else echo -n "--rotate ${selection}" menu_select "${prompt}" ${menu} || return $? fi } function menu_select() { [[ -z "$2" ]] && return ${ERR_BAD_ARGS} typeset selection typeset prompt typeset -a menu prompt="$1" shift menu=($*) if [[ ${#menu} == 1 ]]; then echo "${menu}" else while [[ -z "${menu[(r)${selection}]}" ]]; do echo "${(F)menu}" | dmenu -p "${prompt}" | read selection [[ "${(L)selection}" == 'exit' ]] || [[ -z "${selection}" ]] \ && return ${EXIT_CLEAN} done echo "${selection}" fi } function list_configured_outputs() { typeset -a list typeset output for output in ${(k)OUTPUT_CONFIGURED}; do ${OUTPUT_CONFIGURED[$output]} && list+=("${output}") done echo "${(F)list}" } function list_unconfigured_outputs() { typeset -a list typeset output for output in ${(k)OUTPUT_CONFIGURED}; do ${OUTPUT_CONFIGURED[$output]} || list+=("${output}") done echo "${(F)list}" } function all_outputs_configured() { typeset config for config in ${OUTPUT_CONFIGURED}; do $config || return 257 done return 0 } function bail() { [[ "$1" == ${EXIT_CLEAN} ]] && exit 0 || exit "$1" } main