From 192d4fc1f7b65cd7ffe3502c69aee566219d3ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Marks?= Date: Thu, 5 Mar 2026 09:58:03 +0100 Subject: [PATCH] Make 'frame-use-time' more reliable. (bug#80397) Walking the window tree is more reliable than using the selected window. Implement 'get-mru-frame' on top of 'get-mru-frames' using 'frame-use-time'. * lisp/frame.el (frame-use-time): Change to walk the window tree for the specified frame. (get-mru-frames): New defun. (get-mru-frame): Change to call 'get-mru-frames'. * doc/lispref/frames.texi: Update documentation. * etc/NEWS: Update announcement. --- doc/lispref/frames.texi | 81 +++++++++++++++++++++++++++-------------- etc/NEWS | 12 +++--- lisp/frame.el | 72 ++++++++++++++++++++++-------------- 3 files changed, 105 insertions(+), 60 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 58708cb8eb1..d57d643e922 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -3188,21 +3188,32 @@ could switch to a different terminal without switching back when you're done. @end deffn -@cindex most recently used frame -@defun get-mru-frame &optional all-frames exclude +@cindex frame use time +@cindex use time of frame +@cindex frame order by time of last use +@defun frame-use-time &optional frame -This function is like @code{get-mru-window}, but it returns the most -recently used frame. The @dfn{most recently used frame} is the frame -with the most recently selected window as computed by -@code{get-mru-window}. (@pxref{Selecting Windows}) +This function returns the use time of frame @var{frame}. @var{frame} +must be a live frame and defaults to the selected one. -This function will return @code{nil} if no candidate frames are found, -for example, if there is only one frame. Tooltip, minibuffer only, and -child frames are never candidates. +The @dfn{use time of a frame} is the highest use time as reported by +@code{window-use-time} of any window on @var{frame}. +@end defun -Since in practice the most recently used frame is always the selected -one, it usually makes sense to call this function with a non-@code{nil} -@var{exclude} argument with, for example, the selected frame. +@defun get-mru-frame &optional all-frames exclude-child-frames exclude-frame + +This function returns the frame with the highest use time as reported by +@code{frame-use-time}. It returns @code{nil} if no candidate frames are +found which usually happens if frames are excluded with the help of the +optional arguments. + +By default, tooltip and minibuffer-only frames are never candidates. If +the optional argument @var{exclude-child-frames} is non-nil, child +frames are excluded too. The @var{exclude-frame} argument, if present, +excludes the frame it specifies too. Since in practice the most +recently used frame is always the selected one, it usually makes sense +to call this function with a non-@code{nil} @var{exclude-frame} argument +specifying the selected frame. The optional argument @var{all-frames} specifies which frames to consider: @@ -3210,31 +3221,47 @@ consider: @itemize @bullet @item @code{visible} means to consider all visible frames on the current terminal or -@var{exclude}'s terminal. +@var{exclude-frame}'s terminal. @item 0 means to consider all visible or iconified frames on the current -terminal or @var{exclude}'s terminal. +terminal or @var{exclude-frame}'s terminal. @item Anything else means to consider all frames. @end itemize - -If @code{get-mru-frame} is called from within the body of -@code{with-selected-frame}, it may yield unexpected results as the most -recently used window will not necessarily match the selected one in the -temporarily selected frame. (@pxref{Selecting Windows}) @end defun -@cindex frame use time -@cindex use time of frame -@cindex frame order by time of last use -@defun frame-use-time &optional frame +@defun get-mru-frames &optional all-frames exclude-child-frames exclude-frame -This function returns the use time of frame @var{frame}. @var{frame} -must be a live frame and defaults to the selected one. The result is -the @code{window-use-time} of @var{frame}'s most recently used window -computed by @code{get-mru-window}. (@pxref{Selecting Windows}) +This function returns a list of frames sorted by highest use time as +reported by @code{frame-use-time} which is computed using each frame's +most recently used window. + +By default, tooltip and minibuffer-only frames are never candidates. If +the optional argument @var{exclude-child-frames} is non-nil, child +frames are excluded too. The @var{exclude-frame} argument, if present, +excludes the frame it specifies too. + +It can return @code{nil} which can happen if frames are excluded with +the help of the optional arguments, for example, if there is a single +frame and @var{exclude-frame} is the selected frame. + +The optional argument @var{all-frames} specifies which frames to +consider: + +@itemize @bullet +@item @code{visible} +means to consider all visible frames on the current terminal or +@var{exclude-frame}'s terminal. + +@item 0 +means to consider all visible or iconified frames on the current +terminal or @var{exclude-frame}'s terminal. + +@item Anything else +means to consider all frames. +@end itemize @end defun @deffn Command select-frame-by-id id &optional noerror diff --git a/etc/NEWS b/etc/NEWS index 6286c4ec526..c23265a0d19 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -507,12 +507,12 @@ for a window. The result is the `window-use-time' of a frame's most recently used window. +++ -*** New function 'get-mru-frame'. -This function returns the most recently used frame from among all -frames, or those visible or iconified on the same terminal as the -selected frame. A frame can be excluded, for example, the selected -frame, as it will always be the most recently used frame on the current -terminal. +*** New functions 'get-mru-frames' and 'get-mru-frame'. +'get-mru-frames' returns a list of frames sorted by their most recent +use time from among all frames, or those visible or iconified on the +same terminal as the selected frame. Child frames can be excluded. A +single frame can be excluded, for example, the selected frame. +'get-mru-frame' returns the single most recently used frame. --- *** After deleting, 'delete-frame' now selects the most recently used frame. diff --git a/lisp/frame.el b/lisp/frame.el index 4c22d8c0c11..f46231296a6 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2615,41 +2615,43 @@ for FRAME." (defun frame-use-time (&optional frame) "Return FRAME's last use time. -The result is the `window-use-time' of FRAME's most recently used window -computed by `get-mru-window'. If optional FRAME is nil, use the -selected frame." - (window-use-time - ;; Arguments to `get-mru-window' consider all windows. - (get-mru-window (window-normalize-frame frame) t t t))) +The result is the highest `window-use-time' of any window on FRAME. If +optional FRAME is nil, use the selected frame." + (let ((time 0)) + (walk-window-tree + (lambda (window) + (setq time (max time (window-use-time window)))) + frame) + time)) -(defun get-mru-frame (&optional all-frames exclude) - "Return the most recently used frame on frames specified by ALL-FRAMES. -Compute the result from each frame's most recently used window computed -by `get-mru-window', which see. Return nil if no candidate frames are -found, for example, if there is only one frame. Tooltip, minibuffer -only, and child frames are never candidates. Optional argument EXCLUDE, -when non-nil, is a frame to exclude, for example, the selected frame. +(defun get-mru-frames (&optional all-frames + exclude-child-frames + exclude-frame) + "Return a frame list sorted by most recent use and filtered by ALL-FRAMES. +Compute the result using `frame-use-time', which see. Tooltip, and +minibuffer only frames are never candidates. If optional argument +EXCLUDE-CHILD-FRAMES is non-nil, eliminate child frames as candidates. +If EXCLUDE-FRAME is non-nil, it is a frame to exclude, for example, the +selected frame. The following non-nil values of the optional argument ALL-FRAMES have special meanings: - `visible' means consider all visible frames on the current terminal - or EXCLUDE's terminal if non-nil. + or EXCLUDE-FRAME's terminal if EXCLUDE-FRAME is non-nil. - 0 (the number zero) means consider all all visible and iconified - frames on the current terminal or EXCLUDE's terminal if non-nil. + frames on the current terminal or EXCLUDE-FRAME's terminal if + EXCLUDE-FRAME is non-nil. Any other value of ALL-FRAMES means consider all frames." (setq all-frames (or all-frames t)) - (let* ((mru-frame) - (time) - (best-time 0) - (terminal (frame-terminal (or exclude (selected-frame)))) + (let* ((terminal (frame-terminal (or exclude-frame (selected-frame)))) (frame-list (seq-remove (lambda (frame) - (or (eq frame exclude) + (or (eq frame exclude-frame) (eq (frame-parameter frame 'minibuffer) 'only) - (frame-parent frame))) + (and exclude-child-frames (frame-parent frame)))) (cond ((eq all-frames 'visible) (seq-filter (lambda (frame) @@ -2660,12 +2662,28 @@ Any other value of ALL-FRAMES means consider all frames." (eq (frame-terminal frame) terminal)) (frame-list))) (t (frame-list)))))) - (dolist (frame frame-list) - (setq time (frame-use-time frame)) - (when (> time best-time) - (setq best-time time) - (setq mru-frame frame))) - mru-frame)) + (sort frame-list :key #'frame-use-time :reverse t))) + +(defun get-mru-frame (&optional all-frames exclude-child-frames exclude-frame) + "Return the most recently used frame on frames specified by ALL-FRAMES. +Compute the result using `frame-use-time', which see. Tooltip, and +minibuffer only frames are never candidates. If optional argument +EXCLUDE-CHILD-FRAMES is non-nil, eliminate child frames as candidates. +If EXCLUDE-FRAME is non-nil, it is a frame to exclude, for example, the +selected frame. + +The following non-nil values of the optional argument ALL-FRAMES +have special meanings: + +- `visible' means consider all visible frames on the current terminal + or EXCLUDE-FRAME's terminal if EXCLUDE-FRAME is non-nil. + +- 0 (the number zero) means consider all all visible and iconified + frames on the current terminal or EXCLUDE-FRAME's terminal if + EXCLUDE-FRAME is non-nil. + +Any other value of ALL-FRAMES means consider all frames." + (car (get-mru-frames all-frames exclude-child-frames exclude-frame))) ;;;; Frame/display capabilities.