emacs-31 e560eacf6dc: In pgtk_free_frame_resources transfer keyboard focus to parent (Bug#64625)
Martin Rudalics via Mailing list for Emacs changes <[email protected]> Thu, 2 Jul 2026 04:56:14 -0400 (EDT)
| Newsgroups | gmane.emacs.diffs |
|---|---|
| Message-ID | <[email protected]> |
branch: emacs-31 commit e560eacf6dc5dfc8be1be24568ad40ecb56a07a1 Author: haiyang miao <[email protected]> Commit: Martin Rudalics <[email protected]> In pgtk_free_frame_resources transfer keyboard focus to parent (Bug#64625) * src/pgtkterm.c (pgtk_new_focus_frame): Declare static. (pgtk_free_frame_resources): If this frame currently holds keyboard focus, explicitly transfer focus to its parent frame before releasing resources (Bug#64625). Copyright-paperwork-exempt: yes --- src/pgtkterm.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 66a42b9008a..733c7d27c4e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -109,6 +109,7 @@ static bool current_drop_context_drop; static guint32 current_drop_time; static void pgtk_delete_display (struct pgtk_display_info *); +static void pgtk_new_focus_frame (struct pgtk_display_info *, struct frame *); static void pgtk_clear_frame_area (struct frame *, int, int, int, int); static void pgtk_fill_rectangle (struct frame *, unsigned long, int, int, int, int, bool); @@ -467,9 +468,62 @@ pgtk_free_frame_resources (struct frame *f) #define CLEAR_IF_EQ(FIELD) \ do { if (f == dpyinfo->FIELD) dpyinfo->FIELD = 0; } while (false) - CLEAR_IF_EQ (x_focus_frame); + /* If this frame currently holds keyboard focus, explicitly transfer + focus to its parent frame before releasing resources. + + On X11 the X server delivers a FocusIn event to the parent when a + child window is destroyed, so focus was restored automatically. + On Wayland there is no such mechanism: the compositor does nothing, + leaving Emacs with x_focus_frame == NULL permanently and ignoring + all keyboard input (bug#64625). + + IMPORTANT: we must clear highlight_frame from f BEFORE calling + pgtk_new_focus_frame. pgtk_new_focus_frame calls + pgtk_frame_rehighlight, which calls frame_unhighlight(old_highlight) + where old_highlight = dpyinfo->highlight_frame. If that is still f, + frame_unhighlight -> gui_update_cursor(f) would try to use f's face + cache, which was already freed by free_frame_faces(f) above -> + segfault. By clearing highlight_frame first, pgtk_frame_rehighlight + sees old_highlight == NULL and skips frame_unhighlight entirely. */ + if (f == dpyinfo->x_focus_frame) + { + struct frame *new_focus = FRAME_PARENT_FRAME (f); + + /* Clear highlight_frame from f so pgtk_frame_rehighlight will not + call frame_unhighlight on the dying frame whose faces are freed. */ + if (dpyinfo->highlight_frame == f) + dpyinfo->highlight_frame = NULL; + + if (new_focus != NULL + && FRAME_LIVE_P (new_focus) + && FRAME_GTK_WIDGET (new_focus) != NULL + && gtk_widget_get_realized (FRAME_GTK_WIDGET (new_focus))) + { + /* Transfer Emacs's internal focus/highlight to the parent. */ + pgtk_new_focus_frame (dpyinfo, new_focus); + /* Physically move the GTK/Wayland keyboard focus to the parent + widget. The Wayland compositor will not do this automatically + when the child widget is destroyed. */ + gtk_widget_grab_focus (FRAME_GTK_WIDGET (new_focus)); + } + else + { + /* Parent is gone or unrealized; just clear focus state. */ + dpyinfo->x_focus_frame = NULL; + pgtk_frame_rehighlight (dpyinfo); + } + } + + /* CLEAR_IF_EQ is now a no-op for highlight_frame if we already cleared + it above, which is correct. */ CLEAR_IF_EQ (highlight_frame); - CLEAR_IF_EQ (x_focus_event_frame); + + /* Clear x_focus_event_frame directly so that any focus-out event + emitted by gtk_widget_destroy below is a no-op in + pgtk_focus_changed, preventing a spurious + pgtk_new_focus_frame(dpyinfo, NULL) that would undo the transfer. */ + if (f == dpyinfo->x_focus_event_frame) + dpyinfo->x_focus_event_frame = NULL; CLEAR_IF_EQ (last_mouse_frame); CLEAR_IF_EQ (last_mouse_motion_frame); CLEAR_IF_EQ (last_mouse_glyph_frame);