├── packages.el ├── autoload ├── exwm-firefox.el ├── childframe-workarounds.el ├── exwm.el └── exwm-edit.el ├── exwm-vanilla-emacs-default-config.el ├── README.org └── config.el /packages.el: -------------------------------------------------------------------------------- 1 | ;; -*- no-byte-compile: t; -*- 2 | ;;; os/exwm/packages.el 3 | 4 | (package! exwm) 5 | (when (featurep! :editor evil) 6 | (package! exwm-evil 7 | :recipe (:host github :repo "LemonBreezes/exwm-evil")) 8 | (package! exwm-firefox-evil)) 9 | (package! exwm-edit) 10 | (package! language-detection) 11 | (package! exwm-mff) 12 | (package! app-launcher :recipe 13 | (:host github :repo "SebastienWae/app-launcher")) 14 | -------------------------------------------------------------------------------- /autoload/exwm-firefox.el: -------------------------------------------------------------------------------- 1 | ;;; os/exwm/autoload/exwm-firefox.el -*- lexical-binding: t; -*- 2 | 3 | ;;;###autoload 4 | (defun exwm-firefox-core-hint-links () 5 | "Select and open a link with your keyboard." 6 | (interactive) 7 | (exwm-input--fake-key ?\M-j) 8 | (exwm-firefox-evil-insert) 9 | (add-transient-hook! 'exwm-update-title-hook 10 | (when exwm-firefox-evil-mode (exwm-firefox-evil-normal)))) 11 | 12 | ;;;###autoload 13 | (defun exwm-firefox-core-hint-links-new-tab-and-switch () 14 | "Select and open a link in a new tab using your keyboard." 15 | (interactive) 16 | (exwm-input--fake-key ?\M-l) 17 | (exwm-firefox-evil-insert) 18 | (add-transient-hook! 'exwm-update-title-hook 19 | (when exwm-firefox-evil-mode (exwm-firefox-evil-normal)))) 20 | 21 | ;;;###autoload 22 | (defun +exwm-firefox-core-cancel () 23 | (interactive) 24 | "General cancel action." 25 | (interactive) 26 | (exwm-input--fake-key 'escape) 27 | (+exwm-refocus-application)) 28 | -------------------------------------------------------------------------------- /autoload/childframe-workarounds.el: -------------------------------------------------------------------------------- 1 | ;;; private/exwm/autoload/posframe-workarounds.el -*- lexical-binding: t; -*- 2 | 3 | ;;;###autoload 4 | (defun +corfu--make-frame-a (oldfun &rest args) 5 | (cl-letf (((symbol-function #'frame-parent) 6 | (lambda (frame) 7 | (or (frame-parameter frame 'parent-frame) 8 | exwm-workspace--current)))) 9 | (apply oldfun args)) 10 | (when exwm--connection 11 | (set-frame-parameter corfu--frame 'parent-frame nil))) 12 | 13 | ;;;###autoload 14 | (defun +corfu--popup-redirect-focus-a () 15 | (redirect-frame-focus corfu--frame 16 | (or (frame-parent corfu--frame) 17 | exwm-workspace--current))) 18 | 19 | ;;;###autoload 20 | (defun +corfu-doc--redirect-focus () 21 | (redirect-frame-focus corfu-doc--frame 22 | (or (frame-parent corfu-doc--frame) 23 | exwm-workspace--current))) 24 | 25 | ;;;###autoload 26 | (defun +mini-popup--setup-frame-a (oldfun &rest args) 27 | (cl-letf (((symbol-function #'frame-parent) 28 | (lambda (frame) 29 | (or (frame-parameter frame 'parent-frame) 30 | exwm-workspace--current)))) 31 | (apply oldfun args)) 32 | (when exwm--connection 33 | (set-frame-parameter mini-popup--frame 'parent-frame nil))) 34 | 35 | ;;;###autoload 36 | (defun +corfu-doc--make-frame-a (oldfun &rest args) 37 | (cl-letf (((symbol-function #'frame-parent) 38 | (lambda (frame) 39 | (or (frame-parameter frame 'parent-frame) 40 | exwm-workspace--current)))) 41 | (apply oldfun args)) 42 | (when exwm--connection 43 | (set-frame-parameter corfu-doc--frame 'parent-frame nil))) 44 | -------------------------------------------------------------------------------- /exwm-vanilla-emacs-default-config.el: -------------------------------------------------------------------------------- 1 | ;;; private/exwm/autoload/exwm-vanilla-emacs-default-config.el -*- lexical-binding: t; -*- 2 | 3 | (setopt gc-cons-threshold ,gc-cons-threshold 4 | gc-cons-percentage ,gc-cons-percentage 5 | large-file-warning-threshold ,large-file-warning-threshold 6 | user-full-name ,user-full-name 7 | user-mail-address ,user-mail-address 8 | create-lockfiles nil 9 | inhibit-startup-screen t 10 | abbrev-file-name ,abbrev-file-name) 11 | 12 | (with-eval-after-load 'org 13 | (setopt org-agenda-files ',(progn (require 'org) 14 | org-agenda-files))) 15 | 16 | (set-language-environment "UTF-8") 17 | 18 | ;; Set up some editing conveniences 19 | (electric-pair-mode 1) 20 | (electric-indent-mode 1) 21 | (show-paren-mode 1) 22 | (global-eldoc-mode 1) 23 | (abbrev-mode 1) 24 | (setopt tab-always-indent 'complete) 25 | 26 | ;; Set up modeline and appearance 27 | (blink-cursor-mode -1) 28 | (scroll-bar-mode -1) 29 | (fringe-mode -1) 30 | (menu-bar-mode -1) 31 | (tool-bar-mode -1) 32 | (line-number-mode 1) 33 | (column-number-mode 1) 34 | (global-display-line-numbers-mode 1) 35 | (defalias 'yes-or-no-p 'y-or-n-p) 36 | (load-theme ',+exwm-vanilla-emacs-theme) 37 | 38 | ;; Bootstrap straight 39 | (defvar bootstrap-version) 40 | 41 | (let ((bootstrap-file 42 | (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) 43 | (bootstrap-version 5)) 44 | (unless (file-exists-p bootstrap-file) 45 | (with-current-buffer 46 | (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 47 | 'silent 'inhibit-cookies) 48 | (goto-char (point-max)) 49 | (eval-print-last-sexp))) 50 | (load bootstrap-file nil 'nomessage)) 51 | -------------------------------------------------------------------------------- /autoload/exwm.el: -------------------------------------------------------------------------------- 1 | ;;; os/exwm/autoload/exwm.el -*- lexical-binding: t; -*- 2 | 3 | ;;;###autoload 4 | (defun +exwm-refocus-application (&rest _) 5 | "Refocus input for the currently selected EXWM buffer, if any." 6 | (let ((input-delay 0.05)) 7 | (run-at-time 8 | input-delay 9 | nil 10 | (defun +exwm-refocus-application--timer () 11 | (when exwm-class-name 12 | (advice-add #'+exwm-refocus-application :override #'ignore) 13 | (run-at-time (* 2 input-delay) nil 14 | (defun +exwm-refocus-application--cleanup () 15 | (advice-remove #'+exwm-refocus-application #'ignore))) 16 | (run-at-time input-delay nil 17 | (lambda () (ignore-errors (throw 'exit #'ignore)))) 18 | (read-string "")))))) 19 | 20 | ;;;###autoload 21 | (defun +exwm-select-window-a (oldfun window &rest args) 22 | (when window (apply oldfun window args))) 23 | 24 | ;;;###autoload 25 | (defun +exwm-rename-buffer-to-title () 26 | "Rename the buffer to its `exwm-title'." 27 | (unless (or (string-prefix-p "sun-awt-X11-" exwm-instance-name) 28 | (string= "gimp" exwm-instance-name)) 29 | (exwm-workspace-rename-buffer exwm-title))) 30 | 31 | ;;;###autoload 32 | (defun exwm--update-utf8-title-advice (oldfun id &optional force) 33 | "Only update the window title when the buffer is visible." 34 | (when (get-buffer-window (exwm--id->buffer id)) 35 | (funcall oldfun id force))) 36 | 37 | ;;;###autoload 38 | (defun +exwm-open-nested-emacs (arg) 39 | "Open a separate GUI instance of Emacs. If ARG is non-nil, create 40 | a new workspace as well." 41 | (interactive "P") 42 | (apply #'start-process "Emacs" nil "emacs" 43 | (when arg (list "--debug-init")))) 44 | 45 | ;;;###autoload 46 | (defun +exwm-open-nested-vanilla-emacs (arg) 47 | (interactive "P") 48 | (apply #'start-process "Emacs" nil "emacs" "-Q" 49 | "--eval" (+exwm-read-unquote-config +exwm-vanilla-emacs-config-file) 50 | (when arg (list "--debug-init")))) 51 | 52 | (defun +exwm-read-unquote-config (fname) 53 | (thread-last 54 | `(backquote 55 | ,(let ((sexps nil)) 56 | (with-temp-buffer 57 | (insert-file-contents fname) 58 | (condition-case _ 59 | (while t (push (read (current-buffer)) sexps)) 60 | (end-of-file)) 61 | (setq sexps (nreverse sexps)) 62 | (push 'progn sexps)))) 63 | (eval) 64 | (prin1-to-string))) 65 | 66 | ;;;###autoload 67 | (defun +exwm-vanilla-emacs-find-this-file () 68 | (interactive) 69 | (+exwm-vanilla-emacs-find-file (buffer-file-name))) 70 | 71 | ;;;###autoload 72 | (defun +exwm-vanilla-emacs-find-file (file) 73 | (interactive) 74 | (funcall #'start-process "Emacs" nil "emacs" "-Q" 75 | "--eval" (+exwm-read-unquote-config +exwm-vanilla-emacs-config-file) 76 | "--file" (or file 77 | (read-file-name "Find file: " nil default-directory nil)))) 78 | -------------------------------------------------------------------------------- /autoload/exwm-edit.el: -------------------------------------------------------------------------------- 1 | ;;; os/exwm/autoload/exwm-edit.el -*- lexical-binding: t; -*- 2 | 3 | ;;;###autoload 4 | (defvar +exwm-edit-default-major-mode 'org-mode 5 | "The major mode used when a programming language is not detected 6 | after running `exwm-edit--compose'.") 7 | 8 | ;;;###autoload 9 | (defvar +exwm-edit-activate-appropriate-major-mode--timer nil 10 | "A variable used internally to store a timer object.") 11 | 12 | ;;;###autoload 13 | (defun +exwm-edit-activate-appropriate-major-mode () 14 | "Detects what programming language (if any) is present in the 15 | application's input field and enables the appropriate major mode." 16 | (when (featurep! :editor evil) 17 | (evil-insert-state)) 18 | (setq exwm-edit-activate-appropriate-major-mode--timer 19 | (run-at-time 20 | 0.05 0.05 21 | (defun exwm-edit-activate-appropriate-major-mode--timer-fn (&rest _) 22 | (unless (string-prefix-p "*exwm-edit " 23 | (buffer-name)) 24 | (cancel-timer exwm-edit-activate-appropriate-major-mode--timer)) 25 | (when (buffer-modified-p) 26 | (cancel-timer exwm-edit-activate-appropriate-major-mode--timer) 27 | (let ((header-line-format--old header-line-format)) 28 | (when (string-prefix-p "*exwm-edit " 29 | (buffer-name)) 30 | (cl-case (language-detection-buffer) 31 | (ada (ada-mode)) 32 | ;; Awk mode was getting incorrectly used for ordinary text. 33 | ;; (awk (awk-mode)) 34 | (c (c-mode)) 35 | (cpp (c++-mode)) 36 | (clojure (clojure-mode)) 37 | (csharp (csharp-mode)) 38 | (css (css-mode)) 39 | (dart (dart-mode)) 40 | (delphi (delphi-mode)) 41 | (emacslisp (emacs-lisp-mode)) 42 | (erlang (erlang-mode)) 43 | (fortran (fortran-mode)) 44 | (fsharp (fsharp-mode)) 45 | (go (go-mode)) 46 | (groovy (groovy-mode)) 47 | (haskell (haskell-mode)) 48 | (html (html-mode)) 49 | (java (java-mode)) 50 | (javascript (javascript-mode)) 51 | (json (json-mode)) 52 | (latex (latex-mode)) 53 | (lisp (lisp-mode)) 54 | (lua (lua-mode)) 55 | (matlab (matlab-mode)) 56 | (objc (objc-mode)) 57 | (perl (perl-mode)) 58 | (php (php-mode)) 59 | (prolog (prolog-mode)) 60 | (python (python-mode)) 61 | (r (r-mode)) 62 | (ruby (ruby-mode)) 63 | (rust (rust-mode)) 64 | (scala (scala-mode)) 65 | (shell (shell-script-mode)) 66 | (smalltalk (smalltalk-mode)) 67 | (sql (sql-mode)) 68 | (swift (swift-mode)) 69 | (visualbasic (visual-basic-mode)) 70 | (xml (sgml-mode)) 71 | (t (funcall +exwm-edit-default-major-mode))) 72 | (when (featurep! :editor evil) 73 | (evil-insert-state)) 74 | (end-of-line) 75 | (setq header-line-format header-line-format--old)))))))) 76 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: os/exwm 2 | #+DATE: January 5, 2022 3 | #+SINCE: v1.0.0 4 | #+STARTUP: inlineimages nofold 5 | 6 | * Table of Contents :TOC_3:noexport: 7 | - [[#description][Description]] 8 | - [[#maintainers][Maintainers]] 9 | - [[#module-flags][Module Flags]] 10 | - [[#plugins][Plugins]] 11 | - [[#hacks][Hacks]] 12 | - [[#prerequisites][Prerequisites]] 13 | - [[#features][Features]] 14 | - [[#configuration][Configuration]] 15 | - [[#setting-up-your-xinitrc-file][Setting up your ~xinitrc~ file.]] 16 | - [[#setting-the-default-major-mode-for-exwm-edit][Setting the default major mode for EXWM Edit.]] 17 | - [[#setting-up-link-hints-for-chrome-based-and-firefox-based-web-browsers][Setting up link hints for Chrome-based and Firefox-based web browsers]] 18 | - [[#setting-up-multiple-monitors][Setting up multiple monitors]] 19 | - [[#setting-up-an-alternate-emacs-configuration-for-nested-emacs-sessions][Setting up an alternate Emacs configuration for nested Emacs sessions]] 20 | - [[#using-i3-style-keybindings][Using i3-style keybindings]] 21 | - [[#troubleshooting][Troubleshooting]] 22 | - [[#how-do-i-send-escape-or-c-c-to-applications][How do I send ~escape~ or ~C-c~ to applications?]] 23 | - [[#when-i-mouse-click-while-in-evils-normal-state-i-get-an-error][When I mouse-click while in Evil's normal state I get an error.]] 24 | - [[#future-directions][Future directions]] 25 | 26 | * Description 27 | 28 | This module enables and provides sane defaults for the Emacs X Window Manager 29 | [[https://github.com/ch11ng/exwm][(EXWM)]]. 30 | 31 | + Control applications as Emacs buffers. 32 | + Edit any application's input field with Emacs. 33 | + Use Emacs input methods in other applications. 34 | + Use VIM keys in major web browsers like Firefox and Chrome. 35 | + Launch and control nested Emacs sessions within Emacs. 36 | 37 | ** Maintainers 38 | 39 | + @[[https://github.com/LemonBreezes][LemonBreezes]] (Author) 40 | 41 | ** Module Flags 42 | 43 | This module provides no flags. 44 | 45 | ** Plugins 46 | 47 | + [[https://github.com/ch11ng/exwm][exwm]] 48 | + [[https://github.com/LemonBreezes/exwm-evil][exwm-evil]] 49 | + [[https://github.com/walseb/exwm-firefox-evil][exwm-firefox-evil]] 50 | + [[https://github.com/agzam/exwm-edit][exwm-edit]] 51 | + [[https://github.com/andreasjansson/language-detection.el][language-detection]] 52 | + [[https://github.com/ieure/exwm-mff][exwm-mff]] 53 | + [[https://github.com/SebastienWae/app-launcher][app-launcher]] 54 | 55 | ** Hacks 56 | 57 | When switching to a workspace where an EXWM buffer is focused, inputs do not get 58 | passed to the application. To work around this, I wrote a hook which briefly activates 59 | the minibuffer for input and then quits the minibuffer. 60 | 61 | * Prerequisites 62 | 63 | This module has no prerequisites. 64 | 65 | * Features 66 | 67 | + Edit any application's input field with =C-c '=. 68 | + Use Emacs input methods in other applications. Simply set the input method as 69 | usual with =C-x RET C-\=. 70 | + Open nested sessions of Emacs with alternate configurations. 71 | + Use Evil in any application! Specific browsers such as Google Chrome and 72 | Firefox have extended support for Evil keybindings. Simply install the 73 | ~link-hints~ extension/add-on and start VIM-ing! 74 | 75 | Moreover, when this module is loaded, the following global keybindings are made: 76 | 77 | | Keybinding | Description | 78 | |------------+-------------------------------------| 79 | | =SPC $= | Open a GNU/Linux application | 80 | | =SPC o n= | Open a nested Emacs session | 81 | | =SPC o N= | Open a nested vanilla Emacs session | 82 | | =SPC f v= | Find file in vanilla Emacs | 83 | | =SPC f V= | Find this file in vanilla Emacs | 84 | | =C-c '= | Edit input field in Emacs | 85 | 86 | * Configuration 87 | 88 | ** Setting up your ~xinitrc~ file. 89 | 90 | In order to enable EXWM, Emacs needs to be running within X.Org without any 91 | window manager present. The recommended way to go about this is with the 92 | ~startx~ script. The [[https://github.com/ch11ng/exwm/wiki/Configuration-Example][EXWM wiki]] contains an example ~xinitrc~ file for use by 93 | ~startx~. 94 | 95 | ** Setting the default major mode for EXWM Edit. 96 | 97 | By default, when you edit an application's input field and no programming 98 | language can be detected in the text, ~org-mode~ will your major mode. If you 99 | would like to change this, set the ~+exwm-edit-default-major-mode~ variable: 100 | 101 | #+begin_src elisp 102 | (setq +exwm-edit-default-major-mode 'markdown-mode) 103 | #+end_src 104 | 105 | ** Setting up link hints for Chrome-based and Firefox-based web browsers 106 | 107 | For users of Evil, the ~f~ and ~F~ keys are bound to special Link Hint commands 108 | within Chrome-based and Firefox-based web browsers. For these commands to work, 109 | you must have the Link Hint add-on/extension installed. For Chrome-based web 110 | browsers, install Link Hint through [[https://chrome.google.com/webstore/detail/link-hints/kjjgifdfplpegljdfnpmbjmkngdilmkd][here]]. For Firefox-based web browsers, use 111 | [[https://addons.mozilla.org/en-US/firefox/addon/linkhints/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search][this]] link instead. 112 | 113 | ** Setting up multiple monitors 114 | 115 | Currently this module does not provide any simplified setup for multi-head 116 | configurations. Please refer to 117 | https://github.com/ch11ng/exwm/wiki#randr-multi-screen for how to do this. 118 | 119 | ** Setting up an alternate Emacs configuration for nested Emacs sessions 120 | 121 | Although we provide a curated default vanilla-Emacs configuration (without 122 | Evil), you can write your own configuration file and evaluate 123 | 124 | #+begin_src elisp 125 | (setq +exwm-vanilla-emacs-config-file "/path/to/alternate-emacs-config.el") 126 | #+end_src 127 | 128 | ** Using i3-style keybindings 129 | 130 | If you would like i3-style keybindings, I recommend adding the following 131 | paragraph to your private configuration: 132 | #+begin_src elisp 133 | ;; i3-like keybindings for EXWM 134 | (windmove-mode +1) 135 | (when (featurep! :private exwm) 136 | (map! "s-j" #'windmove-left 137 | "s-k" #'windmove-down 138 | "s-l" #'windmove-up 139 | "s-;" #'windmove-right 140 | "s-J" #'windmove-swap-states-left 141 | "s-K" #'windmove-swap-states-down 142 | "s-L" #'windmove-swap-states-up 143 | "s-Q" #'delete-window 144 | "s-:" #'windmove-swap-states-right 145 | "s-v" #'split-window-right 146 | "s-h" #'split-window-below 147 | (:when (featurep! :ui workspaces) 148 | "s-1" #'+workspace/switch-to-0 149 | "s-2" #'+workspace/switch-to-1 150 | "s-3" #'+workspace/switch-to-2 151 | "s-4" #'+workspace/switch-to-3 152 | "s-5" #'+workspace/switch-to-4 153 | "s-6" #'+workspace/switch-to-5 154 | "s-7" #'+workspace/switch-to-6 155 | "s-8" #'+workspace/switch-to-7 156 | "s-9" #'+workspace/switch-to-8 157 | "s-0" #'+workspace/switch-to-final 158 | (:when (featurep! :term vterm) 159 | "s-" (defun +run-or-raise-vterm () 160 | (interactive) 161 | (+workspace-switch "Vterm" t) 162 | (let ((display-buffer-alist)) 163 | (vterm most-positive-fixnum))))) 164 | "s-d" #'app-launcher-run-app 165 | "s-'" #'exwm-edit--compose) 166 | (after! exwm 167 | (dolist (key '(?\s-h ?\s-j ?\s-k ?\s-l ?\s-H ?\s-J ?\s-K ?\s-L ?\s-0 ?\s-1 168 | ?\s-2 ?\s-3 ?\s-4 ?\s-5 ?\s-6 ?\s-7 ?\s-8 ?\s-9 ?\s-d 169 | ?\s-\; ?\s-v ?\s-' ?\C-\[ ?\s-Q)) 170 | (cl-pushnew key exwm-input-prefix-keys)))) 171 | #+end_src 172 | 173 | * Troubleshooting 174 | 175 | ** How do I send ~escape~ or ~C-c~ to applications? 176 | 177 | | Keybind | Description | 178 | |---------+--------------------------------------------------------| 179 | | =C-c C-i= | Send the escape key (only bound when Evil is enabled). | 180 | | =C-c C-c= | Send the C-c key. | 181 | | C-c C-q | Send the next key to the application. | 182 | 183 | ** When I mouse-click while in Evil's normal state I get an error. 184 | 185 | I (the author) do not know how to suppress or remove the, 186 | "evil-mouse-drag-region must be bound to an event with parameters" error. I 187 | recommend ignoring this error though as your clicks still register. 188 | 189 | This bug is a result of a workaround I copied from this GitHub issue: 190 | https://github.com/walseb/exwm-firefox-evil/issues/1 191 | 192 | * Future directions 193 | :PROPERTIES: 194 | :CREATED_TIME: [2022-02-25 Fri 21:17] 195 | :END: 196 | 197 | - [ ] Document workarounds for packages utilizing child frames. 198 | - [ ] Write a separate nested Emacs mode. 199 | - [ ] Add Evil to the nested vanilla Emacs configuration file. 200 | -------------------------------------------------------------------------------- /config.el: -------------------------------------------------------------------------------- 1 | ;;; os/exwm/config.el -*- lexical-binding: t; -*- 2 | 3 | ;; Only load this module inside the X Window System. 4 | (when (eq 'x (framep (selected-frame))) 5 | 6 | (defvar +exwm-vanilla-emacs-config-file 7 | (expand-file-name "./exwm-vanilla-emacs-default-config.el" (doom-module-locate-path :private 'exwm)) 8 | "The configuration loaded in our nested vanilla Emacs sessions. 9 | The configuration must be a single symbolic expression as it is 10 | handed off to. A macro syntax is valid. Macro 11 | expansion occurs within the parent Emacs session.") 12 | (defvar +exwm-vanilla-emacs-theme 13 | (if (memq doom-theme '(modus-operandi modus-vivendi)) 14 | doom-theme 15 | 'wheatgrass) 16 | "The theme loaded in our vanilla Emacs child sessions.") 17 | 18 | (add-hook 'exwm-update-title-hook #'+exwm-rename-buffer-to-title) 19 | 20 | (use-package! exwm 21 | :config 22 | (use-package! exwm-randr 23 | :config 24 | (exwm-randr-enable)) 25 | 26 | (use-package! exwm-systemtray 27 | :config 28 | (exwm-systemtray-enable)) 29 | 30 | ;; A few `ido' fixes. 31 | (use-package! exwm-config 32 | :config 33 | (exwm-config--fix/ido-buffer-window-other-frame)) 34 | 35 | ;; Using `helm-display-buffer-in-own-frame' causes EXWM to emit an error. 36 | (after! helm 37 | (when (eq helm-default-display-buffer-functions 38 | #'helm-display-buffer-in-own-frame) 39 | (setq! helm-default-prompt-display-function #'helm-default-display-buffer))) 40 | 41 | ;; Configure emacs input methods in all X windows. 42 | (use-package! exwm-xim 43 | :config 44 | ;; These variables are required for X programs to connect with XIM. 45 | (setenv "XMODIFIERS" "@im=exwm-xim") 46 | (setenv "GTK_IM_MODULE" "xim") 47 | (setenv "QT_IM_MODULE" "xim") 48 | (setenv "CLUTTER_IM_MODULE" "xim") 49 | (setenv "QT_QPA_PLATFORM" "xcb") 50 | (setenv "SDL_VIDEODRIVER" "x11") 51 | (exwm-xim-enable)) 52 | 53 | (exwm-enable) 54 | 55 | ;; Never suspend Emacs when EXWM. Doing so locks up Emacs. 56 | (map! [remap suspend-frame] #'undefined) 57 | (advice-add #'suspend-frame :override #'ignore) 58 | 59 | ;; Prevent EXWM buffers from changing their name while not focused. 60 | ;; This allows Persp to restore them as intended. 61 | (when (featurep! :ui workspaces) 62 | (advice-add #'exwm--update-utf8-title :around 63 | #'exwm--update-utf8-title-advice)) 64 | 65 | ;; Show EXWM buffers in buffer switching prompts. 66 | (add-hook 'exwm-mode-hook #'doom-mark-buffer-as-real-h) 67 | 68 | (cl-pushnew ?\C-c exwm-input-prefix-keys) 69 | 70 | (map! :map exwm-mode-map 71 | :prefix "C-c" 72 | ;; Add Eshell-like keys for seding TAB and ESC to applications. 73 | :desc "Send Escape" "C-i" (cmd! (exwm-input--fake-key 'escape)) 74 | :desc "Send C-c" "C-c" (cmd! (exwm-input--fake-key ?\C-c)) 75 | ;; We also set up a separate general way to send prefix keys to our 76 | ;; application. 77 | :desc "Send the next key" "C-q" #'exwm-input-send-next-key) 78 | 79 | ;; Set up commands for running nested Emacs sessions 80 | (map! :leader 81 | (:prefix "o" 82 | :desc "Nested Emacs" "n" #'+exwm-open-nested-emacs 83 | :desc "Nested vanilla Emacs" "N" #'+exwm-open-nested-vanilla-emacs) 84 | (:prefix "f" 85 | :desc "Find file in vanilla Emacs" "v" #'+exwm-vanilla-emacs-find-file 86 | :desc "Find this file in vanilla Emacs" "V" #'+exwm-vanilla-emacs-find-this-file)) 87 | 88 | ;; For people who run nested Emacs instances within EXWM. 89 | (setq! exwm-replace nil) 90 | 91 | ;; Fixes focus being lost from EXWM buffers when switching workspaces or 92 | ;; buffers. This seems to be a problem specific to Doom. There is probably a 93 | ;; more elegant solution but it requires more digging. 94 | (advice-add #'+workspace/switch-to :after #'+exwm-refocus-application) 95 | (add-hook 'doom-switch-buffer-hook #'+exwm-refocus-application) 96 | 97 | (when (featurep! :editor evil) 98 | (evil-set-initial-state 'exwm-mode 'emacs) 99 | (after! evil-snipe 100 | (add-to-list 'evil-snipe-disabled-modes 'exwm-mode)) 101 | (cl-pushnew (aref (kbd doom-leader-alt-key) 0) exwm-input-prefix-keys)) 102 | 103 | (when (featurep! :ui popup) 104 | (cl-pushnew ?\C-` exwm-input-prefix-keys)) 105 | 106 | ;; Workarounds for packages utilizing childframes 107 | (advice-add #'corfu--make-frame :around #'+corfu--make-frame-a) 108 | (advice-add #'corfu--popup-redirect-focus :override 109 | #'+corfu--popup-redirect-focus-a) 110 | (advice-add #'corfu-doc--redirect-focus :override 111 | #'+corfu--popup-redirect-focus-a) 112 | (advice-add #'corfu-doc--make-frame :around #'+corfu-doc--make-frame-a) 113 | (advice-add #'mini-popup--setup-frame :around #'+mini-popup--setup-frame-a) 114 | 115 | ;; Remove `window-live-p' errors 116 | (advice-add #'select-window :around #'+exwm-select-window-a) 117 | 118 | ;; Remove invalid face errors 119 | (setq-hook! exwm-mode 120 | outline-minor-mode-highlight nil)) 121 | 122 | (use-package! exwm-edit 123 | :after exwm 124 | :init 125 | (defvar exwm-edit-bind-default-keys nil) 126 | (map! :map exwm-mode-map 127 | :desc "Edit this input field in Emacs" "C-c '" #'exwm-edit--compose 128 | :localleader 129 | :desc "Edit this input field in Emacs" "'" #'exwm-edit--compose) 130 | :config 131 | (setq! exwm-edit-split "below" 132 | exwm-edit-yank-delay 0 133 | exwm-edit-paste-delay 0) 134 | 135 | ;; For some reason, `exwm-edit--yank' does not work for me reliably without 136 | ;; this. 137 | (advice-add #'exwm-edit--yank 138 | :override 139 | (defun +exwm-edit--yank () 140 | (delete-region (point-min) (point-max)) 141 | (insert (gui-get-selection 'CLIPBOARD 'UTF8_STRING)))) 142 | (advice-add #'exwm-edit--compose 143 | :around 144 | (defun +exwm-edit--compose-a (oldfun &rest args) 145 | (exwm-input--fake-key ?\C-a) 146 | (run-at-time 0.06 nil #'exwm-input--fake-key ?\C-c) 147 | (run-at-time 0.12 nil #'apply oldfun args))) 148 | (advice-add #'exwm-edit--send-to-exwm-buffer 149 | :override 150 | (defun +exwm-edit--send-to-exwm-buffer-a (text) 151 | (exwm-edit--switch) 152 | (exwm-input--set-focus (exwm--buffer->id (window-buffer (selected-window)))) 153 | (+exwm-refocus-application) 154 | (run-at-time 0.07 nil (lambda () (exwm-input--fake-key ?\C-a))) 155 | (run-at-time 0.12 nil 156 | (lambda (text) 157 | (kill-new text) 158 | (exwm-input--fake-key ?\C-v)) 159 | text))) 160 | 161 | (add-hook! '(exwm-edit-before-finish-hook 162 | exwm-edit-before-cancel-hook) 163 | (defun exwm-edit-clear-last-kill () 164 | (setq exwm-edit-last-kill nil))) 165 | (add-hook 'exwm-edit-compose-hook #'+exwm-edit-activate-appropriate-major-mode)) 166 | 167 | (use-package! exwm-mff 168 | :hook (exwm-init . exwm-mff-mode)) 169 | 170 | (use-package! app-launcher 171 | :commands app-launcher-run-app 172 | :init 173 | (map! :leader :desc "Launch Linux app" "$" #'app-launcher-run-app) 174 | :config 175 | (defun +app-launcher--action-function-default-a (selected) 176 | (when (featurep! :ui workspaces) 177 | (when (string-match " ([a-zA-Z]+)$" selected) 178 | (setq selected (substring-no-properties selected 0 (match-beginning 0)))) 179 | (+workspace-switch selected t))) 180 | (advice-add #'app-launcher--action-function-default :before 181 | #'+app-launcher--action-function-default-a)) 182 | 183 | (use-package exwm-evil 184 | :when (featurep! :editor evil) 185 | :after exwm 186 | :config 187 | (exwm-evil-enable-mouse-workaround) 188 | (add-hook 'exwm-manage-finish-hook 'exwm-evil-mode) 189 | (cl-pushnew 'escape exwm-input-prefix-keys)) 190 | 191 | (use-package! exwm-firefox-evil 192 | :when (featurep! :editor evil) 193 | :after exwm 194 | :config 195 | (cl-pushnew 'escape exwm-input-prefix-keys) 196 | ;; We can use VIM keys with any browser that has compatible keybindings. 197 | (cl-loop for class in '("firefoxdeveloperedition" 198 | "\"firefoxdeveloperedition\"" 199 | "IceCat" 200 | "chromium-browser" 201 | "Chromium" 202 | "Google-chrome" 203 | "Google-chrome-unstable" 204 | "Chromium-bin-browser-chromium" 205 | "librewolf-default") 206 | do (cl-pushnew class exwm-firefox-evil-firefox-class-name 207 | :test #'string=)) 208 | 209 | (add-hook 'exwm-manage-finish-hook 'exwm-firefox-evil-activate-if-firefox) 210 | 211 | ;; Automatically reenable `evil-normal-state' after hitting `'. 212 | (advice-add #'exwm-firefox-core-focus-search-bar 213 | :after 214 | (defun +exwm-firefox-core-focus-search-bar-a () 215 | (add-transient-hook! 'exwm-update-title-hook 216 | (when exwm-firefox-evil-mode (exwm-firefox-evil-normal))))) 217 | (advice-add #'exwm-firefox-core-tab-new 218 | :after 219 | (defun +exwm-firefox-core-tab-new-a () 220 | (add-transient-hook! 'exwm-update-title-hook 221 | (+exwm-firefox-core-focus-search-bar-a)))) 222 | 223 | (map! :map exwm-firefox-evil-mode-map 224 | :n "f" #'exwm-firefox-core-hint-links ; Requires Link Hints add-on. 225 | :n "F" #'exwm-firefox-core-hint-links-new-tab-and-switch 226 | :n "u" #'exwm-firefox-core-tab-close-undo 227 | :n "U" #'exwm-firefox-core-undo 228 | :n "/" #'exwm-firefox-core-find ; Compatible with Chrome as well. 229 | ;; Do not accidentally send escape 230 | :n [remap exwm-firefox-core-cancel] #'exwm-evil-core-normal 231 | 232 | :after exwm-evil 233 | ;; These are mroe in line with Evil than the default 234 | :n "g0" #'exwm-firefox-core-tab-first 235 | :n "g$" #'exwm-firefox-core-tab-last 236 | :n "0" #'exwm-evil-core-beginning-of-line 237 | :n "$" #'exwm-evil-core-end-of-line 238 | ;; This way we can use prefix arguments with these commands. 239 | :n "j" #'exwm-evil-core-down 240 | :n "k" #'exwm-evil-core-up 241 | :n "h" #'exwm-evil-core-left 242 | :n "l" #'exwm-evil-core-right 243 | ;; Add zoom commands 244 | :n "+" #'exwm-evil-core-zoom-in 245 | :n "-" #'exwm-evil-core-zoom-out 246 | :n "=" #'exwm-evil-core-reset-zoom 247 | ;; Pass through some keybindings from Firefox 248 | :n "C-" #'exwm-firefox-core-tab-next 249 | :n "C-" #'exwm-firefox-core-tab-previous 250 | :n "" #'exwm-firefox-core-focus-search-bar))) 251 | --------------------------------------------------------------------------------