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.