diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c index 5cb450c86f..226a7f3293 100644 --- a/src/core/linux/SDL_dbus.c +++ b/src/core/linux/SDL_dbus.c @@ -49,6 +49,7 @@ static bool LoadDBUSSyms(void) SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match); SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match); + SDL_DBUS_SYM(const char *(*)(DBusConnection *), bus_get_unique_name); SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private); SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect); SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected); diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h index b22a92af04..097bc31eb3 100644 --- a/src/core/linux/SDL_dbus.h +++ b/src/core/linux/SDL_dbus.h @@ -47,6 +47,7 @@ typedef struct SDL_DBusContext dbus_bool_t (*bus_register)(DBusConnection *, DBusError *); void (*bus_add_match)(DBusConnection *, const char *, DBusError *); void (*bus_remove_match)(DBusConnection *, const char *, DBusError *); + const char *(*bus_get_unique_name)(DBusConnection *); DBusConnection *(*connection_open_private)(const char *, DBusError *); void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t); dbus_bool_t (*connection_get_is_connected)(DBusConnection *); diff --git a/src/events/SDL_clipboardevents.c b/src/events/SDL_clipboardevents.c index d5cf8ad7b8..529af9202b 100644 --- a/src/events/SDL_clipboardevents.c +++ b/src/events/SDL_clipboardevents.c @@ -29,17 +29,7 @@ void SDL_SendClipboardUpdate(bool owner, char **mime_types, size_t num_mime_types) { if (!owner) { - /* Clear our internal clipboard contents when external clipboard is set. - * - * Wayland recursively sends a data offer to the client from which the clipboard data originated, - * and as the client can't determine the origin of the offer, the clipboard must not be cleared, - * or the original data may be destroyed. Cleanup will be done in the backend when an offer - * cancellation event arrives. - */ - if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") != 0) { - SDL_CancelClipboardData(0); - } - + SDL_CancelClipboardData(0); SDL_SaveClipboardMimeTypes((const char **)mime_types, num_mime_types); } diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index d5e39a77e0..3f4c0a2857 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -148,8 +148,8 @@ static SDL_MimeDataList *mime_data_list_find(struct wl_list *list, } static bool mime_data_list_add(struct wl_list *list, - const char *mime_type, - const void *buffer, size_t length) + const char *mime_type, + const void *buffer, size_t length) { bool result = true; size_t mime_type_length = 0; @@ -231,7 +231,10 @@ ssize_t Wayland_data_source_send(SDL_WaylandDataSource *source, const char *mime const void *data = NULL; size_t length = 0; - if (source->callback) { + if (SDL_strcmp(mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + data = source->data_device->id_str; + length = SDL_strlen(source->data_device->id_str); + } else if (source->callback) { data = source->callback(source->userdata.data, mime_type, &length); } @@ -263,8 +266,8 @@ void Wayland_data_source_set_callback(SDL_WaylandDataSource *source, } void Wayland_primary_selection_source_set_callback(SDL_WaylandPrimarySelectionSource *source, - SDL_ClipboardDataCallback callback, - void *userdata) + SDL_ClipboardDataCallback callback, + void *userdata) { if (source) { source->callback = callback; @@ -352,6 +355,113 @@ void Wayland_primary_selection_source_destroy(SDL_WaylandPrimarySelectionSource } } +static void offer_source_done_handler(void *data, struct wl_callback *callback, uint32_t callback_data) +{ + if (!callback) { + return; + } + + SDL_WaylandDataOffer *offer = data; + char *id = NULL; + size_t length = 0; + + wl_callback_destroy(offer->callback); + offer->callback = NULL; + + while (read_pipe(offer->read_fd, (void **)&id, &length) > 0) { + } + close(offer->read_fd); + offer->read_fd = -1; + + if (id) { + const bool source_is_external = SDL_strncmp(offer->data_device->id_str, id, length) != 0; + SDL_free(id); + if (source_is_external) { + Wayland_data_offer_notify_from_mimes(offer, false); + } + } +} + +static struct wl_callback_listener offer_source_listener = { + offer_source_done_handler +}; + +static void Wayland_data_offer_check_source(SDL_WaylandDataOffer *offer, const char *mime_type) +{ + SDL_WaylandDataDevice *data_device = NULL; + int pipefd[2]; + + if (!offer) { + SDL_SetError("Invalid data offer"); + } + data_device = offer->data_device; + if (!data_device) { + SDL_SetError("Data device not initialized"); + } else if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK) == -1) { + SDL_SetError("Could not read pipe"); + } else { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } + + offer->read_fd = pipefd[0]; + + wl_data_offer_receive(offer->offer, mime_type, pipefd[1]); + close(pipefd[1]); + + offer->callback = wl_display_sync(offer->data_device->seat->display->display); + wl_callback_add_listener(offer->callback, &offer_source_listener, offer); + + WAYLAND_wl_display_flush(data_device->seat->display->display); + } +} + +void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, bool check_origin) +{ + int nformats = 0; + char **new_mime_types = NULL; + if (offer) { + size_t alloc_size = 0; + + // Do a first pass to compute allocation size. + SDL_MimeDataList *item = NULL; + wl_list_for_each(item, &offer->mimes, link) { + // If origin metadata is found, queue a check and wait for confirmation that this offer isn't recursive. + if (check_origin && SDL_strcmp(item->mime_type, SDL_DATA_ORIGIN_MIME) == 0) { + Wayland_data_offer_check_source(offer, item->mime_type); + return; + } + + ++nformats; + alloc_size += SDL_strlen(item->mime_type) + 1; + } + + alloc_size += (nformats + 1) * sizeof(char *); + + new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); + if (!new_mime_types) { + SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); + return; + } + + // Second pass to fill. + char *strPtr = (char *)(new_mime_types + nformats + 1); + item = NULL; + int i = 0; + wl_list_for_each(item, &offer->mimes, link) { + new_mime_types[i] = strPtr; + strPtr = stpcpy(strPtr, item->mime_type) + 1; + i++; + } + new_mime_types[nformats] = NULL; + } + + SDL_SendClipboardUpdate(false, new_mime_types, nformats); +} + void *Wayland_data_offer_receive(SDL_WaylandDataOffer *offer, const char *mime_type, size_t *length) { @@ -433,7 +543,7 @@ bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer * } bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -444,7 +554,7 @@ bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, } bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type) + const char *mime_type) { bool found = false; @@ -457,6 +567,12 @@ bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer * void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer) { if (offer) { + if (offer->callback) { + wl_callback_destroy(offer->callback); + } + if (offer->read_fd >= 0) { + close(offer->read_fd); + } wl_data_offer_destroy(offer->offer); mime_data_list_free(&offer->mimes); SDL_free(offer); @@ -522,6 +638,9 @@ bool Wayland_data_device_set_selection(SDL_WaylandDataDevice *data_device, mime_type); } + // Advertise the data origin MIME + wl_data_source_offer(source->source, SDL_DATA_ORIGIN_MIME); + if (index == 0) { Wayland_data_device_clear_selection(data_device); result = SDL_SetError("No mime data"); diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h index 276620f906..bdde6a2345 100644 --- a/src/video/wayland/SDL_waylanddatamanager.h +++ b/src/video/wayland/SDL_waylanddatamanager.h @@ -31,6 +31,10 @@ #define TEXT_MIME "text/plain;charset=utf-8" #define FILE_MIME "text/uri-list" #define FILE_PORTAL_MIME "application/vnd.portal.filetransfer" +#define SDL_DATA_ORIGIN_MIME "application/x-sdl3-source-id" + +typedef struct SDL_WaylandDataDevice SDL_WaylandDataDevice; +typedef struct SDL_WaylandPrimarySelectionDevice SDL_WaylandPrimarySelectionDevice; typedef struct { @@ -49,7 +53,7 @@ typedef struct SDL_WaylandUserdata typedef struct { struct wl_data_source *source; - void *data_device; + SDL_WaylandDataDevice *data_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandDataSource; @@ -57,8 +61,8 @@ typedef struct typedef struct { struct zwp_primary_selection_source_v1 *source; - void *data_device; - void *primary_selection_device; + SDL_WaylandDataDevice *data_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; SDL_ClipboardDataCallback callback; SDL_WaylandUserdata userdata; } SDL_WaylandPrimarySelectionSource; @@ -67,20 +71,25 @@ typedef struct { struct wl_data_offer *offer; struct wl_list mimes; - void *data_device; + SDL_WaylandDataDevice *data_device; + + // Callback data for queued receive. + struct wl_callback *callback; + int read_fd; } SDL_WaylandDataOffer; typedef struct { struct zwp_primary_selection_offer_v1 *offer; struct wl_list mimes; - void *primary_selection_device; + SDL_WaylandPrimarySelectionDevice *primary_selection_device; } SDL_WaylandPrimarySelectionOffer; -typedef struct +struct SDL_WaylandDataDevice { struct wl_data_device *data_device; struct SDL_WaylandSeat *seat; + char *id_str; // Drag and Drop uint32_t drag_serial; @@ -93,9 +102,9 @@ typedef struct // Clipboard and Primary Selection uint32_t selection_serial; SDL_WaylandDataSource *selection_source; -} SDL_WaylandDataDevice; +}; -typedef struct +struct SDL_WaylandPrimarySelectionDevice { struct zwp_primary_selection_device_v1 *primary_selection_device; struct SDL_WaylandSeat *seat; @@ -103,7 +112,7 @@ typedef struct uint32_t selection_serial; SDL_WaylandPrimarySelectionSource *selection_source; SDL_WaylandPrimarySelectionOffer *selection_offer; -} SDL_WaylandPrimarySelectionDevice; +}; // Wayland Data Source / Primary Selection Source - (Sending) extern SDL_WaylandDataSource *Wayland_data_source_create(SDL_VideoDevice *_this); @@ -136,13 +145,15 @@ extern void *Wayland_primary_selection_offer_receive(SDL_WaylandPrimarySelection const char *mime_type, size_t *length); extern bool Wayland_data_offer_has_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); +extern void Wayland_data_offer_notify_from_mimes(SDL_WaylandDataOffer *offer, + bool check_origin); extern bool Wayland_primary_selection_offer_has_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_data_offer_add_mime(SDL_WaylandDataOffer *offer, - const char *mime_type); + const char *mime_type); extern bool Wayland_primary_selection_offer_add_mime(SDL_WaylandPrimarySelectionOffer *offer, - const char *mime_type); + const char *mime_type); extern void Wayland_data_offer_destroy(SDL_WaylandDataOffer *offer); extern void Wayland_primary_selection_offer_destroy(SDL_WaylandPrimarySelectionOffer *offer); diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 7474f6f1ea..098d26e1ff 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -2522,6 +2522,7 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ data_device->seat->display->last_incoming_data_offer_seat = data_device->seat; data_offer->offer = id; data_offer->data_device = data_device; + data_offer->read_fd = -1; WAYLAND_wl_list_init(&(data_offer->mimes)); wl_data_offer_set_user_data(id, data_offer); wl_data_offer_add_listener(id, &data_offer_listener, data_offer); @@ -2763,41 +2764,6 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d data_device->drag_offer = NULL; } -static void notifyFromMimes(struct wl_list *mimes) -{ - int nformats = 0; - char **new_mime_types = NULL; - if (mimes) { - nformats = WAYLAND_wl_list_length(mimes); - size_t alloc_size = (nformats + 1) * sizeof(char *); - - /* do a first pass to compute allocation size */ - SDL_MimeDataList *item = NULL; - wl_list_for_each(item, mimes, link) { - alloc_size += SDL_strlen(item->mime_type) + 1; - } - - new_mime_types = SDL_AllocateTemporaryMemory(alloc_size); - if (!new_mime_types) { - SDL_LogError(SDL_LOG_CATEGORY_INPUT, "unable to allocate new_mime_types"); - return; - } - - /* second pass to fill*/ - char *strPtr = (char *)(new_mime_types + nformats + 1); - item = NULL; - int i = 0; - wl_list_for_each(item, mimes, link) { - new_mime_types[i] = strPtr; - strPtr = stpcpy(strPtr, item->mime_type) + 1; - i++; - } - new_mime_types[nformats] = NULL; - } - - SDL_SendClipboardUpdate(false, new_mime_types, nformats); -} - static void data_device_handle_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { @@ -2816,7 +2782,7 @@ static void data_device_handle_selection(void *data, struct wl_data_device *wl_d data_device->selection_offer = offer; } - notifyFromMimes(offer ? &offer->mimes : NULL); + Wayland_data_offer_notify_from_mimes(offer, true); } static const struct wl_data_device_listener data_device_listener = { @@ -2946,6 +2912,29 @@ static const struct zwp_text_input_v3_listener text_input_listener = { text_input_done }; +static void Wayland_DataDeviceSetID(SDL_WaylandDataDevice *data_device) +{ + if (!data_device->id_str) +#ifdef SDL_USE_LIBDBUS + { + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + if (dbus) { + const char *id = dbus->bus_get_unique_name(dbus->session_conn); + if (id) { + data_device->id_str = SDL_strdup(id); + } + } + } + if (!data_device->id_str) +#endif + { + char id[24]; + Uint64 pid = (Uint64)getpid(); + SDL_snprintf(id, sizeof(id), "%" SDL_PRIu64, pid); + data_device->id_str = SDL_strdup(id); + } +} + static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) { if (!seat->display->data_device_manager) { @@ -2964,6 +2953,7 @@ static void Wayland_SeatCreateDataDevice(SDL_WaylandSeat *seat) if (!data_device->data_device) { SDL_free(data_device); } else { + Wayland_DataDeviceSetID(data_device); wl_data_device_set_user_data(data_device->data_device, data_device); wl_data_device_add_listener(data_device->data_device, &data_device_listener, data_device); @@ -3453,6 +3443,7 @@ void Wayland_SeatDestroy(SDL_WaylandSeat *seat, bool send_events) wl_data_device_destroy(seat->data_device->data_device); } } + SDL_free(seat->data_device->id_str); SDL_free(seat->data_device); }