diff --git a/client/displayservers/Wayland/clipboard.c b/client/displayservers/Wayland/clipboard.c index 37ca34a8..0bbe818c 100644 --- a/client/displayservers/Wayland/clipboard.c +++ b/client/displayservers/Wayland/clipboard.c @@ -194,7 +194,6 @@ static bool hasImageMimetype(char ** mimetypes) enum ClipboardInvalidateMode { - CLIPBOARD_INVALIDATE_SILENT, CLIPBOARD_INVALIDATE_CONDITIONAL, CLIPBOARD_INVALIDATE_FORCE, }; @@ -339,8 +338,9 @@ 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. */ + /* A self-copy offer only echoes the source that we published. It must not + * release the provider or disturb the external offer for a generation + * which the guest may still request. */ if (!selfCopy) waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_FORCE); wl_data_offer_destroy(offer); @@ -664,6 +664,12 @@ static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode) { char * mimetypes[LG_CLIPBOARD_DATA_NONE]; LG_LOCK(wlCb.lock); + const bool remoteSelection = wlCb.selectionSource != NULL; + if (mode == CLIPBOARD_INVALIDATE_CONDITIONAL && remoteSelection) + { + LG_UNLOCK(wlCb.lock); + return; + } struct ClipboardRead * read = clipboardReadTakeCurrentNL(); struct wl_data_offer * offer = wlCb.offer; wlCb.offer = NULL; @@ -1215,11 +1221,6 @@ static void waylandCBPublish(LG_ClipboardData type) wl_data_source_offer(source, *mimetype); wl_data_source_offer(source, wlCb.lgMimetype); - /* Publishing a remote selection supersedes the old external offer. Retire - * it before set_selection so a simultaneous focus loss cannot release the - * provider while this remote source is active. */ - waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_SILENT); - LG_LOCK(wlCb.lock); if (wlCb.dataDevice) { diff --git a/client/src/clipboard.c b/client/src/clipboard.c index 2452cf99..fa77eeff 100644 --- a/client/src/clipboard.c +++ b/client/src/clipboard.c @@ -485,10 +485,6 @@ static void eventNotice(void * opaque, generation = clipboard.remoteGeneration; transfer = clearRemoteRequestNL(); notice = clipboard.localAvailable && g_params.clipboardToLocal; - /* Once published, the remote notice replaces the local clipboard. Do not - * replay that stale local offer if the provider is rebound. */ - if (notice) - clipboard.localTypeCount = 0; LG_UNLOCK(clipboard.stateLock); LG_UNLOCK(clipboard.requestLock); @@ -1226,10 +1222,6 @@ void lgClipboard_setLocalAvailable(bool available) notice = available && clipboard.remoteNotice && g_params.clipboardToLocal; type = clipboard.remoteType; - /* A pending remote notice replaces the local clipboard when it becomes - * publishable. Do not replay the superseded local offer afterwards. */ - if (notice) - clipboard.localTypeCount = 0; if (!available) transfer = clearRemoteRequestNL(); LG_UNLOCK(clipboard.stateLock); diff --git a/client/tests/clipboard_test.c b/client/tests/clipboard_test.c index 55140937..1c5c5268 100644 --- a/client/tests/clipboard_test.c +++ b/client/tests/clipboard_test.c @@ -627,7 +627,7 @@ static void testRequest(void) lgClipboard_free(); } -static void testRemoteReplacesLocal(void) +static void testRemoteKeepsLocalRequest(void) { init(); bind(&p); @@ -651,17 +651,17 @@ static void testRemoteReplacesLocal(void) notice(&q, types, 1); CHECK(d.notice == 1); - /* Rebinding after a remote notice must not replay the superseded local - * offer back to the provider. */ - lgClipboard_setTransport(&plainOps, &r); - CHECK(r.attach == 1); - CHECK(r.notice == 0); - CHECK(r.release == 1); + /* The two clipboard directions have independent generations. A late + * request can still refer to the local offer advertised before this remote + * notice, so its type must remain serviceable. */ + CHECK(q.ev->request(q.evCtx, 43, LG_CLIPBOARD_DATA_TEXT)); + CHECK(d.request == 1); + CHECK(d.reqType[0] == LG_CLIPBOARD_DATA_TEXT); lgClipboard_free(); } -static void testPendingRemoteReplacesLocal(void) +static void testPendingRemoteKeepsLocalRequest(void) { init(); bind(&p); @@ -677,10 +677,11 @@ static void testPendingRemoteReplacesLocal(void) lgClipboard_setLocalAvailable(true); CHECK(d.notice == 1); - lgClipboard_setTransport(&plainOps, &q); - CHECK(q.attach == 1); - CHECK(q.notice == 0); - CHECK(q.release == 1); + /* Publishing a notice which arrived while the local clipboard was + * unavailable must likewise retain the local request allow-list. */ + CHECK(p.ev->request(p.evCtx, 44, LG_CLIPBOARD_DATA_TEXT)); + CHECK(d.request == 1); + CHECK(d.reqType[0] == LG_CLIPBOARD_DATA_TEXT); lgClipboard_free(); } @@ -1129,8 +1130,8 @@ static const struct Test tests[] = { { "preference", testPreference }, { "request" , testRequest }, - { "remote-local", testRemoteReplacesLocal }, - { "pending-remote", testPendingRemoteReplacesLocal }, + { "remote-local", testRemoteKeepsLocalRequest }, + { "pending-remote", testPendingRemoteKeepsLocalRequest }, { "invalid" , testInvalid }, { "cancel" , testCancel }, { "generation", testGeneration }, diff --git a/client/tests/wayland_clipboard_test.c b/client/tests/wayland_clipboard_test.c index 4b0a0d81..1b01f9da 100644 --- a/client/tests/wayland_clipboard_test.c +++ b/client/tests/wayland_clipboard_test.c @@ -706,12 +706,22 @@ static void testSelfCopy(void) waylandCBNotice(LG_CLIPBOARD_DATA_TEXT); struct Proxy * source = &proto.source[0]; CHECK(wlCb.selectionSource == (struct wl_data_source *)source); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + CHECK(!local->dead); + + /* A remote notice and its self echo must not erase the Wayland offer for a + * local clipboard generation that Windows can still request. */ + const uint8_t localData[] = "local clipboard"; + proto.receiveData = localData; + proto.receiveSize = sizeof(localData) - 1; /* Losing focus before the compositor echoes our source must not release * the remote clipboard. */ waylandCBInvalidate(); CHECK(rec.releaseN == 0); CHECK(wlCb.selectionSource == (struct wl_data_source *)source); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + CHECK(!local->dead); struct Proxy * echo = newOffer(); offerMime(echo, "text/plain"); @@ -721,17 +731,28 @@ static void testSelfCopy(void) CHECK(rec.noticeN == 1); CHECK(rec.releaseN == 0); CHECK(wlCb.selectionSource == (struct wl_data_source *)source); - CHECK(!wlCb.offer); - for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i) - CHECK(!wlCb.mimetypes[i]); - CHECK(local->dead); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + CHECK(wlCb.mimetypes[LG_CLIPBOARD_DATA_TEXT]); + CHECK(!local->dead); CHECK(echo->dead); - CHECK(proto.offerDestroyN == 2); + CHECK(proto.offerDestroyN == 1); CHECK(!echo->user); waylandCBInvalidate(); CHECK(rec.releaseN == 0); CHECK(wlCb.selectionSource == (struct wl_data_source *)source); + CHECK(wlCb.offer == (struct wl_data_offer *)local); + + 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(localData) - 1); + CHECK(rec.streamData.endN == 1); + CHECK(memcmp(rec.streamData.data, localData, + sizeof(localData) - 1) == 0); int fds[2]; CHECK(pipe(fds) == 0); @@ -754,6 +775,8 @@ static void testSelfCopy(void) selectOffer(NULL); CHECK(rec.releaseN == 1); CHECK(!wlCb.selectionSource); + CHECK(local->dead); + CHECK(proto.offerDestroyN == 2); CHECK(proto.dirtyDestroyN == 0); finish();