From 608e85fe5d01ea72fff2994c6caceadd2fd5365d Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 14 Aug 2026 12:02:40 +1000 Subject: [PATCH] [client] clipboard: preserve newer Wayland selections --- client/displayservers/Wayland/clipboard.c | 6 +-- client/tests/CMakeLists.txt | 1 + client/tests/wayland_clipboard_test.c | 46 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/client/displayservers/Wayland/clipboard.c b/client/displayservers/Wayland/clipboard.c index 1c78c594..37ca34a8 100644 --- a/client/displayservers/Wayland/clipboard.c +++ b/client/displayservers/Wayland/clipboard.c @@ -339,12 +339,10 @@ static void dataDeviceHandleSelection(void * opaque, for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) free(extra->mimetypes[i]); free(extra); + /* Publishing already retired the old offer, so a delayed self-copy echo + * must not invalidate a newer local selection. */ if (!selfCopy) waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_FORCE); - else - /* This is the compositor echoing the source we just installed. It is - * not a new local clipboard owner and must not send a provider CLEAR. */ - waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_SILENT); wl_data_offer_destroy(offer); return; } diff --git a/client/tests/CMakeLists.txt b/client/tests/CMakeLists.txt index 5c8deb5a..2099a6ad 100644 --- a/client/tests/CMakeLists.txt +++ b/client/tests/CMakeLists.txt @@ -211,6 +211,7 @@ if(ENABLE_WAYLAND) teardown teardown-external self-copy + self-copy-late ignored invalidate-lock dnd diff --git a/client/tests/wayland_clipboard_test.c b/client/tests/wayland_clipboard_test.c index bc698d4b..4b0a0d81 100644 --- a/client/tests/wayland_clipboard_test.c +++ b/client/tests/wayland_clipboard_test.c @@ -759,6 +759,51 @@ static void testSelfCopy(void) finish(); } +static void testLateSelfCopy(void) +{ + start(); + waylandCBNotice(LG_CLIPBOARD_DATA_TEXT); + struct Proxy * source = &proto.source[0]; + CHECK(wlCb.selectionSource == (struct wl_data_source *)source); + + struct Proxy * local = textOffer(); + CHECK(rec.noticeN == 1); + CHECK(!wlCb.selectionSource); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + CHECK(!local->dead); + + /* The compositor may deliver our marked echo after a newer external + * selection. The stale echo must not discard that local selection. */ + struct Proxy * echo = newOffer(); + offerMime(echo, "text/plain"); + offerMime(echo, wlCb.lgMimetype); + selectOffer(echo); + + CHECK(rec.noticeN == 1); + CHECK(rec.releaseN == 0); + CHECK(!wlCb.selectionSource); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + CHECK(wlCb.mimetypes[LG_CLIPBOARD_DATA_TEXT]); + CHECK(!local->dead); + CHECK(echo->dead); + + const uint8_t data[] = "new local selection"; + proto.receiveData = data; + proto.receiveSize = sizeof(data) - 1; + waylandCBRequest(102, LG_CLIPBOARD_DATA_TEXT); + CHECK(proto.receiveN == 1); + CHECK(rec.pollN == 1); + pollFire(0, EPOLLIN); + pollFire(0, EPOLLIN); + CHECK(rec.streamData.request == 102); + CHECK(rec.streamData.size == sizeof(data) - 1); + CHECK(rec.streamData.endN == 1); + CHECK(memcmp(rec.streamData.data, data, sizeof(data) - 1) == 0); + + CHECK(proto.dirtyDestroyN == 0); + finish(); +} + static void testIgnoredOffer(void) { start(); @@ -1329,6 +1374,7 @@ static const struct Test tests[] = { "teardown" , testTeardown }, { "teardown-external", testTeardownExternalOwner }, { "self-copy" , testSelfCopy }, + { "self-copy-late", testLateSelfCopy }, { "ignored" , testIgnoredOffer }, { "invalidate-lock", testInvalidateSerialization }, { "dnd" , testDnd },