Emacs X Window Manager On Raspberry Pi

/images/blog/exwm.jpg

Requirements

Essential:

  • Raspberry Pi 4
  • 4GB RAM (or more)
  • Raspberry Pi OS (previously called Raspbian)
  • "Desktop peripherals" (e.g., monitor, keyboard, mouse)
  • An afternoon

Good To Have:

  • Experience with Emacs
  • Experience with Doom Emacs
  • Experience with Vim
  • Some ability to read LISP

Introduction *

For this set-up, we'll be using Doom Emacs because it is a "Vim Emacs" and loads much quicker than Spacemacs. But if you're an Emacs veteran, you'll be able to set up your system how you like.

Step 1: Get Emacs Running On Raspberry Pi OS

First we need Emacs, and in this case, Doom Emacs. If you've installed Raspberry Pi OS with a desktop, you can open up a terminal to do this. At the prompt, enter the command:

sudo apt install emacs

That’ll have Emacs installed. Now to get Doom Emacs installed, the best way is via the instructions on their GitHub page. Don’t worry, it’s simple, but it will take a minute on your Raspberry Pi, so it’s a good time to grab a cup of tea, ready for the next steps.

Once everything there is installed, open Emacs and verify it’s all working as you want. Now is a good time to get your favourite packages and config all set up. In Doom Emacs, edit your config files (SPC f P), and don’t forget to run doom sync in the terminal. I recommend displaying the time in the mode line:

(setq display-time-24hr-format 1)
(setq display-time-day-and-date 1)
(display-time-mode 1)

Step 2: Get EXWM Installed And Configured

Installing EXWM is the same as installing any Emacs package. In Doom Emacs, use SPC h p i (or M-x package-install in any Emacs), then type exwm at the prompt, and off you go. Installing EXWM also gives you exwm-randr and exwm-systemtray, both of which we’ll use in the config.

Let’s do that config now—in Doom, this’ll go in /.doom.d/config.el. We’ll go from top to bottom. Before even starting with EXWM, I recommend getting the focus to follow the mouse, which is very handy with a dual-monitor set-up:

; Focus follows mouse (must define prior to exwm)
(setq mouse-autoselect-window t
      focus-follows-mouse t)
(package-initialize)

Next we need EXWM! We’ll use it with the included default config:

