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'.
This commit is contained in:
Martin Rudalics 2025-01-14 09:51:17 +01:00
parent 0226d35794
commit d4aeb6bd23
7 changed files with 154 additions and 77 deletions

View file

@ -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

View file

@ -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);

View file

@ -3334,18 +3334,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)
{

View file

@ -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);
Lisp_Object tail;
Lisp_Object frame1 UNINIT; /* This line works around GCC bug 85563. */
eassume (CONSP (Vframe_list));
if (!EQ (frame, frame1)
&& !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))
{
/* 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 ();
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);

View file

@ -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

View file

@ -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.

View file

@ -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);