Add a workaround to prevent klipper racing with clipboard updates

We have a situation where some clients drop their old offer before
creating a new one. This means klipper tries to fill in the empty
clipboard at the same time the client posts its new real contents.

This adds in a flag (via a hidden mimetype) that klipper is trying to
replace a null clipboard. If this flag is set and our clipboard is not
null because the client has updated it in the meantime we ignore the
klipper update.

It's a workaround, rather than an ideal fix at a data level, but it
solves the problem in the interim.

CCBUG: 424855
This commit is contained in:
David Edmundson 2020-08-04 12:58:43 +01:00 committed by David Edmundson
parent 89433eccec
commit 84337d25f9
2 changed files with 51 additions and 2 deletions

View file

@ -139,6 +139,7 @@ private Q_SLOTS:
void cleanup(); void cleanup();
void testCopyToControl(); void testCopyToControl();
void testCopyFromControl(); void testCopyFromControl();
void testKlipperCase();
private: private:
KWayland::Client::ConnectionThread *m_connection; KWayland::Client::ConnectionThread *m_connection;
@ -302,6 +303,46 @@ void DataControlInterfaceTest::testCopyFromControl()
QVERIFY(m_seat->selection()); QVERIFY(m_seat->selection());
QCOMPARE(m_seat->selection()->mimeTypes(), QStringList({"cheese/test1", "cheese/test2"})); QCOMPARE(m_seat->selection()->mimeTypes(), QStringList({"cheese/test1", "cheese/test2"}));
} }
void DataControlInterfaceTest::testKlipperCase()
{
// This tests the setup of klipper's real world operation and a race with a common pattern seen between clients and klipper
// The client's behaviour is faked with direct access to the seat
QScopedPointer<DataControlDevice> dataControlDevice(new DataControlDevice);
dataControlDevice->init(m_dataControlDeviceManager->get_data_device(*m_clientSeat));
QSignalSpy newOfferSpy(dataControlDevice.data(), &DataControlDevice::dataControlOffer);
QSignalSpy selectionSpy(dataControlDevice.data(), &DataControlDevice::selection);
QSignalSpy serverSelectionChangedSpy(m_seat, &SeatInterface::selectionChanged);
// Client A has a data source
QScopedPointer<TestDataSource> testSelection(new TestDataSource);
m_seat->setSelection(testSelection.data());
// klipper gets it
selectionSpy.wait();
// Client A deletes it
testSelection.reset();
//klipper gets told
selectionSpy.wait();
// Client A sets something else
QScopedPointer<TestDataSource> testSelection2(new TestDataSource);
m_seat->setSelection(testSelection2.data());
// Meanwhile klipper updates with the old content
QScopedPointer<DataControlSource> source(new DataControlSource);
source->init(m_dataControlDeviceManager->create_data_source());
source->offer("fromKlipper/test1");
source->offer("application/x-kde-onlyReplaceEmpty");
dataControlDevice->set_selection(source->object());
QVERIFY(!serverSelectionChangedSpy.wait(10));
QCOMPARE(m_seat->selection(), testSelection2.data());
} }

View file

@ -346,6 +346,14 @@ void SeatInterface::Private::registerDataControlDevice(DataControlDeviceV1Interf
QObject::connect(dataDevice, &DataControlDeviceV1Interface::selectionChanged, q, QObject::connect(dataDevice, &DataControlDeviceV1Interface::selectionChanged, q,
[this, dataDevice] { [this, dataDevice] {
// Special klipper workaround to avoid a race
// If the mimetype x-kde-onlyReplaceEmpty is set, and we've had another update in the meantime, do nothing
// See https://github.com/swaywm/wlr-protocols/issues/92
if (dataDevice->selection() && dataDevice->selection()->mimeTypes().contains(QLatin1String("application/x-kde-onlyReplaceEmpty")) &&
currentSelection) {
dataDevice->selection()->cancel();
return;
}
q->setSelection(dataDevice->selection()); q->setSelection(dataDevice->selection());
} }
); );