summaryrefslogtreecommitdiff
path: root/init-exwm.org
blob: 8acf7339894b2b81fc971a8182589ac4a77c02d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
#+PROPERTY: header-args:emacs-lisp :results silent
#+PROPERTY: header-args:emacs-lisp :tangle tangle/init-exwm.el

* Starting EXWM
Either start exwm in =.xinitrc= or if using a display manager setup a desktop file similar to this:
#+HEADER: :tangle /sudo::/usr/share/xsessions/exwm.desktop
#+begin_src conf
[Desktop Entry]
Name=EXWM
Comment=Emacs X Window Manager
TryExec=exwm-start
Exec=exwm-start
Type=Application
#+end_src
With the =exwm-start= script:
#+HEADER: :tangle /sudo::/usr/local/bin/exwm-start
#+begin_src shell :tangle-mode (identity #o755)
#!/usr/bin/env sh

export VISUAL=emacsclient
export EDITOR="$VISUAL"

# exec dbus-launch --exit-with-session emacs --eval "(progn (load \"/home/fpi/git/projects/dotfiles/tangle/init-exwm.el\") (exwm-enable))"
# exec dbus-launch --exit-with-session emacs
gpg-agent --daemon
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)

# exec dbus-launch --exit-with-session /home/fpi/.local/bin/emacs
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
exec /home/fpi/.local/bin/emacs
#+end_src
* EXWM config
#+begin_src emacs-lisp
(use-package exwm
  :straight t)
