mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
[client] clipboard: suppress Wayland self-copy feedback
This commit is contained in:
@@ -192,6 +192,15 @@ static bool hasImageMimetype(char ** mimetypes)
|
||||
return false;
|
||||
}
|
||||
|
||||
enum ClipboardInvalidateMode
|
||||
{
|
||||
CLIPBOARD_INVALIDATE_SILENT,
|
||||
CLIPBOARD_INVALIDATE_CONDITIONAL,
|
||||
CLIPBOARD_INVALIDATE_FORCE,
|
||||
};
|
||||
|
||||
static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode);
|
||||
|
||||
/* wlCb.lock must be held. */
|
||||
static struct ClipboardRead * clipboardReadTakeCurrentNL(void)
|
||||
{
|
||||
@@ -312,14 +321,15 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
LG_LOCK(wlCb.lock);
|
||||
wlCb.selectionSource = NULL;
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
waylandCBInvalidate();
|
||||
waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_FORCE);
|
||||
return;
|
||||
}
|
||||
|
||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
||||
if (!hasAnyMimetype(extra->mimetypes) || extra->isSelfCopy)
|
||||
const bool selfCopy = extra->isSelfCopy;
|
||||
if (!hasAnyMimetype(extra->mimetypes) || selfCopy)
|
||||
{
|
||||
if (!extra->isSelfCopy)
|
||||
if (!selfCopy)
|
||||
{
|
||||
LG_LOCK(wlCb.lock);
|
||||
wlCb.selectionSource = NULL;
|
||||
@@ -329,7 +339,12 @@ static void dataDeviceHandleSelection(void * opaque,
|
||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||
free(extra->mimetypes[i]);
|
||||
free(extra);
|
||||
waylandCBInvalidate();
|
||||
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;
|
||||
}
|
||||
@@ -647,7 +662,7 @@ static void clipboardReadCallback(uint32_t events, void * opaque)
|
||||
}
|
||||
}
|
||||
|
||||
void waylandCBInvalidate(void)
|
||||
static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode)
|
||||
{
|
||||
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||
LG_LOCK(wlCb.lock);
|
||||
@@ -656,16 +671,27 @@ void waylandCBInvalidate(void)
|
||||
wlCb.offer = NULL;
|
||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.mimetypes));
|
||||
const bool hadLocal = read || offer || hasAnyMimetype(mimetypes);
|
||||
const bool notifyProvider = mode == CLIPBOARD_INVALIDATE_FORCE ||
|
||||
(mode == CLIPBOARD_INVALIDATE_CONDITIONAL && hadLocal);
|
||||
const LG_ClipboardRequest request = read ? read->request :
|
||||
LG_CLIPBOARD_REQUEST_INVALID;
|
||||
if (read)
|
||||
clipboardReadRetire(read);
|
||||
|
||||
/* Serialize the provider release with source publication. Clipboard
|
||||
* provider operations may not synchronously deliver event callbacks, so
|
||||
* calling this while holding wlCb.lock cannot re-enter the display server. */
|
||||
if (notifyProvider)
|
||||
{
|
||||
if (read)
|
||||
lgClipboard_abort(request);
|
||||
lgClipboard_release();
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
if (read)
|
||||
{
|
||||
const LG_ClipboardRequest request = read->request;
|
||||
clipboardReadRetire(read);
|
||||
if (read && !notifyProvider)
|
||||
lgClipboard_abort(request);
|
||||
}
|
||||
|
||||
lgClipboard_release();
|
||||
|
||||
if (offer)
|
||||
wl_data_offer_destroy(offer);
|
||||
@@ -673,6 +699,11 @@ void waylandCBInvalidate(void)
|
||||
free(mimetypes[i]);
|
||||
}
|
||||
|
||||
void waylandCBInvalidate(void)
|
||||
{
|
||||
waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_CONDITIONAL);
|
||||
}
|
||||
|
||||
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||
{
|
||||
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
||||
@@ -1186,6 +1217,11 @@ 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)
|
||||
{
|
||||
@@ -1194,13 +1230,13 @@ static void waylandCBPublish(LG_ClipboardData type)
|
||||
wlCb.selectionSource = source;
|
||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||
wlWm.keyboardEnterSerial);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_data_source_destroy(source);
|
||||
free(transfer);
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
return;
|
||||
}
|
||||
LG_UNLOCK(wlCb.lock);
|
||||
|
||||
wl_data_source_destroy(source);
|
||||
free(transfer);
|
||||
}
|
||||
|
||||
void waylandCBNotice(LG_ClipboardData type)
|
||||
|
||||
@@ -485,6 +485,10 @@ 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);
|
||||
|
||||
@@ -1222,6 +1226,10 @@ 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);
|
||||
|
||||
@@ -211,6 +211,8 @@ if(ENABLE_WAYLAND)
|
||||
teardown
|
||||
teardown-external
|
||||
self-copy
|
||||
ignored
|
||||
invalidate-lock
|
||||
dnd
|
||||
)
|
||||
foreach(name IN LISTS WAYLAND_CLIPBOARD_CASES)
|
||||
@@ -471,6 +473,8 @@ target_link_libraries(clipboard-tests
|
||||
set(CLIPBOARD_CASES
|
||||
preference
|
||||
request
|
||||
remote-local
|
||||
pending-remote
|
||||
invalid
|
||||
cancel
|
||||
generation
|
||||
|
||||
@@ -627,6 +627,64 @@ static void testRequest(void)
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testRemoteReplacesLocal(void)
|
||||
{
|
||||
init();
|
||||
bind(&p);
|
||||
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||
|
||||
lgClipboard_notifyTypes(types, 1);
|
||||
CHECK(p.notice == 1);
|
||||
|
||||
g_params.clipboardToLocal = false;
|
||||
notice(&p, types, 1);
|
||||
CHECK(d.notice == 0);
|
||||
|
||||
/* A notice which is not published locally must not discard the local
|
||||
* clipboard advertised towards the guest. */
|
||||
lgClipboard_setTransport(&plainOps, &q);
|
||||
CHECK(q.attach == 1);
|
||||
CHECK(q.notice == 1);
|
||||
CHECK(q.release == 0);
|
||||
|
||||
g_params.clipboardToLocal = true;
|
||||
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);
|
||||
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testPendingRemoteReplacesLocal(void)
|
||||
{
|
||||
init();
|
||||
bind(&p);
|
||||
const LG_ClipboardData types[] = { LG_CLIPBOARD_DATA_TEXT };
|
||||
|
||||
lgClipboard_notifyTypes(types, 1);
|
||||
CHECK(p.notice == 1);
|
||||
lgClipboard_setLocalAvailable(false);
|
||||
|
||||
notice(&p, types, 1);
|
||||
CHECK(d.notice == 0);
|
||||
|
||||
lgClipboard_setLocalAvailable(true);
|
||||
CHECK(d.notice == 1);
|
||||
|
||||
lgClipboard_setTransport(&plainOps, &q);
|
||||
CHECK(q.attach == 1);
|
||||
CHECK(q.notice == 0);
|
||||
CHECK(q.release == 1);
|
||||
|
||||
lgClipboard_free();
|
||||
}
|
||||
|
||||
static void testInvalid(void)
|
||||
{
|
||||
init();
|
||||
@@ -1071,6 +1129,8 @@ static const struct Test tests[] =
|
||||
{
|
||||
{ "preference", testPreference },
|
||||
{ "request" , testRequest },
|
||||
{ "remote-local", testRemoteReplacesLocal },
|
||||
{ "pending-remote", testPendingRemoteReplacesLocal },
|
||||
{ "invalid" , testInvalid },
|
||||
{ "cancel" , testCancel },
|
||||
{ "generation", testGeneration },
|
||||
|
||||
@@ -155,7 +155,9 @@ struct Log
|
||||
bool requestOK;
|
||||
bool requestCancelSync;
|
||||
bool requestBeginSync;
|
||||
bool expectReleaseLocked;
|
||||
bool firing;
|
||||
unsigned int releaseLockedN;
|
||||
};
|
||||
|
||||
struct WaylandDSState wlWm;
|
||||
@@ -440,6 +442,12 @@ void lgClipboard_notifyTypes(
|
||||
|
||||
void lgClipboard_release(void)
|
||||
{
|
||||
if (rec.expectReleaseLocked)
|
||||
{
|
||||
CHECK(atomic_flag_test_and_set_explicit(
|
||||
&wlCb.lock, memory_order_acquire));
|
||||
++rec.releaseLockedN;
|
||||
}
|
||||
++rec.releaseN;
|
||||
}
|
||||
|
||||
@@ -690,11 +698,72 @@ static void testMime(void)
|
||||
}
|
||||
|
||||
static void testSelfCopy(void)
|
||||
{
|
||||
start();
|
||||
struct Proxy * local = textOffer();
|
||||
CHECK(rec.noticeN == 1);
|
||||
|
||||
waylandCBNotice(LG_CLIPBOARD_DATA_TEXT);
|
||||
struct Proxy * source = &proto.source[0];
|
||||
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
|
||||
|
||||
/* 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);
|
||||
|
||||
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 == (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(echo->dead);
|
||||
CHECK(proto.offerDestroyN == 2);
|
||||
CHECK(!echo->user);
|
||||
|
||||
waylandCBInvalidate();
|
||||
CHECK(rec.releaseN == 0);
|
||||
CHECK(wlCb.selectionSource == (struct wl_data_source *)source);
|
||||
|
||||
int fds[2];
|
||||
CHECK(pipe(fds) == 0);
|
||||
sourceListener(source)->send(source->data,
|
||||
(struct wl_data_source *)source, "text/plain", fds[1]);
|
||||
CHECK(rec.requestN == 1);
|
||||
CHECK(rec.requestType == LG_CLIPBOARD_DATA_TEXT);
|
||||
CHECK(rec.requestStream);
|
||||
CHECK(rec.requestOpaque);
|
||||
CHECK(rec.requestStream->begin(rec.requestOpaque,
|
||||
LG_CLIPBOARD_DATA_TEXT, LG_CLIPBOARD_SIZE_UNKNOWN) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
CHECK(rec.requestStream->end(rec.requestOpaque, 0) ==
|
||||
LG_CLIPBOARD_RESULT_ACCEPTED);
|
||||
uint8_t value;
|
||||
CHECK(read(fds[0], &value, 1) == 0);
|
||||
CHECK(close(fds[0]) == 0);
|
||||
checkClosed(fds[1]);
|
||||
|
||||
selectOffer(NULL);
|
||||
CHECK(rec.releaseN == 1);
|
||||
CHECK(!wlCb.selectionSource);
|
||||
|
||||
CHECK(proto.dirtyDestroyN == 0);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testIgnoredOffer(void)
|
||||
{
|
||||
start();
|
||||
struct Proxy * offer = newOffer();
|
||||
offerMime(offer, "text/plain");
|
||||
offerMime(offer, wlCb.lgMimetype);
|
||||
offerMime(offer, "application/x-unsupported");
|
||||
selectOffer(offer);
|
||||
|
||||
CHECK(rec.noticeN == 0);
|
||||
@@ -706,6 +775,21 @@ static void testSelfCopy(void)
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testInvalidateSerialization(void)
|
||||
{
|
||||
start();
|
||||
struct Proxy * offer = textOffer();
|
||||
rec.expectReleaseLocked = true;
|
||||
|
||||
waylandCBInvalidate();
|
||||
|
||||
CHECK(rec.releaseN == 1);
|
||||
CHECK(rec.releaseLockedN == 1);
|
||||
CHECK(offer->dead);
|
||||
CHECK(!wlCb.offer);
|
||||
finish();
|
||||
}
|
||||
|
||||
static void testReplace(void)
|
||||
{
|
||||
start();
|
||||
@@ -1245,6 +1329,8 @@ static const struct Test tests[] =
|
||||
{ "teardown" , testTeardown },
|
||||
{ "teardown-external", testTeardownExternalOwner },
|
||||
{ "self-copy" , testSelfCopy },
|
||||
{ "ignored" , testIgnoredOffer },
|
||||
{ "invalidate-lock", testInvalidateSerialization },
|
||||
{ "dnd" , testDnd },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user