multithemer 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/bin/bash
  2. # Hard dependencies:
  3. # - bash
  4. # - inotifywait
  5. # - find
  6. # - pidof
  7. # - killall
  8. # All other dependencies are optional:
  9. #
  10. # - xrdb (for Xresources), dmenu, conky, tint2, openbox & xmlstarlet, geany
  11. # If the above executables are not found in PATH the according section is skipped.
  12. #
  13. # - sed: replace color_scheme in geany.conf
  14. ############ USER CONFIG BEGIN #############
  15. #
  16. tmpdir=/tmp # no spaces or funny characters! You have been warned! (see trap further down)
  17. tmpdir_cleanup=1 # set to 0 to keep logs
  18. # let's call these **subthemes**
  19. # They refer to subdirectories of the current theme directory
  20. # setting the "boolean" vars to 1 means the script will try its best to find
  21. # config files & take appropiate action.
  22. # Anything other than 1 means FALSE!
  23. # A subdir called conky, relevant files must include the string conky
  24. conky=1
  25. # For those that use a different conky executable:
  26. conky_cmd=conky
  27. conky_fallback=( "$HOME/.config/conky/sys-vert.conkyrc" "$HOME/.config/conky/journal.conkyrc" )
  28. # how long to wait before conky(s) is/are started, in s:
  29. conkydelay=3
  30. # A subdir called dmenu, the only relevant file must be executable and called dmenu
  31. dmenu=1
  32. dmenu_fallback="$HOME/.config/dmenu/dmenu_fallback"
  33. # A subdir called unst, the only relevant file must be called dunstrc
  34. dunst=1
  35. # A subdir called geany, relevant files (a color scheme) must end in conf
  36. geany=1
  37. # A subdir called openbox-3, the only relevant file must be called themerc
  38. openbox=1
  39. # For example LXDE users might need to change this
  40. ob_rc="${XDG_CONFIG_HOME-"$HOME/.config"}/openbox/rc.xml"
  41. # A subdir called tint2, relevant files must end in tint2rc
  42. tint2=1
  43. tint2_fallback=("$HOME/.config/tint2/tint2rc")
  44. # A subdir called Xresources, relevant files must end in .xres
  45. xresources=1
  46. #
  47. ############ USER CONFIG END #############
  48. me="${0##*/}"
  49. configdir="${XDG_CONFIG_HOME-"$HOME/.config"}/$me"
  50. mkdir -p "$configdir"
  51. source "$configdir/config"
  52. conky_restarter_pid=nothing
  53. # deliberately test without quotes, because I don't know how to use variables
  54. # with spaces in trap
  55. ! [ -d $tmpdir ] && echo "$tmpdir is not a valid temporary directory, exiting." && exit 1
  56. # OK. From now on:
  57. tmpdir="$tmpdir/$me"
  58. mkdir -p "$tmpdir"
  59. [[ "$tmpdir_cleanup" == 1 ]] && trap "rm -r $tmpdir;killall $me" EXIT || trap "killall $me" EXIT
  60. notify_file="$tmpdir/notifications"
  61. notify() {
  62. echo "$1" >> "$notify_file"
  63. echo -e "$1"
  64. }
  65. files() {
  66. # simply checks if these files exist, because 'test -r' can only check for 1 file
  67. # mywiki.wooledge.org/BashFAQ/004
  68. shopt -s nullglob dotglob
  69. files=($@)
  70. (( ${#files[*]} )) && return 0 || return 1
  71. }
  72. conky_restarter() {
  73. if grep '^[[:space:]]*background[[:space:]]=[[:space:]]true' "$1"; then
  74. # If your conky forks to the background, just start it normally and drop the respawn
  75. "$conky_cmd" -c "$config" & return
  76. fi
  77. log="$tmpdir/${file##*/}.log"
  78. restarts=0
  79. while :; do
  80. time="$SECONDS"
  81. "$conky_cmd" -c "$1" >>"$log" 2>&1
  82. printf "\nConky apparently crashed at %($TIMESTAMP_FORMAT)T\n\n" >> "$log"
  83. ((restarts++))
  84. time=$(( SECONDS - time ))
  85. if (( time < 10 )) && (( restarts > 10 )); then
  86. printf "Too many restarts and less than 10s between start and crash - not good. Exiting." >> "$log"
  87. return
  88. fi
  89. sleep 1
  90. done
  91. }
  92. strip_ansi() {
  93. shopt -s extglob # function uses extended globbing
  94. while read -r line; do
  95. echo "${line//$'\e'\[*([0-9;])m/}"
  96. done
  97. }
  98. # Works only on systems that have GTK3 rc files.
  99. # Should Work (tm) on LXDE, LXQt and custom desktops
  100. # If however you set your theme with e.g. XFCE4's Settings dialog, no such
  101. # rc files are created.
  102. watchfile="${XDG_CONFIG_HOME-"$HOME/.config"}/gtk-3.0/settings.ini"
  103. [ ! -r "$watchfile" ] && echo "No readable gtk settings file to watch. Exiting." && exit 1
  104. [ ! -r "$ob_rc" ] && openbox=0 && unset ob_rc
  105. geany_dir="${XDG_CONFIG_HOME-"$HOME/.config"}/geany"
  106. [ -d "$geany_dir" ] && [ -x "$geany_dir" ] || { geany=0; unset geany_dir; }
  107. # for each subtheme: the first folder found wins.
  108. # That way we can extend & override system themes in $HOME
  109. themepath=( "$HOME/.local/share/themes" "/usr/share/themes" )
  110. # will be set to 0 at the end of the first loop
  111. firstrun=1
  112. while :; do
  113. rm -f "$notify_file"
  114. CURRENT_THEME="$(/usr/bin/grep gtk-theme-name "$watchfile")"
  115. CURRENT_THEME="${CURRENT_THEME##*=}"
  116. CURRENT_THEME="${CURRENT_THEME#*\"}"
  117. CURRENT_THEME="${CURRENT_THEME%\"*}"
  118. if [[ "x$CURRENT_THEME" == "x" ]]; then
  119. notify "Could not extract current theme name from $watchfile."
  120. else
  121. echo "$CURRENT_THEME" > "$configdir/current_theme"
  122. for path in "${themepath[@]}"; do
  123. path="$path/$CURRENT_THEME/gtk-2.0/gtkrc"
  124. if [ -r "$path" ]; then
  125. file="$configdir/gtk2-color-scheme"
  126. echo "$CURRENT_THEME" > "$file.current"
  127. grep gtk.color.scheme "$path" | cut -d\" -f2 | sed 's/\\n/\n/g' >> "$file.current"
  128. cp "$file.current" "$file.$CURRENT_THEME"
  129. break
  130. fi
  131. done
  132. # lxappearance uses the folder name to name the theme, NOT what is in
  133. # index.theme (and both gtk2 and gtk3 recognize it), so we just assume that
  134. # CURRENT_THEME is the actual name of the theme directory
  135. if [[ "$conky" == "1" ]] && which "$conky_cmd" >/dev/null; then
  136. sleep "$conkydelay"
  137. done=0
  138. for path in "${themepath[@]}"; do
  139. path="$path/$CURRENT_THEME/conky"
  140. if [ -d "$path" ] && [ -x "$path" ] && files "$path"/*conky*; then
  141. done=1
  142. killall "$conky_cmd"
  143. # make sure it's 10x dead:
  144. for ((i=0;i<10;i++)); do
  145. sleep 0.1
  146. pidof "$conky_cmd" >/dev/null || break
  147. killall -9 "$conky_cmd"
  148. done
  149. for file in "$path"/*conky*; do
  150. sleep 0.1
  151. "$conky_cmd" -c "$file" > "$tmpdir/${file##*/}".log 2>&1 &
  152. notify "Started $conky_cmd -c $file"
  153. done
  154. break # because the first path wins
  155. fi
  156. done
  157. # fallback scenario
  158. if [[ "$done" == 0 ]] && (( ${#conky_fallback[@]} > 0 )); then
  159. killall -q "$conky_cmd"
  160. # make sure it's 10x dead:
  161. for ((i=0;i<10;i++)); do
  162. sleep 0.1
  163. pidof "$conky_cmd" >/dev/null || break
  164. killall -9 "$conky_cmd"
  165. done
  166. for file in "${conky_fallback[@]}"; do
  167. "$conky_cmd" -c "$file" > "$tmpdir/${file##*/}".log 2>&1 &
  168. notify "Started $conky_cmd -c $file"
  169. done
  170. fi
  171. fi & conkypid=$!
  172. if [[ "$dmenu" == "1" ]] && [[ "$firstrun" == 0 ]]; then
  173. done=0
  174. for path in "${themepath[@]}"; do
  175. path="$path/$CURRENT_THEME/dmenu"
  176. if [ -d "$path" ] && [ -x "$path" ] && [ -x "$path"/dmenu ]; then
  177. [ -e "$HOME/bin/dmenu" ] && \
  178. [[ "$(file -b --mime-type "$HOME/bin/dmenu")" != *symlink* ]] && \
  179. mv "$HOME/bin/dmenu" "$HOME/bin/dmenu.$me.bak"
  180. ln -sf "$path/dmenu" $HOME/bin/dmenu
  181. notify "Symlinked $path/dmenu to $HOME/bin"
  182. done=1
  183. break # because the first path wins
  184. fi
  185. done
  186. # fallback scenario
  187. if [[ "$done" == 0 ]] && [ -n "$dmenu_fallback" ]; then
  188. [ -e "$HOME/bin/dmenu" ] && \
  189. [[ "$(file -b --mime-type "$HOME/bin/dmenu")" != *symlink* ]] && \
  190. mv "$HOME/bin/dmenu" "$HOME/bin/dmenu.$me.bak"
  191. ln -sf "$dmenu_fallback" $HOME/bin/dmenu
  192. notify "Symlinked $dmenu_fallback to $HOME/bin"
  193. fi
  194. fi & dmenupid=$!
  195. if [[ "$dunst" == "1" ]] && [[ "$firstrun" == 0 ]]; then
  196. for path in "${themepath[@]}"; do
  197. path="$path/$CURRENT_THEME/dunst"
  198. if [ -d "$path" ] && [ -x "$path" ] && [ -r "$path"/dunstrc ]; then
  199. dest="${XDG_CONFIG_HOME-"$HOME/.config"}/dunst/dunstrc"
  200. [ -e "$dest" ] && \
  201. [[ "$(file -b --mime-type "$dest")" != *symlink* ]] && \
  202. mv "$dest" "$dest.$me.bak"
  203. ln -sf "$path/dunstrc" $dest
  204. killall -q dunst
  205. notify "Symlinked $path/dunstrc to $dest"
  206. break # because the first path wins
  207. fi
  208. done
  209. fi & dunstpid=$!
  210. if [[ "$geany" == "1" ]] && [[ "$firstrun" == 0 ]]; then
  211. for path in "${themepath[@]}"; do
  212. path="$path/$CURRENT_THEME/geany"
  213. if [ -d "$path" ] && [ -x "$path" ] && files "$path"/*conf; then
  214. mkdir -p "$geany_dir/colorschemes"
  215. schemefile=""
  216. for file in "$path"/*conf; do
  217. schemefile="${me}_${CURRENT_THEME}_${file##*/}"
  218. ln -s "$file" "$geany_dir/colorschemes/$schemefile"
  219. notify "Symlinked $schemefile to $geanydir/colorschemes"
  220. if [ -w "$geany_dir/geany.conf" ] && which sed >/dev/null; then
  221. cp -f "$geany_dir/geany.conf" "$geany_dir/geany.conf.$me"
  222. sed -i "s/^color_scheme=.*$/color_scheme=$schemefile/" "$geany_dir/geany.conf"
  223. notify "Set color_scheme=$schemefile in $geany_dir/geany.conf.\nNot effective while geany is running."
  224. fi
  225. done
  226. if [[ "$schemefile" != "" ]] && [ -w "$geany_dir/geany.conf" ] && which sed >/dev/null; then
  227. cp -f "$geany_dir/geany.conf" "$geany_dir/geany.conf.$me"
  228. sed -i "s/^color_scheme=.*$/color_scheme=$schemefile/" "$geany_dir/geany.conf"
  229. fi
  230. break # because the first path wins
  231. fi
  232. done
  233. fi & geanypid=$!
  234. if [[ "$openbox" == "1" ]] && [[ "$firstrun" == 0 ]] && which openbox >/dev/null && which xmlstarlet >/dev/null; then
  235. for path in "${themepath[@]}"; do
  236. path="$path/$CURRENT_THEME/openbox-3"
  237. if [ -d "$path" ] && [ -x "$path" ] && [ -r "$path"/themerc ]; then
  238. cp -f "$ob_rc" "$ob_rc"."$me"
  239. # priceless piece of information: https://superuser.com/a/508143
  240. xmlstarlet ed -L -N o="http://openbox.org/3.4/rc" -u '/o:openbox_config/o:theme/o:name' -v "$CURRENT_THEME" "$ob_rc"
  241. pidof openbox >/dev/null && sleep 1 && openbox --reconfigure
  242. notify "Set openbox theme to $CURRENT_THEME"
  243. break # because the first path wins
  244. fi
  245. done
  246. fi & obpid=$!
  247. if [[ "$xresources" == "1" ]] && which xrdb >/dev/null; then
  248. for path in "${themepath[@]}"; do
  249. path="$path/$CURRENT_THEME/Xresources"
  250. if [ -d "$path" ] && [ -x "$path" ] && files "$path"/*xres; then
  251. while pidof xrdb >/dev/null; do sleep 0.1; done
  252. for file in "$path"/*xres; do
  253. xrdb -merge "$file"
  254. notify "Merged $file into Xrdb"
  255. done
  256. break # because the first path wins
  257. fi
  258. done
  259. fi & xrespid=$!
  260. if [[ "$tint2" == "1" ]] && which tint2 >/dev/null; then
  261. done=0
  262. sleep 2 # otherwise tint2 triggers a restart because "configuration change
  263. # in the root window" - no idea why, I tested thoroughly
  264. for path in "${themepath[@]}"; do
  265. path="$path/$CURRENT_THEME/tint2"
  266. if [ -d "$path" ] && [ -x "$path" ] && files "$path"/*tint2rc; then
  267. killall tint2 >/dev/null && sleep 0.1
  268. for file in "$path"/*tint2rc; do
  269. tint2 -c "$file" 2>&1 | strip_ansi > "$tmpdir/${file##*/}".log &
  270. notify "Started tint2 -c $file"
  271. done
  272. done=1
  273. break # because the first path wins
  274. fi
  275. done
  276. # fallback scenario
  277. if [[ "$done" == 0 ]] && (( ${#tint2_fallback[@]} > 0 )); then
  278. killall tint2 >/dev/null && sleep 0.1
  279. for file in "${tint2_fallback[@]}"; do
  280. tint2 -c "$file" 2>&1 | strip_ansi > "$tmpdir/${file##*/}".log &
  281. notify "Started tint2 -c $file"
  282. done
  283. fi
  284. fi & tint2pid=$!
  285. fi
  286. wait $conkypid $dmenupid $dunstpid $geanypid $obpid $tint2pid $xrespid
  287. echo "Waited for $conkypid $dmenupid $dunstpid $geanypid $obpid $tint2pid $xrespid"
  288. ((firstrun<1)) && notify-send -t 0 "$me" "$(< "$notify_file")"
  289. sleep 1
  290. echo "Waiting for changes..."
  291. inotifywait -e modify "$watchfile"
  292. firstrun=0
  293. done