kaashif's blog

Programming, with some mathematics on the side

Hacking StumpWM with Common Lisp

2015-06-28

Before a few weeks ago, I was always one of those people who said that Lisp isn't useful, it's not type-safe, it's not pure, Haskell is better etc etc ad nauseam. All of that may be true for writing some sorts of programs, but Lisp (well, Common Lisp anyway) provides something a lot more pervasive.

What does pervasive mean? Well, right now, I'm controlling my window

manager and browser through a Lisp REPL from Emacs, and it's a lot more useful (and fun) than it sounds.

Setting up Emacs

You too can get in on this action very easily. First, install a Common Lisp implementation: I recommend SBCL, usually available in repos as "sbcl" (e.g. apt install sbcl). Next, type M-x package-install RET slime RET into Emacs and you'll have already installed the Superior Lisp Interaction Mode for Emacs. It's as good as it sounds, trust me. Next, add the following to your Emacs init file to make sure slime knows where to find Lisp:

(setq inferior-lisp-program "sbcl")

As an aside, you may also want to install slime-company, the completion backend for company-mode for SLIME. Without it, company-mode completion doesn't really work for SLIME. If you do do that, then you'll also want to add the following to your Emacs init file:

(slime-setup '(slime-company))

I assume you already have run global-company-mode (why wouldn't you), but if not, just add (global-company-mode) to the above to turn it on.

Also, you will definitely want to install rainbow-delimiters and paredit-mode, they are essential to any Lisp programming experience. They are, however, not impossible to do without and I won't go over how to use them in this article. Do install them, though, they are really cool.

Quicklisp

The de facto Common Lisp library installer is Quicklisp. You'll definitely need it, and need SLIME set up to work with it. Here's how to do that. First, download quicklisp.lisp and run it (this is copied and pasted from http://www.quicklisp.org/beta/):

$ curl -O https://beta.quicklisp.org/quicklisp.lisp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 49843  100 49843    0     0  33639      0  0:00:01  0:00:01 --:--:-- 50397

$ sbcl --load quicklisp.lisp
This is SBCL 1.0.42.52, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.

SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.

  ==== quicklisp quickstart loaded ====

    To continue, evaluate: (quicklisp-quickstart:install)

* (quicklisp-quickstart:install)

There will be more output, but unless it's something glaringly errorful, you're good to go, just tell Quicklisp to add itself to your .sbclrc and quit:

* (ql:add-to-init-file)
I will append the following lines to #P"/Users/quicklisp/.sbclrc":

  ;;; The following lines added by ql:add-to-init-file:
  #-quicklisp
  (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                         (user-homedir-pathname))))
    (when (probe-file quicklisp-init)
      (load quicklisp-init)))

Press Enter to continue.


#P"/Users/quicklisp/.sbclrc"
* (quit)
$

And that's that, you don't need to worry about telling SBCL about any library stuff again. You do, however, need to add the following to your Emacs init file:

(load (expand-file-name "~/quicklisp/slime-helper.el"))

That will make sure SLIME plays nice with anything you do with Quicklisp in the future (it will see all of the libraries you install, completion will work etc).

Some light Lisp hacking

Now you're ready to get hacking with Lisp! Just to test out SLIME, open a file and input the following:

(defun hello-world ()
  (format t "Hello, world!"))

Now, M-x slime. You'll see something like this:

; SLIME 2015-02-19
CL-USER>

That's the fabled REPL everyone always talks about. You should still be in the buffer with the hello-world function, so type C-c C-l to load it, then C-c C-z to switch to the REPL it was loaded to. You could just use C-x o, but you may have multiple buffers open and it may be more convenient to switch right to the REPL.

Now you can execute the procedure you just wrote by typing (hello-world) and hitting return. You should end up with something like this:

CL-USER> (hello-world)
Hello, world!
NIL

Some of what makes Lisp special

Well that wasn't exciting, you can do the same thing with Python and inf-python, or Ruby and inf-ruby! Anyone can load code from a buffer and play around with it, what makes Lisp special? Well, here's a quote from some guy who works on space stuff:

Debugging a program running on a $100M piece of hardware that is 100 million miles away is an interesting experience. Having a read-eval-print loop running on the spacecraft proved invaluable in finding and fixing the problem.

That's actually a quote from a Lisper at JPL talking about how useful a REPL is for debugging.

Think about it: you're running a window manager, you want to change a tiny bit in the configuration, but you don't want to restart the window manager, that could take seconds if not tens of seconds. You're already in Emacs, so wouldn't it be great if you could control and modify the state and configuration of your WM while it runs?

I certainly think so. There aren't millions of dollars on the line here if your WM crashes, but you could definitely save some time.

Anyway, onto the reason the post exists: StumpWM.

Getting StumpWM set up

You installed Quicklisp earlier, and for good reason. You could install StumpWM using the system package manager, but it tends not to work out well. I couldn't even get StumpWM to start with Debian's stumpwm package, because of errors involving the also installed cl-asdf package. I assume it was out of date, or something wasn't being loaded properly or maybe I'm just an idiot.

Anyway, to install StumpWM, open up SBCL and eval the following:

$ sbcl
* (ql:quickload "stumpwm")

It will take care of all dependencies and everything for you. Best of all, you don't need to fiddle with any manual loading of libraries, since Quicklisp takes care of all of that for you.

Now, replace the last line of your .xinitrc with the following:

exec sbcl --load /path/to/startstump

In that startstump script, place the following:

(require :stumpwm)
(stumpwm:stumpwm)

SBCL already knows about all of the libraries Quicklisp installed, so this will start StumpWM. Just one more thing: you want to be able to debug it live, right? Add the following to your .stumpwmrc (well, create it with the following content):

(in-package :stumpwm)

(require :swank)
(swank-loader:init)
(swank:create-server :port 4004
                     :style swank:*communication-style*
                     :dont-close t)

This won't work until you install the swank library:

$ sbcl
* (ql:quickload "swank")

Going back to the .stumpwmrc, notice how the port is set to 4004? I do that so that when you start SLIME in Emacs, there are no errors because the default port is actually 4005. This ensures you can't mess up your WM by accident while writing unrelated code.

OK, ready for the moment of truth? Kill your X session and run startx and you should see a "Welcome to StumpWM message". If it didn't work, chances are there are some errors in the TTY you started X from. Kill X and look at them if something went wrong. Chances are something went wrong before the swank server started, so you wouldn't be able to use SLIME to fix those errors.

If everything worked, fantastic!

A taste of what's possible

So you're sitting around coding up the next Node.js webscale NoSQL business synergy application when you notice something you want fixed with your window manager. You want to fix it right now with minimal hassle. No worries, you can do it from within Emacs!

M-x slime-connect. When prompted for host, accept 127.0.0.1. When prompted for port, put in 4004 (not 4005). You are now inside the live Lisp image of your WM. Exciting, right? Why not see if you can really control it?

CL-USER> (require :stumpwm)
NIL
CL-USER> (stumpwm:select-window-by-number 1)
NIL

That should've switched to window number 1...so you are in control! Why not rebind a key?

CL-USER> (stumpwm:define-key stumpwm:*root-map* (stumpwm:kbd "u") "exec urxvt")
NIL

Try it: press your prefix key then "u" (by default, C-t u) and a urxvt (replace with your favourite terminal) will spawn.

And you did it all without leaving Emacs or restarting your WM!

I hope this has opened up a whole new world of Lisp hacking for you. For me, it was the gateway drug. I now dream about macros and s-expressions.

Happy hacking!