init-exwm.el 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. ;;; EXWM
  2. ;;; When stating the client from .xinitrc, `save-buffer-kill-terminal' will
  3. ;;; force-kill Emacs before it can run through `kill-emacs-hook'.
  4. (global-set-key (kbd "C-x C-c") 'save-buffers-kill-emacs)
  5. ;;; Athena+Xaw3d confuses xcape when binding Caps-lock to both L_Ctrl
  6. ;;; escape, in which case it will procude <C-escape> in Emacs. In practice, it
  7. ;;; means that `C-` keys will works but `<escape>` will need a fast double tap
  8. ;;; on Caps Lock.
  9. ;;;
  10. ;;; See https://github.com/ch11ng/exwm/issues/285
  11. ;;; and https://gitlab.com/interception/linux/plugins/caps2esc/issues/2.
  12. ;;; Rename buffer to window title.
  13. (defun ambrevar/exwm-rename-buffer-to-title () (exwm-workspace-rename-buffer exwm-title))
  14. (add-hook 'exwm-update-title-hook 'ambrevar/exwm-rename-buffer-to-title)
  15. (add-hook 'exwm-floating-setup-hook 'exwm-layout-hide-mode-line)
  16. (add-hook 'exwm-floating-exit-hook 'exwm-layout-show-mode-line)
  17. ;;; Allow non-floating resizing with mouse.
  18. (setq window-divider-default-bottom-width 2
  19. window-divider-default-right-width 2)
  20. (window-divider-mode)
  21. ;;; System tray
  22. (require 'exwm-systemtray)
  23. (exwm-systemtray-enable)
  24. (setq exwm-systemtray-height 16)
  25. ;;; Those cannot be set globally: if Emacs would be run in another WM, the "s-"
  26. ;;; prefix will conflict with the WM bindings.
  27. (exwm-input-set-key (kbd "s-R") #'exwm-reset)
  28. (exwm-input-set-key (kbd "s-x") #'exwm-input-toggle-keyboard)
  29. (exwm-input-set-key (kbd "s-h") #'windmove-left)
  30. (exwm-input-set-key (kbd "s-j") #'windmove-down)
  31. (exwm-input-set-key (kbd "s-k") #'windmove-up)
  32. (exwm-input-set-key (kbd "s-l") #'windmove-right)
  33. (exwm-input-set-key (kbd "s-D") #'kill-this-buffer)
  34. (exwm-input-set-key (kbd "s-b") #'list-buffers)
  35. (exwm-input-set-key (kbd "s-f") #'find-file)
  36. (when (require 'windower nil 'noerror)
  37. (exwm-input-set-key (kbd "s-<tab>") 'windower-switch-to-last-buffer)
  38. (exwm-input-set-key (kbd "s-o") 'windower-toggle-single)
  39. (exwm-input-set-key (kbd "s-\\") 'windower-toggle-split)
  40. (exwm-input-set-key (kbd "s-H") 'windower-swap-left)
  41. (exwm-input-set-key (kbd "s-J") 'windower-swap-below)
  42. (exwm-input-set-key (kbd "s-K") 'windower-swap-above)
  43. (exwm-input-set-key (kbd "s-L") 'windower-swap-right))
  44. ;; The following can only apply to EXWM buffers, else it could have unexpected effects.
  45. (push ?\s- exwm-input-prefix-keys)
  46. (define-key exwm-mode-map (kbd "s-SPC") #'exwm-floating-toggle-floating)
  47. (exwm-input-set-key (kbd "s-i") #'follow-delete-other-windows-and-split)
  48. (exwm-input-set-key (kbd "s-O") #'exwm-layout-toggle-fullscreen)
  49. (with-eval-after-load 'helm
  50. ;; Need `with-eval-after-load' here since 'helm-map is not defined in 'helm-config.
  51. (ambrevar/define-keys helm-map
  52. "s-\\" 'helm-toggle-resplit-and-swap-windows)
  53. (exwm-input-set-key (kbd "s-c") #'helm-resume)
  54. (exwm-input-set-key (kbd "s-b") #'helm-mini)
  55. (exwm-input-set-key (kbd "s-f") #'helm-find-files)
  56. (exwm-input-set-key (kbd "s-F") #'helm-locate)
  57. (when (fboundp 'ambrevar/helm-locate-meta)
  58. (exwm-input-set-key (kbd "s-F") #'ambrevar/helm-locate-meta))
  59. (exwm-input-set-key (kbd "s-g") 'ambrevar/helm-grep-git-or-ag)
  60. (exwm-input-set-key (kbd "s-G") 'ambrevar/helm-grep-git-all-or-ag)
  61. ;; Launcher
  62. (exwm-input-set-key (kbd "s-r") 'helm-run-external-command))
  63. (when (require 'evil nil t)
  64. (exwm-input-set-key (kbd "s-<tab>") #'evil-switch-to-windows-last-buffer)
  65. (exwm-input-set-key (kbd "C-6") #'evil-switch-to-windows-last-buffer))
  66. ;;; Emacs mode shortcuts.
  67. (if (not (require 'helm-selector nil :noerror))
  68. (progn
  69. (exwm-input-set-key (kbd "s-t") (lambda ()
  70. (interactive)
  71. (find-file (car org-agenda-files))))
  72. (exwm-input-set-key (kbd "s-<return>") #'eshell)
  73. (exwm-input-set-key (kbd "s-m") #'notmuch-hello)
  74. (exwm-input-set-key (kbd "s-n") #'elfeed)
  75. (exwm-input-set-key (kbd "s-e") #'eww))
  76. (exwm-input-set-key (kbd "s-t") 'helm-selector-org)
  77. (exwm-input-set-key (kbd "s-T") 'helm-selector-org-other-window)
  78. (exwm-input-set-key (kbd "s-<return>") 'helm-selector-shell)
  79. (exwm-input-set-key (kbd "S-s-<return>") 'helm-selector-shell-other-window)
  80. (exwm-input-set-key (kbd "s-m") #'helm-selector-notmuch)
  81. (exwm-input-set-key (kbd "s-M") #'helm-selector-notmuch-other-window)
  82. (exwm-input-set-key (kbd "s-n") #'helm-selector-elfeed)
  83. (exwm-input-set-key (kbd "s-N") #'helm-selector-elfeed-other-window) ; "n" for "news"
  84. (exwm-input-set-key (kbd "s-e") #'helm-selector-eww)
  85. (exwm-input-set-key (kbd "s-E") #'helm-selector-eww-other-window))
  86. (when (fboundp 'magit-status)
  87. (exwm-input-set-key (kbd "s-v") #'magit-status))
  88. (when (fboundp 'emms-all)
  89. (exwm-input-set-key (kbd "s-a") #'emms-smart-browse)
  90. (exwm-input-set-key (kbd "S-s-<kp-enter>") #'emms-pause)
  91. (if (fboundp 'helm-emms)
  92. (exwm-input-set-key (kbd "s-A") #'helm-emms)
  93. (exwm-input-set-key (kbd "s-A") #'emms)))
  94. (when (fboundp 'helm-pass)
  95. (exwm-input-set-key (kbd "s-p") #'helm-pass))
  96. (autoload 'ambrevar/slime-to-repl "lisp")
  97. (exwm-input-set-key (kbd "s-<backspace>") #'helm-selector-sly)
  98. (defun ambrevar/repl-switcher ()
  99. "Switch between Geiser and SLIME REPLs."
  100. (interactive)
  101. ;; TODO: Apparently, S-s-<backspace> is not recognized.
  102. (pcase
  103. (completing-read "Lisp: " '(cider geiser slime sly racket))
  104. ("cider"
  105. (exwm-input-set-key (kbd "s-<backspace>") 'helm-selector-cider)
  106. (exwm-input-set-key (kbd "M-s-<backspace>") 'helm-selector-cider-other-window))
  107. ("geiser"
  108. (autoload 'helm-geiser-repl-switch "init-scheme")
  109. (exwm-input-set-key (kbd "s-<backspace>") 'helm-selector-geiser)
  110. (exwm-input-set-key (kbd "M-s-<backspace>") 'helm-selector-geiser-other-window))
  111. ("slime"
  112. (exwm-input-set-key (kbd "s-<backspace>") 'helm-selector-slime)
  113. (exwm-input-set-key (kbd "M-s-<backspace>") 'helm-selector-slime-other-window))
  114. ("sly"
  115. (exwm-input-set-key (kbd "s-<backspace>") 'helm-selector-sly)
  116. (exwm-input-set-key (kbd "<M-s-backspace>") 'helm-selector-sly-other-window))
  117. ("racket"
  118. (exwm-input-set-key (kbd "s-<backspace>") #'racket-repl))))
  119. (exwm-input-set-key (kbd "s-C-<backspace>") #'ambrevar/repl-switcher)
  120. ;;; External application shortcuts.
  121. (defun ambrevar/exwm-start (command)
  122. (interactive (list (read-shell-command "$ ")))
  123. (start-process-shell-command command nil command))
  124. (exwm-input-set-key (kbd "s-&") #'ambrevar/exwm-start)
  125. (when (require 'helm-exwm nil t)
  126. (add-to-list 'helm-source-names-using-follow "EXWM buffers")
  127. (setq helm-exwm-emacs-buffers-source (helm-exwm-build-emacs-buffers-source))
  128. (setq helm-exwm-source (helm-exwm-build-source))
  129. (setq helm-mini-default-sources `(helm-exwm-emacs-buffers-source
  130. helm-exwm-source
  131. helm-source-recentf
  132. ,(when (boundp 'helm-source-ls-git) 'helm-source-ls-git)
  133. helm-source-bookmarks
  134. helm-source-bookmark-set
  135. helm-source-buffer-not-found))
  136. (ambrevar/define-keys
  137. helm-exwm-map
  138. "M-d" 'helm-buffer-run-kill-persistent
  139. "S-<return>" 'helm-buffer-switch-other-window)
  140. ;; Web browser
  141. (exwm-input-set-key (kbd "s-w") #'helm-exwm-switch-browser)
  142. (exwm-input-set-key (kbd "s-W") #'helm-exwm-switch-browser-other-window))
  143. (when (require 'desktop-environment nil 'noerror)
  144. ;; REVIEW: Remove the override on next version release:
  145. ;; https://gitlab.petton.fr/DamienCassou/desktop-environment
  146. (defun ambrevar/desktop-environment-lock-screen ()
  147. "Lock the screen, preventing anyone without a password from using the system."
  148. (interactive)
  149. ;; Run command asynchronously so that Emacs does not wait in the background.
  150. (start-process-shell-command "lock" nil desktop-environment-screenlock-command))
  151. (advice-add #'desktop-environment-lock-screen :override #'ambrevar/desktop-environment-lock-screen)
  152. (setq desktop-environment-screenshot-directory "~/Downloads")
  153. (define-key desktop-environment-mode-map (kbd "s-z") #'desktop-environment-lock-screen)
  154. ;; Re-set s-l to navigate windows.
  155. (define-key desktop-environment-mode-map (kbd "s-l") #'windmove-right)
  156. (desktop-environment-mode))
  157. (defun ambrevar/suspend-to-sleep ()
  158. (interactive)
  159. (require 'recentf)
  160. (recentf-save-list)
  161. (call-process "loginctl" nil nil nil "suspend"))
  162. (exwm-input-set-key (kbd "s-Z") #'ambrevar/suspend-to-sleep)
  163. ;;; Volume control
  164. (when (require 'pulseaudio-control nil t)
  165. (exwm-input-set-key (kbd "s-<kp-subtract>") #'pulseaudio-control-decrease-volume)
  166. (exwm-input-set-key (kbd "s-<kp-add>") #'pulseaudio-control-increase-volume)
  167. (exwm-input-set-key (kbd "s-<kp-enter>") #'pulseaudio-control-toggle-current-sink-mute)
  168. (exwm-input-set-key (kbd "s--") #'pulseaudio-control-decrease-volume)
  169. (exwm-input-set-key (kbd "s-=") #'pulseaudio-control-increase-volume)
  170. (exwm-input-set-key (kbd "s-0") #'pulseaudio-control-toggle-current-sink-mute))
  171. ;;; Check for start-up errors. See ~/.profile.
  172. (let ((error-logs (directory-files "~" t "errors.*log$")))
  173. (when error-logs
  174. (warn "Error during system startup. See %s." (mapconcat 'identity error-logs ", "))
  175. (when (daemonp)
  176. ;; Non-daemon Emacs already brings up the *Warning* buffer.
  177. (setq initial-buffer-choice
  178. (lambda () (get-buffer "*Warnings*"))))))
  179. ;;; Some programs such as 'emacs' are better off being started in char-mode.
  180. (defun ambrevar/exwm-start-in-char-mode ()
  181. (when (string-prefix-p "emacs" exwm-instance-name)
  182. (exwm-input-release-keyboard (exwm--buffer->id (window-buffer)))))
  183. (add-hook 'exwm-manage-finish-hook 'ambrevar/exwm-start-in-char-mode)
  184. ;;; Some programs escape EXWM control and need be tamed. See
  185. ;; https://github.com/ch11ng/exwm/issues/287
  186. (add-to-list 'exwm-manage-configurations '((string= exwm-title "Kingdom Come: Deliverance") managed t))
  187. ;; Gens
  188. (add-to-list 'exwm-manage-configurations '((string= exwm-class-name "Gens") floating nil))
  189. (defvar ambrevar/exwm-change-screen-turn-off-primary nil
  190. "Turn off primary display when cable is plugged.")
  191. ;; Function to automatically toggle between internal/external screens.
  192. (defun ambrevar/exwm-change-screen-hook ()
  193. (let* ((xrandr-output-regexp "\n\\([^ ]+\\) connected ")
  194. (xrandr-monitor-regexp "\n .* \\([^ \n]+\\)")
  195. (find-first-output (lambda ()
  196. (call-process "xrandr" nil t nil)
  197. (goto-char (point-min))
  198. (re-search-forward xrandr-output-regexp nil 'noerror)
  199. (match-string 1)))
  200. (default-output (with-temp-buffer
  201. (funcall find-first-output)))
  202. (second-output (with-temp-buffer
  203. (funcall find-first-output)
  204. (forward-line)
  205. (when (re-search-forward xrandr-output-regexp nil 'noerror)
  206. (match-string 1)))))
  207. (if (not second-output)
  208. (progn
  209. (call-process "xrandr" nil nil nil "--output" default-output "--auto" "--primary")
  210. (with-temp-buffer
  211. ;; Turn off all monitors that are not DEFAULT-OUTPUT.
  212. ;; See "--auto" in xrandr(1) and https://github.com/ch11ng/exwm/issues/529.
  213. (call-process "xrandr" nil t nil "--listactivemonitors")
  214. (goto-char (point-min))
  215. (while (not (eobp))
  216. (when (and (re-search-forward xrandr-monitor-regexp nil 'noerror)
  217. (not (string= (match-string 1) default-output)))
  218. (call-process "xrandr" nil nil nil "--output" (match-string 1) "--auto")))))
  219. (apply #'call-process
  220. "xrandr" nil nil nil
  221. "--output" second-output
  222. "--auto"
  223. (append
  224. (if ambrevar/exwm-change-screen-turn-off-primary '("--primary") '())
  225. (when ambrevar/exwm-change-screen-turn-off-primary
  226. (list "--output" default-output "--off"))))
  227. (setq exwm-randr-workspace-monitor-plist (list 0 second-output)))))
  228. (require 'exwm-randr)
  229. (exwm-randr-enable)
  230. (defun ambrevar/exwm-change-screen-toggle (&optional setting)
  231. "Toggle automatic multiscreen configuration.
  232. Turn off if you want to set the display settings manually.
  233. WIth SETTING to :ENABLE or :DISABLE, set"
  234. (interactive)
  235. (if (member 'ambrevar/exwm-change-screen-hook exwm-randr-screen-change-hook)
  236. (progn
  237. (remove-hook 'exwm-randr-screen-change-hook 'ambrevar/exwm-change-screen-hook)
  238. (message "Manual multiscreen handling."))
  239. (add-hook 'exwm-randr-screen-change-hook 'ambrevar/exwm-change-screen-hook)
  240. (message "Automatic multiscreen handling.")))
  241. (add-hook 'exwm-randr-screen-change-hook 'ambrevar/exwm-change-screen-hook)
  242. ;; TODO: Turn the toggle into a global minor mode.
  243. ;; Even better: Use autorandr_launcher (https://github.com/phillipberndt/autorandr/issues/210).
  244. (setq exwm-edit-bind-default-keys nil)
  245. ;; REVIEW: The following prevents passing "C-c" to the child window.
  246. ;; (when (require 'exwm-edit nil 'noerror)
  247. ;; (exwm-input-set-key (kbd "C-c '") #'exwm-edit--compose)
  248. ;; (exwm-input-set-key (kbd "C-c C-'") #'exwm-edit--compose))
  249. (provide 'init-exwm)