From af60636245abbcbda7131cae9df9ae13d89a9dbc Mon Sep 17 00:00:00 2001 From: Martin Rudalics Date: Tue, 14 Jan 2025 09:51:17 +0100 Subject: [PATCH] Handle removal of selected tty child frame * src/dispextern.h (root_frame): * src/frame.h (root_frame): Move declaration from dispextern.h to frame.h. (SET_FRAME_VISIBLE): Whend making the selected tty child frame invisible, use mru_rooted_frame to find a frame to switch to. * src/dispnew.c (root_frame): Move root_frame to frame.c. * src/frame.c (do_switch_frame): On ttys don't change the top frame when switching from a child frame to another frame with the same root. (root_frame): Move here from dispnew.c. (Fframe_root_frame): New Lisp function. (delete_frame): Whend deleting the selected tty child frame use, mru_rooted_frame to find a frame to switch to. * src/window.c (mru_rooted_frame): New function. * src/window.h (mru_rooted_frame): Declare it. * doc/lispref/frames.texi (Child Frames): Describe new function 'frame-root-frame'. (cherry picked from commit d4aeb6bd230c42cf7b773ec9aebd80ad6d928d98) --- doc/lispref/frames.texi | 13 +++- src/dispextern.h | 1 - src/dispnew.c | 12 ---- src/frame.c | 131 ++++++++++++++++++++++++++-------------- src/frame.h | 39 ++++++------ src/window.c | 32 ++++++++++ src/window.h | 1 + 7 files changed, 153 insertions(+), 76 deletions(-) diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 158e255ef06..a0d0e489ad0 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -3579,7 +3579,7 @@ work on all window-systems. Some will drop the object on the parent frame or on some ancestor instead. @end itemize - The following two functions can be useful when working with child and + The following three functions can be useful when working with child and parent frames: @defun frame-parent &optional frame @@ -3591,6 +3591,7 @@ exists, @var{frame} is considered a child frame of that frame. This function returns @code{nil} if @var{frame} has no parent frame. @end defun +@cindex ancestor frame @defun frame-ancestor-p ancestor descendant This functions returns non-@code{nil} if @var{ancestor} is an ancestor of @var{descendant}. @var{ancestor} is an ancestor of @var{descendant} @@ -3599,6 +3600,16 @@ of @var{descendant}'s parent frame. Both, @var{ancestor} and @var{descendant} must specify live frames. @end defun +@cindex root frame +@defun frame-root-frame &optional frame +This function returns the root frame of the specified @var{frame}. +@var{frame} must be a live frame and defaults to the selected one. The +root frame of @var{frame} is the frame obtained by following the chain +of parent frames starting with @var{frame} until a frame is reached that +has no parent. If @var{frame} has no parent, its root frame is +@var{frame} itself. +@end defun + Note also the function @code{window-largest-empty-rectangle} (@pxref{Coordinates and Windows}) which can be used to inscribe a child frame in the largest empty area of an existing window. This can be diff --git a/src/dispextern.h b/src/dispextern.h index e3e621e7318..1d90022a9bc 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3949,7 +3949,6 @@ extern void gui_redo_mouse_highlight (Display_Info *); #endif /* HAVE_WINDOW_SYSTEM */ -struct frame *root_frame (struct frame *f); Lisp_Object frames_in_reverse_z_order (struct frame *f, bool visible); bool is_tty_frame (struct frame *f); bool is_tty_child_frame (struct frame *f); diff --git a/src/dispnew.c b/src/dispnew.c index 66b5edae6db..c3c8b1123b7 100644 --- a/src/dispnew.c +++ b/src/dispnew.c @@ -3331,18 +3331,6 @@ frame_rect_abs (struct frame *f) #endif /* !HAVE_ANDROID */ -/* Return the root frame of frame F. Follow the parent_frame chain - until we reach a frame that has no parent. That is the root frame. - Note that the root of a root frame is itself. */ - -struct frame * -root_frame (struct frame *f) -{ - while (FRAME_PARENT_FRAME (f)) - f = FRAME_PARENT_FRAME (f); - return f; -} - int max_child_z_order (struct frame *parent) { diff --git a/src/frame.c b/src/frame.c index 3cfc9d0bf55..b2d6877e636 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1775,7 +1775,7 @@ do_switch_frame (Lisp_Object frame, int track, int for_deletion, Lisp_Object nor /* Don't mark the frame garbaged if we are switching to the frame that is already the top frame of that TTY. */ - if (!EQ (frame, top_frame)) + if (!EQ (frame, top_frame) && root_frame (f) != XFRAME (top_frame)) { struct frame *new_root = root_frame (f); SET_FRAME_VISIBLE (new_root, true); @@ -2021,6 +2021,39 @@ frame. */) return frame_ancestor_p (af, df) ? Qt : Qnil; } + +/* Return the root frame of frame F. Follow the parent_frame chain + until we reach a frame that has no parent. That is the root frame. + Note that the root of a root frame is itself. */ + +struct frame * +root_frame (struct frame *f) +{ + while (FRAME_PARENT_FRAME (f)) + f = FRAME_PARENT_FRAME (f); + return f; +} + + +DEFUN ("frame-root-frame", Fframe_root_frame, Sframe_root_frame, + 0, 1, 0, + doc: /* Return root frame of specified FRAME. +FRAME must be a live frame and defaults to the selected one. The root +frame of FRAME is the frame obtained by following the chain of parent +frames starting with FRAME until a frame is reached that has no parent. +If FRAME has no parent, its root frame is FRAME. */) + (Lisp_Object frame) +{ + struct frame *f = decode_live_frame (frame); + struct frame *r = root_frame (f); + Lisp_Object root; + + XSETFRAME (root, r); + + return root; +} + + /* Return CANDIDATE if it can be used as 'other-than-FRAME' frame on the same tty (for tty frames) or among frames which uses FRAME's keyboard. If MINIBUF is nil, do not consider minibuffer-only candidate. @@ -2433,61 +2466,68 @@ delete_frame (Lisp_Object frame, Lisp_Object force) /* Don't let the frame remain selected. */ if (f == sf) { - Lisp_Object tail; - Lisp_Object frame1 UNINIT; /* This line works around GCC bug 85563. */ - 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) + if (is_tty_child_frame (f)) + /* If F is a child frame on a tty and is the selected frame, try + to re-select the frame that was selected before F. */ + do_switch_frame (mru_rooted_frame (f), 0, 1, Qnil); + else { - struct frame *f1 = XFRAME (frame1); - - if (!EQ (frame, frame1) - && !FRAME_TOOLTIP_P (f1) - && FRAME_TERMINAL (f) == FRAME_TERMINAL (f1) - && FRAME_VISIBLE_P (f1)) - break; - } + Lisp_Object tail; + Lisp_Object frame1 UNINIT; /* This line works around GCC bug 85563. */ + eassume (CONSP (Vframe_list)); - /* If there is none, find *some* other frame. */ - if (NILP (frame1) || EQ (frame1, frame)) - { + /* 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)) + && !FRAME_TOOLTIP_P (f1) + && FRAME_TERMINAL (f) == FRAME_TERMINAL (f1) + && FRAME_VISIBLE_P (f1)) + break; + } + + /* If there is none, find *some* other frame. */ + if (NILP (frame1) || EQ (frame1, frame)) + { + FOR_EACH_FRAME (tail, frame1) { - if (FRAME_TERMCAP_P (f1) || FRAME_MSDOS_P (f1)) - { - Lisp_Object top_frame = FRAME_TTY (f1)->top_frame; + struct frame *f1 = XFRAME (frame1); - if (!EQ (top_frame, frame)) - frame1 = top_frame; + 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 (); + do_switch_frame (frame1, 0, 1, Qnil); + sf = SELECTED_FRAME (); + } } else /* Ensure any minibuffers on FRAME are moved onto the selected @@ -2583,11 +2623,11 @@ delete_frame (Lisp_Object frame, Lisp_Object force) f->terminal = 0; /* Now the frame is dead. */ unblock_input (); - /* Clear markers and overlays set by F on behalf of an input - method. */ + /* Clear markers and overlays set by F on behalf of an input + method. */ #ifdef HAVE_TEXT_CONVERSION - if (FRAME_WINDOW_P (f)) - reset_frame_state (f); + if (FRAME_WINDOW_P (f)) + reset_frame_state (f); #endif /* If needed, delete the terminal that this frame was on. @@ -7146,6 +7186,7 @@ iconify the top level frame instead. */); defsubr (&Sframe_list); defsubr (&Sframe_parent); defsubr (&Sframe_ancestor_p); + defsubr (&Sframe_root_frame); defsubr (&Snext_frame); defsubr (&Sprevious_frame); defsubr (&Slast_nonminibuf_frame); diff --git a/src/frame.h b/src/frame.h index bff610472c0..fea8baa7332 100644 --- a/src/frame.h +++ b/src/frame.h @@ -1428,23 +1428,6 @@ FRAME_PARENT_FRAME (struct frame *f) /* False means there are no visible garbaged frames. */ extern bool frame_garbaged; -/* Set visibility of frame F. - We call redisplay_other_windows to make sure the frame gets redisplayed - if some changes were applied to it while it wasn't visible (and hence - wasn't redisplayed). */ -INLINE void -SET_FRAME_VISIBLE (struct frame *f, bool v) -{ - if (v) - { - if (v == 1 && f->visible != 1) - redisplay_other_windows (); - if (FRAME_GARBAGED_P (f)) - frame_garbaged = true; - } - f->visible = v; -} - /* Set iconified status of frame F. */ INLINE void SET_FRAME_ICONIFIED (struct frame *f, int i) @@ -1518,6 +1501,7 @@ void check_tty (struct frame *f); struct frame *decode_tty_frame (Lisp_Object frame); extern void frame_make_pointer_invisible (struct frame *); extern void frame_make_pointer_visible (struct frame *); +extern struct frame *root_frame (struct frame *f); extern Lisp_Object delete_frame (Lisp_Object, Lisp_Object); extern bool frame_inhibit_resize (struct frame *, bool, Lisp_Object); extern void adjust_frame_size (struct frame *, int, int, int, bool, @@ -1541,6 +1525,27 @@ extern Lisp_Object Vframe_list; ? XFRAME (selected_frame) \ : (emacs_abort (), (struct frame *) 0)) +/* Set visibility of frame F. + We call redisplay_other_windows to make sure the frame gets redisplayed + if some changes were applied to it while it wasn't visible (and hence + wasn't redisplayed). */ +INLINE void +SET_FRAME_VISIBLE (struct frame *f, bool v) +{ + if (v) + { + if (v == 1 && f->visible != 1) + redisplay_other_windows (); + if (FRAME_GARBAGED_P (f)) + frame_garbaged = true; + } + /* If F is a child frame on a tty and is the selected frame, try to + re-select the frame that was selected before F. */ + else if (is_tty_child_frame (f) && f == XFRAME (selected_frame)) + do_switch_frame (mru_rooted_frame (f), 0, 0, Qnil); + + f->visible = v; +} /*********************************************************************** Display-related Macros diff --git a/src/window.c b/src/window.c index ff58eb12ee0..d7e6cd00c99 100644 --- a/src/window.c +++ b/src/window.c @@ -3097,6 +3097,38 @@ be listed first but no error is signaled. */) { return window_list_1 (window, minibuf, all_frames); } + +/** Return most recently selected frame that has the same root as a + given frame. It's defined here because the static window_list_1 is + here too but in fact it's only needed in the frame code. */ +Lisp_Object +mru_rooted_frame (struct frame *f) +{ + Lisp_Object windows = window_list_1 (FRAME_SELECTED_WINDOW (f), Qnil, Qt); + struct frame *r = root_frame (f); + struct window *b = NULL; + + for (; CONSP (windows); windows = XCDR (windows)) + { + struct window *w = XWINDOW (XCAR (windows)); + struct frame *wf = WINDOW_XFRAME (w); + + if (wf != f && root_frame (wf) == r && FRAME_VISIBLE_P (wf) + && (!b || w->use_time > b->use_time)) + b = w; + } + + if (b) + return WINDOW_FRAME (b); + else + { + Lisp_Object root; + + XSETFRAME (root, r); + + return root; + } +} /* Look at all windows, performing an operation specified by TYPE with argument OBJ. diff --git a/src/window.h b/src/window.h index 39356f80df7..a48c370b198 100644 --- a/src/window.h +++ b/src/window.h @@ -1227,6 +1227,7 @@ extern void wset_buffer (struct window *, Lisp_Object); extern bool window_outdated (struct window *); extern ptrdiff_t window_point (struct window *w); extern void window_discard_buffer_from_dead_windows (Lisp_Object); +extern Lisp_Object mru_rooted_frame (struct frame *); extern void init_window_once (void); extern void init_window (void); extern void syms_of_window (void); -- 2.39.5