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.
This commit is contained in:
Frank Praznik
2026-07-27 14:21:12 -04:00
parent dfcbd16b4c
commit 331e10c2a3
4 changed files with 58 additions and 18 deletions

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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));

View File

@@ -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;