Commit: patch 9.2.0931: the GTK4 GUI is still experimental and untested by CI

Christian Brabandt <[email protected]> Mon, 10 Aug 2026 22:30:06 +0200
Newsgroups gmane.editors.vim.devel
Message-ID <[email protected]>
patch 9.2.0931: the GTK4 GUI is still experimental and untested by CI

Commit: https://github.com/vim/vim/commit/1b7bd58115a5e7b788d01c51b90025ce58246c67
Author: Foxe Chen <[email protected]>
Date:   Mon Aug 10 20:08:09 2026 +0000

    patch 9.2.0931: the GTK4 GUI is still experimental and untested by CI
    
    Problem:  the GTK4 GUI is still experimental and untested by CI
    Solution: Stabilize the GTK4 code, Fix a few minor issues, include a
              GTK4 CI job (Foxe Chen).
    
    closes: #20920
    
    Signed-off-by: Foxe Chen <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/.github/actions/build_vim_on_linux/action.yml b/.github/actions/build_vim_on_linux/action.yml
index c6486cd34..6006f5ec8 100644
--- a/.github/actions/build_vim_on_linux/action.yml
+++ b/.github/actions/build_vim_on_linux/action.yml
@@ -88,7 +88,6 @@ runs:
         sudo cp ci/pinned-pkgs /etc/apt/preferences.d/pinned-pkgs
         echo '::endgroup::'
 
-    # TODO: switch to GTK4 GUI
     - name: Install packages
       shell: bash
       run: |
@@ -103,6 +102,8 @@ runs:
           wayland-utils \
           libgtk-3-dev:${{ inputs.architecture }} \
           libgtk-3-bin:${{ inputs.architecture }} \
+          libgtk-4-dev:${{ inputs.architecture }} \
+          libgtk-4-bin:${{ inputs.architecture }} \
           desktop-file-utils \
           libc6-dbgsym:${{ inputs.architecture }} \
           libtool-bin \
@@ -125,6 +126,8 @@ runs:
           libxcb1-dbgsym:${{ inputs.architecture }} \
           libgtk-3-bin-dbgsym:${{ inputs.architecture }} \
           libgtk-3-0t64-dbgsym:${{ inputs.architecture }} \
+          libgtk-4-bin-dbgsym:${{ inputs.architecture }} \
+          libgtk-4-0t64-dbgsym:${{ inputs.architecture }} \
           libglib2.0-0t64-dbgsym:${{ inputs.architecture }} \
           libglib2.0-bin-dbgsym:${{ inputs.architecture }} \
           libglib2.0-dev-bin-dbgsym:${{ inputs.architecture }} \
@@ -233,6 +236,9 @@ runs:
         if ${{ contains(fromJSON(inputs.extra), 'testgui') }}; then
           echo "TEST=-C src testgui"
         fi
+        if ${{ contains(fromJSON(inputs.extra), 'use_gtk4') }}; then
+          CONFOPT="${CONFOPT} --enable-gui=gtk4"
+        fi
         if ${{ contains(fromJSON(inputs.extra), 'unittests') }}; then
           echo "TEST=unittests"
         fi
@@ -266,6 +272,9 @@ runs:
         sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0
         sudo usermod -a -G audio "${USER}"
         sudo bash ci/setup-xvfb.sh
+        if ${{ contains(fromJSON(inputs.extra), 'use_gtk4') }}; then
+          sudo bash ci/setup-weston.sh
+        fi
         echo '::endgroup::'
 
     - name: Check autoconf
diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml
index 015d9d698..e91a267ac 100644
--- a/.github/workflows/ci-linux.yml
+++ b/.github/workflows/ci-linux.yml
@@ -27,6 +27,8 @@ jobs:
       LOG_DIR: ${{ github.workspace }}/logs
       TERM: xterm
       DISPLAY: ":99"
+      WAYLAND_DISPLAY: "/tmp/weston_sock"
+      GTK_A11Y: "none"
       DEBIAN_FRONTEND: noninteractive
 
     strategy:
@@ -65,6 +67,11 @@ jobs:
             coverage: true
             interface: dynamic
             extra: [uchar, testgui]
+          - features: huge
+            compiler: gcc
+            coverage: true
+            interface: dynamic
+            extra: [uchar, testgui, use_gtk4]
           - features: huge
             compiler: gcc
             coverage: true
@@ -100,7 +107,7 @@ jobs:
           coverage: ${{ matrix.coverage }}
 
       - name: Test
-        timeout-minutes: 20
+        timeout-minutes: 30
         run: make ${SHADOWOPT} ${TEST}
 
       # Enable to debug failing tests live and ssh into the CI runners
diff --git a/.github/workflows/ci-linux_asan.yml b/.github/workflows/ci-linux_asan.yml
index 45f69b1e7..cbba1c05a 100644
--- a/.github/workflows/ci-linux_asan.yml
+++ b/.github/workflows/ci-linux_asan.yml
@@ -25,6 +25,8 @@ jobs:
       LOG_DIR: ${{ github.workspace }}/logs
       TERM: xterm
       DISPLAY: ":99"
+      WAYLAND_DISPLAY: "/tmp/weston_sock"
+      GTK_A11Y: "none"
       DEBIAN_FRONTEND: noninteractive
 
     strategy:
