Complete Computing Environment: EXWM Configuration

Table of Contents


EXWM is a window manager written in Emacs. Some crazy cat went off and implemented XCB in Emacs-Lisp and then wrote a window manager with it. It works surprisingly well, and you can do some incredibly neat things with it, since it's adhering really nicely to the X11 standard stuffs.

(provide 'cce-exwm)

Install slock, my lockscreen of choice

- name: slock installed
    name: slock
    state: installed
  when: ansible_pkg_mgr == "dnf"

- name: slock installed
    name: slock
    state: installed
  when: ansible_pkg_mgr == "apt"

Install exwm using MELPA.

(install-pkgs '(cl-generic exwm))


(setq exwm-debug-on nil)
(require 'xcb)
(require 'exwm)

GIMP needs to be floating.

(add-hook 'exwm-update-class-hook
          (lambda ()
            (unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                        (string= "gimp" exwm-instance-name))
              (rename-buffer exwm-class-name t))))
(add-hook 'exwm-update-title-hook
          (lambda ()
            (when (or (string-prefix-p "sun-awt-X11-" exwm-instance-name)
                      (string= "gimp" exwm-instance-name))
              (rename-buffer exwm-title t))))

Rename buffers to match the X11 window class or title:

(defun exwm-rename-buffer ()
   (concat exwm-class-name ":"
           (if (<= (length exwm-title) 50) exwm-title
             (concat (substring exwm-title 0 49) "...")))))

;; Add these hooks in a suitable place (e.g., as done in exwm-config-default)
(add-hook 'exwm-update-class-hook 'exwm-rename-buffer)
(add-hook 'exwm-update-title-hook 'exwm-rename-buffer)

Set up keybindings; S-r will reset the window manager if you wedge it, S-w will bring up an interactive workspace switcher, and S-0 through S-4 will switch through virtual workspaces.

(exwm-input-set-key (kbd "s-r") 'exwm-reset)
(exwm-input-set-key (kbd "s-w") 'exwm-workspace-switch)
(setq exwm-workspace-number 2)

(defun exwm-workspace-next ()
  (let ((next-numb (mod (+ 1 exwm-workspace-current-index) exwm-workspace-number)))
    (exwm-workspace-switch next-numb)))
(exwm-input-set-key (kbd "s-j") 'exwm-workspace-next)
(push (kbd "s-j") exwm-input-prefix-keys)

(exwm-input-set-key (kbd "s-o") 'other-window)
(exwm-input-set-key (kbd "s-k") 'other-window)
(push (kbd "s-o") exwm-input-prefix-keys)
(push (kbd "s-k") exwm-input-prefix-keys)
(push (kbd "<escape>") exwm-input-prefix-keys)

(defmacro exwm-switch-to-workspace-key (ws-num)
  `(progn (exwm-input-set-key (kbd (concat "s-" ,(number-to-string ws-num)))
                              (lambda ()
                                (exwm-workspace-switch ,ws-num)))
          (let ((key-num (if (eq 0 ,ws-num)
            (exwm-input-set-key (kbd (concat "s-<f" (number-to-string key-num) ">"))
                                (lambda ()
                                  (exwm-workspace-switch ,ws-num))))))
(exwm-switch-to-workspace-key 1)
(exwm-switch-to-workspace-key 0)

Super-p to run commands without dumping output to a buffer.

(add-to-list 'display-buffer-alist
             `(,(rx bos " *async command*")

(defun background-shell-command (command)
  (interactive (list (read-shell-command "$ ")))
  (async-shell-command command (generate-new-buffer " *async command*")))

(exwm-input-set-key (kbd "s-p")
(require 'exwm-config)
(push ?\C-q exwm-input-prefix-keys)
(define-key exwm-mode-map [?\C-q] 'exwm-input-send-next-key)

(setq exwm-input-simulation-keys
  `((,(kbd "C-b") . left)
    (,(kbd "C-f") . right)
    (,(kbd "C-p") . up)
    (,(kbd "C-n") . down)
    (,(kbd "C-a") . home)
    (,(kbd "C-e") . end)
    (,(kbd "C-y") . ,(kbd "C-v"))
    (,(kbd "C-M-b") . ,(kbd "C-b"))))
(when (cond ((equal "work" (system-name)) t)
            ((equal "beebs" (system-name)) t)
            ((equal "kusanagi" (system-name)) t)
            ((equal "tres-ebow" (system-name)) t)
            (t nil))
    (message "Enabling EXWM.")

EXWM supports multiple desktops using XRANDR; I pin EXWM workspaces to particular outputs, assuming they're attached this will "just work". What it means in practice is that by default at work my left monitor is workspace 0, and my right monitor is workspace 1. If these aren't attached, they fall back to the active display, which is nice.

(require 'exwm-randr)
(setq exwm-randr-workspace-output-plist '(1 "DP-1" 0 "HDMI-1" 2 "HDMI2"))
; (setq exwm-randr-workspace-output-plist '(1 "eDP-1" 0 "HDMI-2"))
(add-hook 'exwm-randr-screen-change-hook #'cce/refresh-display-scale)

Author: Ryan Rix

Created: 2019-05-07 Tue 11:12

Validate XHTML 1.0