From 18e2c0edca4e686356c70ec381f8f76ce4616085 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 29 Jun 2026 18:25:16 -0400 Subject: fix(org-capture): reap stray popup frames reliably The Super+N quick-capture popup frame is named "org-capture" and was torn down by deleting the selected frame on capture exit. When the daemon's selected frame was something else at finalize (common with multiple frames), the real capture frame survived and lingered, showing whatever buffer was behind it. Reap by frame name across all frames instead, sparing any popup still mid-capture (*Org Select* or a CAPTURE-* buffer), and expose cj/org-capture-reap-popup-frames for manual cleanup. --- modules/org-capture-config.el | 47 +++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) (limited to 'modules') diff --git a/modules/org-capture-config.el b/modules/org-capture-config.el index 9f5bfbe7f..14fb8e582 100644 --- a/modules/org-capture-config.el +++ b/modules/org-capture-config.el @@ -345,22 +345,43 @@ Captured On: %U" :prepend t) ) ;; end use-package org-protocol ;; ---------------------- Popup Capture Frame Auto-Close ---------------------- -;; The quick-capture script (Hyprland Super+Shift+N) opens an emacsclient +;; The quick-capture script (Hyprland Super+N) opens an emacsclient ;; frame named "org-capture"; Hyprland window rules float and center it by ;; that name. These hooks close the frame when the capture finalizes or ;; aborts, so the popup never lingers. Frames not named "org-capture" are ;; untouched — normal in-Emacs captures keep their windows. -(defun cj/org-capture--popup-frame-p () - "Return non-nil when the selected frame is the quick-capture popup." - (equal (frame-parameter nil 'name) "org-capture")) - -(defun cj/org-capture--delete-popup-frame () - "Delete the current frame when it is the quick-capture popup." - (when (cj/org-capture--popup-frame-p) - (delete-frame))) - -(add-hook 'org-capture-after-finalize-hook #'cj/org-capture--delete-popup-frame) +(defun cj/org-capture--frame-reapable-p (frame-name buffer-names) + "Non-nil when a frame named FRAME-NAME showing BUFFER-NAMES is a reapable popup. +Reapable means the quick-capture popup (FRAME-NAME equal to \"org-capture\") with +no capture UI left in any window — no *Org Select* menu and no CAPTURE-* buffer. +A popup still mid-capture has capture UI and is not reapable, so it is spared." + (and (equal frame-name "org-capture") + (not (seq-some (lambda (b) + (cj/org-capture--popup-sole-window-p frame-name b)) + buffer-names)))) + +(defun cj/org-capture-reap-popup-frames () + "Delete every quick-capture popup frame that no longer shows capture UI. +Reaps across ALL frames, not just the selected one: a capture that finalizes, +aborts, or errors while the daemon's selected frame is something else (the common +multi-frame case) still cleans up its \"org-capture\" popup, while a popup +mid-capture is spared. Never deletes the last remaining frame. Safe to call +anytime — bound to nothing, run via M-x when a stray popup needs clearing." + (interactive) + (dolist (f (frame-list)) + (when (and (frame-live-p f) + (cdr (frame-list)) ; never delete the last frame + (cj/org-capture--frame-reapable-p + (frame-parameter f 'name) + (mapcar (lambda (w) (buffer-name (window-buffer w))) + (window-list f 'no-minibuf)))) + (delete-frame f)))) + +;; Reap on every capture exit. `remove-hook' first so a live module reload swaps +;; the retired narrow (selected-frame) handler for this one without leaving both. +(remove-hook 'org-capture-after-finalize-hook #'cj/org-capture--delete-popup-frame) +(add-hook 'org-capture-after-finalize-hook #'cj/org-capture-reap-popup-frames) ;; The popup opens a fresh emacsclient frame still showing the daemon's last ;; buffer. `org-mks' shows the *Org Select* menu via @@ -439,9 +460,9 @@ daemon's main frame and the capture would otherwise land there." (when frame (select-frame-set-input-focus frame)) (let ((org-capture-templates (cj/--quick-capture-template inbox-file))) (org-capture nil "t"))) - (quit (cj/org-capture--delete-popup-frame)) + (quit (cj/org-capture-reap-popup-frames)) (error (message "Quick-capture: %s" (error-message-string err)) - (cj/org-capture--delete-popup-frame))))) + (cj/org-capture-reap-popup-frames))))) (provide 'org-capture-config) ;;; org-capture-config.el ends here. -- cgit v1.2.3