Archived entries from file home/dan.doom.d/README.org
I love EXWM, but retreated back to XMonad. I had issues with both Emacs and Firefox causing the main thread to block, which (in EXWM) hangs the entire system.
Pros:
- System-wide UI consistency
- X windows and Emacs windows are treated the same
- e.g. Use Ivy to surface Firefox windows with fuzzy search
- Key simulation allows consistent keybindings (e.g. the copy/paste bindings can be made the same between Emacs, browsers, terminals, etc…)
- X windows and Emacs windows are treated the same
- Interactively update WM configuration
- Unlike e.g. XMonad, which requires a re-compile + restart
- Can add new bindings and immediately use them
- No separate WM install + config. It’s just Emacs + Elisp.
Cons:
- Need to be careful not to block the main thread! That will lock the entire system.
- Workaround: just spawn a secondary Emacs within the base Emacs whenever
there’s a risk of blocking.
- e.g. Before using TRAMP, spawn a fresh Emacs.
- Workaround: just spawn a secondary Emacs within the base Emacs whenever
there’s a risk of blocking.
- Less stable than XMonad, which is a tiny, well-tested Haskell program
- Limited support for managing multiple screens.
- It works, but it hardwires each workspace to a specific monitor.
- Need to be careful not to leave your Emacs configuration in a broken state.
- Fallbacks include other WMs installed (XMonad) or switching to a tty (Ctrl-Alt-f#)
mkdir -p ./modules/desktop/exwm
;; -*- no-byte-compile: t; -*-
;;; desktop/exwm/packages.el
(package! exwm)
;; (package! exwm-firefox
;; :recipe (:host github :repo "ieure/exwm-firefox"))
;; (package! exwm-mff
;; :recipe (:host github :repo "ieure/exwm-mff"))
(package! xelb)
(package! exwm-edit)
;;; desktop/exwm/config.el -*- lexical-binding: t; -*-
(use-package! exwm
:init
(setq
mouse-autoselect-window t
focus-follows-mouse t)
:config
(setq exwm-workspace-number 9))
(defun my/exwm-rename-buffer-to-title () (exwm-workspace-rename-buffer (format "%s - %s" exwm-class-name exwm-title)))
(setq exwm-workspace-show-all-buffers t
exwm-layout-show-all-buffers t
exwm-manage-force-tiling t)
(setq exwm-input-prefix-keys '(?\s- ))
(display-battery-mode 1)
(display-time-mode 1)
;; (setq exwm-manage-configurations
;; '(((string= exwm-class-name "Google-chrome")
;; workspace 0)
;; ((string= exwm-class-name "Firefox")
;; workspace 1)
;; ((string= exwm-instance-name "terminator")
;; workspace 8)
;; ((string= exwm-instance-name "keybase")
;; workspace 9)))
(defun my/launch (command)
(interactive (list (read-shell-command "$ ")))
(start-process-shell-command command nil command))
(defun my/launch-terminal ()
(interactive)
(my/launch "terminator"))
(defun my/launch-browser ()
(interactive)
(my/launch "firefox"))
(defun my/launch-emacs ()
(interactive)
(my/launch "emacs"))
(defun my/lock-screen ()
(interactive)
(my/launch "xtrlock -b"))
(defun my/volume-up ()
(interactive)
(my/launch "amixer sset Master unmute")
(my/launch "amixer sset Master 5%+"))
(defun my/volume-down ()
(interactive)
(my/launch "amixer sset Master 5%-"))
(setq exwm-workspace-minibuffer-position 'nil)
(exwm-input-set-key (kbd "s-:") #'eval-expression)
;; https://emacs.stackexchange.com/questions/33326/how-do-i-cut-and-paste-effectively-between-applications-while-using-exwm
(defun my/exwm-input-line-mode ()
"Set exwm window to line-mode and show mode line"
(call-interactively #'exwm-input-grab-keyboard))
(defun my/exwm-input-char-mode ()
"Set exwm window to char-mode and hide mode line"
(call-interactively #'exwm-input-release-keyboard))
(defun my/exwm-input-toggle-mode ()
"Toggle between line- and char-mode"
(interactive)
(with-current-buffer (window-buffer)
(when (eq major-mode 'exwm-mode)
(if (equal (second (second mode-line-process)) "line")
(my/exwm-input-char-mode)
(my/exwm-input-line-mode)))))
(defun my/toggle-exwm-input-line-mode-passthrough ()
(interactive)
(if exwm-input-line-mode-passthrough
(progn
(setq exwm-input-line-mode-passthrough nil)
(message "App receives all the keys now (with some simulation)"))
(progn
(setq exwm-input-line-mode-passthrough t)
(message "emacs receives all the keys now")))
(force-mode-line-update))
(exwm-input-set-key (kbd "s-;") 'my/toggle-exwm-input-line-mode-passthrough)
;; Switch to last workspace
(defvar my/exwm-workspace-previous-index 0 "The previous active workspace index.")
(defun my/exwm-workspace--current-to-previous-index (_x &optional _y)
(setq my/exwm-workspace-previous-index exwm-workspace-current-index))
(advice-add 'exwm-workspace-switch :before #'my/exwm-workspace--current-to-previous-index)
(defun my/exwm-workspace-switch-to-previous ()
(interactive)
"Switch to the previous active workspace."
(let ((index my/exwm-workspace-previous-index))
(exwm-workspace-switch index)))
(defun my/switch-to-last-buffer ()
"Switch to last open buffer in current window."
(interactive)
(switch-to-buffer (other-buffer (current-buffer) 1)))
;; Re-use muscle memory from 6 years of an xmonad setup
(exwm-input-set-key (kbd "s-p") #'dmenu)
(exwm-input-set-key (kbd "s-P") #'counsel-linux-app)
(exwm-input-set-key (kbd "s-s") #'password-store-copy)
(exwm-input-set-key (kbd "s-<return>") #'my/launch-terminal)
(exwm-input-set-key (kbd "s-.") #'my/switch-to-last-buffer)
(exwm-input-set-key (kbd "s-,") #'my/exwm-workspace-switch-to-previous)
(exwm-input-set-key (kbd "s-i") #'my/launch-browser)
(exwm-input-set-key (kbd "s-b") 'switch-to-buffer)
(exwm-input-set-key (kbd "s-M-O") #'my/lock-screen)
(exwm-input-set-key (kbd "s-<up>") #'my/volume-up)
(exwm-input-set-key (kbd "s-<down>") #'my/volume-down)
;; (exwm-input-set-key (kbd "s-<print>") #'my/screen-to-clipboard)
(exwm-input-set-key (kbd "s-R") #'doom/reload)
(exwm-input-set-key (kbd "s-Q") #'kill-emacs)
(exwm-input-set-key (kbd "s-m") #'bury-buffer)
(exwm-input-set-key (kbd "s-M") #'unbury-buffer)
(exwm-input-set-key (kbd "s-j") #'other-window)
(exwm-input-set-key (kbd "s-k") #'rev-other-window)
(exwm-input-set-key (kbd "s-J") #'previous-buffer)
(exwm-input-set-key (kbd "s-K") #'next-buffer)
(exwm-input-set-key (kbd "s-h") 'shrink-window)
(exwm-input-set-key (kbd "s-l") 'enlarge-window)
(exwm-input-set-key (kbd "s-H") 'shrink-window-horizontally)
(exwm-input-set-key (kbd "s-L") 'enlarge-window-horizontally)
(exwm-input-set-key (kbd "s-/") 'winner-undo)
(exwm-input-set-key (kbd "s-?") 'winner-redo)
(exwm-input-set-key (kbd "s-'") 'exwm-edit--compose)
(exwm-input-set-key (kbd "s-w") 'delete-window)
(exwm-input-set-key (kbd "s-q") 'kill-this-buffer)
(exwm-input-set-key (kbd "s-C") 'cfw:open-org-calendar)
(exwm-input-set-key (kbd "s-x") 'counsel-M-x)
(exwm-input-set-key (kbd "s-t") 'vterm)
(exwm-input-set-key (kbd "s-<f7>") 'my/monitor-screen-layout)
(exwm-input-set-key (kbd "s-<f8>") 'my/laptop-screen-layout)
(mapcar (lambda (i)
(exwm-input-set-key (kbd (format "s-%d" i))
`(lambda ()
(interactive)
(exwm-workspace-switch-create ,i))))
(number-sequence 0 9))
;; Configure firefox to open every tab as a new window instead
;; http://p.hagelb.org/exwm-ff-tabs.html
(add-hook 'exwm-manage-finish-hook
(lambda ()
;; these have their own Emacs simulation installed (e.g. Surfingkeys)
(if (or (string= exwm-class-name "Firefox")
(string= exwm-class-name "Google-chrome")
(string= exwm-class-name "Atom"))
(progn
(exwm-input-set-local-simulation-keys
`(([?\s-w] . [?\C-w])
([?\M-w] . [?\C-c])
([?\C-y] . [?\C-v])
([?\C-w] . [?\C-x])))
(exwm-layout-hide-mode-line))
(exwm-layout-show-mode-line))))
;; (add-hook 'exwm-update-title-hook
;; (defun my/exwm-title-hook ()
;; (when (string-match "Firefox" exwm-class-name)
;; (exwm-workspace-rename-buffer exwm-title))))
(add-hook 'exwm-update-title-hook 'my/exwm-rename-buffer-to-title)
(setq browse-url-firefox-arguments '("-new-window"))
(setq exwm-input-simulation-keys
'(
;; movement
([?\C-b] . [left])
([?\M-b] . [C-left])
([?\C-f] . [right])
([?\M-f] . [C-right])
([?\C-p] . [up])
([?\C-n] . [down])
([?\C-e] . [end])
([?\M-v] . [prior])
([?\C-v] . [next])
([?\C-d] . [delete])
;; undo
([?\C-/] . [?\C-z])
;; Interferes with Slack
;; ([?\C-k] . [S-end delete])
;; cut/copy/paste.
([?\C-w] . [?\C-x])
([?\M-w] . [?\C-c])
([?\C-y] . [?\C-v])
;; search
([?\C-s] . [?\C-f])))
(define-ibuffer-column exwm-class (:name "Class")
(if (bound-and-true-p exwm-class-name)
exwm-class-name
""))
(define-ibuffer-column exwm-instance (:name "Instance")
(if (bound-and-true-p exwm-instance-name)
exwm-instance-name
""))
(define-ibuffer-column exwm-urgent (:name "U")
(if (bound-and-true-p exwm--hints-urgency)
"U"
" "))
(defun my/exwm-ibuffer (&optional other-window)
(interactive "P")
(let ((name (buffer-name)))
(ibuffer other-window
"*exwm-ibuffer*"
'((mode . exwm-mode))
nil nil nil
'((mark exwm-urgent
" "
(name 64 64 :left :elide)
" "
(exwm-class 20 -1 :left)
" "
(exwm-instance 10 -1 :left))))
(ignore-errors (ibuffer-jump-to-buffer name))))
(exwm-input-set-key (kbd "s-o") #'my/exwm-ibuffer)
(use-package! exwm-edit
:init
;; Otherwise it steals C-c ' from org
(setq exwm-edit-bind-default-keys nil))
(defun my/exwm-start-in-char-mode ()
(when (or (string-prefix-p "terminator" exwm-instance-name)
(string-prefix-p "emacs" exwm-instance-name)
(string-prefix-p "next" exwm-instance-name))
(exwm-input-release-keyboard (exwm--buffer->id (window-buffer)))))
(add-hook 'exwm-manage-finish-hook 'my/exwm-start-in-char-mode)
(require 'exwm-randr)
;; FIXME
(setq exwm-randr-workspace-monitor-plist '(0 "eDP-1"
1 "HDMI-1"
1 "HDMI-1"
2 "HDMI-1"
3 "HDMI-1"
4 "HDMI-1"
5 "HDMI-1"
6 "HDMI-1"
7 "HDMI-1"
8 "HDMI-1"
9 "HDMI-1"))
(require 'exwm-randr)
(exwm-randr-enable)
;; (exwm-enable)
;; (use-package! exwm-mff
;; :config
;; (exwm-mff-mode 1))
#!/bin/bash
# Disable access control for the current user.
xhost +SI:localuser:$USER
# Identify the home of our gtkrc file, important for setting styles of
# gtk-based applications
export GTK2_RC_FILES="$HOME/.gtkrc-2.0"
# Make Java applications aware this is a non-reparenting window manager.
export _JAVA_AWT_WM_NONREPARENTING=1
# Bind caps to ctrl
setxkbmap -option 'ctrl:nocaps'
# set keyboard rate
xset r rate 160 50
xsetroot -solid black
# Set default cursor.
xsetroot -cursor_name left_ptr
# Nix + direnv
# lorri daemon &
# Email sync
offlineimap &
# Uncomment the following block to use the exwm-xim module.
# export XMODIFIERS=@im=exwm-xim
# export GTK_IM_MODULE=xim
# export QT_IM_MODULE=xim
# export CLUTTER_IM_MODULE=xim
source ~/.profile
# Sync Doom
# ~/.emacs.d/bin/doom sync
# Finally start Emacs
exec ~/.emacs.d/bin/doom run
This gets picked up by DM
[Desktop Entry]
Encoding=UTF-8
Name=EXWM
Comment=Emacs X WM
Exec=/home/dan/.doom.d/modules/desktop/exwm/launch-exwm.sh
Type=XSession
;; Helpful command: (deft-refresh)
(setq deft-recursive t
;; Otherwise too slow
deft-file-limit 100)
(map! "<f8>" 'my/deft)
(defun my/deft ()
(interactive)
(let ((deft-directory org-roam-directory))
(deft)))
(use-package! org-roam-server
:config
(setq org-roam-server-host "127.0.0.1"
org-roam-server-port 8081
org-roam-server-authenticate nil
org-roam-server-export-inline-images t
org-roam-server-serve-files nil
org-roam-server-served-file-extensions '("pdf" "mp4" "ogv")
org-roam-server-network-poll t
org-roam-server-network-arrows nil
org-roam-server-network-label-truncate t
org-roam-server-network-label-truncate-length 60
org-roam-server-network-label-wrap-length 20))
(use-package! org-journal
:after org
:config
(customize-set-variable 'org-journal-dir (concat org-roam-directory "journal"))
(customize-set-variable 'org-journal-file-format "private-%Y-%m-%d.org")
(customize-set-variable 'org-journal-date-prefix "#+TITLE: ")
(customize-set-variable 'org-journal-time-prefix "* ")
(customize-set-variable 'org-journal-time-format "")
(customize-set-variable 'org-journal-carryover-items "TODO=\"TODO\"")
(customize-set-variable 'org-journal-date-format "%Y-%m-%d")
(map! :leader
(:prefix-map ("n" . "notes")
(:prefix ("j" . "journal")
:desc "Today" "t" #'org-journal-today)))
(defun org-journal-today ()
(interactive)
(org-journal-new-entry t)))
Replaced by deadgrep-edit-mode
(use-package! wgrep
:config
(autoload 'wgrep-deadgrep-setup "wgrep-deadgrep")
(add-hook 'deadgrep-finished-hook 'wgrep-deadgrep-setup))
(use-package! ob-rust)
cargo-script
required for org-babel blocks (otherwise each requires a main
function)
cargo install cargo-script
Seems like a good solution to swiper being slow is to just use swiper-isearch, but I find swiper’s handling of multiple results on a line more convenient most of the time.
Instead, I follow advice from this Reddit comment to make swiper ignore visual line mode. Seems to help for now.
(setq swiper-use-visual-line nil)
(setq swiper-use-visual-line-p (lambda (a) nil))
Ivy allows you to find the input to a command by incrementally searching the space of all valid inputs. It’s well-supported in Doom.
(after! ivy
;; Causes open buffers and recentf to be combined in ivy-switch-buffer
(setq ivy-use-virtual-buffers t
counsel-find-file-at-point t
ivy-wrap nil
ivy-posframe-display-functions-alist '((t . ivy-posframe-display-at-frame-top-center))
ivy-posframe-height-alist '((t . 20))
ivy-posframe-parameters '((internal-border-width . 1))
ivy-posframe-width 100)
(add-hook 'eshell-mode-hook
(lambda ()
(eshell-cmpl-initialize)
(define-key eshell-mode-map (kbd "M-r") 'counsel-esh-history)))
(add-to-dk-keymap
'(("g" . +ivy/project-search)
("h" . +ivy/projectile-find-file)
("i" . counsel-semantic-or-imenu)
("j" . ivy-switch-buffer))))
(use-package! dap-mode
:config
;; (dap-ui-mode)
;; (dap-ui-controls-mode 1)
(require 'dap-lldb)
(require 'dap-gdb-lldb)
;; installs .extension/vscode
(dap-gdb-lldb-setup)
(dap-register-debug-template
"Rust::LLDB Run Configuration"
(list :type "lldb"
:request "launch"
:name "LLDB::Run"
:gdbpath "rust-lldb"
:target nil
:cwd nil)))
Had to build lldb
from source to get lldb-mi
. Required installing lldb
and liblldb-dev
to build.
Hit when debugin with char
types: rust-lang/rust#29154 Floating point exception when debugging with lldb on Mac…
undefinederror: need to add support for DW_TAG_base_type 'char' encoded with DW_ATE = 0x8, bit_size = 32
Also, breaks swiper :(
Used with *.jmd literate Julia files (see Weave.jl)
(use-package! poly-markdown)
Including the Doom javascript
module does most of the work…
(use-package! jest
:hook
(typescript-mode . jest-minor-mode))
(use-package! company-org-block
:custom
(company-org-block-edit-style 'inline) ;; 'auto, 'prompt, or 'inline
:hook ((org-mode . (lambda ()
(setq-local company-backends '(company-org-block))
(company-mode +1)))))
~/.emacs.d/.local/straight/repos/rustic/rustic-orb-babel.el
Added to Rustic optional lib load
(customize-set-variable 'rustic-babel-display-compilation-buffer t)
(add-to-list 'org-structure-template-alist '("or" . "src org-rust"))
(customize-set-variable 'rustic-babel-format-src-block t)
(defun my/register-remote-rust-analyzer ()
(interactive)
(lsp-register-client
(make-lsp-client
:new-connection (lsp-tramp-connection "rust-analyzer")
;; (lsp-tramp-connection
;; (lambda ()
;; `(,(or (executable-find
;; (cl-first lsp-rust-analyzer-server-command))
;; (lsp-package-path 'rust-analyzer)
;; "rust-analyzer")
;; ,@(cl-rest lsp-rust-analyzer-server-args))))
:remote? t
:major-modes '(rust-mode rustic-mode)
:initialization-options 'lsp-rust-analyzer--make-init-options
:notification-handlers (ht<-alist lsp-rust-notification-handlers)
:action-handlers (ht ("rust-analyzer.runSingle" #'lsp-rust--analyzer-run-single))
:library-folders-fn (lambda (_workspace) lsp-rust-library-directories)
:after-open-fn (lambda ()
(when lsp-rust-analyzer-server-display-inlay-hints
(lsp-rust-analyzer-inlay-hints-mode)))
:ignore-messages nil
:server-id 'rust-analyzer-remote)))
GitHub - brotzeit/rustic: Rust development environment for Emacs
(defun start-file-process-shell-command@around (start-file-process-shell-command name buffer &rest args)
"Start a program in a subprocess. Return the process object for it. Similar to `start-process-shell-command', but calls `start-file-process'."
;; On remote hosts, the local `shell-file-name' might be useless.
(let ((command (mapconcat 'identity args " ")))
(funcall start-file-process-shell-command name buffer command)))
(advice-add 'start-file-process-shell-command :around #'start-file-process-shell-command@around)
(with-eval-after-load "lsp-rust"
(lsp-register-client
(make-lsp-client
:new-connection (lsp-tramp-connection "rust-analyzer")
:remote? t
:major-modes '(rust-mode rustic-mode)
:initialization-options 'lsp-rust-analyzer--make-init-options
:notification-handlers (ht<-alist lsp-rust-notification-handlers)
:action-handlers (ht ("rust-analyzer.runSingle" #'lsp-rust--analyzer-run-single))
:library-folders-fn (lambda (_workspace) lsp-rust-library-directories)
:after-open-fn (lambda ()
(when lsp-rust-analyzer-server-display-inlay-hints
(lsp-rust-analyzer-inlay-hints-mode)))
:ignore-messages nil
:server-id 'rust-analyzer-remote)))