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")))
订阅系统工匠通讯!
与最新的系统工匠新闻和更新保持同步! 阅读 Newsletter 浏览更多信息。
名称 (optional)
邮箱