; Load exwm
(require 'exwm)
(require 'exwm-config)
(exwm-config-default)

It’s good to have a systray for notifications and use by some applications such as VLC. Including it is simple:

(require 'exwm-systemtray)
(exwm-systemtray-enable)

The next thing to sort out is your monitors and workspaces. For the monitors, we use exwm-randr. Here I’ll show both a single and dual monitor config for Raspberry Pi. If you’re adapting this to some other device, you’ll need to run xrandr to check the names of your monitors. I’m also assuming a monitor resolution of 1920x1080—you may need to run xrandr in the terminal to check your resolution if things don’t look quite right. Here’s my xrandr info; the names of my monitors are "HDMI-1" and "HDMI-2":

paul@raspberrypi:~$ xrandr | grep connected
HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 598mm x 336mm
HDMI-2 connected 1920x1080+1920+0 (normal left inverted right x axis y axis) 598mm x 336mm

Single Monitor

(require 'exwm-randr)
(add-hook 'exwm-randr-screen-change-hook
          (lambda ()
            (start-process-shell-command
             "xrandr" nil "xrandr --output HDMI-1 --primary --mode 1920x1080 --pos 0x0 --rotate normal")))
(setq exwm-workspace-number 9)
(exwm-randr-enable)

Dual Monitor

(require 'exwm-randr)
(add-hook 'exwm-randr-screen-change-hook
          (lambda ()
            (start-process-shell-command
             "xrandr" nil "xrandr --output HDMI-1 --primary --mode 1920x1080 --pos 0x0 --rotate normal --output HDMI-2 --right-of HDMI-1 --mode 1920x1080 --pos 0x0 --rotate normal")))

We also need to assign each workspace to a monitor. You can do this manually like so:

(setq exwm-randr-workspace-output-plist '(0 "HDMI-1" 1 "HDMI-1" 2 "HDMI-2" 3 "HDMI-2"))

Unassigned workspaces will be assigned to the primary monitor by default. In my set-up, I use a little code to assign even-numbered workspaces to "HDMI-2", odd-numbered ones being left to the primary "HDMI-1" monitor:

(setq exwm-randr-workspace-output-plist (seq-reduce (lambda (acc s) (cons s (cons "HDMI-2" acc))) (number-sequence 8 0 -2) '()))

Finally, enable it for lots of workspaces:

(exwm-randr-enable)
(setq exwm-workspace-number 9)

The Last Line

No matter whether you have 1 or 2 monitors, the last line to include in your EXWM config is to enable it:

(exwm-enable)

Step 3: Make `startx` Launch EXWM

Now if you open Emacs in the Raspberry Pi OS desktop environment, it’ll ask you if you want to replace the current window manager—you can try, but it won’t work properly. This does mean you can continue to use Emacs in Raspberry Pi OS just as Emacs.

To make EXWM launch as your window manager, you need a ~/.xinitrc file with the following content:

#!/bin/sh
exec emacs

Now when you type startx after powering the Raspberry Pi on to the command line, instead of Raspberry Pi OS, you’ll get EXWM.

Essentials of Using EXWM

This little section is designed to give you a head start in using EXWM. For more (and perhaps up-to-date) detail, check their docs.

Quitting

To quit, type C-x C-c.

Changing Workspace

Workspaces are numbered; to switch, press Super-w, then select the number you want. If the workspace is in view and you added the mouse config above, you can just move the mouse to the workspace.

Launching Applications

To launch an application in a buffer, press Super &, then type the name of the application at the prompt. Here are a few useful ones on the Raspberry Pi:

  • chromium-browser
  • x-terminal-emulator
  • vlc

C-c in Applications

C-c is an Emacs shortcut, so if you want to copy some text out of your web browser or send C-c to your terminal emulator, you’re in a bit of a bother. Instead, you need to type: C-c C-q C-c.

Vim Warning

If you’re in Doom Emacs and happen to launch Vim, best of luck quitting it! All your Vim shortcuts will be intercepted by Emacs, including ESC and :. Best to use Emacs for editing your files.

Bonus Keybindings for Tiling Window Manager Users

If you’ve been around a bit with tiling window managers, you’ll likely be used to a few standard keybindings—me too. So here are my EXWM-flavoured ones. If you’re in Doom, they go in your ~/.doom.d/config.el file after you’ve required EXWM:

; Keybindings
(defun open-program (name command)
  "Open the program in a buffer called name via the command"
  (start-process-shell-command name nil command))

(defun program-prompt (command)
  (interactive (list (read-shell-command "$ ")))
  (open-program command command))

(defun open-terminal ()
  "Open the default terminal"
  (interactive)
  (open-program "terminal" "x-terminal-emulator"))

(defun open-browser ()
  "Open the default browser"
  (interactive)
  (open-program "browser" "x-www-browser"))

(defconst keybindings `((,(kbd "s-<tab>") evil-window-next)
                        ; n for next
                        (,(kbd "s-n")     evil-window-next)
                        (,(kbd "s-N")     evil-window-previous)
                        ; s for split
                        (,(kbd "s-s")     evil-window-vsplit)
                        (,(kbd "s-S")     evil-window-split)
                        ; d for delete
                        (,(kbd "s-d")     kill-buffer)
                        ; x for close?!
                        (,(kbd "s-x")     kill-this-buffer)
                        ; c for change
                        (,(kbd "s-c")     exwm-workspace-swap)
                        ; m for move
                        (,(kbd "s-m")     exwm-workspace-move)
                        ; k for key
                        (,(kbd "s-k")     exwm-input-send-next-key)
                        ; p for prompt
                        (,(kbd "s-p")     program-prompt)
                        ; q for qool browser?
                        (,(kbd "s-q")     open-browser)
                        ; standard for tiling window managers
                        (,(kbd "s-<return>") open-terminal)
                        ))

; Apply the keybindings
(mapc #'(lambda (mapping) (apply 'exwm-input-set-key mapping)) keybindings)

I find it handy having a few non-EXWM commands bound to Super—mostly it’s about cutting down on keystrokes and accessing these functions in application buffers. Well-chosen keybindings go a long way to making life in EXWM a pleasant experience.

Until next time, Happy Emacs/Vim/Raspberry Pi playing!

Paul