diff --git a/autotests/client/test_drag_drop.cpp b/autotests/client/test_drag_drop.cpp index afbb2d5..a8d6221 100644 --- a/autotests/client/test_drag_drop.cpp +++ b/autotests/client/test_drag_drop.cpp @@ -1,362 +1,385 @@ /******************************************************************** Copyright 2016 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ // Qt #include // KWin #include "../../src/client/compositor.h" #include "../../src/client/connection_thread.h" #include "../../src/client/datadevice.h" #include "../../src/client/datadevicemanager.h" #include "../../src/client/datasource.h" #include "../../src/client/event_queue.h" #include "../../src/client/pointer.h" #include "../../src/client/registry.h" #include "../../src/client/seat.h" #include "../../src/client/shell.h" #include "../../src/client/shm_pool.h" #include "../../src/client/surface.h" #include "../../src/server/display.h" #include "../../src/server/compositor_interface.h" #include "../../src/server/datadevicemanager_interface.h" #include "../../src/server/seat_interface.h" #include "../../src/server/shell_interface.h" class TestDragAndDrop : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void testDragAndDrop(); void testPointerEventsIgnored(); private: KWayland::Client::Surface *createSurface(); KWayland::Server::SurfaceInterface *getServerSurface(); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositorInterface = nullptr; KWayland::Server::DataDeviceManagerInterface *m_dataDeviceManagerInterface = nullptr; KWayland::Server::SeatInterface *m_seatInterface = nullptr; KWayland::Server::ShellInterface *m_shellInterface = nullptr; KWayland::Client::ConnectionThread *m_connection = nullptr; KWayland::Client::Compositor *m_compositor = nullptr; KWayland::Client::EventQueue *m_queue = nullptr; KWayland::Client::DataDevice *m_dataDevice = nullptr; KWayland::Client::DataSource *m_dataSource = nullptr; QThread *m_thread = nullptr; KWayland::Client::Registry *m_registry = nullptr; KWayland::Client::Seat *m_seat = nullptr; KWayland::Client::Pointer *m_pointer = nullptr; KWayland::Client::DataDeviceManager *m_ddm = nullptr; KWayland::Client::ShmPool *m_shm = nullptr; KWayland::Client::Shell *m_shell = nullptr; }; static const QString s_socketName = QStringLiteral("kwayland-test-wayland-drag-n-drop-0"); void TestDragAndDrop::init() { using namespace KWayland::Server; using namespace KWayland::Client; delete m_display; m_display = new Display(this); m_display->setSocketName(s_socketName); m_display->start(); QVERIFY(m_display->isRunning()); // setup connection m_connection = new KWayland::Client::ConnectionThread; QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); QVERIFY(connectedSpy.isValid()); m_connection->setSocketName(s_socketName); m_compositorInterface = m_display->createCompositor(m_display); m_compositorInterface->create(); QVERIFY(m_compositorInterface->isValid()); m_seatInterface = m_display->createSeat(m_display); m_seatInterface->setHasPointer(true); m_seatInterface->create(); QVERIFY(m_seatInterface->isValid()); m_dataDeviceManagerInterface = m_display->createDataDeviceManager(m_display); m_dataDeviceManagerInterface->create(); QVERIFY(m_dataDeviceManagerInterface->isValid()); m_display->createShm(); m_shellInterface = m_display->createShell(m_display); m_shellInterface->create(); m_thread = new QThread(this); m_connection->moveToThread(m_thread); m_thread->start(); m_connection->initConnection(); QVERIFY(connectedSpy.wait()); m_queue = new EventQueue(this); QVERIFY(!m_queue->isValid()); m_queue->setup(m_connection); QVERIFY(m_queue->isValid()); m_registry = new Registry(); QSignalSpy interfacesAnnouncedSpy(m_registry, &Registry::interfaceAnnounced); QVERIFY(interfacesAnnouncedSpy.isValid()); QVERIFY(!m_registry->eventQueue()); m_registry->setEventQueue(m_queue); QCOMPARE(m_registry->eventQueue(), m_queue); m_registry->create(m_connection); QVERIFY(m_registry->isValid()); m_registry->setup(); QVERIFY(interfacesAnnouncedSpy.wait()); #define CREATE(variable, factory, iface) \ variable = m_registry->create##factory(m_registry->interface(Registry::Interface::iface).name, m_registry->interface(Registry::Interface::iface).version, this); \ QVERIFY(variable); CREATE(m_compositor, Compositor, Compositor) CREATE(m_seat, Seat, Seat) CREATE(m_ddm, DataDeviceManager, DataDeviceManager) CREATE(m_shm, ShmPool, Shm) CREATE(m_shell, Shell, Shell) #undef CREATE QSignalSpy pointerSpy(m_seat, &Seat::hasPointerChanged); QVERIFY(pointerSpy.isValid()); QVERIFY(pointerSpy.wait()); m_pointer = m_seat->createPointer(m_seat); QVERIFY(m_pointer->isValid()); m_dataDevice = m_ddm->getDataDevice(m_seat, this); QVERIFY(m_dataDevice->isValid()); m_dataSource = m_ddm->createDataSource(this); QVERIFY(m_dataSource->isValid()); m_dataSource->offer(QStringLiteral("text/plain")); } void TestDragAndDrop::cleanup() { #define DELETE(name) \ if (name) { \ delete name; \ name = nullptr; \ } DELETE(m_dataSource) DELETE(m_dataDevice) DELETE(m_shell) DELETE(m_shm) DELETE(m_compositor) DELETE(m_ddm) DELETE(m_seat) DELETE(m_queue) DELETE(m_registry) #undef DELETE if (m_thread) { m_thread->quit(); m_thread->wait(); delete m_thread; m_thread = nullptr; } delete m_connection; m_connection = nullptr; delete m_display; m_display = nullptr; } KWayland::Client::Surface *TestDragAndDrop::createSurface() { using namespace KWayland::Client; auto s = m_compositor->createSurface(); QImage img(QSize(100, 200), QImage::Format_RGB32); img.fill(Qt::red); s->attachBuffer(m_shm->createBuffer(img)); s->damage(QRect(0, 0, 100, 200)); s->commit(Surface::CommitFlag::None); return s; } KWayland::Server::SurfaceInterface *TestDragAndDrop::getServerSurface() { using namespace KWayland::Server; QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); if (!surfaceCreatedSpy.isValid()) { return nullptr; } if (!surfaceCreatedSpy.wait()) { return nullptr; } return surfaceCreatedSpy.first().first().value(); } void TestDragAndDrop::testDragAndDrop() { // this test verifies the very basic drag and drop on one surface, an enter, a move and the drop using namespace KWayland::Server; using namespace KWayland::Client; // first create a window QScopedPointer s(createSurface()); auto serverSurface = getServerSurface(); QVERIFY(serverSurface); + QSignalSpy dataSourceSelectedActionChangedSpy(m_dataSource, &DataSource::selectedDragAndDropActionChanged); + QVERIFY(dataSourceSelectedActionChangedSpy.isValid()); + // now we need to pass pointer focus to the Surface and simulate a button press QSignalSpy buttonPressSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonPressSpy.isValid()); m_seatInterface->setFocusedPointerSurface(serverSurface); m_seatInterface->setTimestamp(2); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonPressSpy.wait()); QCOMPARE(buttonPressSpy.first().at(1).value(), quint32(2)); // add some signal spies for client side QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered); QVERIFY(dragEnteredSpy.isValid()); QSignalSpy dragMotionSpy(m_dataDevice, &DataDevice::dragMotion); QVERIFY(dragMotionSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); + QSignalSpy sourceDropSpy(m_dataSource, &DataSource::dragAndDropPerformed); + QVERIFY(sourceDropSpy.isValid()); // now we can start the drag and drop QSignalSpy dragStartedSpy(m_seatInterface, &SeatInterface::dragStarted); QVERIFY(dragStartedSpy.isValid()); + m_dataSource->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); m_dataDevice->startDrag(buttonPressSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragStartedSpy.wait()); QCOMPARE(m_seatInterface->dragSurface(), serverSurface); QCOMPARE(m_seatInterface->dragSurfaceTransformation(), QMatrix4x4()); QVERIFY(!m_seatInterface->dragSource()->icon()); QCOMPARE(m_seatInterface->dragSource()->dragImplicitGrabSerial(), buttonPressSpy.first().first().value()); QVERIFY(dragEnteredSpy.wait()); QCOMPARE(dragEnteredSpy.count(), 1); QCOMPARE(dragEnteredSpy.first().first().value(), m_display->serial()); QCOMPARE(dragEnteredSpy.first().last().toPointF(), QPointF(0, 0)); QCOMPARE(m_dataDevice->dragSurface().data(), s.data()); + auto offer = m_dataDevice->dragOffer(); + QVERIFY(offer); + QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::None); + QSignalSpy offerActionChangedSpy(offer, &DataOffer::selectedDragAndDropActionChanged); + QVERIFY(offerActionChangedSpy.isValid()); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().count(), 1); QCOMPARE(m_dataDevice->dragOffer()->offeredMimeTypes().first().name(), QStringLiteral("text/plain")); + QTRY_COMPARE(offer->sourceDragAndDropActions(), DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move); + offer->setDragAndDropActions(DataDeviceManager::DnDAction::Copy | DataDeviceManager::DnDAction::Move, DataDeviceManager::DnDAction::Move); + QVERIFY(offerActionChangedSpy.wait()); + QCOMPARE(offerActionChangedSpy.count(), 1); + QCOMPARE(offer->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); + QCOMPARE(dataSourceSelectedActionChangedSpy.count(), 1); + QCOMPARE(m_dataSource->selectedDragAndDropAction(), DataDeviceManager::DnDAction::Move); // simulate motion m_seatInterface->setTimestamp(3); m_seatInterface->setPointerPos(QPointF(3, 3)); QVERIFY(dragMotionSpy.wait()); QCOMPARE(dragMotionSpy.count(), 1); QCOMPARE(dragMotionSpy.first().first().toPointF(), QPointF(3, 3)); QCOMPARE(dragMotionSpy.first().last().toUInt(), 3u); // simulate drop - QSignalSpy leftSpy(m_dataDevice, &DataDevice::dragLeft); - QVERIFY(leftSpy.isValid()); QSignalSpy serverDragEndedSpy(m_seatInterface, &SeatInterface::dragEnded); QVERIFY(serverDragEndedSpy.isValid()); QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(4); m_seatInterface->pointerButtonReleased(1); + QVERIFY(sourceDropSpy.isEmpty()); QVERIFY(droppedSpy.wait()); - QCOMPARE(leftSpy.count(), 1); + QCOMPARE(sourceDropSpy.count(), 1); QCOMPARE(serverDragEndedSpy.count(), 1); + QSignalSpy finishedSpy(m_dataSource, &DataSource::dragAndDropFinished); + QVERIFY(finishedSpy.isValid()); + offer->dragAndDropFinished(); + QVERIFY(finishedSpy.wait()); + delete offer; + // verify that we did not get any further input events QVERIFY(pointerMotionSpy.isEmpty()); QCOMPARE(buttonPressSpy.count(), 1); } void TestDragAndDrop::testPointerEventsIgnored() { // this test verifies that all pointer events are ignored on the focused Pointer device during drag using namespace KWayland::Server; using namespace KWayland::Client; // first create a window QScopedPointer s(createSurface()); auto serverSurface = getServerSurface(); QVERIFY(serverSurface); // pass it pointer focus m_seatInterface->setFocusedPointerSurface(serverSurface); // create signal spies for all the pointer events QSignalSpy pointerEnteredSpy(m_pointer, &Pointer::entered); QVERIFY(pointerEnteredSpy.isValid()); QSignalSpy pointerLeftSpy(m_pointer, &Pointer::left); QVERIFY(pointerLeftSpy.isValid()); QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); QVERIFY(pointerMotionSpy.isValid()); QSignalSpy axisSpy(m_pointer, &Pointer::axisChanged); QVERIFY(axisSpy.isValid()); QSignalSpy buttonSpy(m_pointer, &Pointer::buttonStateChanged); QVERIFY(buttonSpy.isValid()); QSignalSpy dragEnteredSpy(m_dataDevice, &DataDevice::dragEntered); QVERIFY(dragEnteredSpy.isValid()); // first simulate a few things quint32 timestamp = 1; m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(10, 10)); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Vertical, 5); // verify that we have those QVERIFY(axisSpy.wait()); QCOMPARE(axisSpy.count(), 1); QCOMPARE(pointerMotionSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 1); QVERIFY(buttonSpy.isEmpty()); QVERIFY(pointerLeftSpy.isEmpty()); // let's start the drag m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(1); QVERIFY(buttonSpy.wait()); QCOMPARE(buttonSpy.count(), 1); m_dataDevice->startDrag(buttonSpy.first().first().value(), m_dataSource, s.data()); QVERIFY(dragEnteredSpy.wait()); // now simulate all the possible pointer interactions m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonPressed(2); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(2); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Vertical, 5); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerAxis(Qt::Horizontal, 5); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(nullptr); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setFocusedPointerSurface(serverSurface); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->setPointerPos(QPointF(50, 50)); // last but not least, simulate the drop QSignalSpy droppedSpy(m_dataDevice, &DataDevice::dropped); QVERIFY(droppedSpy.isValid()); m_seatInterface->setTimestamp(timestamp++); m_seatInterface->pointerButtonReleased(1); QVERIFY(droppedSpy.wait()); // all the changes should have been ignored QCOMPARE(axisSpy.count(), 1); QCOMPARE(pointerMotionSpy.count(), 1); QCOMPARE(pointerEnteredSpy.count(), 1); QCOMPARE(buttonSpy.count(), 1); QVERIFY(pointerLeftSpy.isEmpty()); } QTEST_GUILESS_MAIN(TestDragAndDrop) #include "test_drag_drop.moc" diff --git a/src/client/datadevice.cpp b/src/client/datadevice.cpp index f66413a..34bc625 100644 --- a/src/client/datadevice.cpp +++ b/src/client/datadevice.cpp @@ -1,260 +1,263 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "datadevice.h" #include "dataoffer.h" #include "datasource.h" #include "surface.h" #include "wayland_pointer_p.h" // Qt #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN DataDevice::Private { public: explicit Private(DataDevice *q); void setup(wl_data_device *d); WaylandPointer device; QScopedPointer selectionOffer; struct Drag { - DataOffer *offer = nullptr; + QPointer offer; QPointer surface; }; Drag drag; private: void dataOffer(wl_data_offer *id); void selection(wl_data_offer *id); void dragEnter(quint32 serial, const QPointer &surface, const QPointF &relativeToSurface, wl_data_offer *dataOffer); void dragLeft(); static void dataOfferCallback(void *data, wl_data_device *dataDevice, wl_data_offer *id); static void enterCallback(void *data, wl_data_device *dataDevice, uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id); static void leaveCallback(void *data, wl_data_device *dataDevice); static void motionCallback(void *data, wl_data_device *dataDevice, uint32_t time, wl_fixed_t x, wl_fixed_t y); static void dropCallback(void *data, wl_data_device *dataDevice); static void selectionCallback(void *data, wl_data_device *dataDevice, wl_data_offer *id); static const struct wl_data_device_listener s_listener; DataDevice *q; DataOffer *lastOffer = nullptr; }; const wl_data_device_listener DataDevice::Private::s_listener = { dataOfferCallback, enterCallback, leaveCallback, motionCallback, dropCallback, selectionCallback }; void DataDevice::Private::dataOfferCallback(void *data, wl_data_device *dataDevice, wl_data_offer *id) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); d->dataOffer(id); } void DataDevice::Private::dataOffer(wl_data_offer *id) { Q_ASSERT(!lastOffer); lastOffer = new DataOffer(q, id); Q_ASSERT(lastOffer->isValid()); } void DataDevice::Private::enterCallback(void *data, wl_data_device *dataDevice, uint32_t serial, wl_surface *surface, wl_fixed_t x, wl_fixed_t y, wl_data_offer *id) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); d->dragEnter(serial, QPointer(Surface::get(surface)), QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)), id); } void DataDevice::Private::dragEnter(quint32 serial, const QPointer &surface, const QPointF &relativeToSurface, wl_data_offer *dataOffer) { drag.surface = surface; Q_ASSERT(*lastOffer == dataOffer); drag.offer = lastOffer; lastOffer = nullptr; emit q->dragEntered(serial, relativeToSurface); } void DataDevice::Private::leaveCallback(void *data, wl_data_device *dataDevice) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); d->dragLeft(); } void DataDevice::Private::dragLeft() { if (drag.offer) { delete drag.offer; } drag = Drag(); emit q->dragLeft(); } void DataDevice::Private::motionCallback(void *data, wl_data_device *dataDevice, uint32_t time, wl_fixed_t x, wl_fixed_t y) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); emit d->q->dragMotion(QPointF(wl_fixed_to_double(x), wl_fixed_to_double(y)), time); } void DataDevice::Private::dropCallback(void *data, wl_data_device *dataDevice) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); emit d->q->dropped(); } void DataDevice::Private::selectionCallback(void *data, wl_data_device *dataDevice, wl_data_offer *id) { auto d = reinterpret_cast(data); Q_ASSERT(d->device == dataDevice); d->selection(id); } void DataDevice::Private::selection(wl_data_offer *id) { if (!id) { selectionOffer.reset(); emit q->selectionCleared(); return; } Q_ASSERT(*lastOffer == id); selectionOffer.reset(lastOffer); lastOffer = nullptr; emit q->selectionOffered(selectionOffer.data()); } DataDevice::Private::Private(DataDevice *q) : q(q) { } void DataDevice::Private::setup(wl_data_device *d) { Q_ASSERT(d); Q_ASSERT(!device.isValid()); device.setup(d); wl_data_device_add_listener(device, &s_listener, this); } DataDevice::DataDevice(QObject *parent) : QObject(parent) , d(new Private(this)) { } DataDevice::~DataDevice() { + if (d->drag.offer) { + delete d->drag.offer; + } release(); } void DataDevice::destroy() { d->device.destroy(); } void DataDevice::release() { d->device.release(); } bool DataDevice::isValid() const { return d->device.isValid(); } void DataDevice::setup(wl_data_device *dataDevice) { d->setup(dataDevice); } void DataDevice::startDragInternally(quint32 serial, Surface *origin, Surface *icon) { startDrag(serial, nullptr, origin, icon); } namespace { static wl_data_source *dataSource(const DataSource *source) { if (!source) { return nullptr; } return *source; } } void DataDevice::startDrag(quint32 serial, DataSource *source, Surface *origin, Surface *icon) { wl_data_device_start_drag(d->device, dataSource(source), *origin, icon ? (wl_surface*)*icon : nullptr, serial); } void DataDevice::setSelection(quint32 serial, DataSource *source) { wl_data_device_set_selection(d->device, dataSource(source), serial); } void DataDevice::clearSelection(quint32 serial) { setSelection(serial); } DataOffer *DataDevice::offeredSelection() const { return d->selectionOffer.data(); } QPointer DataDevice::dragSurface() const { return d->drag.surface; } DataOffer *DataDevice::dragOffer() const { return d->drag.offer; } DataDevice::operator wl_data_device*() { return d->device; } DataDevice::operator wl_data_device*() const { return d->device; } } } diff --git a/src/client/datadevicemanager.h b/src/client/datadevicemanager.h index d899840..1834bc2 100644 --- a/src/client/datadevicemanager.h +++ b/src/client/datadevicemanager.h @@ -1,142 +1,156 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_DATA_DEVICE_MANAGER_H #define WAYLAND_DATA_DEVICE_MANAGER_H #include #include struct wl_data_device_manager; namespace KWayland { namespace Client { class EventQueue; class DataDevice; class DataSource; class Seat; /** * @short Wrapper for the wl_data_device_manager interface. * * This class provides a convenient wrapper for the wl_data_device_manager interface. * * To use this class one needs to interact with the Registry. There are two * possible ways to create the DataDeviceManager interface: * @code * DataDeviceManager *m = registry->createDataDeviceManager(name, version); * @endcode * * This creates the DataDeviceManager and sets it up directly. As an alternative this * can also be done in a more low level way: * @code * DataDeviceManager *m = new DataDeviceManager; * m->setup(registry->bindDataDeviceManager(name, version)); * @endcode * * The DataDeviceManager can be used as a drop-in replacement for any wl_data_device_manager * pointer as it provides matching cast operators. * * @see Registry **/ class KWAYLANDCLIENT_EXPORT DataDeviceManager : public QObject { Q_OBJECT public: + /** + * Drag and Drop actions supported by DataSource and DataOffer. + * @since 5.42 + **/ + enum class DnDAction { + None = 0, + Copy = 1 << 0, + Move = 1 << 1, + Ask = 1 << 2 + }; + Q_DECLARE_FLAGS(DnDActions, DnDAction) + /** * Creates a new Compositor. * Note: after constructing the Compositor it is not yet valid and one needs * to call setup. In order to get a ready to use Compositor prefer using * Registry::createCompositor. **/ explicit DataDeviceManager(QObject *parent = nullptr); virtual ~DataDeviceManager(); /** * @returns @c true if managing a wl_data_device_manager. **/ bool isValid() const; /** * Setup this DataDeviceManager to manage the @p manager. * When using Registry::createDataDeviceManager there is no need to call this * method. **/ void setup(wl_data_device_manager *manager); /** * Releases the wl_data_device_manager interface. * After the interface has been released the DataDeviceManager instance is no * longer valid and can be setup with another wl_data_device_manager interface. **/ void release(); /** * Destroys the data held by this DataDeviceManager. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new wl_data_device_manager interface * once there is a new connection available. * * This method is automatically invoked when the Registry which created this * DataDeviceManager gets destroyed. * * @see release **/ void destroy(); /** * Sets the @p queue to use for creating a DataSource. **/ void setEventQueue(EventQueue *queue); /** * @returns The event queue to use for creating a DataSource. **/ EventQueue *eventQueue(); DataSource *createDataSource(QObject *parent = nullptr); DataDevice *getDataDevice(Seat *seat, QObject *parent = nullptr); operator wl_data_device_manager*(); operator wl_data_device_manager*() const; Q_SIGNALS: /** * The corresponding global for this interface on the Registry got removed. * * This signal gets only emitted if the Compositor got created by * Registry::createDataDeviceManager * * @since 5.5 **/ void removed(); private: class Private; QScopedPointer d; }; } } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Client::DataDeviceManager::DnDActions) + #endif diff --git a/src/client/dataoffer.cpp b/src/client/dataoffer.cpp index e2db73b..55777b7 100644 --- a/src/client/dataoffer.cpp +++ b/src/client/dataoffer.cpp @@ -1,133 +1,242 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "dataoffer.h" #include "datadevice.h" #include "wayland_pointer_p.h" // Qt #include #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN DataOffer::Private { public: Private(wl_data_offer *offer, DataOffer *q); WaylandPointer dataOffer; QList mimeTypes; + DataDeviceManager::DnDActions sourceActions = DataDeviceManager::DnDAction::None; + DataDeviceManager::DnDAction selectedAction = DataDeviceManager::DnDAction::None; private: void offer(const QString &mimeType); + void setAction(DataDeviceManager::DnDAction action); static void offerCallback(void *data, wl_data_offer *dataOffer, const char *mimeType); + static void sourceActionsCallback(void *data, wl_data_offer *wl_data_offer, uint32_t source_actions); + static void actionCallback(void *data, wl_data_offer *wl_data_offer, uint32_t dnd_action); DataOffer *q; static const struct wl_data_offer_listener s_listener; }; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_offer_listener DataOffer::Private::s_listener = { - offerCallback + offerCallback, + sourceActionsCallback, + actionCallback }; #endif DataOffer::Private::Private(wl_data_offer *offer, DataOffer *q) : q(q) { dataOffer.setup(offer); wl_data_offer_add_listener(offer, &s_listener, this); } void DataOffer::Private::offerCallback(void *data, wl_data_offer *dataOffer, const char *mimeType) { auto d = reinterpret_cast(data); Q_ASSERT(d->dataOffer == dataOffer); d->offer(QString::fromUtf8(mimeType)); } void DataOffer::Private::offer(const QString &mimeType) { QMimeDatabase db; const auto &m = db.mimeTypeForName(mimeType); if (m.isValid()) { mimeTypes << m; emit q->mimeTypeOffered(m.name()); } } +void DataOffer::Private::sourceActionsCallback(void *data, wl_data_offer *wl_data_offer, uint32_t source_actions) +{ + Q_UNUSED(wl_data_offer) + DataDeviceManager::DnDActions actions; + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + actions |= DataDeviceManager::DnDAction::Copy; + } + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + actions |= DataDeviceManager::DnDAction::Move; + } + if (source_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + actions |= DataDeviceManager::DnDAction::Ask; + } + auto d = reinterpret_cast(data); + if (d->sourceActions != actions) { + d->sourceActions = actions; + emit d->q->sourceDragAndDropActionsChanged(); + } +} + +void DataOffer::Private::actionCallback(void *data, wl_data_offer *wl_data_offer, uint32_t dnd_action) +{ + Q_UNUSED(wl_data_offer) + auto d = reinterpret_cast(data); + switch(dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + d->setAction(DataDeviceManager::DnDAction::Copy); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + d->setAction(DataDeviceManager::DnDAction::Move); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: + d->setAction(DataDeviceManager::DnDAction::Ask); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + d->setAction(DataDeviceManager::DnDAction::None); + break; + default: + Q_UNREACHABLE(); + } +} + +void DataOffer::Private::setAction(DataDeviceManager::DnDAction action) +{ + if (action == selectedAction) { + return; + } + selectedAction = action; + emit q->selectedDragAndDropActionChanged(); +} + DataOffer::DataOffer(DataDevice *parent, wl_data_offer *dataOffer) : QObject(parent) , d(new Private(dataOffer, this)) { } DataOffer::~DataOffer() { release(); } void DataOffer::release() { d->dataOffer.release(); } void DataOffer::destroy() { d->dataOffer.destroy(); } bool DataOffer::isValid() const { return d->dataOffer.isValid(); } QList< QMimeType > DataOffer::offeredMimeTypes() const { return d->mimeTypes; } void DataOffer::receive(const QMimeType &mimeType, qint32 fd) { receive(mimeType.name(), fd); } void DataOffer::receive(const QString &mimeType, qint32 fd) { Q_ASSERT(isValid()); wl_data_offer_receive(d->dataOffer, mimeType.toUtf8().constData(), fd); } DataOffer::operator wl_data_offer*() { return d->dataOffer; } DataOffer::operator wl_data_offer*() const { return d->dataOffer; } +void DataOffer::dragAndDropFinished() +{ + Q_ASSERT(isValid()); + if (wl_proxy_get_version(d->dataOffer) < WL_DATA_OFFER_FINISH_SINCE_VERSION) { + return; + } + wl_data_offer_finish(d->dataOffer); +} + +DataDeviceManager::DnDActions DataOffer::sourceDragAndDropActions() const +{ + return d->sourceActions; +} + +void DataOffer::setDragAndDropActions(DataDeviceManager::DnDActions supported, DataDeviceManager::DnDAction preferred) +{ + if (wl_proxy_get_version(d->dataOffer) < WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) { + return; + } + auto toWayland = [] (DataDeviceManager::DnDAction action) { + switch (action) { + case DataDeviceManager::DnDAction::Copy: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + case DataDeviceManager::DnDAction::Move: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + case DataDeviceManager::DnDAction::Ask: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + case DataDeviceManager::DnDAction::None: + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + default: + Q_UNREACHABLE(); + } + }; + uint32_t wlSupported = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (supported.testFlag(DataDeviceManager::DnDAction::Copy)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Copy); + } + if (supported.testFlag(DataDeviceManager::DnDAction::Move)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Move); + } + if (supported.testFlag(DataDeviceManager::DnDAction::Ask)) { + wlSupported |= toWayland(DataDeviceManager::DnDAction::Ask); + } + wl_data_offer_set_actions(d->dataOffer, wlSupported, toWayland(preferred)); +} + +DataDeviceManager::DnDAction DataOffer::selectedDragAndDropAction() const +{ + return d->selectedAction; +} + } } diff --git a/src/client/dataoffer.h b/src/client/dataoffer.h index edcebd3..cf954d9 100644 --- a/src/client/dataoffer.h +++ b/src/client/dataoffer.h @@ -1,100 +1,145 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_DATAOFFER_H #define WAYLAND_DATAOFFER_H #include #include +#include "datadevicemanager.h" + struct wl_data_offer; class QMimeType; namespace KWayland { namespace Client { class DataDevice; /** * @short Wrapper for the wl_data_offer interface. * * This class is a convenient wrapper for the wl_data_offer interface. * The DataOffer gets created by DataDevice. * * @see DataOfferManager **/ class KWAYLANDCLIENT_EXPORT DataOffer : public QObject { Q_OBJECT public: virtual ~DataOffer(); /** * Releases the wl_data_offer interface. * After the interface has been released the DataOffer instance is no * longer valid and can be setup with another wl_data_offer interface. **/ void release(); /** * Destroys the data held by this DataOffer. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new wl_data_offer interface * once there is a new connection available. * * This method is automatically invoked when the Registry which created this * DataOffer gets destroyed. * * @see release **/ void destroy(); /** * @returns @c true if managing a wl_data_offer. **/ bool isValid() const; QList offeredMimeTypes() const; void receive(const QMimeType &mimeType, qint32 fd); void receive(const QString &mimeType, qint32 fd); + /** + * Notifies the compositor that the drag destination successfully + * finished the drag-and-drop operation. + * + * After this operation it is only allowed to release the DataOffer. + * + * @since 5.42 + **/ + void dragAndDropFinished(); + + /** + * The actions offered by the DataSource. + * @since 5.42 + * @see sourceDragAndDropActionsChanged + **/ + DataDeviceManager::DnDActions sourceDragAndDropActions() const; + + /** + * Sets the @p supported and @p preferred Drag and Drop actions. + * @since 5.42 + **/ + void setDragAndDropActions(DataDeviceManager::DnDActions supported, DataDeviceManager::DnDAction preferred); + + /** + * The currently selected drag and drop action by the compositor. + * @see selectedDragAndDropActionChanged + * @since 5.42 + **/ + DataDeviceManager::DnDAction selectedDragAndDropAction() const; + operator wl_data_offer*(); operator wl_data_offer*() const; Q_SIGNALS: void mimeTypeOffered(const QString&); + /** + * Emitted whenever the @link{sourceDragAndDropActions} changed, e.g. on enter or when + * the DataSource changes the supported actions. + * @see sourceDragAndDropActions + * @since 5.42 + **/ + void sourceDragAndDropActionsChanged(); + /** + * Emitted whenever the selected drag and drop action changes. + * @see selectedDragAndDropAction + * @since 5.42 + **/ + void selectedDragAndDropActionChanged(); private: friend class DataDevice; explicit DataOffer(DataDevice *parent, wl_data_offer *dataOffer); class Private; QScopedPointer d; }; } } Q_DECLARE_METATYPE(KWayland::Client::DataOffer*) #endif diff --git a/src/client/datasource.cpp b/src/client/datasource.cpp index def785b..615fe8c 100644 --- a/src/client/datasource.cpp +++ b/src/client/datasource.cpp @@ -1,145 +1,218 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "datasource.h" #include "wayland_pointer_p.h" // Qt #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN DataSource::Private { public: explicit Private(DataSource *q); void setup(wl_data_source *s); WaylandPointer source; + DataDeviceManager::DnDAction selectedAction = DataDeviceManager::DnDAction::None; private: + void setAction(DataDeviceManager::DnDAction action); static void targetCallback(void *data, wl_data_source *dataSource, const char *mimeType); static void sendCallback(void *data, wl_data_source *dataSource, const char *mimeType, int32_t fd); static void cancelledCallback(void *data, wl_data_source *dataSource); + static void dndDropPerformedCallback(void *data, wl_data_source *wl_data_source); + static void dndFinishedCallback(void *data, wl_data_source *wl_data_source); + static void actionCallback(void *data, wl_data_source *wl_data_source, uint32_t dnd_action); static const struct wl_data_source_listener s_listener; DataSource *q; }; const wl_data_source_listener DataSource::Private::s_listener = { targetCallback, sendCallback, - cancelledCallback + cancelledCallback, + dndDropPerformedCallback, + dndFinishedCallback, + actionCallback }; DataSource::Private::Private(DataSource *q) : q(q) { } void DataSource::Private::targetCallback(void *data, wl_data_source *dataSource, const char *mimeType) { auto d = reinterpret_cast(data); Q_ASSERT(d->source == dataSource); emit d->q->targetAccepts(QString::fromUtf8(mimeType)); } void DataSource::Private::sendCallback(void *data, wl_data_source *dataSource, const char *mimeType, int32_t fd) { auto d = reinterpret_cast(data); Q_ASSERT(d->source == dataSource); emit d->q->sendDataRequested(QString::fromUtf8(mimeType), fd); } void DataSource::Private::cancelledCallback(void *data, wl_data_source *dataSource) { auto d = reinterpret_cast(data); Q_ASSERT(d->source == dataSource); emit d->q->cancelled(); } +void DataSource::Private::dndDropPerformedCallback(void *data, wl_data_source *wl_data_source) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + emit d->q->dragAndDropPerformed(); +} + +void DataSource::Private::dndFinishedCallback(void *data, wl_data_source *wl_data_source) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + emit d->q->dragAndDropFinished(); +} + +void DataSource::Private::actionCallback(void *data, wl_data_source *wl_data_source, uint32_t dnd_action) +{ + Q_UNUSED(wl_data_source) + auto d = reinterpret_cast(data); + switch(dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + d->setAction(DataDeviceManager::DnDAction::Copy); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + d->setAction(DataDeviceManager::DnDAction::Move); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK: + d->setAction(DataDeviceManager::DnDAction::Ask); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + d->setAction(DataDeviceManager::DnDAction::None); + break; + default: + Q_UNREACHABLE(); + } +} + +void DataSource::Private::setAction(DataDeviceManager::DnDAction action) +{ + if (action == selectedAction) { + return; + } + selectedAction = action; + emit q->selectedDragAndDropActionChanged(); +} + void DataSource::Private::setup(wl_data_source *s) { Q_ASSERT(!source.isValid()); Q_ASSERT(s); source.setup(s); wl_data_source_add_listener(s, &s_listener, this); } DataSource::DataSource(QObject *parent) : QObject(parent) , d(new Private(this)) { } DataSource::~DataSource() { release(); } void DataSource::release() { d->source.release(); } void DataSource::destroy() { d->source.destroy(); } bool DataSource::isValid() const { return d->source.isValid(); } void DataSource::setup(wl_data_source *dataSource) { d->setup(dataSource); } void DataSource::offer(const QString &mimeType) { wl_data_source_offer(d->source, mimeType.toUtf8().constData()); } void DataSource::offer(const QMimeType &mimeType) { if (!mimeType.isValid()) { return; } offer(mimeType.name()); } DataSource::operator wl_data_source*() const { return d->source; } DataSource::operator wl_data_source*() { return d->source; } +void DataSource::setDragAndDropActions(DataDeviceManager::DnDActions actions) +{ + uint32_t wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (actions.testFlag(DataDeviceManager::DnDAction::Copy)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + if (actions.testFlag(DataDeviceManager::DnDAction::Move)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + if (actions.testFlag(DataDeviceManager::DnDAction::Ask)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_source_set_actions(d->source, wlActions); +} + +DataDeviceManager::DnDAction DataSource::selectedDragAndDropAction() const +{ + return d->selectedAction; +} + } } diff --git a/src/client/datasource.h b/src/client/datasource.h index 66fb005..d38a885 100644 --- a/src/client/datasource.h +++ b/src/client/datasource.h @@ -1,117 +1,174 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_DATASOURCE_H #define WAYLAND_DATASOURCE_H #include "buffer.h" +#include "datadevicemanager.h" #include #include struct wl_data_source; class QMimeType; namespace KWayland { namespace Client { /** * @short Wrapper for the wl_data_source interface. * * This class is a convenient wrapper for the wl_data_source interface. * To create a DataSource call DataDeviceManager::createDataSource. * * @see DataDeviceManager **/ class KWAYLANDCLIENT_EXPORT DataSource : public QObject { Q_OBJECT public: explicit DataSource(QObject *parent = nullptr); virtual ~DataSource(); /** * Setup this DataSource to manage the @p dataSource. * When using DataDeviceManager::createDataSource there is no need to call this * method. **/ void setup(wl_data_source *dataSource); /** * Releases the wl_data_source interface. * After the interface has been released the DataSource instance is no * longer valid and can be setup with another wl_data_source interface. **/ void release(); /** * Destroys the data held by this DataSource. * This method is supposed to be used when the connection to the Wayland * server goes away. If the connection is not valid anymore, it's not * possible to call release anymore as that calls into the Wayland * connection and the call would fail. This method cleans up the data, so * that the instance can be deleted or set up to a new wl_data_source interface * once there is a new connection available. * * This method is automatically invoked when the Registry which created this * DataSource gets destroyed. * * @see release **/ void destroy(); /** * @returns @c true if managing a wl_data_source. **/ bool isValid() const; void offer(const QString &mimeType); void offer(const QMimeType &mimeType); + /** + * Sets the actions that the source side client supports for this + * operation. + * + * This request must be made once only, and can only be made on sources + * used in drag-and-drop, so it must be performed before + * @link{DataDevice::startDrag}. Attempting to use the source other than + * for drag-and-drop will raise a protocol error. + * @since 5.42 + **/ + void setDragAndDropActions(DataDeviceManager::DnDActions actions); + + /** + * The currently selected drag and drop action by the compositor. + * @see selectedDragAndDropActionChanged + * @since 5.42 + **/ + DataDeviceManager::DnDAction selectedDragAndDropAction() const; + operator wl_data_source*(); operator wl_data_source*() const; Q_SIGNALS: /** * Emitted when a target accepts pointer_focus or motion events. If * a target does not accept any of the offered types, @p mimeType is empty. **/ void targetAccepts(const QString &mimeType); /** * Request for data from the client. Send the data as the * specified @p mimeType over the passed file descriptor @p fd, then close * it. **/ void sendDataRequested(const QString &mimeType, qint32 fd); /** * This DataSource has been replaced by another DataSource. * The client should clean up and destroy this DataSource. **/ void cancelled(); + /** + * The drag-and-drop operation physically finished. + * + * The user performed the drop action. This signal does not + * indicate acceptance, @link{cancelled} may still be + * emitted afterwards if the drop destination does not accept any + * mime type. + * + * However, this signal might not be received if the + * compositor cancelled the drag-and-drop operation before this + * signal could happen. + * + * Note that the DataSource may still be used in the future and + * should not be destroyed here. + * @since 5.42 + **/ + void dragAndDropPerformed(); + + /** + * The drag-and-drop operation concluded. + * + * The drop destination finished interoperating with this DataSource, + * so the client is now free to destroy this DataSource. + * + * If the action used to perform the operation was "move", the + * source can now delete the transferred data. + * @since 5.42 + */ + void dragAndDropFinished(); + + /** + * Emitted whenever the selected drag and drop action changes. + * @see selectedDragAndDropAction + * @since 5.42 + **/ + void selectedDragAndDropActionChanged(); + private: class Private; QScopedPointer d; }; } } #endif diff --git a/src/client/registry.cpp b/src/client/registry.cpp index 4cf1ab5..a00f726 100644 --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -1,815 +1,815 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "registry.h" #include "compositor.h" #include "connection_thread.h" #include "datadevicemanager.h" #include "dpms.h" #include "event_queue.h" #include "fakeinput.h" #include "fullscreen_shell.h" #include "idle.h" #include "idleinhibit.h" #include "logging_p.h" #include "outputconfiguration.h" #include "outputmanagement.h" #include "outputdevice.h" #include "output.h" #include "plasmashell.h" #include "plasmawindowmanagement.h" #include "pointerconstraints.h" #include "pointergestures.h" #include "seat.h" #include "shadow.h" #include "blur.h" #include "contrast.h" #include "relativepointer.h" #include "server_decoration.h" #include "slide.h" #include "shell.h" #include "shm_pool.h" #include "subcompositor.h" #include "textinput_p.h" #include "xdgshell.h" #include "xdgshell_p.h" #include "wayland_pointer_p.h" #include "xdgforeign_v2.h" #include "appmenu.h" // Qt #include // wayland #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /***** * How to add another interface: * * define a new enum value in Registry::Interface * * define the bind method * * define the create method * * define the Announced signal * * define the Removed signal * * add a block to s_interfaces * * add the BIND macro for the new bind * * add the CREATE macro for the new create * * extend registry unit test to verify that it works ****/ namespace KWayland { namespace Client { namespace { struct SuppertedInterfaceData { quint32 maxVersion; QByteArray name; const wl_interface *interface; void (Registry::*announcedSignal)(quint32, quint32); void (Registry::*removedSignal)(quint32); }; static const QMap s_interfaces = { {Registry::Interface::Compositor, { 3, QByteArrayLiteral("wl_compositor"), &wl_compositor_interface, &Registry::compositorAnnounced, &Registry::compositorRemoved }}, {Registry::Interface::DataDeviceManager, { - 2, + 3, QByteArrayLiteral("wl_data_device_manager"), &wl_data_device_manager_interface, &Registry::dataDeviceManagerAnnounced, &Registry::dataDeviceManagerRemoved }}, {Registry::Interface::Output, { 2, QByteArrayLiteral("wl_output"), &wl_output_interface, &Registry::outputAnnounced, &Registry::outputRemoved }}, {Registry::Interface::Shm, { 1, QByteArrayLiteral("wl_shm"), &wl_shm_interface, &Registry::shmAnnounced, &Registry::shmRemoved }}, {Registry::Interface::Seat, { 4, QByteArrayLiteral("wl_seat"), &wl_seat_interface, &Registry::seatAnnounced, &Registry::seatRemoved }}, {Registry::Interface::Shell, { 1, QByteArrayLiteral("wl_shell"), &wl_shell_interface, &Registry::shellAnnounced, &Registry::shellRemoved }}, {Registry::Interface::SubCompositor, { 1, QByteArrayLiteral("wl_subcompositor"), &wl_subcompositor_interface, &Registry::subCompositorAnnounced, &Registry::subCompositorRemoved }}, {Registry::Interface::PlasmaShell, { 4, QByteArrayLiteral("org_kde_plasma_shell"), &org_kde_plasma_shell_interface, &Registry::plasmaShellAnnounced, &Registry::plasmaShellRemoved }}, {Registry::Interface::PlasmaWindowManagement, { 7, QByteArrayLiteral("org_kde_plasma_window_management"), &org_kde_plasma_window_management_interface, &Registry::plasmaWindowManagementAnnounced, &Registry::plasmaWindowManagementRemoved }}, {Registry::Interface::Idle, { 1, QByteArrayLiteral("org_kde_kwin_idle"), &org_kde_kwin_idle_interface, &Registry::idleAnnounced, &Registry::idleRemoved }}, {Registry::Interface::FakeInput, { 2, QByteArrayLiteral("org_kde_kwin_fake_input"), &org_kde_kwin_fake_input_interface, &Registry::fakeInputAnnounced, &Registry::fakeInputRemoved }}, {Registry::Interface::OutputManagement, { 1, QByteArrayLiteral("org_kde_kwin_outputmanagement"), &org_kde_kwin_outputmanagement_interface, &Registry::outputManagementAnnounced, &Registry::outputManagementRemoved }}, {Registry::Interface::OutputDevice, { 1, QByteArrayLiteral("org_kde_kwin_outputdevice"), &org_kde_kwin_outputdevice_interface, &Registry::outputDeviceAnnounced, &Registry::outputDeviceRemoved }}, {Registry::Interface::Shadow, { 2, QByteArrayLiteral("org_kde_kwin_shadow_manager"), &org_kde_kwin_shadow_manager_interface, &Registry::shadowAnnounced, &Registry::shadowRemoved }}, {Registry::Interface::Blur, { 1, QByteArrayLiteral("org_kde_kwin_blur_manager"), &org_kde_kwin_blur_manager_interface, &Registry::blurAnnounced, &Registry::blurRemoved }}, {Registry::Interface::Contrast, { 1, QByteArrayLiteral("org_kde_kwin_contrast_manager"), &org_kde_kwin_contrast_manager_interface, &Registry::contrastAnnounced, &Registry::contrastRemoved }}, {Registry::Interface::Slide, { 1, QByteArrayLiteral("org_kde_kwin_slide_manager"), &org_kde_kwin_slide_manager_interface, &Registry::slideAnnounced, &Registry::slideRemoved }}, {Registry::Interface::FullscreenShell, { 1, QByteArrayLiteral("_wl_fullscreen_shell"), &_wl_fullscreen_shell_interface, &Registry::fullscreenShellAnnounced, &Registry::fullscreenShellRemoved }}, {Registry::Interface::Dpms, { 1, QByteArrayLiteral("org_kde_kwin_dpms_manager"), &org_kde_kwin_dpms_manager_interface, &Registry::dpmsAnnounced, &Registry::dpmsRemoved }}, {Registry::Interface::ServerSideDecorationManager, { 1, QByteArrayLiteral("org_kde_kwin_server_decoration_manager"), &org_kde_kwin_server_decoration_manager_interface, &Registry::serverSideDecorationManagerAnnounced, &Registry::serverSideDecorationManagerRemoved }}, {Registry::Interface::TextInputManagerUnstableV0, { 1, QByteArrayLiteral("wl_text_input_manager"), &wl_text_input_manager_interface, &Registry::textInputManagerUnstableV0Announced, &Registry::textInputManagerUnstableV0Removed }}, {Registry::Interface::TextInputManagerUnstableV2, { 1, QByteArrayLiteral("zwp_text_input_manager_v2"), &zwp_text_input_manager_v2_interface, &Registry::textInputManagerUnstableV2Announced, &Registry::textInputManagerUnstableV2Removed }}, {Registry::Interface::XdgShellUnstableV5, { 1, QByteArrayLiteral("xdg_shell"), &xdg_shell_interface, &Registry::xdgShellUnstableV5Announced, &Registry::xdgShellUnstableV5Removed }}, {Registry::Interface::RelativePointerManagerUnstableV1, { 1, QByteArrayLiteral("zwp_relative_pointer_manager_v1"), &zwp_relative_pointer_manager_v1_interface, &Registry::relativePointerManagerUnstableV1Announced, &Registry::relativePointerManagerUnstableV1Removed }}, {Registry::Interface::PointerGesturesUnstableV1, { 1, QByteArrayLiteral("zwp_pointer_gestures_v1"), &zwp_pointer_gestures_v1_interface, &Registry::pointerGesturesUnstableV1Announced, &Registry::pointerGesturesUnstableV1Removed }}, {Registry::Interface::PointerConstraintsUnstableV1, { 1, QByteArrayLiteral("zwp_pointer_constraints_v1"), &zwp_pointer_constraints_v1_interface, &Registry::pointerConstraintsUnstableV1Announced, &Registry::pointerConstraintsUnstableV1Removed }}, {Registry::Interface::XdgExporterUnstableV2, { 1, QByteArrayLiteral("zxdg_exporter_v2"), &zxdg_exporter_v2_interface, &Registry::exporterUnstableV2Announced, &Registry::exporterUnstableV2Removed }}, {Registry::Interface::XdgImporterUnstableV2, { 1, QByteArrayLiteral("zxdg_importer_v2"), &zxdg_importer_v2_interface, &Registry::importerUnstableV2Announced, &Registry::importerUnstableV2Removed }}, {Registry::Interface::XdgShellUnstableV6, { 1, QByteArrayLiteral("zxdg_shell_v6"), &zxdg_shell_v6_interface, &Registry::xdgShellUnstableV6Announced, &Registry::xdgShellUnstableV6Removed }}, {Registry::Interface::IdleInhibitManagerUnstableV1, { 1, QByteArrayLiteral("zwp_idle_inhibit_manager_v1"), &zwp_idle_inhibit_manager_v1_interface, &Registry::idleInhibitManagerUnstableV1Announced, &Registry::idleInhibitManagerUnstableV1Removed }}, {Registry::Interface::AppMenu, { 1, QByteArrayLiteral("org_kde_kwin_appmenu_manager"), &org_kde_kwin_appmenu_manager_interface, &Registry::appMenuAnnounced, &Registry::appMenuRemoved }} }; static quint32 maxVersion(const Registry::Interface &interface) { auto it = s_interfaces.find(interface); if (it != s_interfaces.end()) { return it.value().maxVersion; } return 0; } } class Q_DECL_HIDDEN Registry::Private { public: Private(Registry *q); void setup(); bool hasInterface(Interface interface) const; AnnouncedInterface interface(Interface interface) const; QVector interfaces(Interface interface) const; Interface interfaceForName(quint32 name) const; template T *bind(Interface interface, uint32_t name, uint32_t version) const; template T *create(quint32 name, quint32 version, QObject *parent, WL *(Registry::*bindMethod)(uint32_t, uint32_t) const); WaylandPointer registry; static const struct wl_callback_listener s_callbackListener; WaylandPointer callback; EventQueue *queue = nullptr; private: void handleAnnounce(uint32_t name, const char *interface, uint32_t version); void handleRemove(uint32_t name); void handleGlobalSync(); static void globalAnnounce(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void globalRemove(void *data, struct wl_registry *registry, uint32_t name); static void globalSync(void *data, struct wl_callback *callback, uint32_t serial); Registry *q; struct InterfaceData { Interface interface; uint32_t name; uint32_t version; }; QList m_interfaces; static const struct wl_registry_listener s_registryListener; }; Registry::Private::Private(Registry *q) : q(q) { } void Registry::Private::setup() { wl_registry_add_listener(registry, &s_registryListener, this); wl_callback_add_listener(callback, &s_callbackListener, this); } Registry::Registry(QObject *parent) : QObject(parent) , d(new Private(this)) { } Registry::~Registry() { release(); } void Registry::release() { d->registry.release(); d->callback.release(); } void Registry::destroy() { emit registryDestroyed(); d->registry.destroy(); d->callback.destroy(); } void Registry::create(wl_display *display) { Q_ASSERT(display); Q_ASSERT(!isValid()); d->registry.setup(wl_display_get_registry(display)); d->callback.setup(wl_display_sync(display)); if (d->queue) { d->queue->addProxy(d->registry); d->queue->addProxy(d->callback); } } void Registry::create(ConnectionThread *connection) { create(connection->display()); connect(connection, &ConnectionThread::connectionDied, this, &Registry::destroy); } void Registry::setup() { Q_ASSERT(isValid()); d->setup(); } void Registry::setEventQueue(EventQueue *queue) { d->queue = queue; if (!queue) { return; } if (d->registry) { d->queue->addProxy(d->registry); } if (d->callback) { d->queue->addProxy(d->callback); } } EventQueue *Registry::eventQueue() { return d->queue; } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_registry_listener Registry::Private::s_registryListener = { globalAnnounce, globalRemove }; const struct wl_callback_listener Registry::Private::s_callbackListener = { globalSync }; #endif void Registry::Private::globalAnnounce(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { auto r = reinterpret_cast(data); Q_ASSERT(registry == r->registry); r->handleAnnounce(name, interface, version); } void Registry::Private::globalRemove(void *data, wl_registry *registry, uint32_t name) { auto r = reinterpret_cast(data); Q_ASSERT(registry == r->registry); r->handleRemove(name); } void Registry::Private::globalSync(void* data, wl_callback* callback, uint32_t serial) { Q_UNUSED(serial) auto r = reinterpret_cast(data); Q_ASSERT(r->callback == callback); r->handleGlobalSync(); r->callback.destroy(); } void Registry::Private::handleGlobalSync() { emit q->interfacesAnnounced(); } namespace { static Registry::Interface nameToInterface(const char *interface) { for (auto it = s_interfaces.begin(); it != s_interfaces.end(); ++it) { if (qstrcmp(interface, it.value().name) == 0) { return it.key(); } } return Registry::Interface::Unknown; } } void Registry::Private::handleAnnounce(uint32_t name, const char *interface, uint32_t version) { Interface i = nameToInterface(interface); emit q->interfaceAnnounced(QByteArray(interface), name, version); if (i == Interface::Unknown) { qCDebug(KWAYLAND_CLIENT) << "Unknown interface announced: " << interface << "/" << name << "/" << version; return; } qCDebug(KWAYLAND_CLIENT) << "Wayland Interface: " << interface << "/" << name << "/" << version; m_interfaces.append({i, name, version}); auto it = s_interfaces.constFind(i); if (it != s_interfaces.end()) { emit (q->*it.value().announcedSignal)(name, version); } } void Registry::Private::handleRemove(uint32_t name) { auto it = std::find_if(m_interfaces.begin(), m_interfaces.end(), [name](const InterfaceData &data) { return data.name == name; } ); if (it != m_interfaces.end()) { InterfaceData data = *(it); m_interfaces.erase(it); auto sit = s_interfaces.find(data.interface); if (sit != s_interfaces.end()) { emit (q->*sit.value().removedSignal)(data.name); } } emit q->interfaceRemoved(name); } bool Registry::Private::hasInterface(Registry::Interface interface) const { auto it = std::find_if(m_interfaces.begin(), m_interfaces.end(), [interface](const InterfaceData &data) { return data.interface == interface; } ); return it != m_interfaces.end(); } QVector Registry::Private::interfaces(Interface interface) const { QVector retVal; for (auto it = m_interfaces.constBegin(); it != m_interfaces.constEnd(); ++it) { const auto &data = *it; if (data.interface == interface) { retVal << AnnouncedInterface{data.name, data.version}; } } return retVal; } Registry::AnnouncedInterface Registry::Private::interface(Interface interface) const { const auto all = interfaces(interface); if (!all.isEmpty()) { return all.last(); } return AnnouncedInterface{0, 0}; } Registry::Interface Registry::Private::interfaceForName(quint32 name) const { auto it = std::find_if(m_interfaces.constBegin(), m_interfaces.constEnd(), [name] (const InterfaceData &data) { return data.name == name; }); if (it == m_interfaces.constEnd()) { return Interface::Unknown; } return (*it).interface; } bool Registry::hasInterface(Registry::Interface interface) const { return d->hasInterface(interface); } QVector Registry::interfaces(Interface interface) const { return d->interfaces(interface); } Registry::AnnouncedInterface Registry::interface(Interface interface) const { return d->interface(interface); } #define BIND2(__NAME__, __INAME__, __WL__) \ __WL__ *Registry::bind##__NAME__(uint32_t name, uint32_t version) const \ { \ return d->bind<__WL__>(Interface::__INAME__, name, qMin(maxVersion(Interface::__INAME__), version)); \ } #define BIND(__NAME__, __WL__) BIND2(__NAME__, __NAME__, __WL__) BIND(Compositor, wl_compositor) BIND(Output, wl_output) BIND(Seat, wl_seat) BIND(Shell, wl_shell) BIND(Shm, wl_shm) BIND(SubCompositor, wl_subcompositor) BIND(FullscreenShell, _wl_fullscreen_shell) BIND(DataDeviceManager, wl_data_device_manager) BIND(PlasmaShell, org_kde_plasma_shell) BIND(PlasmaWindowManagement, org_kde_plasma_window_management) BIND(Idle, org_kde_kwin_idle) BIND(FakeInput, org_kde_kwin_fake_input) BIND(OutputManagement, org_kde_kwin_outputmanagement) BIND(OutputDevice, org_kde_kwin_outputdevice) BIND(ServerSideDecorationManager, org_kde_kwin_server_decoration_manager) BIND(TextInputManagerUnstableV0, wl_text_input_manager) BIND(TextInputManagerUnstableV2, zwp_text_input_manager_v2) BIND(XdgShellUnstableV5, xdg_shell) BIND(XdgShellUnstableV6, zxdg_shell_v6) BIND(RelativePointerManagerUnstableV1, zwp_relative_pointer_manager_v1) BIND(PointerGesturesUnstableV1, zwp_pointer_gestures_v1) BIND(PointerConstraintsUnstableV1, zwp_pointer_constraints_v1) BIND(XdgExporterUnstableV2, zxdg_exporter_v2) BIND(XdgImporterUnstableV2, zxdg_importer_v2) BIND(IdleInhibitManagerUnstableV1, zwp_idle_inhibit_manager_v1) BIND2(ShadowManager, Shadow, org_kde_kwin_shadow_manager) BIND2(BlurManager, Blur, org_kde_kwin_blur_manager) BIND2(ContrastManager, Contrast, org_kde_kwin_contrast_manager) BIND2(SlideManager, Slide, org_kde_kwin_slide_manager) BIND2(DpmsManager, Dpms, org_kde_kwin_dpms_manager) BIND2(AppMenuManager, AppMenu, org_kde_kwin_appmenu_manager) #undef BIND #undef BIND2 template T *Registry::Private::create(quint32 name, quint32 version, QObject *parent, WL *(Registry::*bindMethod)(uint32_t, uint32_t) const) { T *t = new T(parent); t->setEventQueue(queue); t->setup((q->*bindMethod)(name, version)); QObject::connect(q, &Registry::interfaceRemoved, t, [t, name] (quint32 removed) { if (name == removed) { emit t->removed(); } } ); QObject::connect(q, &Registry::registryDestroyed, t, &T::destroy); return t; } #define CREATE2(__NAME__, __BINDNAME__) \ __NAME__ *Registry::create##__NAME__(quint32 name, quint32 version, QObject *parent) \ { \ return d->create<__NAME__>(name, version, parent, &Registry::bind##__BINDNAME__); \ } #define CREATE(__NAME__) CREATE2(__NAME__, __NAME__) CREATE(Compositor) CREATE(Seat) CREATE(Shell) CREATE(SubCompositor) CREATE(FullscreenShell) CREATE(Output) CREATE(DataDeviceManager) CREATE(PlasmaShell) CREATE(PlasmaWindowManagement) CREATE(Idle) CREATE(FakeInput) CREATE(OutputManagement) CREATE(OutputDevice) CREATE(ShadowManager) CREATE(BlurManager) CREATE(ContrastManager) CREATE(SlideManager) CREATE(DpmsManager) CREATE(ServerSideDecorationManager) CREATE2(ShmPool, Shm) CREATE(AppMenuManager) #undef CREATE #undef CREATE2 XdgExporter *Registry::createXdgExporter(quint32 name, quint32 version, QObject *parent) { //only V1 supported for now return d->create(name, version, parent, &Registry::bindXdgExporterUnstableV2); } XdgImporter *Registry::createXdgImporter(quint32 name, quint32 version, QObject *parent) { //only V1 supported for now return d->create(name, version, parent, &Registry::bindXdgImporterUnstableV2); } TextInputManager *Registry::createTextInputManager(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::TextInputManagerUnstableV0: return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV0); case Interface::TextInputManagerUnstableV2: return d->create(name, version, parent, &Registry::bindTextInputManagerUnstableV2); default: return nullptr; } } XdgShell *Registry::createXdgShell(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::XdgShellUnstableV5: return d->create(name, version, parent, &Registry::bindXdgShellUnstableV5); case Interface::XdgShellUnstableV6: return d->create(name, version, parent, &Registry::bindXdgShellUnstableV6); default: return nullptr; } } RelativePointerManager *Registry::createRelativePointerManager(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::RelativePointerManagerUnstableV1: return d->create(name, version, parent, &Registry::bindRelativePointerManagerUnstableV1); default: return nullptr; } } PointerGestures *Registry::createPointerGestures(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::PointerGesturesUnstableV1: return d->create(name, version, parent, &Registry::bindPointerGesturesUnstableV1); default: return nullptr; } } PointerConstraints *Registry::createPointerConstraints(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::PointerConstraintsUnstableV1: return d->create(name, version, parent, &Registry::bindPointerConstraintsUnstableV1); default: return nullptr; } } IdleInhibitManager *Registry::createIdleInhibitManager(quint32 name, quint32 version, QObject *parent) { switch (d->interfaceForName(name)) { case Interface::IdleInhibitManagerUnstableV1: return d->create(name, version, parent, &Registry::bindIdleInhibitManagerUnstableV1); default: return nullptr; } } namespace { static const wl_interface *wlInterface(Registry::Interface interface) { auto it = s_interfaces.find(interface); if (it != s_interfaces.end()) { return it.value().interface; } return nullptr; } } template T *Registry::Private::bind(Registry::Interface interface, uint32_t name, uint32_t version) const { auto it = std::find_if(m_interfaces.begin(), m_interfaces.end(), [=](const InterfaceData &data) { return data.interface == interface && data.name == name && data.version >= version; }); if (it == m_interfaces.end()) { qCDebug(KWAYLAND_CLIENT) << "Don't have interface " << int(interface) << "with name " << name << "and minimum version" << version; return nullptr; } auto t = reinterpret_cast(wl_registry_bind(registry, name, wlInterface(interface), version)); if (queue) { queue->addProxy(t); } return t; } bool Registry::isValid() const { return d->registry.isValid(); } wl_registry *Registry::registry() { return d->registry; } Registry::operator wl_registry*() const { return d->registry; } Registry::operator wl_registry*() { return d->registry; } } } diff --git a/src/server/datadevice_interface.cpp b/src/server/datadevice_interface.cpp index efcbdff..e7af140 100644 --- a/src/server/datadevice_interface.cpp +++ b/src/server/datadevice_interface.cpp @@ -1,303 +1,350 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "datadevice_interface.h" #include "datadevicemanager_interface.h" -#include "dataoffer_interface.h" +#include "dataoffer_interface_p.h" #include "datasource_interface.h" #include "display.h" #include "resource_p.h" #include "pointer_interface.h" #include "seat_interface.h" #include "surface_interface.h" // Wayland #include namespace KWayland { namespace Server { class DataDeviceInterface::Private : public Resource::Private { public: Private(SeatInterface *seat, DataDeviceInterface *q, DataDeviceManagerInterface *manager, wl_resource *parentResource); ~Private(); DataOfferInterface *createDataOffer(DataSourceInterface *source); SeatInterface *seat; DataSourceInterface *source = nullptr; SurfaceInterface *surface = nullptr; SurfaceInterface *icon = nullptr; DataSourceInterface *selection = nullptr; QMetaObject::Connection selectionUnboundConnection; QMetaObject::Connection selectionDestroyedConnection; struct Drag { SurfaceInterface *surface = nullptr; QMetaObject::Connection destroyConnection; QMetaObject::Connection pointerPosConnection; + QMetaObject::Connection sourceActionConnection; + QMetaObject::Connection targetActionConnection; quint32 serial = 0; }; Drag drag; private: DataDeviceInterface *q_func() { return reinterpret_cast(q); } void startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *icon, quint32 serial); void setSelection(DataSourceInterface *dataSource); static void startDragCallback(wl_client *client, wl_resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial); static void setSelectionCallback(wl_client *client, wl_resource *resource, wl_resource *source, uint32_t serial); static const struct wl_data_device_interface s_interface; }; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_device_interface DataDeviceInterface::Private::s_interface = { startDragCallback, setSelectionCallback, resourceDestroyedCallback }; #endif DataDeviceInterface::Private::Private(SeatInterface *seat, DataDeviceInterface *q, DataDeviceManagerInterface *manager, wl_resource *parentResource) : Resource::Private(q, manager, parentResource, &wl_data_device_interface, &s_interface) , seat(seat) { } DataDeviceInterface::Private::~Private() = default; void DataDeviceInterface::Private::startDragCallback(wl_client *client, wl_resource *resource, wl_resource *source, wl_resource *origin, wl_resource *icon, uint32_t serial) { Q_UNUSED(client) Q_UNUSED(serial) // TODO: verify serial cast(resource)->startDrag(DataSourceInterface::get(source), SurfaceInterface::get(origin), SurfaceInterface::get(icon), serial); } void DataDeviceInterface::Private::startDrag(DataSourceInterface *dataSource, SurfaceInterface *origin, SurfaceInterface *i, quint32 serial) { // TODO: allow touch if (seat->hasImplicitPointerGrab(serial) && seat->focusedPointerSurface() != origin) { wl_resource_post_error(resource, 0, "Surface doesn't have pointer grab"); return; } // TODO: source is allowed to be null, handled client internally! source = dataSource; surface = origin; icon = i; drag.serial = serial; Q_Q(DataDeviceInterface); emit q->dragStarted(); } void DataDeviceInterface::Private::setSelectionCallback(wl_client *client, wl_resource *resource, wl_resource *source, uint32_t serial) { Q_UNUSED(client) Q_UNUSED(serial) // TODO: verify serial cast(resource)->setSelection(DataSourceInterface::get(source)); } void DataDeviceInterface::Private::setSelection(DataSourceInterface *dataSource) { + if (dataSource->supportedDragAndDropActions()) { + wl_resource_post_error(dataSource->resource(), WL_DATA_SOURCE_ERROR_INVALID_SOURCE, "Data source is for drag and drop"); + return; + } Q_Q(DataDeviceInterface); QObject::disconnect(selectionUnboundConnection); QObject::disconnect(selectionDestroyedConnection); if (selection) { selection->cancel(); } selection = dataSource; if (selection) { auto clearSelection = [this] { setSelection(nullptr); }; selectionUnboundConnection = QObject::connect(selection, &Resource::unbound, q, clearSelection); selectionDestroyedConnection = QObject::connect(selection, &QObject::destroyed, q, clearSelection); emit q->selectionChanged(selection); } else { selectionUnboundConnection = QMetaObject::Connection(); selectionDestroyedConnection = QMetaObject::Connection(); emit q->selectionCleared(); } } DataOfferInterface *DataDeviceInterface::Private::createDataOffer(DataSourceInterface *source) { if (!resource) { return nullptr; } Q_Q(DataDeviceInterface); DataOfferInterface *offer = new DataOfferInterface(source, q, resource); auto c = q->global()->display()->getConnection(wl_resource_get_client(resource)); offer->create(c, wl_resource_get_version(resource), 0); if (!offer->resource()) { // TODO: send error? delete offer; return nullptr; } wl_data_device_send_data_offer(resource, offer->resource()); offer->sendAllOffers(); return offer; } DataDeviceInterface::DataDeviceInterface(SeatInterface *seat, DataDeviceManagerInterface *parent, wl_resource *parentResource) : Resource(new Private(seat, this, parent, parentResource)) { } DataDeviceInterface::~DataDeviceInterface() = default; SeatInterface *DataDeviceInterface::seat() const { Q_D(); return d->seat; } DataSourceInterface *DataDeviceInterface::dragSource() const { Q_D(); return d->source; } SurfaceInterface *DataDeviceInterface::icon() const { Q_D(); return d->icon; } SurfaceInterface *DataDeviceInterface::origin() const { Q_D(); return d->surface; } DataSourceInterface *DataDeviceInterface::selection() const { Q_D(); return d->selection; } void DataDeviceInterface::sendSelection(DataDeviceInterface *other) { Q_D(); auto otherSelection = other->selection(); if (!otherSelection) { sendClearSelection(); return; } auto r = d->createDataOffer(otherSelection); if (!r) { return; } if (!d->resource) { return; } wl_data_device_send_selection(d->resource, r->resource()); } void DataDeviceInterface::sendClearSelection() { Q_D(); if (!d->resource) { return; } wl_data_device_send_selection(d->resource, nullptr); } void DataDeviceInterface::drop() { Q_D(); if (!d->resource) { return; } wl_data_device_send_drop(d->resource); + if (d->drag.pointerPosConnection) { + disconnect(d->drag.pointerPosConnection); + d->drag.pointerPosConnection = QMetaObject::Connection(); + } + disconnect(d->drag.destroyConnection); + d->drag.destroyConnection = QMetaObject::Connection(); + d->drag.surface = nullptr; client()->flush(); } void DataDeviceInterface::updateDragTarget(SurfaceInterface *surface, quint32 serial) { Q_D(); if (d->drag.surface) { if (d->resource && d->drag.surface->resource()) { wl_data_device_send_leave(d->resource); } if (d->drag.pointerPosConnection) { disconnect(d->drag.pointerPosConnection); d->drag.pointerPosConnection = QMetaObject::Connection(); } disconnect(d->drag.destroyConnection); d->drag.destroyConnection = QMetaObject::Connection(); d->drag.surface = nullptr; + if (d->drag.sourceActionConnection) { + disconnect(d->drag.sourceActionConnection); + d->drag.sourceActionConnection = QMetaObject::Connection(); + } + if (d->drag.targetActionConnection) { + disconnect(d->drag.targetActionConnection); + d->drag.targetActionConnection = QMetaObject::Connection(); + } // don't update serial, we need it } if (!surface) { + d->seat->dragSource()->dragSource()->dndAction(DataDeviceManagerInterface::DnDAction::None); return; } - DataOfferInterface *offer = d->createDataOffer(d->seat->dragSource()->dragSource()); + auto *source = d->seat->dragSource()->dragSource(); + DataOfferInterface *offer = d->createDataOffer(source); d->drag.surface = surface; if (d->seat->isDragPointer()) { d->drag.pointerPosConnection = connect(d->seat, &SeatInterface::pointerPosChanged, this, [this] { Q_D(); const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); wl_data_device_send_motion(d->resource, d->seat->timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); client()->flush(); } ); } // TODO: same for touch d->drag.destroyConnection = connect(d->drag.surface, &QObject::destroyed, this, [this] { Q_D(); if (d->resource) { wl_data_device_send_leave(d->resource); } if (d->drag.pointerPosConnection) { disconnect(d->drag.pointerPosConnection); } d->drag = Private::Drag(); } ); // TODO: handle touch position const QPointF pos = d->seat->dragSurfaceTransformation().map(d->seat->pointerPos()); wl_data_device_send_enter(d->resource, serial, surface->resource(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y()), offer ? offer->resource() : nullptr); + if (offer) { + offer->d_func()->sendSourceActions(); + auto matchOffers = [source, offer] { + DataDeviceManagerInterface::DnDAction action{DataDeviceManagerInterface::DnDAction::None}; + if (source->supportedDragAndDropActions().testFlag(offer->preferredDragAndDropAction())) { + action = offer->preferredDragAndDropAction(); + } else { + if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Copy)) { + action = DataDeviceManagerInterface::DnDAction::Copy; + } else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Move)) { + action = DataDeviceManagerInterface::DnDAction::Move; + } else if (source->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask) && + offer->supportedDragAndDropActions().testFlag(DataDeviceManagerInterface::DnDAction::Ask)) { + action = DataDeviceManagerInterface::DnDAction::Ask; + } + } + offer->dndAction(action); + source->dndAction(action); + }; + d->drag.targetActionConnection = connect(offer, &DataOfferInterface::dragAndDropActionsChanged, offer, matchOffers); + d->drag.sourceActionConnection = connect(source, &DataSourceInterface::supportedDragAndDropActionsChanged, source, matchOffers); + } d->client->flush(); } quint32 DataDeviceInterface::dragImplicitGrabSerial() const { Q_D(); return d->drag.serial; } DataDeviceInterface::Private *DataDeviceInterface::d_func() const { return reinterpret_cast(d.data()); } } } diff --git a/src/server/datadevicemanager_interface.cpp b/src/server/datadevicemanager_interface.cpp index 168f6af..952d215 100644 --- a/src/server/datadevicemanager_interface.cpp +++ b/src/server/datadevicemanager_interface.cpp @@ -1,134 +1,135 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "datadevicemanager_interface.h" +#include "datasource_interface.h" #include "global_p.h" #include "display.h" #include "seat_interface_p.h" // Wayland #include namespace KWayland { namespace Server { class DataDeviceManagerInterface::Private : public Global::Private { public: Private(DataDeviceManagerInterface *q, Display *d); private: void bind(wl_client *client, uint32_t version, uint32_t id) override; void createDataSource(wl_client *client, wl_resource *resource, uint32_t id); void getDataDevice(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat); static void unbind(wl_resource *resource); static void createDataSourceCallback(wl_client *client, wl_resource *resource, uint32_t id); static void getDataDeviceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat); static Private *cast(wl_resource *r) { return reinterpret_cast(wl_resource_get_user_data(r)); } DataDeviceManagerInterface *q; static const struct wl_data_device_manager_interface s_interface; static const quint32 s_version; static const qint32 s_dataDeviceVersion; static const qint32 s_dataSourceVersion; }; -const quint32 DataDeviceManagerInterface::Private::s_version = 2; -const qint32 DataDeviceManagerInterface::Private::s_dataDeviceVersion = 2; -const qint32 DataDeviceManagerInterface::Private::s_dataSourceVersion = 1; +const quint32 DataDeviceManagerInterface::Private::s_version = 3; +const qint32 DataDeviceManagerInterface::Private::s_dataDeviceVersion = 3; +const qint32 DataDeviceManagerInterface::Private::s_dataSourceVersion = 3; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_device_manager_interface DataDeviceManagerInterface::Private::s_interface = { createDataSourceCallback, getDataDeviceCallback }; #endif DataDeviceManagerInterface::Private::Private(DataDeviceManagerInterface *q, Display *d) : Global::Private(d, &wl_data_device_manager_interface, s_version) , q(q) { } void DataDeviceManagerInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { auto c = display->getConnection(client); wl_resource *resource = c->createResource(&wl_data_device_manager_interface, qMin(version, s_version), id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &s_interface, this, unbind); // TODO: should we track? } void DataDeviceManagerInterface::Private::unbind(wl_resource *resource) { Q_UNUSED(resource) } void DataDeviceManagerInterface::Private::createDataSourceCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->createDataSource(client, resource, id); } void DataDeviceManagerInterface::Private::createDataSource(wl_client *client, wl_resource *resource, uint32_t id) { DataSourceInterface *dataSource = new DataSourceInterface(q, resource); dataSource->create(display->getConnection(client), qMin(wl_resource_get_version(resource), s_dataSourceVersion) , id); if (!dataSource->resource()) { wl_resource_post_no_memory(resource); delete dataSource; return; } emit q->dataSourceCreated(dataSource); } void DataDeviceManagerInterface::Private::getDataDeviceCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat) { cast(resource)->getDataDevice(client, resource, id, seat); } void DataDeviceManagerInterface::Private::getDataDevice(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat) { SeatInterface *s = SeatInterface::get(seat); Q_ASSERT(s); DataDeviceInterface *dataDevice = new DataDeviceInterface(s, q, resource); dataDevice->create(display->getConnection(client), qMin(wl_resource_get_version(resource), s_dataDeviceVersion), id); if (!dataDevice->resource()) { wl_resource_post_no_memory(resource); return; } s->d_func()->registerDataDevice(dataDevice); emit q->dataDeviceCreated(dataDevice); } DataDeviceManagerInterface::DataDeviceManagerInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { } DataDeviceManagerInterface::~DataDeviceManagerInterface() = default; } } diff --git a/src/server/datadevicemanager_interface.h b/src/server/datadevicemanager_interface.h index 25e07d0..bc8d3c5 100644 --- a/src/server/datadevicemanager_interface.h +++ b/src/server/datadevicemanager_interface.h @@ -1,60 +1,74 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_SERVER_DATA_DEVICE_MANAGER_INTERFACE_H #define WAYLAND_SERVER_DATA_DEVICE_MANAGER_INTERFACE_H #include #include #include "global.h" #include "datadevice_interface.h" -#include "datasource_interface.h" namespace KWayland { namespace Server { class Display; +class DataSourceInterface; /** * @brief Represents the Global for wl_data_device_manager interface. * **/ class KWAYLANDSERVER_EXPORT DataDeviceManagerInterface : public Global { Q_OBJECT public: virtual ~DataDeviceManagerInterface(); + /** + * Drag and Drop actions supported by the DataSourceInterface. + * @since 5.XX + **/ + enum class DnDAction { + None = 0, + Copy = 1 << 0, + Move = 1 << 1, + Ask = 1 << 2 + }; + Q_DECLARE_FLAGS(DnDActions, DnDAction) + Q_SIGNALS: void dataSourceCreated(KWayland::Server::DataSourceInterface*); void dataDeviceCreated(KWayland::Server::DataDeviceInterface*); private: explicit DataDeviceManagerInterface(Display *display, QObject *parent = nullptr); friend class Display; class Private; }; } } +Q_DECLARE_OPERATORS_FOR_FLAGS(KWayland::Server::DataDeviceManagerInterface::DnDActions) + #endif diff --git a/src/server/dataoffer_interface.cpp b/src/server/dataoffer_interface.cpp index 0b04653..b15ede8 100644 --- a/src/server/dataoffer_interface.cpp +++ b/src/server/dataoffer_interface.cpp @@ -1,130 +1,216 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ -#include "dataoffer_interface.h" +#include "dataoffer_interface_p.h" #include "datadevice_interface.h" #include "datasource_interface.h" -#include "resource_p.h" // Qt #include // Wayland #include namespace KWayland { namespace Server { -class DataOfferInterface::Private : public Resource::Private -{ -public: - Private(DataSourceInterface *source, DataDeviceInterface *parentInterface, DataOfferInterface *q, wl_resource *parentResource); - ~Private(); - DataSourceInterface *source; - DataDeviceInterface *dataDevice; - -private: - DataOfferInterface *q_func() { - return reinterpret_cast(q); - } - void receive(const QString &mimeType, qint32 fd); - static void acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType); - static void receiveCallback(wl_client *client, wl_resource *resource, const char *mimeType, int32_t fd); - - static const struct wl_data_offer_interface s_interface; -}; - #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_offer_interface DataOfferInterface::Private::s_interface = { acceptCallback, receiveCallback, - resourceDestroyedCallback + resourceDestroyedCallback, + finishCallback, + setActionsCallback }; #endif DataOfferInterface::Private::Private(DataSourceInterface *source, DataDeviceInterface *parentInterface, DataOfferInterface *q, wl_resource *parentResource) : Resource::Private(q, nullptr, parentResource, &wl_data_offer_interface, &s_interface) , source(source) , dataDevice(parentInterface) { // TODO: connect to new selections } DataOfferInterface::Private::~Private() = default; void DataOfferInterface::Private::acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType) { Q_UNUSED(client) Q_UNUSED(serial) auto p = cast(resource); if (!p->source) { return; } p->source->accept(mimeType ? QString::fromUtf8(mimeType) : QString()); } void DataOfferInterface::Private::receiveCallback(wl_client *client, wl_resource *resource, const char *mimeType, int32_t fd) { Q_UNUSED(client) cast(resource)->receive(QString::fromUtf8(mimeType), fd); } void DataOfferInterface::Private::receive(const QString &mimeType, qint32 fd) { source->requestData(mimeType, fd); } +void DataOfferInterface::Private::finishCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + auto p = cast(resource); + if (!p->source) { + return; + } + p->source->dndFinished(); + // TODO: It is a client error to perform other requests than wl_data_offer.destroy after this one +} + +void DataOfferInterface::Private::setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions, uint32_t preferred_action) +{ + // TODO: check it's drag and drop, otherwise send error + Q_UNUSED(client) + DataDeviceManagerInterface::DnDActions supportedActions; + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Copy; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Move; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Ask; + } + // verify that the no other actions are sent + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) { + wl_resource_post_error(resource, WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK, "Invalid action mask"); + return; + } + if (preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK && + preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) { + wl_resource_post_error(resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, "Invalid preferred action"); + return; + } + + DataDeviceManagerInterface::DnDAction preferredAction = DataDeviceManagerInterface::DnDAction::None; + if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + preferredAction = DataDeviceManagerInterface::DnDAction::Copy; + } else if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + preferredAction = DataDeviceManagerInterface::DnDAction::Move; + } else if (preferred_action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + preferredAction = DataDeviceManagerInterface::DnDAction::Ask; + } + + auto p = cast(resource); + p->supportedDnDActions = supportedActions; + p->preferredDnDAction = preferredAction; + emit p->q_func()->dragAndDropActionsChanged(); +} + +void DataOfferInterface::Private::sendSourceActions() +{ + if (!source) { + return; + } + if (wl_resource_get_version(resource) < WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION) { + return; + } + uint32_t wlActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + const auto actions = source->supportedDragAndDropActions(); + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Copy)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Move)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } + if (actions.testFlag(DataDeviceManagerInterface::DnDAction::Ask)) { + wlActions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_offer_send_source_actions(resource, wlActions); +} + DataOfferInterface::DataOfferInterface(DataSourceInterface *source, DataDeviceInterface *parentInterface, wl_resource *parentResource) : Resource(new Private(source, parentInterface, this, parentResource)) { Q_ASSERT(source); connect(source, &DataSourceInterface::mimeTypeOffered, this, [this](const QString &mimeType) { Q_D(); if (!d->resource) { return; } wl_data_offer_send_offer(d->resource, mimeType.toUtf8().constData()); } ); QObject::connect(source, &QObject::destroyed, this, [this] { Q_D(); d->source = nullptr; } ); } DataOfferInterface::~DataOfferInterface() = default; void DataOfferInterface::sendAllOffers() { Q_D(); for (const QString &mimeType : d->source->mimeTypes()) { wl_data_offer_send_offer(d->resource, mimeType.toUtf8().constData()); } } DataOfferInterface::Private *DataOfferInterface::d_func() const { return reinterpret_cast(d.data()); } +DataDeviceManagerInterface::DnDActions DataOfferInterface::supportedDragAndDropActions() const +{ + Q_D(); + return d->supportedDnDActions; +} + +DataDeviceManagerInterface::DnDAction DataOfferInterface::preferredDragAndDropAction() const +{ + Q_D(); + return d->preferredDnDAction; +} + +void DataOfferInterface::dndAction(DataDeviceManagerInterface::DnDAction action) +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_OFFER_ACTION_SINCE_VERSION) { + return; + } + uint32_t wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (action == DataDeviceManagerInterface::DnDAction::Copy) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (action == DataDeviceManagerInterface::DnDAction::Move ) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (action == DataDeviceManagerInterface::DnDAction::Ask) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_offer_send_action(d->resource, wlAction); +} + } } diff --git a/src/server/dataoffer_interface.h b/src/server/dataoffer_interface.h index 2412fbd..8e415d9 100644 --- a/src/server/dataoffer_interface.h +++ b/src/server/dataoffer_interface.h @@ -1,62 +1,89 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_SERVER_DATA_OFFER_INTERFACE_H #define WAYLAND_SERVER_DATA_OFFER_INTERFACE_H #include #include #include "resource.h" +#include "datadevicemanager_interface.h" namespace KWayland { namespace Server { class DataDeviceInterface; class DataSourceInterface; /** * @brief Represents the Resource for the wl_data_offer interface. * **/ class KWAYLANDSERVER_EXPORT DataOfferInterface : public Resource { Q_OBJECT public: virtual ~DataOfferInterface(); void sendAllOffers(); + /** + * @returns The Drag and Drop actions supported by this DataOfferInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDActions supportedDragAndDropActions() const; + + /** + * @returns The preferred Drag and Drop action of this DataOfferInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDAction preferredDragAndDropAction() const; + + /** + * This event indicates the @p action selected by the compositor after matching the + * source/destination side actions. Only one action (or none) will be offered here. + * @since 5.42 + **/ + void dndAction(DataDeviceManagerInterface::DnDAction action); + +Q_SIGNALS: + /** + * Emitted whenever the supported or preferred Drag and Drop actions changed. + * @since 5.42 + **/ + void dragAndDropActionsChanged(); + private: friend class DataDeviceInterface; explicit DataOfferInterface(DataSourceInterface *source, DataDeviceInterface *parentInterface, wl_resource *parentResource); class Private; Private *d_func() const; }; } } Q_DECLARE_METATYPE(KWayland::Server::DataOfferInterface*) #endif diff --git a/src/server/dataoffer_interface_p.h b/src/server/dataoffer_interface_p.h new file mode 100644 index 0000000..e66c315 --- /dev/null +++ b/src/server/dataoffer_interface_p.h @@ -0,0 +1,61 @@ +/******************************************************************** +Copyright 2017 Martin Flöser + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) version 3, or any +later version accepted by the membership of KDE e.V. (or its +successor approved by the membership of KDE e.V.), which shall +act as a proxy defined in Section 6 of version 3 of the license. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library. If not, see . +*********************************************************************/ +#ifndef KWAYLAND_SERVER_DATAOFFERINTERFACE_P_H +#define KWAYLAND_SERVER_DATAOFFERINTERFACE_P_H +#include "dataoffer_interface.h" +#include "datasource_interface.h" +#include "resource_p.h" +#include + +namespace KWayland +{ +namespace Server +{ + +class Q_DECL_HIDDEN DataOfferInterface::Private : public Resource::Private +{ +public: + Private(DataSourceInterface *source, DataDeviceInterface *parentInterface, DataOfferInterface *q, wl_resource *parentResource); + ~Private(); + DataSourceInterface *source; + DataDeviceInterface *dataDevice; + // defaults are set to sensible values for < version 3 interfaces + DataDeviceManagerInterface::DnDActions supportedDnDActions = DataDeviceManagerInterface::DnDAction::Copy | DataDeviceManagerInterface::DnDAction::Move; + DataDeviceManagerInterface::DnDAction preferredDnDAction = DataDeviceManagerInterface::DnDAction::Copy; + + void sendSourceActions(); + +private: + DataOfferInterface *q_func() { + return reinterpret_cast(q); + } + void receive(const QString &mimeType, qint32 fd); + static void acceptCallback(wl_client *client, wl_resource *resource, uint32_t serial, const char *mimeType); + static void receiveCallback(wl_client *client, wl_resource *resource, const char *mimeType, int32_t fd); + static void finishCallback(wl_client *client, wl_resource *resource); + static void setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions, uint32_t preferred_action); + + static const struct wl_data_offer_interface s_interface; +}; + +} +} + +#endif diff --git a/src/server/datasource_interface.cpp b/src/server/datasource_interface.cpp index ee2db95..96f277f 100644 --- a/src/server/datasource_interface.cpp +++ b/src/server/datasource_interface.cpp @@ -1,133 +1,203 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "datasource_interface.h" #include "datadevicemanager_interface.h" #include "clientconnection.h" #include "resource_p.h" // Qt #include // Wayland #include // system #include namespace KWayland { namespace Server { class DataSourceInterface::Private : public Resource::Private { public: Private(DataSourceInterface *q, DataDeviceManagerInterface *parent, wl_resource *parentResource); ~Private(); QStringList mimeTypes; + // sensible default for < version 3 + DataDeviceManagerInterface::DnDActions supportedDnDActions = DataDeviceManagerInterface::DnDAction::Copy; private: DataSourceInterface *q_func() { return reinterpret_cast(q); } void offer(const QString &mimeType); static void offerCallback(wl_client *client, wl_resource *resource, const char *mimeType); + static void setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions); const static struct wl_data_source_interface s_interface; }; #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_data_source_interface DataSourceInterface::Private::s_interface = { offerCallback, - resourceDestroyedCallback + resourceDestroyedCallback, + setActionsCallback }; #endif DataSourceInterface::Private::Private(DataSourceInterface *q, DataDeviceManagerInterface *parent, wl_resource *parentResource) : Resource::Private(q, parent, parentResource, &wl_data_source_interface, &s_interface) { } DataSourceInterface::Private::~Private() = default; void DataSourceInterface::Private::offerCallback(wl_client *client, wl_resource *resource, const char *mimeType) { Q_UNUSED(client) cast(resource)->offer(QString::fromUtf8(mimeType)); } void DataSourceInterface::Private::offer(const QString &mimeType) { mimeTypes << mimeType; Q_Q(DataSourceInterface); emit q->mimeTypeOffered(mimeType); } +void DataSourceInterface::Private::setActionsCallback(wl_client *client, wl_resource *resource, uint32_t dnd_actions) +{ + Q_UNUSED(client) + DataDeviceManagerInterface::DnDActions supportedActions; + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Copy; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Move; + } + if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) { + supportedActions |= DataDeviceManagerInterface::DnDAction::Ask; + } + // verify that the no other actions are sent + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) { + wl_resource_post_error(resource, WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK, "Invalid action mask"); + return; + } + auto p = cast(resource); + if (p->supportedDnDActions!= supportedActions) { + p->supportedDnDActions = supportedActions; + emit p->q_func()->supportedDragAndDropActionsChanged(); + } +} + DataSourceInterface::DataSourceInterface(DataDeviceManagerInterface *parent, wl_resource *parentResource) : Resource(new Private(this, parent, parentResource)) { } DataSourceInterface::~DataSourceInterface() = default; void DataSourceInterface::accept(const QString &mimeType) { Q_D(); // TODO: does this require a sanity check on the possible mimeType? wl_data_source_send_target(d->resource, mimeType.isEmpty() ? nullptr : mimeType.toUtf8().constData()); } void DataSourceInterface::requestData(const QString &mimeType, qint32 fd) { Q_D(); // TODO: does this require a sanity check on the possible mimeType? if (d->resource) { wl_data_source_send_send(d->resource, mimeType.toUtf8().constData(), int32_t(fd)); } close(fd); } void DataSourceInterface::cancel() { Q_D(); if (!d->resource) { return; } wl_data_source_send_cancelled(d->resource); client()->flush(); } QStringList DataSourceInterface::mimeTypes() const { Q_D(); return d->mimeTypes; } DataSourceInterface *DataSourceInterface::get(wl_resource *native) { return Private::get(native); } DataSourceInterface::Private *DataSourceInterface::d_func() const { return reinterpret_cast(d.data()); } +DataDeviceManagerInterface::DnDActions DataSourceInterface::supportedDragAndDropActions() const +{ + Q_D(); + return d->supportedDnDActions; +} + +void DataSourceInterface::dropPerformed() +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION) { + return; + } + wl_data_source_send_dnd_drop_performed(d->resource); +} + +void DataSourceInterface::dndFinished() +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION) { + return; + } + wl_data_source_send_dnd_finished(d->resource); +} + +void DataSourceInterface::dndAction(DataDeviceManagerInterface::DnDAction action) +{ + Q_D(); + if (wl_resource_get_version(d->resource) < WL_DATA_SOURCE_ACTION_SINCE_VERSION) { + return; + } + uint32_t wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + if (action == DataDeviceManagerInterface::DnDAction::Copy) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + } else if (action == DataDeviceManagerInterface::DnDAction::Move ) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + } else if (action == DataDeviceManagerInterface::DnDAction::Ask) { + wlAction = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + } + wl_data_source_send_action(d->resource, wlAction); +} + } } diff --git a/src/server/datasource_interface.h b/src/server/datasource_interface.h index 4ed5a04..48c1340 100644 --- a/src/server/datasource_interface.h +++ b/src/server/datasource_interface.h @@ -1,68 +1,96 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_SERVER_DATA_SOURCE_INTERFACE_H #define WAYLAND_SERVER_DATA_SOURCE_INTERFACE_H #include #include #include "resource.h" +#include "datadevicemanager_interface.h" namespace KWayland { namespace Server { -class DataDeviceManagerInterface; /** * @brief Represents the Resource for the wl_data_source interface. **/ class KWAYLANDSERVER_EXPORT DataSourceInterface : public Resource { Q_OBJECT public: virtual ~DataSourceInterface(); void accept(const QString &mimeType); void requestData(const QString &mimeType, qint32 fd); void cancel(); QStringList mimeTypes() const; static DataSourceInterface *get(wl_resource *native); + /** + * @returns The Drag and Drop actions supported by this DataSourceInterface. + * @since 5.42 + **/ + DataDeviceManagerInterface::DnDActions supportedDragAndDropActions() const; + + /** + * The user performed the drop action during a drag and drop operation. + * @since 5.42 + **/ + void dropPerformed(); + /** + * The drop destination finished interoperating with this data source. + * @since 5.42 + **/ + void dndFinished(); + /** + * This event indicates the @p action selected by the compositor after matching the + * source/destination side actions. Only one action (or none) will be offered here. + * @since 5.42 + **/ + void dndAction(DataDeviceManagerInterface::DnDAction action); + Q_SIGNALS: void mimeTypeOffered(const QString&); + /** + * Emitted whenever this DataSourceInterface changes the supported drag and drop actions + * @since 5.42 + **/ + void supportedDragAndDropActionsChanged(); private: friend class DataDeviceManagerInterface; explicit DataSourceInterface(DataDeviceManagerInterface *parent, wl_resource *parentResource); class Private; Private *d_func() const; }; } } Q_DECLARE_METATYPE(KWayland::Server::DataSourceInterface*) #endif diff --git a/src/server/seat_interface.cpp b/src/server/seat_interface.cpp index 7a295f7..4029da1 100644 --- a/src/server/seat_interface.cpp +++ b/src/server/seat_interface.cpp @@ -1,1493 +1,1496 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "seat_interface.h" #include "seat_interface_p.h" #include "display.h" #include "datadevice_interface.h" #include "datasource_interface.h" #include "keyboard_interface.h" #include "keyboard_interface_p.h" #include "pointer_interface.h" #include "pointer_interface_p.h" #include "surface_interface.h" #include "textinput_interface_p.h" // Wayland #ifndef WL_SEAT_NAME_SINCE_VERSION #define WL_SEAT_NAME_SINCE_VERSION 2 #endif // linux #include #if HAVE_LINUX_INPUT_H #include #endif #include namespace KWayland { namespace Server { const quint32 SeatInterface::Private::s_version = 4; const qint32 SeatInterface::Private::s_pointerVersion = 3; const qint32 SeatInterface::Private::s_touchVersion = 3; const qint32 SeatInterface::Private::s_keyboardVersion = 4; SeatInterface::Private::Private(SeatInterface *q, Display *display) : Global::Private(display, &wl_seat_interface, s_version) , q(q) { } #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_seat_interface SeatInterface::Private::s_interface = { getPointerCallback, getKeyboardCallback, getTouchCallback }; #endif SeatInterface::SeatInterface(Display *display, QObject *parent) : Global(new Private(this, display), parent) { Q_D(); connect(this, &SeatInterface::nameChanged, this, [this, d] { for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { d->sendName(*it); } } ); auto sendCapabilitiesAll = [this, d] { for (auto it = d->resources.constBegin(); it != d->resources.constEnd(); ++it) { d->sendCapabilities(*it); } }; connect(this, &SeatInterface::hasPointerChanged, this, sendCapabilitiesAll); connect(this, &SeatInterface::hasKeyboardChanged, this, sendCapabilitiesAll); connect(this, &SeatInterface::hasTouchChanged, this, sendCapabilitiesAll); } SeatInterface::~SeatInterface() { Q_D(); while (!d->resources.isEmpty()) { wl_resource_destroy(d->resources.takeLast()); } } void SeatInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id) { wl_resource *r = wl_resource_create(client, &wl_seat_interface, qMin(s_version, version), id); if (!r) { wl_client_post_no_memory(client); return; } resources << r; wl_resource_set_implementation(r, &s_interface, this, unbind); sendCapabilities(r); sendName(r); } void SeatInterface::Private::unbind(wl_resource *r) { cast(r)->resources.removeAll(r); } void SeatInterface::Private::updatePointerButtonSerial(quint32 button, quint32 serial) { auto it = globalPointer.buttonSerials.find(button); if (it == globalPointer.buttonSerials.end()) { globalPointer.buttonSerials.insert(button, serial); return; } it.value() = serial; } void SeatInterface::Private::updatePointerButtonState(quint32 button, Pointer::State state) { auto it = globalPointer.buttonStates.find(button); if (it == globalPointer.buttonStates.end()) { globalPointer.buttonStates.insert(button, state); return; } it.value() = state; } bool SeatInterface::Private::updateKey(quint32 key, Keyboard::State state) { auto it = keys.states.find(key); if (it == keys.states.end()) { keys.states.insert(key, state); return true; } if (it.value() == state) { return false; } it.value() = state; return true; } void SeatInterface::Private::sendName(wl_resource *r) { if (wl_resource_get_version(r) < WL_SEAT_NAME_SINCE_VERSION) { return; } wl_seat_send_name(r, name.toUtf8().constData()); } void SeatInterface::Private::sendCapabilities(wl_resource *r) { uint32_t capabilities = 0; if (pointer) { capabilities |= WL_SEAT_CAPABILITY_POINTER; } if (keyboard) { capabilities |= WL_SEAT_CAPABILITY_KEYBOARD; } if (touch) { capabilities |= WL_SEAT_CAPABILITY_TOUCH; } wl_seat_send_capabilities(r, capabilities); } SeatInterface::Private *SeatInterface::Private::cast(wl_resource *r) { return r ? reinterpret_cast(wl_resource_get_user_data(r)) : nullptr; } namespace { template static T *interfaceForSurface(SurfaceInterface *surface, const QVector &interfaces) { if (!surface) { return nullptr; } for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { if ((*it)->client() == surface->client()) { return (*it); } } return nullptr; } template static QVector interfacesForSurface(SurfaceInterface *surface, const QVector &interfaces) { QVector ret; if (!surface) { return ret; } for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { if ((*it)->client() == surface->client() && (*it)->resource()) { ret << *it; } } return ret; } template static bool forEachInterface(SurfaceInterface *surface, const QVector &interfaces, std::function method) { if (!surface) { return false; } bool calledAtLeastOne = false; for (auto it = interfaces.begin(); it != interfaces.end(); ++it) { if ((*it)->client() == surface->client() && (*it)->resource()) { method(*it); calledAtLeastOne = true; } } return calledAtLeastOne; } } QVector SeatInterface::Private::pointersForSurface(SurfaceInterface *surface) const { return interfacesForSurface(surface, pointers); } QVector SeatInterface::Private::keyboardsForSurface(SurfaceInterface *surface) const { return interfacesForSurface(surface, keyboards); } TouchInterface *SeatInterface::Private::touchForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, touchs); } DataDeviceInterface *SeatInterface::Private::dataDeviceForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, dataDevices); } TextInputInterface *SeatInterface::Private::textInputForSurface(SurfaceInterface *surface) const { return interfaceForSurface(surface, textInputs); } void SeatInterface::Private::registerDataDevice(DataDeviceInterface *dataDevice) { Q_ASSERT(dataDevice->seat() == q); dataDevices << dataDevice; auto dataDeviceCleanup = [this, dataDevice] { dataDevices.removeOne(dataDevice); if (keys.focus.selection == dataDevice) { keys.focus.selection = nullptr; } if (currentSelection == dataDevice) { // current selection is cleared currentSelection = nullptr; if (keys.focus.selection) { keys.focus.selection->sendClearSelection(); } } }; QObject::connect(dataDevice, &QObject::destroyed, q, dataDeviceCleanup); QObject::connect(dataDevice, &Resource::unbound, q, dataDeviceCleanup); QObject::connect(dataDevice, &DataDeviceInterface::selectionChanged, q, [this, dataDevice] { updateSelection(dataDevice, true); } ); QObject::connect(dataDevice, &DataDeviceInterface::selectionCleared, q, [this, dataDevice] { updateSelection(dataDevice, false); } ); QObject::connect(dataDevice, &DataDeviceInterface::dragStarted, q, [this, dataDevice] { if (q->hasImplicitPointerGrab(dataDevice->dragImplicitGrabSerial())) { drag.mode = Drag::Mode::Pointer; } else { // TODO: touch return; } drag.source = dataDevice; drag.target = dataDevice; drag.surface = dataDevice->origin(); drag.sourcePointer = interfaceForSurface(drag.surface, pointers); // TODO: transformation needs to be either pointer or touch drag.transformation = globalPointer.focus.transformation; drag.destroyConnection = QObject::connect(dataDevice, &QObject::destroyed, q, [this] { endDrag(display->nextSerial()); } ); dataDevice->updateDragTarget(dataDevice->origin(), dataDevice->dragImplicitGrabSerial()); emit q->dragStarted(); emit q->dragSurfaceChanged(); } ); // is the new DataDevice for the current keyoard focus? if (keys.focus.surface && !keys.focus.selection) { // same client? if (keys.focus.surface->client() == dataDevice->client()) { keys.focus.selection = dataDevice; if (currentSelection && currentSelection->selection()) { dataDevice->sendSelection(currentSelection); } } } } void SeatInterface::Private::registerTextInput(TextInputInterface *ti) { // text input version 0 might call this multiple times if (textInputs.contains(ti)) { return; } textInputs << ti; if (textInput.focus.surface && textInput.focus.surface->client() == ti->client()) { // this is a text input for the currently focused text input surface if (!textInput.focus.textInput) { textInput.focus.textInput = ti; ti->d_func()->sendEnter(textInput.focus.surface, textInput.focus.serial); emit q->focusedTextInputChanged(); } } QObject::connect(ti, &QObject::destroyed, q, [this, ti] { textInputs.removeAt(textInputs.indexOf(ti)); if (textInput.focus.textInput == ti) { textInput.focus.textInput = nullptr; emit q->focusedTextInputChanged(); } } ); } void SeatInterface::Private::endDrag(quint32 serial) { auto target = drag.target; QObject::disconnect(drag.destroyConnection); + if (drag.source) { + drag.source->dragSource()->dropPerformed(); + } if (target) { target->drop(); target->updateDragTarget(nullptr, serial); } drag = Drag(); emit q->dragSurfaceChanged(); emit q->dragEnded(); } void SeatInterface::Private::cancelPreviousSelection(DataDeviceInterface *dataDevice) { if (!currentSelection) { return; } if (auto s = currentSelection->selection()) { if (currentSelection != dataDevice) { // only if current selection is not on the same device // that would cancel the newly set source s->cancel(); } } } void SeatInterface::Private::updateSelection(DataDeviceInterface *dataDevice, bool set) { if (keys.focus.surface && (keys.focus.surface->client() == dataDevice->client())) { // cancel the previous selection cancelPreviousSelection(dataDevice); // new selection on a data device belonging to current keyboard focus currentSelection = dataDevice; } if (dataDevice == currentSelection) { // need to send out the selection if (keys.focus.selection) { if (set) { keys.focus.selection->sendSelection(dataDevice); } else { keys.focus.selection->sendClearSelection(); currentSelection = nullptr; } } } } void SeatInterface::setHasKeyboard(bool has) { Q_D(); if (d->keyboard == has) { return; } d->keyboard = has; emit hasKeyboardChanged(d->keyboard); } void SeatInterface::setHasPointer(bool has) { Q_D(); if (d->pointer == has) { return; } d->pointer = has; emit hasPointerChanged(d->pointer); } void SeatInterface::setHasTouch(bool has) { Q_D(); if (d->touch == has) { return; } d->touch = has; emit hasTouchChanged(d->touch); } void SeatInterface::setName(const QString &name) { Q_D(); if (d->name == name) { return; } d->name = name; emit nameChanged(d->name); } void SeatInterface::Private::getPointerCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getPointer(client, resource, id); } void SeatInterface::Private::getPointer(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has pointer? PointerInterface *pointer = new PointerInterface(q, resource); auto clientConnection = display->getConnection(client); pointer->create(clientConnection, qMin(wl_resource_get_version(resource), s_pointerVersion), id); if (!pointer->resource()) { wl_resource_post_no_memory(resource); delete pointer; return; } pointers << pointer; if (globalPointer.focus.surface && globalPointer.focus.surface->client() == clientConnection) { // this is a pointer for the currently focused pointer surface globalPointer.focus.pointers << pointer; pointer->setFocusedSurface(globalPointer.focus.surface, globalPointer.focus.serial); if (globalPointer.focus.pointers.count() == 1) { // got a new pointer emit q->focusedPointerChanged(pointer); } } QObject::connect(pointer, &QObject::destroyed, q, [pointer,this] { pointers.removeAt(pointers.indexOf(pointer)); if (globalPointer.focus.pointers.removeOne(pointer)) { if (globalPointer.focus.pointers.isEmpty()) { emit q->focusedPointerChanged(nullptr); } } } ); emit q->pointerCreated(pointer); } void SeatInterface::Private::getKeyboardCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getKeyboard(client, resource, id); } void SeatInterface::Private::getKeyboard(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has keyboard? KeyboardInterface *keyboard = new KeyboardInterface(q, resource); auto clientConnection = display->getConnection(client); keyboard->create(clientConnection, qMin(wl_resource_get_version(resource), s_keyboardVersion) , id); if (!keyboard->resource()) { wl_resource_post_no_memory(resource); delete keyboard; return; } keyboard->repeatInfo(keys.keyRepeat.charactersPerSecond, keys.keyRepeat.delay); if (keys.keymap.xkbcommonCompatible) { keyboard->setKeymap(keys.keymap.fd, keys.keymap.size); } keyboards << keyboard; if (keys.focus.surface && keys.focus.surface->client() == clientConnection) { // this is a keyboard for the currently focused keyboard surface keys.focus.keyboards << keyboard; keyboard->setFocusedSurface(keys.focus.surface, keys.focus.serial); } QObject::connect(keyboard, &QObject::destroyed, q, [keyboard,this] { keyboards.removeAt(keyboards.indexOf(keyboard)); keys.focus.keyboards.removeOne(keyboard); } ); emit q->keyboardCreated(keyboard); } void SeatInterface::Private::getTouchCallback(wl_client *client, wl_resource *resource, uint32_t id) { cast(resource)->getTouch(client, resource, id); } void SeatInterface::Private::getTouch(wl_client *client, wl_resource *resource, uint32_t id) { // TODO: only create if seat has touch? TouchInterface *touch = new TouchInterface(q, resource); auto clientConnection = display->getConnection(client); touch->create(clientConnection, qMin(wl_resource_get_version(resource), s_touchVersion), id); if (!touch->resource()) { wl_resource_post_no_memory(resource); delete touch; return; } touchs << touch; if (touchInterface.focus.surface && touchInterface.focus.surface->client() == clientConnection) { // this is a touch for the currently focused touch surface if (!touchInterface.focus.touch) { touchInterface.focus.touch = touch; if (!touchInterface.ids.isEmpty()) { // TODO: send out all the points } } } QObject::connect(touch, &QObject::destroyed, q, [touch,this] { touchs.removeAt(touchs.indexOf(touch)); if (touchInterface.focus.touch == touch) { touchInterface.focus.touch = nullptr; } } ); emit q->touchCreated(touch); } QString SeatInterface::name() const { Q_D(); return d->name; } bool SeatInterface::hasPointer() const { Q_D(); return d->pointer; } bool SeatInterface::hasKeyboard() const { Q_D(); return d->keyboard; } bool SeatInterface::hasTouch() const { Q_D(); return d->touch; } SeatInterface *SeatInterface::get(wl_resource *native) { return Private::get(native); } SeatInterface::Private *SeatInterface::d_func() const { return reinterpret_cast(d.data()); } QPointF SeatInterface::pointerPos() const { Q_D(); return d->globalPointer.pos; } void SeatInterface::setPointerPos(const QPointF &pos) { Q_D(); if (d->globalPointer.pos == pos) { return; } d->globalPointer.pos = pos; emit pointerPosChanged(pos); } quint32 SeatInterface::timestamp() const { Q_D(); return d->timestamp; } void SeatInterface::setTimestamp(quint32 time) { Q_D(); if (d->timestamp == time) { return; } d->timestamp = time; emit timestampChanged(time); } void SeatInterface::setDragTarget(SurfaceInterface *surface, const QPointF &globalPosition, const QMatrix4x4 &inputTransformation) { Q_D(); if (surface == d->drag.surface) { // no change return; } const quint32 serial = d->display->nextSerial(); if (d->drag.target) { d->drag.target->updateDragTarget(nullptr, serial); } d->drag.target = d->dataDeviceForSurface(surface); // TODO: update touch if (d->drag.mode == Private::Drag::Mode::Pointer) { setPointerPos(globalPosition); } if (d->drag.target) { d->drag.surface = surface; d->drag.transformation = inputTransformation; d->drag.target->updateDragTarget(surface, serial); } else { d->drag.surface = nullptr; } emit dragSurfaceChanged(); return; } void SeatInterface::setDragTarget(SurfaceInterface *surface, const QMatrix4x4 &inputTransformation) { // TODO: handle touch setDragTarget(surface, pointerPos(), inputTransformation); } SurfaceInterface *SeatInterface::focusedPointerSurface() const { Q_D(); return d->globalPointer.focus.surface; } void SeatInterface::setFocusedPointerSurface(SurfaceInterface *surface, const QPointF &surfacePosition) { QMatrix4x4 m; m.translate(-surfacePosition.x(), -surfacePosition.y()); setFocusedPointerSurface(surface, m); Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.offset = surfacePosition; } } void SeatInterface::setFocusedPointerSurface(SurfaceInterface *surface, const QMatrix4x4 &transformation) { Q_D(); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } const quint32 serial = d->display->nextSerial(); for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { (*it)->setFocusedSurface(nullptr, serial); } if (d->globalPointer.focus.surface) { disconnect(d->globalPointer.focus.destroyConnection); } d->globalPointer.focus = Private::Pointer::Focus(); d->globalPointer.focus.surface = surface; auto p = d->pointersForSurface(surface); d->globalPointer.focus.pointers = p; if (d->globalPointer.focus.surface) { d->globalPointer.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); d->globalPointer.focus = Private::Pointer::Focus(); emit focusedPointerChanged(nullptr); } ); d->globalPointer.focus.offset = QPointF(); d->globalPointer.focus.transformation = transformation; d->globalPointer.focus.serial = serial; } if (p.isEmpty()) { emit focusedPointerChanged(nullptr); return; } // TODO: signal with all pointers emit focusedPointerChanged(p.first()); for (auto it = p.constBegin(), end = p.constEnd(); it != end; ++it) { (*it)->setFocusedSurface(surface, serial); } } PointerInterface *SeatInterface::focusedPointer() const { Q_D(); if (d->globalPointer.focus.pointers.isEmpty()) { return nullptr; } return d->globalPointer.focus.pointers.first(); } void SeatInterface::setFocusedPointerSurfacePosition(const QPointF &surfacePosition) { Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.offset = surfacePosition; d->globalPointer.focus.transformation = QMatrix4x4(); d->globalPointer.focus.transformation.translate(-surfacePosition.x(), -surfacePosition.y()); } } QPointF SeatInterface::focusedPointerSurfacePosition() const { Q_D(); return d->globalPointer.focus.offset; } void SeatInterface::setFocusedPointerSurfaceTransformation(const QMatrix4x4 &transformation) { Q_D(); if (d->globalPointer.focus.surface) { d->globalPointer.focus.transformation = transformation; } } QMatrix4x4 SeatInterface::focusedPointerSurfaceTransformation() const { Q_D(); return d->globalPointer.focus.transformation; } namespace { static quint32 qtToWaylandButton(Qt::MouseButton button) { #if HAVE_LINUX_INPUT_H static const QHash s_buttons({ {Qt::LeftButton, BTN_LEFT}, {Qt::RightButton, BTN_RIGHT}, {Qt::MiddleButton, BTN_MIDDLE}, {Qt::ExtraButton1, BTN_BACK}, // note: QtWayland maps BTN_SIDE {Qt::ExtraButton2, BTN_FORWARD}, // note: QtWayland maps BTN_EXTRA {Qt::ExtraButton3, BTN_TASK}, // note: QtWayland maps BTN_FORWARD {Qt::ExtraButton4, BTN_EXTRA}, // note: QtWayland maps BTN_BACK {Qt::ExtraButton5, BTN_SIDE}, // note: QtWayland maps BTN_TASK {Qt::ExtraButton6, BTN_TASK + 1}, {Qt::ExtraButton7, BTN_TASK + 2}, {Qt::ExtraButton8, BTN_TASK + 3}, {Qt::ExtraButton9, BTN_TASK + 4}, {Qt::ExtraButton10, BTN_TASK + 5}, {Qt::ExtraButton11, BTN_TASK + 6}, {Qt::ExtraButton12, BTN_TASK + 7}, {Qt::ExtraButton13, BTN_TASK + 8} // further mapping not possible, 0x120 is BTN_JOYSTICK }); return s_buttons.value(button, 0); #else return 0; #endif } } bool SeatInterface::isPointerButtonPressed(Qt::MouseButton button) const { return isPointerButtonPressed(qtToWaylandButton(button)); } bool SeatInterface::isPointerButtonPressed(quint32 button) const { Q_D(); auto it = d->globalPointer.buttonStates.constFind(button); if (it == d->globalPointer.buttonStates.constEnd()) { return false; } return it.value() == Private::Pointer::State::Pressed ? true : false; } void SeatInterface::pointerAxis(Qt::Orientation orientation, quint32 delta) { Q_D(); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } if (d->globalPointer.focus.surface) { for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { (*it)->axis(orientation, delta); } } } void SeatInterface::pointerButtonPressed(Qt::MouseButton button) { const quint32 nativeButton = qtToWaylandButton(button); if (nativeButton == 0) { return; } pointerButtonPressed(nativeButton); } void SeatInterface::pointerButtonPressed(quint32 button) { Q_D(); const quint32 serial = d->display->nextSerial(); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Pressed); if (d->drag.mode == Private::Drag::Mode::Pointer) { // ignore return; } if (d->globalPointer.focus.surface) { for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { (*it)->buttonPressed(button, serial); } if (d->globalPointer.focus.surface == d->keys.focus.surface) { // update the focused child surface auto p = focusedPointer(); if (p) { for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->d_func()->focusChildSurface(p->d_func()->focusedChildSurface, serial); } } } } } void SeatInterface::pointerButtonReleased(Qt::MouseButton button) { const quint32 nativeButton = qtToWaylandButton(button); if (nativeButton == 0) { return; } pointerButtonReleased(nativeButton); } void SeatInterface::pointerButtonReleased(quint32 button) { Q_D(); const quint32 serial = d->display->nextSerial(); const quint32 currentButtonSerial = pointerButtonSerial(button); d->updatePointerButtonSerial(button, serial); d->updatePointerButtonState(button, Private::Pointer::State::Released); if (d->drag.mode == Private::Drag::Mode::Pointer) { if (d->drag.source->dragImplicitGrabSerial() != currentButtonSerial) { // not our drag button - ignore return; } d->endDrag(serial); return; } if (d->globalPointer.focus.surface) { for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { (*it)->buttonReleased(button, serial); } } } quint32 SeatInterface::pointerButtonSerial(Qt::MouseButton button) const { return pointerButtonSerial(qtToWaylandButton(button)); } quint32 SeatInterface::pointerButtonSerial(quint32 button) const { Q_D(); auto it = d->globalPointer.buttonSerials.constFind(button); if (it == d->globalPointer.buttonSerials.constEnd()) { return 0; } return it.value(); } void SeatInterface::relativePointerMotion(const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 microseconds) { Q_D(); if (d->globalPointer.focus.surface) { for (auto it = d->globalPointer.focus.pointers.constBegin(), end = d->globalPointer.focus.pointers.constEnd(); it != end; ++it) { (*it)->relativeMotion(delta, deltaNonAccelerated, microseconds); } } } void SeatInterface::startPointerSwipeGesture(quint32 fingerCount) { Q_D(); if (!d->globalPointer.gestureSurface.isNull()) { return; } d->globalPointer.gestureSurface = QPointer(d->globalPointer.focus.surface); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial, fingerCount] (PointerInterface *p) { p->d_func()->startSwipeGesture(serial, fingerCount); } ); } void SeatInterface::updatePointerSwipeGesture(const QSizeF &delta) { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [delta] (PointerInterface *p) { p->d_func()->updateSwipeGesture(delta); } ); } void SeatInterface::endPointerSwipeGesture() { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial] (PointerInterface *p) { p->d_func()->endSwipeGesture(serial); } ); d->globalPointer.gestureSurface.clear(); } void SeatInterface::cancelPointerSwipeGesture() { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial] (PointerInterface *p) { p->d_func()->cancelSwipeGesture(serial); } ); d->globalPointer.gestureSurface.clear(); } void SeatInterface::startPointerPinchGesture(quint32 fingerCount) { Q_D(); if (!d->globalPointer.gestureSurface.isNull()) { return; } d->globalPointer.gestureSurface = QPointer(d->globalPointer.focus.surface); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial, fingerCount] (PointerInterface *p) { p->d_func()->startPinchGesture(serial, fingerCount); } ); } void SeatInterface::updatePointerPinchGesture(const QSizeF &delta, qreal scale, qreal rotation) { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [delta, scale, rotation] (PointerInterface *p) { p->d_func()->updatePinchGesture(delta, scale, rotation); } ); } void SeatInterface::endPointerPinchGesture() { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial] (PointerInterface *p) { p->d_func()->endPinchGesture(serial); } ); d->globalPointer.gestureSurface.clear(); } void SeatInterface::cancelPointerPinchGesture() { Q_D(); if (d->globalPointer.gestureSurface.isNull()) { return; } const quint32 serial = d->display->nextSerial(); forEachInterface(d->globalPointer.gestureSurface.data(), d->pointers, [serial] (PointerInterface *p) { p->d_func()->cancelPinchGesture(serial); } ); d->globalPointer.gestureSurface.clear(); } void SeatInterface::keyPressed(quint32 key) { Q_D(); d->keys.lastStateSerial = d->display->nextSerial(); if (!d->updateKey(key, Private::Keyboard::State::Pressed)) { return; } if (d->keys.focus.surface) { for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->keyPressed(key, d->keys.lastStateSerial); } } } void SeatInterface::keyReleased(quint32 key) { Q_D(); d->keys.lastStateSerial = d->display->nextSerial(); if (!d->updateKey(key, Private::Keyboard::State::Released)) { return; } if (d->keys.focus.surface) { for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->keyReleased(key, d->keys.lastStateSerial); } } } SurfaceInterface *SeatInterface::focusedKeyboardSurface() const { Q_D(); return d->keys.focus.surface; } void SeatInterface::setFocusedKeyboardSurface(SurfaceInterface *surface) { Q_D(); const quint32 serial = d->display->nextSerial(); for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->setFocusedSurface(nullptr, serial); } if (d->keys.focus.surface) { disconnect(d->keys.focus.destroyConnection); } d->keys.focus = Private::Keyboard::Focus(); d->keys.focus.surface = surface; d->keys.focus.keyboards = d->keyboardsForSurface(surface); if (d->keys.focus.surface) { d->keys.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); d->keys.focus = Private::Keyboard::Focus(); } ); d->keys.focus.serial = serial; // selection? d->keys.focus.selection = d->dataDeviceForSurface(surface); if (d->keys.focus.selection) { if (d->currentSelection && d->currentSelection->selection()) { d->keys.focus.selection->sendSelection(d->currentSelection); } else { d->keys.focus.selection->sendClearSelection(); } } } for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->setFocusedSurface(surface, serial); } // focused text input surface follows keyboard if (hasKeyboard()) { setFocusedTextInputSurface(surface); } } void SeatInterface::setKeymap(int fd, quint32 size) { Q_D(); d->keys.keymap.xkbcommonCompatible = true; d->keys.keymap.fd = fd; d->keys.keymap.size = size; for (auto it = d->keyboards.constBegin(); it != d->keyboards.constEnd(); ++it) { (*it)->setKeymap(fd, size); } } void SeatInterface::updateKeyboardModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group) { Q_D(); bool changed = false; #define UPDATE( value ) \ if (d->keys.modifiers.value != value) { \ d->keys.modifiers.value = value; \ changed = true; \ } UPDATE(depressed) UPDATE(latched) UPDATE(locked) UPDATE(group) if (!changed) { return; } const quint32 serial = d->display->nextSerial(); d->keys.modifiers.serial = serial; if (d->keys.focus.surface) { for (auto it = d->keys.focus.keyboards.constBegin(), end = d->keys.focus.keyboards.constEnd(); it != end; ++it) { (*it)->updateModifiers(depressed, latched, locked, group, serial); } } } void SeatInterface::setKeyRepeatInfo(qint32 charactersPerSecond, qint32 delay) { Q_D(); d->keys.keyRepeat.charactersPerSecond = qMax(charactersPerSecond, 0); d->keys.keyRepeat.delay = qMax(delay, 0); for (auto it = d->keyboards.constBegin(); it != d->keyboards.constEnd(); ++it) { (*it)->repeatInfo(d->keys.keyRepeat.charactersPerSecond, d->keys.keyRepeat.delay); } } qint32 SeatInterface::keyRepeatDelay() const { Q_D(); return d->keys.keyRepeat.delay; } qint32 SeatInterface::keyRepeatRate() const { Q_D(); return d->keys.keyRepeat.charactersPerSecond; } bool SeatInterface::isKeymapXkbCompatible() const { Q_D(); return d->keys.keymap.xkbcommonCompatible; } int SeatInterface::keymapFileDescriptor() const { Q_D(); return d->keys.keymap.fd; } quint32 SeatInterface::keymapSize() const { Q_D(); return d->keys.keymap.size; } quint32 SeatInterface::depressedModifiers() const { Q_D(); return d->keys.modifiers.depressed; } quint32 SeatInterface::groupModifiers() const { Q_D(); return d->keys.modifiers.group; } quint32 SeatInterface::latchedModifiers() const { Q_D(); return d->keys.modifiers.latched; } quint32 SeatInterface::lockedModifiers() const { Q_D(); return d->keys.modifiers.locked; } quint32 SeatInterface::lastModifiersSerial() const { Q_D(); return d->keys.modifiers.serial; } QVector< quint32 > SeatInterface::pressedKeys() const { Q_D(); QVector keys; for (auto it = d->keys.states.begin(); it != d->keys.states.end(); ++it) { if (it.value() == Private::Keyboard::State::Pressed) { keys << it.key(); } } return keys; } KeyboardInterface *SeatInterface::focusedKeyboard() const { Q_D(); if (d->keys.focus.keyboards.isEmpty()) { return nullptr; } return d->keys.focus.keyboards.first(); } void SeatInterface::cancelTouchSequence() { Q_D(); if (d->touchInterface.focus.touch) { d->touchInterface.focus.touch->cancel(); } d->touchInterface.ids.clear(); } TouchInterface *SeatInterface::focusedTouch() const { Q_D(); return d->touchInterface.focus.touch; } SurfaceInterface *SeatInterface::focusedTouchSurface() const { Q_D(); return d->touchInterface.focus.surface; } QPointF SeatInterface::focusedTouchSurfacePosition() const { Q_D(); return d->touchInterface.focus.offset; } bool SeatInterface::isTouchSequence() const { Q_D(); return !d->touchInterface.ids.isEmpty(); } void SeatInterface::setFocusedTouchSurface(SurfaceInterface *surface, const QPointF &surfacePosition) { if (isTouchSequence()) { // changing surface not allowed during a touch sequence return; } Q_D(); if (d->touchInterface.focus.surface) { disconnect(d->touchInterface.focus.destroyConnection); } d->touchInterface.focus = Private::Touch::Focus(); d->touchInterface.focus.surface = surface; d->touchInterface.focus.offset = surfacePosition; TouchInterface *t = d->touchForSurface(surface); if (t && !t->resource()) { t = nullptr; } d->touchInterface.focus.touch = t; if (d->touchInterface.focus.surface) { d->touchInterface.focus.destroyConnection = connect(surface, &QObject::destroyed, this, [this] { Q_D(); if (isTouchSequence() && d->touchInterface.focus.touch) { // Surface destroyed during touch sequence - send a cancel d->touchInterface.focus.touch->cancel(); } d->touchInterface.focus = Private::Touch::Focus(); } ); } } void SeatInterface::setFocusedTouchSurfacePosition(const QPointF &surfacePosition) { Q_D(); d->touchInterface.focus.offset = surfacePosition; } qint32 SeatInterface::touchDown(const QPointF &globalPosition) { Q_D(); const qint32 id = d->touchInterface.ids.isEmpty() ? 0 : d->touchInterface.ids.last() + 1; const quint32 serial = display()->nextSerial(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->down(id, serial, globalPosition - d->touchInterface.focus.offset); } else if (id == 0 && focusedTouchSurface()) { #if HAVE_LINUX_INPUT_H const QPointF pos = globalPosition - d->touchInterface.focus.offset; const bool result = forEachInterface(focusedTouchSurface(), d->pointers, [this, pos, serial] (PointerInterface *p) { wl_pointer_send_enter(p->resource(), serial, focusedTouchSurface()->resource(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); wl_pointer_send_motion(p->resource(), timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); wl_pointer_send_button(p->resource(), serial, timestamp(), BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED); } ); if (!result) { return id; } #endif } d->touchInterface.ids << id; return id; } void SeatInterface::touchMove(qint32 id, const QPointF &globalPosition) { Q_D(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->move(id, globalPosition - d->touchInterface.focus.offset); } else if (id == 0 && focusedTouchSurface()) { const QPointF pos = globalPosition - d->touchInterface.focus.offset; forEachInterface(focusedTouchSurface(), d->pointers, [this, pos] (PointerInterface *p) { wl_pointer_send_motion(p->resource(), timestamp(), wl_fixed_from_double(pos.x()), wl_fixed_from_double(pos.y())); } ); } } void SeatInterface::touchUp(qint32 id) { Q_D(); Q_ASSERT(d->touchInterface.ids.contains(id)); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->up(id, display()->nextSerial()); } else if (id == 0 && focusedTouchSurface()) { #if HAVE_LINUX_INPUT_H const quint32 serial = display()->nextSerial(); forEachInterface(focusedTouchSurface(), d->pointers, [this, serial] (PointerInterface *p) { wl_pointer_send_button(p->resource(), serial, timestamp(), BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); } ); #endif } d->touchInterface.ids.removeAll(id); } void SeatInterface::touchFrame() { Q_D(); if (d->touchInterface.focus.touch && d->touchInterface.focus.surface) { d->touchInterface.focus.touch->frame(); } } bool SeatInterface::isDrag() const { Q_D(); return d->drag.mode != Private::Drag::Mode::None; } bool SeatInterface::isDragPointer() const { Q_D(); return d->drag.mode == Private::Drag::Mode::Pointer; } bool SeatInterface::isDragTouch() const { Q_D(); return d->drag.mode == Private::Drag::Mode::Touch; } bool SeatInterface::hasImplicitPointerGrab(quint32 serial) const { Q_D(); const auto &serials = d->globalPointer.buttonSerials; for (auto it = serials.begin(), end = serials.end(); it != end; it++) { if (it.value() == serial) { return isPointerButtonPressed(it.key()); } } return false; } QMatrix4x4 SeatInterface::dragSurfaceTransformation() const { Q_D(); return d->drag.transformation; } SurfaceInterface *SeatInterface::dragSurface() const { Q_D(); return d->drag.surface; } PointerInterface *SeatInterface::dragPointer() const { Q_D(); if (d->drag.mode != Private::Drag::Mode::Pointer) { return nullptr; } return d->drag.sourcePointer; } DataDeviceInterface *SeatInterface::dragSource() const { Q_D(); return d->drag.source; } void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface) { Q_D(); const quint32 serial = d->display->nextSerial(); const auto old = d->textInput.focus.textInput; if (d->textInput.focus.textInput) { // TODO: setFocusedSurface like in other interfaces d->textInput.focus.textInput->d_func()->sendLeave(serial, d->textInput.focus.surface); } if (d->textInput.focus.surface) { disconnect(d->textInput.focus.destroyConnection); } d->textInput.focus = Private::TextInput::Focus(); d->textInput.focus.surface = surface; TextInputInterface *t = d->textInputForSurface(surface); if (t && !t->resource()) { t = nullptr; } d->textInput.focus.textInput = t; if (d->textInput.focus.surface) { d->textInput.focus.destroyConnection = connect(surface, &Resource::aboutToBeUnbound, this, [this] { setFocusedTextInputSurface(nullptr); } ); d->textInput.focus.serial = serial; } if (t) { // TODO: setFocusedSurface like in other interfaces t->d_func()->sendEnter(surface, serial); } if (old != t) { emit focusedTextInputChanged(); } } SurfaceInterface *SeatInterface::focusedTextInputSurface() const { Q_D(); return d->textInput.focus.surface; } TextInputInterface *SeatInterface::focusedTextInput() const { Q_D(); return d->textInput.focus.textInput; } DataDeviceInterface *SeatInterface::selection() const { Q_D(); return d->currentSelection; } void SeatInterface::setSelection(DataDeviceInterface *dataDevice) { Q_D(); if (d->currentSelection == dataDevice) { return; } // cancel the previous selection d->cancelPreviousSelection(dataDevice); d->currentSelection = dataDevice; if (d->keys.focus.selection) { if (dataDevice && dataDevice->selection()) { d->keys.focus.selection->sendSelection(dataDevice); } else { d->keys.focus.selection->sendClearSelection(); } } } } }