summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-03-07 07:40:57 +0100
committercashmere <cashmere@autistici.org>2026-03-07 07:40:57 +0100
commitd7ad7f88221e343f460cc4fcce7649aace725a51 (patch)
tree920be57b5a32d37f9edb0c9a506e029f21c271cc
parent5e0dce0f6fec9751d98391b7068ebc09559d6414 (diff)
downloademacs-d7ad7f88221e343f460cc4fcce7649aace725a51.tar.gz
2026-03-07T07:41:01+01:00
-rw-r--r--.gitignore1
-rw-r--r--config.org476
2 files changed, 212 insertions, 265 deletions
diff --git a/.gitignore b/.gitignore
index 6280f3b..a11bc59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,4 @@ dirvish/thumbnails
.env
/elfeed/data/
/eaf/
+*.md
diff --git a/config.org b/config.org
index 86391dc..14ef75f 100644
--- a/config.org
+++ b/config.org
@@ -9,14 +9,14 @@
#+begin_src emacs-lisp
;;; config.el --- Emacs-Kick --- A feature rich Emacs config for (neo)vi(m)mers -*- lexical-binding: t; -*-
;; (setenv "LSP_USE_PLISTS" "true")
-;; (setq debug-on-error '(wrong-type-argument))
+;; (setq debug-on-error t)
;; (setq gc-cons-threshold #x40000000)
;; (setq gc-cons-threshold 50000000)
;; (setenv "LSP_USE_PLISTS" "true")
;; (setq lsp-use-plists t)
(setq pgtk-wait-for-event-timeout 0.001)
(setq package-enable-at-startup nil)
-(setq-default mode-line-format t)
+;; (setq-default mode-line-format t) ;; disabled: boolean t is not valid for mode-line-format
(setq default-frame-alist '((undecorated . t)))
(setq gc-cons-threshold 100000000)
@@ -960,6 +960,7 @@ Not necessary but an eyecandy to look at!
(use-package flycheck
:ensure t
:config
+ (setq flycheck-global-modes '(not org-mode))
(add-hook 'after-init-hook #'global-flycheck-mode))
#+end_src
@@ -1185,14 +1186,46 @@ Old config
#+begin_src emacs-lisp
(with-eval-after-load 'org
+ (setq org-confirm-babel-evaluate nil)
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)
- (python . t)
+ (python . t)
+ (ruby . t)
+ (perl . t)
+ (C . t)
+ (js . t)
+ (css . t)
+ (sass . t)
+ (sql . t)
+ (sqlite . t)
+ (lua . t)
+ (lisp . t)
+ (scheme . t)
+ (clojure . t)
+ (haskell . t)
+ (ocaml . t)
+ (java . t)
(dot . t)
- (C . t)
- (makefile . t))))
+ (gnuplot . t)
+ (plantuml . t)
+ (ditaa . t)
+ (calc . t)
+ (latex . t)
+ (org . t)
+ (makefile . t)
+ (awk . t)
+ (sed . t)
+ (screen . t)
+ (eshell . t))))
+#+end_src
+
+** ob-async
+
+#+begin_src emacs-lisp
+(use-package ob-async
+ :after org)
#+end_src
** my/snip-upload
@@ -2638,9 +2671,10 @@ completion-category-overrides '((file (styles partial-completion))))) ;; Customi
(setq eglot-autoshutdown t)
(setq eglot-send-changes-idle-time 0.1)
(setq eglot-sync-connect nil)
- (setq eglot-connect-timeout nil)
+ (setq eglot-connect-timeout 30)
(setq eglot-events-buffer-size 0)
(setq eglot-report-progress nil)
+ (setq jsonrpc-default-request-timeout 10)
(add-hook 'eglot-managed-mode-hook
(lambda ()
@@ -2787,15 +2821,65 @@ ily <3.
#+end_src
-** js-mode
+** JavaScript / TypeScript / Vue
+
+JavaScript and TypeScript support via tree-sitter modes and [[https://github.com/joaotavora/rassumfrassum][rassumfrassum]]. The =rass tslint= preset multiplexes =typescript-language-server= + =eslint=.
+Vue files use =web-mode= with the =rass vuetail= preset (=vue-language-server= + =tailwindcss-language-server=).
+
+*** js-mode
#+begin_src emacs-lisp
(use-package js
:ensure t
:mode ("\\.js\\'" . js-mode)
+ :hook (js-mode . eglot-ensure)
+ :config
+ (setq js-indent-level 2)
+ (with-eval-after-load 'eglot
+ (add-to-list 'eglot-server-programs
+ '(js-mode . ("rass" "tslint")))))
+#+end_src
+
+*** typescript-ts-mode
+
+Uses built-in tree-sitter modes for =.ts= and =.tsx= files.
+Requires tree-sitter grammars: =M-x treesit-install-language-grammar RET typescript= (and =tsx=).
+
+#+begin_src emacs-lisp
+(use-package typescript-ts-mode
+ :straight nil
+ :ensure nil
+ :mode (("\\.ts\\'" . typescript-ts-mode)
+ ("\\.tsx\\'" . tsx-ts-mode))
+ :hook ((typescript-ts-mode . eglot-ensure)
+ (tsx-ts-mode . eglot-ensure))
+ :config
+ (with-eval-after-load 'eglot
+ (add-to-list 'eglot-server-programs
+ '((typescript-ts-mode tsx-ts-mode) . ("rass" "tslint")))))
+#+end_src
+
+*** web-mode (Vue)
+
+#+begin_src emacs-lisp
+(use-package web-mode
+ :ensure t
+ :mode "\\.vue\\'"
+ :hook (web-mode . eglot-ensure)
:config
- (setq js-indent-level 2))
+ (setq web-mode-markup-indent-offset 2)
+ (setq web-mode-css-indent-offset 2)
+ (setq web-mode-code-indent-offset 2)
+ (setq web-mode-style-padding 2)
+ (setq web-mode-script-padding 2)
+ (with-eval-after-load 'eglot
+ (add-to-list 'eglot-server-programs
+ '(web-mode . ("rass" "vuetail")))))
+#+end_src
+*** mint-mode
+
+#+begin_src emacs-lisp
(use-package mint-mode
:straight (mint-mode
:type git
@@ -2945,92 +3029,6 @@ ily <3.
:defer t)
#+end_src
-** eee.el
-
-#+begin_src emacs-lisp
-(use-package eee
- :ensure t
- :straight (:type git :host github :repo "eval-exec/eee.el"
- :files (:defaults "*.el" "*.sh"))
- :config
- (setq ee-terminal-command "kitty"))
-#+end_src
-
-* eaf
-
-Emacs Application Framework -- browser and terminal via Qt widgets.
-Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
-
-EAF loading is deferred until a graphical frame exists so the daemon
-can start headless without crashing. All requires are wrapped in
-=condition-case= so a broken EAF never takes down the rest of init.
-
-#+begin_src emacs-lisp
-;; Settings can be declared before EAF loads -- they are just variables.
-(setq eaf-pyqterminal-font-size 24
- eaf-pyqterminal-refresh-ms 17
- eaf-pyqterminal-font-family "GeistMono Nerd Font"
- ;; eaf-pyqterminal-cursor-type "bar"
- ;; eaf-pyqterminal-cursor-size 1
- )
-
-(defvar my/eaf-loaded nil
- "Non-nil after EAF has been successfully loaded.")
-
-(defun my/eaf-init ()
- "Load EAF and its apps, but only when a graphical display is available.
-Errors are caught so a broken EAF never kills the Emacs session."
- (when (and (display-graphic-p) (not my/eaf-loaded))
- (condition-case err
- (progn
- (require 'eaf)
- (require 'eaf-browser)
- (require 'eaf-pyqterminal)
- (require 'eaf-auralis)
-
- ;; Evil leader integration
- (when (featurep 'evil)
- (require 'eaf-evil)
- (setq eaf-evil-leader-key "C-SPC")
- (let ((leader-km (when (boundp 'general-override-mode-map)
- (lookup-key
- (evil-get-auxiliary-keymap general-override-mode-map 'normal)
- (kbd "SPC")))))
- (when (keymapp leader-km)
- (setq eaf-evil-leader-keymap leader-km))))
-
- ;; Process sentinel: clean up buffers when the EAF python
- ;; process dies unexpectedly instead of leaving zombies.
- (advice-add 'eaf--start-process :after
- (lambda (&rest _)
- (when-let* ((proc (get-process "eaf")))
- (set-process-sentinel
- proc
- (lambda (process event)
- (when (memq (process-status process) '(exit signal))
- (message "EAF process terminated: %s" (string-trim event))
- (dolist (buf (buffer-list))
- (when (and (buffer-live-p buf)
- (with-current-buffer buf
- (derived-mode-p 'eaf-mode)))
- (with-current-buffer buf
- (let ((inhibit-read-only t))
- (erase-buffer)
- (insert (format "EAF process crashed: %s\nRe-open this buffer to restart."
- (string-trim event)))))))))))))
-
- (setq my/eaf-loaded t)
- (message "EAF loaded successfully."))
- (error
- (message "EAF failed to load: %s" (error-message-string err))))))
-
-;; Daemon: wait for the first graphical frame, then load EAF once.
-;; Normal start: load immediately.
-(if (daemonp)
- (add-hook 'server-after-make-frame-hook #'my/eaf-init)
- (add-hook 'after-init-hook #'my/eaf-init))
-#+end_src
-
* olivetti
#+begin_src emacs-lisp
@@ -3503,47 +3501,6 @@ every =emacsclient -c= frame gets its own set of perspectives.
(consult-customize consult--source-buffer :hidden t :default nil)
(add-to-list 'consult-buffer-sources persp-consult-source))
- ;; ---- EAF workspace helpers ------------------------------------
-
- (defun persp-eaf-open (persp-name eaf-open-fn)
- "Switch to PERSP-NAME and call EAF-OPEN-FN unless a live EAF buffer exists."
- (unless my/eaf-loaded
- (user-error "EAF is not loaded -- open a graphical frame first"))
- (persp-switch persp-name)
- (unless (cl-some (lambda (buf)
- (and (buffer-live-p buf)
- (with-current-buffer buf
- (derived-mode-p 'eaf-mode))))
- (persp-buffers (persp-curr)))
- (condition-case err
- (funcall eaf-open-fn)
- (error
- (message "EAF open failed in perspective '%s': %s"
- persp-name (error-message-string err))))))
-
- (defun persp-eaf-terminal ()
- "Open pyqterminal in the terminal perspective."
- (interactive)
- (persp-eaf-open "terminal" #'eaf-open-pyqterminal))
-
- (defun persp-eaf-auralis ()
- "Open the auralis dashboard in the auralis perspective."
- (interactive)
- (persp-eaf-open "auralis" #'eaf-open-auralis))
-
- ;; Reposition Qt widgets in ALL windows after a perspective switch,
- ;; not just the current buffer. Without this, EAF overlays in
- ;; non-selected windows become misaligned.
- (add-hook 'persp-activated-hook
- (lambda ()
- (when (fboundp 'eaf-monitor-configuration-change)
- (walk-windows
- (lambda (win)
- (with-current-buffer (window-buffer win)
- (when (derived-mode-p 'eaf-mode)
- (eaf-monitor-configuration-change))))
- nil (selected-frame)))))
-
;; ---- keybindings via general ----------------------------------
(my-leader
@@ -3556,104 +3513,10 @@ every =emacsclient -c= frame gets its own set of perspectives.
"TAB b" '(persp-switch-to-buffer :wk "buffer")
"TAB B" '(consult-buffer :wk "global buffer")
"TAB a" '(persp-add-buffer :wk "add buffer")
- "TAB k" '(persp-remove-buffer :wk "remove buffer")
- "TAB 1" '(persp-eaf-terminal :wk "terminal")
- "TAB 2" '(persp-eaf-auralis :wk "auralis")))
+ "TAB k" '(persp-remove-buffer :wk "remove buffer")))
#+end_src
-* eaf-frames
-
-Open EAF apps in dedicated frames (independent WM windows). Each frame
-is named so it can be found and focused instead of spawning duplicates.
-Useful for tiling EAF apps on separate monitors via mango keybinds.
-
-#+begin_src emacs-lisp
-(defvar eaf-named-frames nil
- "Alist of (NAME . frame) for dedicated EAF frames.")
-(defun eaf-frame-open (name open-fn &optional width height)
- "Open or focus a dedicated frame NAME running EAF app via OPEN-FN.
-If OPEN-FN signals an error the newly created frame is deleted
-so no empty zombie frames are left behind."
- (interactive)
- (unless my/eaf-loaded
- (user-error "EAF is not loaded -- open a graphical frame first"))
- (let ((existing (alist-get name eaf-named-frames nil nil #'string=)))
- (if (and existing (frame-live-p existing))
- (select-frame-set-input-focus existing)
- ;; Remove stale entry
- (setq eaf-named-frames
- (assoc-delete-all name eaf-named-frames #'string=))
- (let ((frame (make-frame `((name . ,(format "EAF: %s" name))
- (width . ,(or width 120))
- (height . ,(or height 40))))))
- (select-frame-set-input-focus frame)
- (condition-case err
- (progn
- (push (cons name frame) eaf-named-frames)
- (funcall open-fn))
- (error
- (setq eaf-named-frames
- (assoc-delete-all name eaf-named-frames #'string=))
- (delete-frame frame)
- (message "EAF frame '%s' failed: %s" name
- (error-message-string err))))))))
-
-(defun eaf-frame-pyqterminal ()
- "Open pyqterminal in a dedicated frame."
- (interactive)
- (eaf-frame-open "terminal" #'eaf-open-pyqterminal))
-
-(defun eaf-frame-auralis ()
- "Open auralis dashboard in a dedicated frame."
- (interactive)
- (eaf-frame-open "auralis" #'eaf-open-auralis))
-
-(defun eaf-frame-browser (url)
- "Open EAF browser in a dedicated frame."
- (interactive "sURL: ")
- (eaf-frame-open "browser" (lambda () (eaf-open-browser url))))
-
-(my-leader
- "o" '(:ignore t :wk "open frame")
- "ot" '(eaf-frame-pyqterminal :wk "terminal")
- "oa" '(eaf-frame-auralis :wk "auralis")
- "ob" '(eaf-frame-browser :wk "browser"))
-#+end_src
-
-* auralis music controls
-
-Native Emacs keybindings for auralis music player control. These work
-globally (no EAF buffer required) by calling the auralis API directly.
-Includes modeline now-playing indicator.
-
-#+begin_src emacs-lisp
-;; Load auralis unconditionally — it only makes HTTP calls and has no
-;; EAF dependency. Auto-enable the modeline polling timer on load.
-(condition-case err
- (progn
- (require 'auralis)
- (auralis-modeline-enable))
- (error (message "auralis failed to load: %s" (error-message-string err))))
-
-(my-leader
- "m" '(:ignore t :wk "music")
- "m SPC" '(auralis-play-pause :wk "play/pause")
- "mn" '(auralis-next :wk "next track")
- "mp" '(auralis-prev :wk "prev track")
- "ms" '(auralis-search :wk "search library")
- "mr" '(auralis-play-random :wk "shuffle all")
- "mi" '(auralis-now-playing :wk "now playing")
- "m+" '(auralis-volume-up :wk "volume up")
- "m-" '(auralis-volume-down :wk "volume down")
- "mS" '(auralis-toggle-shuffle :wk "toggle shuffle")
- "mR" '(auralis-toggle-repeat :wk "toggle repeat")
- "mA" '(auralis-toggle-radio :wk "toggle auto-radio")
- "mq" '(auralis-stop :wk "stop")
- "mL" '(auralis-scan-library :wk "scan library")
- "mm" '(auralis-modeline-enable :wk "modeline on")
- "mM" '(auralis-modeline-disable :wk "modeline off"))
-#+end_src
* nerd icons
@@ -4152,16 +4015,6 @@ Includes modeline now-playing indicator.
(desc (tmr-mode-line--format-description timer)))
(concat remaining desc)))
- (maple-modeline-define-segment auralis-now-playing
- :if (and (boundp 'auralis--modeline-string)
- (not (string-empty-p auralis--modeline-string)))
- :format
- (concat
- (maple-modeline--concat-or
- (maple-modeline--icon 'mdicon "nf-md-music" face)
- "♪")
- auralis--modeline-string))
-
(maple-modeline-define my-custom-style
:left ((evil :left (bar :left ""))
macro
@@ -4173,7 +4026,6 @@ Includes modeline now-playing indicator.
:right (narrow
org-clock-task
tmr-timer
- auralis-now-playing
python
my-modeline-time
process
@@ -5168,7 +5020,6 @@ Includes modeline now-playing indicator.
"d" '(dashboard-open :wk "dashboard")
"f" '(dirvish :wk "file manager")
"t" '(multi-vterm :wk "terminal")
- "T" '(eaf-open-pyqterminal :wk "eaf terminal")
"z" '(golden-ratio-mode :wk "zoom/golden ratio")
"i" '(persp-irc :wk "irc")
"m" '(mu4e :wk "mail")
@@ -5451,31 +5302,126 @@ this package provides the ability to display rgb and hex colors.
* ewm
#+begin_src emacs-lisp
-(when (string= (system-name) "sportmacher")
- (use-package ewm
- :straight nil
- :commands (ewm-start-module ewm-launch-app ewm-toggle-fullscreen
- ewm-screenshot ewm-show-state ewm-debug-mode
- ewm-lock-session ewm-next-surface-buffer ewm-prev-surface-buffer
- ewm-transient ewm-stop-module)
- :custom
- (ewm-mouse-follows-focus t)
- (ewm-output-config '(("DSI-1" :width 800 :height 1280 :transform 3 :scale 1.0)))
- (ewm-input-config '((keyboard :repeat-delay 200 :repeat-rate 25)))
- (ewm-idle 300)
- :config
- (require 'ewm-transient)
+(with-eval-after-load 'ewm
+ (require 'ewm-transient)
+
+ ;; ── Bugfix: guard against nil output in pointer functions ──
+ ;; ewm-input--pointer-in-window-p crashes with "stringp, nil" when
+ ;; called on a frame without ewm-output (e.g. the initial daemon frame
+ ;; during startup when dashboard does switch-to-buffer).
+ (defun ewm-input--pointer-in-window-p (window)
+ "Return non-nil if pointer is inside WINDOW. Safe for non-EWM frames."
+ (let* ((frame (window-frame window))
+ (output (frame-parameter frame 'ewm-output)))
+ (when output
+ (let* ((output-offset (ewm--get-output-offset output))
+ (edges (window-inside-pixel-edges window))
+ (left (+ (car output-offset) (nth 0 edges)))
+ (top (+ (cdr output-offset) (nth 1 edges)))
+ (right (+ (car output-offset) (nth 2 edges)))
+ (bottom (+ (cdr output-offset) (nth 3 edges)))
+ (pointer (ewm-get-pointer-location))
+ (px (car pointer))
+ (py (cdr pointer)))
+ (and (<= left px right)
+ (<= top py bottom))))))
+
+ (defun ewm-input--warp-pointer-to-window (window)
+ "Warp pointer to center of WINDOW. Safe for non-EWM frames."
+ (unless (minibufferp (window-buffer window))
+ (let* ((frame (window-frame window))
+ (output (frame-parameter frame 'ewm-output)))
+ (when (and output (not (ewm-input--pointer-in-window-p window)))
+ (let* ((output-offset (ewm--get-output-offset output))
+ (edges (window-inside-pixel-edges window))
+ (x (+ (car output-offset) (/ (+ (nth 0 edges) (nth 2 edges)) 2)))
+ (y (+ (cdr output-offset) (/ (+ (nth 1 edges) (nth 3 edges)) 2))))
+ (ewm-warp-pointer (float x) (float y)))))))
+
+ ;; ── Settings ─────────────────────────────────────────────
+ (setq ewm-mouse-follows-focus t)
+ (setq ewm-input-config '((keyboard :repeat-delay 200 :repeat-rate 25)))
+ (setq ewm-idle '(300 . "swaylock -f -c 333333"))
+
+ ;; Per-host output config
+ (pcase (system-name)
+ ("md"
+ (setq ewm-output-config
+ '(("DP-1" :x 0 :y 0 :refresh 144))))
+ ("sportmacher"
+ (setq ewm-output-config
+ '(("DSI-1" :width 800 :height 1280 :transform 3 :scale 1.0)))))
+
+ ;; ── Programm-Launcher ──────────────────────────────────────
+ (define-key ewm-mode-map (kbd "s-<return>")
+ (lambda () (interactive) (start-process "kitty" nil "kitty")))
+ (define-key ewm-mode-map (kbd "M-s-7")
+ (lambda (command)
+ (interactive (list (read-shell-command "$ ")))
+ (start-process-shell-command command nil command)))
+ (define-key ewm-mode-map (kbd "s-SPC")
+ (lambda () (interactive) (start-process "vicinae" nil "vicinae" "toggle")))
+ (define-key ewm-mode-map (kbd "s-b")
+ (lambda () (interactive) (start-process "helium" nil "helium-browser")))
+ (define-key ewm-mode-map (kbd "s-p")
+ (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/tinkerbells/pass/pass")))
+ (define-key ewm-mode-map (kbd "s-v")
+ (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/clipboard/history")))
+ (define-key ewm-mode-map (kbd "s-s")
+ (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/vicinae/power")))
+ (define-key ewm-mode-map (kbd "s-d")
+ (lambda () (interactive) (start-process "vicinae" nil "vicinae" "vicinae://extensions/cashmere/deepl-improve/deepl-improve")))
+ (define-key ewm-mode-map (kbd "M-s")
+ (lambda () (interactive) (start-process "snipe" nil "snipe")))
+ (define-key ewm-mode-map (kbd "s-<print>")
+ (lambda () (interactive)
+ (start-process-shell-command "screenshot" nil "grim -g \"$(slurp)\" - | wl-copy")))
+ (define-key ewm-mode-map (kbd "<print>")
+ (lambda () (interactive)
+ (start-process-shell-command "screenshot-pb" nil
+ "grim -g \"$(slurp)\" - | ssh pb@pb.cashmere.rs -- -ext png 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+' | sed 's/$/?r=1/' | wl-copy")))
+
+ ;; ── Window Management ────────────────────────────────────
+ (define-key ewm-mode-map (kbd "s-h") #'windmove-left)
+ (define-key ewm-mode-map (kbd "s-j") #'windmove-down)
+ (define-key ewm-mode-map (kbd "s-k") #'windmove-up)
+ (define-key ewm-mode-map (kbd "s-l") #'windmove-right)
+ (define-key ewm-mode-map (kbd "s-q") #'kill-current-buffer)
+ (define-key ewm-mode-map (kbd "s-f") #'ewm-toggle-fullscreen)
+ (define-key ewm-mode-map (kbd "s-S-q") #'ewm-stop-module)
+
+ ;; tab switching s-0..s-9 (s-0 = tab 10)
+ (dotimes (i 10)
+ (define-key ewm-mode-map
+ (kbd (format "s-%d" i))
+ (let ((n i))
+ (lambda () (interactive) (tab-bar-select-tab (if (= n 0) 10 n))))))
- ;; vim-style window nav on super key (works inside surface buffers)
- (when (and (boundp 'ewm-mode-map) (keymapp ewm-mode-map))
- (define-key ewm-mode-map (kbd "s-h") #'windmove-left)
- (define-key ewm-mode-map (kbd "s-j") #'windmove-down)
- (define-key ewm-mode-map (kbd "s-k") #'windmove-up)
- (define-key ewm-mode-map (kbd "s-l") #'windmove-right)
- (define-key ewm-mode-map (kbd "s-q") #'kill-current-buffer)
- (define-key ewm-mode-map (kbd "s-S-q") #'ewm-stop-module)))
+ ;; ── Media Keys ───────────────────────────────────────────
+ (define-key ewm-mode-map (kbd "<XF86AudioLowerVolume>")
+ (lambda () (interactive) (start-process "vol-" nil "pamixer" "-d" "5")))
+ (define-key ewm-mode-map (kbd "<XF86AudioRaiseVolume>")
+ (lambda () (interactive) (start-process "vol+" nil "pamixer" "-i" "5")))
+ (define-key ewm-mode-map (kbd "<XF86AudioMute>")
+ (lambda () (interactive) (start-process "mute" nil "pamixer" "--toggle-mute")))
+ (define-key ewm-mode-map (kbd "<XF86AudioMicMute>")
+ (lambda () (interactive)
+ (start-process-shell-command "micmute" nil
+ "pactl list sources short | grep input | awk '{print $1}' | xargs -I{} pactl set-source-mute {} toggle")))
+ (define-key ewm-mode-map (kbd "<XF86AudioPlay>")
+ (lambda () (interactive) (start-process "play" nil "playerctl" "play-pause")))
+ (define-key ewm-mode-map (kbd "<XF86AudioMedia>")
+ (lambda () (interactive) (start-process "play" nil "playerctl" "play-pause")))
+ (define-key ewm-mode-map (kbd "<XF86AudioNext>")
+ (lambda () (interactive) (start-process "next" nil "playerctl" "next")))
+ (define-key ewm-mode-map (kbd "<XF86AudioPrev>")
+ (lambda () (interactive) (start-process "prev" nil "playerctl" "previous")))
+ (define-key ewm-mode-map (kbd "<XF86MonBrightnessUp>")
+ (lambda () (interactive) (start-process "bri+" nil "brightnessctl" "set" "+5%")))
+ (define-key ewm-mode-map (kbd "<XF86MonBrightnessDown>")
+ (lambda () (interactive) (start-process "bri-" nil "brightnessctl" "set" "5%-")))
- ;; ewm leader bindings under SPC e
+ ;; ── Leader bindings under SPC e ──────────────────────────
(my-leader
"e" '(:ignore t :wk "ewm")
"e a" '(ewm-launch-app :wk "launch app")