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 <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Foxe Chen
2026-06-09 18:49:31 +00:00
committed by Christian Brabandt
parent 781a91ac54
commit 7daab2ad98
12 changed files with 480 additions and 165 deletions
+2
View File
@@ -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 \
+12 -2
View File
@@ -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 \
+117 -57
View File
@@ -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;
len = buf.ga_len;
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);
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);
+4
View File
@@ -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;
+87 -99
View File
@@ -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)
clip_read_cb(GdkClipboard *cb, GAsyncResult *result, ClipReadData *crd)
{
GdkClipboard *clipboard = GDK_CLIPBOARD(source);
ClipReadData *crd = (ClipReadData *)user_data;
Clipboard_T *cbd = crd->cbd;
char *text;
GError *error = NULL;
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;
text = gdk_clipboard_read_text_finish(clipboard, result, &error);
if (text != 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);
// 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;
// Chop off any trailing NUL bytes.
while (len > 0 && p[len - 1] == NUL)
--len;
clip_yank_selection(motion_type, p, (long)len, cbd);
vim_free(tmpbuf);
g_free(text);
g_error_free(error);
goto exit;
}
else
arr = g_byte_array_new();
while ((r = g_input_stream_read(in_stream, buf, 512, NULL, NULL)) > 0)
g_byte_array_append(arr, (uint8_t *)buf, r);
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);
}
+198
View File
@@ -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);
}
+23
View File
@@ -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
+26 -6
View File
@@ -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);
+2
View File
@@ -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);
+2 -1
View File
@@ -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')
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
606,
/**/
605,
/**/
+5
View File
@@ -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