patch 9.2.0606: GTK4: does not support all clipboard formats

Commit: 
https://github.com/vim/vim/commit/7daab2ad985740d93275fd2edfba293a32af05fa
Author: Foxe Chen <[email protected]>
Date:   Tue Jun 9 18:49:31 2026 +0000

    patch 9.2.0606: GTK4: does not support all clipboard formats
    
    Problem:  GTK4: GUI does not support Vim's internal specific
              formats that preserve motion type and encoding. It also
              doesn't support the 'html' option in 'clipboard'.
    Solution: Refactor code and support for all clipboard formats
              (Foxe Chen).
    
    closes: #20445
    
    Signed-off-by: Foxe Chen <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/Filelist b/Filelist
index 53113b456..2f8e4f021 100644
--- a/Filelist
+++ b/Filelist
@@ -507,6 +507,8 @@ SRC_UNIX =  \
                src/gui_gtk4.c \
                src/gui_gtk4_f.c \
                src/gui_gtk4_f.h \
+               src/gui_gtk4_cb.c \
+               src/gui_gtk4_cb.h \
                src/gui_gtk_res.xml \
                src/gui_motif.c \
                src/gui_xmdlg.c \
diff --git a/src/Makefile b/src/Makefile
index dd94e7418..e623616ae 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1234,9 +1234,11 @@ GTK_BUNDLE       =
 
 ### GTK4 GUI
 GTK4_SRC       = gui.c gui_gtk4.c gui_gtk4_f.c \
+                       gui_gtk4_cb.c \
                        $(GRESOURCE_SRC)
 GTK4_OBJ       = objects/gui.o objects/gui_gtk4.o \
                        objects/gui_gtk4_f.o \
+                       objects/gui_gtk4_cb.o \
                        $(GRESOURCE_OBJ)
 GTK4_DEFS      = -DFEAT_GUI_GTK $(NARROW_PROTO)
 GTK4_IPATH     = $(GUI_INC_LOC)
@@ -1260,7 +1262,7 @@ MOTIF_IPATH       = $(GUI_INC_LOC)
 MOTIF_LIBS_DIR = $(GUI_LIB_LOC)
 MOTIF_LIBS1    =
 MOTIF_LIBS2    = $(MOTIF_LIBNAME) -lXt
-MOTIF_INSTALL   = install_normal install_gui_extra
+MOTIF_INSTALL  = install_normal install_gui_extra
 MOTIF_TARGETS  = installglinks
 MOTIF_MAN_TARGETS = yes
 MOTIF_TESTTARGET = gui
@@ -1306,7 +1308,7 @@ HAIKUGUI_TESTTARGET = gui
 HAIKUGUI_BUNDLE =
 
 # All GUI files
-ALL_GUI_SRC  = gui.c gui_gtk.c gui_gtk_f.c gui_gtk4.c gui_gtk4_f.c gui_motif.c 
gui_xmdlg.c gui_xmebw.c gui_gtk_x11.c gui_x11.c gui_haiku.cc
+ALL_GUI_SRC  = gui.c gui_gtk.c gui_gtk_f.c gui_gtk4.c gui_gtk4_f.c 
gui_gtk4_cb.c gui_motif.c gui_xmdlg.c gui_xmebw.c gui_gtk_x11.c gui_x11.c 
gui_haiku.cc
 ALL_GUI_PRO  = proto/gui.pro proto/gui_gtk.pro proto/gui_gtk4.pro 
proto/gui_motif.pro proto/gui_xmdlg.pro proto/gui_gtk_x11.pro proto/gui_x11.pro 
proto/gui_w32.pro proto/gui_photon.pro
 
 # }}}
@@ -3389,6 +3391,9 @@ objects/gui_gtk4.o: gui_gtk4.c
 objects/gui_gtk4_f.o: gui_gtk4_f.c
        $(CCC) -o $@ gui_gtk4_f.c
 
+objects/gui_gtk4_cb.o: gui_gtk4_cb.c
+       $(CCC) -o $@ gui_gtk4_cb.c
+
 
 objects/gui_haiku.o: gui_haiku.cc
        $(CCC) -o $@ gui_haiku.cc
@@ -4467,6 +4472,11 @@ objects/gui_gtk4_f.o: auto/osdef.h gui_gtk4_f.c vim.h 
protodef.h auto/config.h f
  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
+objects/gui_gtk4_cb.o: auto/osdef.h gui_gtk4_cb.c vim.h protodef.h 
auto/config.h feature.h \
+ os_unix.h 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_cb.h
 objects/gui_gtk_f.o: auto/osdef.h gui_gtk_f.c vim.h protodef.h auto/config.h 
feature.h \
  os_unix.h ascii.h keymap.h termdefs.h macros.h option.h \
  beval.h structs.h regexp.h gui.h \
