Setting up EWM
If you have a flake.nix based setup, good for you!
If not, RTFM.
My personal setup
Nix
I also use the emacs-overlay. However, instead of linking my Emacs configuration from Nix as in the example, I still load it at runtime.
programs.ewm = {
enable = true;
emacsPackage = pkgs.emacs-git-pgtk.pkgs.withPackages (epkgs: [
epkgs.mu4e
epkgs.offlineimap
epkgs.pdf-tools
epkgs.nushell-ts-mode
config.programs.ewm.ewmPackage
(epkgs.treesit-grammars.with-grammars (grammars: [
grammars.tree-sitter-nu
]))
]);
};Emacs configuration
EWM comes with the default tiling window manager keybinds out of the box, which you can of course override. I was able to move around the workspaces like in any other WM. However, this will not be your typical EXWM experience as far as I can remember.
I suggest using my EWM setup as a reference. It's also very advisable to have a good amount of packages, as they may interfere with your startup. In my case, emacs-dashboard was interfering with startup, and it took me 2-3 days to get behind that issue. Of course, my fix is included. And being good FOSS citizens, I will report this bug upstream.
(with-eval-after-load 'ewm
(require 'ewm-transient)
;; ── Bugfix: guard against nil output in pointer functions ──
;; ewm-input--pointer-in-window-p crashes with "stringp, nil" when
;; called on a frame without ewm-output (e.g. the initial daemon frame
;; during startup when dashboard does switch-to-buffer).
(defun ewm-input--pointer-in-window-p (window)
"Return non-nil if pointer is inside WINDOW. Safe for non-EWM frames."
(let* ((frame (window-frame window))
(output (frame-parameter frame 'ewm-output)))
(when output
(let* ((output-offset (ewm--get-output-offset output))
(edges (window-inside-pixel-edges window))
(left (+ (car output-offset) (nth 0 edges)))
(top (+ (cdr output-offset) (nth 1 edges)))
(right (+ (car output-offset) (nth 2 edges)))
(bottom (+ (cdr output-offset) (nth 3 edges)))
(pointer (ewm-get-pointer-location))
(px (car pointer))
(py (cdr pointer)))
(and (<= left px right)
(<= top py bottom))))))
(defun ewm-input--warp-pointer-to-window (window)
"Warp pointer to center of WINDOW. Safe for non-EWM frames."
(unless (minibufferp (window-buffer window))
(let* ((frame (window-frame window))
(output (frame-parameter frame 'ewm-output)))
(when (and output (not (ewm-input--pointer-in-window-p window)))
(let* ((output-offset (ewm--get-output-offset output))
(edges (window-inside-pixel-edges window))
(x (+ (car output-offset) (/ (+ (nth 0 edges) (nth 2 edges)) 2)))
(y (+ (cdr output-offset) (/ (+ (nth 1 edges) (nth 3 edges)) 2))))
(ewm-warp-pointer (float x) (float y)))))))
;; ── Settings ─────────────────────────────────────────────
(setq ewm-mouse-follows-focus t)
(setq ewm-input-config '((keyboard :repeat-delay 200 :repeat-rate 25)))
(setq ewm-idle '(300 . "swaylock -f -c 333333"))
;; Per-host output config
(pcase (system-name)
("md"
(setq ewm-output-config
'(("DP-1" :x 0 :y 0 :refresh 144))))
("sportmacher"
(setq ewm-output-config
'(("DSI-1" :width 800 :height 1280 :transform 3 :scale 1.0)))))
;; ── Programm-Launcher ──────────────────────────────────────
(define-key ewm-mode-map (kbd "s-<return>")
(lambda () (interactive) (start-process "kitty" nil "kitty")))
(define-key ewm-mode-map (kbd "M-s-7")
(lambda (command)
(interactive (list (read-shell-command "$ ")))
(start-process-shell-command command nil command)))
(define-key ewm-mode-map (kbd "s-SPC")
(lambda () (interactive) (start-process "vicinae" nil "vicinae" "toggle")))
(define-key ewm-mode-map (kbd "s-b")
(lambda () (interactive) (start-process "helium" nil "helium-browser")))
(define-key ewm-mode-map (kbd "s-p")
(lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/tinkerbells/pass/pass")))
(define-key ewm-mode-map (kbd "s-v")
(lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/clipboard/history")))
(define-key ewm-mode-map (kbd "s-s")
(lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/power")))
(define-key ewm-mode-map (kbd "s-d")
(lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/cashmere/deepl-improve/deepl-improve")))
(define-key ewm-mode-map (kbd "M-s")
(lambda () (interactive) (start-process "snipe" nil "snipe")))
(define-key ewm-mode-map (kbd "s-<print>")
(lambda () (interactive)
(start-process-shell-command "screenshot" nil "grim -g \"$(slurp)\" - | wl-copy")))
(define-key ewm-mode-map (kbd "<print>")
(lambda () (interactive)
(start-process-shell-command "screenshot-pb" nil
"grim -g \"$(slurp)\" - | ssh pb@pb.cashmere.rs -- -ext png 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+' | sed 's/$/?r=1/' | wl-copy")))
;; ── Window Management ────────────────────────────────────
(define-key ewm-mode-map (kbd "s-h") #'windmove-left)
(define-key ewm-mode-map (kbd "s-j") #'windmove-down)
(define-key ewm-mode-map (kbd "s-k") #'windmove-up)
(define-key ewm-mode-map (kbd "s-l") #'windmove-right)
(define-key ewm-mode-map (kbd "s-q") #'kill-current-buffer)
(define-key ewm-mode-map (kbd "s-f") #'ewm-toggle-fullscreen)
(define-key ewm-mode-map (kbd "s-S-q") #'ewm-stop-module)
;; tab switching s-0..s-9 (s-0 = tab 10)
(dotimes (i 10)
(define-key ewm-mode-map
(kbd (format "s-%d" i))
(let ((n i))
(lambda () (interactive) (tab-bar-select-tab (if (= n 0) 10 n))))))
;; ── Media Keys ───────────────────────────────────────────
(define-key ewm-mode-map (kbd "<XF86AudioLowerVolume>")
(lambda () (interactive) (start-process "vol-" nil "pamixer" "-d" "5")))
(define-key ewm-mode-map (kbd "<XF86AudioRaiseVolume>")
(lambda () (interactive) (start-process "vol+" nil "pamixer" "-i" "5")))
(define-key ewm-mode-map (kbd "<XF86AudioMute>")
(lambda () (interactive) (start-process "mute" nil "pamixer" "--toggle-mute")))
(define-key ewm-mode-map (kbd "<XF86AudioMicMute>")
(lambda () (interactive)
(start-process-shell-command "micmute" nil
"pactl list sources short | grep input | awk '{print $1}' | xargs -I{} pactl set-source-mute {} toggle")))
(define-key ewm-mode-map (kbd "<XF86AudioPlay>")
(lambda () (interactive) (start-process "play" nil "playerctl" "play-pause")))
(define-key ewm-mode-map (kbd "<XF86AudioMedia>")
(lambda () (interactive) (start-process "play" nil "playerctl" "play-pause")))
(define-key ewm-mode-map (kbd "<XF86AudioNext>")
(lambda () (interactive) (start-process "next" nil "playerctl" "next")))
(define-key ewm-mode-map (kbd "<XF86AudioPrev>")
(lambda () (interactive) (start-process "prev" nil "playerctl" "previous")))
(define-key ewm-mode-map (kbd "<XF86MonBrightnessUp>")
(lambda () (interactive) (start-process "bri+" nil "brightnessctl" "set" "+5%")))
(define-key ewm-mode-map (kbd "<XF86MonBrightnessDown>")
(lambda () (interactive) (start-process "bri-" nil "brightnessctl" "set" "5%-")))
;; ── Leader bindings under SPC e ──────────────────────────
(my-leader
"e" '(:ignore t :wk "ewm")
"e a" '(ewm-launch-app :wk "launch app")
"e n" '(ewm-next-surface-buffer :wk "next surface")
"e p" '(ewm-prev-surface-buffer :wk "prev surface")
"e f" '(ewm-toggle-fullscreen :wk "fullscreen")
"e t" '(ewm-transient :wk "control panel")
"e s" '(ewm-screenshot :wk "screenshot")
"e i" '(ewm-show-state :wk "inspect state")
"e d" '(ewm-debug-mode :wk "debug")
"e l" '(ewm-lock-session :wk "lock")
"e T" '(:ignore t :wk "tab")
"e T n" '(tab-new :wk "new tab")
"e T c" '(tab-close :wk "close tab")
"e T l" '(tab-next :wk "next tab")
"e T h" '(tab-previous :wk "prev tab")
"e T r" '(tab-recent :wk "recent tab")))Review
You run programs inside Emacs frames. You live inside Emacs. Emacs consumes your life. While I didn't enjoy it that much on my main machine, I see a lot of value for my 7-inch laptop with a fake Thinkpad trackpoint:

