diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index bdd79528cac..58708cb8eb1 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -3188,6 +3188,55 @@ 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 + +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 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. + +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. + +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}'s terminal. + +@item 0 +means to consider all visible or iconified frames on the current +terminal or @var{exclude}'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 + +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}) +@end defun + @deffn Command select-frame-by-id id &optional noerror This function searches open and undeletable frames for a matching frame identifier @var{id} (@pxref{Frames}). If found, its frame is undeleted, diff --git a/etc/NEWS b/etc/NEWS index 5d02d0a2889..26a16dd0d4c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -494,6 +494,27 @@ fail to delete its argument FRAME or might signal an error. It is therefore advisable to use this function as part of a condition that determines whether to call 'delete-frame'. ++++ +*** New function 'frame-use-time'. +This function is the frame equivalent of the function 'window-use-time' +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. + +--- +*** After deleting, 'delete-frame' now selects the most recently used frame. +Previously, after deleting a specified frame, 'delete-frame' would +select the next eligible frame in the order of the frame list. This is +often unrelated to frames' recent use and can appear somewhat random to +users. + +++ *** New value 'force' for user option 'frame-inhibit-implied-resize'. This will inhibit implied resizing while a new frame is made and can be diff --git a/lisp/frame.el b/lisp/frame.el index 54502837bf6..48d70f7b9d1 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2610,6 +2610,60 @@ for FRAME." (or (/= (window-old-pixel-width root) (window-pixel-width root)) (/= (+ (window-old-pixel-height root) mini-old-height) (+ (window-pixel-height root) mini-height))))) + +(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))) + +(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. + +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. + +- 0 (the number zero) means consider all all visible and iconified + frames on the current terminal or EXCLUDE's terminal if 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)))) + (frame-list + (seq-remove (lambda (frame) + (or (eq frame exclude) + (eq (frame-parameter frame 'minibuffer) 'only) + (frame-parent frame))) + (cond + ((eq all-frames 'visible) + (seq-filter (lambda (frame) + (eq (frame-terminal frame) terminal)) + (visible-frame-list))) + ((eq all-frames 0) + (seq-filter (lambda (frame) + (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)) ;;;; Frame/display capabilities. diff --git a/src/frame.c b/src/frame.c index 8dff1470149..6b5d8942db4 100644 --- a/src/frame.c +++ b/src/frame.c @@ -2748,57 +2748,77 @@ delete_frame (Lisp_Object frame, Lisp_Object force) do_switch_frame (mru_rooted_frame (f), 0, 1, Qnil); else { - Lisp_Object tail; - eassume (CONSP (Vframe_list)); - - /* Look for another visible frame on the same terminal. - Do not call next_frame here because it may loop forever. - See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15025. */ - FOR_EACH_FRAME (tail, frame1) + frame1 = Qnil; + if (!EQ (force, Qnoelisp)) { - struct frame *f1 = XFRAME (frame1); - - if (!EQ (frame, frame1) - && !FRAME_TOOLTIP_P (f1) - && FRAME_TERMINAL (f) == FRAME_TERMINAL (f1) - && FRAME_VISIBLE_P (f1)) - break; + /* Find the most recently used visible frame among all + frames on the same terminal as FRAME, excluding FRAME + which we are about to delete. */ + frame1 = calln (Qget_mru_frame, Qvisible, frame); + if (!NILP (frame1)) + { + struct frame *f1 = XFRAME (frame1); + if (FRAME_TOOLTIP_P (f1) + || FRAME_TERMINAL (f) != FRAME_TERMINAL (f1) + || !FRAME_VISIBLE_P (f1)) + frame1 = Qnil; + } } - /* If there is none, find *some* other frame. */ - if (NILP (frame1) || EQ (frame1, frame)) + if (NILP (frame1)) { + Lisp_Object tail; + eassume (CONSP (Vframe_list)); + + /* Look for another visible frame on the same terminal. + Do not call next_frame here because it may loop forever. + See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15025. */ FOR_EACH_FRAME (tail, frame1) { struct frame *f1 = XFRAME (frame1); if (!EQ (frame, frame1) - && FRAME_LIVE_P (f1) - && !FRAME_TOOLTIP_P (f1)) - { - if (FRAME_TERMCAP_P (f1) || FRAME_MSDOS_P (f1)) - { - Lisp_Object top_frame = FRAME_TTY (f1)->top_frame; + && !FRAME_TOOLTIP_P (f1) + && FRAME_TERMINAL (f) == FRAME_TERMINAL (f1) + && FRAME_VISIBLE_P (f1)) + break; + } - if (!EQ (top_frame, frame)) - frame1 = top_frame; + /* If there is none, find *some* other frame. */ + if (NILP (frame1) || EQ (frame1, frame)) + { + FOR_EACH_FRAME (tail, frame1) + { + struct frame *f1 = XFRAME (frame1); + + if (!EQ (frame, frame1) + && FRAME_LIVE_P (f1) + && !FRAME_TOOLTIP_P (f1)) + { + if (FRAME_TERMCAP_P (f1) || FRAME_MSDOS_P (f1)) + { + Lisp_Object top_frame = FRAME_TTY (f1)->top_frame; + + if (!EQ (top_frame, frame)) + frame1 = top_frame; + } + break; } - break; } } - } #ifdef NS_IMPL_COCOA - else - { - /* Under NS, there is no system mechanism for choosing a new - window to get focus -- it is left to application code. - So the portion of THIS application interfacing with NS - needs to make the frame we switch to the key window. */ - struct frame *f1 = XFRAME (frame1); - if (FRAME_NS_P (f1)) - ns_make_frame_key_window (f1); - } + else + { + /* Under NS, there is no system mechanism for choosing a new + window to get focus -- it is left to application code. + So the portion of THIS application interfacing with NS + needs to make the frame we switch to the key window. */ + struct frame *f1 = XFRAME (frame1); + if (FRAME_NS_P (f1)) + ns_make_frame_key_window (f1); + } #endif + } do_switch_frame (frame1, 0, 1, Qnil); sf = SELECTED_FRAME (); @@ -7185,6 +7205,7 @@ syms_of_frame (void) DEFSYM (Qframe_monitor_attributes, "frame-monitor-attributes"); DEFSYM (Qwindow__pixel_to_total, "window--pixel-to-total"); DEFSYM (Qmake_initial_minibuffer_frame, "make-initial-minibuffer-frame"); + DEFSYM (Qget_mru_frame, "get-mru-frame"); DEFSYM (Qexplicit_name, "explicit-name"); DEFSYM (Qheight, "height"); DEFSYM (Qicon, "icon");