mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ClipboardInvalidateMode
|
||||||
|
{
|
||||||
|
CLIPBOARD_INVALIDATE_SILENT,
|
||||||
|
CLIPBOARD_INVALIDATE_CONDITIONAL,
|
||||||
|
CLIPBOARD_INVALIDATE_FORCE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void waylandCBInvalidateLocal(enum ClipboardInvalidateMode mode);
|
||||||
|
|
||||||
/* wlCb.lock must be held. */
|
/* wlCb.lock must be held. */
|
||||||
static struct ClipboardRead * clipboardReadTakeCurrentNL(void)
|
static struct ClipboardRead * clipboardReadTakeCurrentNL(void)
|
||||||
{
|
{
|
||||||
@@ -312,14 +321,15 @@ static void dataDeviceHandleSelection(void * opaque,
|
|||||||
LG_LOCK(wlCb.lock);
|
LG_LOCK(wlCb.lock);
|
||||||
wlCb.selectionSource = NULL;
|
wlCb.selectionSource = NULL;
|
||||||
LG_UNLOCK(wlCb.lock);
|
LG_UNLOCK(wlCb.lock);
|
||||||
waylandCBInvalidate();
|
waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_FORCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DataOffer * extra = wl_data_offer_get_user_data(offer);
|
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);
|
LG_LOCK(wlCb.lock);
|
||||||
wlCb.selectionSource = NULL;
|
wlCb.selectionSource = NULL;
|
||||||
@@ -329,7 +339,12 @@ static void dataDeviceHandleSelection(void * opaque,
|
|||||||
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
for (enum LG_ClipboardData i = 0; i < LG_CLIPBOARD_DATA_NONE; ++i)
|
||||||
free(extra->mimetypes[i]);
|
free(extra->mimetypes[i]);
|
||||||
free(extra);
|
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);
|
wl_data_offer_destroy(offer);
|
||||||
return;
|
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];
|
char * mimetypes[LG_CLIPBOARD_DATA_NONE];
|
||||||
LG_LOCK(wlCb.lock);
|
LG_LOCK(wlCb.lock);
|
||||||
@@ -656,16 +671,27 @@ void waylandCBInvalidate(void)
|
|||||||
wlCb.offer = NULL;
|
wlCb.offer = NULL;
|
||||||
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
memcpy(mimetypes, wlCb.mimetypes, sizeof(mimetypes));
|
||||||
memset(wlCb.mimetypes, 0, sizeof(wlCb.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);
|
LG_UNLOCK(wlCb.lock);
|
||||||
|
|
||||||
if (read)
|
if (read && !notifyProvider)
|
||||||
{
|
|
||||||
const LG_ClipboardRequest request = read->request;
|
|
||||||
clipboardReadRetire(read);
|
|
||||||
lgClipboard_abort(request);
|
lgClipboard_abort(request);
|
||||||
}
|
|
||||||
|
|
||||||
lgClipboard_release();
|
|
||||||
|
|
||||||
if (offer)
|
if (offer)
|
||||||
wl_data_offer_destroy(offer);
|
wl_data_offer_destroy(offer);
|
||||||
@@ -673,6 +699,11 @@ void waylandCBInvalidate(void)
|
|||||||
free(mimetypes[i]);
|
free(mimetypes[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void waylandCBInvalidate(void)
|
||||||
|
{
|
||||||
|
waylandCBInvalidateLocal(CLIPBOARD_INVALIDATE_CONDITIONAL);
|
||||||
|
}
|
||||||
|
|
||||||
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
void waylandCBRequest(LG_ClipboardRequest request, LG_ClipboardData type)
|
||||||
{
|
{
|
||||||
if (request == LG_CLIPBOARD_REQUEST_INVALID ||
|
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, *mimetype);
|
||||||
wl_data_source_offer(source, wlCb.lgMimetype);
|
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);
|
LG_LOCK(wlCb.lock);
|
||||||
if (wlCb.dataDevice)
|
if (wlCb.dataDevice)
|
||||||
{
|
{
|
||||||
@@ -1194,13 +1230,13 @@ static void waylandCBPublish(LG_ClipboardData type)
|
|||||||
wlCb.selectionSource = source;
|
wlCb.selectionSource = source;
|
||||||
wl_data_device_set_selection(wlCb.dataDevice, source,
|
wl_data_device_set_selection(wlCb.dataDevice, source,
|
||||||
wlWm.keyboardEnterSerial);
|
wlWm.keyboardEnterSerial);
|
||||||
}
|
LG_UNLOCK(wlCb.lock);
|
||||||
else
|
return;
|
||||||
{
|
|
||||||
wl_data_source_destroy(source);
|
|
||||||
free(transfer);
|
|
||||||
}
|
}
|
||||||
LG_UNLOCK(wlCb.lock);
|
LG_UNLOCK(wlCb.lock);
|
||||||
|
|
||||||
|
wl_data_source_destroy(source);
|
||||||
|
free(transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void waylandCBNotice(LG_ClipboardData type)
|
void waylandCBNotice(LG_ClipboardData type)
|
||||||
|
|||||||
@@ -485,6 +485,10 @@ static void eventNotice(void * opaque,
|
|||||||
generation = clipboard.remoteGeneration;
|
generation = clipboard.remoteGeneration;
|
||||||
transfer = clearRemoteRequestNL();
|
transfer = clearRemoteRequestNL();
|
||||||
notice = clipboard.localAvailable && g_params.clipboardToLocal;
|
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.stateLock);
|
||||||
LG_UNLOCK(clipboard.requestLock);
|
LG_UNLOCK(clipboard.requestLock);
|
||||||
|
|
||||||
@@ -1222,6 +1226,10 @@ void lgClipboard_setLocalAvailable(bool available)
|
|||||||
notice = available && clipboard.remoteNotice &&
|
notice = available && clipboard.remoteNotice &&
|
||||||
g_params.clipboardToLocal;
|
g_params.clipboardToLocal;
|
||||||
type = clipboard.remoteType;
|
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)
|
if (!available)
|
||||||
transfer = clearRemoteRequestNL();
|
transfer = clearRemoteRequestNL();
|
||||||
LG_UNLOCK(clipboard.stateLock);
|
LG_UNLOCK(clipboard.stateLock);
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ if(ENABLE_WAYLAND)
|
|||||||
teardown
|
teardown
|
||||||
teardown-external
|
teardown-external
|
||||||
self-copy
|
self-copy
|
||||||
|
ignored
|
||||||
|
invalidate-lock
|
||||||
dnd
|
dnd
|
||||||
)
|
)
|
||||||
foreach(name IN LISTS WAYLAND_CLIPBOARD_CASES)
|
foreach(name IN LISTS WAYLAND_CLIPBOARD_CASES)
|
||||||
@@ -471,6 +473,8 @@ target_link_libraries(clipboard-tests
|
|||||||
set(CLIPBOARD_CASES
|
set(CLIPBOARD_CASES
|
||||||
preference
|
preference
|
||||||
request
|
request
|
||||||
|
remote-local
|
||||||
|
pending-remote
|
||||||
invalid
|
invalid
|
||||||
cancel
|
cancel
|
||||||
generation
|
generation
|
||||||
|
|||||||
@@ -627,6 +627,64 @@ static void testRequest(void)
|
|||||||
lgClipboard_free();
|
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)
|
static void testInvalid(void)
|
||||||
{
|
{
|
||||||
init();
|
init();
|
||||||
@@ -1071,6 +1129,8 @@ static const struct Test tests[] =
|
|||||||
{
|
{
|
||||||
{ "preference", testPreference },
|
{ "preference", testPreference },
|
||||||
{ "request" , testRequest },
|
{ "request" , testRequest },
|
||||||
|
{ "remote-local", testRemoteReplacesLocal },
|
||||||
|
{ "pending-remote", testPendingRemoteReplacesLocal },
|
||||||
{ "invalid" , testInvalid },
|
{ "invalid" , testInvalid },
|
||||||
{ "cancel" , testCancel },
|
{ "cancel" , testCancel },
|
||||||
{ "generation", testGeneration },
|
{ "generation", testGeneration },
|
||||||
|
|||||||
@@ -155,7 +155,9 @@ struct Log
|
|||||||
bool requestOK;
|
bool requestOK;
|
||||||
bool requestCancelSync;
|
bool requestCancelSync;
|
||||||
bool requestBeginSync;
|
bool requestBeginSync;
|
||||||
|
bool expectReleaseLocked;
|
||||||
bool firing;
|
bool firing;
|
||||||
|
unsigned int releaseLockedN;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WaylandDSState wlWm;
|
struct WaylandDSState wlWm;
|
||||||
@@ -440,6 +442,12 @@ void lgClipboard_notifyTypes(
|
|||||||
|
|
||||||
void lgClipboard_release(void)
|
void lgClipboard_release(void)
|
||||||
{
|
{
|
||||||
|
if (rec.expectReleaseLocked)
|
||||||
|
{
|
||||||
|
CHECK(atomic_flag_test_and_set_explicit(
|
||||||
|
&wlCb.lock, memory_order_acquire));
|
||||||
|
++rec.releaseLockedN;
|
||||||
|
}
|
||||||
++rec.releaseN;
|
++rec.releaseN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,11 +698,72 @@ static void testMime(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void testSelfCopy(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();
|
start();
|
||||||
struct Proxy * offer = newOffer();
|
struct Proxy * offer = newOffer();
|
||||||
offerMime(offer, "text/plain");
|
offerMime(offer, "application/x-unsupported");
|
||||||
offerMime(offer, wlCb.lgMimetype);
|
|
||||||
selectOffer(offer);
|
selectOffer(offer);
|
||||||
|
|
||||||
CHECK(rec.noticeN == 0);
|
CHECK(rec.noticeN == 0);
|
||||||
@@ -706,6 +775,21 @@ static void testSelfCopy(void)
|
|||||||
finish();
|
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)
|
static void testReplace(void)
|
||||||
{
|
{
|
||||||
start();
|
start();
|
||||||
@@ -1245,6 +1329,8 @@ static const struct Test tests[] =
|
|||||||
{ "teardown" , testTeardown },
|
{ "teardown" , testTeardown },
|
||||||
{ "teardown-external", testTeardownExternalOwner },
|
{ "teardown-external", testTeardownExternalOwner },
|
||||||
{ "self-copy" , testSelfCopy },
|
{ "self-copy" , testSelfCopy },
|
||||||
|
{ "ignored" , testIgnoredOffer },
|
||||||
|
{ "invalidate-lock", testInvalidateSerialization },
|
||||||
{ "dnd" , testDnd },
|
{ "dnd" , testDnd },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user