├── .gitignore ├── README.org ├── config.el ├── files ├── exwm-start └── exwm.desktop ├── funcs.el ├── img └── spacemacsOS.jpg └── packages.el /.gitignore: -------------------------------------------------------------------------------- 1 | auto-save-list/ 2 | elpa/ 3 | export/ 4 | !/core/tools/export/ 5 | ac-comphist.dat 6 | eproject.lst 7 | .smex-items 8 | \#* 9 | .places 10 | .cache 11 | /eww-bookmarks 12 | eshell/history 13 | .emacs.desktop 14 | .emacs.desktop.lock 15 | eshell/alias 16 | eshell/lastdir 17 | url/* 18 | my-org/ 19 | org-files/ 20 | semanticdb/ 21 | edts/ 22 | .recentf 23 | .recentf~ 24 | projectile.cache 25 | projectile-bookmarks.eld 26 | tramp 27 | elnode/ 28 | var/ 29 | crown/ 30 | *.stackdump 31 | bookmarks 32 | /.my-keybindings.el.swp 33 | .DS_Store 34 | tmp/ 35 | /history 36 | .python-environments/ 37 | server/ 38 | /network-security.data 39 | *.elc 40 | *.pyc 41 | /*.zip 42 | *.db 43 | python-*-docs-html 44 | /.cask/ 45 | /session.* 46 | /srecode-map.el 47 | /recentf 48 | .mc-lists.el 49 | 50 | # Private directory 51 | private/ 52 | !private/README.md 53 | !private/snippets/README.md 54 | !private/local/README.md 55 | /games/ 56 | /doc/COMMUNITY.org 57 | /doc/CONTRIBUTING.org 58 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * EXWM contribution layer for Spacemacs 2 | 3 | ** Table of Contents :TOC@4: 4 | - [[#exwm-contribution-layer-for-spacemacs][EXWM contribution layer for Spacemacs]] 5 | - [[#description][Description]] 6 | - [[#install][Install]] 7 | - [[#note-about-display-managers][Note about Display Managers]] 8 | - [[#not-having-display-managers][Not having Display Managers]] 9 | - [[#osx][OSX]] 10 | - [[#features][Features]] 11 | - [[#autostart][Autostart]] 12 | - [[#randr][RandR]] 13 | - [[#key-bindings][Key bindings]] 14 | - [[#normal-state-bindings][Normal State Bindings]] 15 | - [[#major-mode-bindings][Major Mode Bindings]] 16 | - [[#global-bindings][Global Bindings]] 17 | - [[#window-behaviour-regarding-char-and-line-mode][Window behaviour regarding char and line mode]] 18 | - [[#known-limitations][Known Limitations]] 19 | 20 | ** Description 21 | 22 | *Fork Note:* This layer is based on CestDiego's PR from 23 | https://github.com/syl20bnr/spacemacs/pull/3321. 24 | 25 | The time has arrived when one can finally use Emacs as a Window Manager, long 26 | ago the concepts of *windows* and *frames* made so much sense when one was 27 | working in a TTY, and basically that's all the window management you got. In 28 | these *modern* times though, it sounds silly to have those names. But not 29 | Anymore! 30 | 31 | Thanks to @ch11ng and his [[https://github.com/ch11ng/exwm][EXWM]] project we can now use Emacs as our window 32 | manager, and all those *windows* that didn't make sense before, now can not only 33 | hold buffers, but X Windows, that means that you can spawn a Browser Window, or 34 | your music player, or anything. 35 | 36 | I urge you to read the [[https://github.com/ch11ng/exwm/wiki][EXWM Wiki]] for a more in depth explanation and if you can 37 | contribute, please do so! The purpose of me making this layer is that I find it 38 | awesome and having nice defaults would make more people dive into it and the 39 | project would receive more attention and contributions which will only make it 40 | more awesome, so if you try it and like it, share it! 41 | 42 | This is how it looks like: 43 | 44 | [[img/spacemacsOS.jpg]] 45 | 46 | ** Install 47 | To use this contribution, 48 | - (status quo 2021-03-28) install Spacemacs, switch to *develop* branch 49 | - place this repository in one of Spacemacs' layer directories 50 | (=~/.emacs.d/private/local/exwm=, =~/.spacemacs.d/private/exwm=, or =~/.spacemacs.d/layers/exwm=) 51 | - add an entry ~exwm~ for the layer in your =~/.spacemacs=, optionally set some 52 | configuration variables: 53 | 54 | #+begin_src emacs-lisp 55 | ;; (setq-default dotspacemacs-configuration-layers ... 56 | (exwm :variables 57 | exwm-enable-systray t 58 | exwm-autostart-xdg-applications t 59 | exwm-locking-command "i3lock -n" 60 | exwm-install-logind-lock-handler t 61 | exwm-autostart-environment '("DESKTOP_SESSION=kde" "KDE_SESSION_VERSION=5") 62 | exwm-custom-init (lambda() 63 | (exwm/autostart-process "Dunst OSD" "dunst") 64 | (exwm/autostart-process "KWallet Daemon" "kwalletd5"))) 65 | ;; ) 66 | #+end_src 67 | 68 | *** Note about Display Managers 69 | 70 | This is most common among Ubuntu and derivative users: 71 | 72 | LightDM, GDM, and other Display managers that need a session file will need you 73 | to copy the [[file:files/exwm.desktop][desktop file]] I bundled with this layer to 74 | ~/usr/local/xsessions/exwm.desktop~, that's what I used on my Ubuntu box, but 75 | make sure the [[file:files/exwm-start][exwm-start script]] is in your PATH, you can put it in 76 | ~/usr/local/bin/exwm-start~ and that should be enough, next time you log out, 77 | select the EXWM session instead of the Ubuntu one and you should be alright. 78 | 79 | *** Not having Display Managers 80 | 81 | If you use Arch, Gentoo, or any other Linux, most probably you'll start your 82 | configurations via a ~.xinitrc~ file, just append ~exwm-start~ to the end of 83 | that file and you should be fine. I do recommend to check the [[https://github.com/ch11ng/exwm/wiki][EXWM Wiki]] for more 84 | details. 85 | 86 | *** OSX 87 | 88 | If you are an OSX user, please report back on whether this works with xQuartz, 89 | always back up your data before attempting to try stuff like this. 90 | 91 | ** Features 92 | Configurable support for optional EXWM features: 93 | 94 | - System Tray 95 | 96 | The following features are preconfigured/provided on top of EXWM: 97 | 98 | - EXWM buffer names are prefixed with a configurable prefix (default: ~X:~), to 99 | aid buffer selection in ivy/helm. 100 | - Session locking with specified lock command 101 | - Support for D-Bus based locking (e.g. ~loginctl lock-session~) 102 | - Optionally use [[https://github.com/DamienCassou/desktop-environment][~desktop-environment~]] to support special keys for things like 103 | audio volume control or brightness control (except for locking) 104 | - Uses ~framemove~ to enable navigation between frames by using window 105 | navigation at frame borders. Can be disabled by setting 106 | ~exwm-move-frame-at-edge~ to ~nil~. 107 | 108 | *** Autostart 109 | If ~exwm-autostart-xdg-applications~ is non-nil, ~.desktop~ files in 110 | =$XDG_CONFIG_HOME/autostart= and ~XDG_CONFIG_DIRS~ will be used to run applications at 111 | startup. (See [[https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html][Specification]] for details). 112 | 113 | For the purpose of controlling whether an entry should be run under SpacemacsOS, 114 | the ~OnlyShowIn~ and ~NotShowIn~ keys are checked for the string ~EXWM~. 115 | 116 | The variable ~exwm-autostart-environment~ can be used to extend 117 | ~process-environment~ for the started processes. 118 | 119 | This is disabled per default. 120 | 121 | *** RandR 122 | If ~autorandr~ is found in the current path, ~exwm//load-autorandr-profile~ 123 | can be used to interactively select from the list of autorandr profiles. The 124 | default entry is either the one that ~autorandr -c~ would choose, or one of the 125 | builtin configurations. The default binding for that command is ~s-s~. 126 | 127 | Automatic switching of profiles is supported by ~autorandr~ directly, or there 128 | should be some OS support to install the corresponding system configuration. 129 | 130 | Note that any custom autorandr profiles have to be configured outside of Spacemacs (see ~autorandr 131 | --help~). 132 | 133 | The variable ~exwm-randr-dwim~, if set (default), will try to assign a sensible 134 | value to ~exwm-randr-monitor-plist~ to spread the workspaces out to the detected screens. 135 | 136 | The ~s-s~ binding is not assigned if the ~autorandr~ executable is 137 | not found at startup. 138 | 139 | One tool which is useful for interactively setting up screen configurations is 140 | [[https://christian.amsuess.com/tools/arandr/][ARandR]] (note that their save files don't have anything to with autorandr's). 141 | 142 | ** Key bindings 143 | 144 | *** Normal State Bindings 145 | 146 | | Key Binding | Description | 147 | |--------------------+-----------------------------------------------------------------------| 148 | | ~i,~ | Switch from normal state to insert state, (similar to EXWM char mode) | 149 | 150 | **** Major Mode Bindings 151 | 152 | These bindings are accessible via the major mode leader keys when EXWM buffers 153 | are focused, but only in normal state. This means that you usually have to get 154 | out of insert state with ~s-ESC~ first. 155 | 156 | | Key Binding | Description | 157 | |-------------+--------------------------------------------| 158 | | ~SPC m f~ | Toggle floating/tiling for current window | 159 | | ~SPC m w~ | Move current window to different workspace | 160 | | ~SPC m F~ | Toggle fullscreen | 161 | 162 | *** Global Bindings 163 | 164 | As other window managers the ~s~ or *Super* key (Windows Key) is the one that 165 | is the prefix to every action. We'll use a lot of ~s~. These commands work in 166 | both normal state and insert state. 167 | 168 | 169 | | Key Binding | Description | 170 | |-----------------------+---------------------------------------------------------| 171 | | ~s-SPC~ | Spacemacs Leader Key (in insert state) | 172 | | ~s-[1 2 3 ... 8 9 0]~ | Switch to workspace [0 ... 9] | 173 | | ~s-[~, ~s-]~ | Switch to previous/next workspace | 174 | | ~s-{~, ~s-}~ | Move current buffer to previous/next workspace | 175 | | ~s-TAB~ | Switch to last workspace | 176 | | ~s-c~ | Switch to EXWM char mode | 177 | | ~s-ESC~ | Switch to normal state, cancel fullscreen | 178 | | ~s-r~ | App Launcher | 179 | | ~s-pause~ | Lock Screen | 180 | | ~s-~ | Open Terminal in current dir (requires ~terminal-here~) | 181 | | ~s-u,U~ | Undo, Redo window configurations | 182 | | ~s-b~ | Select and switch to buffer | 183 | | ~s-q~ | Close current buffer | 184 | | ~s-h,j,k,l~ | Switch to left,lower,upper,right window | 185 | | ~s-H,J,K,L~ | Move window to far left,down,lower,upper,right | 186 | | ~M-s-h,j,k,l~ | Resizing (try them, it's too hard to explain) | 187 | | ~s-m~ | Toggle maximized buffer | 188 | | ~SPC T D~ | Toggle ~desktop-environment-mode~ | 189 | | ~s-d~ | Toggle exwm debug mode (for development) | 190 | | ~s-s~ | (conditional, see [[*RandR][RandR]]) Change autorandr profile | 191 | 192 | Since running programs is one of the more common task, some explicit support is 193 | provided for that via the leader key ~SPC &~. 194 | 195 | For these commands as well as for ~s-r~ the working directory is set to the 196 | value of =user-home-directory= for the started process. 197 | 198 | | Key Binding | Description | 199 | |-------------+---------------------------------------------------------------------------| 200 | | ~SPC & s~ | Ask for program to run in separate window below current window | 201 | | ~SPC & v~ | Ask for program to run in separate window to the right of current window. | 202 | 203 | *This layer overrides the leader bindings on* ~SPC F~, which are tricky with EXWM 204 | anyways, and replaces them with the following bindings. 205 | 206 | | Key Binding | Description | 207 | |-------------+--------------------------------------------| 208 | | ~SPC F r~ | Reset state of current window (exwm-reset) | 209 | | ~SPC F h~ | Hide floating window | 210 | | ~SPC F w~ | Prompt for workspace and switch to it | 211 | | ~SPC F a~ | Add new workspace | 212 | | ~SPC F d~ | Delete current workspace | 213 | | ~SPC F m~ | Move Workspace to different position | 214 | | ~SPC F s~ | Interchange position of two workspaces | 215 | | ~SPC F M d~ | Detach Minibuffer (for autohide enabled) | 216 | | ~SPC F M a~ | Attach Minibuffer (for autohide enabled) | 217 | 218 | * Window behaviour regarding char and line mode 219 | 220 | In stock Emacs, EXWM uses char mode and line mode to distinguish between using 221 | the keyboard to control an application vs. using the keyboard to control the 222 | application's *buffer*. Since Spacemacs pursues a different concept regarding 223 | keybindings SpacemacsOS handles this differently: 224 | 225 | - EXWM buffers are used in line mode per default, all local key bindings are removed per 226 | default. This corresponds to Spacemacs insert state. 227 | - The command ~exwm/enter-normal-state~ (default binding ~s-ESC~) enters 228 | EXWM's input passthrough mode, meaning that *all* key-presses are sent to 229 | Spacemacs, and not the application. This corresponds to Spacemacs normal 230 | state. Press ~i~ to get back to sending input to the application. 231 | - Certain applications (e.g. SDL based) may not work correctly with line mode, 232 | and register double keyboard events. In this case, ~s-c~ can be used to 233 | enter EXWM char mode. To return from that, also use ~exwm/enter-normal-state~ 234 | 235 | * Known Limitations 236 | (3rd party research welcome...) 237 | 238 | - ~s-SPC m~ does not work for accessing major mode bindings 239 | - When clicking into a buffer in normal state to press something, it has to be 240 | clicked twice, because the first click only gets you into insert state 241 | - In some cases, the Emacs GUI becomes completely unresponsive if an X window 242 | was opened by Emacs' foreground command loop, e.g. when emacs starts an waits 243 | for the return of an interactive graphical password entry dialog. To get it 244 | to respond again, switch to a text console and send the SIGUSR2 signal 245 | (e.g. =pkill -USR2 emacs=). 246 | - Under certain conditions, an EXWM buffer may end up in a state where Emacs 247 | wants to insert into the underlying buffer, and pressing ~i~ does not get you 248 | into insert state. If that happens, use ~s-ESC~ to get to line mode/normal 249 | state, where you should be able to use ~i~ to to get into insert state again. 250 | - `which-key` does only display "prefix" for nested leader key bindings for ~s-SPC~ 251 | - There are dependencies on EXWM internals: 252 | - ~exwm--id~ 253 | - ~exwm-workspace--count~ 254 | - ~exwm-workspace--workspace-from-frame-or-index~ 255 | - ~exwm-layout--fullscreen-p~ 256 | - ~exwm-randr--get-monitors~ 257 | - ~exwm/workspace-move-buffer-to-workspace~ is buggy. It seems to depend on the window 258 | layout whether the current buffer will be moved correctly, or the current 259 | frame will be messed up... 260 | -------------------------------------------------------------------------------- /config.el: -------------------------------------------------------------------------------- 1 | (defvar exwm-locking-command "lock" 2 | "Command to run when locking session") 3 | 4 | (defvar exwm-install-logind-lock-handler nil 5 | "If this is non-nil and `exwm-locking-command' is set, register a D-BUS handler on the session lock signal.") 6 | 7 | (defvar exwm-app-launcher--prompt "$ " 8 | "Prompt for the EXWM application launcher") 9 | 10 | (defvar exwm-hide-tiling-modeline nil 11 | "Whether to hide modeline.") 12 | 13 | (defvar exwm-buffer-name-prefix "X:" 14 | "A prefix to append to each buffer managed by exwm") 15 | 16 | (defvar exwm-enable-systray nil 17 | "Whether to enable EXWM's bundled system tray implementation.") 18 | 19 | (defvar exwm-autostart-xdg-applications nil 20 | "Whether to run $XDG_USER_HOME/autostart applications after initialization.") 21 | 22 | (defvar exwm-autostart-environment '() 23 | "List of \"KEY=value\" strings which should be set when running autostart applications. 24 | 25 | Example: '(\"DESKTOP_SESSION=kde\" \"KDE_SESSION_VERSION=5\") ") 26 | 27 | (defvar exwm-custom-init nil 28 | "This can be set to a function that runs after all other EXWM initialization.") 29 | 30 | (defvar exwm-workspace-switch-wrap t 31 | "Whether `exwm/workspace-next' and `exwm/workspace-prev' should wrap.") 32 | 33 | (defvar exwm-randr-dwim t 34 | "Whether to try to dwim workspace/screen association in the screen change hook.") 35 | 36 | (defvar exwm-move-frame-at-edge t 37 | "If enabled, use framemove to switch frames when trying to move 38 | outside of current frame." ) 39 | -------------------------------------------------------------------------------- /files/exwm-start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # this makes it work in Ubuntu 4 | xhost + 5 | ## you might need to append the TTY you are working on 6 | xinit 7 | 8 | wmname LG3D 9 | 10 | # Remap caps lock to left control. This is not strictly speaking 11 | # exwm related, but it's handy 12 | setxkbmap -option 'ctrl:no caps' 13 | 14 | # Set fallback cursor 15 | xsetroot -cursor_name left_ptr 16 | 17 | # If Emacs is started in server mode, `emacsclient` is a convenient way to edit 18 | # files in place (used by e.g. `git commit`) 19 | export VISUAL=emacsclient 20 | export EDITOR="$VISUAL" 21 | 22 | # Finally launch emacs and enable exwm 23 | exec dbus-launch --exit-with-session emacs --eval "(exwm-enable)" 24 | -------------------------------------------------------------------------------- /files/exwm.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=EXWM 3 | Comment=Emacs Window Manager 4 | Exec=exwm-start 5 | Type=Application 6 | -------------------------------------------------------------------------------- /funcs.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t; -*- 2 | 3 | (require 'dbus) 4 | (require 'xdg) 5 | (require 'edmacro) 6 | ;; Can be used to bind a key to jumping to an application, or alternatively starting it. E.g.: 7 | ;; 8 | ;; (exwm/bind-switch-to-or-run-command "s-f" "Firefox" "firefox") 9 | ;; 10 | ;; The window class can be found out with exwm's builtin info functions, but for most applications it should just match the buffer name. 11 | (defun exwm/bind-switch-to-or-run-command (key window-class command) 12 | (exwm-input-set-key (kbd key) 13 | `(lambda () 14 | (interactive) 15 | (exwm/switch-to-buffer-or-run ,window-class ,command)))) 16 | 17 | ;; (defun exwm//switch-to-line-mode () 18 | ;; "Used as a hook to switch to line mode when transient mode starts." 19 | ;; (when (eq exwm--input-mode 'char-mode) 20 | ;; ;; (setq exwm--switch-to-char-after-transient (current-buffer)) 21 | ;; (call-interactively 'exwm-input-grab-keyboard))) 22 | 23 | (defun exwm//persp-mode-inhibit-p (frame) 24 | (frame-parameter frame 'unsplittable)) 25 | 26 | (defun exwm/bind-command (key command &rest bindings) 27 | (while key 28 | (exwm-input-set-key (kbd key) 29 | `(lambda () 30 | (interactive) 31 | (start-process-shell-command ,command nil ,command))) 32 | (setq key (pop bindings) 33 | command (pop bindings)))) 34 | 35 | ;; Simulate insert state by using line mode without passthrough 36 | (defun exwm/enter-insert-state () 37 | (interactive) 38 | (setq exwm-input-line-mode-passthrough nil) 39 | (call-interactively 'exwm-input-grab-keyboard) 40 | (evil-insert-state)) 41 | 42 | ;; Simulate normal state by using line mode with passthrough, i.e. forward all commands to emacs 43 | (defun exwm/enter-normal-state () 44 | (interactive) 45 | (setq exwm-input-line-mode-passthrough t) 46 | (call-interactively 'exwm-input-grab-keyboard) 47 | (evil-normal-state)) 48 | 49 | (defun exwm/escape () 50 | "Switch to normal state, and cancel possible fullscreen layout. Also close minibuffer." 51 | (interactive) 52 | (exwm/enter-normal-state) 53 | (exwm-layout-unset-fullscreen) 54 | (when (active-minibuffer-window) 55 | (minibuffer-keyboard-quit))) 56 | 57 | (defun exwm/enter-char-mode () 58 | "Enter EXWM char mode." 59 | (interactive) 60 | (when exwm--id 61 | (exwm/enter-insert-state) 62 | (call-interactively 'exwm-input-release-keyboard))) 63 | 64 | (defun exwm/switch-to-buffer-or-run (window-class command) 65 | "Switch to first buffer with window-class, and if not present, run command." 66 | (let ((buffer 67 | (cl-find window-class (buffer-list) :key (lambda(b) (cdr (assoc 'exwm-class-name (buffer-local-variables b)))) :test 'string-equal))) 68 | (if buffer 69 | (exwm-workspace-switch-to-buffer buffer) 70 | (start-process-shell-command command nil command)))) 71 | 72 | (defun exwm/rename-buffer-name () 73 | (let* ((part1 exwm-class-name) 74 | (part2 (when (not (string-equal exwm-class-name exwm-title)) 75 | (concat "/" exwm-title))) 76 | (maxlen 40) 77 | (name (concat exwm-buffer-name-prefix part1 (or part2 "")))) 78 | ;; (if (> (length name) maxlen) 79 | ;; (concat (cl-subseq name 0 (- maxlen 3)) "...") 80 | ;; name) 81 | name 82 | )) 83 | 84 | ;; All buffers created in EXWM mode are named "*EXWM*". You may want to change 85 | ;; it in `exwm-update-class-hook' and `exwm-update-title-hook', which are run 86 | ;; when a new window class name or title is available. Here's some advice on 87 | ;; this subject: 88 | ;; + Always use `exwm-workspace-rename-buffer` to avoid naming conflict. 89 | ;; + Only renaming buffer in one hook and avoid it in the other. There's no 90 | ;; guarantee on the order in which they are run. 91 | ;; + For applications with multiple windows (e.g. GIMP), the class names of all 92 | ;; windows are probably the same. Using window titles for them makes more 93 | ;; sense. 94 | ;; + Some application change its title frequently (e.g. browser, terminal). 95 | ;; Its class name may be more suitable for such case. 96 | ;; In the following example, we use class names for all windows expect for 97 | ;; Java applications and GIMP. 98 | (defun exwm/rename-buffer () 99 | (let* ((part1 exwm-class-name) 100 | (part2 (when (not (string-equal exwm-class-name exwm-title)) 101 | (concat "/" exwm-title))) 102 | (name (exwm/rename-buffer-name))) 103 | (exwm-workspace-rename-buffer name))) 104 | 105 | ;; Helper 106 | ;; TODO: actually incorporate exwm-workspace-switch-wrap here... 107 | (defun exwm//ws-offset (offset) 108 | "Return the number of the workspace which is OFFSET workspaces 109 | away from the current workspace, cycling if necessary." 110 | (mod (+ offset exwm-workspace-current-index) (exwm-workspace--count))) 111 | 112 | (defun exwm/workspace-next () 113 | "Switch to next exwm-workspace (to the right)." 114 | (interactive) 115 | (exwm-workspace-switch (exwm//ws-offset 1))) 116 | 117 | (defun exwm/workspace-prev () 118 | "Switch to next exwm-workspace (to the left)." 119 | (interactive) 120 | (exwm-workspace-switch (exwm//ws-offset -1))) 121 | 122 | ;; Buffer move between frames semantics: 123 | ;; - In source window, display last buffer that was replaced in this frame using `other-window' 124 | ;; - In target frame, show the buffer in the active window 125 | (defun exwm/workspace-move-buffer-to-workspace (ws-id) 126 | "Move the current buffer to the exwm-workspace with id WS-ID" 127 | (let ((target-frame (exwm-workspace--workspace-from-frame-or-index ws-id)) 128 | (src-exwm-id exwm--id)) 129 | (if src-exwm-id 130 | (progn 131 | (exwm-workspace-move-window target-frame src-exwm-id) 132 | (exwm-workspace-switch target-frame)) 133 | (let ((src-buffer (current-buffer))) 134 | (switch-to-buffer (other-buffer src-buffer)) 135 | (pop-to-buffer src-buffer `((display-buffer-in-previous-window 136 | display-buffer-use-some-window 137 | display-buffer-pop-up-window 138 | ;; display-buffer-use-some-frame 139 | ) 140 | (inhibit-same-window . t) 141 | (reusable-frames . ,target-frame) 142 | ;; (frame-predicate . (lambda(f) (eql f ,target-frame))) 143 | )))))) 144 | 145 | (defun exwm/workspace-move-buffer-next () 146 | "Display active buffer in next frame (to the right)." 147 | (interactive) 148 | (exwm/workspace-move-buffer-to-workspace (exwm//ws-offset 1))) 149 | 150 | (defun exwm/workspace-move-buffer-prev () 151 | "Display active buffer in next frame (to the right)." 152 | (interactive) 153 | (exwm/workspace-move-buffer-to-workspace (exwm//ws-offset -1))) 154 | 155 | (defun exwm/layout-toggle-fullscreen () 156 | "Togggles full screen for Emacs and X windows" 157 | (interactive) 158 | (if exwm--id 159 | (if (exwm-layout--fullscreen-p) 160 | (exwm-reset) 161 | (exwm-layout-set-fullscreen)) 162 | (spacemacs/toggle-maximize-buffer))) 163 | 164 | (defun exwm/run-program-in-home (command) 165 | (let ((default-directory user-home-directory)) 166 | (start-process-shell-command command nil command))) 167 | 168 | (defun exwm/app-launcher (command) 169 | "Launches an application in your PATH. 170 | Can show completions at point for COMMAND using helm or ivy" 171 | (interactive (list (read-shell-command exwm-app-launcher--prompt))) 172 | (exwm/run-program-in-home command)) 173 | 174 | (defun exwm/launch-split-below (command) 175 | (interactive (list (read-shell-command exwm-app-launcher--prompt))) 176 | (split-window-below-and-focus) 177 | (exwm/run-program-in-home command)) 178 | 179 | (defun exwm/launch-split-right (command) 180 | (interactive (list (read-shell-command exwm-app-launcher--prompt))) 181 | (split-window-right-and-focus) 182 | (exwm/run-program-in-home command)) 183 | 184 | (defun exwm/jump-to-last-exwm () 185 | (interactive) 186 | (exwm-workspace-switch exwm-toggle-workspace)) 187 | 188 | (defun exwm/exwm-buffers-info () 189 | (interactive) 190 | "Helper, return information about open exwm windows" 191 | (cl-loop for buffer in (buffer-list) 192 | for name = (buffer-name buffer) 193 | for ecname = (buffer-local-value 'exwm-class-name buffer) 194 | when ecname 195 | do (message "Buffer name: '%s', exwm class name: '%s'" name ecname))) 196 | 197 | (defun exwm//convert-key-to-event (key) 198 | "Converts something from (kbd ...) format to something suitable for 199 | exwm-input-prefix-keys" 200 | (let ((key (kbd key))) 201 | (if (and (sequencep key) 202 | (= (length key) 1)) 203 | (etypecase key 204 | (string (string-to-char key)) 205 | (vector (elt key 0))) 206 | (error "cannot convert to key event: %s" key)))) 207 | 208 | 209 | (let ((debug-modes-active nil)) 210 | (defun exwm/toggle-debug-mode () 211 | "Toggle exwm and xcb debug modes" 212 | (interactive) 213 | (setf debug-modes-active (not debug-modes-active)) 214 | (message (if debug-modes-active 215 | "Enabling xcb and exwm debug modes." 216 | "Disabling xcb and exqm debug modes.")) 217 | (let ((flag (if debug-modes-active 1 0))) 218 | (exwm-debug flag) 219 | (xcb:debug flag)))) 220 | 221 | (defvar exwm//autostart-process-list nil 222 | "List of processes run during autostart.") 223 | 224 | (defun exwm/autostart-process (name command &optional directory) 225 | "Can be used during initialization to run COMMAND as a process 226 | with NAME and add it to the list of autostarted processes. 227 | 228 | DIRECTORY can be set to a string which will be used as working directory for the 229 | process. If not supplied, will be set to `user-home-directory'. 230 | 231 | Prepends the value of `exwm-autostart-environmwnt' to 232 | `process-environment' for the started process. 233 | " 234 | (push (let ((default-directory (or directory user-home-directory)) 235 | (process-environment (append exwm-autostart-environment process-environment))) 236 | (start-process-shell-command name nil command)) 237 | exwm//autostart-process-list)) 238 | 239 | (defun exwm//start-desktop-application (basename xdg) 240 | "Autostart an application from a XDG desktop entry specification." 241 | (let* ((type (gethash "Type" xdg)) 242 | ;; (name (gethash "Name" xdg)) 243 | (cmd (gethash "Exec" xdg)) 244 | (hidden (gethash "Hidden" xdg)) 245 | (include (gethash "OnlyShowIn" xdg)) 246 | (exclude (gethash "NotShowIn" xdg)) 247 | (try-exec (gethash "TryExec" xdg)) 248 | (exec-directory (gethash "Path" xdg)) 249 | ;; (dbus-p (gethash "DBusActivatable" xdg)) ; TODO: support 250 | (included-p (cond (include (member "EXWM" (split-string include ";" t))) 251 | (exclude (not (member "EXWM" (split-string exclude ";" t)))) 252 | (t))) 253 | (should-exec-p (and 254 | (string= type "Application") 255 | included-p 256 | (if try-exec 257 | (executable-find try-exec) 258 | t)))) 259 | (when should-exec-p (exwm/autostart-process basename cmd exec-directory)))) 260 | 261 | (defun exwm//read-xdg-autostart-files () 262 | "Return a hashtable for autostart applications as defined by the freedesktop specification." 263 | (cl-loop with xdg-specs = (make-hash-table :test 'equal) 264 | for dir in (append (xdg-config-dirs) (list (xdg-config-home))) 265 | for autostart-dir = (expand-file-name "autostart/" dir) 266 | for autostart-files = (when (file-exists-p autostart-dir) 267 | (directory-files autostart-dir t (rx (1+ word) ".desktop"))) 268 | do 269 | (cl-loop for file in autostart-files do 270 | (setf (gethash (file-name-base file) xdg-specs) (xdg-desktop-read-file file))) 271 | finally (return xdg-specs))) 272 | 273 | (defun exwm//autostart-xdg-applications () 274 | "Run the autostart applications as defined by the freedesktop autostart specification." 275 | (unless exwm//autostart-process-list 276 | (cl-loop for basename being the hash-keys of 277 | (exwm//read-xdg-autostart-files) 278 | using (hash-values xdg) do 279 | (exwm//start-desktop-application basename xdg)))) 280 | 281 | (defun exwm//kill-autostart-processes () 282 | (cl-loop for p in exwm//autostart-process-list do 283 | (if (process-live-p p) (kill-process p))) 284 | (setq exwm//autostart-process-list nil)) 285 | 286 | (let ((sm-keyvec (elt (edmacro-parse-keys dotspacemacs-leader-key t) 0)) 287 | (our-keyvec (elt (edmacro-parse-keys "s-SPC" t) 0))) 288 | (defun exwm//which-key-transform-filter (oldargs) 289 | (cl-destructuring-bind (key-seq &rest rest) oldargs 290 | (list* (cl-substitute sm-keyvec our-keyvec key-seq) rest)))) 291 | 292 | ;; D-Bus locking 293 | ;; We should be able to talk to loginctl to handle the current session, so we 294 | ;; can react to the lock signal. 295 | 296 | (defun exwm//install-logind-lock-handler () 297 | (let ((session (dbus-call-method :system "org.freedesktop.login1" "/org/freedesktop/login1" 298 | "org.freedesktop.login1.Manager" "GetSessionByPID" (emacs-pid)))) 299 | (dbus-register-signal :system "org.freedesktop.login1" session 300 | "org.freedesktop.login1.Session" "Lock" 301 | (lambda() 302 | (message "Lock signal received") 303 | (start-process-shell-command "session-lock" nil exwm-locking-command))))) 304 | 305 | (defun exwm//geom-< (r1 r2) 306 | "Compare two xcb-rectangles for ordering by their offsets, in 307 | row-major order." 308 | (let ((x1 (slot-value r1 'x)) 309 | (y1 (slot-value r1 'y)) 310 | (x2 (slot-value r2 'x)) 311 | (y2 (slot-value r2 'y))) 312 | (if (= y1 y2) 313 | (< x1 x2) 314 | (< y1 y2)))) 315 | 316 | ;; EXPERIMENTAL: depends on exwm-randr internals, also assumes randr-1.5 support 317 | (defun exwm//randr-dwim () 318 | "Map one workspace per monitor, sorted 319 | left-to-right/top-to-bottom based on the reported virtual screen 320 | offsets by xrandr." 321 | (let ((geom-alist (second (exwm-randr--get-monitors)))) 322 | (cl-loop for entry in (cl-sort geom-alist 'exwm//geom-< :key 'cdr) 323 | for i from 0 324 | for name = (car entry) 325 | do (setq exwm-randr-workspace-monitor-plist 326 | (plist-put exwm-randr-workspace-monitor-plist i name))))) 327 | 328 | (defvar exwm--autorandr-hist nil) 329 | 330 | (defun exwm//autorandr-executable () 331 | "Return path to autorandr executable or nil." 332 | (executable-find "autorandr")) 333 | 334 | (defun exwm/load-autorandr-profile () 335 | "Select autorandr configuration." 336 | (interactive) 337 | (let* ((candidates (append (split-string (shell-command-to-string "autorandr --detected") "\n" t) 338 | '("common" "clone-largest" "horizontal" "vertical"))) 339 | (profile (completing-read "Select autorandr profile: " 340 | candidates nil t nil 'exwm--autorandr-hist (first candidates)))) 341 | (start-process-shell-command "autorandr" nil (concat "autorandr -l " profile)))) 342 | 343 | (defun exwm//fm-frame-bbox-from-randr (frame) 344 | "Replacement for `fm-frame-bbox' which uses exwm's idea of frame geometry" 345 | (with-slots (x y width height) (frame-parameter frame 'exwm-geometry) 346 | (list x y (+ x width) (+ y height)))) 347 | -------------------------------------------------------------------------------- /img/spacemacsOS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timor/spacemacsOS/124564ffcb2823932074daebf9589ccf30688594/img/spacemacsOS.jpg -------------------------------------------------------------------------------- /packages.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t; -*- 2 | 3 | ;;; packages.el --- exwm Layer packages File for Spacemacs 4 | ;; 5 | ;; Copyright (c) 2012-2014 Sylvain Benner 6 | ;; Copyright (c) 2014-2015 Sylvain Benner & Contributors 7 | ;; 8 | ;; Author: Sylvain Benner 9 | ;; URL: https://github.com/syl20bnr/spacemacs 10 | ;; 11 | ;; This file is not part of GNU Emacs. 12 | ;; 13 | ;;; License: GPLv3 14 | 15 | ;; List of all packages to install and/or initialize. Built-in packages 16 | ;; which require an initialization must be listed explicitly in the list. 17 | (setq exwm-packages 18 | '( 19 | ;; (xelb :location (recipe :fetcher github 20 | ;; :repo "ch11ng/xelb") 21 | ;; :step pre) 22 | ;; (exwm :location (recipe :fetcher github 23 | ;; :repo "ch11ng/exwm") 24 | ;; :step pre) 25 | (xelb :location elpa) 26 | (exwm :location elpa) 27 | 28 | ;; desktop-environment 29 | ;; TODO: remove :commit binding once upstream has new release 30 | (desktop-environment :location (recipe :fetcher github :repo "DamienCassou/desktop-environment" :upgrade t :commit "cd5145288944f4bbd7b2459e4b55a6a95e37f06d")) 31 | (framemove :location (recipe :fetcher github :repo "emacsmirror/framemove")) 32 | )) 33 | 34 | (defun exwm/init-framemove () 35 | (use-package framemove 36 | :after exwm 37 | :config 38 | (progn 39 | ;; Emacs frame parameters don't seem to be too reliable... 40 | (define-advice fm-frame-bbox (:around (oldfun frame) exwm-frame-bbox-from-randr) 41 | (if (frame-parameter frame 'exwm-geometry) 42 | (exwm//fm-frame-bbox-from-randr frame) 43 | (funcall oldfun frame))) 44 | (setq framemove-hook-into-windmove exwm-move-frame-at-edge)))) 45 | 46 | (defun exwm/init-desktop-environment () 47 | (use-package desktop-environment 48 | :after exwm 49 | :diminish desktop-environment-mode 50 | :defer t 51 | :init 52 | (progn 53 | (spacemacs|add-toggle desktop-environment 54 | :mode desktop-environment-mode 55 | :documentation "Keybindings for Desktop Environment functionality." 56 | :evil-leader "TD") 57 | ) 58 | :config 59 | (progn 60 | ;; We bypass desktop-environment's locking functionality for 2 reasons: 61 | ;; 1. s-l is most likely needed for window manipulation 62 | ;; 2. desktop-environment's locking mechanism does not support registering as session manager 63 | ;; TODO: To be completely consistent, we should put our own locking stuff also under this toggle 64 | ;; The following line would instead assign their locking command to the default binding: 65 | ;; (define-key desktop-environment-mode-map (kbd "") (lookup-key desktop-environment-mode-map (kbd "s-l"))) 66 | (setq desktop-environment-update-exwm-global-keys :prefix) 67 | (define-key desktop-environment-mode-map (kbd "s-l") nil) 68 | ;; If we don't enable this, exwm/switch-to-buffer-or-run won't move an X window to the current frame 69 | (setq exwm-layout-show-all-buffers t)))) 70 | 71 | (defun exwm/init-xelb () 72 | (use-package xelb)) 73 | 74 | (defun exwm//install-frame-keybindings () 75 | "This installes the bindings that override the original ~SPC F~ frame 76 | commands. This is a separate function becuse it is called only after exwm 77 | initialization, when the window manager has successfully be started." 78 | ;; EXWM is quite particular about handling frames, so we override the default 79 | ;; ~SPC F~ Frame leader mappings with EXWM workspace-specific stuff. 80 | (define-key spacemacs-default-map (kbd "F") nil) 81 | (define-key spacemacs-cmds (kbd "F") nil) 82 | 83 | ;; Keybindings for ~s-SPC F~ Frame handling Menu 84 | (spacemacs/declare-prefix "F" "EXWM") 85 | (spacemacs/declare-prefix "FM" "minibuffer") 86 | (spacemacs/set-leader-keys 87 | "Fr" 'exwm-reset 88 | "Fh" 'exwm-floating-hide 89 | "Fw" 'exwm-workspace-switch 90 | "Fa" 'exwm-workspace-add 91 | "Fd" 'exwm-workspace-delete 92 | "Fm" 'exwm-workspace-move 93 | "Fs" 'exwm-workspace-swap 94 | "FMd" 'exwm-workspace-detach-minibuffer 95 | "FMa" 'exwm-workspace-attach-minibuffer 96 | )) 97 | 98 | (defun exwm/init-exwm () 99 | (use-package exwm 100 | :init 101 | ;; Disable dialog boxes since they are unusable in EXWM 102 | (setq use-dialog-box nil) 103 | ;; You may want Emacs to show you the time 104 | (display-time-mode t) 105 | (when exwm-hide-tiling-modeline 106 | (add-hook 'exwm-mode-hook #'hidden-mode-line-mode)) 107 | (setq exwm-input-line-mode-passthrough t) 108 | 109 | 110 | ;; introduce leader for running programs 111 | (spacemacs/declare-prefix "&" "exwm-run") 112 | (spacemacs/set-leader-keys "&s" 'exwm/launch-split-below) 113 | (spacemacs/set-leader-keys "&v" 'exwm/launch-split-right) 114 | 115 | ;; Keybindings for ~SPC m~ 116 | (spacemacs/set-leader-keys-for-major-mode 'exwm-mode 117 | "f" 'exwm-floating-toggle-floating 118 | "m" 'exwm-workspace-move-window 119 | "F" 'exwm-layout-toggle-fullscreen 120 | ) 121 | 122 | ;; make winner aware of new window configuration 123 | (with-eval-after-load 'winner 124 | (add-hook 'exwm-manage-finish-hook 'winner-save-old-configurations t)) 125 | :config 126 | 127 | ;; make sure that displaying transient states gets the keyboard input. 128 | ;; Borrowed from: https://github.com/abo-abo/hydra/issues/232 129 | (define-advice hydra-set-transient-map (:around (fun keymap on-exit &optional foreign-keys) exwm-passthrough) 130 | (setq exwm-input-line-mode-passthrough t) 131 | (let ((on-exit (let ((on-exit on-exit)) 132 | (lambda () 133 | (setq exwm-input-line-mode-passthrough nil) 134 | (when on-exit (funcall on-exit)))))) 135 | (funcall fun keymap on-exit foreign-keys))) 136 | 137 | ;; override persp-mode's idea of frame creation for floating frames. These 138 | ;; are characterized by the 'unsplittable' frame parameter, and should not 139 | ;; be tried to assign an existing layout to. 140 | 141 | (eval-after-load 'persp-mode 142 | (advice-add 'persp-init-new-frame :before-until 'exwm//persp-mode-inhibit-p)) 143 | 144 | (eval-after-load 'terminal-here 145 | (exwm-input-set-key (kbd "") 'terminal-here-launch)) 146 | 147 | (add-hook 'exwm-update-class-hook 'exwm/rename-buffer) 148 | (add-hook 'exwm-update-title-hook 'exwm/rename-buffer) 149 | 150 | ;; kick all exwm buffers into insert mode per default 151 | (add-hook 'exwm-manage-finish-hook 'exwm/enter-insert-state) 152 | 153 | ;; Quick swtiching between workspaces 154 | (defvar exwm-toggle-workspace 0 155 | "Previously selected workspace. Used with `exwm/jump-to-last-exwm'.") 156 | 157 | (defadvice exwm-workspace-switch (before save-toggle-workspace activate) 158 | (setq exwm-toggle-workspace exwm-workspace-current-index)) 159 | 160 | ;; `exwm-input-set-key' sets global key bindings, independent of char mode, line mode, and line mode passthru 161 | 162 | ;; + We always need a way to get to normal state if we are in insert state. 163 | (exwm-input-set-key (kbd "s-") 'exwm/escape) 164 | 165 | (exwm-input-set-key (kbd "s-c") 'exwm/enter-char-mode) 166 | 167 | (exwm-input-set-key (kbd "") #'exwm/jump-to-last-exwm) 168 | ;; + Set shortcuts to switch to a certain workspace. 169 | (exwm-input-set-key (kbd "s-1") 170 | (lambda () (interactive) (exwm-workspace-switch 0))) 171 | (exwm-input-set-key (kbd "s-2") 172 | (lambda () (interactive) (exwm-workspace-switch 1))) 173 | (exwm-input-set-key (kbd "s-3") 174 | (lambda () (interactive) (exwm-workspace-switch 2))) 175 | (exwm-input-set-key (kbd "s-4") 176 | (lambda () (interactive) (exwm-workspace-switch 3))) 177 | (exwm-input-set-key (kbd "s-5") 178 | (lambda () (interactive) (exwm-workspace-switch 4))) 179 | (exwm-input-set-key (kbd "s-6") 180 | (lambda () (interactive) (exwm-workspace-switch 5))) 181 | (exwm-input-set-key (kbd "s-7") 182 | (lambda () (interactive) (exwm-workspace-switch 6))) 183 | (exwm-input-set-key (kbd "s-8") 184 | (lambda () (interactive) (exwm-workspace-switch 7))) 185 | (exwm-input-set-key (kbd "s-9") 186 | (lambda () (interactive) (exwm-workspace-switch 8))) 187 | (exwm-input-set-key (kbd "s-0") 188 | (lambda () (interactive) (exwm-workspace-switch 9))) 189 | ;; + Application launcher ('M-&' also works if the output buffer does not 190 | ;; bother you). Note that there is no need for processes to be created by 191 | ;; Emacs. 192 | (exwm-input-set-key (kbd "s-r") #'exwm/app-launcher) 193 | ;; + 'slock' is a simple X display locker provided by suckless tools. 'i3lock' 194 | ;; is a more feature-rich alternative. 195 | (exwm-input-set-key (kbd "") 196 | (lambda () (interactive) (start-process-shell-command "lock" nil exwm-locking-command))) 197 | 198 | ;; in normal state/line mode, use the familiar i key to switch to input state 199 | ;; (evil-define-key 'normal exwm-mode-map (kbd "i") 'exwm-input-release-keyboard) 200 | (evil-define-key 'normal exwm-mode-map (kbd "i") 'exwm/enter-insert-state) 201 | (dolist (k '("" "" "")) 202 | (evil-define-key 'normal exwm-mode-map (kbd k) 'exwm/enter-insert-state)) 203 | 204 | ;; Define super-space as default leader key. 205 | (exwm-input-set-key (kbd "s-SPC") spacemacs-default-map) 206 | ;; Don't have to lift finger from s-key for M-x behavior: 207 | (if (configuration-layer/layer-used-p 'helm) 208 | (spacemacs/set-leader-keys "s-SPC" 'helm-M-x) 209 | (spacemacs/set-leader-keys "s-SPC" 'execute-extended-command)) 210 | 211 | ;; EXWM does not bypass exwm-mode-map keybindings in line-mode, so the 212 | ;; default bindings are still mapped to C-c. We remap that to C-s-c. 213 | 214 | (define-key exwm-mode-map (kbd "C-s-c") (lookup-key exwm-mode-map (kbd "C-c"))) 215 | (define-key exwm-mode-map (kbd "C-c") nil) 216 | 217 | ;; User s-q to close buffers 218 | (exwm-input-set-key (kbd "s-q") 'spacemacs/kill-this-buffer) 219 | 220 | ;; Don't override any keybindings in line-mode 221 | (setq exwm-input-prefix-keys '()) 222 | 223 | ;; Undo window configurations 224 | (exwm-input-set-key (kbd "s-u") #'winner-undo) 225 | (exwm-input-set-key (kbd "s-U") #'winner-redo) 226 | ;; Change buffers 227 | (exwm-input-set-key (kbd "s-b") #'switch-to-buffer) 228 | ;; Focusing windows 229 | (exwm-input-set-key (kbd "s-h") #'evil-window-left) 230 | (exwm-input-set-key (kbd "s-j") #'evil-window-down) 231 | (exwm-input-set-key (kbd "s-k") #'evil-window-up) 232 | (exwm-input-set-key (kbd "s-l") #'evil-window-right) 233 | ;; Moving Windows 234 | (exwm-input-set-key (kbd "s-H") #'evil-window-move-far-left) 235 | (exwm-input-set-key (kbd "s-J") #'evil-window-move-very-bottom) 236 | (exwm-input-set-key (kbd "s-K") #'evil-window-move-very-top) 237 | (exwm-input-set-key (kbd "s-L") #'evil-window-move-far-right) 238 | ;; Resize 239 | (exwm-input-set-key (kbd "M-s-h") #'spacemacs/shrink-window-horizontally) 240 | (exwm-input-set-key (kbd "M-s-j") #'spacemacs/enlarge-window) 241 | (exwm-input-set-key (kbd "M-s-k") #'spacemacs/shrink-window) 242 | (exwm-input-set-key (kbd "M-s-l") #'spacemacs/enlarge-window-horizontally) 243 | (exwm-input-set-key (kbd "s-m") #'spacemacs/toggle-maximize-buffer) 244 | ;; Workspaces 245 | (exwm-input-set-key (kbd "s-]") #'exwm/workspace-next) 246 | (exwm-input-set-key (kbd "s-[") #'exwm/workspace-prev) 247 | (exwm-input-set-key (kbd "s-}") #'exwm/workspace-move-buffer-next) 248 | (exwm-input-set-key (kbd "s-{") #'exwm/workspace-move-buffer-prev) 249 | ;; Autorandr 250 | (when (exwm//autorandr-executable) 251 | (exwm-input-set-key (kbd "s-s") #'exwm/load-autorandr-profile)) 252 | ;; Debugging 253 | (exwm-input-set-key (kbd "s-d") #'exwm/toggle-debug-mode) 254 | 255 | (require 'exwm-randr) 256 | (setq exwm-randr-workspace-monitor-plist '(0 "VGA1")) 257 | (when exwm-randr-dwim 258 | (add-hook 'exwm-randr-screen-change-hook 'exwm//randr-dwim)) 259 | (exwm-randr-enable) 260 | (when exwm-enable-systray 261 | (require 'exwm-systemtray) 262 | (exwm-systemtray-enable)) 263 | (when (and exwm-install-logind-lock-handler 264 | exwm-locking-command) 265 | (add-hook 'exwm-init-hook 'exwm//install-logind-lock-handler)) 266 | (when exwm-autostart-xdg-applications 267 | (add-hook 'exwm-init-hook 'exwm//autostart-xdg-applications t)) 268 | (add-hook 'exwm-init-hook 'exwm//install-frame-keybindings t) 269 | (when exwm-custom-init 270 | (add-hook 'exwm-init-hook exwm-custom-init t)) 271 | )) 272 | --------------------------------------------------------------------------------