mirror of
https://github.com/vim/vim.git
synced 2026-06-10 15:37:26 +02:00
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:
committed by
Christian Brabandt
parent
781a91ac54
commit
7daab2ad98
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -729,6 +729,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
606,
|
||||
/**/
|
||||
605,
|
||||
/**/
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user