From 331e10c2a39c4ad086577af34f55492dca46afa8 Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Mon, 27 Jul 2026 14:21:12 -0400 Subject: [PATCH] wayland: Clear references to the clipboard when setting new data Ensure that references to the clipboard being held by other seats are cleared when new data is being set, or some seats may hold references to invalid data and callbacks. --- src/video/wayland/SDL_waylandclipboard.c | 29 ++++++++++++---- src/video/wayland/SDL_waylanddatamanager.c | 40 +++++++++++++++++----- src/video/wayland/SDL_waylandevents.c | 3 +- src/video/wayland/SDL_waylandvideo.h | 4 +-- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/video/wayland/SDL_waylandclipboard.c b/src/video/wayland/SDL_waylandclipboard.c index 81f8f8c3c5..9e786afd22 100644 --- a/src/video/wayland/SDL_waylandclipboard.c +++ b/src/video/wayland/SDL_waylandclipboard.c @@ -39,9 +39,26 @@ bool Wayland_SetClipboardData(SDL_VideoDevice *_this) seat = wl_container_of(video_data->seat_list.next, seat, link); } - video_data->last_incoming_data_offer_seat = seat; + video_data->current_data_offer_seat = seat; if (seat && seat->data_device) { + /* Clear references to the clipboard held by other seats, as they are about to become invalid. + * For the target seat, the new source data is set before clearing the old source, as some + * clipboard managers prefer this behavior. + */ + SDL_WaylandSeat *s; + wl_list_for_each (s, &video_data->seat_list, link) { + if (s->data_device) { + if (s != seat) { + Wayland_DataSourceDestroy(s->data_device->selection_source); + s->data_device->selection_source = NULL; + } + + Wayland_DataOfferDestroy(s->data_device->selection_offer); + s->data_device->selection_offer = NULL; + } + } + SDL_WaylandDataDevice *data_device = seat->data_device; if (_this->clipboard_callback && _this->clipboard_mime_types) { @@ -65,7 +82,7 @@ bool Wayland_SetClipboardData(SDL_VideoDevice *_this) void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *length) { SDL_VideoData *video_data = _this->internal; - SDL_WaylandSeat *seat = video_data->last_incoming_data_offer_seat; + SDL_WaylandSeat *seat = video_data->current_data_offer_seat; void *buffer = NULL; if (seat && seat->data_device) { @@ -83,7 +100,7 @@ void *Wayland_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, si bool Wayland_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) { SDL_VideoData *video_data = _this->internal; - SDL_WaylandSeat *seat = video_data->last_incoming_data_offer_seat; + SDL_WaylandSeat *seat = video_data->current_data_offer_seat; bool result = false; if (seat && seat->data_device) { @@ -122,7 +139,7 @@ bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) seat = wl_container_of(video_data->seat_list.next, seat, link); } - video_data->last_incoming_primary_selection_seat = seat; + video_data->current_primary_selection_seat = seat; if (seat && seat->primary_selection_device) { SDL_WaylandPrimarySelectionDevice *primary_selection_device = seat->primary_selection_device; @@ -148,7 +165,7 @@ bool Wayland_SetPrimarySelectionText(SDL_VideoDevice *_this, const char *text) char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this) { SDL_VideoData *video_data = _this->internal; - SDL_WaylandSeat *seat = video_data->last_incoming_primary_selection_seat; + SDL_WaylandSeat *seat = video_data->current_primary_selection_seat; char *text = NULL; size_t length = 0; @@ -176,7 +193,7 @@ char *Wayland_GetPrimarySelectionText(SDL_VideoDevice *_this) bool Wayland_HasPrimarySelectionText(SDL_VideoDevice *_this) { SDL_VideoData *video_data = _this->internal; - SDL_WaylandSeat *seat = video_data->last_incoming_primary_selection_seat; + SDL_WaylandSeat *seat = video_data->current_primary_selection_seat; bool result = false; if (seat && seat->primary_selection_device) { diff --git a/src/video/wayland/SDL_waylanddatamanager.c b/src/video/wayland/SDL_waylanddatamanager.c index 5137c3bef1..49b973f971 100644 --- a/src/video/wayland/SDL_waylanddatamanager.c +++ b/src/video/wayland/SDL_waylanddatamanager.c @@ -446,6 +446,29 @@ static void DataOfferCheckSource(SDL_WaylandDataOffer *offer, const char *mime_t } } +static void SetCurrentClipboardOffer(SDL_WaylandDataOffer *offer) +{ + SDL_WaylandSeat *offer_seat = offer->data_device->seat; + SDL_VideoData *video_data = offer_seat->display; + + // Clear any existing references to the existing clipboard data before replacing the current offer. + SDL_WaylandSeat *s; + wl_list_for_each (s, &video_data->seat_list, link) { + if (s->data_device) { + Wayland_DataSourceDestroy(s->data_device->selection_source); + s->data_device->selection_source = NULL; + + // Don't clear the offer that is about to be set. + if (s != offer_seat) { + Wayland_DataOfferDestroy(s->data_device->selection_offer); + s->data_device->selection_offer = NULL; + } + } + } + + video_data->current_data_offer_seat = offer_seat; +} + void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_origin) { int nformats = 0; @@ -494,6 +517,7 @@ void Wayland_DataOfferNotifyFromMIMEs(SDL_WaylandDataOffer *offer, bool check_or new_mime_types[nformats] = NULL; } + SetCurrentClipboardOffer(offer); SDL_SendClipboardUpdate(false, new_mime_types, nformats); } @@ -697,26 +721,26 @@ bool Wayland_PrimarySelectionDeviceSetSelection(SDL_WaylandPrimarySelectionDevic void Wayland_DataDeviceSetSerial(SDL_WaylandDataDevice *data_device, uint32_t serial) { if (data_device) { - data_device->selection_serial = serial; - - // If there was no serial and there is a pending selection set it now. + // If there was no serial and there is a pending selection, set it now. if (data_device->selection_serial == 0 && data_device->selection_source) { - wl_data_device_set_selection(data_device->data_device, data_device->selection_source->source, data_device->selection_serial); + wl_data_device_set_selection(data_device->data_device, data_device->selection_source->source, serial); } + + data_device->selection_serial = serial; } } void Wayland_PrimarySelectionDeviceSetSerial(SDL_WaylandPrimarySelectionDevice *primary_selection_device, uint32_t serial) { if (primary_selection_device) { - primary_selection_device->selection_serial = serial; - - // If there was no serial and there is a pending selection set it now. + // If there was no serial and there is a pending selection, set it now. if (primary_selection_device->selection_serial == 0 && primary_selection_device->selection_source) { zwp_primary_selection_device_v1_set_selection(primary_selection_device->primary_selection_device, primary_selection_device->selection_source->source, - primary_selection_device->selection_serial); + serial); } + + primary_selection_device->selection_serial = serial; } } diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c index 14031d8e4c..761aae86fd 100644 --- a/src/video/wayland/SDL_waylandevents.c +++ b/src/video/wayland/SDL_waylandevents.c @@ -2749,7 +2749,6 @@ static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ SDL_WaylandDataOffer *data_offer = SDL_calloc(1, sizeof(*data_offer)); if (data_offer) { SDL_WaylandDataDevice *data_device = (SDL_WaylandDataDevice *)data; - 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; @@ -3054,7 +3053,7 @@ static void primary_selection_device_handle_offer(void *data, struct zwp_primary SDL_WaylandPrimarySelectionOffer *primary_selection_offer = SDL_calloc(1, sizeof(*primary_selection_offer)); if (primary_selection_offer) { SDL_WaylandPrimarySelectionDevice *primary_selection_device = (SDL_WaylandPrimarySelectionDevice *)data; - primary_selection_device->seat->display->last_incoming_primary_selection_seat = primary_selection_device->seat; + primary_selection_device->seat->display->current_primary_selection_seat = primary_selection_device->seat; primary_selection_offer->offer = id; primary_selection_offer->primary_selection_device = primary_selection_device; WAYLAND_wl_list_init(&(primary_selection_offer->mimes)); diff --git a/src/video/wayland/SDL_waylandvideo.h b/src/video/wayland/SDL_waylandvideo.h index 17a9ba24fa..74c8ed2363 100644 --- a/src/video/wayland/SDL_waylandvideo.h +++ b/src/video/wayland/SDL_waylandvideo.h @@ -96,8 +96,8 @@ struct SDL_VideoData struct wl_list seat_list; struct SDL_WaylandSeat *last_implicit_grab_seat; - struct SDL_WaylandSeat *last_incoming_data_offer_seat; - struct SDL_WaylandSeat *last_incoming_primary_selection_seat; + struct SDL_WaylandSeat *current_data_offer_seat; + struct SDL_WaylandSeat *current_primary_selection_seat; Wayland_EventThreadContext *event_thread_context; SDL_DisplayData **output_list;