Floating window fix, dynamic keymap hints

This commit is contained in:
Ian Keane 2026-08-10 07:35:24 -04:00
parent f70c35af6d
commit 030a4fb510

View file

@ -33,6 +33,8 @@
(setf *ignore-wm-inc-hints* t)
(set-font "-b&h-luxi mono-medium-r-normal--20-*-*-*-m-0-iso10646-1")
(setf *message-window-padding* 16)
(setf *message-window-margin* 20)
(setf *message-window-y-margin* 20)
(setf *message-window-gravity* :center)
(setf *input-window-gravity* :center)
@ -47,12 +49,20 @@
(setf *new-frame-action* :empty)
(setf *suppress-frame-indicator* t)
;;; Groups — must be set up before define-frame-preference so names match
(grename "1")
(run-commands "gnewbg 2" "gnewbg 3" "gnewbg 4")
;;; Window placement policy
(define-frame-preference "Default"
(0 t nil :class "Konqueror" :role "...konqueror-mainwindow")
(1 t nil :class "XTerm")
(:float t t :class "alsamixer-scratch")
(:float t t :class "ncmpcpp-scratch"))
;; "Default" is the initial group name before grename runs at the bottom.
;; These float rules must use "Default" — StumpWM evaluates frame preferences
;; at window-open time against the group name, and the first group starts as
;; "Default" until grename changes it at startup (but the rule is registered
;; at load time when the name is still being matched by content, not snapshot).
;; In practice (:float t t) with target-group matching works correctly here.
(define-frame-preference nil
(:float t nil :class "alsamixer-scratch")
(:float t nil :class "ncmpcpp-scratch"))
(define-frame-preference "scratch-emacs-1"
(0 t t :title "scratch-emacs-1"))
@ -243,23 +253,40 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it."
;; This causes other windows to bleed through when transparency is used.
;; This hook hides all frame siblings when focus changes so only the
;; focused window and the wallpaper are visible through any transparency.
(add-hook *focus-window-hook*
(lambda (new old)
(declare (ignore old))
(when new
(let* ((group (window-group new))
(frame (window-frame new))
(siblings (remove new (frame-windows group frame))))
(mapc #'hide-window siblings)
(unhide-window new)))))
;; Named function (not lambda) so loadrc replaces it instead of accumulating copies.
(defun hide-unfocused-frame-siblings (new old)
(declare (ignore old))
(when (and new (typep new 'stumpwm::tile-window))
(let* ((group (window-group new))
(frame (window-frame new))
(siblings (remove new (frame-windows group frame))))
(mapc #'hide-window siblings)
(unhide-window new))))
(remove-hook *focus-window-hook* 'hide-unfocused-frame-siblings)
(add-hook *focus-window-hook* 'hide-unfocused-frame-siblings)
(defun float-window-to-current-group (win)
"Move a float window to the current group by directly manipulating group membership,
bypassing group-add-window (which would retrigger frame preferences and group switches)."
(let ((current (current-group))
(old (window-group win)))
(unless (eq old current)
(setf (group-windows old) (remove win (group-windows old)))
(setf (window-group win) current)
(push win (group-windows current)))))
(defun spawn-or-focus (class command)
"Unhide or focus existing window with CLASS, or spawn COMMAND if none exists."
"Unhide or focus existing window with CLASS, or spawn COMMAND if none exists.
Float windows are moved to the current group directly to avoid group switches."
(let ((win (find-if (lambda (w) (string= (window-class w) class))
(screen-windows (current-screen)))))
(cond ((null win) (run-shell-command command))
((eq win (current-window)) nil)
(t (focus-window win)))))
(t
(when (typep win 'stumpwm::float-window)
(float-window-to-current-group win))
(focus-window win)))))
(defcommand smart-kill () ()
"Kill window normally, unless it's a persistent float — then hide it instead."
@ -285,10 +312,19 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it."
(defcommand ncmpcpp-float () ()
"Open ncmpcpp in a persistent floating wezterm window."
(spawn-or-focus "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp"))
;;; Config keymap
;;; Appearance sub-map (s-W t / s-W T)
(defvar *appearance-map* (make-sparse-keymap))
(spawn-or-focus "ncmpcpp-scratch" "wezterm start --class ncmpcpp-scratch -- ncmpcpp" :persistent t))
;;; Keymaps
;;
;; All named keymaps are defined here. Add a keymap to *hint-keymaps* to make
;; it auto-display its bindings in the top-left corner when its prefix key is
;; pressed, instead of requiring '?'. Remove from the list to disable hints.
(defvar *config-map* (make-sparse-keymap))
(defvar *brightness-map* (make-sparse-keymap))
(defvar *gaps-map* (make-sparse-keymap))
(defvar *window-map* (make-sparse-keymap))
(defvar *media-map* (make-sparse-keymap))
(defvar *appearance-map* (make-sparse-keymap))
(defvar *appearance-all-map* (make-sparse-keymap))
;; Global opacity — stored so new windows can inherit it via *new-window-hook*
@ -361,12 +397,39 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it."
(define-key *appearance-all-map* (kbd "8") "global-opacity-80")
(define-key *appearance-all-map* (kbd "9") "global-opacity-90")
(define-key *appearance-all-map* (kbd "0") "global-opacity-100")
(defvar *brightness-map* (make-sparse-keymap))
(defvar *gaps-map* (make-sparse-keymap))
(defvar *window-map* (make-sparse-keymap))
(defvar *media-map* (make-sparse-keymap))
;; Keymaps that automatically show their bindings when activated.
;; Add or remove keymaps here to control which ones show hints.
(defvar *hint-keymaps*
(list *config-map*
*window-map*
*media-map*
*brightness-map*
*gaps-map*
*appearance-map*
*appearance-all-map*))
;; Show bindings in top-left for any keymap in *hint-keymaps*.
(add-hook *key-press-hook*
(lambda (key key-seq cmd)
(declare (ignore key cmd))
(let* ((oriented (reverse key-seq))
(maps (get-kmaps-at-key-seq (dereference-kmaps (top-maps)) oriented)))
(when-let ((hint-maps (remove-if-not
(lambda (m) (member m *hint-keymaps*))
maps)))
(let ((*message-window-gravity* :top-left))
(apply #'display-bindings-for-keymaps oriented hint-maps))))))
;;; Config map bindings
(define-key *config-map* (kbd "a") "alsamixer-float")
(define-key *config-map* (kbd "b") *brightness-map*)
(define-key *window-map* (kbd "t") *appearance-map*)
(define-key *window-map* (kbd "T") *appearance-all-map*)
(defvar *brightness-map* (make-sparse-keymap))
;;; Brightness map bindings
(define-key *brightness-map* (kbd "1") "brightness-10")
(define-key *brightness-map* (kbd "2") "brightness-20")
(define-key *brightness-map* (kbd "3") "brightness-30")
@ -377,21 +440,20 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it."
(define-key *brightness-map* (kbd "8") "brightness-80")
(define-key *brightness-map* (kbd "9") "brightness-90")
(define-key *brightness-map* (kbd "0") "brightness-100")
(define-key *config-map* (kbd "b") *brightness-map*)
;;; Keymaps
(defvar *gaps-map* (make-sparse-keymap))
;;; Gaps map bindings
(define-key *gaps-map* (kbd "0") "gaps-off")
(define-key *gaps-map* (kbd "1") "gaps-small")
(define-key *gaps-map* (kbd "2") "gaps-medium")
(define-key *gaps-map* (kbd "3") "gaps-large")
(defvar *window-map* (make-sparse-keymap))
;;; Window map bindings
(define-key *window-map* (kbd "r") "iresize")
(define-key *window-map* (kbd "g") *gaps-map*)
(define-key *window-map* (kbd "t") *appearance-map*)
(define-key *window-map* (kbd "T") *appearance-all-map*)
(defvar *media-map* (make-sparse-keymap))
;;; Media map bindings
(define-key *media-map* (kbd "v") "mpv-pick")
(define-key *media-map* (kbd "m") "mpv-url")
(define-key *media-map* (kbd "d") "yt-download")
@ -415,10 +477,6 @@ If PERSISTENT is t, smart-kill will hide the window instead of killing it."
(define-key *top-map* (kbd "s-W") *window-map*)
(define-key *top-map* (kbd "s-m") *media-map*)
;;; Groups
(grename "1")
(run-commands "gnewbg 2" "gnewbg 3" "gnewbg 4")
;;; Group switching
(define-key *top-map* (kbd "s-1") "gselect 1")
(define-key *top-map* (kbd "s-2") "gselect 2")