complete-computing-environment/exwm.org

7.3 KiB

EXWM

(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 frames1[Modern 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.
(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.

(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

(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

(use-package exwm-edit)

1

[[id:modern_interface_terms