#+title: exwm configuration * Load and basic config #+begin_src emacs-lisp (defvar jao-exwm--use-afio t) (use-package exwm :ensure t :init (setq exwm-workspace-number 1 exwm-workspace-show-all-buffers t exwm-workspace-warp-cursor nil exwm-layout-show-all-buffers t exwm-floating-border-color (if (jao-colors-scheme-dark-p) "black" "grey90"))) (use-package exwm-edit :ensure t) (require 'exwm) #+end_src * Frames as workspaces #+begin_src emacs-lisp (defun jao-exwm--new-frame-p () (not (frame-parameter nil 'jao-frames-initialized))) (defun jao-exwm--mark-frame (force) (prog1 (or force (jao-exwm--new-frame-p)) (set-frame-parameter nil 'jao-frames-initialized t))) (defun jao-exwm--goto-main (&optional init) (interactive "P") (exwm-workspace-switch-create 1) (when (jao-exwm--mark-frame init) (jao-trisect))) (defun jao-exwm--goto-mail (&optional init) (interactive "P") (exwm-workspace-switch-create 2) (when (jao-exwm--mark-frame init) (jao-afio-open-gnus))) (defun jao-exwm--goto-w3m (&optional init) (interactive "P") (exwm-workspace-switch-create 5) (when (jao-exwm--mark-frame init) (jao-afio-open-w3m) (let ((scroll-bar-mode 'left)) (toggle-scroll-bar 1) (set-frame-parameter (window-frame) 'scroll-bar-width 12)) (jao-toggle-inactive-mode-line))) (defun jao-exwm--goto-docs (&optional init) (interactive "P") (exwm-workspace-switch-create 4) (when (jao-exwm--mark-frame init) (jao-afio-open-doc))) (defun jao-exwm-open-doc (file) (jao-exwm--goto-docs) (jao-find-or-open file)) (defun jao-exwm-no-afio-setup () (interactive) (defalias 'jao-open-gnus-frame 'jao-exwm--goto-mail) (defalias 'jao-goto-w3m-frame 'jao-exwm--goto-w3m) (setq jao-open-doc-fun #'jao-exwm-open-doc) (setq minibuffer-follows-selected-frame t) (global-set-key "\C-cf" 'jao-exwm--goto-main) (global-set-key "\C-cg" 'jao-exwm--goto-mail) (global-set-key "\C-cw" 'jao-exwm--goto-w3m) (global-set-key "\C-cz" 'jao-exwm--goto-docs)) (if jao-exwm--use-afio (setq minibuffer-follows-selected-frame nil) (jao-exwm-no-afio-setup)) #+end_src * Tracking #+begin_src emacs-lisp (add-hook 'exwm-workspace-switch-hook 'tracking-remove-visible-buffers) #+end_src * Buffer titles #+BEGIN_SRC emacs-lisp (defun jao-exwm--use-title-p () (or (string-prefix-p "sun-awt-X11-" exwm-instance-name) (string= "Navigator" exwm-instance-name) (string= "Spotify" exwm-class-name) (string= "XTerm" exwm-class-name) (string= "Zathura" exwm-class-name))) (defun jao-exwm-rename-buffer/class () (unless (jao-exwm--use-title-p) (exwm-workspace-rename-buffer exwm-class-name))) (defun jao-exwm-rename-buffer/title () (cond ((or (not exwm-instance-name) (jao-exwm--use-title-p)) (exwm-workspace-rename-buffer exwm-title)) ((string= "Zathura" exwm-class-name) (exwm-workspace-rename-buffer (format "zathura: %s" (file-name-nondirectory exwm-title)))))) (defun jao-exwm--set-exwm-name () (when (not jao-exwm--name) (setq jao-exwm--name jao-exwm--current-name jao-exwm--current-name nil))) (add-hook 'exwm-mode-hook 'jao-exwm--set-exwm-name) (add-hook 'exwm-update-class-hook 'jao-exwm-rename-buffer/class) (add-hook 'exwm-update-title-hook 'jao-exwm-rename-buffer/title) #+END_SRC * Float windows #+begin_src emacs-lisp (defvar jao-exwm-max-x (x-display-pixel-width)) (defvar jao-exwm-max-y (x-display-pixel-height)) (defun jao-exwm--float-to (x y &optional w h) (let* ((w (or w (frame-pixel-width))) (h (or h (frame-pixel-height))) (x (if (< x 0) (- jao-exwm-max-x (- x) w) x)) (y (if (< y 0) (- jao-exwm-max-y (- y) h) y)) (p (or (frame-parameter nil 'jao-position) (frame-position)))) (exwm-floating-move (- x (car p)) (- y (cdr p))) (exwm-layout-enlarge-window-horizontally (- w (frame-pixel-width))) (exwm-layout-enlarge-window (- h (frame-pixel-height))) (set-frame-parameter nil 'jao-position (cons x y)))) (defun jao-exwm--center-float (&optional w h) (interactive) (let* ((mx jao-exwm-max-x) (my jao-exwm-max-y) (w (or w (frame-pixel-width))) (h (or h (/ (* w my) mx)))) (jao-exwm--float-to (/ (- mx w) 2) (/ (- my h) 2) w h))) (defun jao-exwm--setup-float () (set-frame-parameter nil 'jao-position nil) (cond ((string= "Firefox" exwm-class-name) (jao-exwm--center-float 900 600)) ((member exwm-class-name '("mpv" "vlc")) (jao-exwm--center-float 1200)))) (defvar jao-exwm-floating-classes '("mpv" "vlc")) (defun jao-exwm--maybe-float () (when (member exwm-class-name jao-exwm-floating-classes) (when (not exwm--floating-frame) (exwm-floating-toggle-floating)))) (add-hook 'exwm-floating-setup-hook #'jao-exwm--setup-float) (add-hook 'exwm-manage-finish-hook #'jao-exwm--maybe-float) #+end_src * Minibuffer and system tray #+BEGIN_SRC emacs-lisp (require 'exwm-systemtray) (setq exwm-systemtray-height 14) (exwm-systemtray-enable) (setq jao-minibuffer-frame-width 271) (add-hook 'exwm-workspace-switch-hook #'jao-minibuffer-refresh) (defun jao-exwm--watch-tray (sym newval op where) (setq jao-minibuffer-right-margin (* 2 (length newval))) (jao-minibuffer-refresh)) (add-variable-watcher 'exwm-systemtray--list #'jao-exwm--watch-tray) #+END_SRC * Switch to buffer / app #+begin_src emacs-lisp (defvar-local jao-exwm--name nil) (defvar jao-exwm--current-name nil) (defun jao-exwm--check-name (name) (or (equalp jao-exwm--name name) (equalp (buffer-name) name) (equalp exwm-class-name name) (equalp exwm-title name))) (defun jao-exwm-switch-to-class/title (cln) (interactive) (when cln (if (jao-exwm--check-name cln) (current-buffer) (let ((bfs (seq-filter `(lambda (b) (and (not (eq b ,(current-buffer))) (with-current-buffer b (jao-exwm--check-name ,cln)))) (buffer-list)))) (when (car bfs) (pop-to-buffer (car (reverse bfs)))))))) (defun jao-exwm-switch-to-next-class () (interactive) (jao-exwm-switch-to-class/title exwm-class-name)) (defun jao-exwm-switch-to-next-x () (interactive) (let ((bfs (seq-filter (lambda (b) (buffer-local-value 'exwm-class-name b)) (buffer-list (window-frame))))) (when (car bfs) (switch-to-buffer (car (reverse bfs)))))) #+end_src * App runners #+BEGIN_SRC emacs-lisp (defun jao-exwm-run (command) (interactive (list (read-shell-command "$ " (if current-prefix-arg (cons (concat " " (buffer-file-name)) 0) "")))) (setq jao-exwm--current-name nil) (start-process-shell-command command nil command)) (defmacro jao-exwm-runner (&rest args) `(lambda () (interactive) (start-process "" nil ,@args))) (defun jao-exwm-workspace (n) (if jao-exwm--use-afio (cl-case n ((1) (jao-afio--goto-main)) ((2) (jao-afio--goto-gnus)) ((3) (jao-afio--goto-w3m)) ((4) (jao-afio--goto-docs)) ((5) (jao-afio--goto-scratch-1)) ((0) (jao-afio--goto-scratch))) (exwm-workspace-switch-create n))) (defmacro jao-def-runner (name ws class &rest args) `(defun ,name (&rest other-args) (interactive) ,@(when ws `((jao-exwm-workspace ,ws))) (if (jao-exwm-switch-to-class/title ,class) ,(or (stringp (car args)) args) (setq jao-exwm--current-name ,class) ,(if (stringp (car args)) `(start-process-shell-command ,(car args) "* exwm - console *" (string-join (append (list ,@args) other-args) " ")) args)))) (jao-def-runner jao-exwm-spotify 6 "Spotify" "spotify") (jao-def-runner jao-exwm-spt 6 "spt" "xterm" "-e" "spt") (jao-def-runner jao-exwm-firefox 5 "Firefox" "firefox") (defun jao-exwm-firefox-1 () (interactive) (jao-exwm-firefox) (delete-other-windows)) (jao-def-runner jao-exwm-vlc 4 "VLC" "vlc") (jao-def-runner jao-exwm-slack 0 "Slack" "slack") (jao-def-runner jao-exwm-signal 0 "Signal" "signal-desktop") (jao-def-runner jao-exwm-proton-bridge 0 "*proton-bridge*" "protonmail-bridge") (jao-def-runner jao-exwm-htop 0 "htop-xterm" "xterm" "-title" "htop-xterm" "-e" "htop") (jao-def-runner jao-exwm-aptitude 0 "aptitude-xterm" "xterm" "-title" "aptitude-xterm" "-e" "aptitude") (jao-def-runner jao-exwm-blueman 0 "Blueman-manager" "blueman-manager") (jao-def-runner jao-exwm-ncmpcpp 0 "ncmpcpp" "xterm" "-e" "ncmpcpp" "-p" "6669") (jao-def-runner jao-exwm-proton-vpn 0 "*pvpn*" proton-vpn-status) (jao-def-runner jao-exwm-enwc 0 "*ENWC*" enwc) (jao-def-runner jao-exwm-bluetooth 0 "*Bluetooth*" bluetooth-list-devices) (jao-def-runner jao-exwm-paradox 0 "*Packages*" paradox-list-packages nil) (jao-def-runner jao-exwm-proced 0 "*Proced*" proced) (defun jao-exwm-kill-xmobar () (interactive) (shell-command "killall xmobar-exwm")) (defun jao-exwm-xmobar () (interactive) (jao-exwm-kill-xmobar) (start-process "" nil "xmobar-exwm" "-d")) (jao-def-runner jao-exwm-open-with-zathura nil nil "zathura" (buffer-file-name)) (jao-def-runner jao-exwm-open-with-mupdf nil nil "mupdf" (buffer-file-name)) (jao-def-runner jao-exwm-xterm 0 nil "xterm") (defun jao-exwm-import-screen (&optional area) (interactive "P") (when (not (file-directory-p "/tmp/screenshot")) (make-directory "/tmp/screenshot")) (let ((c (format "import %s %s" (if area "" "-window root") "/tmp/screenshot/$(date +'%g%m%d-%H%M%S').png"))) (start-process-shell-command "import" "* exwm - console *" c))) #+END_SRC * Zathura support #+begin_src emacs-lisp (defun jao-zathura--buffers () (seq-filter (lambda (b) (string= "Zathura" (or (buffer-local-value 'exwm-class-name b) ""))) (buffer-list))) (defun jao-zathura--file-info (b) (with-current-buffer b (when (string-match "\\(.+\\) \\[\\(.+\\) (\\([0-9]+\\)/\\([0-9]+\\))\\]" (or exwm-title "")) (list (expand-file-name (match-string 1 exwm-title)) (string-to-number (match-string 3 exwm-title)) (string-to-number (match-string 4 exwm-title)) (match-string 2 exwm-title))))) (defun jao-exwm--send-str (str) (dolist (k (string-to-list (kbd str))) (exwm-input--fake-key k))) (defun jao-zathura-open-doc (file-name &optional page-no height) (let* ((file-name (expand-file-name file-name)) (buffer (seq-find `(lambda (b) (string= ,file-name (car (jao-zathura--file-info b)))) (jao-zathura--buffers)))) (if jao-exwm--use-afio (jao-afio--goto-docs) (jao-exwm--goto-docs)) (if (not buffer) (jao-exwm-run (if page-no (format "zathura -P %s %s" page-no file-name) (format "zathura %s" file-name))) (pop-to-buffer buffer) (jao-exwm--send-str (format "%sg" page-no))))) (defun jao-exwm-pdf-zathura-close-all () (interactive) (dolist (b (jao-zathura--buffers)) (ignore-errors (with-current-buffer b (jao-exwm--send-str "q")))) t) (defun jao-exwm-pdf-goto-org (&optional arg) (interactive "P") (when-let ((info (jao-zathura--file-info (current-buffer)))) (when-let ((file (jao-org-pdf-to-org-file (car info)))) (let ((newp (not (file-exists-p file)))) (when (or arg newp) (org-store-link nil t)) (find-file-other-window file) (when newp (jao-org-insert-doc-skeleton) (org-insert-link)))))) (defun jao-exwm-pdf-goto-org* () (interactive) (jao-exwm-pdf-goto-org t)) (defun jao-exwm-org-store-zathura-link () (when-let ((info (jao-zathura--file-info (current-buffer)))) (jao-org-links-store-pdf-link (car info) (cadr info) (jao-org--pdf-title (car info))))) (defun jao-exwm-pdf-enable-zathura () (interactive) (add-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all) (setq jao-org-open-pdf-fun #'jao-zathura-open-doc) (setq jao-org-links-pdf-store-fun #'jao-exwm-org-store-zathura-link) (setq jao-open-doc-fun #'jao-zathura-open-doc)) (defun jao-exwm-pdf-disable-zathura () (interactive) (remove-hook 'kill-emacs-query-functions #'jao-exwm-pdf-zathura-close-all) (setq jao-org-open-pdf-fun #'jao-afio-open-pdf) (setq jao-org-links-pdf-store-fun nil) (setq jao-open-doc-fun #'jao-find-or-open)) (when (not jao-browse-doc-use-emacs-p) (jao-exwm-pdf-enable-zathura)) #+end_src * Hydras #+begin_src emacs-lisp (major-mode-hydra-define pdf-view-mode nil ("Elsewhere" (("b" (jao-buffer-same-mode 'pdf-view-mode) "other PDFs") ("o" jao-org-pdf-goto-org "notes file") ("O" (jao-org-pdf-goto-org 4) "notes file, linking")) "External" (("p" doc-view-presentation "presentation") ("z" jao-exwm-open-with-zathura "open with zathura") ("m" jao-exwm-open-with-mupdf "open with mupdf")))) (defhydra jao-hydra-float (:color blue) "Float" ("f" exwm-floating-toggle-floating "float") ("F" exwm-layout-toggle-fullscreen "full") ("tl" (jao-exwm--float-to 20 20)) ("tr" (jao-exwm--float-to -20 20)) ("bl" (jao-exwm--float-to 20 -20)) ("br" (jao-exwm--float-to -20 -20)) ("c" jao-exwm--center-float) ("k" (exwm-floating-move 0 -5) :color red) ("j" (exwm-floating-move 0 5) :color red) ("h" (exwm-floating-move -5 0) :color red) ("l" (exwm-floating-move 5 0) :color red) ("K" (exwm-layout-enlarge-window 5) :color red) ("J" (exwm-layout-enlarge-window -5) :color red) ("H" (exwm-layout-enlarge-window 5 t) :color red) ("L" (exwm-layout-enlarge-window -5 t) :color red) ("q" nil "")) (pretty-hydra-define jao-hydra-exwm-misc (:color blue :quit-key "q") ("Jump around" (("s" jao-slack-download "slack downloads") ("h" (jao-buffer-same-mode 'helpful) "helpful buffer") ("g" exwm-workspace-switch nil)) "X-Windows" (("f" exwm-floating-toggle-floating "toggle floating window") ("F" exwm-layout-toggle-fullscreen "toggle fullscreen mode") ("m" exwm-workspace-move-window nil)) "Zathura" (("o" jao-exwm-pdf-goto-org "Go to org notes") ("O" (jao-exwm-pdf-goto-org t) "Go to org notes, linking")) "Exwm utilities" (("k" exwm-input-release-keyboard "release keyboard") ("x" exwm-reset "reset exwm")))) (pretty-hydra-define jao-hydra-exwm (global-map "s-w" :color blue :quit-key "q") ("Notes" (("n" jao-org-notes-open-or-create "capture note") ("N" jao-org-notes-open "go to note")) "Packages" (("a" jao-vterm-aptitude "aptitude") ("l" jao-exwm-paradox "package list") ("s-w" jao-hydra-exwm/body nil)) "Network" (("s" jao-ssh "ssh") ("b" jao-exwm-bluetooth "bluetooth") ;; ("N" jao-exwm-enwc "networks") ) "Proton" (("v" jao-exwm-proton-vpn "proton vpn") ("m" run-proton-bridge "proton bridge")) "Monitors" (("p" jao-vterm-htop "htop") ("P" jao-exwm-proced "proced") ("t" jao-time-echo-times "current time")) "Apps" (("f" jao-exwm-firefox-1 "firefox") ("x" jao-exwm-xmobar "restart xmobar") ("X" jao-exwm-kill-xmobar "kill xmobar")) "Looks" (("T" jao-toggle-transparency "toggle transparency" :toggle (jao-transparent-p) :color red) ("w" jao-set-wallpaper "set wallpaper") ("W" jao-set-random-wallpaper "set radom wallpaper")) "Helpers" (("r" org-reveal "org reveal") ("k" jao-kb-toggle "toggle keyboard" :toggle (jao-kb-toggled-p) :color red) ("M" jao-minibuffer-toggle "toggle minibuffer" :toggle jao-minibuffer-enabled-p)))) #+end_src * Keybindings #+BEGIN_SRC emacs-lisp (define-key exwm-mode-map [?\C-q] #'exwm-input-send-next-key) (define-key exwm-mode-map [?\s-f] #'jao-hydra-float/body) (define-key exwm-mode-map [?\s-m] #'jao-hydra-media/body) (define-key exwm-mode-map [?\s-s] #'jao-hydra-spotify/body) (define-key exwm-mode-map [?\s-w] #'jao-hydra-exwm/body) (define-key exwm-mode-map [?\s-p] #'jao-exwm-pdf-goto-org) (define-key exwm-mode-map [?\s-P] #'jao-exwm-pdf-goto-org*) (setq exwm-input-global-keys '(([?\s-0] . jao-afio--goto-scratch) ([?\s-1] . jao-afio--goto-main) ([?\s-2] . jao-afio--goto-gnus) ([?\s-3] . jao-afio--goto-w3m) ([?\s-4] . jao-afio--goto-docs) ([?\s-A] . org-agenda-list) ([?\s-a] . jao-first-window) ([?\s-b] . jao-hydra-org-blog/body) ([?\s-c] . jao-hydra-chats/body) ([?\s-e] . jao-exwm-firefox-1) ([?\s-t] . jao-vterm-here-toggle) ([?\s-n] . jao-hydra-ednc/body) ([?\s-O] . ace-swap-window) ([?\s-o] . ace-window) ([?\s-x] . jao-hydra-exwm-misc/body) ([?\s-z] . jao-hydra-sleep/body) ([XF86AudioMute] . jao-mixer-master-toogle) ([XF86AudioPlay] . jao-player-toggle) ([XF86AudioPause] . jao-player-toggle) ([XF86AudioNext] . jao-player-next) ([XF86AudioPrev] . jao-player-previous) ([XF86AudioRaiseVolume] . jao-mixer-master-up) ([XF86AudioLowerVolume] . jao-mixer-master-down) ([XF86MonBrightnessUp] . jao-bright-up) ([XF86MonBrightnessDown] . jao-bright-down) ([?\s-\`] . jao-exwm-switch-to-next-x) ([s-tab] . jao-exwm-switch-to-next-class) ([print] . jao-exwm-import-screen) ([f5] . jao-weather) ([f6] . jao-bright-show) ([f7] . jao-toggle-audio-applet) ([f8] . jao-toggle-nm-applet))) (when (eq 'counsel jao-completion-engine) (add-to-list 'exwm-input-global-keys '([?\s-r] . counsel-linux-app))) ;; (customize-set-variable 'exwm-input-global-keys exwm-input-global-keys) #+END_SRC