Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

327 lines
8.8 KiB

  1. #!/usr/bin/zsh
  2. #
  3. # Setup outputs with xrandr as a backend and dmenu as a frontend
  4. #
  5. # external commands: cat, cut, dmenu, grep, sed, xrandr
  6. # Released under GPLv2
  7. #
  8. # TODO: add dpi support
  9. typeset XRANDR_TXT # readonly; stdout of running xrandr without any options
  10. typeset -A OUTPUT_CONFIGURED # key=connected output name
  11. typeset -a DISCONNECT_OUTPUTS
  12. typeset OPT_MODE='auto'
  13. typeset OPT_ROTATION='normal'
  14. typeset -ir EXIT_CLEAN=7
  15. typeset -ir ERR_BAD_ARGS=257
  16. typeset -ir ERR_BAD_SELECTION=258
  17. typeset -ir ERR_NO_OPTIONS=259
  18. if ! command -v cat &>/dev/null; then
  19. echo 'coreutils seem to be missing. You'\''re gonna have a bad time.' >&2
  20. exit 255
  21. elif ! command -v grep &>/dev/null; then
  22. echo 'grep seems to be missing. You'\''re gonna have a bad time.' >&2
  23. exit 255
  24. elif ! command -v xrandr &>/dev/null; then
  25. echo 'Ran xrandr-dmenu without xrandr? You'\''re gonna have a bad time.' >&2
  26. exit 255
  27. elif ! command -v dmenu &>/dev/null; then
  28. echo 'Ran xrandr-dmenu without dmenu? You'\''re gonna have a bad time.' >&2
  29. exit 255
  30. elif ! xset q &>/dev/null; then
  31. echo 'Woah there, cowboy! You need to run this from inside X!' >&2
  32. exit 1
  33. fi
  34. XRANDR_TXT="$(xrandr)" || exit $?
  35. function () {
  36. typeset opt
  37. typeset output
  38. typeset help_msg="usage: xrandr-dmenu [options]
  39. Setup X outputs position, mode, etc
  40. Input 'exit' or press 'ESC' at any point to cancel.
  41. Options:
  42. -m Select mode for outputs
  43. -M Use current mode for outputs
  44. -r Select rotation for outputs
  45. -R Use current rotation for outputs"
  46. while getopts mMrRh opt; do
  47. case "${opt}" in
  48. ('m') OPT_MODE='change' ;;
  49. ('M') OPT_MODE='no_change' ;;
  50. ('r') OPT_ROTATION='change' ;;
  51. ('R') OPT_ROTATION='no_change' ;;
  52. ('h')
  53. echo "${help_msg}"
  54. exit 0
  55. ;;
  56. ('?')
  57. echo "${help_msg}"
  58. exit 1
  59. ;;
  60. esac
  61. done
  62. for output in $(grep ' connected' <<< "${XRANDR_TXT}" | cut -d ' ' -f 1); do
  63. OUTPUT_CONFIGURED[${output}]='false'
  64. done
  65. for output ($(grep ' disconnected' <<< "${XRANDR_TXT}" | cut -d ' ' -f 1)) {
  66. DISCONNECTED_OUTPUTS+=("${output}")
  67. }
  68. } "$@"
  69. typeset -r XRANDR_TXT
  70. typeset -r OPT_MODE
  71. typeset -r OPT_ROTATION
  72. typeset -r DISCONNECTED_OUTPUTS
  73. function main() {
  74. typeset prompt
  75. typeset menu
  76. typeset output
  77. typeset mode
  78. typeset rotation
  79. typeset position
  80. typeset xrandr_cmd
  81. # set primary output
  82. prompt='Select primary output:'
  83. output="$(menu_select "${prompt}" ${(k)=OUTPUT_CONFIGURED})"
  84. if [ "$(pidof x11vnc)" ]; then
  85. killvnc="$(printf "no\nyes" | dmenu -p "Would you like to kill existing vnc display?")"
  86. if [ "$killvnc" = "yes" ]; then
  87. xrandr --output HDMI2 --off
  88. killall -9 x11vnc
  89. vnc="$(printf "no\nyes" | dmenu -p "Would you like to connect a remote display?")"
  90. fi
  91. else
  92. vnc="$(printf "no\nyes" | dmenu -p "Would you like to connect a remote display?")"
  93. fi
  94. if [ "$vnc" = "yes" ]; then
  95. echo "1920x1080" | dmenu -p "Please enter resolution:" | read resolution
  96. [ "$resolution" ] || exit 1
  97. printf "normal\nleft" | dmenu -p "Please enter rotation:" | read rotation
  98. [ "$rotation" ] || exit 1
  99. xrandr --output HDMI2 --off
  100. h=$(echo "$resolution" | cut -d'x' -f1)
  101. w=$(echo "$resolution" | cut -d'x' -f2)
  102. modeline="$(gtf $h $w 60 | tail -n 2 | xargs | sed 's/Modeline [^ ]*//g'| xargs)"
  103. name="${h}x${w}_$$.00"
  104. wlan="$(ip -o -4 addr list wlan0 | awk '{print $4}' | cut -d/ -f1)"
  105. eth="$(ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)"
  106. bash -c "xrandr --newmode \"$name\" $modeline"
  107. if [ "$rotation" = "normal" ]; then
  108. clip="${h}x${w}+0+0"
  109. else
  110. clip="${w}x${h}+0+0"
  111. fi
  112. xrandr --addmode HDMI2 "$name"
  113. xrandr --output HDMI2 --rotation $rotation --mode $name --left-of $output && { x11vnc \
  114. -repeat -clip $clip & } || notify-send -a "Display Manager" "An issue occured"
  115. pidof x11vnc && notify-send -t 60000 -a " Network info" "$(echo -e "wlan0: $wlan\neth0: $eth\nRunning on port 5900")"
  116. daily-update -f
  117. fi
  118. [ "$output" = "eDP1" ] && bail 1
  119. position='--primary'
  120. mode="$(select_mode ${output})" || bail $?
  121. rotation="$(select_rotation ${output})" || bail $?
  122. OUTPUT_CONFIGURED[${output}]='true'
  123. xrandr_cmd="xrandr --output ${output} ${position} ${rotation} ${mode}"
  124. # set additional outputs
  125. prompt='Select next output:'
  126. while ! all_outputs_configured; do
  127. menu="$(list_unconfigured_outputs)"
  128. output="$(menu_select ${prompt} ${=menu})" || bail $?
  129. position="$(select_position ${output})" || bail $?
  130. if [[ "${position}" != '--off' ]]; then
  131. mode="$(select_mode ${output})" || bail $?
  132. rotation="$(select_rotation ${output})" || bail $?
  133. fi
  134. OUTPUT_CONFIGURED[${output}]='true'
  135. xrandr_cmd+=" --output ${output} ${position} ${rotation} ${mode}"
  136. done
  137. # forcibly '--off' disconnected outputs
  138. for output in ${DISCONNECTED_OUTPUTS}; do
  139. xrandr_cmd+=" --output ${output} --off"
  140. done
  141. # do the deed
  142. if ! ${=xrandr_cmd}; then
  143. echo "Failed to execute xrandr command:\n${xrandr_cmd}"
  144. bail 255
  145. fi
  146. }
  147. ################################################################################
  148. # Uses dmenu to select the position of a given output relative to an already
  149. # configured output. --same-as and --off are considered a position.
  150. # Prints in the form of xrandr option (eg, '--right-of DP1') to stdout
  151. # Global variables:
  152. # ERR_BAD_ARG
  153. # Arguments:
  154. # $1=name of output to configure
  155. # Returns:
  156. # ERR_BAD_ARG for no $1
  157. ################################################################################
  158. function select_position() {
  159. [[ -z $1 ]] && return "${ERR_BAD_ARG}"
  160. typeset output
  161. typeset prompt
  162. typeset -a menu
  163. typeset anchor
  164. typeset position
  165. typeset selection
  166. output="$1"
  167. prompt="Select position of ${output}:"
  168. for anchor in $(list_configured_outputs); do
  169. for position in 'left of' 'right of' 'above' 'below' 'same as'; do
  170. menu+=("${position} ${anchor}")
  171. done
  172. done
  173. menu+=('off')
  174. selection="$(menu_select "${prompt}" ${menu})" || return $?
  175. case "${selection[(w)1]}" in
  176. (left|right|same) echo "--${selection/ /-}" ;;
  177. (above|below|mirror|off) echo "--${selection}" ;;
  178. esac
  179. }
  180. ################################################################################
  181. # Uses dmenu to display the detected mode options for a given output and lets
  182. # the user select a mode to use. Prints choice in xrandr option format
  183. # (eg, '--mode 800x600' or '--auto') to stdout
  184. # Global variables:
  185. # XRANDR_TXT
  186. # OPT_MODE
  187. # ERR_BAD_ARGS
  188. # Arguments:
  189. # $1 - name of which output we are working with
  190. # Returns:
  191. # ERR_BAD_ARGS
  192. ################################################################################
  193. function select_mode() {
  194. [[ -z $1 ]] && return "${ERR_BAD_ARGS}"
  195. typeset output
  196. typeset prompt
  197. typeset menu
  198. typeset selection
  199. output="$1"
  200. prompt="Select mode for ${output}:"
  201. if [[ "${OPT_MODE}" == 'auto' ]]; then
  202. echo '--auto'
  203. elif [[ "${OPT_MODE}" == 'no_change' ]]; then
  204. echo ''
  205. else
  206. # TODO: make this not ugly. A better sed should negate the need for cut/grep
  207. menu="$(echo \"${XRANDR_TXT}\" \
  208. | sed -n '/^'${output}' /,/^[^ ]/ s/ * //p' \
  209. | cut -d ' ' -f 1 \
  210. | grep x \
  211. | cat <(echo auto) -)"
  212. selection="$(menu_select "${prompt}" ${=menu})" || return $?
  213. if [[ 'auto' == "${selection}" ]]; then
  214. echo '--auto'
  215. else
  216. echo "--mode ${selection}"
  217. fi
  218. fi
  219. }
  220. function select_rotation() {
  221. [[ -z $1 ]] && return "${ERR_BAD_ARGS}"
  222. typeset menu
  223. typeset prompt
  224. prompt="Select rotation of $1:"
  225. menu=('normal' 'inverted' 'left' 'right')
  226. if [[ "${OPT_ROTATION}" == 'normal' ]]; then
  227. echo '--rotate normal'
  228. elif [[ "${OPT_ROTATION}" == 'no_change' ]]; then
  229. echo ''
  230. else
  231. echo -n "--rotate ${selection}"
  232. menu_select "${prompt}" ${menu} || return $?
  233. fi
  234. }
  235. function menu_select() {
  236. [[ -z "$2" ]] && return ${ERR_BAD_ARGS}
  237. typeset selection
  238. typeset prompt
  239. typeset -a menu
  240. prompt="$1"
  241. shift
  242. menu=($*)
  243. if [[ ${#menu} == 1 ]]; then
  244. echo "${menu}"
  245. else
  246. while [[ -z "${menu[(r)${selection}]}" ]]; do
  247. echo "${(F)menu}" | dmenu -p "${prompt}" | read selection
  248. [[ "${(L)selection}" == 'exit' ]] || [[ -z "${selection}" ]] \
  249. && return ${EXIT_CLEAN}
  250. done
  251. echo "${selection}"
  252. fi
  253. }
  254. function list_configured_outputs() {
  255. typeset -a list
  256. typeset output
  257. for output in ${(k)OUTPUT_CONFIGURED}; do
  258. ${OUTPUT_CONFIGURED[$output]} && list+=("${output}")
  259. done
  260. echo "${(F)list}"
  261. }
  262. function list_unconfigured_outputs() {
  263. typeset -a list
  264. typeset output
  265. for output in ${(k)OUTPUT_CONFIGURED}; do
  266. ${OUTPUT_CONFIGURED[$output]} || list+=("${output}")
  267. done
  268. echo "${(F)list}"
  269. }
  270. function all_outputs_configured() {
  271. typeset config
  272. for config in ${OUTPUT_CONFIGURED}; do
  273. $config || return 257
  274. done
  275. return 0
  276. }
  277. function bail() {
  278. [[ "$1" == ${EXIT_CLEAN} ]] && exit 0 || exit "$1"
  279. }
  280. main