From a4d443ced99b900f7194a9e355cdd8b1be09a321 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Wed, 6 May 2026 15:08:07 -0400 Subject: [PATCH 1/9] initial commit --- src/clipboard.c | 338 +++++++++++++++++++++++++++++++--------- src/evalfunc.c | 50 +++++- src/proto/clipboard.pro | 4 + src/vim.h | 11 ++ 4 files changed, 325 insertions(+), 78 deletions(-) diff --git a/src/clipboard.c b/src/clipboard.c index e47c9a5adf29fe..3ee9c15e9b21d0 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -68,7 +68,7 @@ typedef struct { clip_wl_selection_T primary; } clip_wl_T; -// Mime types we support sending and receiving +// Mime types we support sending and receiving, as the default format. // Mimes with a lower index in the array are prioritized first when we are // receiving data. static const char *supported_mimes[] = { @@ -83,8 +83,8 @@ static const char *supported_mimes[] = { clip_wl_T clip_wl; -static void -clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd); +static int clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd, garray_T *ga); +static int clip_wl_request_format(Clipboard_T *cbd, const char *format, garray_T *ga, bool roundtrip); static void clip_wl_request_selection(Clipboard_T *cbd); static int clip_wl_own_selection(Clipboard_T *cbd); static void clip_wl_lose_selection(Clipboard_T *cbd); @@ -126,6 +126,8 @@ clip_init(int can_use) cb->end.lnum = 0; cb->end.col = 0; cb->state = SELECT_CLEARED; + clip_clear_formats(cb); + ga_init2(&cb->formats, sizeof(clipformat_T), 2); skip: if (cb == &clip_plus) @@ -2271,6 +2273,143 @@ may_set_selection(void) } } +# ifdef FEAT_EVAL + +/* + * Get the corresponding Clipboard_T struct based on "regname". Returns NULL if + * "regname" is not a clipboad register. + */ + Clipboard_T * +clip_get_clipboard(int regname) +{ + if (regname == '+') + return &clip_plus; + else if (regname == '*') + return &clip_star; + else + return NULL; +} + +/* + * Add the given format to the clipboard. If "blob" is not NULL, then it is + * copied and placed with the format. + */ + void +clip_add_format(Clipboard_T *cbd, char_u *format, blob_T *blob) +{ + char_u *str; + + if (ga_grow(&cbd->formats, 1) == FAIL) + return; + + str = vim_strsave(format); + + if (str != NULL) + { + clipformat_T *fmt = + &((clipformat_T *)cbd->formats.ga_data)[cbd->formats.ga_len++]; + + if (blob != NULL && blob->bv_ga.ga_data != NULL) + { + fmt->data = vim_memsave(blob->bv_ga.ga_data, blob->bv_ga.ga_len); + fmt->len = blob->bv_ga.ga_len; + } + else + fmt->data = NULL; + fmt->name = str; + } +} + + void +clip_clear_formats(Clipboard_T *cbd) +{ + for (int i = 0; i formats.ga_len; i++) + { + clipformat_T *fmt = &((clipformat_T *)cbd->formats.ga_data)[i]; + + vim_free(fmt->data); + vim_free(fmt->name); + } + ga_clear(&cbd->formats); +} + + static int +clip_gen_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) +{ +# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) +# ifdef FEAT_GUI + if (gui.in_use) + /* clip_mch_request_selection(cbd); */ + else +# endif + { + if (clipmethod == CLIPMETHOD_WAYLAND) + { +# ifdef FEAT_WAYLAND_CLIPBOARD + return clip_wl_request_format(cbd, (char *)format, ga, true); +# endif + } + else if (clipmethod == CLIPMETHOD_X11) + { +# ifdef FEAT_XCLIPBOARD + /* clip_xterm_request_selection(cbd); */ +# endif + } + } +# else + /* clip_mch_request_selection(cbd); */ +# endif + return FAIL; +} + +/* + * Get the contents of the given format from the clipboard. Returns NULL on + * failure or if format is not available. + */ + blob_T * +clip_get_selection_format(Clipboard_T *cbd, char_u *format) +{ + blob_T *blob; + garray_T ga; + + blob = blob_alloc(); + if(blob == NULL) + return NULL; + + ga_init2(&ga, 1, 512); + + // Check if we already have the contents, from setreg(). TODO + for (int i = 0; i formats.ga_len; i++) + { + clipformat_T *fmt = &((clipformat_T *)cbd->formats.ga_data)[i]; + + if (STRCMP(format, fmt->name) == 0 && fmt->data != NULL) + { + ga_concat_len(&ga, fmt->data, fmt->len); + break; + } + } + if (ga.ga_data == NULL) + { + // Try requesting the contents now + if (clip_gen_request_format(cbd, format, &ga) == FAIL) + goto fail; + } + + if (ga.ga_data == NULL) + goto fail; + + blob->bv_ga = ga; + blob->bv_refcount++; + + return blob; +fail: + blob_free(blob); + return NULL; +} + +# endif + # if defined(FEAT_WAYLAND_CLIPBOARD) static clip_wl_selection_T * @@ -2322,12 +2461,14 @@ clip_wl_get_selection_type(clip_wl_selection_T *sel) static bool wl_data_offer_listener_event_offer( - void *data UNUSED, + void *data, vwl_data_offer_T *offer UNUSED, const char *mime_type ) { - // Only accept mime type if we support it + clip_add_format(data, (char_u *)mime_type, NULL); + + // Only accept mime type if we support it for the default format for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes); i++) if (STRCMP(mime_type, supported_mimes[i]) == 0) return true; @@ -2340,12 +2481,13 @@ static const vwl_data_offer_listener_T vwl_data_offer_listener = { static void vwl_data_device_listener_event_data_offer( - void *data UNUSED, + void *data, vwl_data_device_T *device UNUSED, vwl_data_offer_T *offer) { + clip_clear_formats(data); // Immediately start listening for offer events from the data offer - vwl_data_offer_add_listener(offer, &vwl_data_offer_listener, NULL); + vwl_data_offer_add_listener(offer, &vwl_data_offer_listener, data); } static void @@ -2492,12 +2634,12 @@ clip_init_wayland(void) // Start listening for selection updates if (clip_wl.regular.device != NULL) vwl_data_device_add_listener(clip_wl.regular.device, - &vwl_data_device_listener, NULL); + &vwl_data_device_listener, &clip_plus); // Don't want to listen to the same data device twice if (clip_wl.primary.device != NULL && clip_wl.primary.device != clip_wl.regular.device) vwl_data_device_add_listener(clip_wl.primary.device, - &vwl_data_device_listener, NULL); + &vwl_data_device_listener, &clip_star); return OK; } @@ -2554,14 +2696,17 @@ clip_reset_wayland(void) } /* - * Read data from a file descriptor and write it to the given clipboard. + * Read data from a file descriptor and write it to given grow array (which will + * be initialized first). Returns OK on success and FAIL on failure. */ - static void -clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd) + static int +clip_wl_receive_data( + Clipboard_T *cbd, + const char *mime_type, + int fd, + garray_T *buf) { - char_u *start, *final, *enc; - garray_T buf; - int motion_type = MAUTO; + char_u *start; ssize_t r = 0; # ifndef HAVE_SELECT struct pollfd pfd; @@ -2578,16 +2723,16 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd) // Make pipe (read end) non-blocking if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) - return; + return FAIL; - ga_init2(&buf, 1, 4096); + ga_init2(buf, 1, 4096); // 4096 bytes seems reasonable for initial buffer size, memory is cheap // anyways. - if (ga_grow(&buf, 4096) == FAIL) - return; + if (ga_grow(buf, 4096) == FAIL) + return FAIL; - start = buf.ga_data; + start = buf->ga_data; # ifndef HAVE_SELECT while (poll(&pfd, 1, p_wtm) > 0) @@ -2596,98 +2741,96 @@ clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd) select(fd + 1, &rfds, NULL, NULL, &tv) > 0) # endif { - r = read(fd, start, buf.ga_maxlen - 1 - buf.ga_len); + r = read(fd, start, buf->ga_maxlen - 1 - buf->ga_len); if (r == 0) break; else if (r < 0) { - if (errno == EAGAIN || errno == EINTR) + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + { + if (got_int) + break; continue; + } break; } start += r; - buf.ga_len += r; + buf->ga_len += r; // Realloc if we are at the end of the buffer - if (buf.ga_len >= buf.ga_maxlen - 1) + if (buf->ga_len >= buf->ga_maxlen - 1) { - if (ga_grow(&buf, 8192) == FAIL) + if (ga_grow(buf, 8192) == FAIL) break; - start = (char_u *)buf.ga_data + buf.ga_len; + start = (char_u *)buf->ga_data + buf->ga_len; } } - if (buf.ga_len == 0) - { - clip_free_selection(cbd); // Nothing received, clear register - ga_clear(&buf); - return; - } + return OK; +} - final = buf.ga_data; +/* + * Request the selection data for the given format (mime type). "ga" will be + * initialized. Returns OK on success and FAIL on failure. + */ + static int +clip_wl_request_format( + Clipboard_T *cbd, + const char *format, + garray_T *ga, + bool roundtrip) +{ + clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd); + int fds[2]; + int ret = FAIL; - 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; + if (!sel->available) + return FAIL; - // first byte is motion type - motion_type = *final++; - buf.ga_len--; + // Dispatch any events that still queued up before checking for a data + // offer. + if (roundtrip && vwl_connection_roundtrip(wayland_ct) == FAIL) + return FAIL; - // Get encoding of selection - enc = final; + if (sel->offer == NULL) + return FAIL; - // Skip the encoding type including null terminator in final text - final += STRLEN(final) + 1; + if (pipe(fds) == -1) + return FAIL; - // Subtract pointers to get length of encoding; - buf.ga_len -= final - enc; + vwl_data_offer_receive(sel->offer, format, fds[1]); - conv.vc_type = CONV_NONE; - convert_setup(&conv, enc, p_enc); - if (conv.vc_type != CONV_NONE) - { - char_u *tmp; + close(fds[1]); // Close before we read data so that when the source client + // closes their end we receive an EOF. - convlen = buf.ga_len; - tmp = string_convert(&conv, final, &convlen); - buf.ga_len = convlen; - if (tmp != NULL) - final = tmp; - convert_setup(&conv, NULL, NULL); - } - } + if (vwl_connection_flush(wayland_ct) >= 0) + ret = clip_wl_receive_data(cbd, format, fds[0], ga); - clip_yank_selection(motion_type, final, (long)buf.ga_len, cbd); - ga_clear(&buf); + close(fds[0]); + + return ret; } /* * Get the current selection and fill the respective register for cbd with the - * data. + * data. If "format" is not NULL, request data for that specific format. */ static void clip_wl_request_selection(Clipboard_T *cbd) { clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd); - int fds[2]; int mime_types_len; const char **mime_types; const char *chosen_mime = NULL; + garray_T buf; + char_u *final, *enc; + int motion_type = MAUTO; if (!sel->available) goto clear; - // Dispatch any events that still queued up before checking for a data - // offer. if (vwl_connection_roundtrip(wayland_ct) == FAIL) goto clear; @@ -2707,18 +2850,59 @@ clip_wl_request_selection(Clipboard_T *cbd) chosen_mime = supported_mimes[i]; } - if (chosen_mime == NULL || pipe(fds) == -1) + if (chosen_mime == NULL + || clip_wl_request_format(cbd, chosen_mime, &buf, false) == FAIL) goto clear; - vwl_data_offer_receive(sel->offer, chosen_mime, fds[1]); + if (buf.ga_len == 0) + { + // Nothing received, clear register + ga_clear(&buf); + goto clear; + } - close(fds[1]); // Close before we read data so that when the source client - // closes their end we receive an EOF. + final = buf.ga_data; - if (vwl_connection_flush(wayland_ct) >= 0) - clip_wl_receive_data(cbd, chosen_mime, fds[0]); + if (STRCMP(chosen_mime, VIM_ATOM_NAME) == 0 && buf.ga_len >= 2) + { + motion_type = *final++; + buf.ga_len--; + } + else if (STRCMP(chosen_mime, VIMENC_ATOM_NAME) == 0 && buf.ga_len >= 3) + { + vimconv_T conv; + int convlen; - close(fds[0]); + // 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); + ga_clear(&buf); return; clear: diff --git a/src/evalfunc.c b/src/evalfunc.c index ed03592635511f..fc24364874a75b 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -1328,6 +1328,7 @@ static argcheck_T arg3_string_or_list_bool_number[] = {arg_string_or_list_any, a static argcheck_T arg3_string_string_bool[] = {arg_string, arg_string, arg_bool}; static argcheck_T arg3_string_string_dict[] = {arg_string, arg_string, arg_dict_any}; static argcheck_T arg3_string_string_number[] = {arg_string, arg_string, arg_number}; +static argcheck_T arg4_string_bool_bool_blob[] = {arg_string, arg_bool, arg_bool, arg_blob}; static argcheck_T arg4_number_number_string_any[] = {arg_number, arg_number, arg_string, arg_any}; static argcheck_T arg4_string_string_any_string[] = {arg_string, arg_string, arg_any, arg_string}; static argcheck_T arg4_string_string_number_string[] = {arg_string, arg_string, arg_number, arg_string}; @@ -2354,7 +2355,7 @@ static const funcentry_T global_functions[] = ret_list_number, f_getpos}, {"getqflist", 0, 1, 0, arg1_dict_any, ret_list_or_dict_0, f_getqflist}, - {"getreg", 0, 3, FEARG_1, arg3_string_bool_bool, + {"getreg", 0, 4, FEARG_1, arg4_string_bool_bool_blob, ret_getreg, f_getreg}, {"getreginfo", 0, 1, FEARG_1, arg1_string, ret_dict_any, f_getreginfo}, @@ -6547,7 +6548,36 @@ f_getreg(typval_T *argvars, typval_T *rettv) arg2 = (int)tv_get_bool_chk(&argvars[1], &error); if (!error && argvars[2].v_type != VAR_UNKNOWN) + { + Clipboard_T *cbd = clip_get_clipboard(regname); + return_list = (int)tv_get_bool_chk(&argvars[2], &error); + + if (argvars[3].v_type != VAR_UNKNOWN) + { + if (cbd == NULL) + { + emsg(_(e_invalid_argument)); + return; + } + + // Return blob for the format from the clipboard + char_u *fmt = tv_get_string_chk(&argvars[3]); + blob_T *blob; + + if (fmt == NULL) + return; + blob = clip_get_selection_format(cbd, fmt); + + if (blob == NULL) + return; + + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = blob; + + return; + } + } if (error) return; } @@ -10422,6 +10452,7 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) long reglen = 0; dict_T *dict; list_T *list; + Clipboard_T *cbd; if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL) return; @@ -10482,6 +10513,23 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) (void)dict_add(dict, item); } } + + cbd = clip_get_clipboard(regname); + if (cbd != NULL) + { + // Add a list of supported formats if register is a clipboard register. + list_T *flist = list_alloc(); + + if (flist == NULL) + return; + + for (int i = 0; i < cbd->formats.ga_len; i++) + list_append_string(flist, + ((clipformat_T *)cbd->formats.ga_data)[i].name, -1); + + list->lv_refcount++; + (void)dict_add_list(dict, "formats", flist); + } } static void diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro index 5bcaf2ce99cedd..0ac9cf40caa218 100644 --- a/src/proto/clipboard.pro +++ b/src/proto/clipboard.pro @@ -33,6 +33,10 @@ void clip_yank_selection(int type, char_u *str, long len, 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); +Clipboard_T *clip_get_clipboard(int regname); +void clip_add_format(Clipboard_T *cbd, char_u *format, blob_T *blob); +void clip_clear_formats(Clipboard_T *cbd); +blob_T *clip_get_selection_format(Clipboard_T *cbd, char_u *format); int clip_init_wayland(void); void clip_uninit_wayland(void); int clip_reset_wayland(void); diff --git a/src/vim.h b/src/vim.h index 8949e867deff3c..16c9782d80ce92 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2327,6 +2327,13 @@ typedef enum { # endif # endif +typedef struct +{ + char_u *data; // Binary data, may be NULL + int len; + char_u *name; +} clipformat_T; + // Info about selected text typedef struct { @@ -2369,6 +2376,10 @@ typedef struct # ifdef FEAT_GUI_HAIKU // No clipboard at the moment. TODO? # endif +# ifdef FEAT_EVAL + // Array of possible formats for this selection, if any. + garray_T formats; +# endif } Clipboard_T; #else typedef int Clipboard_T; // This is required for the prototypes. From f317e4fd433354cc27d1601a230e400bd88bac74 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Wed, 6 May 2026 21:49:59 -0400 Subject: [PATCH 2/9] fix build warnings for now --- src/clipboard.c | 2 +- src/evalfunc.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/clipboard.c b/src/clipboard.c index 3ee9c15e9b21d0..4f9a9594a7c576 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -2334,7 +2334,7 @@ clip_clear_formats(Clipboard_T *cbd) } static int -clip_gen_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) +clip_gen_request_format(Clipboard_T *cbd UNUSED, char_u *format UNUSED, garray_T *ga UNUSED) { # if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) # ifdef FEAT_GUI diff --git a/src/evalfunc.c b/src/evalfunc.c index fc24364874a75b..9390dc5dd2fd60 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6567,7 +6567,10 @@ f_getreg(typval_T *argvars, typval_T *rettv) if (fmt == NULL) return; + blob = clip_get_selection_format(cbd, fmt); + if (blob == NULL) + blob = blob_alloc(); if (blob == NULL) return; From 0a26813982d29d06f7228f88c906a3269c8e8886 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Wed, 6 May 2026 23:58:08 -0400 Subject: [PATCH 3/9] fix wayland primary --- src/clipboard.c | 334 ++++++++++++++++++++++++++++++++-------- src/evalfunc.c | 19 ++- src/os_unix.c | 21 +++ src/proto/clipboard.pro | 4 + src/proto/os_unix.pro | 2 + 5 files changed, 313 insertions(+), 67 deletions(-) diff --git a/src/clipboard.c b/src/clipboard.c index 4f9a9594a7c576..2ecd97e6898c80 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -83,8 +83,10 @@ static const char *supported_mimes[] = { clip_wl_T clip_wl; -static int clip_wl_receive_data(Clipboard_T *cbd, const char *mime_type, int fd, garray_T *ga); +static int clip_wl_receive_data(int fd, garray_T *ga); +# ifdef FEAT_EVAL static int clip_wl_request_format(Clipboard_T *cbd, const char *format, garray_T *ga, bool roundtrip); +# endif static void clip_wl_request_selection(Clipboard_T *cbd); static int clip_wl_own_selection(Clipboard_T *cbd); static void clip_wl_lose_selection(Clipboard_T *cbd); @@ -126,8 +128,10 @@ clip_init(int can_use) cb->end.lnum = 0; cb->end.col = 0; cb->state = SELECT_CLEARED; +#ifdef FEAT_EVAL clip_clear_formats(cb); ga_init2(&cb->formats, sizeof(clipformat_T), 2); +#endif skip: if (cb == &clip_plus) @@ -1754,17 +1758,62 @@ clip_x11_request_selection_cb( *(int *)success = TRUE; } + static void +clip_x11_common_wait(Display *dpy, int *success, int *timed_out) +{ + XEvent event; + time_t start_time; + + // Make sure the request for the selection goes out before waiting for a + // response. + XFlush(dpy); + + start_time = time(NULL); + + while (*success == MAYBE) + { + if (XCheckTypedEvent(dpy, PropertyNotify, &event) + || XCheckTypedEvent(dpy, SelectionNotify, &event) + || XCheckTypedEvent(dpy, SelectionRequest, &event)) + { + // This is where clip_x11_request_selection_cb() should be + // called. It may actually happen a bit later, so we loop + // until "success" changes. + // We may get a SelectionRequest here and if we don't handle + // it we hang. KDE klipper does this, for example. + // We need to handle a PropertyNotify for large selections. + XtDispatchEvent(&event); + continue; + } + + // Time out after 2 to 3 seconds to avoid that we hang when the + // other process doesn't respond. Note that the SelectionNotify + // event may still come later when the selection owner comes back + // to life and the text gets inserted unexpectedly. Don't know + // why that happens or how to avoid that :-(. + if (time(NULL) > start_time + 2) + { + *timed_out = TRUE; + break; + } + + // Do we need this? Probably not. + XSync(dpy, False); + + // Wait for 1 msec to avoid that we eat up all CPU time. + ui_delay(1L, TRUE); + } +} + void clip_x11_request_selection( Widget myShell, Display *dpy, Clipboard_T *cbd) { - XEvent event; Atom type; static int success; int i; - time_t start_time; int timed_out = FALSE; for (i = 0; i < 6; i++) @@ -1790,49 +1839,12 @@ clip_x11_request_selection( XtGetSelectionValue(myShell, cbd->sel_atom, type, clip_x11_request_selection_cb, (XtPointer)&success, CurrentTime); - // Make sure the request for the selection goes out before waiting for - // a response. - XFlush(dpy); - /* * Wait for result of selection request, otherwise if we type more * characters, then they will appear before the one that requested the * paste! Don't worry, we will catch up with any other events later. */ - start_time = time(NULL); - while (success == MAYBE) - { - if (XCheckTypedEvent(dpy, PropertyNotify, &event) - || XCheckTypedEvent(dpy, SelectionNotify, &event) - || XCheckTypedEvent(dpy, SelectionRequest, &event)) - { - // This is where clip_x11_request_selection_cb() should be - // called. It may actually happen a bit later, so we loop - // until "success" changes. - // We may get a SelectionRequest here and if we don't handle - // it we hang. KDE klipper does this, for example. - // We need to handle a PropertyNotify for large selections. - XtDispatchEvent(&event); - continue; - } - - // Time out after 2 to 3 seconds to avoid that we hang when the - // other process doesn't respond. Note that the SelectionNotify - // event may still come later when the selection owner comes back - // to life and the text gets inserted unexpectedly. Don't know - // why that happens or how to avoid that :-(. - if (time(NULL) > start_time + 2) - { - timed_out = TRUE; - break; - } - - // Do we need this? Probably not. - XSync(dpy, False); - - // Wait for 1 msec to avoid that we eat up all CPU time. - ui_delay(1L, TRUE); - } + clip_x11_common_wait(dpy, &success, &timed_out); if (success == TRUE) return; @@ -1847,6 +1859,138 @@ clip_x11_request_selection( yank_cut_buffer0(dpy, cbd); } +# ifdef FEAT_EVAL + + static void +clip_x11_update_formats_cb( + Widget w UNUSED, + XtPointer success, + Atom *sel_atom, + Atom *type, + XtPointer value, + long_u *length, + int *format) +{ + Display *dpy = XtDisplay(w); + Clipboard_T *cbd; + Atom *targets = (Atom *)value; + + if (*sel_atom == clip_plus.sel_atom) + cbd = &clip_plus; + else + cbd = &clip_star; + + if (value == NULL || *length == 0 + || *type == XT_CONVERT_FAIL || *type != XA_ATOM) + { + clip_clear_formats(cbd); + *(int *)success = FALSE; + return; + } + + for (long_u i = 0; i < *length; i++) + { + char *name = XGetAtomName(dpy, targets[i]); + + if (format != NULL) + { + clip_add_format(cbd, (char_u *)name, NULL); + XFree(name); + } + } + *(int *)success = TRUE; + + XtFree(value); +} + +/* + * Do a roundtrip for the TARGETS request to get the formats supported for the + * current selection/clipboard, and store it in "cbd->formats". + */ + void +clip_x11_update_formats(Widget myShell, Display *dpy, Clipboard_T *cbd) +{ + static int success; + int timed_out = FALSE; + + clip_clear_formats(cbd); + + success = MAYBE; + XtGetSelectionValue(myShell, cbd->sel_atom, targets_atom, + clip_x11_update_formats_cb, &success, CurrentTime); + + clip_x11_common_wait(dpy, &success, &timed_out); + + if (success == TRUE) + return; + + clip_clear_formats(cbd); +} + +typedef struct +{ + garray_T *buf; + int *success; +} request_format_data_T; + + static void +clip_x11_request_format_cb( + Widget w UNUSED, + XtPointer ptr, + Atom *sel_atom UNUSED, + Atom *type UNUSED, + XtPointer value, + long_u *length, + int *format UNUSED) +{ + int *success = ((request_format_data_T *)ptr)->success; + garray_T *buf = ((request_format_data_T *)ptr)->buf; + char_u *p = (char_u *)value; + long_u len = *length; + + ga_concat_len(buf, p, len); + *success = TRUE; + XtFree(value); +} + + int +clip_x11_request_format( + Widget myShell, + Display *dpy, + Clipboard_T *cbd, + char_u *format, + garray_T *ga) +{ + Atom type; + static int success; + int timed_out = FALSE; + + request_format_data_T udata; + + // Check if format is supported + clip_x11_update_formats(myShell, dpy, cbd); + if (!clip_format_is_supported(cbd, format)) + return FAIL; + + type = XInternAtom(dpy, (char *)format, False); + + success = MAYBE; + udata.success = &success; + udata.buf = ga; + + XtGetSelectionValue(myShell, cbd->sel_atom, type, + clip_x11_request_format_cb, (XtPointer)&udata, CurrentTime); + + clip_x11_common_wait(dpy, &success, &timed_out); + + if (success == TRUE) + return OK; + + return FAIL; +} + +# endif // FEAT_EVAL + void clip_x11_lose_selection(Widget myShell, Clipboard_T *cbd) { @@ -2339,7 +2483,7 @@ clip_gen_request_format(Clipboard_T *cbd UNUSED, char_u *format UNUSED, garray_T # if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) # ifdef FEAT_GUI if (gui.in_use) - /* clip_mch_request_selection(cbd); */ + /* clip_mch_request_selection(cbd); */; else # endif { @@ -2352,7 +2496,7 @@ clip_gen_request_format(Clipboard_T *cbd UNUSED, char_u *format UNUSED, garray_T else if (clipmethod == CLIPMETHOD_X11) { # ifdef FEAT_XCLIPBOARD - /* clip_xterm_request_selection(cbd); */ + return clip_xterm_request_format(cbd, format, ga); # endif } } @@ -2373,7 +2517,7 @@ clip_get_selection_format(Clipboard_T *cbd, char_u *format) garray_T ga; blob = blob_alloc(); - if(blob == NULL) + if (blob == NULL) return NULL; ga_init2(&ga, 1, 512); @@ -2408,6 +2552,54 @@ clip_get_selection_format(Clipboard_T *cbd, char_u *format) return NULL; } +/* + * Note that a roundtrip or an update should be done before this, so everything + * is up to date. + */ + bool +clip_format_is_supported(Clipboard_T *cbd, char_u *format) +{ + for (int i = 0; i formats.ga_len; i++) + { + clipformat_T *fmt = &((clipformat_T *)cbd->formats.ga_data)[i]; + + if (STRCMP(fmt->name, format) == 0) + return true; + } + return false; +} + +/* + * Update the list of supported mime types for cbd. + */ + void +clip_update_supported_formats(Clipboard_T *cbd UNUSED) +{ +# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) +# ifdef FEAT_GUI + if (gui.in_use) + /* clip_mch_request_selection(cbd); */; + else +# endif + { + if (clipmethod == CLIPMETHOD_WAYLAND) + { +# ifdef FEAT_WAYLAND_CLIPBOARD + vwl_connection_roundtrip(wayland_ct); +# endif + } + else if (clipmethod == CLIPMETHOD_X11) + { +# ifdef FEAT_XCLIPBOARD + clip_xterm_update_formats(cbd); +# endif + } + } +# else + /* clip_mch_request_selection(cbd); */ +# endif +} + # endif # if defined(FEAT_WAYLAND_CLIPBOARD) @@ -2461,18 +2653,12 @@ clip_wl_get_selection_type(clip_wl_selection_T *sel) static bool wl_data_offer_listener_event_offer( - void *data, + void *data UNUSED, vwl_data_offer_T *offer UNUSED, - const char *mime_type + const char *mime_type UNUSED ) { - clip_add_format(data, (char_u *)mime_type, NULL); - - // Only accept mime type if we support it for the default format - for (int i = 0; i < (int)ARRAY_LENGTH(supported_mimes); i++) - if (STRCMP(mime_type, supported_mimes[i]) == 0) - return true; - return FALSE; + return true; } static const vwl_data_offer_listener_T vwl_data_offer_listener = { @@ -2485,7 +2671,6 @@ vwl_data_device_listener_event_data_offer( vwl_data_device_T *device UNUSED, vwl_data_offer_T *offer) { - clip_clear_formats(data); // Immediately start listening for offer events from the data offer vwl_data_offer_add_listener(offer, &vwl_data_offer_listener, data); } @@ -2498,10 +2683,26 @@ vwl_data_device_listener_event_selection( wayland_selection_T selection) { clip_wl_selection_T *sel = clip_wl_get_selection(selection); + Clipboard_T *cbd = clip_wl_get_cbd_from_selection(sel); // Destroy previous offer if any, it is now invalid vwl_data_offer_destroy(sel->offer); +#ifdef FEAT_EVAL + // Add formats (aka mime types) to clipboard + clip_clear_formats(cbd); + if (offer != NULL) + { + for (int i = 0; i < offer->mime_types.ga_len; i++) + { + char_u *fmt = ((char_u **)offer->mime_types.ga_data)[i]; + clip_add_format(cbd, fmt, NULL); + } + } +#else + (void)cbd; +#endif + // There are two cases when sel->offer is NULL // 1. No one owns the selection // 2. We own the selection (we'll just access the register directly) @@ -2634,12 +2835,12 @@ clip_init_wayland(void) // Start listening for selection updates if (clip_wl.regular.device != NULL) vwl_data_device_add_listener(clip_wl.regular.device, - &vwl_data_device_listener, &clip_plus); + &vwl_data_device_listener, NULL); // Don't want to listen to the same data device twice if (clip_wl.primary.device != NULL && clip_wl.primary.device != clip_wl.regular.device) vwl_data_device_add_listener(clip_wl.primary.device, - &vwl_data_device_listener, &clip_star); + &vwl_data_device_listener, NULL); return OK; } @@ -2700,11 +2901,7 @@ clip_reset_wayland(void) * be initialized first). Returns OK on success and FAIL on failure. */ static int -clip_wl_receive_data( - Clipboard_T *cbd, - const char *mime_type, - int fd, - garray_T *buf) +clip_wl_receive_data(int fd, garray_T *buf) { char_u *start; ssize_t r = 0; @@ -2771,6 +2968,7 @@ clip_wl_receive_data( return OK; } +#ifdef FEAT_EVAL /* * Request the selection data for the given format (mime type). "ga" will be * initialized. Returns OK on success and FAIL on failure. @@ -2791,8 +2989,15 @@ clip_wl_request_format( // Dispatch any events that still queued up before checking for a data // offer. - if (roundtrip && vwl_connection_roundtrip(wayland_ct) == FAIL) - return FAIL; + if (roundtrip) + { + if (vwl_connection_roundtrip(wayland_ct) == FAIL) + return FAIL; + + // Check if mime type is advertised by the source client + if (!clip_format_is_supported(cbd, (char_u *)format)) + return FAIL; + } if (sel->offer == NULL) return FAIL; @@ -2806,12 +3011,13 @@ clip_wl_request_format( // closes their end we receive an EOF. if (vwl_connection_flush(wayland_ct) >= 0) - ret = clip_wl_receive_data(cbd, format, fds[0], ga); + ret = clip_wl_receive_data(fds[0], ga); close(fds[0]); return ret; } +#endif /* * Get the current selection and fill the respective register for cbd with the diff --git a/src/evalfunc.c b/src/evalfunc.c index 9390dc5dd2fd60..35ffdeb6c7b15d 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6549,12 +6549,15 @@ f_getreg(typval_T *argvars, typval_T *rettv) if (!error && argvars[2].v_type != VAR_UNKNOWN) { +#ifdef FEAT_CLIPBOARD Clipboard_T *cbd = clip_get_clipboard(regname); +#endif return_list = (int)tv_get_bool_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { +#ifdef FEAT_CLIPBOARD if (cbd == NULL) { emsg(_(e_invalid_argument)); @@ -6579,6 +6582,10 @@ f_getreg(typval_T *argvars, typval_T *rettv) rettv->vval.v_blob = blob; return; +#else + emsg(_(e_invalid_argument)); + return; +#endif } } if (error) @@ -10455,7 +10462,9 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) long reglen = 0; dict_T *dict; list_T *list; +#ifdef FEAT_CLIPBOARD Clipboard_T *cbd; +#endif if (in_vim9script() && check_for_opt_string_arg(argvars, 0) == FAIL) return; @@ -10517,6 +10526,7 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) } } +#ifdef FEAT_CLIPBOARD cbd = clip_get_clipboard(regname); if (cbd != NULL) { @@ -10526,13 +10536,16 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) if (flist == NULL) return; - for (int i = 0; i < cbd->formats.ga_len; i++) - list_append_string(flist, + clip_update_supported_formats(cbd); + + for (int i = 0; i < cbd->formats.ga_len; i++) + list_append_string(flist, ((clipformat_T *)cbd->formats.ga_data)[i].name, -1); - list->lv_refcount++; + list->lv_refcount++; (void)dict_add_list(dict, "formats", flist); } +#endif } static void diff --git a/src/os_unix.c b/src/os_unix.c index 12285b8e48c21c..b79350b470b9ea 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -8712,6 +8712,27 @@ clip_xterm_request_selection(Clipboard_T *cbd) clip_x11_request_selection(xterm_Shell, xterm_dpy, cbd); } +# ifdef FEAT_EVAL + void +clip_xterm_update_formats(Clipboard_T *cbd) +{ + if (xterm_Shell != (Widget)0) + clip_x11_update_formats(xterm_Shell, xterm_dpy, cbd); +} + + int +clip_xterm_request_format( + Clipboard_T *cbd, + char_u *format, + garray_T *ga) +{ + if (xterm_Shell != (Widget)0) + return clip_x11_request_format(xterm_Shell, xterm_dpy, cbd, + format, ga); + return FAIL; +} +# endif + void clip_xterm_set_selection(Clipboard_T *cbd) { diff --git a/src/proto/clipboard.pro b/src/proto/clipboard.pro index 0ac9cf40caa218..6dac80eac54dcf 100644 --- a/src/proto/clipboard.pro +++ b/src/proto/clipboard.pro @@ -22,6 +22,8 @@ void open_app_context(void); void x11_setup_atoms(Display *dpy); void x11_setup_selection(Widget w); void clip_x11_request_selection(Widget myShell, Display *dpy, Clipboard_T *cbd); +void clip_x11_update_formats(Widget myShell, Display *dpy, Clipboard_T *cbd); +int clip_x11_request_format(Widget myShell, Display *dpy, Clipboard_T *cbd, char_u *format, garray_T *ga); void clip_x11_lose_selection(Widget myShell, Clipboard_T *cbd); int clip_x11_own_selection(Widget myShell, Clipboard_T *cbd); void clip_x11_set_selection(Clipboard_T *cbd); @@ -37,6 +39,8 @@ Clipboard_T *clip_get_clipboard(int regname); void clip_add_format(Clipboard_T *cbd, char_u *format, blob_T *blob); void clip_clear_formats(Clipboard_T *cbd); blob_T *clip_get_selection_format(Clipboard_T *cbd, char_u *format); +bool clip_format_is_supported(Clipboard_T *cbd, char_u *format); +void clip_update_supported_formats(Clipboard_T *cbd); int clip_init_wayland(void); void clip_uninit_wayland(void); int clip_reset_wayland(void); diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro index 7513e400f04600..506f26005bc38c 100644 --- a/src/proto/os_unix.pro +++ b/src/proto/os_unix.pro @@ -88,6 +88,8 @@ void xterm_update(void); int clip_xterm_own_selection(Clipboard_T *cbd); void clip_xterm_lose_selection(Clipboard_T *cbd); void clip_xterm_request_selection(Clipboard_T *cbd); +void clip_xterm_update_formats(Clipboard_T *cbd); +int clip_xterm_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga); void clip_xterm_set_selection(Clipboard_T *cbd); int xsmp_handle_requests(void); void xsmp_init(void); From c5b2b69eb46d581705841bd4680d60871df925a9 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 13:34:43 -0400 Subject: [PATCH 4/9] support gtk gui --- src/clipboard.c | 48 +++++++++++++++---------------- src/gui_gtk_x11.c | 60 +++++++++++++++++++++++++++++++++++++++ src/proto/gui_gtk_x11.pro | 2 ++ src/vim.h | 2 +- 4 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/clipboard.c b/src/clipboard.c index 2ecd97e6898c80..ab1baba96ec3b1 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -2483,7 +2483,7 @@ clip_gen_request_format(Clipboard_T *cbd UNUSED, char_u *format UNUSED, garray_T # if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) # ifdef FEAT_GUI if (gui.in_use) - /* clip_mch_request_selection(cbd); */; + return clip_mch_request_format(cbd, format, ga); else # endif { @@ -2520,25 +2520,24 @@ clip_get_selection_format(Clipboard_T *cbd, char_u *format) if (blob == NULL) return NULL; - ga_init2(&ga, 1, 512); + ga_init2(&ga, 1, 4096); - // Check if we already have the contents, from setreg(). TODO - for (int i = 0; i formats.ga_len; i++) - { - clipformat_T *fmt = &((clipformat_T *)cbd->formats.ga_data)[i]; + // Check if we already have the contents,from setreg(). TODO + /* for (int i = 0; i formats.ga_len; i++) */ + /* { */ + /* clipformat_T *fmt = &((clipformat_T *)cbd->formats.ga_data)[i]; */ - if (STRCMP(format, fmt->name) == 0 && fmt->data != NULL) - { - ga_concat_len(&ga, fmt->data, fmt->len); - break; - } - } - if (ga.ga_data == NULL) - { - // Try requesting the contents now - if (clip_gen_request_format(cbd, format, &ga) == FAIL) - goto fail; - } + /* if (STRCMP(format, fmt->name) == 0 && fmt->data != NULL) */ + /* { */ + /* ga_concat_len(&ga, fmt->data, fmt->len); */ + /* break; */ + /* } */ + /* } */ + /* if (ga.ga_data == NULL) */ + + // Try requesting the contents now + if (clip_gen_request_format(cbd, format, &ga) == FAIL) + goto fail; if (ga.ga_data == NULL) goto fail; @@ -2578,7 +2577,7 @@ clip_update_supported_formats(Clipboard_T *cbd UNUSED) # if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) # ifdef FEAT_GUI if (gui.in_use) - /* clip_mch_request_selection(cbd); */; + clip_mch_update_formats(cbd); else # endif { @@ -2897,8 +2896,8 @@ clip_reset_wayland(void) } /* - * Read data from a file descriptor and write it to given grow array (which will - * be initialized first). Returns OK on success and FAIL on failure. + * Read data from a file descriptor and write it to given grow array. Returns OK + * on success and FAIL on failure. */ static int clip_wl_receive_data(int fd, garray_T *buf) @@ -2922,8 +2921,6 @@ clip_wl_receive_data(int fd, garray_T *buf) if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) return FAIL; - ga_init2(buf, 1, 4096); - // 4096 bytes seems reasonable for initial buffer size, memory is cheap // anyways. if (ga_grow(buf, 4096) == FAIL) @@ -2970,8 +2967,8 @@ clip_wl_receive_data(int fd, garray_T *buf) #ifdef FEAT_EVAL /* - * Request the selection data for the given format (mime type). "ga" will be - * initialized. Returns OK on success and FAIL on failure. + * Request the selection data for the given format (mime type). Returns OK on + * success and FAIL on failure. */ static int clip_wl_request_format( @@ -3056,6 +3053,7 @@ clip_wl_request_selection(Clipboard_T *cbd) chosen_mime = supported_mimes[i]; } + ga_init2(&buf, 1, 4096); if (chosen_mime == NULL || clip_wl_request_format(cbd, chosen_mime, &buf, false) == FAIL) goto clear; diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c index 2dab62939452ec..4856928748f75d 100644 --- a/src/gui_gtk_x11.c +++ b/src/gui_gtk_x11.c @@ -7253,6 +7253,66 @@ clip_mch_request_selection(Clipboard_T *cbd) cbd); } +#ifdef FEAT_EVAL +/* + * Request the data of the given format from the clipboard + */ + int +clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) +{ + GtkClipboard *cb; + GdkAtom target; + GtkSelectionData *data; + const guchar *content; + int len; + + cb = gtk_clipboard_get(cbd->gtk_sel_atom); + target = gdk_atom_intern((char *)format, FALSE); + data = gtk_clipboard_wait_for_contents(cb, target); + + if (data != NULL) + { + content = gtk_selection_data_get_data_with_length(data, &len); + ga_concat_len(ga, (char_u *)content, len); + + gtk_selection_data_free(data); + return OK; + } + + return FAIL; +} + +/* + * Get the currently supported formats for the selection and place it in + * "cbd->formats" + */ + void +clip_mch_update_formats(Clipboard_T *cbd) +{ + GtkClipboard *cb; + GdkAtom *targets; + gint n_targets; + + cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + + clip_clear_formats(cbd); + if (gtk_clipboard_wait_for_targets( + cb, + &targets, + &n_targets)) + { + for (gint i = 0; i < n_targets; i++) + { + const gchar *name = gdk_atom_name(targets[i]); + + clip_add_format(cbd, (char_u *)name, NULL); + } + + g_free(targets); + } +} +#endif + /* * Disown the selection. */ diff --git a/src/proto/gui_gtk_x11.pro b/src/proto/gui_gtk_x11.pro index 5a572401355d3a..1162c480367766 100644 --- a/src/proto/gui_gtk_x11.pro +++ b/src/proto/gui_gtk_x11.pro @@ -65,6 +65,8 @@ void gui_mch_clear_all(void); void gui_mch_delete_lines(int row, int num_lines); void gui_mch_insert_lines(int row, int num_lines); void clip_mch_request_selection(Clipboard_T *cbd); +int clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga); +void clip_mch_update_formats(Clipboard_T *cbd); void clip_mch_lose_selection(Clipboard_T *cbd); int clip_mch_own_selection(Clipboard_T *cbd); void clip_mch_set_selection(Clipboard_T *cbd); diff --git a/src/vim.h b/src/vim.h index 16c9782d80ce92..cb356992aa5e13 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2329,7 +2329,7 @@ typedef enum { typedef struct { - char_u *data; // Binary data, may be NULL + char_u *data; // Binary data, may be NULL, currently unused int len; char_u *name; } clipformat_T; From 4c42db3a95b5298c7c50d8982e97fd42d6237ddd Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 13:39:53 -0400 Subject: [PATCH 5/9] put stuff under +clipboard_formats feature --- src/clipboard.c | 65 +++++++++++++++++++++++++---------------------- src/evalfunc.c | 8 +++--- src/feature.h | 9 +++++++ src/gui_gtk_x11.c | 2 +- src/os_unix.c | 2 +- src/vim.h | 2 +- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/clipboard.c b/src/clipboard.c index ab1baba96ec3b1..93a6f6fa4f4111 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -84,7 +84,7 @@ static const char *supported_mimes[] = { clip_wl_T clip_wl; static int clip_wl_receive_data(int fd, garray_T *ga); -# ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS static int clip_wl_request_format(Clipboard_T *cbd, const char *format, garray_T *ga, bool roundtrip); # endif static void clip_wl_request_selection(Clipboard_T *cbd); @@ -128,10 +128,10 @@ clip_init(int can_use) cb->end.lnum = 0; cb->end.col = 0; cb->state = SELECT_CLEARED; -#ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS clip_clear_formats(cb); ga_init2(&cb->formats, sizeof(clipformat_T), 2); -#endif +# endif skip: if (cb == &clip_plus) @@ -1859,7 +1859,7 @@ clip_x11_request_selection( yank_cut_buffer0(dpy, cbd); } -# ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS static void clip_x11_update_formats_cb( @@ -1989,7 +1989,7 @@ clip_x11_request_format( return FAIL; } -# endif // FEAT_EVAL +# endif // FEAT_CLIPBOARD_FORMATS void clip_x11_lose_selection(Widget myShell, Clipboard_T *cbd) @@ -2417,7 +2417,7 @@ may_set_selection(void) } } -# ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS /* * Get the corresponding Clipboard_T struct based on "regname". Returns NULL if @@ -2478,31 +2478,34 @@ clip_clear_formats(Clipboard_T *cbd) } static int -clip_gen_request_format(Clipboard_T *cbd UNUSED, char_u *format UNUSED, garray_T *ga UNUSED) +clip_gen_request_format( + Clipboard_T *cbd UNUSED, + char_u *format UNUSED, + garray_T *ga UNUSED) { -# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) -# ifdef FEAT_GUI +# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) +# ifdef FEAT_GUI if (gui.in_use) return clip_mch_request_format(cbd, format, ga); else -# endif +# endif { if (clipmethod == CLIPMETHOD_WAYLAND) { -# ifdef FEAT_WAYLAND_CLIPBOARD +# ifdef FEAT_WAYLAND_CLIPBOARD return clip_wl_request_format(cbd, (char *)format, ga, true); -# endif +# endif } else if (clipmethod == CLIPMETHOD_X11) { -# ifdef FEAT_XCLIPBOARD +# ifdef FEAT_XCLIPBOARD return clip_xterm_request_format(cbd, format, ga); -# endif +# endif } } -# else - /* clip_mch_request_selection(cbd); */ -# endif +# else + return clip_mch_request_format(cbd, format, ga); +# endif return FAIL; } @@ -2574,32 +2577,32 @@ clip_format_is_supported(Clipboard_T *cbd, char_u *format) void clip_update_supported_formats(Clipboard_T *cbd UNUSED) { -# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) -# ifdef FEAT_GUI +# if defined(FEAT_XCLIPBOARD) || defined(FEAT_WAYLAND_CLIPBOARD) +# ifdef FEAT_GUI if (gui.in_use) clip_mch_update_formats(cbd); else -# endif +# endif { if (clipmethod == CLIPMETHOD_WAYLAND) { -# ifdef FEAT_WAYLAND_CLIPBOARD +# ifdef FEAT_WAYLAND_CLIPBOARD vwl_connection_roundtrip(wayland_ct); -# endif +# endif } else if (clipmethod == CLIPMETHOD_X11) { -# ifdef FEAT_XCLIPBOARD +# ifdef FEAT_XCLIPBOARD clip_xterm_update_formats(cbd); -# endif +# endif } } -# else - /* clip_mch_request_selection(cbd); */ -# endif +# else + clip_mch_request_selection(cbd); +# endif } -# endif +# endif // FEAT_CLIPBOARD_FORMATS # if defined(FEAT_WAYLAND_CLIPBOARD) @@ -2687,7 +2690,7 @@ vwl_data_device_listener_event_selection( // Destroy previous offer if any, it is now invalid vwl_data_offer_destroy(sel->offer); -#ifdef FEAT_EVAL +#ifdef FEAT_CLIPBOARD_FORMATS // Add formats (aka mime types) to clipboard clip_clear_formats(cbd); if (offer != NULL) @@ -2965,7 +2968,7 @@ clip_wl_receive_data(int fd, garray_T *buf) return OK; } -#ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS /* * Request the selection data for the given format (mime type). Returns OK on * success and FAIL on failure. @@ -3014,7 +3017,7 @@ clip_wl_request_format( return ret; } -#endif +# endif /* * Get the current selection and fill the respective register for cbd with the diff --git a/src/evalfunc.c b/src/evalfunc.c index 35ffdeb6c7b15d..e4cebf098a72f8 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6549,7 +6549,7 @@ f_getreg(typval_T *argvars, typval_T *rettv) if (!error && argvars[2].v_type != VAR_UNKNOWN) { -#ifdef FEAT_CLIPBOARD +#ifdef FEAT_CLIPBOARD_FORMATS Clipboard_T *cbd = clip_get_clipboard(regname); #endif @@ -6557,7 +6557,7 @@ f_getreg(typval_T *argvars, typval_T *rettv) if (argvars[3].v_type != VAR_UNKNOWN) { -#ifdef FEAT_CLIPBOARD +#ifdef FEAT_CLIPBOARD_FORMATS if (cbd == NULL) { emsg(_(e_invalid_argument)); @@ -10462,7 +10462,7 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) long reglen = 0; dict_T *dict; list_T *list; -#ifdef FEAT_CLIPBOARD +#ifdef FEAT_CLIPBOARD_FORMATS Clipboard_T *cbd; #endif @@ -10526,7 +10526,7 @@ f_getreginfo(typval_T *argvars, typval_T *rettv) } } -#ifdef FEAT_CLIPBOARD +#ifdef FEAT_CLIPBOARD_FORMATS cbd = clip_get_clipboard(regname); if (cbd != NULL) { diff --git a/src/feature.h b/src/feature.h index 3f3c67a17673ec..85f21af872173a 100644 --- a/src/feature.h +++ b/src/feature.h @@ -925,6 +925,15 @@ # endif #endif +/* + * +clipboard_formats Support for handling arbitrary formats from the + * clipboard. + */ +#if defined(FEAT_CLIPBOARD) && (defined(FEAT_WAYLAND_CLIPBOARD) || \ + defined(FEAT_GUI_GTK) || defined(FEAT_XCLIPBOARD)) +# define FEAT_CLIPBOARD_FORMATS +#endif + /* * +dnd Drag'n'drop support. Always used for the GTK+ GUI. */ diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c index 4856928748f75d..43b8d71fc81f73 100644 --- a/src/gui_gtk_x11.c +++ b/src/gui_gtk_x11.c @@ -7253,7 +7253,7 @@ clip_mch_request_selection(Clipboard_T *cbd) cbd); } -#ifdef FEAT_EVAL +#ifdef FEAT_CLIPBOARD_FORMATS /* * Request the data of the given format from the clipboard */ diff --git a/src/os_unix.c b/src/os_unix.c index b79350b470b9ea..62404badf322bc 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -8712,7 +8712,7 @@ clip_xterm_request_selection(Clipboard_T *cbd) clip_x11_request_selection(xterm_Shell, xterm_dpy, cbd); } -# ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS void clip_xterm_update_formats(Clipboard_T *cbd) { diff --git a/src/vim.h b/src/vim.h index cb356992aa5e13..50e868af0545b7 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2376,7 +2376,7 @@ typedef struct # ifdef FEAT_GUI_HAIKU // No clipboard at the moment. TODO? # endif -# ifdef FEAT_EVAL +# ifdef FEAT_CLIPBOARD_FORMATS // Array of possible formats for this selection, if any. garray_T formats; # endif From 5b50c553aa2ba8db7221c0702018a9c05e6c00a3 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 16:02:53 -0400 Subject: [PATCH 6/9] add win32 support --- src/clipboard.c | 2 +- src/feature.h | 13 +++- src/os_win32.h | 1 + src/proto/winclip.pro | 2 + src/winclip.c | 147 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 4 deletions(-) diff --git a/src/clipboard.c b/src/clipboard.c index 93a6f6fa4f4111..afb1377b41c6f1 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -2598,7 +2598,7 @@ clip_update_supported_formats(Clipboard_T *cbd UNUSED) } } # else - clip_mch_request_selection(cbd); + clip_mch_update_formats(cbd); # endif } diff --git a/src/feature.h b/src/feature.h index 85f21af872173a..c2b92be9739f86 100644 --- a/src/feature.h +++ b/src/feature.h @@ -900,9 +900,13 @@ #ifdef FEAT_CYGWIN_WIN32_CLIPBOARD # define FEAT_CLIPBOARD +# define FEAT_CLIPBOARD_FORMATS #endif #ifdef FEAT_GUI +# ifdef FEAT_GUI_GTK +# define FEAT_CLIPBOARD_FORMATS +# endif # ifndef FEAT_CLIPBOARD # define FEAT_CLIPBOARD # endif @@ -915,6 +919,7 @@ # ifndef FEAT_CLIPBOARD # define FEAT_CLIPBOARD # endif +# define FEAT_CLIPBOARD_FORMATS #endif #if defined(FEAT_NORMAL) && defined(UNIX) \ @@ -923,15 +928,17 @@ # ifndef FEAT_CLIPBOARD # define FEAT_CLIPBOARD # endif +# define FEAT_CLIPBOARD_FORMATS #endif /* * +clipboard_formats Support for handling arbitrary formats from the * clipboard. */ -#if defined(FEAT_CLIPBOARD) && (defined(FEAT_WAYLAND_CLIPBOARD) || \ - defined(FEAT_GUI_GTK) || defined(FEAT_XCLIPBOARD)) -# define FEAT_CLIPBOARD_FORMATS +#if !defined(FEAT_CLIPBOARD) || !defined(FEAT_EVAL) +# ifdef FEAT_CLIPBOARD_FORMATS +# undef FEAT_CLIPBOARD_FORMATS +# endif #endif /* diff --git a/src/os_win32.h b/src/os_win32.h index cfdff674fd4512..327ad04163d92e 100644 --- a/src/os_win32.h +++ b/src/os_win32.h @@ -52,6 +52,7 @@ #define USE_FNAME_CASE // adjust case of file names #if !defined(FEAT_CLIPBOARD) +# define FEAT_CLIPBOARD_FORMATS // include support for arbitrary formats # define FEAT_CLIPBOARD // include clipboard support #endif #if defined(__DATE__) && defined(__TIME__) diff --git a/src/proto/winclip.pro b/src/proto/winclip.pro index c7cba71a292240..978b1eaa462e45 100644 --- a/src/proto/winclip.pro +++ b/src/proto/winclip.pro @@ -7,6 +7,8 @@ void win_clip_init(void); int clip_mch_own_selection(Clipboard_T *cbd); void clip_mch_lose_selection(Clipboard_T *cbd); void clip_mch_request_selection(Clipboard_T *cbd); +int clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga); +void clip_mch_update_formats(Clipboard_T *cbd); void clip_mch_set_selection(Clipboard_T *cbd); short_u *enc_to_utf16(char_u *str, int *lenp); char_u *utf16_to_enc(short_u *str, int *lenp); diff --git a/src/winclip.c b/src/winclip.c index 57f057287b1adf..a82082a2de4cb9 100644 --- a/src/winclip.c +++ b/src/winclip.c @@ -431,6 +431,153 @@ clip_mch_request_selection(Clipboard_T *cbd) vim_free(to_free); } +#ifdef FEAT_CLIPBOARD_FORMATS +/* + * Get the format id associated with "name". Returns zero on failure. + */ +UINT +get_format_id(char_u *name) +{ + static const struct + { + const char *name; + UINT fmt; + } standard_formats[] = { + {"CF_TEXT", CF_TEXT}, + {"CF_BITMAP", CF_BITMAP}, + {"CF_METAFILEPICT", CF_METAFILEPICT}, + {"CF_SYLK", CF_SYLK}, + {"CF_DIF", CF_DIF}, + {"CF_TIFF", CF_TIFF}, + {"CF_OEMTEXT", CF_OEMTEXT}, + {"CF_DIB", CF_DIB}, + {"CF_PALETTE", CF_PALETTE}, + {"CF_PENDATA", CF_PENDATA}, + {"CF_RIFF", CF_RIFF}, + {"CF_WAVE", CF_WAVE}, + {"CF_UNICODETEXT", CF_UNICODETEXT}, + {"CF_ENHMETAFILE", CF_ENHMETAFILE}, + {"CF_HDROP", CF_HDROP}, + {"CF_LOCALE", CF_LOCALE}, + {"CF_DIBV5", CF_DIBV5}, + {"CF_OWNERDISPLAY", CF_OWNERDISPLAY}, + {"CF_DSPTEXT", CF_DSPTEXT}, + {"CF_DSPBITMAP", CF_DSPBITMAP}, + {"CF_DSPMETAFILEPICT", CF_DSPMETAFILEPICT}, + {"CF_DSPENHMETAFILE", CF_DSPENHMETAFILE}, + }; + + for (int i = 0; i < ARRAY_LENGTH(standard_formats); i++) + if (STRCMP(standard_formats[i].name, name) == 0) + return standard_formats[i].fmt; + + // Not a standard format, try registered formats + return RegisterClipboardFormat((char *)name); +} + + int +clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) +{ + HGLOBAL hMemW; + UINT fmt; + + if (!vim_open_clipboard()) + return FAIL; + + fmt = get_format_id(format); + if (fmt == 0 || !IsClipboardFormatAvailable(fmt)) + { + CloseClipboard(); + return FAIL; + } + + if ((hMemW = GetClipboardData(fmt)) != NULL) + { + int len = (int)GlobalSize(hMemW); + WCHAR *hMemWstr = (WCHAR *)GlobalLock(hMemW); + + ga_concat_len(ga, (char_u *)hMemWstr, len); + GlobalUnlock(hMemW); + } + + CloseClipboard(); + + return OK; +} + + const WCHAR * +get_standard_name(UINT fmt) { + switch (fmt) { + case CF_TEXT: return L"CF_TEXT"; + case CF_BITMAP: return L"CF_BITMAP"; + case CF_METAFILEPICT: return L"CF_METAFILEPICT"; + case CF_SYLK: return L"CF_SYLK"; + case CF_DIF: return L"CF_DIF"; + case CF_TIFF: return L"CF_TIFF"; + case CF_OEMTEXT: return L"CF_OEMTEXT"; + case CF_DIB: return L"CF_DIB"; + case CF_PALETTE: return L"CF_PALETTE"; + case CF_PENDATA: return L"CF_PENDATA"; + case CF_RIFF: return L"CF_RIFF"; + case CF_WAVE: return L"CF_WAVE"; + case CF_UNICODETEXT: return L"CF_UNICODETEXT"; + case CF_ENHMETAFILE: return L"CF_ENHMETAFILE"; + case CF_HDROP: return L"CF_HDROP"; + case CF_LOCALE: return L"CF_LOCALE"; + case CF_DIBV5: return L"CF_DIBV5"; + case CF_OWNERDISPLAY: return L"CF_OWNERDISPLAY"; + case CF_DSPTEXT: return L"CF_DSPTEXT"; + case CF_DSPBITMAP: return L"CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: return L"CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: return L"CF_DSPENHMETAFILE"; + default: return NULL; + } +} + + const WCHAR * +get_format_name(UINT fmt) { + const WCHAR *standard; + static WCHAR name[256]; + + // Try standard formats first + standard = get_standard_name(fmt); + if (standard != NULL) + return standard; + + // Try registered formats + if (GetClipboardFormatNameW(fmt, name, 256) > 0) + return name; + + // Unknown clipboard format or private + vim_snprintf((char *)name, 256, "%x", fmt); + return name; +} + + void +clip_mch_update_formats(Clipboard_T *cbd) +{ + UINT fmt = 0; + + clip_clear_formats(cbd); + if (!vim_open_clipboard()) + return; + + while ((fmt = EnumClipboardFormats(fmt)) != 0) + { + const WCHAR *wname = get_format_name(fmt); + char_u *name = utf16_to_enc((short_u *)wname, NULL); + + if (name != NULL) + { + clip_add_format(cbd, name, NULL); + vim_free(name); + } + } + + CloseClipboard(); +} +#endif + /* * Send the current selection to the clipboard. */ From be73c02762e3a68139abffd70bc098dfba0b7e39 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 16:26:21 -0400 Subject: [PATCH 7/9] add docs and feature --- runtime/doc/builtin.txt | 33 +++++++++++++++++++++++++++++++-- runtime/doc/tags | 2 ++ runtime/doc/various.txt | 2 ++ src/evalfunc.c | 7 +++++++ src/version.c | 5 +++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index e9036c18f10617..cb0d82149974a3 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -5019,7 +5019,7 @@ getqflist([{what}]) *getqflist()* Return type: list> or dict -getreg([{regname} [, 1 [, {list}]]]) *getreg()* +getreg([{regname} [, 1 [, {list} [, {format}]]]]) *getreg()* The result is a String, which is the contents of register {regname}. Example: > :let cliptext = getreg('*') @@ -5040,6 +5040,15 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()* (see |NL-used-for-Nul|). When the register was not set an empty list is returned. + If {regname} is a clipboard register ("+" or "*"), then + {format} is allowed, which specifies the which clipboard + format to return as a |Blob|. If {format} is specified, then + the other optional arguments are ignored. To see what formats + are available, see |getreginfo()|. To see how formats are + named, see |clipboard-format-naming|. + {only available when compiled with the |+clipboard_formats| + feature} + If {regname} is "", the unnamed register '"' is used. If {regname} is not specified, |v:register| is used. In |Vim9-script| {regname} must be one character. @@ -5047,7 +5056,8 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()* Can also be used as a |method|: > GetRegname()->getreg() < - Return type: |String| or list depending on {list} + Return type: |String| or list or |Blob| depending on + {list} and {format} getreginfo([{regname}]) *getreginfo()* @@ -5068,6 +5078,13 @@ getreginfo([{regname}]) *getreginfo()* with `dd`, this field will be "1", which is the register that got the deleted text. + formats If {regname} is a clipboard register + ("+" or "*"), then this will be added + to the dictionary, containing a list + of all formats supported by the + clipboard. + {only available when compiled with the + |+clipboard_formats| feature} The {regname} argument is a string. If {regname} is invalid or not set, an empty Dictionary will be returned. @@ -5076,6 +5093,18 @@ getreginfo([{regname}]) *getreginfo()* The returned Dictionary can be passed to |setreg()|. In |Vim9-script| {regname} must be one character. + *clipboard-format-naming* + Note that how the clipboard formats are named (for "formats"), + is system dependent: + wayland Usually a mime type. + x11 Mime types or UPPERCASE style names. + win32 Capitalized name (e.g. "HTML Format"). + However, for standard clipboard + formats, which do not have a + registered name, it will be the same as + its enumeration. For example, the + format CF_TEXT will be "CF_TEXT". + Can also be used as a |method|: > GetRegname()->getreginfo() < diff --git a/runtime/doc/tags b/runtime/doc/tags index 4ad699ed55db93..e07ec9f0370116 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1428,6 +1428,7 @@ $quote eval.txt /*$quote* +cindent various.txt /*+cindent* +clientserver various.txt /*+clientserver* +clipboard various.txt /*+clipboard* ++clipboard_formats various.txt /*+clipboard_formats* +clipboard_provider various.txt /*+clipboard_provider* +clipboard_working various.txt /*+clipboard_working* +cmd editing.txt /*+cmd* @@ -6730,6 +6731,7 @@ clipboard-autoselect options.txt /*clipboard-autoselect* clipboard-autoselectml options.txt /*clipboard-autoselectml* clipboard-autoselectplus options.txt /*clipboard-autoselectplus* clipboard-exclude options.txt /*clipboard-exclude* +clipboard-format-naming builtin.txt /*clipboard-format-naming* clipboard-html options.txt /*clipboard-html* clipboard-providers eval.txt /*clipboard-providers* clipboard-providers-available eval.txt /*clipboard-providers-available* diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index e60a557dfdbf52..e527dc725677d3 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -380,6 +380,8 @@ m *+channel* inter process communication |channel| T *+cindent* 'cindent', C indenting; Always enabled N *+clientserver* Unix and Win32: Remote invocation |clientserver| *+clipboard* |clipboard| support compiled-in + *+clipboard_formats* Support for handling arbitrary clipboard formats. + Only on MS-Windows, Wayland, or X11. *+clipboard_working* |clipboard| support compiled-in and working *+clipboard_provider* |clipboard-providers| support compiled-in T *+cmdline_compl* command line completion |cmdline-completion| diff --git a/src/evalfunc.c b/src/evalfunc.c index e4cebf098a72f8..8730280a506857 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6972,6 +6972,13 @@ f_has(typval_T *argvars, typval_T *rettv) 1 #else 0 +#endif + }, + {"clipboard_formats", +#ifdef FEAT_CLIPBOARD_FORMATS + 1 +#else + 0 #endif }, {"clipboard_provider", diff --git a/src/version.c b/src/version.c index 78efc6c77729fc..28f568f1ee0345 100644 --- a/src/version.c +++ b/src/version.c @@ -156,6 +156,11 @@ static char *(features[]) = #else "-clipboard", #endif +#ifdef FEAT_CLIPBOARD_FORMATS + "+clipboard_formats", +#else + "-clipboard_formats", +#endif #ifdef FEAT_CLIPBOARD_PROVIDER "+clipboard_provider", #else From 91f83575aeb4c12bd6b2238d06f36850c084f4ce Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 16:41:46 -0400 Subject: [PATCH 8/9] small fix --- src/winclip.c | 121 +++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/src/winclip.c b/src/winclip.c index a82082a2de4cb9..22a1ebe861993d 100644 --- a/src/winclip.c +++ b/src/winclip.c @@ -435,7 +435,7 @@ clip_mch_request_selection(Clipboard_T *cbd) /* * Get the format id associated with "name". Returns zero on failure. */ -UINT + static UINT get_format_id(char_u *name) { static const struct @@ -443,33 +443,33 @@ get_format_id(char_u *name) const char *name; UINT fmt; } standard_formats[] = { - {"CF_TEXT", CF_TEXT}, - {"CF_BITMAP", CF_BITMAP}, - {"CF_METAFILEPICT", CF_METAFILEPICT}, - {"CF_SYLK", CF_SYLK}, - {"CF_DIF", CF_DIF}, - {"CF_TIFF", CF_TIFF}, - {"CF_OEMTEXT", CF_OEMTEXT}, - {"CF_DIB", CF_DIB}, - {"CF_PALETTE", CF_PALETTE}, - {"CF_PENDATA", CF_PENDATA}, - {"CF_RIFF", CF_RIFF}, - {"CF_WAVE", CF_WAVE}, - {"CF_UNICODETEXT", CF_UNICODETEXT}, - {"CF_ENHMETAFILE", CF_ENHMETAFILE}, - {"CF_HDROP", CF_HDROP}, - {"CF_LOCALE", CF_LOCALE}, - {"CF_DIBV5", CF_DIBV5}, - {"CF_OWNERDISPLAY", CF_OWNERDISPLAY}, - {"CF_DSPTEXT", CF_DSPTEXT}, - {"CF_DSPBITMAP", CF_DSPBITMAP}, - {"CF_DSPMETAFILEPICT", CF_DSPMETAFILEPICT}, - {"CF_DSPENHMETAFILE", CF_DSPENHMETAFILE}, + {"CF_TEXT", CF_TEXT}, + {"CF_BITMAP", CF_BITMAP}, + {"CF_METAFILEPICT", CF_METAFILEPICT}, + {"CF_SYLK", CF_SYLK}, + {"CF_DIF", CF_DIF}, + {"CF_TIFF", CF_TIFF}, + {"CF_OEMTEXT", CF_OEMTEXT}, + {"CF_DIB", CF_DIB}, + {"CF_PALETTE", CF_PALETTE}, + {"CF_PENDATA", CF_PENDATA}, + {"CF_RIFF", CF_RIFF}, + {"CF_WAVE", CF_WAVE}, + {"CF_UNICODETEXT", CF_UNICODETEXT}, + {"CF_ENHMETAFILE", CF_ENHMETAFILE}, + {"CF_HDROP", CF_HDROP}, + {"CF_LOCALE", CF_LOCALE}, + {"CF_DIBV5", CF_DIBV5}, + {"CF_OWNERDISPLAY", CF_OWNERDISPLAY}, + {"CF_DSPTEXT", CF_DSPTEXT}, + {"CF_DSPBITMAP", CF_DSPBITMAP}, + {"CF_DSPMETAFILEPICT", CF_DSPMETAFILEPICT}, + {"CF_DSPENHMETAFILE", CF_DSPENHMETAFILE}, }; for (int i = 0; i < ARRAY_LENGTH(standard_formats); i++) - if (STRCMP(standard_formats[i].name, name) == 0) - return standard_formats[i].fmt; + if (STRCMP(standard_formats[i].name, name) == 0) + return standard_formats[i].fmt; // Not a standard format, try registered formats return RegisterClipboardFormat((char *)name); @@ -505,36 +505,37 @@ clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) return OK; } - const WCHAR * -get_standard_name(UINT fmt) { + static const WCHAR * +get_standard_name(UINT fmt) +{ switch (fmt) { - case CF_TEXT: return L"CF_TEXT"; - case CF_BITMAP: return L"CF_BITMAP"; - case CF_METAFILEPICT: return L"CF_METAFILEPICT"; - case CF_SYLK: return L"CF_SYLK"; - case CF_DIF: return L"CF_DIF"; - case CF_TIFF: return L"CF_TIFF"; - case CF_OEMTEXT: return L"CF_OEMTEXT"; - case CF_DIB: return L"CF_DIB"; - case CF_PALETTE: return L"CF_PALETTE"; - case CF_PENDATA: return L"CF_PENDATA"; - case CF_RIFF: return L"CF_RIFF"; - case CF_WAVE: return L"CF_WAVE"; - case CF_UNICODETEXT: return L"CF_UNICODETEXT"; - case CF_ENHMETAFILE: return L"CF_ENHMETAFILE"; - case CF_HDROP: return L"CF_HDROP"; - case CF_LOCALE: return L"CF_LOCALE"; - case CF_DIBV5: return L"CF_DIBV5"; - case CF_OWNERDISPLAY: return L"CF_OWNERDISPLAY"; - case CF_DSPTEXT: return L"CF_DSPTEXT"; - case CF_DSPBITMAP: return L"CF_DSPBITMAP"; - case CF_DSPMETAFILEPICT: return L"CF_DSPMETAFILEPICT"; - case CF_DSPENHMETAFILE: return L"CF_DSPENHMETAFILE"; - default: return NULL; + case CF_TEXT: return L"CF_TEXT"; + case CF_BITMAP: return L"CF_BITMAP"; + case CF_METAFILEPICT: return L"CF_METAFILEPICT"; + case CF_SYLK: return L"CF_SYLK"; + case CF_DIF: return L"CF_DIF"; + case CF_TIFF: return L"CF_TIFF"; + case CF_OEMTEXT: return L"CF_OEMTEXT"; + case CF_DIB: return L"CF_DIB"; + case CF_PALETTE: return L"CF_PALETTE"; + case CF_PENDATA: return L"CF_PENDATA"; + case CF_RIFF: return L"CF_RIFF"; + case CF_WAVE: return L"CF_WAVE"; + case CF_UNICODETEXT: return L"CF_UNICODETEXT"; + case CF_ENHMETAFILE: return L"CF_ENHMETAFILE"; + case CF_HDROP: return L"CF_HDROP"; + case CF_LOCALE: return L"CF_LOCALE"; + case CF_DIBV5: return L"CF_DIBV5"; + case CF_OWNERDISPLAY: return L"CF_OWNERDISPLAY"; + case CF_DSPTEXT: return L"CF_DSPTEXT"; + case CF_DSPBITMAP: return L"CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: return L"CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: return L"CF_DSPENHMETAFILE"; + default: return NULL; } } - const WCHAR * +static const WCHAR * get_format_name(UINT fmt) { const WCHAR *standard; static WCHAR name[256]; @@ -546,7 +547,7 @@ get_format_name(UINT fmt) { // Try registered formats if (GetClipboardFormatNameW(fmt, name, 256) > 0) - return name; + return name; // Unknown clipboard format or private vim_snprintf((char *)name, 256, "%x", fmt); @@ -564,14 +565,14 @@ clip_mch_update_formats(Clipboard_T *cbd) while ((fmt = EnumClipboardFormats(fmt)) != 0) { - const WCHAR *wname = get_format_name(fmt); - char_u *name = utf16_to_enc((short_u *)wname, NULL); - - if (name != NULL) - { - clip_add_format(cbd, name, NULL); - vim_free(name); - } + const WCHAR *wname = get_format_name(fmt); + char_u *name = utf16_to_enc((short_u *)wname, NULL); + + if (name != NULL) + { + clip_add_format(cbd, name, NULL); + vim_free(name); + } } CloseClipboard(); From d514d007be97096c0ccc4e3c9811e979fd933e19 Mon Sep 17 00:00:00 2001 From: Foxe Chen Date: Thu, 7 May 2026 18:13:37 -0400 Subject: [PATCH 9/9] fix when vim is owner of selection --- runtime/doc/builtin.txt | 18 ++++++++++--- runtime/doc/tags | 1 + src/clipboard.c | 56 ++++++++++++++++++++++++++++------------- src/winclip.c | 6 +++-- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index cb0d82149974a3..4c290145b82886 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -5040,12 +5040,21 @@ getreg([{regname} [, 1 [, {list} [, {format}]]]]) *getreg()* (see |NL-used-for-Nul|). When the register was not set an empty list is returned. + *clipboard-format* If {regname} is a clipboard register ("+" or "*"), then {format} is allowed, which specifies the which clipboard format to return as a |Blob|. If {format} is specified, then - the other optional arguments are ignored. To see what formats - are available, see |getreginfo()|. To see how formats are - named, see |clipboard-format-naming|. + the other optional arguments are ignored. + + If Vim is the owner of the selection (only relevant for X11, + Wayland, and GTK GUI versions of Vim), then some formats are + not supported fully, such as "TIMESTAMP" or "TARGETS". If + |getreg()| is called on them, then regular text will be + returned. However the Vim specific formats, "_VIM_TEXT" and + "_VIMENC_TEXT" are supported. + + To see what formats are available, see |getreginfo()|. To see + how formats are named, see |clipboard-format-naming|. {only available when compiled with the |+clipboard_formats| feature} @@ -10358,7 +10367,8 @@ setreg({regname}, {value} [, {options}]) *setreg()* {regname} must be one character. {value} may be any value returned by |getreg()| or - |getreginfo()|, including a |List| or |Dict|. + |getreginfo()|, including a |List| or |Dict|. However the + "formats" entry in |getreginfo()| is currently not supported. If {options} contains "a" or {regname} is upper case, then the value is appended. diff --git a/runtime/doc/tags b/runtime/doc/tags index e07ec9f0370116..155632504cf69f 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6731,6 +6731,7 @@ clipboard-autoselect options.txt /*clipboard-autoselect* clipboard-autoselectml options.txt /*clipboard-autoselectml* clipboard-autoselectplus options.txt /*clipboard-autoselectplus* clipboard-exclude options.txt /*clipboard-exclude* +clipboard-format builtin.txt /*clipboard-format* clipboard-format-naming builtin.txt /*clipboard-format-naming* clipboard-html options.txt /*clipboard-html* clipboard-providers eval.txt /*clipboard-providers* diff --git a/src/clipboard.c b/src/clipboard.c index afb1377b41c6f1..da566c1b516d5b 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -2536,22 +2536,44 @@ clip_get_selection_format(Clipboard_T *cbd, char_u *format) /* break; */ /* } */ /* } */ - /* if (ga.ga_data == NULL) */ - // Try requesting the contents now - if (clip_gen_request_format(cbd, format, &ga) == FAIL) - goto fail; + // If we own the clipboard, then just access the register directly. For our + // special formats, also handle them too. + if (cbd->owned) + { + char_u *str; + long_u len; + int motion; - if (ga.ga_data == NULL) - goto fail; + clip_update_supported_formats(cbd); + if (!clip_format_is_supported(cbd, format)) + goto exit; + + motion = clip_convert_selection(&str, &len, cbd); + if (str == NULL) + goto exit; + + if (STRCMP(format, VIM_ATOM_NAME) == 0) + ga_append(&ga, motion); + else if (STRCMP(format, VIMENC_ATOM_NAME) == 0) + { + ga_append(&ga, motion); + ga_concat(&ga, p_enc); + ga_append(&ga, NUL); + } + ga_concat_len(&ga, str, len); + vim_free(str); + } + else + // Try requesting the contents now. Just return empty blob if error + // occurs or if cleared. + clip_gen_request_format(cbd, format, &ga); +exit: blob->bv_ga = ga; blob->bv_refcount++; return blob; -fail: - blob_free(blob); - return NULL; } /* @@ -2968,7 +2990,6 @@ clip_wl_receive_data(int fd, garray_T *buf) return OK; } -# ifdef FEAT_CLIPBOARD_FORMATS /* * Request the selection data for the given format (mime type). Returns OK on * success and FAIL on failure. @@ -2978,19 +2999,21 @@ clip_wl_request_format( Clipboard_T *cbd, const char *format, garray_T *ga, - bool roundtrip) + bool for_formats UNUSED) { clip_wl_selection_T *sel = clip_wl_get_selection_from_cbd(cbd); int fds[2]; int ret = FAIL; - if (!sel->available) - return FAIL; - // Dispatch any events that still queued up before checking for a data // offer. - if (roundtrip) +# ifdef FEAT_CLIPBOARD_FORMATS + if (for_formats) +# endif { + if (!sel->available) + return FAIL; + if (vwl_connection_roundtrip(wayland_ct) == FAIL) return FAIL; @@ -3008,7 +3031,7 @@ clip_wl_request_format( vwl_data_offer_receive(sel->offer, format, fds[1]); close(fds[1]); // Close before we read data so that when the source client - // closes their end we receive an EOF. + // closes theirs end we receive an EOF. if (vwl_connection_flush(wayland_ct) >= 0) ret = clip_wl_receive_data(fds[0], ga); @@ -3017,7 +3040,6 @@ clip_wl_request_format( return ret; } -# endif /* * Get the current selection and fill the respective register for cbd with the diff --git a/src/winclip.c b/src/winclip.c index 22a1ebe861993d..bc3d31a50e4fbc 100644 --- a/src/winclip.c +++ b/src/winclip.c @@ -508,7 +508,8 @@ clip_mch_request_format(Clipboard_T *cbd, char_u *format, garray_T *ga) static const WCHAR * get_standard_name(UINT fmt) { - switch (fmt) { + switch (fmt) + { case CF_TEXT: return L"CF_TEXT"; case CF_BITMAP: return L"CF_BITMAP"; case CF_METAFILEPICT: return L"CF_METAFILEPICT"; @@ -536,7 +537,8 @@ get_standard_name(UINT fmt) } static const WCHAR * -get_format_name(UINT fmt) { +get_format_name(UINT fmt) +{ const WCHAR *standard; static WCHAR name[256];