diff --git a/src/clipboard.c b/src/clipboard.c
index 6f847edc1..243625047 100644
--- a/src/clipboard.c
+++ b/src/clipboard.c
@@ -72,7 +72,9 @@ typedef struct {
 // Mimes with a lower index in the array are prioritized first when we are
 // receiving data.
 static const char *supported_mimes[] = {
+    VIMENC_MIMETYPE_NAME,
     VIMENC_ATOM_NAME,
+    VIM_MIMETYPE_NAME,
     VIM_ATOM_NAME,
     "text/plain;charset=utf-8",
     "text/plain",
@@ -1424,6 +1426,10 @@ open_app_context(void)
 
 static Atom    vim_atom;       // Vim's own special selection format
 static Atom    vimenc_atom;    // Vim's extended selection format
+static Atom    vim_mt_atom;    // Vim's own special selection format (in mime
+                               // type format)
+static Atom    vimenc_mt_atom; // Vim's extended selection format (in mime type
+                               // format)
 static Atom    utf8_atom;
 static Atom    compound_text_atom;
 static Atom    text_atom;
@@ -1435,6 +1441,8 @@ x11_setup_atoms(Display *dpy)
 {
     vim_atom          = XInternAtom(dpy, VIM_ATOM_NAME,   False);
     vimenc_atom               = XInternAtom(dpy, VIMENC_ATOM_NAME,False);
+    vim_mt_atom               = XInternAtom(dpy, VIM_MIMETYPE_NAME,   False);
+    vimenc_mt_atom     = XInternAtom(dpy, VIMENC_MIMETYPE_NAME,False);
     utf8_atom         = XInternAtom(dpy, "UTF8_STRING",   False);
     compound_text_atom = XInternAtom(dpy, "COMPOUND_TEXT", False);
     text_atom         = XInternAtom(dpy, "TEXT",          False);
@@ -1476,13 +1484,15 @@ clip_x11_convert_selection_cb(
     // requestor wants to know what target types we support
     if (*target == targets_atom)
     {
-       static Atom array[7];
+       static Atom array[9];
 
        *value = (XtPointer)array;
        i = 0;
        array[i++] = targets_atom;
        array[i++] = vimenc_atom;
        array[i++] = vim_atom;
+       array[i++] = vimenc_mt_atom;
+       array[i++] = vim_mt_atom;
        if (enc_utf8)
            array[i++] = utf8_atom;
        array[i++] = XA_STRING;
@@ -1499,8 +1509,10 @@ clip_x11_convert_selection_cb(
 
     if (       *target != XA_STRING
            && *target != vimenc_atom
+           && *target != vimenc_mt_atom
            && (*target != utf8_atom || !enc_utf8)
            && *target != vim_atom
+           && *target != vim_mt_atom
            && *target != text_atom
            && *target != compound_text_atom)
        return False;
@@ -1511,11 +1523,11 @@ clip_x11_convert_selection_cb(
        return False;
 
     // For our own format, the first byte contains the motion type
-    if (*target == vim_atom)
+    if (*target == vim_atom || *target == vim_mt_atom)
        (*length)++;
 
     // Our own format with encoding: motion 'encoding' NUL text
-    if (*target == vimenc_atom)
+    if (*target == vimenc_atom || *target == vimenc_mt_atom)
        *length += STRLEN(p_enc) + 2;
 
     if (save_length < *length || save_length / 2 >= *length)
@@ -1558,20 +1570,26 @@ clip_x11_convert_selection_cb(
        save_result = (char_u *)*value;
        save_length = *length;
     }
-    else if (*target == vimenc_atom)
+    else if (*target == vimenc_atom || *target == vimenc_mt_atom)
     {
        int l = STRLEN(p_enc);
 
        save_result[0] = motion_type;
        STRCPY(save_result + 1, p_enc);
        mch_memmove(save_result + l + 2, string, (size_t)(*length - l - 2));
-       *type = vimenc_atom;
+       if (*target == vimenc_atom)
+           *type = vimenc_atom;
+       else
+           *type = vimenc_mt_atom;
     }
     else
     {
        save_result[0] = motion_type;
        mch_memmove(save_result + 1, string, (size_t)(*length - 1));
-       *type = vim_atom;
+       if (*target == vim_atom)
+           *type = vim_atom;
+       else
+           *type = vim_mt_atom;
     }
     *format = 8;           // 8 bits per char
     vim_free(string);
@@ -1681,13 +1699,13 @@ clip_x11_request_selection_cb(
     }
     p = (char_u *)value;
     len = *length;
-    if (*type == vim_atom)
+    if (*type == vim_atom || *type == vim_mt_atom)
     {
        motion_type = *p++;
        len--;
     }
 
-    else if (*type == vimenc_atom)
+    else if (*type == vimenc_atom || *type == vimenc_mt_atom)
     {
        char_u          *enc;
        vimconv_T       conv;
@@ -1765,15 +1783,17 @@ clip_x11_request_selection(
     time_t     start_time;
     int                timed_out = FALSE;
 
-    for (i = 0; i < 6; i++)
+    for (i = 0; i < 8; i++)
     {
        switch (i)
        {
-           case 0:  type = vimenc_atom;        break;
-           case 1:  type = vim_atom;           break;
-           case 2:  type = utf8_atom;          break;
-           case 3:  type = compound_text_atom; break;
-           case 4:  type = text_atom;          break;
+           case 0:  type = vimenc_mt_atom;     break;
+           case 1:  type = vimenc_atom;        break;
+           case 2:  type = vim_mt_atom;        break;
+           case 3:  type = vim_atom;           break;
+           case 4:  type = utf8_atom;          break;
+           case 5:  type = compound_text_atom; break;
+           case 6:  type = text_atom;          break;
            default: type = XA_STRING;
        }
        if (type == utf8_atom
@@ -2155,7 +2175,7 @@ clip_yank_selection(
     str_to_reg(y_ptr, type, str, len, -1, FALSE);
 }
 
-    static int
+    int
 clip_convert_selection_offset(
        char_u      **str,
        long_u      *len,
@@ -2554,16 +2574,85 @@ clip_reset_wayland(void)
     return OK;
 }
 
+/*
+ * If "vim" is TRUE, then get the motion type. If "vimenc" is TRUE, then get 
the
+ * motion type and also convert "*buf". "buf" and "len_store" will be updated 
to
+ * reflect the actual contents, but should be set beforehand with the initial
+ * contents. Returns OK on success and FAIL on failure.
+ */
+    int
+clip_convert_data(
+       char_u  **buf,
+       long    *len_store,
+       int     *motion,
+       bool    vim,
+       bool    vimenc,
+       char_u  **tofree)
+{
+    char_u  *final = *buf;
+    char_u  *enc;
+    long    len = *len_store;
+
+    if (vim && len >= 2)
+    {
+       *motion = *final++;
+       len--;
+    }
+    else if (vimenc && len >= 3)
+    {
+       vimconv_T   conv;
+       int         convlen;
+
+       // First byte is motion type
+       *motion = *final++;
+       len--;
+
+       // Get encoding of selection
+       enc = final;
+
+       // Skip the encoding type including null terminator in final text
+       final = memchr(final, NUL, len);
+       if (final == NULL)
+           return FAIL;
+       final++; // Skip NUL
+
+       // Subtract pointers to get length of encoding;
+       len -= final - enc;
+
+       conv.vc_type = CONV_NONE;
+       convert_setup(&conv, enc, p_enc);
+       if (conv.vc_type != CONV_NONE)
+       {
+          char_u *tmp;
+
+          convlen = len;
+          tmp = string_convert(&conv, final, &convlen);
+          len = convlen;
+          if (tmp != NULL)
+          {
+               final = tmp;
+               *tofree = final;
+          }
+          convert_setup(&conv, NULL, NULL);
+       }
+    }
+    *buf = final;
+    *len_store = len;
+    return OK;
+}
+
 /*
  * Read data from a file descriptor and write it to the given clipboard.
  */
     static void
 clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd)
 {
-    char_u     *start, *final, *enc;
+    char_u     *start, *final;
+    long       len;
     garray_T   buf;
     int                motion_type = MAUTO;
     ssize_t    r = 0;
+    char_u     *tofree = NULL;
 #  ifndef HAVE_SELECT
     struct pollfd   pfd;
 
@@ -2628,47 +2717,16 @@ clip_wl_receive_data(Clipboard_T *cbd, const char 
*mime_type, int fd)
     }
 
     final = buf.ga_data;
-
-    if (STRCMP(mime_type, VIM_ATOM_NAME) == 0 && buf.ga_len >= 2)
-    {
-       motion_type = *final++;
-       buf.ga_len--;
-    }
-    else if (STRCMP(mime_type, VIMENC_ATOM_NAME) == 0 && buf.ga_len >= 3)
-    {
-       vimconv_T   conv;
-       int         convlen;
-
-       // first byte is motion type
-       motion_type = *final++;
-       buf.ga_len--;
-
-       // Get encoding of selection
-       enc = final;
-
-       // Skip the encoding type including null terminator in final text
-       final += STRLEN(final) + 1;
-
-       // Subtract pointers to get length of encoding;
-       buf.ga_len -= final - enc;
-
-       conv.vc_type = CONV_NONE;
-       convert_setup(&conv, enc, p_enc);
-       if (conv.vc_type != CONV_NONE)
-       {
-          char_u *tmp;
-
-          convlen = buf.ga_len;
-          tmp = string_convert(&conv, final, &convlen);
-          buf.ga_len = convlen;
-          if (tmp != NULL)
-               final = tmp;
-          convert_setup(&conv, NULL, NULL);
-       }
-    }
-
-    clip_yank_selection(motion_type, final, (long)buf.ga_len, cbd);
+    len = buf.ga_len;
+
+    if (clip_convert_data(&final, &len, &motion_type,
+           STRCMP(mime_type, VIM_ATOM_NAME) == 0
+           || STRCMP(mime_type, VIM_MIMETYPE_NAME) == 0,
+           STRCMP(mime_type, VIMENC_ATOM_NAME) == 0
+           || STRCMP(mime_type, VIMENC_MIMETYPE_NAME) == 0, &tofree) == OK)
+       clip_yank_selection(motion_type, final, len, cbd);
     ga_clear(&buf);
+    vim_free(tofree);
 }
 
 /*
@@ -2772,8 +2830,10 @@ vwl_data_source_listener_event_send(
     // format, after the first byte is the encoding type, which is null
     // terminated.
 
-    is_vimenc = STRCMP(mime_type, VIMENC_ATOM_NAME) == 0;
-    is_vim = STRCMP(mime_type, VIM_ATOM_NAME) == 0;
+    is_vimenc = STRCMP(mime_type, VIMENC_ATOM_NAME) == 0
+       || STRCMP(mime_type, VIMENC_MIMETYPE_NAME) == 0;
+    is_vim = STRCMP(mime_type, VIM_ATOM_NAME) == 0
+       || STRCMP(mime_type, VIM_MIMETYPE_NAME) == 0;
 
     if (is_vimenc)
        offset += 2 + STRLEN(p_enc);
diff --git a/src/gui.h b/src/gui.h
index 609100a17..771e5d307 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -477,6 +477,10 @@ typedef struct Gui
 #endif
 #if defined(FEAT_GUI_GTK) && defined(USE_GTK4)
     int decor_height;
+
+    // Used for clipboard functionality in GTK4 GUI
+    GdkContentProvider *regular_provider;
+    GdkContentProvider *primary_provider;
 #endif
 } gui_T;
 
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 1c2c935d6..033b3f7ba 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -29,6 +29,7 @@
 #include <gdk/gdk.h>
 #include <gtk/gtk.h>
 #include "gui_gtk4_f.h"
+#include "gui_gtk4_cb.h"
 
 /*
  * Geometry string parser, replacing XParseGeometry to remove X11 dependency.
@@ -607,6 +608,9 @@ gui_mch_init(void)
                    G_CALLBACK(clipboard_changed_cb), &clip_plus);
     }
 
+    gui.regular_provider = vim_content_provider_new(&clip_plus);
+    gui.primary_provider = vim_content_provider_new(&clip_star);
+
     return OK;
 }
 
@@ -3477,11 +3481,11 @@ get_menu_tool_height(void)
 }
 
 /*
- * Get the GdkClipboard for the given Clipboard_T.
+ * Get the GdkClipboard and GdkContentProvider for the given Clipboard_T.
  * clip_star (*) uses PRIMARY, clip_plus (+) uses CLIPBOARD.
  */
     static GdkClipboard *
-gtk4_get_clipboard(Clipboard_T *cbd)
+gtk4_get_clipboard(Clipboard_T *cbd, GdkContentProvider **provider)
 {
     GdkDisplay *display;
 
@@ -3493,9 +3497,17 @@ gtk4_get_clipboard(Clipboard_T *cbd)
        return NULL;
 
     if (cbd == &clip_plus)
+    {
+       if (provider != NULL)
+           *provider = gui.regular_provider;
        return gdk_display_get_clipboard(display);
+    }
     else
+    {
+       if (provider != NULL)
+           *provider = gui.primary_provider;
        return gdk_display_get_primary_clipboard(display);
+    }
 }
 
 typedef struct {
@@ -3504,52 +3516,55 @@ typedef struct {
 } ClipReadData;
 
 /*
- * Callback for gdk_clipboard_read_text_async().
+ * Callback for gdk_clipboard_read_async().
  */
     static void
-clip_read_text_cb(GObject *source, GAsyncResult *result, gpointer user_data)
-{
-    GdkClipboard       *clipboard = GDK_CLIPBOARD(source);
-    ClipReadData       *crd = (ClipReadData *)user_data;
-    Clipboard_T                *cbd = crd->cbd;
-    char               *text;
-    GError             *error = NULL;
-
-    text = gdk_clipboard_read_text_finish(clipboard, result, &error);
-    if (text != NULL)
+clip_read_cb(GdkClipboard *cb, GAsyncResult *result, ClipReadData *crd)
+{
+    Clipboard_T            *cbd = crd->cbd;
+    GError         *error = NULL;
+    GInputStream    *in_stream;
+    const char     *mime_type;
+    GByteArray     *arr;
+    static char            buf[512];
+    ssize_t        r;
+    char_u         *actual, *final;
+    long           len;
+    int                    motion_type = MAUTO;
+    char_u         *tofree = NULL;
+
+    in_stream = gdk_clipboard_read_finish(cb, result, &mime_type, &error);
+    if (in_stream == NULL)
     {
-       char_u  *tmpbuf = NULL;
-       char_u  *p;
-       int     len;
-       int     motion_type = MAUTO;
-
-       len = (int)STRLEN(text);
+       g_error_free(error);
+       goto exit;
+    }
 
-       // Convert from UTF-8 to 'encoding' if needed.
-       if (input_conv.vc_type != CONV_NONE)
-       {
-           tmpbuf = string_convert(&input_conv, (char_u *)text, &len);
-           if (tmpbuf != NULL)
-               p = tmpbuf;
-           else
-               p = (char_u *)text;
-       }
-       else
-           p = (char_u *)text;
+    arr = g_byte_array_new();
 
-       // Chop off any trailing NUL bytes.
-       while (len > 0 && p[len - 1] == NUL)
-           --len;
+    while ((r = g_input_stream_read(in_stream, buf, 512, NULL, NULL)) > 0)
+       g_byte_array_append(arr, (uint8_t *)buf, r);
 
-       clip_yank_selection(motion_type, p, (long)len, cbd);
-       vim_free(tmpbuf);
-       g_free(text);
-    }
-    else
+    if (r == -1)
     {
-       if (error != NULL)
-           g_error_free(error);
+       g_byte_array_free(arr, TRUE);
+       goto exit;
     }
+    assert(r == 0);
+
+    len = (long)arr->len;
+    actual = final = g_byte_array_free(arr, FALSE);
+
+    if (clip_convert_data(&final, &len, &motion_type,
+           STRCMP(mime_type, VIM_MIMETYPE_NAME) == 0,
+           STRCMP(mime_type, VIMENC_MIMETYPE_NAME) == 0, &tofree) == OK)
+       clip_yank_selection(motion_type, final, len, cbd);
+    g_free(actual);
+    vim_free(tofree);
+
+exit:
+    if (in_stream != NULL)
+       g_object_unref(in_stream);
     crd->done = TRUE;
 }
 
@@ -3559,17 +3574,27 @@ clip_read_text_cb(GObject *source, GAsyncResult 
*result, gpointer user_data)
     void
 clip_mch_request_selection(Clipboard_T *cbd)
 {
+    static const char  *mimes_no_html[] = {
+       VIMENC_MIMETYPE_NAME,
+       VIM_MIMETYPE_NAME,
+       "text/plain;charset=utf-8",
+       "text/plain",
+       NULL
+    };
     GdkClipboard       *clipboard;
     ClipReadData       crd;
     time_t             start;
 
-    clipboard = gtk4_get_clipboard(cbd);
+    clipboard = gtk4_get_clipboard(cbd, NULL);
     if (clipboard == NULL)
        return;
 
     crd.cbd = cbd;
     crd.done = FALSE;
-    gdk_clipboard_read_text_async(clipboard, NULL, clip_read_text_cb, &crd);
+
+    gdk_clipboard_read_async(
+           clipboard, clip_html ? supported_mimes : mimes_no_html,
+           G_PRIORITY_HIGH, NULL, (GAsyncReadyCallback)clip_read_cb, &crd);
 
     // Spin until the async callback fires, with a 3-second wall-clock
     // timeout as a safety net.
@@ -3581,57 +3606,12 @@ clip_mch_request_selection(Clipboard_T *cbd)
 static int in_clipboard_set = FALSE;
 
 /*
- * Send the current selection to the clipboard.
+ * Send the current selection to the clipboard. Do nothing for because we
+ * subclass GdkContentProvider which will provide the data only when needed.
  */
     void
-clip_mch_set_selection(Clipboard_T *cbd)
+clip_mch_set_selection(Clipboard_T *cbd UNUSED)
 {
-    GdkClipboard       *clipboard;
-    char_u             *str = NULL;
-    long_u             len;
-    int                        motion_type;
-
-    clipboard = gtk4_get_clipboard(cbd);
-    if (clipboard == NULL)
-       return;
-
-    // Get the selection text from the register.
-    clip_get_selection(cbd);
-    motion_type = clip_convert_selection(&str, &len, cbd);
-    if (motion_type < 0 || str == NULL)
-       return;
-
-    // Convert from 'encoding' to UTF-8 if needed.
-    if (output_conv.vc_type != CONV_NONE)
-    {
-       char_u  *conv_str;
-       int     conv_len = (int)len;
-
-       conv_str = string_convert(&output_conv, str, &conv_len);
-       if (conv_str != NULL)
-       {
-           vim_free(str);
-           str = conv_str;
-           len = conv_len;
-       }
-    }
-
-    // Ensure NUL-terminated string for GTK.
-    {
-       char_u *nul_str = alloc(len + 1);
-
-       if (nul_str != NULL)
-       {
-           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);
-       }
-    }
-
-    vim_free(str);
 }
 
     static void
@@ -3647,13 +3627,23 @@ clipboard_changed_cb(GdkClipboard *clipboard, gpointer 
user_data)
 }
 
 /*
- * Own the selection.  In GTK4, ownership is implicit when content is set
- * on the clipboard.  Return OK to indicate we can own it.
+ * Own the selection.
  */
     int
-clip_mch_own_selection(Clipboard_T *cbd UNUSED)
+clip_mch_own_selection(Clipboard_T *cbd)
 {
-    return OK;
+    GdkContentProvider *cp;
+    GdkClipboard       *cb = gtk4_get_clipboard(cbd, &cp);
+    int                        ret;
+
+    if (cb == NULL)
+       return FAIL;
+
+    in_clipboard_set = TRUE;
+    ret = gdk_clipboard_set_content(cb, cp);
+    in_clipboard_set = FALSE;
+
+    return ret ? OK : FAIL;
 }
 
 /*
@@ -3665,14 +3655,12 @@ clip_mch_lose_selection(Clipboard_T *cbd)
 {
     GdkClipboard *clipboard;
 
-    clipboard = gtk4_get_clipboard(cbd);
+    clipboard = gtk4_get_clipboard(cbd, NULL);
     if (clipboard == NULL)
        return;
 
-    // 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.
+    // Only release ownership if we still own it. We don't want to clear the
+    // current selection when we aren't actually the source.
     if (gdk_clipboard_is_local(clipboard))
        gdk_clipboard_set_content(clipboard, NULL);
 }
diff --git a/src/gui_gtk4_cb.c b/src/gui_gtk4_cb.c
new file mode 100644
index 000000000..e627ebeda
--- /dev/null
+++ b/src/gui_gtk4_cb.c
@@ -0,0 +1,198 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved           by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#include "vim.h"
+#include <gtk/gtk.h>
+#include "gui_gtk4_cb.h"
+
+struct _VimContentProvider
+{
+    GdkContentProvider parent;
+
+    // Clipboard this content provider is associated with.
+    Clipboard_T *cbd;
+};
+
+// Note that order is important, mime types placed first have the highest
+// priority for GTK when looking what mime type to receive from.
+//
+// NOTE: GTK4 only supports conforming mime types, meaning formats like
+// "_VIMENC_TEXT" will not work. See
+// https://gitlab.gnome.org/GNOME/gtk/-/work_items/4087
+// and
+// 
https://discourse.gnome.org/t/gtk4-clipboard-does-not-provide-contents-using-custom-mime-type-without-character/6858
+// To get around this, add a new VIM*_MIMETYPE_NAME conforming mime type.
+const char *supported_mimes[] = {
+    VIMENC_MIMETYPE_NAME,
+    VIM_MIMETYPE_NAME,
+    "text/html",
+    "text/plain;charset=utf-8",
+    "text/plain",
+    NULL // gdk_clipboard_read_async expects array to be NULL terminated.
+};
+#define SUPPORTED_MIMES_LEN (ARRAY_LENGTH(supported_mimes) - 1)
+
+G_DEFINE_TYPE(VimContentProvider, vim_content_provider, 
GDK_TYPE_CONTENT_PROVIDER)
+
+static GdkContentFormats *vim_content_provider_ref_formats(GdkContentProvider 
*cp);
+static void vim_content_provider_write_mime_type_async(GdkContentProvider *cp, 
const char *mime_type, GOutputStream *stream, int io_priority, GCancellable 
*cancellable, GAsyncReadyCallback callback, gpointer user_data);
+static gboolean vim_content_provider_write_mime_type_finish(GdkContentProvider 
*cp, GAsyncResult *result, GError **error);
+
+    static void
+vim_content_provider_class_init(VimContentProviderClass *class)
+{
+    GdkContentProviderClass *cp_class = GDK_CONTENT_PROVIDER_CLASS(class);
+
+    cp_class->ref_formats = vim_content_provider_ref_formats;
+    cp_class->write_mime_type_async = 
vim_content_provider_write_mime_type_async;
+    cp_class->write_mime_type_finish = 
vim_content_provider_write_mime_type_finish;
+}
+
+    static void
+vim_content_provider_init(VimContentProvider *self)
+{
+}
+
+    GdkContentProvider *
+vim_content_provider_new(Clipboard_T *cbd)
+{
+    VimContentProvider *vcp = g_object_new(VIM_TYPE_CONTENT_PROVIDER, NULL);
+
+    vcp->cbd = cbd;
+
+    return GDK_CONTENT_PROVIDER(vcp);
+}
+
+    static GdkContentFormats *
+vim_content_provider_ref_formats(GdkContentProvider *cp UNUSED)
+{
+    // We support text formats + our own Vim specific mime types. Also expose
+    // html if user specified 'html' in 'clipboard' option.
+    GdkContentFormatsBuilder *builder = gdk_content_formats_builder_new();
+
+    for (int i = 0; i < SUPPORTED_MIMES_LEN; i++)
+    {
+       if (STRCMP(supported_mimes[i], "text/html") == 0 && !clip_html)
+           continue;
+       gdk_content_formats_builder_add_mime_type(builder, supported_mimes[i]);
+    }
+    return gdk_content_formats_builder_free_to_formats(builder);
+}
+
+static void
+vim_content_provider_write_mime_type_done (
+       GObject         *stream,
+       GAsyncResult    *result,
+       GTask           *task)
+{
+    GError *error = NULL;
+
+    if (!g_output_stream_write_all_finish (G_OUTPUT_STREAM (stream),
+               result, NULL, &error))
+       g_task_return_error (task, error);
+    else
+       g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+    static void
+vim_content_provider_write_mime_type_async(
+       GdkContentProvider  *cp,
+       const char          *mime_type,
+       GOutputStream       *stream,
+       int                 io_priority,
+       GCancellable        *cancellable,
+       GAsyncReadyCallback callback,
+       void                *udata)
+{
+    VimContentProvider *self = VIM_CONTENT_PROVIDER(cp);
+    Clipboard_T                *cbd = self->cbd;
+    int                        motion_type;
+    long_u             length;
+    char_u             *string;
+    int                        offset = 0;
+    bool               is_vim, is_vimenc;
+    GTask              *task;
+    gboolean           have_mime = FALSE;
+
+    task = g_task_new (self, cancellable, callback, udata);
+    g_task_set_priority (task, io_priority);
+    g_task_set_source_tag (task, vim_content_provider_write_mime_type_async);
+
+    if (STRCMP(mime_type, "text/html") == 0 && !clip_html)
+    {
+       g_task_return_new_error(
+               task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+               "HTML not supported");
+       g_object_unref(task);
+       return;
+    }
+
+    // Check if we actually support the mime type
+    for (int i = 0; i < (int)SUPPORTED_MIMES_LEN; i++)
+       if (STRCMP(supported_mimes[i], mime_type) == 0)
+       {
+           have_mime = TRUE;
+           break;
+       }
+
+    if (!have_mime)
+    {
+       g_task_return_new_error(
+               task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+               "Cannot provide contents as '%s'", mime_type);
+       g_object_unref(task);
+       return;
+    }
+
+    // Add the required stuff for our own specific formats.
+    is_vimenc = STRCMP(mime_type, VIMENC_MIMETYPE_NAME) == 0;
+    is_vim = STRCMP(mime_type, VIM_MIMETYPE_NAME) == 0;
+
+    if (is_vimenc)
+       offset += 2 + STRLEN(p_enc);
+    else if (is_vim)
+       offset += 1;
+
+    clip_get_selection(cbd);
+    motion_type = clip_convert_selection_offset(&string, &length, offset, cbd);
+
+    if (motion_type < 0)
+    {
+       g_task_return_new_error(
+               task, G_IO_ERROR, G_IO_ERROR_FAILED, "Error converting data");
+       g_object_unref(task);
+       return;
+    }
+
+    if (is_vimenc)
+    {
+       string[0] = (char_u)motion_type;
+       // Use vim_strncpy for safer copying
+       vim_strncpy(string + 1, p_enc, STRLEN(p_enc));
+    }
+    else if (is_vim)
+       string[0] = (char_u)motion_type;
+
+    // "string" is allocated using vim's allocation functions
+    g_task_set_task_data(task, string, vim_free);
+
+    g_output_stream_write_all_async(
+           stream, string, length, io_priority, cancellable,
+           (GAsyncReadyCallback)vim_content_provider_write_mime_type_done, 
task);
+}
+
+    static gboolean
+vim_content_provider_write_mime_type_finish(
+       GdkContentProvider  *cp,
+       GAsyncResult        *result,
+       GError              **error)
+{
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/gui_gtk4_cb.h b/src/gui_gtk4_cb.h
new file mode 100644
index 000000000..d7f9ec5e5
--- /dev/null
+++ b/src/gui_gtk4_cb.h
@@ -0,0 +1,23 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved           by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+#ifndef GUI_GTK4_CB_H
+#define GUI_GTK4_CB_H
+
+#include "vim.h"
+#include <gtk/gtk.h>
+
+#define VIM_TYPE_CONTENT_PROVIDER (vim_content_provider_get_type())
+G_DECLARE_FINAL_TYPE(VimContentProvider, vim_content_provider, VIM, 
CONTENT_PROVIDER, GdkContentProvider)
+
+extern const char *supported_mimes[];
+
+GdkContentProvider *vim_content_provider_new(Clipboard_T *cbd);
+
+#endif
diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c
index 8b57eef08..ee9dedf17 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -98,7 +98,9 @@ enum
     TARGET_TEXT_URI_LIST,
     TARGET_TEXT_PLAIN,
     TARGET_TEXT_PLAIN_UTF8,
+    TARGET_VIM_MT,
     TARGET_VIM,
+    TARGET_VIMENC_MT,
     TARGET_VIMENC
 };
 
@@ -110,6 +112,8 @@ static const GtkTargetEntry selection_targets[] =
 {
     {VIMENC_ATOM_NAME, 0, TARGET_VIMENC},
     {VIM_ATOM_NAME,    0, TARGET_VIM},
+    {VIMENC_MIMETYPE_NAME,  0, TARGET_VIMENC_MT},
+    {VIM_MIMETYPE_NAME,            0, TARGET_VIM_MT},
     {"text/html",      0, TARGET_HTML},
     {"UTF8_STRING",    0, TARGET_UTF8_STRING},
     {"COMPOUND_TEXT",  0, TARGET_COMPOUND_TEXT},
@@ -163,6 +167,10 @@ static GdkAtom html_atom = GDK_NONE;
 static GdkAtom utf8_string_atom = GDK_NONE;
 static GdkAtom vim_atom = GDK_NONE;    // Vim's own special selection format
 static GdkAtom vimenc_atom = GDK_NONE; // Vim's extended selection format
+static GdkAtom vim_mt_atom = GDK_NONE; // Vim's own special selection format
+                                       // (in mime type format)
+static GdkAtom vimenc_mt_atom = GDK_NONE;      // Vim's extended selection
+                                               // format (in mime type format)
 
 /*
  * Keycodes recognized by vim.
@@ -1452,12 +1460,14 @@ selection_received_cb(GtkWidget         *widget UNUSED,
        return;
     }
 
-    if (gtk_selection_data_get_data_type(data) == vim_atom)
+    if (gtk_selection_data_get_data_type(data) == vim_atom
+           || gtk_selection_data_get_data_type(data) == vim_mt_atom)
     {
        motion_type = *text++;
        --len;
     }
-    else if (gtk_selection_data_get_data_type(data) == vimenc_atom)
+    else if (gtk_selection_data_get_data_type(data) == vimenc_atom
+           || gtk_selection_data_get_data_type(data) == vimenc_mt_atom)
     {
        char_u          *enc;
        vimconv_T       conv;
@@ -1562,6 +1572,8 @@ selection_get_cb(GtkWidget            *widget UNUSED,
            && info != (guint)TARGET_UTF8_STRING
            && info != (guint)TARGET_VIMENC
            && info != (guint)TARGET_VIM
+           && info != (guint)TARGET_VIMENC_MT
+           && info != (guint)TARGET_VIM_MT
            && info != (guint)TARGET_COMPOUND_TEXT
            && info != (guint)TARGET_TEXT_PLAIN
            && info != (guint)TARGET_TEXT_PLAIN_UTF8
@@ -1579,7 +1591,7 @@ selection_get_cb(GtkWidget            *widget UNUSED,
     // (Not that pasting 2G of text is ever going to work, but... ;-)
     length = MIN(tmplen, (long_u)(G_MAXINT - 1));
 
-    if (info == (guint)TARGET_VIM)
+    if (info == (guint)TARGET_VIM || info == (guint)TARGET_VIM_MT)
     {
        tmpbuf = alloc(length + 1);
        if (tmpbuf != NULL)
@@ -1591,7 +1603,10 @@ selection_get_cb(GtkWidget           *widget UNUSED,
        ++length;
        vim_free(string);
        string = tmpbuf;
-       type = vim_atom;
+       if (info == (guint)TARGET_VIM)
+           type = vim_atom;
+       else
+           type = vim_mt_atom;
     }
 
     else if (info == (guint)TARGET_HTML)
@@ -1635,7 +1650,7 @@ selection_get_cb(GtkWidget            *widget UNUSED,
        }
        return;
     }
-    else if (info == (guint)TARGET_VIMENC)
+    else if (info == (guint)TARGET_VIMENC || info == (guint)TARGET_VIMENC_MT)
     {
        int l = STRLEN(p_enc);
 
@@ -1650,7 +1665,10 @@ selection_get_cb(GtkWidget           *widget UNUSED,
            vim_free(string);
            string = tmpbuf;
        }
-       type = vimenc_atom;
+       if (info == (guint)TARGET_VIMENC)
+           type = vimenc_atom;
+       else
+           type = vimenc_mt_atom;
     }
 
     // gtk_selection_data_set_text() handles everything for us.  This is
@@ -4131,6 +4149,8 @@ gui_mch_init(void)
      */
     vim_atom = gdk_atom_intern(VIM_ATOM_NAME, FALSE);
     vimenc_atom = gdk_atom_intern(VIMENC_ATOM_NAME, FALSE);
+    vim_mt_atom = gdk_atom_intern(VIM_MIMETYPE_NAME, FALSE);
+    vimenc_mt_atom = gdk_atom_intern(VIMENC_MIMETYPE_NAME, FALSE);
     clip_star.gtk_sel_atom = GDK_SELECTION_PRIMARY;
     clip_plus.gtk_sel_atom = gdk_atom_intern("CLIPBOARD", FALSE);
 
diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro
index 5bcaf2ce9..25633ca39 100644
--- a/src/proto/clipboard.pro
+++ b/src/proto/clipboard.pro
@@ -30,12 +30,14 @@ void x11_export_final_selection(void);
 void clip_free_selection(Clipboard_T *cbd);
 void clip_get_selection(Clipboard_T *cbd);
 void clip_yank_selection(int type, char_u *str, long len, Clipboard_T *cbd);
+int clip_convert_selection_offset(char_u **str, long_u *len, int offset, 
Clipboard_T *cbd);
 int clip_convert_selection(char_u **str, long_u *len, Clipboard_T *cbd);
 int may_get_selection(int regname);
 void may_set_selection(void);
 int clip_init_wayland(void);
 void clip_uninit_wayland(void);
 int clip_reset_wayland(void);
+int clip_convert_data(char_u **buf, long *len_store, int *motion, bool vim, 
bool vimenc, char_u **tofree);
 char *choose_clipmethod(void);
 void ex_clipreset(exarg_T *eap);
 void adjust_clip_reg(int *rp);
diff --git a/src/testdir/test_wayland.vim b/src/testdir/test_wayland.vim
index 904907563..1a84ffcd1 100644
--- a/src/testdir/test_wayland.vim
+++ b/src/testdir/test_wayland.vim
@@ -229,14 +229,15 @@ func Test_wayland_mime_types_correct()
   call s:PreTest()
 
   let l:mimes = [
+        \ 'application/x-vim-enc-text',
         \ '_VIMENC_TEXT',
         \ '_VIM_TEXT',
+        \ 'application/x-vim-text',
         \ 'text/plain;charset=utf-8',
         \ 'text/plain',
         \ 'UTF8_STRING',
         \ 'STRING',
         \ 'TEXT',
-        \ 'application/x-vim-instance-' .. getpid()
         \ ]
 
   call setreg('+', 'text', 'c')
diff --git a/src/version.c b/src/version.c
index 68013a0ea..7633b7e54 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 */
+/**/
+    606,
 /**/
     605,
 /**/
diff --git a/src/vim.h b/src/vim.h
index 6906547c4..7d1914cd6 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2319,6 +2319,11 @@ typedef enum {
 # define VIM_ATOM_NAME "_VIM_TEXT"
 # define VIMENC_ATOM_NAME "_VIMENC_TEXT"
 
+// These are used for the GTK4 GUI, since GTK4 only supports conforming mime
+// types, see gui_gtk4_cb.c for more information.
+# define VIM_MIMETYPE_NAME "application/x-vim-text"
+# define VIMENC_MIMETYPE_NAME "application/x-vim-enc-text"
+
 // Selection states for modeless selection
 # define SELECT_CLEARED                0
 # define SELECT_IN_PROGRESS    1

-- 
-- 
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/E1wX1gD-006fJe-DL%40256bit.org.

Raspunde prin e-mail lui