StumpWM

Stumpwm is the Emacs of window managers. If you’re using it, you’ll want to put a few thing in your .stumpwmrc.

SLIME

If you’ve enabled multi-threading, add this code to your .stumpwmrc for interactive control of Stumpwm via Slime:

;; Load swank.
;; *prefix-key* ; swank will kick this off
(load "/path/to/slime/swank-loader.lisp")
(swank-loader:init)
(defcommand swank () ()
    (swank:create-server :port 4005
                       :style swank:*communication-style*
                       :dont-close t)
  (echo-string (current-screen) 
	       "Starting swank. M-x slime-connect RET RET, then (in-package stumpwm)."))
(swank)

Multi-threading is available in SBCL, though not in CLisp. Running Swank (or any other command that doesn’t fork or exit) on a single-threaded StumpWM will hang StumpWM. You’ll be able to move the mouse, but the prefix key won’t react anymore. You’ll have to Ctrl+Alt+Backspace to start a new session. You can still hack using the StumpWM Eval functionality though.

Controlling emacs through stumpwm commands

This is an example of how you can use stumpwm commands to call emacs functions.

(defcommand wanderlust () ()
  (emacs)
  (send-meta-key (current-screen) (kbd "M-x"))
  (window-send-string "wl")
  (send-meta-key (current-screen) (kbd "RET")))

Loading swank from a quicklisp installation

Code to put in .stumpwmrc

If, like me, you have installed slime through quicklisp then then /path/to/slime/swank-loader.lisp will not be fixed. The code in the SLIME section works but it would have to be changed every time slime is updated. However in this case you can use quicklisp itself to load swank, by replacing the first two lines of code with:

    (ql:quickload :swank)

Code to put in .emacs or .init.d

If you are using quicklisp you will need the code like the following in your Emacs start-up file:

;; In my case /path/to/quicklisp is ~/quicklisp
(defvar quicklisp-path "/path/to/quicklisp")
;;
;; Load slime-helper, this sets up various autoloads:
;;
(load (concat quicklisp-path "/slime-helper"))
;;
;; Set up slime with whatever modules you decide to use:
;;
(slime-setup '(slime-fancy slime-mrepl slime-banner slime-tramp
	       slime-xref-browser slime-highlight-edits
	       slime-sprof))
;;
;; Decide where to put a slime scratch file, if you want one, and set
;; it up with:
;;
(setf slime-scratch-file "/path/to/.slime-scratch.lisp")
;;
;; Unfortunately there is no hook for the slime-scratch mode so if you
;; want to automatically enable/disable various minor modes in the
;; slime scratch buffer you must do something like:
;;
(defadvice slime-scratch
    (after slime-scratch-adjust-modes () activate compile)
  (turn-some-mode-off)
  (turn-some-other-mode-on))

Security Issues

The usual way to connect to the swank server in emacs is with code like:

    (slime-connect "127.0.0.1" "4005")

Two things are worth noticing. The first is that the port doesn’t have to be 4005, it just has to be the same as the one defined in stumpwm. With a multi-threaded Common Lisp like sbcl this means that you can have more than one swank server running. The second thing to notice is that you don’t have to use the localhost IP number, 127.0.0.1. This means that an attacker could control your window manager from another machine. You may wish to adjust the firewall settings on your machine to take this into account.

If your machine is a laptop that you use on many different networks, you may not wish to automatically start the swank server when stumpwm starts up. If you replace the call to swank on the last line of the code with:

    (define-key *root-map* (kbd "C-s") "swank")

Then the key sequence “C-t C-s”, which you can change if you like, will be bound to the command to start the server, but the server will not start up automatically.

Even better, you can amend the code for the command “swank” so that it toggles the server on/off as follows:

(let ((server-running nil))
  (defcommand swank () ()
    "Toggle the swank server on/off"
    (if server-running
	(progn
	  (swank:stop-server 4005)
	  (echo-string
	   (current-screen)
	   "Stopping swank.")
	  (setf server-running nil))
	(progn
	  (swank:create-server :port 4005
			       :style swank:*communication-style*
			       :dont-close t)
	  (echo-string
	   (current-screen)
	   "Starting swank. M-x slime-connect RET RET, then (in-package stumpwm).")
	  (setf server-running t)))))

Using the stumpish shell

The stumpish shell is used both in the code for lazy loading swank below and in stumpwm-mode. Unfortunately the path to it changes every time quicklisp updates stumpwm. To overcome this problem, put a “fake” stumpish that loads the “real” stumpish somewhere in your PATH, say in ${HOME}/bin if you use that. Don’t forget to make it executable.

#!/bin/bash
#
# `fake' stumpish.
# This loads the latest version of `real' stumpish

# In my case /path/to/quicklisp is ${HOME}/quicklisp; your mileage may be different!

stump_start="/path/to/quicklisp"
stump_middle=$(cat ${stump_start}/dists/quicklisp/installed/releases/stumpwm.txt)
stump_end="contrib/stumpish"

stumpish=${stump_start}/${stump_middle}${stump_end}

