# -*- mode: org; -*- #+TITLE: {{{hl}}} configuration file #+AUTHOR: Marius Peter #+DATE: <2021-08-01 Sun> #+PROPERTY: header-args :tangle "~/.config/herbstluftwm/autostart" #+PROPERTY: header-args+ :shebang "#!/usr/bin/env bash" #+SETUPFILE: ~/.emacs.d/resources/templates/documents/gnu-default.setup #+INCLUDE: ~/.emacs.d/resources/templates/documents/default.org_title #+MACRO: hl Herbstluftwm * Introduction This is my literate configuration for {{{hl}}} and other programs started at the beginning of a graphical session. {{{hl}}} is a manual tiling window manager and is configured via the ~herbstclient~ program. #+begin_center \Huge\textbf{Herbstluft}\\ \normalsize\textit{autumn air} #+end_center * Early setup ** Definitions We begin by defining a shorthand name for the ~herbstclient~ program: #+NAME: hc #+BEGIN_SRC shell hc() { herbstclient "$@" } #+END_SRC ** Basic We make {{{hl}}} execute code associated with the ~reload~ hook. #+BEGIN_SRC shell hc emit_hook reload #+END_SRC We set the color of the default window bar. #+BEGIN_SRC shell xsetroot -solid '#5A8E3A' #+END_SRC * Keybindings We remove all existing keybindings. #+BEGIN_SRC shell hc keyunbind --all #+END_SRC Choice of Modkey. =Mod1= is =Alt=, =Mod4= is =Super=. #+BEGIN_SRC shell Mod=Mod4 #+END_SRC ** Basic interaction with {{{hl}}} #+BEGIN_SRC shell hc keybind $Mod-Shift-q quit hc keybind $Mod-Shift-r reload hc keybind $Mod-Shift-c close #+END_SRC ** Close focused window or remove frame #+BEGIN_SRC shell hc keybind $Mod-q close_or_remove #+END_SRC ** Application shortcuts *** Menu {{{hl}}} wrapper around dmenu. #+BEGIN_SRC shell hc keybind $Mod-d spawn "/etc/xdg/herbstluftwm/dmenu_run_hlwm" #+END_SRC *** Terminal We use =st=. #+BEGIN_SRC shell hc keybind $Mod-Return spawn "st" #+END_SRC *** TODO Emacs We assign shortcuts to commonly used applications. #+BEGIN_SRC shell hc keybind $Mod-e spawn "emacsclient -nc -s main" #+END_SRC ** Movement Basic movement in tiling and floating mode focusing clients. #+BEGIN_SRC shell hc keybind $Mod-Left focus left hc keybind $Mod-Down focus down hc keybind $Mod-Up focus up hc keybind $Mod-Right focus right hc keybind $Mod-h focus left hc keybind $Mod-j focus down hc keybind $Mod-k focus up hc keybind $Mod-l focus right #+END_SRC Moving clients in tiling and floating mode. #+BEGIN_SRC shell hc keybind $Mod-Shift-Left shift left hc keybind $Mod-Shift-Down shift down hc keybind $Mod-Shift-Up shift up hc keybind $Mod-Shift-Right shift right hc keybind $Mod-Shift-h shift left hc keybind $Mod-Shift-j shift down hc keybind $Mod-Shift-k shift up hc keybind $Mod-Shift-l shift right #+END_SRC ** Splitting frames Create an empty frame at the specified direction. #+BEGIN_SRC shell hc keybind $Mod-u split bottom 0.5 hc keybind $Mod-o split left 0.5 #+END_SRC We let the current frame explode into subframes. #+BEGIN_SRC shell hc keybind $Mod-Control-space split explode #+END_SRC ** Resizing frames and clients Resizing frames and floating clients. #+BEGIN_SRC shell resizestep=0.02 hc keybind $Mod-Control-h resize left +$resizestep hc keybind $Mod-Control-j resize down +$resizestep hc keybind $Mod-Control-k resize up +$resizestep hc keybind $Mod-Control-l resize right +$resizestep hc keybind $Mod-Control-Left resize left +$resizestep hc keybind $Mod-Control-Down resize down +$resizestep hc keybind $Mod-Control-Up resize up +$resizestep hc keybind $Mod-Control-Right resize right +$resizestep #+END_SRC ** Tags *** Basic definitions #+BEGIN_SRC shell tag_names=( {1..5} ) tag_keys=( {1..5} 0 ) hc rename default "${tag_names[0]}" || true for i in "${!tag_names[@]}" ; do hc add "${tag_names[$i]}" key="${tag_keys[$i]}" if ! [ -z "$key" ] ; then hc keybind "$Mod-$key" use_index "$i" hc keybind "$Mod-Shift-$key" move_index "$i" fi done #+END_SRC *** Cycle tags #+BEGIN_SRC shell hc keybind $Mod-period use_index +1 --skip-visible hc keybind $Mod-comma use_index -1 --skip-visible #+END_SRC *** Layouting #+BEGIN_SRC shell hc keybind $Mod-r remove hc keybind $Mod-s floating toggle # Schwebend = floating! hc keybind $Mod-f fullscreen toggle hc keybind $Mod-Shift-f set_attr clients.focus.floating toggle hc keybind $Mod-Shift-m set_attr clients.focus.minimized true hc keybind $Mod-Control-m jumpto last-minimized hc keybind $Mod-p pseudotile toggle #+END_SRC The following cycles through the available layouts within a frame, but skips layouts, if the layout change wouldn't affect the actual window positions. I.e. if there are two windows within a frame, the grid layout is skipped. #+BEGIN_SRC shell hc keybind $Mod-space \ or , and . compare tags.focus.curframe_wcount = 2 \ . cycle_layout +1 vertical horizontal max vertical grid \ , cycle_layout +1 #+END_SRC ** Mouse bindings #+BEGIN_SRC shell hc mouseunbind --all hc mousebind $Mod-Button1 move hc mousebind $Mod-Button2 zoom hc mousebind $Mod-Button3 resize #+END_SRC ** Cycle the active screens #+BEGIN_SRC shell hc keybind $Mod-Tab cycle_all +1 hc keybind $Mod-Shift-Tab cycle_all -1 #+END_SRC This is a ``shorter'' shortcut. #+BEGIN_SRC shell hc keybind $Mod-c cycle_all +1 #+END_SRC ** Jump to to next urgent program Super useful when getting prompted for permissions in a browser on a different tag! #+BEGIN_SRC shell hc keybind $Mod-i jumpto urgent #+END_SRC * Theme #+BEGIN_SRC shell hc attr theme.tiling.reset 1 hc attr theme.floating.reset 1 #+END_SRC ** Colors #+begin_src shell color_main='#666611' xsetroot -solid $color_main #+end_src ** Frames #+BEGIN_SRC shell hc set frame_border_active_color '#222222cc' hc set frame_border_normal_color '#101010cc' hc set frame_bg_normal_color '#565656aa' hc set frame_bg_active_color '#345F0Caa' hc set frame_border_width 1 hc set always_show_frame on hc set frame_bg_transparent on hc set frame_transparent_width 3 #+END_SRC We have a grand total of 4 pixels dedicated to the frame borders, per frame---so six pixels wasted at most. ** Gap between frames We set no gap between frames and other frames/monitors. #+BEGIN_SRC shell hc set frame_gap 0 #+END_SRC ** Windows *** Basic #+BEGIN_SRC shell hc attr theme.title_height 24 hc attr theme.title_font 'Hack:pixelsize=20' # example using Xft # hc attr theme.title_font '-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*' hc attr theme.padding_top 10 # space below the title's baseline (i.e. text depth) hc attr theme.active.color '#345F0Cef' hc attr theme.title_color '#ffffff' hc attr theme.normal.color '#323232dd' hc attr theme.urgent.color '#7811A1dd' hc attr theme.normal.title_color '#898989' hc attr theme.inner_width 1 hc attr theme.inner_color black hc attr theme.border_width 1 hc attr theme.floating.border_width 12 hc attr theme.floating.outer_width 1 hc attr theme.floating.outer_color black hc attr theme.active.inner_color '#789161' hc attr theme.urgent.inner_color '#9A65B0' hc attr theme.normal.inner_color '#606060' #+END_SRC Copy ~inner_color~ to ~outer_color~. #+BEGIN_SRC shell for state in active urgent normal ; do hc substitute C theme.${state}.inner_color \ attr theme.${state}.outer_color C done hc attr theme.active.outer_width 1 hc attr theme.background_color '#141414' #+END_SRC *** Appearance Gap between windows, tiled in a frame. #+BEGIN_SRC shell hc set window_gap 32 #+END_SRC We could reserve extra room on the edges common to all the tiled windows. (Despite being a frame parameter, it really applies to the sum total of tiled windows in a given frame, so it belongs to this section). #+BEGIN_SRC shell hc set frame_padding 0 #+END_SRC If only one frame exists on a given monitor, no border gap is added to the unique frame. But since we set the frame gap to 0, this has no effect. #+BEGIN_SRC shell hc set smart_frame_surroundings off #+END_SRC If only one window exists in a given frame, the border gap between the window and the frame may be omitted. #+BEGIN_SRC shell hc set smart_window_surroundings off #+END_SRC #+BEGIN_SRC shell hc set mouse_recenter_gap 0 #+END_SRC ** Rules First, we cancel all previously bound rules. #+BEGIN_SRC shell hc unrule -F #hc rule class=XTerm tag=3 # move all xterms to tag 3 hc rule focus=on # normally focus new clients hc rule floatplacement=smart #hc rule focus=off # normally do not focus new clients # give focus to most common terminals #hc rule class~'(.*[Rr]xvt.*|.*[Tt]erm|Konsole)' focus=on hc rule windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' floating=on hc rule windowtype='_NET_WM_WINDOW_TYPE_DIALOG' focus=on hc rule windowtype~'_NET_WM_WINDOW_TYPE_(NOTIFICATION|DOCK|DESKTOP)' manage=off hc set tree_style '╾│ ├└╼─┐' #+END_SRC ** Unlock monitors #+BEGIN_SRC shell hc unlock #+END_SRC * Other programs ** Panel #+begin_src shell xset +fp /home/blendux/.fonts/ kill $(pgrep --full "panel.sh") bash ~/.config/herbstluftwm/panel.sh #+end_src This panel is adapted from the one provided by default by {{{hl}}}, located at [[file:/etc/xdg/herbstluftwm/panel.sh]]. I'm using =lemonbar-xft=, which provides XFT support. #+begin_src shell :tangle no font='-*-hack-medium-r-*-*-*-*-*-*-*-*-*-*' font_xft='Hack' # extract colors from hlwm and omit alpha-value bgcolor=$(herbstclient get frame_border_normal_color|sed 's,^\(\#[0-9a-f]\{6\}\)[0-9a-f]\{2\}$,\1,') selbg=$(herbstclient get window_border_active_color|sed 's,^\(\#[0-9a-f]\{6\}\)[0-9a-f]\{2\}$,\1,') selfg='#101010' # Quick maffs monitor_width=$(herbstclient monitor_rect 0 | awk '{ print $3 }') monitor_height=$(herbstclient monitor_rect 0 | awk '{ print $4 }') window_gap=$(herbstclient get window_gap) let "bar_width = monitor_width - (2 * $window_gap)" bar_height=42 offset_horizontal=$window_gap offset_vertical=0 bar_dimensions=${bar_width}x${bar_height}+${offset_horizontal}+${offset_vertical} hlwm_datetime_short () { date +"%R | %F" } hlwm_datetime_long () { date +"%A, %d %B %Y | %R" } hlwm_battery_status () { acpi | awk -F', ' '{ print $3 }' } hlwm_battery_level () { acpi | awk -F', ' '{ print $2 }' } (while true; do echo "%{c}$(hlwm_datetime_short)%{r}$(hlwm_battery_status) $(hlwm_battery_level)" sleep 1 done ) | lemonbar -f ${font_xft} -g $bar_dimensions # -B $selbg #+end_src #+RESULTS: #+begin_src shell :tangle "~/.config/herbstluftwm/panel.sh" quote() { local q="$(printf '%q ' "$@")" printf '%s' "${q% }" } if [[ -f /usr/lib/bash/sleep ]]; then # load and enable 'sleep' builtin (does not support unit suffixes: h, m, s!) # requires pkg 'bash-builtins' on debian; included in 'bash' on arch. enable -f /usr/lib/bash/sleep sleep fi hc_quoted="$(quote "${herbstclient_command[@]:-herbstclient}")" hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} monitor=${1:-0} geometry=( $(hc monitor_rect "$monitor") ) if [ -z "$geometry" ] ;then echo "Invalid monitor $monitor" exit 1 fi # geometry has the format W H X Y x=${geometry[0]} y=${geometry[1]} panel_width=${geometry[2]} panel_height=32 # font="-*-fixed-medium-*-*-*-24-*-*-*-*-*-*-*" font='-*-hack-medium-r-*-*-*-*-*-*-*-*-*-*' # font='Hack:pixelsize=20' # extract colors from hlwm and omit alpha-value bgcolor=$(hc get frame_border_normal_color|sed 's,^\(\#[0-9a-f]\{6\}\)[0-9a-f]\{2\}$,\1,') selbg=$(hc get window_border_active_color|sed 's,^\(\#[0-9a-f]\{6\}\)[0-9a-f]\{2\}$,\1,') selfg='#101010' #### # Try to find textwidth binary. # In e.g. Ubuntu, this is named dzen2-textwidth. if which textwidth &> /dev/null ; then textwidth="textwidth"; elif which dzen2-textwidth &> /dev/null ; then textwidth="dzen2-textwidth"; elif which xftwidth &> /dev/null ; then # For guix textwidth="xftwidth"; else echo "This script requires the textwidth tool of the dzen2 project." exit 1 fi #### # true if we are using the svn version of dzen2 # depending on version/distribution, this seems to have version strings like # "dzen-" or "dzen-x.x.x-svn" if dzen2 -v 2>&1 | head -n 1 | grep -q '^dzen-\([^,]*-svn\|\),'; then dzen2_svn="true" else dzen2_svn="" fi if awk -Wv 2>/dev/null | head -1 | grep -q '^mawk'; then # mawk needs "-W interactive" to line-buffer stdout correctly # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504 uniq_linebuffered() { awk -W interactive '$0 != l { print ; l=$0 ; fflush(); }' "$@" } else # other awk versions (e.g. gawk) issue a warning with "-W interactive", so # we don't want to use it there. uniq_linebuffered() { awk '$0 != l { print ; l=$0 ; fflush(); }' "$@" } fi hc pad $monitor $panel_height { ### Event generator ### # based on different input data (mpc, date, hlwm hooks, ...) this generates events, formed like this: # \t [...] # e.g. # date ^fg(#efefef)18:33^fg(#909090), 2013-10-^fg(#efefef)29 #mpc idleloop player & while true ; do # output is checked once a second, but a "date" event is only # generated if the output changed compared to the previous run. printf 'date\t^fg(#efefef)%(%H:%M)T^fg(#909090), %(%Y-%m)T-^fg(#efefef)%(%d)T\n' sleep 1 || break done > >(uniq_linebuffered) & childpid=$! hc --idle kill $childpid } 2> /dev/null | { IFS=$'\t' read -ra tags <<< "$(hc tag_status $monitor)" visible=true date="" windowtitle="" while true ; do ### Output ### # This part prints dzen data based on the _previous_ data handling run, # and then waits for the next event to happen. separator="^bg()^fg($selbg)|" # draw tags for i in "${tags[@]}" ; do case ${i:0:1} in '#') echo -n "^bg($selbg)^fg($selfg)" ;; '+') echo -n "^bg(#9CA668)^fg(#141414)" ;; ':') echo -n "^bg()^fg(#ffffff)" ;; '!') echo -n "^bg(#FF0675)^fg(#141414)" ;; ,*) echo -n "^bg()^fg(#ababab)" ;; esac if [ ! -z "$dzen2_svn" ] ; then # clickable tags if using SVN dzen echo -n "^ca(1,$hc_quoted focus_monitor \"$monitor\" && " echo -n "$hc_quoted use \"${i:1}\") ${i:1} ^ca()" else # non-clickable tags if using older dzen echo -n " ${i:1} " fi done echo -n "$separator" echo -n "^bg()^fg() ${windowtitle//^/^^}" # small adjustments right="$separator^bg() $date $separator" right_text_only=$(echo -n "$right" | sed 's.\^[^(]*([^)]*)..g') # get width of right aligned text.. and add some space.. width=$($textwidth "$font" "$right_text_only ") echo -n "^pa($(($panel_width - $width)))$right" echo ### Data handling ### # This part handles the events generated in the event loop, and sets # internal variables based on them. The event and its arguments are # read into the array cmd, then action is taken depending on the event # name. # "Special" events (quit_panel/togglehidepanel/reload) are also handled # here. # wait for next event IFS=$'\t' read -ra cmd || break # find out event origin case "${cmd[0]}" in tag*) #echo "resetting tags" >&2 IFS=$'\t' read -ra tags <<< "$(hc tag_status $monitor)" ;; date) #echo "resetting date" >&2 date="${cmd[@]:1}" ;; quit_panel) exit ;; togglehidepanel) currentmonidx=$(hc list_monitors | sed -n '/\[FOCUS\]$/s/:.*//p') if [ "${cmd[1]}" -ne "$monitor" ] ; then continue fi if [ "${cmd[1]}" = "current" ] && [ "$currentmonidx" -ne "$monitor" ] ; then continue fi echo "^togglehide()" if $visible ; then visible=false hc pad $monitor 0 else visible=true hc pad $monitor $panel_height fi ;; reload) exit ;; focus_changed|window_title_changed) windowtitle="${cmd[@]:2}" ;; #player) # ;; esac done ### dzen2 ### # After the data is gathered and processed, the output of the previous block # gets piped to dzen2. } | dzen2 -w $panel_width -x $x -y $y -fn "$font" -h $panel_height \ -e "button3=;button4=exec:$hc_quoted use_index -1;button5=exec:$hc_quoted use_index +1" \ -ta l -bg "$bgcolor" -fg '#efefef' #+end_src ** Dock #+BEGIN_SRC shell if [ $(pgrep plank) ] then pkill plank else plank & fi #+END_SRC ** System information #+begin_src shell if [ $(pgrep conky) ] then pkill conky else conky & fi #+end_src ** Compositor #+BEGIN_SRC shell if [ $(pgrep picom) ] then pkill picom else picom & fi #+END_SRC ** TODO Desktop Background #+begin_src shell ~/.fehbg & #+end_src