Ivory Siege Tower

mobile construct built of thoughts and parentheses

Interface for StumpWM with Emacs

[2023-09-22]

emacs lisp noweb st_dev stumpwm

My home environment setup is a bit inhomogeneous at a glance, with Guix in Package manager mode on top of foreign Linux distro and StumpWM - a rather esoteric software system - for a DE. Yet I believe it's one of the few manageable ways for me to describe my world: through a single "source of truth" in an Org-Roam repository, inter-weaving configuration for chosen systems.

While tweaking with my StumpWM Config recently I've added a couple of definitions for interfacing with Emacs launched in server mode:

  1. one that stores a selected text as a link for inserting in Org buffer

  2. other that captures text from clipboard to a selected Org-capture template

Those are small but very valuable improvements that impact a lot the way I research topics on the web (or even offline). Most of the captured info comes from the browser and ends up in a dedicated "Backlog" section of my Org-Roam, but the approach doesn't stick with only one selected/currently open buffer. I have a selection menu with actually loaded capture templates! That's possible with the approach I borrowed from this friendly config: one can send lisp commands to a running emacs server (daemon) instance. Essentially it's communication through a socket.

I use this post to try out noweb-weaving. The definitions below also appear inside the StumpWM Config node and are tangled to .stumpwmrc without duplication. Since the source and destination nodes are in separate files (hopefully with fixed relative paths against each other), this thread pointing to that solution come in handy and make org-babel-tangle output the source at a desired point.

ยง Definitions Source Blocks

First I define a rather dumb code walker function that just reflects the form it is given as an argument. "Just reflects" means the form appears "as written" even when called from another lisp package.

I need that because I want to feed forms from StumpWM=Common Lisp image to actually another lisp system (Emacs Lisp), and I don't want package names prepended in front of symbol names by default printing method.

That's exactly what the walker does: it leaves everything untouched except for the symbols - and them it just re-prints within the form. It looks easier than customizing CL Printer behavior for them (which is a better solution ofc).

I stumbled upon the problem occasionally, finding out that commands in StumpWM are defined in :stumpwm package, but then are called in :stumpwm-user through default interface. Working around it with this defun.

  (defun walk-code-cite-symnames (form)
    (cond
      ((symbolp form)
       (read-from-string
        (symbol-name form)))
      ((listp form)
       (cons (walk-code-cite-symnames (car form))
             (walk-code-cite-symnames (cdr form))))
      (t form)))

This sends a form for evaluation in emacs:

  (defun eval-in-emacs (&rest s-exps)
    "Evaluate S-EXPS with emacsclient. Gets result written to string."
    (let ((s-exps-string                  ; walk-code-cite-...
            (write-to-string
             (walk-code-cite-symnames `(progn ,@s-exps))
             :case :downcase)))
      (format *error-output* "Sending to Emacs:~%~a~%" s-exps-string)
      (shell-command-to-string
              (format nil "emacsclient --eval \'~a\'" s-exps-string))))

Application #1: Store some content in org-stored-links list in emacs. Those links are afterwards easy to insert in Org-mode buffers.

  (defun emacs-store-link (content-string)
    (when content-string
      (format nil "^2Stored:^n ~a"
              (eval-in-emacs
               `(push (list ,content-string
                            ,(read-one-line (current-screen) "<Link Description>: "))
                      org-stored-links)))))

Application #2: Send content to an Org-capture template.

The fact that communication with emacs server instance is two-ways allows to select from the actual org-capture-templates list.

  (defun emacs-select-org-capture-template ()
   (second
    (select-from-menu
     (current-screen)
     (read-from-string
      (eval-in-emacs
       '(mapcar
         (lambda (i) (list (second i) (first i)))
         org-capture-templates)))
     "Select Template")))

Push content to selected template (using org capture template key).

  (defun emacs-push-to-template (content-string target-keys)
    (when (and content-string target-keys)
      (eval-in-emacs
       `(org-capture-string
         ,content-string
         ,target-keys))))

Main Sections:

Social Timeline: