diff --git a/Filelist b/Filelist index 53113b456a..2f8e4f0212 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 dd94e74180..e623616ae5 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 6f847edc1c..243625047d 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; + 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); diff --git a/src/gui.h b/src/gui.h index 609100a172..771e5d307e 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 1c2c935d62..033b3f7bac 100644 --- a/src/gui_gtk4.c +++ b/src/gui_gtk4.c @@ -29,6 +29,7 @@ #include #include #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); } diff --git a/src/gui_gtk4_cb.c b/src/gui_gtk4_cb.c new file mode 100644 index 0000000000..e627ebeda8 --- /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 +#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 0000000000..d7f9ec5e55 --- /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 + +#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 8b57eef088..ee9dedf175 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 5bcaf2ce99..25633ca394 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 9049075637..1a84ffcd14 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 68013a0ea5..7633b7e541 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 6906547c4e..7d1914cd66 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