Commit: patch 9.2.0529: GTK4: clipboard returns empty after a foreign app takes the selection

2 views
Skip to first unread message

Christian Brabandt

unread,
May 24, 2026, 1:45:12 PM (12 hours ago) May 24
to vim...@googlegroups.com
patch 9.2.0529: GTK4: clipboard returns empty after a foreign app takes the selection

Commit: https://github.com/vim/vim/commit/06ef3a54bf0f740d1ada5524599c7d05f0603b20
Author: Yasuhiro Matsumoto <matt...@gmail.com>
Date: Sun May 24 17:29:04 2026 +0000

patch 9.2.0529: GTK4: clipboard returns empty after a foreign app takes the selection

Problem: GTK4: clipboard read returns empty after a foreign app takes
the selection (lilydjwg, after v9.2.0501)
Solution: Skip the set_content call unless gdk_clipboard_is_local()
still says we own the clipboard (Yasuhiro Matsumoto).

clipboard_changed_cb calls clip_lose_selection() which cascades into
clip_mch_lose_selection(), and that unconditionally called
gdk_clipboard_set_content(clipboard, NULL). When the signal fires
because *another* app took the selection, this re-claims ownership
with NULL content, so the next gdk_clipboard_read_text_async() returns
empty and the user sees "Nothing in register *".

fixes: #20256
closes: #20261

Co-Authored-by: Claude <nor...@anthropic.com>
Signed-off-by: Yasuhiro Matsumoto <matt...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index a9c9de7ca..c12256790 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -285,6 +285,7 @@ static void drawarea_unrealize_cb(GtkWidget *widget, gpointer data);
static void drawarea_resize_cb(GtkDrawingArea *area, int width, int height, gpointer data);
static void drawarea_scale_factor_cb(GObject *object, GParamSpec *pspec, gpointer data);
static cairo_surface_t *create_backing_surface(int width, int height);
+static void clipboard_changed_cb(GdkClipboard *clipboard, gpointer user_data);

/*
* Parse the GUI related command-line arguments. Any arguments used are
@@ -589,6 +590,19 @@ gui_mch_init(void)
// Create a blank (invisible) cursor for hiding the mouse pointer.
gui.blank_pointer = gdk_cursor_new_from_name("none", NULL);

+ {
+ GdkDisplay *display = gtk_widget_get_display(gui.mainwin);
+ GdkClipboard *primary = gdk_display_get_primary_clipboard(display);
+ GdkClipboard *board = gdk_display_get_clipboard(display);
+
+ if (primary != NULL)
+ g_signal_connect(primary, "changed",
+ G_CALLBACK(clipboard_changed_cb), &clip_star);
+ if (board != NULL)
+ g_signal_connect(board, "changed",
+ G_CALLBACK(clipboard_changed_cb), &clip_plus);
+ }
+
return OK;
}

@@ -3130,6 +3144,8 @@ clip_mch_request_selection(Clipboard_T *cbd)
g_main_context_iteration(NULL, TRUE);
}

+static int in_clipboard_set = FALSE;
+
/*
* Send the current selection to the clipboard.
*/
@@ -3174,7 +3190,9 @@ clip_mch_set_selection(Clipboard_T *cbd)
{
mch_memmove(nul_str, str, len);
nul_str[len] = NUL;
+ in_clipboard_set = TRUE;
gdk_clipboard_set_text(clipboard, (const char *)nul_str);
+ in_clipboard_set = FALSE;
vim_free(nul_str);
}
}
@@ -3182,6 +3200,18 @@ clip_mch_set_selection(Clipboard_T *cbd)
vim_free(str);
}

+ static void
+clipboard_changed_cb(GdkClipboard *clipboard, gpointer user_data)
+{
+ Clipboard_T *cbd = (Clipboard_T *)user_data;
+
+ if (in_clipboard_set)
+ return;
+ if (gdk_clipboard_is_local(clipboard))
+ return;
+ clip_lose_selection(cbd);
+}
+
/*
* Own the selection. In GTK4, ownership is implicit when content is set
* on the clipboard. Return OK to indicate we can own it.
@@ -3205,8 +3235,12 @@ clip_mch_lose_selection(Clipboard_T *cbd)
if (clipboard == NULL)
return;

- // Setting NULL content provider releases ownership.
- gdk_clipboard_set_content(clipboard, NULL);
+ // Only release ownership if we still own it. Otherwise we would
+ // clobber another application's clipboard content with NULL, which
+ // happens when this is called from clipboard_changed_cb after a
+ // foreign app took the selection.
+ if (gdk_clipboard_is_local(clipboard))
+ gdk_clipboard_set_content(clipboard, NULL);
}

// Balloon eval - use GTK4 tooltip
diff --git a/src/version.c b/src/version.c
index 19dbebbdb..ca5b3ec12 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 529,
/**/
528,
/**/
Reply all
Reply to author
Forward
0 new messages