diff --git a/Filelist b/Filelist
index ca9e8a7b9..30971bacd 100644
--- a/Filelist
+++ b/Filelist
@@ -31,6 +31,7 @@ SRC_ALL =	\
 		ci/if_feat_check.vim \
 		ci/lychee.toml \
 		ci/setup-xvfb.sh \
+		ci/setup-weston.sh \
 		ci/remove_snap.sh \
 		ci/ddebs.list \
 		ci/pinned-pkgs \
diff --git a/ci/setup-weston.sh b/ci/setup-weston.sh
new file mode 100644
index 000000000..f9f7d4a53
--- /dev/null
+++ b/ci/setup-weston.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+set -e
+
+apt-get install -y weston
+
+cat <<EOT >/etc/systemd/system/weston.service
+[Unit]
+Description=Weston Compositor Service
+After=network.target
+[Service]
+ExecStart=/usr/bin/weston --backend=headless --fake-seat --width=5000 --height=5000 --socket=/tmp/weston_sock
+[Install]
+WantedBy=multi-user.target
+EOT
+
+systemctl enable weston.service
+systemctl start weston.service
diff --git a/runtime/doc/gui_x11.txt b/runtime/doc/gui_x11.txt
index 3bb469cec..f199801ee 100644
--- a/runtime/doc/gui_x11.txt
+++ b/runtime/doc/gui_x11.txt
@@ -1,4 +1,4 @@
-*gui_x11.txt*	For Vim version 9.2.  Last change: 2026 Jul 27
+*gui_x11.txt*	For Vim version 9.2.  Last change: 2026 Aug 10
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -304,7 +304,7 @@ If a name is used that exists on other systems, but not in X11, the default
 "arrow" pointer is used.
 
 ==============================================================================
-5. GTK version					*gui-gtk* *GTK+* *GTK* *GTK3*
+5. GTK version				*gui-gtk* *GTK+* *GTK* *GTK3* *GTK4*
 
 The GTK version of the GUI works a little bit different.
 
@@ -680,7 +680,11 @@ X11R5 with a library for X11R6 probably doesn't work (although the linking
 won't give an error message, Vim will crash later).
 
 							*gui-wayland*
-Support for the Wayland display server protocol has landed in patch 9.1.0064.
+Support for the Wayland display server protocol has landed in patch 9.1.0064,
+for the GTK3 GUI. It is recommended to use the GTK4 GUI if you want a better
+Wayland experience however, as the GTK3 GUI is heavily built around X11
+specific features and API calls.
+
 
 Note: The Wayland protocol is subject to some restrictions, so the following
 functions won't work: |getwinpos()|, |getwinposx()|, |getwinposy()| and the
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 521446ada..869cc9f98 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 9.2.  Last change: 2026 Aug 04
+*options.txt*	For Vim version 9.2.  Last change: 2026 Aug 10
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -4494,7 +4494,7 @@ A jump table for the options with a short description can be found at |Q_op|.
 						*'guiheadroom'* *'ghr'*
 'guiheadroom' 'ghr'	number	(default 50)
 			global
-			{only for GTK and X11 GUI}
+			{only for GTK and X11 GUI, except GTK4 GUI}
 	The number of pixels subtracted from the screen height when fitting
 	the GUI window on the screen.  Set this before the GUI is started,
 	e.g., in your |gvimrc| file.  When zero, the whole screen height will
@@ -5030,6 +5030,9 @@ A jump table for the options with a short description can be found at |Q_op|.
 <	"S-space" means shift+space.  This is the activation key for kinput2 +
 	canna (Japanese), and ami (Korean).
 
+	Note that when using the GTK4 GUI, this option is not supported. The
+	only valid value for this option will be an empty string.
+
 				*'imcmdline'* *'imc'* *'noimcmdline'* *'noimc'*
 'imcmdline' 'imc'	boolean (default off)
 			global