# Note the `.' - the `real' stumpish must be sourced!

. ${stumpish}

Note: this has only been tested for Debian Wheezy but should work for any Unix-like operating system. If anyone can work out how to modify it for other operating system, please comment on this.

Lazy Loading Swank

Since Swank is a fairly big package, it would be a good idea not to load it every time the StumpWM starts up.

The Problem

The “obvious” solution doesn’t work. If you write:

    (ql:quickload :swank)
    (swank:create-server :port 4005 ...

inside a function, you come across the problem that Common Lisp first loads and parses a form and only then interprets it. In a case like this, it will not know that swank:create-server exists when it first sees it as swank will not have been loaded. If you are using SBCL then the problem is even more complicated as it incrementally compiles forms as they are loaded. This is one of the few places where this behaviour of SBCL makes a real difference.

The Usual Solution

In most versions of Common Lisp, you can simply intern the symbol create-server as follows:

    (ql:quickload :swank)
    (funcall (intern "create-server" :swank) :port 4005 ....

Then Lisp knows that create-swank is a function when the form is being loaded, and when the form is interpreted, swank has already loaded so the code does what you expect. However, as explained below, this doesn’t work for SBCL.

The Solution for SBCL

Since SBCL tries to compile a form when it is first loaded, this solution will not work. The solution has to be more complex as somehow we must ensure that :swank is loaded before the call to the function even though it is not loaded at start-up. One solution is the use stumpish, stumpwm’s shell, (see section on using stumpish) to load the package. Put the definition of the function is another file something like:

;; swank.lisp -*- Mode: Lisp -*-
(in-package :stumpwm)
;;
;; Load swank
(ql:quickload :swank)
;;
(let ((server-running nil)
      (first-time t))
  ;;
  (defcommand swank () ()
    "Toggle the swank server on/off."
    (if server-running
        (progn
          (swank:stop-server 4005)
          (echo-string
           (current-screen)
           "Stopping swank.")
          (setf server-running nil))
        (progn
          (swank:create-server :port 4005
                               :style swank:*communication-style*
                               :dont-close t)
	  (if first-time
            (echo-string
             (current-screen)
             "Re-starting swank.")
	    (setf first-time nil))
          (setf server-running t)))))
;; Now call the re-defined command
(swank)

Then in your .stumpwmrc put some code like:

(defcommand swank () ()
  "Turn on the swank server the first time.
Load a file that re-defines swank and then calls it."
  ;; Be careful with the quotes!
  (run-shell-command  "stumpish 'eval (load \"/path/to/swank.lisp\")'")
  (echo-string
   (current-screen)
   "Starting swank. M-x slime-connect RET RET, then (in-package stumpwm)."))
;;
(define-key *root-map* (kbd "C-s") "swank")

This looks like magic, but it isn’t! To understand what is happening you have to distinguish between a Common Lisp function and the (interned) name that Lisp uses to refer to it. There are two functions that both appear to be called swank. The first is defined in .stumpwmrc and when you first use this name to call a function it is called. However it loads a file with another function definition, and the code in the file says that from now on the name “swank” shall refer to the new function. The old function still exists, as a Lisp object and runs to completion displaying a message after the new function has been defined! Once the old function has completed, there will no longer be any references to it and it will eventually be garbage-collected.

Using stumpwm-mode with quicklisp

The quicklisp package for stumpwm comes bundled with Shawn Betts’ stumpwm-mode. There is the usual problem that its path changes every time stumpwm is updated. If you wish to be able to use it put code like the following in your Emacs startup file:

(let
    ((stump-ver
      (with-temp-buffer
	(insert-file-contents
	 (concat quicklisp-path
		 "/dists/quicklisp/installed/releases/stumpwm.txt"))
	(buffer-string))))
  (add-to-list 'load-path
	       (format "%s%s%s%s" quicklisp-path "/" stump-ver "contrib/")))
;;
(autoload 'stumpwm-mode "stumpwm-mode" nil t)

Notes: [1] quicklisp-path is defined as in previous code. [2] sumpwm-mode uses the stumpish shell (see section on stumpish.) [3] This solution presumably works for any other Common Lisp that incrementally compiles code, but it has only been tested for SBCL.

Problems with latest quicklisp versions of StumpWM

As of version stumpwm-20140316-git the quicklisp distribution of stumpwm does not contain the contrib directory. The idea is that the various contribs should be in separate quicklisp packages but this has not happened yet. This means that it no longer has stumpish or stumpwm-mode bundled with it. There could also be issues if you lazy load swank, depending on how you do it. A quick hack is to copy the contrib directory from an older installation to the new one. Everything seems to work as advertised above, but it might be a good idea to ensure that the .lisp files in this directory have been compiled with the latest version of your Common Lisp you have installed.

Most of the modules that used to be in the contrib directory can now be found at:

https://github.com/stumpwm/stumpwm-contrib

Which is good news for anyone using them, however stumpish and stumpwm-mode are not included. Stumpwm-mode is now available on melpa but you still need stumpish installed to use it. None of the contrib modules have appeared on quicklisp yet.


CategoryRelated