#+end_src
When starting the client from .xinitrc, `save-buffer-kill-terminal' will
force-kill Emacs before it can run through `kill-emacs-hook'.
#+BEGIN_SRC emacs-lisp
(global-set-key (kbd "C-x C-c") 'save-buffers-kill-emacs)
#+END_SRC

set keyboard
#+BEGIN_SRC emacs-lisp
(shell-command "setxkbmap -layout \"de(neo),us,ru,de\"")
#+END_SRC

** Monitor setup
#+BEGIN_SRC emacs-lisp
(use-package exwm-randr
  :config
  (setq exwm-randr-workspace-output-plist
        (when (equal fpi/current-device "pan") '(0 "DisplayPort-3" 1 "DisplayPort-5")))
  ;; (when (equal system-name "pan")
  ;;   (start-process-shell-command "xrandr" nil "xrandr --output DisplayPort-0 --off --output DisplayPort-1 --off --output DisplayPort-2 --off --output HDMI-A-0 --off --output DisplayPort-3 --mode 2560x1440 --pos 0x612 --rotate normal --output DisplayPort-4 --off --output DisplayPort-5 --mode 2560x1440 --pos 2560x0 --rotate right --output DisplayPort-6 --off")
  ;;   (exwm-workspace-add))
  (exwm-randr-enable))
#+END_SRC
** functions
#+BEGIN_SRC emacs-lisp
(defun ambrevar/switch-to-last-buffer ()
  "Switch to last open buffer in current window."
  (interactive)
  (switch-to-buffer (other-buffer (current-buffer) 1)))

(defun ambrevar/toggle-window-dedicated ()
  "Toggle whether the current active window is dedicated or not.
Run it in each window you want to 'freeze', i.e. prevent Emacs
from acting on it."
  (interactive)
  (message
   (if (let ((window (get-buffer-window (current-buffer))))
         (set-window-dedicated-p window
                                 (not (window-dedicated-p window))))
       "Window '%s' is dedicated"
     "Window '%s' is normal")
   (current-buffer)))

(defun ambrevar/toggle-window-split ()
  "Switch between vertical and horizontal split.
It only works for frames with exactly two windows."
  (interactive)
  (if (= (count-windows) 2)
      (let* ((this-win-buffer (window-buffer))
             (next-win-buffer (window-buffer (next-window)))
             (this-win-edges (window-edges (selected-window)))
             (next-win-edges (window-edges (next-window)))
             (this-win-2nd (not (and (<= (car this-win-edges)
                                         (car next-win-edges))
                                     (<= (cadr this-win-edges)
                                         (cadr next-win-edges)))))
             (splitter
              (if (= (car this-win-edges)
                     (car (window-edges (next-window))))
                  'split-window-horizontally
                'split-window-vertically)))
        (delete-other-windows)
        (let ((first-win (selected-window)))
          (funcall splitter)
          (if this-win-2nd (other-window 1))
          (set-window-buffer (selected-window) this-win-buffer)
          (set-window-buffer (next-window) next-win-buffer)
          (select-window first-win)
          (if this-win-2nd (other-window 1))))))

(defun ambrevar/toggle-single-window ()
  "Un-maximize current window.
If multiple windows are active, save window configuration and
delete other windows.  If only one window is active and a window
configuration was previously save, restore that configuration."
  (interactive)
  (if (= (count-windows) 1)
      (when single-window--last-configuration
        (set-window-configuration single-window--last-configuration))
    (setq single-window--last-configuration (current-window-configuration))
    (delete-other-windows)))
#+END_SRC
*** Window swapping
#+BEGIN_SRC emacs-lisp
(defun ambrevar/swap-windows (&optional w1 w2)
  "If 2 windows are up, swap them.
Else if W1 is a window, swap it with current window.
If W2 is a window too, swap both."
  (interactive)
  (unless (or (= 2 (count-windows))
              (windowp w1)
              (windowp w2))
    (error "Ambiguous window selection"))
  (let* ((w1 (or w1 (car (window-list))))
         (w2 (or w2
                 (if (eq w1 (car (window-list)))
                     (nth 1 (window-list))
                   (car (window-list)))))
         (b1 (window-buffer w1))
         (b2 (window-buffer w2))
         (s1 (window-start w1))
         (s2 (window-start w2)))
    (with-temp-buffer
      ;; Some buffers like EXWM buffers can only be in one live buffer at once.
      ;; Switch to a dummy buffer in w2 so that we don't display any buffer twice.
      (set-window-buffer w2 (current-buffer))
      (set-window-buffer w1 b2)
      (set-window-buffer w2 b1))
    (set-window-start w1 s2)
    (set-window-start w2 s1))
  (select-window w1))

(defun ambrevar/swap-windows-left ()
  "Swap current window with the window to the left."
  (interactive)
  (ambrevar/swap-windows (window-in-direction 'left)))
(defun ambrevar/swap-windows-below ()
  "Swap current window with the window below."
  (interactive)
  (ambrevar/swap-windows (window-in-direction 'below)))
(defun ambrevar/swap-windows-above ()
  "Swap current window with the window above."
  (interactive)
  (ambrevar/swap-windows (window-in-direction 'above)))
(defun ambrevar/swap-windows-right ()
  "Swap current window with the window to the right."
  (interactive)
  (ambrevar/swap-windows (window-in-direction 'right)))
#+END_SRC
*** Volume & Brightness
#+BEGIN_SRC emacs-lisp
(defun exwm-brightness (incdec)
  (shell-command (concat "xbacklight " incdec "10"))
  (notifications-notify :title (substring (shell-command-to-string "xbacklight") 0 -1)
			:replaces-id 6969
			:urgency 'low
			:timeout 550))

(defun exwm-volume (incdec)
  (notifications-notify
   :title (format
	   "Volume %s"
	   (substring
	    (shell-command-to-string
	     (format "amixer -D pulse set Master 5%%%s|tail -n 1|cut -d '[' -f 2|cut -d ']' -f 1"
		     incdec)) 0 -1))
   :replaces-id 6968
   :urgency 'low
   :timeout 550))
(defun exwm-togglemute ()
  (interactive)
  (notifications-notify
   :title (format
	   "Volume %s"
	   (substring
	    (shell-command-to-string
	     "amixer -D pulse set Master toggle|tail -n 1|cut -d '[' -f 3|cut -d ']' -f 1") 0 -1))
   :replaces-id 6968
   :urgency 'low
   :timeout 550))
#+END_SRC
*** XF86 Multimedia keys
#+BEGIN_SRC emacs-lisp
(defun exwm-xf86audio (cmd)
  ;; Control Spotify
  (shell-command (concat "dbus-send --type=method_call --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player." cmd)))
#+END_SRC
*** Browser switching
#+BEGIN_SRC emacs-lisp
(defun fpi/helm-exwm-switch (class &optional program other-window)
  "Switch to some EXWM windows belonging to CLASS.
If current window is not showing CLASS, switch to the last open CLASS window.
If there is none, start PROGRAM.

If PROGRAM is nil, it defaults to CLASS.
With prefix argument or if OTHER-WINDOW is non-nil, open in other window."
  ;; If current window is not in `exwm-mode' we switch to it.  Therefore we must
  ;; also make sure that current window is not a Helm buffer, otherwise calling
  ;; this function will lose focus in Helm.
  (unless helm-alive-p
    (setq program (or program class)
          other-window (or other-window current-prefix-arg))
    (let ((filter (lambda ()
                    (member (downcase (or exwm-class-name "")) class))))
      (if (and (eq major-mode 'exwm-mode)
               (funcall filter))
          (let ((helm-buffer-details-flag nil))
            (helm-exwm filter))
	(let ((last (buffer-list)))
          (while (and last
                      (not (with-current-buffer (car last)
                             (and (eq major-mode 'exwm-mode)
                                  (funcall filter)))))
            (setq last (cdr last)))
          (if last
              (funcall (if other-window 'switch-to-buffer-other-window 'switch-to-buffer) (car last))
            (when other-window (select-window (split-window-sensibly)))
            (start-process-shell-command program nil program)))))))

(defun fpi/helm-exwm-switch-browser ()
  "Switch to some `browse-url-generic-program' windows.

See `helm-exwm-switch'."
  (interactive)
  (fpi/helm-exwm-switch (quote ("qutebrowser"
				"firefox"))
			browse-url-generic-program))
#+END_SRC
** config
Time & Battery display
#+BEGIN_SRC emacs-lisp
(display-time)
(when (eq (fpi/current-device-info :type) 'mobile)
  (display-battery-mode))
#+END_SRC
Rename buffer to window title.\\
Spotify's title does not include "spotify" while playing music so just
append it.
#+BEGIN_SRC emacs-lisp
(defun fpie/exwm-rename-buffer-to-title ()
  (let ((newname (if (string-match "Spotify" (buffer-name))
		     (concat exwm-title " - Spotify")
		   exwm-title)))
    (exwm-workspace-rename-buffer newname)))

(add-hook 'exwm-update-title-hook 'fpie/exwm-rename-buffer-to-title)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(add-hook 'exwm-floating-setup-hook 'exwm-layout-hide-mode-line)
(add-hook 'exwm-floating-exit-hook 'exwm-layout-show-mode-line)
#+END_SRC

Non-floating resizing with mouse
#+BEGIN_SRC emacs-lisp
(setq window-divider-default-bottom-width 2
      window-divider-default-right-width 2)
(window-divider-mode)
#+END_SRC
System tray
#+BEGIN_SRC emacs-lisp
(use-package exwm-systemtray
  :straight exwm)
(exwm-systemtray-enable)
(setq exwm-systemtray-height 16)
#+END_SRC
+auto focus+
#+BEGIN_SRC emacs-lisp :tangle no
(setq mouse-autoselect-window t
      focus-follows-mouse t)
#+END_SRC
List all buffers
#+BEGIN_SRC emacs-lisp
(setq exwm-workspace-show-all-buffers t)
(setq exwm-layout-show-all-buffers t)
#+END_SRC
*** Helm
#+BEGIN_SRC emacs-lisp :results silent
(with-eval-after-load 'helm
  ;; Need `with-eval-after-load' here since 'helm-map is not defined in 'helm-config.
  (define-key helm-map (kbd "s-\\") 'helm-toggle-resplit-and-swap-windows)
  (exwm-input--set-key (kbd "s-p") 'helm-run-external-command)
  (exwm-input-set-key (kbd "s-c") 'helm-resume)
  (exwm-input-set-key (kbd "s-b") 'helm-mini)
  (exwm-input-set-key (kbd "s-f") 'helm-find-files)
  (exwm-input-set-key (kbd "s-F") 'helm-locate)
  ;;(when (fboundp 'ambrevar/helm-locate-meta)
  ;;  (exwm-input-set-key (kbd "s-F") #'ambrevar/helm-locate-meta))
  ;;(exwm-input-set-key (kbd "s-g") 'ambrevar/helm-grep-git-or-ag)
  ;;(exwm-input-set-key (kbd "s-G") 'ambrevar/helm-grep-git-all-or-ag)
  )

(use-package helm-exwm
  :straight t)
(exwm-input-set-key (kbd "s-w") #'fpi/helm-exwm-switch-browser)
(exwm-input-set-key (kbd "s-W") #'helm-exwm-switch-browser-other-window)
#+END_SRC
*** Keys
Global bindings
#+BEGIN_SRC emacs-lisp
(exwm-input-set-key (kbd "s-K") #'exwm-reset)
(exwm-input-set-key (kbd "s-x") #'exwm-input-toggle-keyboard)

(exwm-input-set-key (kbd "s-s") #'windmove-left)
(exwm-input-set-key (kbd "s-n") #'windmove-down)
(exwm-input-set-key (kbd "s-r") #'windmove-up)
(exwm-input-set-key (kbd "s-t") #'windmove-right)

(exwm-input-set-key (kbd "s-B") #'ibuffer-list-buffers)
(exwm-input-set-key (kbd "s-X") #'kill-this-buffer)

(exwm-input-set-key (kbd "s-M") #'exwm-workspace-switch)

(exwm-input-set-key (kbd "s-\\") 'ambrevar/toggle-window-split)
(exwm-input-set-key (kbd "s-S") 'ambrevar/swap-windows-left)
(exwm-input-set-key (kbd "s-N") 'ambrevar/swap-windows-below)
(exwm-input-set-key (kbd "s-R") 'ambrevar/swap-windows-above)
(exwm-input-set-key (kbd "s-T") 'ambrevar/swap-windows-right)

(exwm-input-set-key (kbd "s-<tab>") #'ambrevar/switch-to-last-buffer)
(exwm-input-set-key (kbd "s-<return>") #'vterm)
(exwm-input-set-key (kbd "S-s-<return>") (lambda ()
					 (interactive)
					 (start-process "term" nil "tilix")))
(exwm-input-set-key (kbd "s-h") 'bury-buffer)

(exwm-input-set-key (kbd "s-g") 'previous-buffer)
(exwm-input-set-key (kbd "s-G") 'next-buffer)
#+END_SRC
#+BEGIN_SRC emacs-lisp
(exwm-input-set-key (kbd "s-!") 'helm-pass)
#+END_SRC
Volume & Brightness
#+BEGIN_SRC emacs-lisp
(exwm-input-set-key [XF86AudioLowerVolume] (lambda () (interactive) (exwm-volume "-")))
(exwm-input-set-key [XF86AudioRaiseVolume] (lambda () (interactive) (exwm-volume "+")))
(exwm-input-set-key [XF86AudioMute] 'exwm-togglemute)
(exwm-input-set-key [XF86MonBrightnessUp] (lambda () (interactive) (exwm-brightness "+")))
(exwm-input-set-key [XF86MonBrightnessDown] (lambda () (interactive) (exwm-brightness "-")))
#+END_SRC
XF86 Multimedia Keys
#+BEGIN_SRC emacs-lisp
(exwm-input--set-key [XF86AudioPlay] (lambda () (interactive) (exwm-xf86audio "PlayPause")))
(exwm-input--set-key [XF86AudioPause] (lambda () (interactive) (exwm-xf86audio "PlayPause")))
(exwm-input--set-key [XF86AudioNext] (lambda () (interactive) (exwm-xf86audio "Next")))
(exwm-input--set-key [XF86AudioPrev] (lambda () (interactive) (exwm-xf86audio "Previous")))
#+END_SRC
**** Local bindings
#+BEGIN_SRC emacs-lisp
(push ?\s-  exwm-input-prefix-keys)
(define-key exwm-mode-map (kbd "s-SPC") #'exwm-floating-toggle-floating)
(define-key exwm-mode-map (kbd "s-i") #'follow-delete-other-windows-and-split) ;; any useful?
(define-key exwm-mode-map (kbd "s-o") #'ambrevar/toggle-single-window)
(define-key exwm-mode-map (kbd "s-O") #'exwm-layout-toggle-fullscreen)

(define-key exwm-mode-map (kbd "C-q") #'exwm-input-send-next-key)
#+END_SRC
Allow access to my personal keymap.
#+BEGIN_SRC emacs-lisp
(push ?\C-z exwm-input-prefix-keys)
#+END_SRC

**** Simulation keys
#+BEGIN_SRC emacs-lisp
(setq exwm-input-simulation-keys
      '(([?\C-b] . [left])
        ([?\C-f] . [right])
        ([?\C-p] . [up])
        ([?\C-n] . [down])
        ([?\C-a] . [home])
        ([?\C-e] . [end])
        ([?\M-v] . [prior])
        ([?\C-v] . [next])
        ([?\C-d] . [delete])))
        ;;([?\C-k] . [S-end delete]))) ; doesn't work in tilix
#+END_SRC
*** Configure helm-raise-command
~(shell-command "emacsclient -e ...")~ does not work. Advice
~helm-run-or-raise~ instead and overshadow ~shell-command~.

For now ~helm-run-or-raise~ is redefined after helm is loaded in
~emacs-init.org~ instead of advised.
#+begin_src emacs-lisp
(defun fpi/get-proc-buffers (proc)
  (let ((cand (helm-exwm-candidates)))
    (remove
     nil (mapcar
	  (lambda (c)
	    (if (equal
		 (downcase proc)
		 (downcase (with-current-buffer c (or exwm-class-name ""))))
		c
	      nil)) cand))))
(defun fpi/switch-to-proc-buffer (proc)
  (switch-to-buffer (car (fpi/get-proc-buffers proc))))

;; (setq helm-raise-command "emacsclient -e '(fpi/switch-to-proc-buffer \"%s\")'")
(setq helm-raise-command t)
#+end_src
*** Screenshots
UncleDave has a nice exwm configuration in his [[https://github.com/daedreth/UncleDavesEmacs/blob/master/config.org][config]]. These snippets
are taken from there.

A nice alternative for screenshots in org-mode is ~org-screenshot.el~.
It uses ~scrot~ to take screenshots of windows and insert a link the
image into the current org buffer.

**** Screenshotting the entire screen
#+BEGIN_SRC emacs-lisp
  (defun daedreth/take-screenshot ()
    "Takes a fullscreen screenshot of the current workspace"
    (interactive)
    (when window-system
    (loop for i downfrom 3 to 1 do
          (progn
            (message (concat (number-to-string i) "..."))
            (sit-for 1)))
    (message "Cheese!")
    (sit-for 1)
    (start-process "screenshot" nil "import" "-window" "root" 
               (concat (getenv "HOME") "/" (subseq (number-to-string (float-time)) 0 10) ".png"))
    (message "Screenshot taken!")))
  (global-set-key (kbd "<print>") 'daedreth/take-screenshot)
#+END_SRC

**** Screenshotting a region
#+BEGIN_SRC emacs-lisp
(defun daedreth/take-screenshot-region ()
  "Takes a screenshot of a region selected by the user."
  (interactive)
  (when window-system
    (call-process "import" nil nil nil ".newScreen.png")
    (call-process "convert" nil nil nil ".newScreen.png" "-shave" "1x1"
                  (concat (getenv "HOME") "/" (subseq (number-to-string (float-time)) 0 10) ".png"))
    (call-process "rm" nil nil nil ".newScreen.png")))
;; (global-set-key (kbd "<Scroll_Lock>") 'daedreth/take-screenshot-region)
#+END_SRC