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