changeset 28503:bae90eec6d14 v8.2.4776

patch 8.2.4776: GTK: 'lines' and 'columns' may change during startup Commit: https://github.com/vim/vim/commit/9f53e7bd7f0865585a5447fa3665c5d4c4f91408 Author: Ernie Rael <errael@raelity.com> Date: Sun Apr 17 18:27:49 2022 +0100 patch 8.2.4776: GTK: 'lines' and 'columns' may change during startup Problem: GTK: 'lines' and 'columns' may change during startup. Solution: Ignore stale GTK resize events. (Ernie Rael, closes https://github.com/vim/vim/issues/10179)
author Bram Moolenaar <Bram@vim.org>
date Sun, 17 Apr 2022 19:30:03 +0200
parents 2a6e6f8ece07
children 7939138bfab8
files src/gui_gtk_x11.c src/version.c
diffstat 2 files changed, 164 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -397,6 +397,130 @@ static int using_gnome = 0;
 #endif
 
 /*
+ * Keep a short term resize history so that stale gtk responses can be
+ * discarded.
+ * When a gtk_window_resize() request is sent to gtk, the width/height of
+ * the request is saved. Recent stale requests are kept around in a list.
+ * See https://github.com/vim/vim/issues/10123
+ */
+#if 0  // Change to 1 to enable ch_log() calls for debugging.
+# ifdef FEAT_JOB_CHANNEL
+#  define ENABLE_RESIZE_HISTORY_LOG
+# endif
+#endif
+
+/*
+ * History item of a resize request.
+ * Width and height are of gui.mainwin.
+ */
+typedef struct resize_history {
+    int used;	    // If true, can't match for discard. Only matches once.
+    int width;
+    int height;
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+    int seq;	    // for ch_log messages
+#endif
+    struct resize_history *next;
+} resize_hist_T;
+
+// never NULL during execution
+static resize_hist_T *latest_resize_hist;
+// list of stale resize requests
+static resize_hist_T *old_resize_hists;
+
+/*
+ * Used when calling gtk_window_resize().
+ * Create a resize request history item, put previous request on stale list.
+ * Width/height are the size of the request for the gui.mainwin.
+ */
+    static void
+alloc_resize_hist(int width, int height)
+{
+    // alloc a new resize hist, save current in list of old history
+    resize_hist_T *prev_hist = latest_resize_hist;
+    resize_hist_T *new_hist = ALLOC_CLEAR_ONE(resize_hist_T);
+
+    new_hist->width = width;
+    new_hist->height = height;
+    latest_resize_hist = new_hist;
+
+    // previous hist item becomes head of list
+    prev_hist->next = old_resize_hists;
+    old_resize_hists = prev_hist;
+
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+    new_hist->seq = prev_hist->seq + 1;
+    ch_log(NULL, "gui_gtk: New resize seq %d (%d, %d) [%d, %d]",
+	   new_hist->seq, width, height, (int)Columns, (int)Rows);
+#endif
+}
+
+/*
+ * Free everything on the stale resize history list.
+ * This list is empty when there are no outstanding resize requests.
+ */
+    static void
+clear_resize_hists()
+{
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+    int		    i = 0;
+#endif
+
+    if (latest_resize_hist)
+	latest_resize_hist->used = TRUE;
+    while (old_resize_hists != NULL)
+    {
+	resize_hist_T *next_hist = old_resize_hists->next;
+
+	vim_free(old_resize_hists);
+	old_resize_hists = next_hist;
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+	i++;
+#endif
+    }
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+    ch_log(NULL, "gui_gtk: free %d hists", i);
+#endif
+}
+
+// true if hist item is unused and matches w,h
+#define MATCH_WIDTH_HEIGHT(hist, w, h) \
+	  (!hist->used && hist->width == w && hist->height == h)
+
+/*
+ * Search the resize hist list.
+ * Return true if the specified width,height match an item in the list that
+ * has never matched before. Mark the matching item as used so it will
+ * not match again.
+ */
+    static int
+match_stale_width_height(int width, int height)
+{
+    resize_hist_T *hist = old_resize_hists;
+
+    for (hist = old_resize_hists; hist != NULL; hist = hist->next)
+	if (MATCH_WIDTH_HEIGHT(hist, width, height))
+	{
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+	    ch_log(NULL, "gui_gtk: discard seq %d, cur seq %d",
+		   hist->seq, latest_resize_hist->seq);
+#endif
+	    hist->used = TRUE;
+	    return TRUE;
+	}
+    return FALSE;
+}
+
+#if defined(EXITFREE)
+    static void
+free_all_resize_hist()
+{
+    clear_resize_hists();
+    vim_free(latest_resize_hist);
+}
+#endif
+
+/*
  * GTK doesn't set the GDK_BUTTON1_MASK state when dragging a touch. Add this
  * state when dragging.
  */
