From a64efec92039045d0c845c22901c1c1dccbb563c Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Sat, 5 Sep 2026 20:54:09 -0400 Subject: [PATCH] Emacs window navigation stumpsom integration --- config/stumpwm/config.lisp | 21 ++++++++++++++++---- emacs-config.org | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/config/stumpwm/config.lisp b/config/stumpwm/config.lisp index 71706fc..d6f12d4 100644 --- a/config/stumpwm/config.lisp +++ b/config/stumpwm/config.lisp @@ -462,10 +462,23 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it." ;;; Top-level bindings (define-key *top-map* (kbd "s-d") "rofi-run") (define-key *top-map* (kbd "s-w") "rofi-pull-window") -(define-key *top-map* (kbd "s-h") "move-focus left") -(define-key *top-map* (kbd "s-j") "move-focus down") -(define-key *top-map* (kbd "s-k") "move-focus up") -(define-key *top-map* (kbd "s-l") "move-focus right") +(defun emacs-window-p (win) + (and win (string= (window-class win) "Emacs"))) + +(defcommand smart-move-focus (direction) ((:direction "Direction: ")) + "If the focused window is Emacs and it has a window in DIRECTION, move + focus there via windmove. Otherwise move focus to the next StumpWM frame." + (if (and (emacs-window-p (current-window)) + (search "t" (run-shell-command + (format nil "emacsclient -e '(if (ignore-errors (windmove-~(~a~) nil) t) t nil)'" direction) + t))) + nil + (move-focus direction))) + +(define-key *top-map* (kbd "s-h") "smart-move-focus left") +(define-key *top-map* (kbd "s-j") "smart-move-focus down") +(define-key *top-map* (kbd "s-k") "smart-move-focus up") +(define-key *top-map* (kbd "s-l") "smart-move-focus right") (define-key *top-map* (kbd "s-p") "next-in-frame") (define-key *top-map* (kbd "s-n") "prev-in-frame") (define-key *top-map* (kbd "s-q") "smart-kill") diff --git a/emacs-config.org b/emacs-config.org index 46fbba6..ec5cd7d 100644 --- a/emacs-config.org +++ b/emacs-config.org @@ -428,6 +428,45 @@ at the tab level rather than with a dedicated workspace package. (ace-window arg) )) #+end_src + +*** StumpWM-aware window navigation +=s-h/j/k/l= (mod+hjkl) move between Emacs windows using =windmove=. When +there's no further Emacs window in that direction (i.e. we're at the edge of +Emacs's frame), the command instead shells out to StumpWM via =stumpish= to +move focus to the next tiled frame in that direction — so navigation flows +seamlessly out of Emacs and into the rest of the WM using the same keys +StumpWM itself binds to =s-h/j/k/l=. + +#+begin_src emacs-lisp + (defvar stumpwm-stumpish-path + (expand-file-name "~/.stumpwm.d/modules/util/stumpish/stumpish") + "Path to the stumpish binary used to talk to a running StumpWM.") + + (defun stumpwm-move-focus (direction) + "Ask StumpWM to move focus to the frame in DIRECTION (\"left\" \"right\" \"up\" \"down\"). + No-op on macOS (or anywhere StumpWM isn't running/available)." + (when (and (not IS-MACOS) + (file-executable-p stumpwm-stumpish-path)) + (call-process stumpwm-stumpish-path nil nil nil + (concat "move-focus " direction)))) + + (defun stumpwm-windmove-or-escape (windmove-fn direction) + "Call WINDMOVE-FN; if it errors (no Emacs window in DIRECTION), + fall through to StumpWM and move focus to the next tiled frame there." + (condition-case nil + (funcall windmove-fn) + (error (stumpwm-move-focus direction)))) + + (defun stumpwm-windmove-left () (interactive) (stumpwm-windmove-or-escape #'windmove-left "left")) + (defun stumpwm-windmove-right () (interactive) (stumpwm-windmove-or-escape #'windmove-right "right")) + (defun stumpwm-windmove-up () (interactive) (stumpwm-windmove-or-escape #'windmove-up "up")) + (defun stumpwm-windmove-down () (interactive) (stumpwm-windmove-or-escape #'windmove-down "down")) + + (global-set-key (kbd "s-h") #'stumpwm-windmove-left) + (global-set-key (kbd "s-j") #'stumpwm-windmove-down) + (global-set-key (kbd "s-k") #'stumpwm-windmove-up) + (global-set-key (kbd "s-l") #'stumpwm-windmove-right) +#+end_src ** Popper Transient/popup buffers — terminals, help, compilation, messages — are managed by popper, which tracks them as a group and lets you cycle or dismiss them