Configuring HerbstluftWM with Emacs Lisp

Updates

  • I’ve replaced EXWM with HerbstluftWM in my configuration and I like it!
  • Next video will be about how I show “presentation” slides in Org Mode like in my videos

Configuring HerbstluftWM with Emacs Lisp

Today we’re going to try and write an Emacs Lisp package that can control the venerable Herbstluft Window Manager.

This is a really fun and versatile tiling window manager which (like bspwm) is configured through a command line interface called herbstclient.

An interesting implication of how you configure herbstluftwm is that the configuration file can be written as a script in any language you like so long as it’s at the expected configuration path (~/.config/herbstluftwm/autostart) and properly executable.

Let’s build the configuration package and try to write a simple configuration script in Emacs Lisp!

Here are a few of the things we’ll try to script:

Changing settings variables

Setting the theme

Configuring key bindings

Workspace management

Window management

The final code

(require 'cl-lib)

(defun herbst-run-command (command)
  (start-process-shell-command "herbstclient"
                               nil
                               (format "herbstclient %s" command)))

(defun herbst-run-commands (&rest commands)
  (mapcar #'herbst-run-command commands))

(defun herbst-set (setting-name value)
  (herbst-run-command (format  "set %s %s" setting-name value)))

(herbst-set "frame_gap" 10)

(defmacro herbst-setq (&rest setting-pairs)
  `(progn
     ,@(cl-loop for (key val) on setting-pairs by #'cddr collect
                `(herbst-run-command ,(format "set %s %s"
                                              (string-replace "-" "_" (symbol-name key))
                                              val)))))

(defmacro herbst-set-attr (&rest attr-lists)
  `(herbst-run-commands ,@(herbst--attr-list-to-strings "" attr-lists)))

;; Building up the object path

(defun herbst--attr-list-to-strings (current-path current-item)
  (if current-item
      (if (listp (car current-item))
          ;; This needs to loop
          (apply #'append
                 (mapcar (lambda (item)
                           (herbst--attr-list-to-strings current-path item))
                         current-item))
        (if (keywordp (car current-item))
            (append
             (list (format "attr %s%s %s"
                           current-path
                           (string-replace "-"
                                           "_"
                                           (substring (symbol-name (car current-item)) 1))
                           (cadr current-item)))
             (herbst--attr-list-to-strings current-path (cddr current-item)))
          (if (symbolp (car current-item))
              (herbst--attr-list-to-strings
               (concat current-path
                       (symbol-name (car current-item))
                       ".")
               (cdr current-item)))))
    '()))

;; Speculative key binding syntax
;; (herbst-define-key "S-c" "cycle"
;;                    "S-i" "jumpto urgent"
;;                    "S-q" "close_and_remove")

(provide 'herbstmacs)

The autostart file:

#!/usr/bin/env -S emacs -Q --script

;; TODO: Don't harcode this path!
(load "~/Projects/Code/herbstmacs/herbstmacs.el")

(herbst-setq frame-gap 10
             window-gap 0
             smart-frame-surroundings off)

(herbst-set-attr
 (theme :title-font "'JetBrains Mono:pixelsize=32'"
        :title-height 40
        :title-depth 15
        :outer-width 3
        (floating :border-width 4
                  :outer-width 1
                  :outer-color black)
        (urgent :inner-color "#9A65B0")))
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address