The Complete Computer

EXWM

LifeTechEmacsArcology
#+ARROYO_EMACS_MODULE: exwm
emacs-lisp source: :tangle ~/org/cce/exwm.el
(provide 'cce/exwm)

The Emacs X11 Window Manager is a desktop window manager written in and for Emacs . EXWM takes over my desktop's window decorations, opting instead to treat windows as if they were Emacs buffers, able to exist within windows carved out of framesfn1Modern Interface Terms .

Right now, I use EXWM everywhere that I can get away with using it. It's more than simply an argument that "everything should run in Emacs because it should" that a lot of people get trapped in to thinking this is about. Emacs is actually a surprisingly facile window manager once I gain facility with it. Stacking window managers are, for me, a great way of organizing applications on a system, and the Emacs windowing system exposes a really decent stacking window manager. The keybindings for this system are designed such that it's even a serviceable window manager on small devices like a GPD Pocket

It's configured with some simple quality-of-life improvements, outlined in short below:

  • Buffers are named in Emacs based on both the window class and title, so that it's reasonable to find them again.

  • There are two workspaces, that's all. I sometimes will create extra workspaces, if I am working with long-running desktop applications, simply using C-x 5 2 aka make-frame-command. Switching between them is as easy as hitting S-j. Switching windows is bound close to this at S-k.

  • S-p brings up a simple shell command runner, I'd like to add .desktop support eventually.

  • EXWM runs in ido support mode, which allows it to work with other things that interact with the minibuffer.

emacs-lisp source: :noweb yes :tangle ~/org/cce/exwm.el
(use-package exwm :init (setq exwm-debug nil) :commands (exwm-enable) :config (setq exwm-workspace-number 6) ;; rename buffers to match window title (defun cce/exwm-rename-buffer () (interactive) (exwm-workspace-rename-buffer (format "%s:%s" exwm-class-name (if (<= (length exwm-title) 50) exwm-title (concat (substring exwm-title 0 49) "..."))))) ;; basic keybindings (defun cce/exwm-workspace-next (&optional reverse) (interactive "P") (let ((fn (if reverse #'- #'+))) (exwm-workspace-switch (mod (apply fn (list 1 exwm-workspace-current-index)) exwm-workspace-number)))) (exwm-input-set-key (kbd "s-j") #'cce/exwm-workspace-next) (exwm-input-set-key (kbd "s-k") #'other-window) (exwm-input-set-key (kbd "s-r") #'exwm-reset) (exwm-input-set-key (kbd "s-w") #'exwm-workspace-switch) ; (exwm-input-set-key (kbd "s-o") #'evil-window-rotate-downwards) (exwm-input-set-key (kbd "s-o") #'other-window) (push (kbd "s-j") exwm-input-prefix-keys) (push (kbd "s-k") exwm-input-prefix-keys) (push (kbd "s-r") exwm-input-prefix-keys) (push (kbd "s-o") exwm-input-prefix-keys) (mapcar (lambda (it) (exwm-input-set-key (kbd (format "s-%d" it)) `(lambda () (interactive) (exwm-workspace-switch-create ,it)))) (number-sequence 0 exwm-workspace-number)) ;; go to previously selected workspace (defun cce/exwm-record-before-workspace-change (ws &optional force) (setq cce-exwm-last-workspace exwm-workspace--current)) (advice-add #'exwm-workspace-switch :before #'cce/exwm-record-before-workspace-change) (defun cce-exwm-workspace-back () (interactive) (exwm-workspace-switch cce-exwm-last-workspace)) (exwm-input-set-key (kbd "s-<tab>") #'cce-exwm-workspace-back) ;; switch buffers on different displays (setq exwm-workspace-show-all-buffers t) (setq exwm-layout-show-all-buffers t) ;; xrandr <<exwm-xrandr>> ;; manage configurations <<exwm-manage-configurations>> ;; startup handling (defcustom cce-start-exwm nil "Whether Emacs should try to start EXWM on startup" :type 'boolean) (defun cce/exwm-init-command-line-args () "hook for command-line-functions" (if (setq command-line-args (delete command-line-args "--no-exwm")) (setq cce-start-exwm nil) (setq cce-start-exwm t)) (if (setq command-line-args (delete command-line-args "--exwm")) (setq cce-start-exwm t) (setq cce-start-exwm nil))) (add-to-list 'command-line-functions #'cce/exwm-init-command-line-args) (defun cce/exwm-init (&rest args) (when cce-start-exwm (apply #'exwm-init args))) (exwm-input--update-global-prefix-keys) :hook (exwm-update-class . cce/exwm-rename-buffer) (exwm-update-title . cce/exwm-rename-buffer) (after-init . cce/exwm-init))

EXWM also supports multiple monitors, using the fairly well supported XRANDR protocols. When I am in multi-monitor configurations, I prefer to spread my two workspaces across those desktops, rather than multiplying. S-j in this mode of operation will swap between the two displays and this can be enhanced later on with things like mff-mode. When my display status changes, when I plug or unplug devices, EXWM will run cce/refresh-display-scale defined in Themeing my Emacs . This is run in the use-package config stage, it is embedded in the use-package statement using Literate Programming noweb syntax .

emacs-lisp source: :tangle no
(require 'exwm-randr) (defun cce/exwm-zip-connected-displays () (setq exwm-randr-workspace-monitor-plist (pcase (system-name) ("kusanagi" '(0 "DP-4" 1 "DP-2")) (_ (cce/exwm-connected-displays))))) (add-hook 'exwm-randr-screen-change-hook #'cce/exwm-zip-connected-displays) (add-hook 'exwm-randr-screen-change-hook #'cce/refresh-display-scale) (exwm-randr-enable)

Rules for new Windows

emacs-lisp source: :noweb-ref exwm-manage-configurations
(unless (boundp 'exwm-manage-configurations) (setq exwm-manage-configurations nil)) (mapcar (lambda (it) (add-to-list 'exwm-manage-configurations it)) (list '((equal exwm-class-name "Cantata") workspace 3) '((equal exwm-class-name "Signal") workspace 2) '((equal exwm-class-name "Element") workspace 2) '((equal exwm-class-name "virt-manager") workspace 5) '((s-contains? "Computers :(" exwm-title) workspace 2) '((s-contains? "Picture-in-Picture" exwm-title) floating nil)))

Deeper Web Integration with Emacs

eventually move this in to the firefox integration page

emacs-lisp source: :tangle ~/org/cce/exwm.el
(use-package exwm-edit)