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"
  • An afternoon

Good To Have:

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

Introduction

For this setup 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 quite able to setup 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 be emacs installed. Now to get Doom Emacs installed, the best way to do that is via the instructions on their github page. Don't worry it's simple but will take a minute on your Rasberry Pi, so it's a good time to get 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 installed and setup, in Doom Emacs edit your config files (SPC f P), don't forget to :~$ doom sync. I recommend displaying the time in the mode bar:

(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 in normal mode you can just type:

: package-install

Or in any other Emacs M-x and typing in package-install will get you to the same place. At the next prompt type exwm and off you go. Installing EXWM also gives you exwm-randr and exwm-systray, 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 setup.

; 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 defaut. In my setup 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. But this does mean you can continue to use Emacs in the 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 when 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.

Quiting

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's 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 quiting 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's my EXWM flavoured ones, if you're in Doom they go in your ~./doom.d/config.el file after you're 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