diff --git a/runtime/doc/tags b/runtime/doc/tags
index afb7a3626..92279726b 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -5740,6 +5740,7 @@ GNOME	gui_x11.txt	/*GNOME*
 GTK	gui_x11.txt	/*GTK*
 GTK+	gui_x11.txt	/*GTK+*
 GTK3	gui_x11.txt	/*GTK3*
+GTK4	gui_x11.txt	/*GTK4*
 GUI	gui.txt	/*GUI*
 GUI-X11	gui_x11.txt	/*GUI-X11*
 GUIEnter	autocmd.txt	/*GUIEnter*
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 330b552db..ef371b569 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -52587,10 +52587,11 @@ work in progress.
 
 GUI ~
 -----
-- Added support for the GTK 4 GUI (via patch 9.2.0501).  Enable with
-  `--enable-gui=gtk4` configure switch at build time.
+- Added support for the GTK 4 GUI (via patch 9.2.0501), stablized as of
+  patch 9.2.0931.  Enable with `--enable-gui=gtk4` configure switch at build
+  time.
 - Add support for fullscreen mode in the GTK UI |'go-s'|.
-- Enable native printing diaglog for the GTK4 UI using pango library, see
+- Enable native printing dialog for the GTK4 UI using pango library, see
   |pango-printing|.
 
 Popups ~
diff --git a/src/Makefile b/src/Makefile
index 04917c064..98baecf6f 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1924,7 +1924,6 @@ PROTO_FILES = \
 	proto/gc.pro \
 	proto/getchar.pro \
 	proto/gui_beval.pro \
-	proto/gui_gtk_gresources.pro \
 	proto/gui_xim.pro \
 	proto/hardcopy.pro \
 	proto/hardcopy_pango.pro \
@@ -2025,6 +2024,7 @@ PROTO_FILES = \
 	proto/wayland.pro \
 	proto/winclip.pro \
 	proto/window.pro \
+	$(GRESOURCE_PRO) \
 	$(ALL_GUI_PRO)
 
 # Default target is making the executable and tools
@@ -4546,7 +4546,7 @@ objects/gui_gtk4.o: auto/osdef.h gui_gtk4.c vim.h protodef.h auto/config.h featu
  ascii.h keymap.h termdefs.h macros.h option.h beval.h \
  structs.h regexp.h gui.h libvterm/include/vterm.h \
  libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
- globals.h errors.h gui_gtk4_f.h auto/gui_gtk_gresources.h \
+ globals.h errors.h gui_gtk4_f.h \
  gui_gtk4_cb.h gui_gtk4_da.h gui_gtk4_tb.h gui_gtk4_menu.h
 objects/gui_gtk4_f.o: auto/osdef.h gui_gtk4_f.c vim.h protodef.h auto/config.h feature.h \
  os_unix.h ascii.h keymap.h termdefs.h macros.h option.h \
diff --git a/src/auto/configure b/src/auto/configure
index 312ab6f6c..4e92948f4 100755
--- a/src/auto/configure
+++ b/src/auto/configure
@@ -664,6 +664,7 @@ PANGO_CPPFLAGS
 PANGO_CFLAGS
 PANGO_LIBS
 GRESOURCE_OBJ
+GRESOURCE_PRO
 GRESOURCE_SRC
 UPDATE_DESKTOP_DATABASE
 GTK_UPDATE_ICON_CACHE
@@ -11388,6 +11389,7 @@ printf "%s
" "usable." >&6; }
 	printf "%s
" "#define USE_GRESOURCE 1" >>confdefs.h
 
 	GRESOURCE_SRC="auto/gui_gtk_gresources.c"
+    GRESOURCE_PRO="proto/gui_gtk_gresources.pro"
 	GRESOURCE_OBJ="objects/gui_gtk_gresources.o"
       fi
     else
@@ -11545,6 +11547,7 @@ fi
 
 
 
+
 { printf "%s
" "$as_me:${as_lineno-$LINENO}: checking --enable-hardcopy-pango argument" >&5
 printf %s "checking --enable-hardcopy-pango argument... " >&6; }
 # Check whether --enable-hardcopy-pango was given.
diff --git a/src/config.mk.in b/src/config.mk.in
index 06a77aed6..76e5a2ff7 100644
--- a/src/config.mk.in
+++ b/src/config.mk.in
@@ -176,6 +176,7 @@ GTK_LIBNAME	= @GTK_LIBNAME@
 
 GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
 GRESOURCE_SRC = @GRESOURCE_SRC@
+GRESOURCE_PRO = @GRESOURCE_PRO@
 GRESOURCE_OBJ = @GRESOURCE_OBJ@
 
 GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@
diff --git a/src/configure.ac b/src/configure.ac
index 75035d754..a3d06d0e8 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -3112,6 +3112,7 @@ if test "x$GUITYPE" = "xGTK"; then
 	AC_MSG_RESULT([usable.])
 	AC_DEFINE(USE_GRESOURCE)
 	GRESOURCE_SRC="auto/gui_gtk_gresources.c"
+    GRESOURCE_PRO="proto/gui_gtk_gresources.pro"
 	GRESOURCE_OBJ="objects/gui_gtk_gresources.o"
       fi
     else
@@ -3153,6 +3154,7 @@ if test "x$GUITYPE" = "xGTK"; then
 fi
 AC_SUBST(GLIB_COMPILE_RESOURCES)
 AC_SUBST(GRESOURCE_SRC)
+AC_SUBST(GRESOURCE_PRO)
 AC_SUBST(GRESOURCE_OBJ)
 AC_SUBST(GTK_UPDATE_ICON_CACHE)
 AC_SUBST(UPDATE_DESKTOP_DATABASE)
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 74ca2716e..23dc21e95 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7186,7 +7186,7 @@ f_has(typval_T *argvars, typval_T *rettv)
 #endif
 		},
 	{"gui_gtk2",
-#if defined(FEAT_GUI_GTK) && !defined(USE_GTK3)
+#if defined(FEAT_GUI_GTK) && !defined(USE_GTK3) && !defined(USE_GTK4)
 		1
 #else
 		0
diff --git a/src/gui.h b/src/gui.h
index 653937cfe..5be8d0fd9 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -15,15 +15,7 @@
 # ifdef VMS
 #  include "gui_gtk_vms.h"
 # endif
-# ifdef USE_GTK4
-// Types used in proto files but not available without X11 headers
-typedef void *Widget;
-typedef void *XtAppContext;
-typedef void  Display;
-typedef unsigned long Window;
-typedef unsigned long Atom;
-typedef GdkEvent GdkEventKey;	// GTK4: GdkEventKey merged into GdkEvent
-# else
+# ifndef USE_GTK4
 #  include <X11/Intrinsic.h>
 # endif
 # pragma GCC diagnostic push
@@ -33,6 +25,15 @@ typedef GdkEvent GdkEventKey;	// GTK4: GdkEventKey merged into GdkEvent
 # endif
 # include <gtk/gtk.h>
 # pragma GCC diagnostic pop
+# ifdef USE_GTK4
+// Types used in proto files but not available without X11 headers
+typedef void *Widget;
+typedef void *XtAppContext;
+typedef void  Display;
+typedef unsigned long Window;
+typedef unsigned long Atom;
+typedef GdkEvent GdkEventKey;	// GTK4: GdkEventKey merged into GdkEvent
+# endif
 #endif
 
 #ifdef FEAT_GUI_HAIKU
diff --git a/src/gui_beval.c b/src/gui_beval.c
index c7357fb30..4e86bca8d 100644
--- a/src/gui_beval.c
+++ b/src/gui_beval.c
@@ -169,6 +169,7 @@ gui_mch_destroy_beval_area(BalloonEval *beval)
     if (beval->vts)
 	vim_free(beval->vts);
 #  endif
+    vim_free(beval->msg);
     vim_free(beval);
 }
 # endif
@@ -303,7 +304,7 @@ removeEventHandler(BalloonEval *beval)
 
 #  ifdef USE_GTK4
     static gboolean
-event_cb(GtkEventController *controller, GdkEvent *event, BalloonEval *beval)
+event_cb(GtkEventController *controller UNUSED, GdkEvent *event, BalloonEval *beval)
 {
     int type = gdk_event_get_event_type(event);
 
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 40fa89d89..c2f2564e6 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -258,7 +258,9 @@ static int query_pointer_pos(int *x, int *y, GdkModifierType *state);
 static void mainwin_fullscreened_cb(GObject *obj, GParamSpec *pspec, gpointer user_data);
 static void drawarea_realize_cb(GtkWidget *widget, gpointer data);
 static void drawarea_unrealize_cb(GtkWidget *widget, gpointer data);
+#if defined(FEAT_IMAGE)
 static void scale_factor_cb(GdkSurface *surface, GParamSpec *pspec, void *udata);
+#endif
 static void clipboard_changed_cb(GdkClipboard *clipboard, gpointer user_data);
 #ifdef FEAT_MENU
 static void show_menubar_popover(void);
@@ -732,6 +734,7 @@ gui_mch_open(void)
     guicolor_T bg_pixel = INVALCOLOR;
     guint pixel_width;
     guint pixel_height;
+    long columns = Columns, rows = Rows;
 
     if (gui.geom != NULL)
     {
@@ -741,12 +744,12 @@ gui_mch_open(void)
 	mask = vim_parse_geometry((char *)gui.geom, &w, &h);
 
 	if (mask & WidthValue)
-	    Columns = w;
+	    columns = Columns = w;
 	if (mask & HeightValue)
 	{
 	    if (p_window > (long)h - 1 || !option_was_set((char_u *)"window"))
 		p_window = h - 1;
-	    Rows = h;
+	    rows = Rows = h;
 	}
 	limit_screen_size();
 
@@ -763,6 +766,12 @@ gui_mch_open(void)
 
     pixel_width = (guint)(gui_get_base_width() + Columns * gui.char_width);
     pixel_height = (guint)(gui_get_base_height() + Rows * gui.char_height);
+
+    pixel_width  += get_menu_tool_width();
+    pixel_height += get_menu_tool_height();
+
+    // Dimensions may be smaller because of client side decorations, we handle
+    // that after we present the window.
     gtk_window_set_default_size(GTK_WINDOW(gui.mainwin),
 	    pixel_width, pixel_height);
 
@@ -795,8 +804,15 @@ gui_mch_open(void)
 		     G_CALLBACK(mainwin_destroy_cb), NULL);
     // Resize is handled by GtkForm's size_allocate callback.
 
-    // Not sure if this needed but still do it I guess?
-    gtk_widget_set_visible(gui.mainwin, TRUE);
+    gtk_window_present(GTK_WINDOW(gui.mainwin));
+
+    // Update so that we get the "gui.decor_height", which we can then use to
+    // set the exact dimensions of the window.
+    gui_mch_update();
+    Columns = columns;
+    Rows = rows;
+    gtk_window_set_default_size(GTK_WINDOW(gui.mainwin),
+	    pixel_width, pixel_height + gui.decor_height);
 
     // Make sure the drawing area gets keyboard focus.
     gtk_widget_grab_focus(gui.drawarea);
@@ -951,6 +967,8 @@ gui_mch_set_shellsize(int width, int height,
     height += gui.decor_height;
 
     gtk_window_set_default_size(GTK_WINDOW(gui.mainwin), width, height);
+
+    gui_mch_update();
 }
 
     void
@@ -1290,9 +1308,13 @@ gui_mch_init_font(char_u *font_name, int fontset UNUSED)
     ascii_glyph_table_init();
 
     // im window position depends on cursor size which depends on font metrics
-    // update the position after we've initialized font
-    im_set_position(gui.row, gui.col);
-
+    // update the position after we've initialized font. Make sure to not go out
+    // of bounds of the screen.
+    {
+	int im_row = gui.row < screen_Rows ? gui.row : screen_Rows - 1;
+	int im_col = gui.col < screen_Columns ? gui.col : screen_Columns - 1;
+	im_set_position(im_row, im_col);
+    }
     return OK;
 }
 
@@ -1952,7 +1974,7 @@ static double prev_mouse_y = -1.0;
 static GdkModifierType cur_state = 0;
 
     static timeout_cb_type
-mouse_repeat_timer_cb(gpointer data)
+mouse_repeat_timer_cb(gpointer data UNUSED)
 {
     int x, y;
 
@@ -2130,6 +2152,7 @@ focus_out_event(GtkEventControllerFocus *controller UNUSED,
     static void
 drawarea_realize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
 {
+#if defined(FEAT_IMAGE)
     // Use GdkSurface, as that handles fractional scale values.
     GdkSurface *surface = gtk_native_get_surface(
 	    gtk_widget_get_native(gui.drawarea));
@@ -2139,7 +2162,7 @@ drawarea_realize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
     popup_update_scale(old);
     g_signal_connect(G_OBJECT(surface), "notify::scale",
 	    G_CALLBACK(scale_factor_cb), NULL);
-
+#endif
     gui_mch_new_colors();
 }
 
@@ -2151,18 +2174,18 @@ drawarea_unrealize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
 #endif
 }
 
+#if defined(FEAT_IMAGE)
     static void
 scale_factor_cb(GdkSurface  *surface,
 	GParamSpec	    *pspec UNUSED,
 	void		    *udata UNUSED)
 {
-#if defined(FEAT_IMAGE)
     double old = gui.scale;
 
     gui.scale = gdk_surface_get_scale(surface);
     popup_update_scale(old);
-#endif
 }
+#endif
 
 typedef enum
 {
@@ -2334,7 +2357,7 @@ gui_gtk_set_dnd_targets(void)
  * Handle textual DND data. Note that this does not finish the drop.
  */
     static void