@@ -593,6 +717,7 @@ gui_mch_free_all(void)
 #if defined(USE_GNOME_SESSION)
     vim_free(abs_restart_command);
 #endif
+    free_all_resize_hist();
 }
 #endif
 
@@ -709,7 +834,7 @@ property_event(GtkWidget *widget,
 	xev.xproperty.window = commWindow;
 	xev.xproperty.state = PropertyNewValue;
 	serverEventProc(GDK_WINDOW_XDISPLAY(gtk_widget_get_window(widget)),
-		&xev, 0);
+								      &xev, 0);
     }
     return FALSE;
 }
@@ -720,8 +845,8 @@ property_event(GtkWidget *widget,
  */
     static void
 gtk_settings_xft_dpi_changed_cb(GtkSettings *gtk_settings UNUSED,
-                                GParamSpec *pspec UNUSED,
-                                gpointer data UNUSED)
+				    GParamSpec *pspec UNUSED,
+				    gpointer data UNUSED)
 {
     // Create a new PangoContext for this screen, and initialize it
     // with the current font if necessary.
@@ -4006,7 +4131,36 @@ form_configure_event(GtkWidget *widget U
 		     GdkEventConfigure *event,
 		     gpointer data UNUSED)
 {
-    int usable_height = event->height;
+    int		usable_height = event->height;
+    // Resize requests are made for gui.mainwin,
+    // get it's dimensions for searching if this event
+    // is a response to a vim request.
+    GdkWindow	*win = gtk_widget_get_window(gui.mainwin);
+    int		w = gdk_window_get_width(win);
+    int		h = gdk_window_get_height(win);
+
+#ifdef ENABLE_RESIZE_HISTORY_LOG
+    ch_log(NULL, "gui_gtk: form_configure_event: (%d, %d) [%d, %d]",
+	   w, h, (int)Columns, (int)Rows);
+#endif
+
+    // Look through history of recent vim resize reqeusts.
+    // If this event matches:
+    //	    - "latest resize hist" We're caught up;
+    //		clear the history and process this event.
+    //		If history is, old to new, 100, 99, 100, 99. If this event is
+    //		99 for the stale, it is matched against the current. History
+    //		is cleared, we my bounce, but no worse than before.
+    //	    - "older/stale hist" If match an unused event in history,
+    //		then discard this event, and mark the matching event as used.
+    //	    - "no match" Figure it's a user resize event, clear history.
+    // NOTE: clear history is default, then all incoming events are processed
+
+    if (!MATCH_WIDTH_HEIGHT(latest_resize_hist, w, h)
+					     && match_stale_width_height(w, h))
+	// discard stale event
+	return TRUE;
+    clear_resize_hists();
 
 #if GTK_CHECK_VERSION(3,22,2) && !GTK_CHECK_VERSION(3,22,4)
     // As of 3.22.2, GdkWindows have started distributing configure events to
@@ -4329,15 +4483,16 @@ gui_mch_open(void)
      * manager upon us and should not interfere with what VIM is requesting
      * upon startup.
      */
+    latest_resize_hist = ALLOC_CLEAR_ONE(resize_hist_T);
     g_signal_connect(G_OBJECT(gui.formwin), "configure-event",
-		     G_CALLBACK(form_configure_event), NULL);
+				       G_CALLBACK(form_configure_event), NULL);
 
 #ifdef FEAT_DND
     // Set up for receiving DND items.
     gui_gtk_set_dnd_targets();
 
     g_signal_connect(G_OBJECT(gui.drawarea), "drag-data-received",
-		     G_CALLBACK(drag_data_received_cb), NULL);
+				      G_CALLBACK(drag_data_received_cb), NULL);
 #endif
 
 	// With GTK+ 2, we need to iconify the window before calling show()
@@ -4516,6 +4671,7 @@ gui_mch_set_shellsize(int width, int hei
     width  += get_menu_tool_width();
     height += get_menu_tool_height();
 
+    alloc_resize_hist(width, height); // track the resize request
     if (gtk_socket_id == 0)
 	gtk_window_resize(GTK_WINDOW(gui.mainwin), width, height);
     else
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4776,
+/**/
     4775,
 /**/
     4774,