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!