-drop_read_text(GdkDrop *drop, char_u *text)
+drop_read_text(GdkDrop *drop UNUSED, char_u *text)
 {
     GdkModifierType state;
     char_u	    dropkey[6] = {
@@ -3080,7 +3103,7 @@ on_tab_reordered(
  * Handle selecting an item in the tab line popup menu.
  */
     static void
-tabline_menu_event_cb(VimMenuItem *item, VimMenuItemEvent event, void *udata)
+tabline_menu_event_cb(VimMenuItem *item UNUSED, VimMenuItemEvent event, void *udata)
 {
     if (event == VIM_MENU_ITEM_CLICKED)
 	send_tabline_menu_event(tabpage_hover, GPOINTER_TO_INT(udata));
@@ -3399,7 +3422,7 @@ get_menu_tool_height(void)
 
     int height = 0;
 
-    for (int i = 0; i < ARRAY_LENGTH(widgets); i++)
+    for (int i = 0; i < (int)ARRAY_LENGTH(widgets); i++)
     {
 	GtkRequisition	min;
 	GtkRequisition	nat;
@@ -3690,6 +3713,9 @@ static int last_text_area_h = 0;
  * ============================================================
  */
 
+#ifdef FEAT_MENU
+
+# ifdef FEAT_TOOLBAR
 /*
  * Icon name table for toolbar buttons.
  * Must match toolbar_names[] in menu.c.
@@ -3774,10 +3800,11 @@ create_toolbar_icon(vimmenu_T *menu)
 
     return image;
 }
+# endif
 
     static void
 menu_button_clicked_cb(
-	VimMenuItem	    *item,
+	VimMenuItem	    *item UNUSED,
 	VimMenuItemEvent    event,
 	vimmenu_T	    *menu)
 {
@@ -3870,7 +3897,7 @@ gui_mch_add_menu_item(vimmenu_T *menu, int idx)
 {
     vimmenu_T	*parent = menu->parent;
 
-#ifdef FEAT_TOOLBAR
+# ifdef FEAT_TOOLBAR
     if (parent != NULL && menu_is_toolbar(parent->name))
     {
 	if (menu_is_separator(menu->name))
@@ -3904,7 +3931,7 @@ gui_mch_add_menu_item(vimmenu_T *menu, int idx)
 	}
 	return;
     }
-#endif
+# endif
 
     // Menu items (non-toolbar)
     if (parent == NULL || parent->submenu_id == NULL)
@@ -3969,16 +3996,22 @@ gui_mch_destroy_menu(vimmenu_T *menu)
     // For toolbar buttons and separators, remove from the toolbar box.
     if (menu->parent != NULL && menu_is_toolbar(menu->parent->name))
     {
-	vim_toolbar_remove(VIM_TOOLBAR(gui.toolbar), menu->id);
-	menu->id = NULL;
+	if (menu->id != NULL)
+	{
+	    vim_toolbar_remove(VIM_TOOLBAR(gui.toolbar), menu->id);
+	    menu->id = NULL;
+	}
 	return;
     }
 
     // For popup menus, unparent the menu as well
     if (menu->name[0] == ']' || menu_is_popup(menu->name))
-	gtk_widget_unparent(menu->submenu_id);
-    else if (menu->parent == NULL)
-	// Remove from menubar
+    {
+	if (menu->submenu_id != NULL)
+	    gtk_widget_unparent(menu->submenu_id);
+    }
+    else if (menu->parent == NULL && menu->id != NULL)
+	// Remove from menubar, if not NULL.
 	vim_menu_bar_remove(VIM_MENU_BAR(gui.menubar), menu->id);
     // "menu->id" is NULL for window toolbar
     else if (menu->id != NULL)
@@ -4029,6 +4062,8 @@ show_menubar_popover(void)
     gtk_popover_popup(GTK_POPOVER(menu));
 }
 
+#endif // FEAT_MENU
+
 /*
  * ============================================================
  * Scrollbar functions
@@ -4473,7 +4508,7 @@ typedef struct
 } DialogState;
 
     static void
-dialog_button_clicked_cb(GtkButton *button, DialogButtonState *state)
+dialog_button_clicked_cb(GtkButton *button UNUSED, DialogButtonState *state)
 {
     *state->response = state->but_idx;
     *state->done = TRUE;
@@ -4481,9 +4516,9 @@ dialog_button_clicked_cb(GtkButton *button, DialogButtonState *state)
 
     static gboolean
 dialog_key_pressed_cb(
-	GtkEventControllerKey	*controller,
+	GtkEventControllerKey	*controller UNUSED,
 	guint			keyval,
-	guint			keycode,
+	guint			keycode UNUSED,
 	GdkModifierType		state,
 	DialogState		*dstate)
 {
@@ -4499,7 +4534,7 @@ dialog_key_pressed_cb(
 }
 
     static gboolean
-dialog_close_request_cb(GtkWindow *win, gboolean *win_closed)
+dialog_close_request_cb(GtkWindow *win UNUSED, gboolean *win_closed)
 {
     *win_closed = TRUE;
     return FALSE;
@@ -4798,9 +4833,9 @@ entry_changed_cb(GtkWidget *entry, GtkWidget *dialog)
 
     static gboolean
 find_key_pressed_cb(
-	GtkEventControllerKey	*controller,
+	GtkEventControllerKey	*controller UNUSED,
 	guint			keyval,
-	guint			keycode,
+	guint			keycode UNUSED,
 	GdkModifierType		state,
 	SharedFindReplace	*frdp)
 {
diff --git a/src/gui_gtk4_cb.c b/src/gui_gtk4_cb.c
index e627ebeda..5a3a43800 100644
--- a/src/gui_gtk4_cb.c
+++ b/src/gui_gtk4_cb.c
@@ -55,7 +55,7 @@ vim_content_provider_class_init(VimContentProviderClass *class)
 }
 
     static void
-vim_content_provider_init(VimContentProvider *self)
+vim_content_provider_init(VimContentProvider *self UNUSED)
 {
 }
 
@@ -76,7 +76,7 @@ vim_content_provider_ref_formats(GdkContentProvider *cp UNUSED)
     // html if user specified 'html' in 'clipboard' option.
     GdkContentFormatsBuilder *builder = gdk_content_formats_builder_new();
 
-    for (int i = 0; i < SUPPORTED_MIMES_LEN; i++)
+    for (size_t i = 0; i < SUPPORTED_MIMES_LEN; i++)
     {
 	if (STRCMP(supported_mimes[i], "text/html") == 0 && !clip_html)
 	    continue;
@@ -190,7 +190,7 @@ vim_content_provider_write_mime_type_async(
 
     static gboolean
 vim_content_provider_write_mime_type_finish(
-	GdkContentProvider  *cp,
+	GdkContentProvider  *cp UNUSED,
 	GAsyncResult	    *result,
 	GError		    **error)
 {
diff --git a/src/gui_gtk4_da.c b/src/gui_gtk4_da.c
index 80d3044cb..330706e86 100644
--- a/src/gui_gtk4_da.c
+++ b/src/gui_gtk4_da.c
@@ -162,7 +162,9 @@ struct _VimDrawArea
 #endif
 };
 
+#ifdef FEAT_IMAGE_GDK
 static void draw_image_free(DrawImage *dimg);
+#endif
 static void draw_row_init(DrawRow *drow, int row, int cols);
 static void draw_row_clear(DrawRow *drow);
 static void draw_row_dirty_layer(DrawRow *drow, DrawLayerType dlayer_t);
@@ -1034,6 +1036,7 @@ draw_row_render_text(DrawRow *drow, VimDrawArea *da)
 	    empty_cells++;
 	    continue;
 	}
+#if defined(FEAT_NETBEANS_INTG) || defined(FEAT_SIGN_ICONS)
 	else if (dglyphs->font == NULL)
 	{
 	    // Add sign icon
@@ -1055,6 +1058,7 @@ draw_row_render_text(DrawRow *drow, VimDrawArea *da)
 			// loop
 	    continue;
 	}
+#endif
 	else if (dglyphs->font != cur_font || cur_fg != dglyphs->fg_color)
 	{
 	    FLUSH_NODE();
@@ -1157,7 +1161,7 @@ draw_row_render_special(DrawRow *drow, VimDrawArea *da)
     {
 	dlayer->node = gsk_container_node_new(nodes, 2);
 	// gsk_container_node_new() takes its own ref
-	for (int i = 0; i < ARRAY_LENGTH(nodes); i++)
+	for (int i = 0; i < (int)ARRAY_LENGTH(nodes); i++)
 	    gsk_render_node_unref(nodes[i]);
     }
 
diff --git a/src/gui_gtk4_f.c b/src/gui_gtk4_f.c
index 56949d997..195749d2a 100644
--- a/src/gui_gtk4_f.c
+++ b/src/gui_gtk4_f.c
@@ -81,9 +81,8 @@ vim_form_class_init(VimFormClass *class)
 }
 
     static void
-vim_form_init(VimForm *self)
+vim_form_init(VimForm *self UNUSED)
 {
-
 }
 
     GtkWidget *
@@ -229,7 +228,7 @@ vim_form_size_allocate(
 	GtkWidget *widget,
 	int width,
 	int height,
-	int baseline)
+	int baseline UNUSED)
 {
     VimForm *self = VIM_FORM(widget);
 
diff --git a/src/gui_gtk4_menu.c b/src/gui_gtk4_menu.c
index 4abb38624..7b534b255 100644
--- a/src/gui_gtk4_menu.c
+++ b/src/gui_gtk4_menu.c
@@ -881,7 +881,7 @@ vim_menu_init(VimMenu *self)
     // Set all shortcut controllers in the window to not require a modifier for
     // mnemonics.
     controllers = gtk_widget_observe_controllers(GTK_WIDGET(self));
-    for (int i = 0; i < g_list_model_get_n_items(controllers); i++)
+    for (guint i = 0; i < g_list_model_get_n_items(controllers); i++)
     {
 	controller = g_list_model_get_item(controllers, i);
 	if (GTK_IS_SHORTCUT_CONTROLLER(controller))
diff --git a/src/gui_gtk4_tb.c b/src/gui_gtk4_tb.c
index 1971a4728..d2091f6ea 100644
--- a/src/gui_gtk4_tb.c
+++ b/src/gui_gtk4_tb.c
@@ -303,7 +303,11 @@ vim_toolbar_set_style(VimToolbar *self, int style, int iconsize)
     void
 vim_toolbar_remove(VimToolbar *self, GtkWidget *item)
 {
-    gtk_box_remove(GTK_BOX(self->strip),item);
+    if (gtk_widget_get_parent(item) == self->strip)
+	gtk_box_remove(GTK_BOX(self->strip), item);
+    else
+	gtk_box_remove(GTK_BOX(self->overflow_box), item);
+
     self->items = g_list_remove(self->items, item);
     g_object_unref(item);
     gtk_widget_queue_allocate(GTK_WIDGET(self));
@@ -327,8 +331,10 @@ vim_toolbar_move_item_to(
 
     from = overflow ? GTK_BOX(self->strip) : GTK_BOX(self->overflow_box);
 
+    g_object_ref(item);
     gtk_box_remove(from, item);
     gtk_box_append(to, item);
+    g_object_unref(item);
 
     if (GTK_IS_SEPARATOR(item))
 	gtk_widget_set_visible(item, !overflow);
diff --git a/src/gui_xim.c b/src/gui_xim.c
index 0700ce41a..184945583 100644
--- a/src/gui_xim.c
+++ b/src/gui_xim.c
@@ -189,12 +189,10 @@ static int im_preedit_cursor   = 0;	// cursor offset in characters
 static int im_preedit_trailing = 0;	// number of characters after cursor
 
 static unsigned long im_commit_handler_id  = 0;
-#  ifdef USE_GTK4
+#  ifndef USE_GTK4
 static unsigned int  im_activatekey_keyval = GDK_KEY_VoidSymbol;
-#  else
-static unsigned int  im_activatekey_keyval = GDK_VoidSymbol;
-#  endif
 static unsigned int  im_activatekey_state  = 0;
+#  endif
 
 static GtkWidget *preedit_window = NULL;
 static GtkWidget *preedit_label = NULL;
diff --git a/src/option.h b/src/option.h
index 326b48b63..e92bd5f0d 100644
--- a/src/option.h
+++ b/src/option.h
@@ -688,7 +688,7 @@ EXTERN int	p_guipty;	// 'guipty'
 #if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MSWIN)
 EXTERN char_u	*p_guiligatures;  // 'guiligatures'
 #endif
-#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
+#if (defined(FEAT_GUI_GTK) && !defined(USE_GTK4)) || defined(FEAT_GUI_X11)
 EXTERN long	p_ghr;		// 'guiheadroom'
 #endif
 #ifdef CURSOR_SHAPE
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 187747368..10fbd82e0 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -1278,7 +1278,7 @@ static struct vimoption options[] =
 
 
     {"guiheadroom", "ghr",  P_NUM|P_VI_DEF,
-#if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_X11)
+#if (defined(FEAT_GUI_GTK) && !defined(USE_GTK4)) || defined(FEAT_GUI_X11)
 			    (char_u *)&p_ghr, PV_NONE, NULL, NULL,
 #else
 			    (char_u *)NULL, PV_NONE, NULL, NULL,
diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim
index 83c43b117..0652d3de6 100644
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -119,6 +119,9 @@ endfunc
 
 func Test_quoteplus()
   CheckX11BasedGui
+  " Doesn't work with GTK4 GUI, because theres no CUT BUFFER in Wayland, meaning
+  " when the GVim that is launched exits, the clipboard is also cleared as well.
+  CheckNotFeature gui_gtk4
 
   let g:test_is_flaky = 1
 
@@ -667,6 +670,7 @@ endfunc
 
 func Test_set_guiheadroom()
   CheckX11BasedGui
+  CheckNotFeature gui_gtk4
 
   " Since this script is to be read together with '-U NONE', the default
   " value must be preserved.
diff --git a/src/testdir/test_gui_init.vim b/src/testdir/test_gui_init.vim
index 1a60a49ec..d5d1d0578 100644
--- a/src/testdir/test_gui_init.vim
+++ b/src/testdir/test_gui_init.vim
@@ -21,6 +21,7 @@ gui -f
 
 func Test_set_guiheadroom()
   CheckX11BasedGui
+  CheckNotFeature gui_gtk4
 
   " The 'expected' value must be consistent with the value specified with
   " gui_init.vim.
@@ -35,6 +36,7 @@ endfunc
 
 func Test_set_guioptions_for_p()
   CheckX11BasedGui
+  CheckNotFeature gui_gtk4
 
   sleep 200ms
   " Check if the 'p' option is included.
diff --git a/src/testdir/test_startup.vim b/src/testdir/test_startup.vim
index bb8d86d7e..04e4447b0 100644
--- a/src/testdir/test_startup.vim
+++ b/src/testdir/test_startup.vim
@@ -461,6 +461,7 @@ endfunc
 " Test the --echo-wid argument (for GTK GUI only).
 func Test_echo_wid()
   CheckCanRunGui
+  CheckNotFeature gui_gtk4
   CheckFeature gui_gtk
 
   if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid')
@@ -475,6 +476,7 @@ endfunction
 " Test the -reverse and +reverse arguments (for GUI only).
 func Test_reverse()
   CheckCanRunGui
+  CheckNotFeature gui_gtk4
   CheckAnyOf Feature:gui_gtk Feature:gui_motif
 
   let after =<< trim [CODE]
@@ -1427,7 +1429,9 @@ func Test_progname()
       "       Class: XmCascadeButton
       "       Illegal mnemonic character;  Could not convert X KEYSYM to a keycode
       " So don't check that stderr is empty with GUI Motif.
-      if run_with_gui && !has('gui_motif')
+      "
+      " GTK4 also can output warnings, so don't do it as well
+      if run_with_gui && !has('gui_motif') && !has('gui_gtk4')
         call assert_equal('', stdout_stderr, progname)
       endif
       call assert_equal(expectations[progname], readfile('Xprogname_out'), progname)
diff --git a/src/testdir/util/check.vim b/src/testdir/util/check.vim
index c69d39944..47fea7edf 100644
--- a/src/testdir/util/check.vim
+++ b/src/testdir/util/check.vim
@@ -301,7 +301,10 @@ endfunc
 " Command to check for X11 based GUI
 command CheckX11BasedGui call CheckX11BasedGui()
 func CheckX11BasedGui()
-  if !g:x11_based_gui
+  " GTK4 is technically not X11 based, but many tests still apply to it. However
+  " don't set g:x11_based_gui to true, because some tests (such as windowid)
+  " don't apply to GTK4.
+  if !g:x11_based_gui && !has('gui_gtk4')
     throw 'Skipped: requires X11 based GUI'
   endif
 endfunc
diff --git a/src/testdir/util/gen_opt_test.vim b/src/testdir/util/gen_opt_test.vim
index f5d71ad18..738e76e94 100644
--- a/src/testdir/util/gen_opt_test.vim
+++ b/src/testdir/util/gen_opt_test.vim
@@ -237,7 +237,7 @@ let test_values = {
       \		['xxx', 'a,b']],
       \ 'helplang': [['', 'de', 'de,it'], ['xxx']],
       \ 'highlight': [['', 'e:Error'], ['xxx']],
-      \ 'imactivatekey': [['', 'S-space'], ['xxx']],
+      \ 'imactivatekey': [[''], ['xxx']],
       \ 'isfname': [['', '@', '@,48-52'], ['xxx', '@48']],
       \ 'isident': [['', '@', '@,48-52'], ['xxx', '@48']],
       \ 'iskeyword': [['', '@', '@,48-52'], ['xxx', '@48']],
@@ -395,6 +395,10 @@ let test_values = {
       \ 'otherstring': [['', 'xxx'], []],
       \}
 
+if !has('gui_gtk4')
+    let test_values['imactivatekey'] = [['', 'S-space'], ['xxx']]
+endif
+
 if !has('clipboard')
   " If +clipboard isn't enabled but +clipboard_provider is, then 'clipboard' is
   " limited to "unnamed" and "unnamedplus"
diff --git a/src/version.c b/src/version.c
index ccd788a59..41fe299ee 100644
--- a/src/version.c
+++ b/src/version.c
@@ -763,6 +763,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    931,
 /**/
     930,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wtWdK-005ruq-Ra%40256bit.org.