diff --git a/autotests/client/CMakeLists.txt b/autotests/client/CMakeLists.txt --- a/autotests/client/CMakeLists.txt +++ b/autotests/client/CMakeLists.txt @@ -348,3 +348,10 @@ add_test(kwayland-testXdgShellV5 testXdgShellV5) ecm_mark_as_test(testXdgShellV5) +######################################################## +# Test Pointer Constraints +######################################################## +add_executable(testPointerConstraints test_pointer_constraints.cpp) +target_link_libraries( testPointerConstraints Qt5::Test Qt5::Gui KF5::WaylandServer KF5::WaylandClient Wayland::Client) +add_test(kwayland-testPointerConstraints testPointerConstraints) +ecm_mark_as_test(testPointerConstraints) diff --git a/autotests/client/test_pointer_constraints.cpp b/autotests/client/test_pointer_constraints.cpp new file mode 100644 --- /dev/null +++ b/autotests/client/test_pointer_constraints.cpp @@ -0,0 +1,433 @@ +/******************************************************************** +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 +// client +#include "../../src/client/connection_thread.h" +#include "../../src/client/compositor.h" +#include "../../src/client/event_queue.h" +#include "../../src/client/pointer.h" +#include "../../src/client/pointerconstraints.h" +#include "../../src/client/registry.h" +#include "../../src/client/seat.h" +#include "../../src/client/surface.h" +// server +#include "../../src/server/display.h" +#include "../../src/server/compositor_interface.h" +#include "../../src/server/pointerconstraints_interface.h" +#include "../../src/server/seat_interface.h" +#include "../../src/server/surface_interface.h" + +using namespace KWayland::Client; +using namespace KWayland::Server; + +Q_DECLARE_METATYPE(KWayland::Client::PointerConstraints::LifeTime) +Q_DECLARE_METATYPE(KWayland::Server::ConfinedPointerInterface::LifeTime) +Q_DECLARE_METATYPE(KWayland::Server::LockedPointerInterface::LifeTime) + +class TestPointerConstraints : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testLockPointer_data(); + void testLockPointer(); + + void testConfinePointer_data(); + void testConfinePointer(); + void testAlreadyConstrained_data(); + void testAlreadyConstrained(); + +private: + Display *m_display = nullptr; + CompositorInterface *m_compositorInterface = nullptr; + SeatInterface *m_seatInterface = nullptr; + PointerConstraintsInterface *m_pointerConstraintsInterface = nullptr; + ConnectionThread *m_connection = nullptr; + QThread *m_thread = nullptr; + EventQueue *m_queue = nullptr; + Compositor *m_compositor = nullptr; + Seat *m_seat = nullptr; + Pointer *m_pointer = nullptr; + PointerConstraints *m_pointerConstraints = nullptr; +}; + +static const QString s_socketName = QStringLiteral("kwayland-test-pointer_constraint-0"); + +void TestPointerConstraints::init() +{ + delete m_display; + m_display = new Display(this); + m_display->setSocketName(s_socketName); + m_display->start(); + QVERIFY(m_display->isRunning()); + m_display->createShm(); + m_seatInterface = m_display->createSeat(m_display); + m_seatInterface->setHasPointer(true); + m_seatInterface->create(); + m_compositorInterface = m_display->createCompositor(m_display); + m_compositorInterface->create(); + m_pointerConstraintsInterface = m_display->createPointerConstraints(PointerConstraintsInterfaceVersion::UnstableV1, m_display); + m_pointerConstraintsInterface->create(); + + // setup connection + m_connection = new KWayland::Client::ConnectionThread; + QSignalSpy connectedSpy(m_connection, &ConnectionThread::connected); + QVERIFY(connectedSpy.isValid()); + m_connection->setSocketName(s_socketName); + + m_thread = new QThread(this); + m_connection->moveToThread(m_thread); + m_thread->start(); + + m_connection->initConnection(); + QVERIFY(connectedSpy.wait()); + + m_queue = new EventQueue(this); + m_queue->setup(m_connection); + + Registry registry; + QSignalSpy interfacesAnnouncedSpy(®istry, &Registry::interfacesAnnounced); + QVERIFY(interfacesAnnouncedSpy.isValid()); + QSignalSpy interfaceAnnouncedSpy(®istry, &Registry::interfaceAnnounced); + QVERIFY(interfaceAnnouncedSpy.isValid()); + registry.setEventQueue(m_queue); + registry.create(m_connection); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + + m_compositor = registry.createCompositor(registry.interface(Registry::Interface::Compositor).name, registry.interface(Registry::Interface::Compositor).version, this); + QVERIFY(m_compositor); + QVERIFY(m_compositor->isValid()); + + m_pointerConstraints = registry.createPointerConstraints(registry.interface(Registry::Interface::PointerConstraintsUnstableV1).name, + registry.interface(Registry::Interface::PointerConstraintsUnstableV1).version, this); + QVERIFY(m_pointerConstraints); + QVERIFY(m_pointerConstraints->isValid()); + + m_seat = registry.createSeat(registry.interface(Registry::Interface::Seat).name, registry.interface(Registry::Interface::Seat).version, this); + QVERIFY(m_seat); + QVERIFY(m_seat->isValid()); + QSignalSpy pointerChangedSpy(m_seat, &Seat::hasPointerChanged); + QVERIFY(pointerChangedSpy.isValid()); + QVERIFY(pointerChangedSpy.wait()); + m_pointer = m_seat->createPointer(this); + QVERIFY(m_pointer); +} + +void TestPointerConstraints::cleanup() +{ +#define CLEANUP(variable) \ + if (variable) { \ + delete variable; \ + variable = nullptr; \ + } + CLEANUP(m_compositor) + CLEANUP(m_pointerConstraints) + CLEANUP(m_pointer) + CLEANUP(m_seat) + CLEANUP(m_queue) + if (m_connection) { + m_connection->deleteLater(); + m_connection = nullptr; + } + if (m_thread) { + m_thread->quit(); + m_thread->wait(); + delete m_thread; + m_thread = nullptr; + } + + CLEANUP(m_compositorInterface) + CLEANUP(m_seatInterface); + CLEANUP(m_pointerConstraintsInterface) + CLEANUP(m_display) +#undef CLEANUP +} + +void TestPointerConstraints::testLockPointer_data() +{ + QTest::addColumn("clientLifeTime"); + QTest::addColumn("serverLifeTime"); + QTest::addColumn("hasConstraintAfterUnlock"); + QTest::addColumn("pointerChangedCount"); + + QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << LockedPointerInterface::LifeTime::Persistent << true << 1; + QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << LockedPointerInterface::LifeTime::OneShot << false << 2; +} + +void TestPointerConstraints::testLockPointer() +{ + // this test verifies the basic interaction for lock pointer + // first create a surface + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surface->isValid()); + QVERIFY(surfaceCreatedSpy.wait()); + + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + QVERIFY(serverSurface->lockedPointer().isNull()); + QVERIFY(serverSurface->confinedPointer().isNull()); + + // now create the locked pointer + QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged); + QVERIFY(pointerConstraintsChangedSpy.isValid()); + QFETCH(PointerConstraints::LifeTime, clientLifeTime); + QScopedPointer lockedPointer(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, clientLifeTime)); + QSignalSpy lockedSpy(lockedPointer.data(), &LockedPointer::locked); + QVERIFY(lockedSpy.isValid()); + QSignalSpy unlockedSpy(lockedPointer.data(), &LockedPointer::unlocked); + QVERIFY(unlockedSpy.isValid()); + QVERIFY(lockedPointer->isValid()); + QVERIFY(pointerConstraintsChangedSpy.wait()); + + auto serverLockedPointer = serverSurface->lockedPointer(); + QVERIFY(serverLockedPointer); + QVERIFY(serverSurface->confinedPointer().isNull()); + + QCOMPARE(serverLockedPointer->isLocked(), false); + QCOMPARE(serverLockedPointer->region(), QRegion()); + QFETCH(LockedPointerInterface::LifeTime, serverLifeTime); + QCOMPARE(serverLockedPointer->lifeTime(), serverLifeTime); + // setting to unlocked now should not trigger an unlocked spy + serverLockedPointer->setLocked(false); + QVERIFY(!unlockedSpy.wait()); + + // try setting a region + QSignalSpy destroyedSpy(serverLockedPointer.data(), &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + QSignalSpy regionChangedSpy(serverLockedPointer.data(), &LockedPointerInterface::regionChanged); + QVERIFY(regionChangedSpy.isValid()); + lockedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor)); + // it's double buffered + QVERIFY(!regionChangedSpy.wait()); + surface->commit(Surface::CommitFlag::None); + QVERIFY(regionChangedSpy.wait()); + QCOMPARE(serverLockedPointer->region(), QRegion(0, 5, 10, 20)); + + // let's lock the surface + QSignalSpy lockedChangedSpy(serverLockedPointer.data(), &LockedPointerInterface::lockedChanged); + QVERIFY(lockedChangedSpy.isValid()); + m_seatInterface->setFocusedPointerSurface(serverSurface); + QSignalSpy pointerMotionSpy(m_pointer, &Pointer::motion); + QVERIFY(pointerMotionSpy.isValid()); + m_seatInterface->setPointerPos(QPoint(0, 1)); + QVERIFY(pointerMotionSpy.wait()); + + serverLockedPointer->setLocked(true); + QCOMPARE(serverLockedPointer->isLocked(), true); + m_seatInterface->setPointerPos(QPoint(1, 1)); + QCOMPARE(lockedChangedSpy.count(), 1); + QCOMPARE(pointerMotionSpy.count(), 1); + QVERIFY(lockedSpy.isEmpty()); + QVERIFY(lockedSpy.wait()); + QVERIFY(unlockedSpy.isEmpty()); + + // and unlock again + serverLockedPointer->setLocked(false); + QCOMPARE(serverLockedPointer->isLocked(), false); + QCOMPARE(lockedChangedSpy.count(), 2); + QTEST(!serverSurface->lockedPointer().isNull(), "hasConstraintAfterUnlock"); + QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount"); + QVERIFY(unlockedSpy.wait()); + QCOMPARE(unlockedSpy.count(), 1); + QCOMPARE(lockedSpy.count(), 1); + + // now motion should work again + m_seatInterface->setPointerPos(QPoint(0, 1)); + QVERIFY(pointerMotionSpy.wait()); + QCOMPARE(pointerMotionSpy.count(), 2); + + lockedPointer.reset(); + QVERIFY(destroyedSpy.wait()); + QCOMPARE(pointerConstraintsChangedSpy.count(), 2); +} + +void TestPointerConstraints::testConfinePointer_data() +{ + QTest::addColumn("clientLifeTime"); + QTest::addColumn("serverLifeTime"); + QTest::addColumn("hasConstraintAfterUnlock"); + QTest::addColumn("pointerChangedCount"); + + QTest::newRow("persistent") << PointerConstraints::LifeTime::Persistent << ConfinedPointerInterface::LifeTime::Persistent << true << 1; + QTest::newRow("oneshot") << PointerConstraints::LifeTime::OneShot << ConfinedPointerInterface::LifeTime::OneShot << false << 2; +} + +void TestPointerConstraints::testConfinePointer() +{ + // this test verifies the basic interaction for confined pointer + // first create a surface + QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated); + QVERIFY(surfaceCreatedSpy.isValid()); + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surface->isValid()); + QVERIFY(surfaceCreatedSpy.wait()); + + auto serverSurface = surfaceCreatedSpy.first().first().value(); + QVERIFY(serverSurface); + QVERIFY(serverSurface->lockedPointer().isNull()); + QVERIFY(serverSurface->confinedPointer().isNull()); + + // now create the confined pointer + QSignalSpy pointerConstraintsChangedSpy(serverSurface, &SurfaceInterface::pointerConstraintsChanged); + QVERIFY(pointerConstraintsChangedSpy.isValid()); + QFETCH(PointerConstraints::LifeTime, clientLifeTime); + QScopedPointer confinedPointer(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, clientLifeTime)); + QSignalSpy confinedSpy(confinedPointer.data(), &ConfinedPointer::confined); + QVERIFY(confinedSpy.isValid()); + QSignalSpy unconfinedSpy(confinedPointer.data(), &ConfinedPointer::unconfined); + QVERIFY(unconfinedSpy.isValid()); + QVERIFY(confinedPointer->isValid()); + QVERIFY(pointerConstraintsChangedSpy.wait()); + + auto serverConfinedPointer = serverSurface->confinedPointer(); + QVERIFY(serverConfinedPointer); + QVERIFY(serverSurface->lockedPointer().isNull()); + + QCOMPARE(serverConfinedPointer->isConfined(), false); + QCOMPARE(serverConfinedPointer->region(), QRegion()); + QFETCH(ConfinedPointerInterface::LifeTime, serverLifeTime); + QCOMPARE(serverConfinedPointer->lifeTime(), serverLifeTime); + // setting to unconfined now should not trigger an unconfined spy + serverConfinedPointer->setConfined(false); + QVERIFY(!unconfinedSpy.wait()); + + // try setting a region + QSignalSpy destroyedSpy(serverConfinedPointer.data(), &QObject::destroyed); + QVERIFY(destroyedSpy.isValid()); + QSignalSpy regionChangedSpy(serverConfinedPointer.data(), &ConfinedPointerInterface::regionChanged); + QVERIFY(regionChangedSpy.isValid()); + confinedPointer->setRegion(m_compositor->createRegion(QRegion(0, 5, 10, 20), m_compositor)); + // it's double buffered + QVERIFY(!regionChangedSpy.wait()); + surface->commit(Surface::CommitFlag::None); + QVERIFY(regionChangedSpy.wait()); + QCOMPARE(serverConfinedPointer->region(), QRegion(0, 5, 10, 20)); + + // let's confine the surface + QSignalSpy confinedChangedSpy(serverConfinedPointer.data(), &ConfinedPointerInterface::confinedChanged); + QVERIFY(confinedChangedSpy.isValid()); + m_seatInterface->setFocusedPointerSurface(serverSurface); + serverConfinedPointer->setConfined(true); + QCOMPARE(serverConfinedPointer->isConfined(), true); + QCOMPARE(confinedChangedSpy.count(), 1); + QVERIFY(confinedSpy.isEmpty()); + QVERIFY(confinedSpy.wait()); + QVERIFY(unconfinedSpy.isEmpty()); + + // and unconfine again + serverConfinedPointer->setConfined(false); + QCOMPARE(serverConfinedPointer->isConfined(), false); + QCOMPARE(confinedChangedSpy.count(), 2); + QTEST(!serverSurface->confinedPointer().isNull(), "hasConstraintAfterUnlock"); + QTEST(pointerConstraintsChangedSpy.count(), "pointerChangedCount"); + QVERIFY(unconfinedSpy.wait()); + QCOMPARE(unconfinedSpy.count(), 1); + QCOMPARE(confinedSpy.count(), 1); + + confinedPointer.reset(); + QVERIFY(destroyedSpy.wait()); + QCOMPARE(pointerConstraintsChangedSpy.count(), 2); +} + +enum class Constraint { + Lock, + Confine +}; + +Q_DECLARE_METATYPE(Constraint) + +void TestPointerConstraints::testAlreadyConstrained_data() +{ + QTest::addColumn("firstConstraint"); + QTest::addColumn("secondConstraint"); + + QTest::newRow("confine-confine") << Constraint::Confine << Constraint::Confine; + QTest::newRow("lock-confine") << Constraint::Lock << Constraint::Confine; + QTest::newRow("confine-lock") << Constraint::Confine << Constraint::Lock; + QTest::newRow("lock-lock") << Constraint::Lock << Constraint::Lock; +} + +void TestPointerConstraints::testAlreadyConstrained() +{ + // this test verifies that creating a pointer constraint for an already constrained surface triggers an error + // first create a surface + QScopedPointer surface(m_compositor->createSurface()); + QVERIFY(surface->isValid()); + QFETCH(Constraint, firstConstraint); + QScopedPointer confinedPointer; + QScopedPointer lockedPointer; + switch (firstConstraint) { + case Constraint::Lock: + lockedPointer.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); + break; + case Constraint::Confine: + confinedPointer.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); + break; + default: + Q_UNREACHABLE(); + } + QVERIFY(confinedPointer || lockedPointer); + + QSignalSpy errorSpy(m_connection, &ConnectionThread::errorOccurred); + QVERIFY(errorSpy.isValid()); + QFETCH(Constraint, secondConstraint); + QScopedPointer confinedPointer2; + QScopedPointer lockedPointer2; + switch (secondConstraint) { + case Constraint::Lock: + lockedPointer2.reset(m_pointerConstraints->lockPointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); + break; + case Constraint::Confine: + confinedPointer2.reset(m_pointerConstraints->confinePointer(surface.data(), m_pointer, nullptr, PointerConstraints::LifeTime::OneShot)); + break; + default: + Q_UNREACHABLE(); + } + QVERIFY(errorSpy.wait()); + QVERIFY(m_connection->hasError()); + if (confinedPointer2) { + confinedPointer2->destroy(); + } + if (lockedPointer2) { + lockedPointer2->destroy(); + } + if (confinedPointer) { + confinedPointer->destroy(); + } + if (lockedPointer) { + lockedPointer->destroy(); + } + surface->destroy(); + m_compositor->destroy(); + m_pointerConstraints->destroy(); + m_pointer->destroy(); + m_seat->destroy(); + m_queue->destroy(); +} + +QTEST_GUILESS_MAIN(TestPointerConstraints) +#include "test_pointer_constraints.moc" diff --git a/autotests/client/test_wayland_registry.cpp b/autotests/client/test_wayland_registry.cpp --- a/autotests/client/test_wayland_registry.cpp +++ b/autotests/client/test_wayland_registry.cpp @@ -26,6 +26,7 @@ #include "../../src/client/event_queue.h" #include "../../src/client/registry.h" #include "../../src/client/output.h" +#include "../../src/client/pointerconstraints.h" #include "../../src/client/pointergestures.h" #include "../../src/client/seat.h" #include "../../src/client/relativepointer.h" @@ -47,6 +48,7 @@ #include "../../src/server/subcompositor_interface.h" #include "../../src/server/outputmanagement_interface.h" #include "../../src/server/outputdevice_interface.h" +#include "../../src/server/pointerconstraints_interface.h" #include "../../src/server/pointergestures_interface.h" #include "../../src/server/textinput_interface.h" #include "../../src/server/xdgshell_interface.h" @@ -60,6 +62,7 @@ #include #include #include +#include class TestWaylandRegistry : public QObject { @@ -88,6 +91,7 @@ void testBindXdgShellUnstableV5(); void testBindRelativePointerManagerUnstableV1(); void testBindPointerGesturesUnstableV1(); + void testBindPointerConstraintsUnstableV1(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -111,6 +115,7 @@ KWayland::Server::XdgShellInterface *m_xdgShellUnstableV5; KWayland::Server::RelativePointerManagerInterface *m_relativePointerV1; KWayland::Server::PointerGesturesInterface *m_pointerGesturesV1; + KWayland::Server::PointerConstraintsInterface *m_pointerConstraintsV1; }; static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0"); @@ -132,6 +137,7 @@ , m_xdgShellUnstableV5(nullptr) , m_relativePointerV1(nullptr) , m_pointerGesturesV1(nullptr) + , m_pointerConstraintsV1(nullptr) { } @@ -179,6 +185,9 @@ m_pointerGesturesV1 = m_display->createPointerGestures(KWayland::Server::PointerGesturesInterfaceVersion::UnstableV1); m_pointerGesturesV1->create(); QCOMPARE(m_pointerGesturesV1->interfaceVersion(), KWayland::Server::PointerGesturesInterfaceVersion::UnstableV1); + m_pointerConstraintsV1 = m_display->createPointerConstraints(KWayland::Server::PointerConstraintsInterfaceVersion::UnstableV1); + m_pointerConstraintsV1->create(); + QCOMPARE(m_pointerConstraintsV1->interfaceVersion(), KWayland::Server::PointerConstraintsInterfaceVersion::UnstableV1); } void TestWaylandRegistry::cleanup() @@ -331,6 +340,11 @@ TEST_BIND(KWayland::Client::Registry::Interface::PointerGesturesUnstableV1, SIGNAL(pointerGesturesUnstableV1Announced(quint32,quint32)), bindPointerGesturesUnstableV1, zwp_pointer_gestures_v1_destroy) } +void TestWaylandRegistry::testBindPointerConstraintsUnstableV1() +{ + TEST_BIND(KWayland::Client::Registry::Interface::PointerConstraintsUnstableV1, SIGNAL(pointerConstraintsUnstableV1Announced(quint32,quint32)), bindPointerConstraintsUnstableV1, zwp_pointer_constraints_v1_destroy) +} + #undef TEST_BIND void TestWaylandRegistry::testRemoval() diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -28,6 +28,7 @@ logging.cpp output.cpp pointer.cpp + pointerconstraints.cpp pointergestures.cpp plasmashell.cpp plasmawindowmanagement.cpp @@ -133,6 +134,10 @@ PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/pointer-gestures-unstable-v1.xml BASENAME pointer-gestures-unstable-v1 ) +ecm_add_wayland_client_protocol(CLIENT_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/pointer-constraints-unstable-v1.xml + BASENAME pointer-constraints-unstable-v1 +) add_library(KF5WaylandClient ${CLIENT_LIB_SRCS}) generate_export_header(KF5WaylandClient diff --git a/src/client/pointerconstraints.h b/src/client/pointerconstraints.h new file mode 100644 --- /dev/null +++ b/src/client/pointerconstraints.h @@ -0,0 +1,473 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_CLIENT_POINTERCONSTRAINTS_H +#define KWAYLAND_CLIENT_POINTERCONSTRAINTS_H + +#include + +#include + +struct zwp_pointer_constraints_v1; +struct zwp_locked_pointer_v1; +struct zwp_confined_pointer_v1; + +class QPointF; + +namespace KWayland +{ +namespace Client +{ + +class EventQueue; +class LockedPointer; +class Surface; +class Region; +class ConfinedPointer; +class Pointer; + +/** + * @short Wrapper for the zwp_pointer_constraints_v1 interface. + * + * This class provides a convenient wrapper for the zwp_pointer_constraints_v1 interface. + * + * To use this class one needs to interact with the Registry. There are two + * possible ways to create the PointerConstraints interface: + * @code + * PointerConstraints *c = registry->createPointerConstraints(name, version); + * @endcode + * + * This creates the PointerConstraints and sets it up directly. As an alternative this + * can also be done in a more low level way: + * @code + * PointerConstraints *c = new PointerConstraints; + * c->setup(registry->bindPointerConstraints(name, version)); + * @endcode + * + * The PointerConstraints can be used as a drop-in replacement for any zwp_pointer_constraints_v1 + * pointer as it provides matching cast operators. + * + * @see Registry + * @since 5.29 + **/ +class KWAYLANDCLIENT_EXPORT PointerConstraints : public QObject +{ + Q_OBJECT +public: + /** + * Creates a new PointerConstraints. + * Note: after constructing the PointerConstraints it is not yet valid and one needs + * to call setup. In order to get a ready to use PointerConstraints prefer using + * Registry::createPointerConstraints. + **/ + explicit PointerConstraints(QObject *parent = nullptr); + virtual ~PointerConstraints(); + + /** + * Setup this PointerConstraints to manage the @p pointerconstraints. + * When using Registry::createPointerConstraints there is no need to call this + * method. + **/ + void setup(zwp_pointer_constraints_v1 *pointerconstraints); + /** + * @returns @c true if managing a zwp_pointer_constraints_v1. + **/ + bool isValid() const; + /** + * Releases the zwp_pointer_constraints_v1 interface. + * After the interface has been released the PointerConstraints instance is no + * longer valid and can be setup with another zwp_pointer_constraints_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this PointerConstraints. + * 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 zwp_pointer_constraints_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, pointerconstraints, &PointerConstraints::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Sets the @p queue to use for creating objects with this PointerConstraints. + **/ + void setEventQueue(EventQueue *queue); + /** + * @returns The event queue to use for creating objects with this PointerConstraints. + **/ + EventQueue *eventQueue(); + + /** + * These values represent different lifetime semantics. They are passed + * as arguments to the factory requests to specify how the constraint + * lifetimes should be managed. + * @see lockPointer + * @see confinePointer + **/ + enum class LifeTime { + /** + * A OneShot pointer constraint will never reactivate once it has been + * deactivated. + **/ + OneShot, + /** + * A persistent pointer constraint may again reactivate once it has + * been deactivated. + **/ + Persistent + }; + + /** + * This factory method creates a LockedPointer. + * + * A LockedPointer lets the client request to disable movements of + * the virtual pointer (i.e. the cursor), effectively locking the pointer + * to a position. + * + * Creating a LockedPointer does not lock the pointer immediately; in the + * future, when the compositor deems implementation-specific constraints + * are satisfied, the pointer lock will be activated and the compositor + * sends a locked event, reported by @link{LockedPointer::locked}. + * + * The protocol provides no guarantee that the constraints are ever + * satisfied, and does not require the compositor to send an error if the + * constraints cannot ever be satisfied. It is thus possible to request a + * lock that will never activate. + * + * There may not be another pointer constraint of any kind requested or + * active on the @p surface for any of the Pointer objects of the Seat of + * the passed @p pointer when requesting a lock. If there is, an error will be + * raised. + * + * The intersection of the @p region passed with this request and the input + * region of the @p surface is used to determine where the pointer must be + * in order for the lock to activate. It is up to the compositor whether to + * warp the pointer or require some kind of user interaction for the lock + * to activate. If the @p region is null the surface input region is used. + * + * A Surface may receive pointer focus without the lock being activated. + * + * Note that while a pointer is locked, the Pointer objects of the + * corresponding seat will not emit any @link{Pointer::motion} signals, but + * relative motion events will still be emitted via @link{RelativePointer::relativeMotion}. + * Pointer axis and button events are unaffected. + * + * @param surface The Surface which should be constrained in pointer motion + * @param pointer The Pointer object for which this LockedPointer should be created + * @param region Region where to lock the pointer, if @c null the input region of the Surface is used + * @param lifetime Whether the LockedPointer becomes invalid on unlocked + * @param parent The parent object for the LockedPointer + * @returns The factored LockedPointer + **/ + LockedPointer *lockPointer(Surface *surface, Pointer *pointer, Region *region, LifeTime lifetime, QObject *parent = nullptr); + + /** + * This factory method creates a ConfinedPointer. + * + * A ConfinedPointer lets the client request to confine the + * pointer cursor to a given @p region. Creating a ConfinedPointer + * does not take effect immediately; in the future, when the compositor + * deems implementation-specific constraints are satisfied, the pointer + * confinement will be activated and the compositor sends a confined event, + * which is reported through the @link{ConfinedPointer::confined} signal. + * + * The intersection of the @p region passed and the input region of the + * @p surface is used to determine where the pointer must be + * in order for the confinement to activate. It is up to the compositor + * whether to warp the pointer or require some kind of user interaction for + * the confinement to activate. If the @p region is @c null the @p surface input + * region is used. + * + * @param surface The Surface which should be constrained in pointer motion + * @param pointer The Pointer object for which this LockedPointer should be created + * @param region Region where to confine the pointer, if @c null the input region of the Surface is used + * @param lifetime Whether the ConfinedPointer becomes invalid on unconfined + * @param parent The parent object for the ConfinedPointer + * @returns The factored ConfinedPointer + **/ + ConfinedPointer *confinePointer(Surface *surface, Pointer *pointer, Region *region, LifeTime lifetime, QObject *parent = nullptr); + + operator zwp_pointer_constraints_v1*(); + operator zwp_pointer_constraints_v1*() const; + +Q_SIGNALS: + /** + * The corresponding global for this interface on the Registry got removed. + * + * This signal gets only emitted if the PointerConstraints got created by + * Registry::createPointerConstraints + **/ + void removed(); + +private: + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for the zwp_locked_pointer_v1 interface. + * + * The LockedPointer represents a locked pointer state. + * + * While the lock of this object is active, the Pointer objects of the + * associated seat will not emit any @link{Pointer::motion} events. + * + * This object will send the signal locked when the lock is activated. + * Whenever the lock is activated, it is guaranteed that the locked surface + * will already have received pointer focus and that the pointer will be + * within the region passed to the request creating this object. + * + * To unlock the pointer, delete the object. + * + * If the compositor decides to unlock the pointer the unlocked signal is + * emitted. + * + * When unlocking, the compositor may warp the cursor position to the set + * cursor position hint. If it does, it will not result in any relative + * motion events emitted via @link{RelativePointer::relativeMotion}. + * + * If the Surface the lock was requested on is destroyed and the lock is not + * yet activated, the LockedPointer object is now defunct and must be + * deleted. + * + * @see PointerConstraints::lockedPointer + * @since 5.29 + **/ +class KWAYLANDCLIENT_EXPORT LockedPointer : public QObject +{ + Q_OBJECT +public: + virtual ~LockedPointer(); + + /** + * Setup this LockedPointer to manage the @p lockedpointer. + * When using PointerConstraints::createLockedPointer there is no need to call this + * method. + **/ + void setup(zwp_locked_pointer_v1 *lockedpointer); + /** + * @returns @c true if managing a zwp_locked_pointer_v1. + **/ + bool isValid() const; + /** + * Releases the zwp_locked_pointer_v1 interface. + * After the interface has been released the LockedPointer instance is no + * longer valid and can be setup with another zwp_locked_pointer_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this LockedPointer. + * 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 zwp_locked_pointer_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, lockedpointer, &LockedPointer::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Set the cursor position hint relative to the top left corner of the Surface. + * + * If the client is drawing its own cursor, it should update the position + * hint to the position of its own cursor. A compositor may use this + * information to warp the pointer upon unlock in order to avoid pointer + * jumps. + * + * The cursor position hint is double buffered. The new hint will only take + * effect when the associated surface gets it pending state applied. + * See @link{Surface::commit} for details. + * + * @param surfaceLocal The new position hint in surface local coordinates + * @see Surface::commit + **/ + void setCursorPositionHint(const QPointF &surfaceLocal); + + /** + * Set a new region used to lock the pointer. + * + * The new lock region is double-buffered. The new lock region will + * only take effect when the associated Surface gets its pending state + * applied. See @link{Surface::commit} for details. + * + * @param region The new lock region. + * @see Surface::commit + * @see PointerConstraints::lockPointer + **/ + void setRegion(Region *region); + + operator zwp_locked_pointer_v1*(); + operator zwp_locked_pointer_v1*() const; + +Q_SIGNALS: + /** + * Notification that the pointer lock of the seat's pointer is activated. + * @see unlocked + **/ + void locked(); + + /** + * Notification that the pointer lock of the seat's pointer is no longer + * active. If this is a oneshot pointer lock (see + * wp_pointer_constraints.lifetime) this object is now defunct and should + * be destroyed. If this is a persistent pointer lock (see + * wp_pointer_constraints.lifetime) this pointer lock may again + * reactivate in the future. + * @see locked + **/ + void unlocked(); + +private: + friend class PointerConstraints; + explicit LockedPointer(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + +/** + * @short Wrapper for zwp_confined_pointer_v1 protocol + * The confine pointer interface represents a confined pointer state. + * + * This object will send the signal 'confined' when the confinement is + * activated. Whenever the confinement is activated, it is guaranteed that + * the surface the pointer is confined to will already have received pointer + * focus and that the pointer will be within the region passed to the request + * creating this object. It is up to the compositor to decide whether this + * requires some user interaction and if the pointer will warp to within the + * passed region if outside. + * + * To unconfine the pointer, delete the object. + * + * If the compositor decides to unconfine the pointer the unconfined signal is + * emitted. The ConfinedPointer object is at this point defunct and should + * be deleted. + * @see PointerConstraints::confinePointer + * @since 5.29 + **/ +class KWAYLANDCLIENT_EXPORT ConfinedPointer : public QObject +{ + Q_OBJECT +public: + virtual ~ConfinedPointer(); + + /** + * Setup this ConfinedPointer to manage the @p confinedpointer. + * When using PointerConstraints::createConfinedPointer there is no need to call this + * method. + **/ + void setup(zwp_confined_pointer_v1 *confinedpointer); + /** + * @returns @c true if managing a zwp_confined_pointer_v1. + **/ + bool isValid() const; + /** + * Releases the zwp_confined_pointer_v1 interface. + * After the interface has been released the ConfinedPointer instance is no + * longer valid and can be setup with another zwp_confined_pointer_v1 interface. + **/ + void release(); + /** + * Destroys the data held by this ConfinedPointer. + * 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 zwp_confined_pointer_v1 interface + * once there is a new connection available. + * + * It is suggested to connect this method to ConnectionThread::connectionDied: + * @code + * connect(connection, &ConnectionThread::connectionDied, confinedpointer, &ConfinedPointer::destroy); + * @endcode + * + * @see release + **/ + void destroy(); + + /** + * Set a new region used to confine the pointer. + * + * The new confine region is double-buffered. The new confine region will + * only take effect when the associated Surface gets its pending state + * applied. See @link{Surface::commit} for details. + * + * If the confinement is active when the new confinement region is applied + * and the pointer ends up outside of newly applied region, the pointer may + * warped to a position within the new confinement region. If warped, a + * @link{Pointer::motion} signal will be emitted, but no + * @link{RelativePointer::relativeMotion} signal. + * + * The compositor may also, instead of using the new region, unconfine the + * pointer. + * + * @param region The new confine region. + * @see Surface::commit + * @see PointerConstraints::confinePointer + **/ + void setRegion(Region *region); + + operator zwp_confined_pointer_v1*(); + operator zwp_confined_pointer_v1*() const; + +Q_SIGNALS: + /** + * Notification that the pointer confinement of the seat's pointer is activated. + * @see unconfined + **/ + void confined(); + + /** + * Notification that the pointer confinement of the seat's pointer is no + * longer active. If this is a oneshot pointer confinement (see + * wp_pointer_constraints.lifetime) this object is now defunct and should + * be destroyed. If this is a persistent pointer confinement (see + * wp_pointer_constraints.lifetime) this pointer confinement may again + * reactivate in the future. + * @see confined + **/ + void unconfined(); + +private: + friend class PointerConstraints; + explicit ConfinedPointer(QObject *parent = nullptr); + class Private; + QScopedPointer d; +}; + + +} +} + +#endif diff --git a/src/client/pointerconstraints.cpp b/src/client/pointerconstraints.cpp new file mode 100644 --- /dev/null +++ b/src/client/pointerconstraints.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "pointerconstraints.h" +#include "event_queue.h" +#include "pointer.h" +#include "region.h" +#include "surface.h" +#include "wayland_pointer_p.h" + +#include + +namespace KWayland +{ +namespace Client +{ + +class PointerConstraints::Private +{ +public: + Private() = default; + + void setup(zwp_pointer_constraints_v1 *arg); + + WaylandPointer pointerconstraints; + EventQueue *queue = nullptr; +}; + +PointerConstraints::PointerConstraints(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +void PointerConstraints::Private::setup(zwp_pointer_constraints_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!pointerconstraints); + pointerconstraints.setup(arg); +} + +PointerConstraints::~PointerConstraints() +{ + release(); +} + +void PointerConstraints::setup(zwp_pointer_constraints_v1 *pointerconstraints) +{ + d->setup(pointerconstraints); +} + +void PointerConstraints::release() +{ + d->pointerconstraints.release(); +} + +void PointerConstraints::destroy() +{ + d->pointerconstraints.destroy(); +} + +PointerConstraints::operator zwp_pointer_constraints_v1*() { + return d->pointerconstraints; +} + +PointerConstraints::operator zwp_pointer_constraints_v1*() const { + return d->pointerconstraints; +} + +bool PointerConstraints::isValid() const +{ + return d->pointerconstraints.isValid(); +} + +void PointerConstraints::setEventQueue(EventQueue *queue) +{ + d->queue = queue; +} + +EventQueue *PointerConstraints::eventQueue() +{ + return d->queue; +} + +LockedPointer *PointerConstraints::lockPointer(Surface *surface, Pointer *pointer, Region *region, LifeTime lifetime, QObject *parent) +{ + Q_ASSERT(isValid()); + auto p = new LockedPointer(parent); + zwp_pointer_constraints_v1_lifetime lf; + switch (lifetime) { + case LifeTime::OneShot: + lf = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + break; + case LifeTime::Persistent: + lf = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT; + break; + default: + Q_UNREACHABLE(); + break; + } + wl_region *wr = nullptr; + if (region) { + wr = *region; + } + auto w = zwp_pointer_constraints_v1_lock_pointer(d->pointerconstraints, *surface, *pointer, wr, lf); + if (d->queue) { + d->queue->addProxy(w); + } + p->setup(w); + return p; +} + +ConfinedPointer *PointerConstraints::confinePointer(Surface *surface, Pointer *pointer, Region *region, LifeTime lifetime, QObject *parent) +{ + Q_ASSERT(isValid()); + auto p = new ConfinedPointer(parent); + zwp_pointer_constraints_v1_lifetime lf; + switch (lifetime) { + case LifeTime::OneShot: + lf = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + break; + case LifeTime::Persistent: + lf = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT; + break; + default: + Q_UNREACHABLE(); + break; + } + wl_region *wr = nullptr; + if (region) { + wr = *region; + } + auto w = zwp_pointer_constraints_v1_confine_pointer(d->pointerconstraints, *surface, *pointer, wr, lf); + if (d->queue) { + d->queue->addProxy(w); + } + p->setup(w); + return p; +} + +class LockedPointer::Private +{ +public: + Private(LockedPointer *q); + + void setup(zwp_locked_pointer_v1 *arg); + + WaylandPointer lockedpointer; + +private: + LockedPointer *q; + +private: + static void lockedCallback(void *data, zwp_locked_pointer_v1 *zwp_locked_pointer_v1); + static void unlockedCallback(void *data, zwp_locked_pointer_v1 *zwp_locked_pointer_v1); + + static const zwp_locked_pointer_v1_listener s_listener; +}; + +const zwp_locked_pointer_v1_listener LockedPointer::Private::s_listener = { + lockedCallback, + unlockedCallback +}; + +void LockedPointer::Private::lockedCallback(void *data, zwp_locked_pointer_v1 *zwp_locked_pointer_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->lockedpointer == zwp_locked_pointer_v1); + emit p->q->locked(); +} + +void LockedPointer::Private::unlockedCallback(void *data, zwp_locked_pointer_v1 *zwp_locked_pointer_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->lockedpointer == zwp_locked_pointer_v1); + emit p->q->unlocked(); +} + +LockedPointer::Private::Private(LockedPointer *q) + : q(q) +{ +} + +LockedPointer::LockedPointer(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void LockedPointer::Private::setup(zwp_locked_pointer_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!lockedpointer); + lockedpointer.setup(arg); + zwp_locked_pointer_v1_add_listener(lockedpointer, &s_listener, this); +} + +LockedPointer::~LockedPointer() +{ + release(); +} + +void LockedPointer::setup(zwp_locked_pointer_v1 *lockedpointer) +{ + d->setup(lockedpointer); +} + +void LockedPointer::release() +{ + d->lockedpointer.release(); +} + +void LockedPointer::destroy() +{ + d->lockedpointer.destroy(); +} + +LockedPointer::operator zwp_locked_pointer_v1*() { + return d->lockedpointer; +} + +LockedPointer::operator zwp_locked_pointer_v1*() const { + return d->lockedpointer; +} + +bool LockedPointer::isValid() const +{ + return d->lockedpointer.isValid(); +} + +void LockedPointer::setCursorPositionHint(const QPointF &surfaceLocal) +{ + Q_ASSERT(isValid()); + zwp_locked_pointer_v1_set_cursor_position_hint(d->lockedpointer, wl_fixed_from_double(surfaceLocal.x()), wl_fixed_from_double(surfaceLocal.y())); +} + +void LockedPointer::setRegion(Region *region) +{ + Q_ASSERT(isValid()); + zwp_locked_pointer_v1_set_region(d->lockedpointer, *region); +} + +class ConfinedPointer::Private +{ +public: + Private(ConfinedPointer *q); + + void setup(zwp_confined_pointer_v1 *arg); + + WaylandPointer confinedpointer; + +private: + ConfinedPointer *q; + +private: + static void confinedCallback(void *data, zwp_confined_pointer_v1 *zwp_confined_pointer_v1); + static void unconfinedCallback(void *data, zwp_confined_pointer_v1 *zwp_confined_pointer_v1); + + static const zwp_confined_pointer_v1_listener s_listener; +}; + +const zwp_confined_pointer_v1_listener ConfinedPointer::Private::s_listener = { + confinedCallback, + unconfinedCallback +}; + +void ConfinedPointer::Private::confinedCallback(void *data, zwp_confined_pointer_v1 *zwp_confined_pointer_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->confinedpointer == zwp_confined_pointer_v1); + emit p->q->confined(); +} + +void ConfinedPointer::Private::unconfinedCallback(void *data, zwp_confined_pointer_v1 *zwp_confined_pointer_v1) +{ + auto p = reinterpret_cast(data); + Q_ASSERT(p->confinedpointer == zwp_confined_pointer_v1); + emit p->q->unconfined(); +} + +ConfinedPointer::Private::Private(ConfinedPointer *q) + : q(q) +{ +} + +ConfinedPointer::ConfinedPointer(QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ +} + +void ConfinedPointer::Private::setup(zwp_confined_pointer_v1 *arg) +{ + Q_ASSERT(arg); + Q_ASSERT(!confinedpointer); + confinedpointer.setup(arg); + zwp_confined_pointer_v1_add_listener(confinedpointer, &s_listener, this); +} + +ConfinedPointer::~ConfinedPointer() +{ + release(); +} + +void ConfinedPointer::setup(zwp_confined_pointer_v1 *confinedpointer) +{ + d->setup(confinedpointer); +} + +void ConfinedPointer::release() +{ + d->confinedpointer.release(); +} + +void ConfinedPointer::destroy() +{ + d->confinedpointer.destroy(); +} + +ConfinedPointer::operator zwp_confined_pointer_v1*() { + return d->confinedpointer; +} + +ConfinedPointer::operator zwp_confined_pointer_v1*() const { + return d->confinedpointer; +} + +bool ConfinedPointer::isValid() const +{ + return d->confinedpointer.isValid(); +} + +void ConfinedPointer::setRegion(Region *region) +{ + Q_ASSERT(isValid()); + zwp_confined_pointer_v1_set_region(d->confinedpointer, *region); +} + +} +} diff --git a/src/client/protocols/pointer-constraints-unstable-v1.xml b/src/client/protocols/pointer-constraints-unstable-v1.xml new file mode 100644 --- /dev/null +++ b/src/client/protocols/pointer-constraints-unstable-v1.xml @@ -0,0 +1,344 @@ + + + + + Copyright © 2014 Jonas Ådahl + Copyright © 2015 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol specifies a set of interfaces used for adding constraints to + the motion of a pointer. Possible constraints include confining pointer + motions to a given region, or locking it to its current position. + + In order to constrain the pointer, a client must first bind the global + interface "wp_pointer_constraints" which, if a compositor supports pointer + constraints, is exposed by the registry. Using the bound global object, the + client uses the request that corresponds to the type of constraint it wants + to make. See wp_pointer_constraints for more details. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + + + + + The global interface exposing pointer constraining functionality. It + exposes two requests: lock_pointer for locking the pointer to its + position, and confine_pointer for locking the pointer to a region. + + The lock_pointer and confine_pointer requests create the objects + wp_locked_pointer and wp_confined_pointer respectively, and the client can + use these objects to interact with the lock. + + For any surface, only one lock or confinement may be active across all + wl_pointer objects of the same seat. If a lock or confinement is requested + when another lock or confinement is active or requested on the same surface + and with any of the wl_pointer objects of the same seat, an + 'already_constrained' error will be raised. + + + + + These errors can be emitted in response to wp_pointer_constraints + requests. + + + + + + + These values represent different lifetime semantics. They are passed + as arguments to the factory requests to specify how the constraint + lifetimes should be managed. + + + + A oneshot pointer constraint will never reactivate once it has been + deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + A persistent pointer constraint may again reactivate once it has + been deactivated. See the corresponding deactivation event + (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for + details. + + + + + + + Used by the client to notify the server that it will no longer use this + pointer constraints object. + + + + + + The lock_pointer request lets the client request to disable movements of + the virtual pointer (i.e. the cursor), effectively locking the pointer + to a position. This request may not take effect immediately; in the + future, when the compositor deems implementation-specific constraints + are satisfied, the pointer lock will be activated and the compositor + sends a locked event. + + The protocol provides no guarantee that the constraints are ever + satisfied, and does not require the compositor to send an error if the + constraints cannot ever be satisfied. It is thus possible to request a + lock that will never activate. + + There may not be another pointer constraint of any kind requested or + active on the surface for any of the wl_pointer objects of the seat of + the passed pointer when requesting a lock. If there is, an error will be + raised. See general pointer lock documentation for more details. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the lock to activate. It is up to the compositor whether to + warp the pointer or require some kind of user interaction for the lock + to activate. If the region is null the surface input region is used. + + A surface may receive pointer focus without the lock being activated. + + The request creates a new object wp_locked_pointer which is used to + interact with the lock as well as receive updates about its state. See + the the description of wp_locked_pointer for further information. + + Note that while a pointer is locked, the wl_pointer objects of the + corresponding seat will not emit any wl_pointer.motion events, but + relative motion events will still be emitted via wp_relative_pointer + objects of the same seat. wl_pointer.axis and wl_pointer.button events + are unaffected. + + + + + + + + + + + + The confine_pointer request lets the client request to confine the + pointer cursor to a given region. This request may not take effect + immediately; in the future, when the compositor deems implementation- + specific constraints are satisfied, the pointer confinement will be + activated and the compositor sends a confined event. + + The intersection of the region passed with this request and the input + region of the surface is used to determine where the pointer must be + in order for the confinement to activate. It is up to the compositor + whether to warp the pointer or require some kind of user interaction for + the confinement to activate. If the region is null the surface input + region is used. + + The request will create a new object wp_confined_pointer which is used + to interact with the confinement as well as receive updates about its + state. See the the description of wp_confined_pointer for further + information. + + + + + + + + + + + + + The wp_locked_pointer interface represents a locked pointer state. + + While the lock of this object is active, the wl_pointer objects of the + associated seat will not emit any wl_pointer.motion events. + + This object will send the event 'locked' when the lock is activated. + Whenever the lock is activated, it is guaranteed that the locked surface + will already have received pointer focus and that the pointer will be + within the region passed to the request creating this object. + + To unlock the pointer, send the destroy request. This will also destroy + the wp_locked_pointer object. + + If the compositor decides to unlock the pointer the unlocked event is + sent. See wp_locked_pointer.unlock for details. + + When unlocking, the compositor may warp the cursor position to the set + cursor position hint. If it does, it will not result in any relative + motion events emitted via wp_relative_pointer. + + If the surface the lock was requested on is destroyed and the lock is not + yet activated, the wp_locked_pointer object is now defunct and must be + destroyed. + + + + + Destroy the locked pointer object. If applicable, the compositor will + unlock the pointer. + + + + + + Set the cursor position hint relative to the top left corner of the + surface. + + If the client is drawing its own cursor, it should update the position + hint to the position of its own cursor. A compositor may use this + information to warp the pointer upon unlock in order to avoid pointer + jumps. + + The cursor position hint is double buffered. The new hint will only take + effect when the associated surface gets it pending state applied. See + wl_surface.commit for details. + + + + + + + + + Set a new region used to lock the pointer. + + The new lock region is double-buffered. The new lock region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + For details about the lock region, see wp_locked_pointer. + + + + + + + + Notification that the pointer lock of the seat's pointer is activated. + + + + + + Notification that the pointer lock of the seat's pointer is no longer + active. If this is a oneshot pointer lock (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer lock (see + wp_pointer_constraints.lifetime) this pointer lock may again + reactivate in the future. + + + + + + + The wp_confined_pointer interface represents a confined pointer state. + + This object will send the event 'confined' when the confinement is + activated. Whenever the confinement is activated, it is guaranteed that + the surface the pointer is confined to will already have received pointer + focus and that the pointer will be within the region passed to the request + creating this object. It is up to the compositor to decide whether this + requires some user interaction and if the pointer will warp to within the + passed region if outside. + + To unconfine the pointer, send the destroy request. This will also destroy + the wp_confined_pointer object. + + If the compositor decides to unconfine the pointer the unconfined event is + sent. The wp_confined_pointer object is at this point defunct and should + be destroyed. + + + + + Destroy the confined pointer object. If applicable, the compositor will + unconfine the pointer. + + + + + + Set a new region used to confine the pointer. + + The new confine region is double-buffered. The new confine region will + only take effect when the associated surface gets its pending state + applied. See wl_surface.commit for details. + + If the confinement is active when the new confinement region is applied + and the pointer ends up outside of newly applied region, the pointer may + warped to a position within the new confinement region. If warped, a + wl_pointer.motion event will be emitted, but no + wp_relative_pointer.relative_motion event. + + The compositor may also, instead of using the new region, unconfine the + pointer. + + For details about the confine region, see wp_confined_pointer. + + + + + + + + Notification that the pointer confinement of the seat's pointer is + activated. + + + + + + Notification that the pointer confinement of the seat's pointer is no + longer active. If this is a oneshot pointer confinement (see + wp_pointer_constraints.lifetime) this object is now defunct and should + be destroyed. If this is a persistent pointer confinement (see + wp_pointer_constraints.lifetime) this pointer confinement may again + reactivate in the future. + + + + + diff --git a/src/client/registry.h b/src/client/registry.h --- a/src/client/registry.h +++ b/src/client/registry.h @@ -52,6 +52,7 @@ struct xdg_shell; struct zwp_relative_pointer_manager_v1; struct zwp_pointer_gestures_v1; +struct zwp_pointer_constraints_v1; namespace KWayland { @@ -71,6 +72,7 @@ class Output; class PlasmaShell; class PlasmaWindowManagement; +class PointerConstraints; class PointerGestures; class Seat; class ShadowManager; @@ -147,7 +149,8 @@ TextInputManagerUnstableV2, ///< Refers to zwp_text_input_manager_v2, @since 5.23 XdgShellUnstableV5, ///< Refers to xdg_shell (unstable version 5), @since 5.25 RelativePointerManagerUnstableV1, ///< Refers to zwp_relative_pointer_manager_v1, @since 5.28 - PointerGesturesUnstableV1 /// Refers to zwp_pointer_gestures_v1, @since 5.29 + PointerGesturesUnstableV1, ///< Refers to zwp_pointer_gestures_v1, @since 5.29 + PointerConstraintsUnstableV1 ///< Refers to zwp_pointer_constraints_v1, @since 5.29 }; explicit Registry(QObject *parent = nullptr); virtual ~Registry(); @@ -502,6 +505,16 @@ * @since 5.29 **/ zwp_pointer_gestures_v1 *bindPointerGesturesUnstableV1(uint32_t name, uint32_t version) const; + /** + * Binds the zwp_pointer_constraints_v1 with @p name and @p version. + * If the @p name does not exist or is not for the pointer constraints interface in unstable version 1, + * @c null will be returned. + * + * Prefer using createPointerConstraints instead. + * @see createPointerConstraints + * @since 5.29 + **/ + zwp_pointer_constraints_v1 *bindPointerConstraintsUnstableV1(uint32_t name, uint32_t version) const; ///@} /** @@ -894,6 +907,24 @@ * @since 5.29 **/ PointerGestures *createPointerGestures(quint32 name, quint32 version, QObject *parent = nullptr); + /** + * Creates a PointerConstraints and sets it up to manage the interface identified by + * @p name and @p version. + * + * This factory method supports the following interfaces: + * @li zwp_pointer_constraints_v1 + * + * If @p name is for one of the supported interfaces the corresponding manager will be created, + * otherwise @c null will be returned. + * + * @param name The name of the interface to bind + * @param version The version of the interface to use + * @param parent The parent for the PointerConstraints + * + * @returns The created PointerConstraints + * @since 5.29 + **/ + PointerConstraints *createPointerConstraints(quint32 name, quint32 version, QObject *parent = nullptr); ///@} /** @@ -1076,6 +1107,13 @@ * @since 5.29 **/ void pointerGesturesUnstableV1Announced(quint32 name, quint32 version); + /** + * Emitted whenever a zwp_pointer_constraints_v1 interface gets announced. + * @param name The name for the announced interface + * @param version The maximum supported version of the announced interface + * @since 5.29 + **/ + void pointerConstraintsUnstableV1Announced(quint32 name, quint32 version); ///@} /** * @name Interface removed signals. @@ -1223,6 +1261,12 @@ * @since 5.29 **/ void pointerGesturesUnstableV1Removed(quint32 name); + /** + * Emitted whenever a zwp_pointer_constraints_v1 interface gets removed. + * @param name The name for the removed interface + * @since 5.29 + **/ + void pointerConstraintsUnstableV1Removed(quint32 name); ///@} /** * Generic announced signal which gets emitted whenever an interface gets diff --git a/src/client/registry.cpp b/src/client/registry.cpp --- a/src/client/registry.cpp +++ b/src/client/registry.cpp @@ -33,6 +33,7 @@ #include "output.h" #include "plasmashell.h" #include "plasmawindowmanagement.h" +#include "pointerconstraints.h" #include "pointergestures.h" #include "seat.h" #include "shadow.h" @@ -70,6 +71,7 @@ #include #include #include +#include /***** * How to add another interface: @@ -272,6 +274,13 @@ &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 }} }; @@ -569,6 +578,7 @@ BIND(XdgShellUnstableV5, xdg_shell) BIND(RelativePointerManagerUnstableV1, zwp_relative_pointer_manager_v1) BIND(PointerGesturesUnstableV1, zwp_pointer_gestures_v1) +BIND(PointerConstraintsUnstableV1, zwp_pointer_constraints_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) @@ -668,6 +678,16 @@ } } +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; + } +} + namespace { static const wl_interface *wlInterface(Registry::Interface interface) { diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -21,6 +21,8 @@ pointer_interface.cpp plasmashell_interface.cpp plasmawindowmanagement_interface.cpp + pointerconstraints_interface.cpp + pointerconstraints_interface_v1.cpp pointergestures_interface.cpp pointergestures_interface_v1.cpp qtsurfaceextension_interface.cpp @@ -134,6 +136,11 @@ BASENAME pointer-gestures-unstable-v1 ) +ecm_add_wayland_server_protocol(SERVER_LIB_SRCS + PROTOCOL ${KWayland_SOURCE_DIR}/src/client/protocols/pointer-constraints-unstable-v1.xml + BASENAME pointer-constraints-unstable-v1 +) + add_library(KF5WaylandServer ${SERVER_LIB_SRCS}) generate_export_header(KF5WaylandServer BASE_NAME @@ -185,6 +192,7 @@ outputmanagement_interface.h output_interface.h pointer_interface.h + pointerconstraints_interface.h pointergestures_interface.h plasmashell_interface.h plasmawindowmanagement_interface.h diff --git a/src/server/display.h b/src/server/display.h --- a/src/server/display.h +++ b/src/server/display.h @@ -78,6 +78,8 @@ class RelativePointerManagerInterface; enum class PointerGesturesInterfaceVersion; class PointerGesturesInterface; +enum class PointerConstraintsInterfaceVersion; +class PointerConstraintsInterface; /** * @brief Class holding the Wayland server display loop. @@ -210,6 +212,14 @@ PointerGesturesInterface *createPointerGestures(const PointerGesturesInterfaceVersion &version, QObject *parent = nullptr); /** + * Creates the PointerConstraintsInterface in interface @p version + * + * @returns The created manager object + * @since 5.29 + **/ + PointerConstraintsInterface *createPointerConstraints(const PointerConstraintsInterfaceVersion &version, QObject *parent = nullptr); + + /** * Gets the ClientConnection for the given @p client. * If there is no ClientConnection yet for the given @p client, it will be created. * @param client The native client for which the ClientConnection is retrieved diff --git a/src/server/display.cpp b/src/server/display.cpp --- a/src/server/display.cpp +++ b/src/server/display.cpp @@ -30,6 +30,7 @@ #include "output_interface.h" #include "plasmashell_interface.h" #include "plasmawindowmanagement_interface.h" +#include "pointerconstraints_interface_p.h" #include "pointergestures_interface_p.h" #include "qtsurfaceextension_interface.h" #include "seat_interface.h" @@ -395,6 +396,18 @@ return p; } +PointerConstraintsInterface *Display::createPointerConstraints(const PointerConstraintsInterfaceVersion &version, QObject *parent) +{ + PointerConstraintsInterface *p = nullptr; + switch (version) { + case PointerConstraintsInterfaceVersion::UnstableV1: + p = new PointerConstraintsUnstableV1Interface(this, parent); + break; + } + connect(this, &Display::aboutToTerminate, p, [p] { delete p; }); + return p; +} + void Display::createShm() { Q_ASSERT(d->display); diff --git a/src/server/pointer_interface.cpp b/src/server/pointer_interface.cpp --- a/src/server/pointer_interface.cpp +++ b/src/server/pointer_interface.cpp @@ -19,6 +19,7 @@ *********************************************************************/ #include "pointer_interface.h" #include "pointer_interface_p.h" +#include "pointerconstraints_interface.h" #include "pointergestures_interface_p.h" #include "resource_p.h" #include "relativepointer_interface_p.h" @@ -228,6 +229,9 @@ return; } if (d->focusedSurface && d->resource) { + if (!d->focusedSurface->lockedPointer().isNull() && d->focusedSurface->lockedPointer()->isLocked()) { + return; + } const QPointF pos = d->seat->focusedPointerSurfaceTransformation().map(d->seat->pointerPos()); auto targetSurface = d->focusedSurface->surfaceAt(pos); if (!targetSurface) { diff --git a/src/server/pointerconstraints_interface.h b/src/server/pointerconstraints_interface.h new file mode 100644 --- /dev/null +++ b/src/server/pointerconstraints_interface.h @@ -0,0 +1,268 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_POINTERCONSTRAINTS_H +#define KWAYLAND_SERVER_POINTERCONSTRAINTS_H + +#include "global.h" +#include "resource.h" + +#include + +#include + +namespace KWayland +{ +namespace Server +{ + +class Display; +class SurfaceInterface; + +/** + * Enum describing the interface versions the PointerConstraintsInterface can support. + * + * @since 5.29 + **/ +enum class PointerConstraintsInterfaceVersion { + /** + * zwp_pointer_constraints_v1 + **/ + UnstableV1 +}; + +/** + * Manager object to create pointer constraints. + * + * To create this manager use @link{Display::createPointerConstraints} + * + * @see ConfinedPointerInterface + * @see LockedPointerInterface + * @see Display::createPointerConstraints + * @since 5.29 + **/ +class KWAYLANDSERVER_EXPORT PointerConstraintsInterface : public Global +{ + Q_OBJECT +public: + virtual ~PointerConstraintsInterface(); + + /** + * @returns The interface version used by this PointerConstraintsInterface + **/ + PointerConstraintsInterfaceVersion interfaceVersion() const; + +protected: + class Private; + explicit PointerConstraintsInterface(Private *d, QObject *parent = nullptr); + +private: + Private *d_func() const; +}; + +/** + * The LockedPointerInterface lets the client request to disable movements of + * the virtual pointer (i.e. the cursor), effectively locking the pointer + * to a position. + * + * It is up to the compositor whether the lock gets activated. + * To activate it needs to use @link{LockedPointerInterface::setLocked}. + * The compositor needs to ensure that the SurfaceInterface has pointer focus + * and that the pointer is inside the @link{LockedPointerInterface::region} when + * it activates the lock. + * + * While the lock is active the PointerInterface does no longer emit pointer motion + * events, but still emits relative pointer motion events. + * + * @since 5.29 + **/ +class KWAYLANDSERVER_EXPORT LockedPointerInterface : public Resource +{ + Q_OBJECT +public: + + virtual ~LockedPointerInterface(); + + /** + * @returns The interface version used by this LockedPointerInterface + **/ + PointerConstraintsInterfaceVersion interfaceVersion() const; + + enum class LifeTime { + OneShot, + Persistent + }; + + LifeTime lifeTime() const; + + /** + * The intersection of this region and the input region of the SurfaceInterface is used + * to determine where the pointer must be in order for the lock to activate. + * It is up to the compositor whether to warp the pointer or require some kind of + * user interaction for the lock to activate. + * + * If the region is empty the SurfaceInterface input region is used. + * + * @see regionChanged + * @see SurfaceInterface::input + **/ + QRegion region() const; + + /** + * Whether the Compositor set this pointer lock to be active. + * @see setLocked + * @see lockedChanged + **/ + bool isLocked() const; + + /** + * Activates or deactivates the lock. + * + * A pointer lock can only be activated if the SurfaceInterface + * this LockedPointerInterface was created for has pointer focus + * and the pointer is inside the @link{region}. + * + * @param locked Whether the lock should be active + * @see isLocked + * @see lockedChanged + **/ + void setLocked(bool locked); + +Q_SIGNALS: + /** + * Emitted whenever the region changes. + * This happens when the parent SurfaceInterface gets committed + * @see region + **/ + void regionChanged(); + + /** + * Emitted whenever the @link{isLocked} state changes. + * @see isLocked + * @see setLocked + **/ + void lockedChanged(); + +protected: + class Private; + explicit LockedPointerInterface(Private *p, QObject *parent = nullptr); + +private: + Private *d_func() const; + friend class SurfaceInterface; +}; + +/** + * + * The ConfinedPointerInterface gets installed on a SurfaceInterface. + * The confinement indicates that the SurfaceInterface wants to confine the + * pointer to a region of the SurfaceInterface. + * + * It is up to the compositor whether the confinement gets activated. + * To activate it needs to use @link{ConfinedPointerInterface::setConfined}. + * The compositor needs to ensure that the SurfaceInterface has pointer focus + * and that the pointer is inside the @link{ConfinedPointerInterface::region} when + * it activates the confinement. + * + * From client side the confinement gets deactivated by destroying the ConfinedPointerInterface. + * From compositor side the confinement can be deactivated by setting + * @link{ConfinedPointerInterface::setConfined} to @c false. + * + * @since 5.29 + **/ +class KWAYLANDSERVER_EXPORT ConfinedPointerInterface : public Resource +{ + Q_OBJECT +public: + + virtual ~ConfinedPointerInterface(); + + /** + * @returns The interface version used by this ConfinedPointerInterface + **/ + PointerConstraintsInterfaceVersion interfaceVersion() const; + + enum class LifeTime { + OneShot, + Persistent + }; + + LifeTime lifeTime() const; + + /** + * The intersection of this region and the input region of the SurfaceInterface is used + * to determine where the pointer must be in order for the confinement to activate. + * It is up to the compositor whether to warp the pointer or require some kind of + * user interaction for the confinement to activate. + * + * If the region is empty the SurfaceInterface input region is used. + * + * @see regionChanged + * @see SurfaceInterface::input + **/ + QRegion region() const; + + /** + * Whether the Compositor set this pointer confinement to be active. + * @see setConfined + * @see confinedChanged + **/ + bool isConfined() const; + + /** + * Activates or deactivates the confinement. + * + * A pointer confinement can only be activated if the SurfaceInterface + * this ConfinedPointerInterface was created for has pointer focus + * and the pointer is inside the @link{region}. + * + * @param confined Whether the confinement should be active + * @see isConfined + * @see confinedChanged + **/ + void setConfined(bool confined); + +Q_SIGNALS: + /** + * Emitted whenever the region changes. + * This happens when the parent SurfaceInterface gets committed + * @see region + **/ + void regionChanged(); + + /** + * Emitted whenever the @link{isConfined} state changes. + * @see isConfined + * @see setConfined + **/ + void confinedChanged(); + +protected: + class Private; + explicit ConfinedPointerInterface(Private *p, QObject *parent = nullptr); + +private: + Private *d_func() const; + friend class SurfaceInterface; +}; + +} +} + +#endif diff --git a/src/server/pointerconstraints_interface.cpp b/src/server/pointerconstraints_interface.cpp new file mode 100644 --- /dev/null +++ b/src/server/pointerconstraints_interface.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "pointerconstraints_interface_p.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +PointerConstraintsInterface::Private::Private(PointerConstraintsInterfaceVersion interfaceVersion, PointerConstraintsInterface *q, Display *d, const wl_interface *interface, quint32 version) + : Global::Private(d, interface, version) + , interfaceVersion(interfaceVersion) + , q(q) +{ +} + +PointerConstraintsInterface::PointerConstraintsInterface(Private *d, QObject *parent) + : Global(d, parent) +{ +} + +PointerConstraintsInterface::~PointerConstraintsInterface() = default; + +PointerConstraintsInterfaceVersion PointerConstraintsInterface::interfaceVersion() const +{ + Q_D(); + return d->interfaceVersion; +} + +PointerConstraintsInterface::Private *PointerConstraintsInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +LockedPointerInterface::Private::Private(PointerConstraintsInterfaceVersion interfaceVersion, LockedPointerInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation) + : Resource::Private(q, c, parentResource, interface, implementation) + , interfaceVersion(interfaceVersion) +{ +} + +LockedPointerInterface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +void LockedPointerInterface::Private::commit() +{ + if (!regionIsSet) { + return; + } + region = pendingRegion; + pendingRegion = QRegion(); + regionIsSet = false; + emit q_func()->regionChanged(); +} + +LockedPointerInterface::LockedPointerInterface(Private *p, QObject *parent) + : Resource(p, parent) +{ + connect(this, &LockedPointerInterface::unbound, this, std::bind(&LockedPointerInterface::setLocked, this, false)); +} + +LockedPointerInterface::~LockedPointerInterface() = default; + +PointerConstraintsInterfaceVersion LockedPointerInterface::interfaceVersion() const +{ + Q_D(); + return d->interfaceVersion; +} + +LockedPointerInterface::LifeTime LockedPointerInterface::lifeTime() const +{ + Q_D(); + return d->lifeTime; +} + +QRegion LockedPointerInterface::region() const +{ + Q_D(); + return d->region; +} + +bool LockedPointerInterface::isLocked() const +{ + Q_D(); + return d->locked; +} + +void LockedPointerInterface::setLocked(bool locked) +{ + Q_D(); + if (locked == d->locked) { + return; + } + d->locked = locked; + d->updateLocked(); + emit lockedChanged(); +} + +LockedPointerInterface::Private *LockedPointerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +ConfinedPointerInterface::Private::Private(PointerConstraintsInterfaceVersion interfaceVersion, ConfinedPointerInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation) + : Resource::Private(q, c, parentResource, interface, implementation) + , interfaceVersion(interfaceVersion) +{ +} + +ConfinedPointerInterface::Private::~Private() +{ + if (resource) { + wl_resource_destroy(resource); + resource = nullptr; + } +} + +void ConfinedPointerInterface::Private::commit() +{ + if (!regionIsSet) { + return; + } + region = pendingRegion; + pendingRegion = QRegion(); + regionIsSet = false; + emit q_func()->regionChanged(); +} + +ConfinedPointerInterface::ConfinedPointerInterface(Private *p, QObject *parent) + : Resource(p, parent) +{ + connect(this, &ConfinedPointerInterface::unbound, this, std::bind(&ConfinedPointerInterface::setConfined, this, false)); +} + +ConfinedPointerInterface::~ConfinedPointerInterface() = default; + +PointerConstraintsInterfaceVersion ConfinedPointerInterface::interfaceVersion() const +{ + Q_D(); + return d->interfaceVersion; +} + +ConfinedPointerInterface::LifeTime ConfinedPointerInterface::lifeTime() const +{ + Q_D(); + return d->lifeTime; +} + +QRegion ConfinedPointerInterface::region() const +{ + Q_D(); + return d->region; +} + +bool ConfinedPointerInterface::isConfined() const +{ + Q_D(); + return d->confined; +} + +void ConfinedPointerInterface::setConfined(bool confined) +{ + Q_D(); + if (confined == d->confined) { + return; + } + d->confined = confined; + d->updateConfined(); + emit confinedChanged(); +} + +ConfinedPointerInterface::Private *ConfinedPointerInterface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +} +} diff --git a/src/server/pointerconstraints_interface_p.h b/src/server/pointerconstraints_interface_p.h new file mode 100644 --- /dev/null +++ b/src/server/pointerconstraints_interface_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#ifndef KWAYLAND_SERVER_POINTERCONSTRAINTS_P_H +#define KWAYLAND_SERVER_POINTERCONSTRAINTS_P_H +#include "pointerconstraints_interface.h" +#include "global_p.h" +#include "resource_p.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class PointerConstraintsInterface::Private : public Global::Private +{ +public: + PointerConstraintsInterfaceVersion interfaceVersion; + +protected: + Private(PointerConstraintsInterfaceVersion interfaceVersion, PointerConstraintsInterface *q, Display *d, const wl_interface *interface, quint32 version); + PointerConstraintsInterface *q; +}; + +class PointerConstraintsUnstableV1Interface : public PointerConstraintsInterface +{ + Q_OBJECT +public: + explicit PointerConstraintsUnstableV1Interface(Display *display, QObject *parent = nullptr); + virtual ~PointerConstraintsUnstableV1Interface(); + +private: + class Private; +}; + +class LockedPointerInterface::Private : public Resource::Private +{ +public: + ~Private(); + + virtual void updateLocked() = 0; + void commit(); + + PointerConstraintsInterfaceVersion interfaceVersion; + + LifeTime lifeTime; + QRegion region; + bool locked = false; + +protected: + Private(PointerConstraintsInterfaceVersion interfaceVersion, LockedPointerInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation); + + QRegion pendingRegion; + bool regionIsSet = false; + +private: + LockedPointerInterface *q_func() { + return reinterpret_cast(q); + } +}; + +class LockedPointerUnstableV1Interface : public LockedPointerInterface +{ + Q_OBJECT +public: + explicit LockedPointerUnstableV1Interface(PointerConstraintsUnstableV1Interface *parent, wl_resource *parentResource); + virtual ~LockedPointerUnstableV1Interface(); + +private: + class Private; + Private *d_func() const; + friend class PointerConstraintsUnstableV1Interface; +}; + +class ConfinedPointerInterface::Private : public Resource::Private +{ +public: + ~Private(); + + virtual void updateConfined() = 0; + void commit(); + + PointerConstraintsInterfaceVersion interfaceVersion; + + LifeTime lifeTime; + QRegion region; + + bool confined = false; + +protected: + Private(PointerConstraintsInterfaceVersion interfaceVersion, ConfinedPointerInterface *q, Global *c, wl_resource *parentResource, const wl_interface *interface, const void *implementation); + + QRegion pendingRegion; + bool regionIsSet = false; + +private: + ConfinedPointerInterface *q_func() { + return reinterpret_cast(q); + } +}; + +class ConfinedPointerUnstableV1Interface : public ConfinedPointerInterface +{ + Q_OBJECT +public: + explicit ConfinedPointerUnstableV1Interface(PointerConstraintsUnstableV1Interface *parent, wl_resource *parentResource); + virtual ~ConfinedPointerUnstableV1Interface(); + +private: + class Private; + Private *d_func() const; + friend class PointerConstraintsUnstableV1Interface; +}; + +} +} + +#endif diff --git a/src/server/pointerconstraints_interface_v1.cpp b/src/server/pointerconstraints_interface_v1.cpp new file mode 100644 --- /dev/null +++ b/src/server/pointerconstraints_interface_v1.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +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 . +****************************************************************************/ +#include "pointerconstraints_interface_p.h" +#include "display.h" +#include "pointer_interface.h" +#include "region_interface.h" +#include "surface_interface_p.h" + +#include + +namespace KWayland +{ +namespace Server +{ + +class PointerConstraintsUnstableV1Interface::Private : public PointerConstraintsInterface::Private +{ +public: + Private(PointerConstraintsUnstableV1Interface *q, Display *d); + +private: + void bind(wl_client *client, uint32_t version, uint32_t id) override; + + template + void createConstraint(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *pointer, wl_resource *region, uint32_t lifetime); + + static void unbind(wl_resource *resource); + static Private *cast(wl_resource *r) { + return reinterpret_cast(wl_resource_get_user_data(r)); + } + + static void destroyCallback(wl_client *client, wl_resource *resource); + static void lockPointerCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface, wl_resource * pointer, wl_resource * region, uint32_t lifetime); + static void confinePointerCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource * surface, wl_resource * pointer, wl_resource * region, uint32_t lifetime); + + PointerConstraintsUnstableV1Interface *q; + static const struct zwp_pointer_constraints_v1_interface s_interface; + static const quint32 s_version; +}; + +class LockedPointerUnstableV1Interface::Private : public LockedPointerInterface::Private +{ +public: + Private(LockedPointerUnstableV1Interface *q, PointerConstraintsUnstableV1Interface *c, wl_resource *parentResource); + ~Private(); + + void updateLocked() override; + +private: + static void setCursorPositionHintCallback(wl_client *client, wl_resource *resource, wl_fixed_t surface_x, wl_fixed_t surface_y); + static void setRegionCallback(wl_client *client, wl_resource *resource, wl_resource * region); + + LockedPointerUnstableV1Interface *q_func() { + return reinterpret_cast(q); + } + + static const struct zwp_locked_pointer_v1_interface s_interface; +}; + +const quint32 PointerConstraintsUnstableV1Interface::Private::s_version = 1; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zwp_pointer_constraints_v1_interface PointerConstraintsUnstableV1Interface::Private::s_interface = { + destroyCallback, + lockPointerCallback, + confinePointerCallback +}; +#endif + +void PointerConstraintsUnstableV1Interface::Private::destroyCallback(wl_client *client, wl_resource *resource) +{ + Q_UNUSED(client) + wl_resource_destroy(resource); +} + +template +void PointerConstraintsUnstableV1Interface::Private::createConstraint(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *pointer, wl_resource *region, uint32_t lifetime) +{ + auto s = SurfaceInterface::get(surface); + auto p = PointerInterface::get(pointer); + if (!s || !p) { + // send error? + return; + } + if (!s->lockedPointer().isNull() || !s->confinedPointer().isNull()) { + wl_resource_post_error(s->resource(), ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, "Surface already constrained"); + return; + } + auto constraint = new T(q, resource); + switch (lifetime) { + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT: + constraint->d_func()->lifeTime = T::LifeTime::Persistent; + break; + case ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT: // fall through + default: + constraint->d_func()->lifeTime = T::LifeTime::OneShot; + break; + } + auto r = RegionInterface::get(region); + constraint->d_func()->region = r ? r->region() : QRegion(); + constraint->d_func()->create(display->getConnection(client), version, id); + s->d_func()->installPointerConstraint(constraint); +} + +void PointerConstraintsUnstableV1Interface::Private::lockPointerCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *pointer, wl_resource *region, uint32_t lifetime) +{ + cast(resource)->createConstraint(client, resource, id, surface, pointer, region, lifetime); +} + +void PointerConstraintsUnstableV1Interface::Private::confinePointerCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *surface, wl_resource *pointer, wl_resource *region, uint32_t lifetime) +{ + cast(resource)->createConstraint(client, resource, id, surface, pointer, region, lifetime); +} + +PointerConstraintsUnstableV1Interface::Private::Private(PointerConstraintsUnstableV1Interface *q, Display *d) + : PointerConstraintsInterface::Private(PointerConstraintsInterfaceVersion::UnstableV1, q, d, &zwp_pointer_constraints_v1_interface, s_version) + , q(q) +{ +} + +void PointerConstraintsUnstableV1Interface::Private::bind(wl_client *client, uint32_t version, uint32_t id) +{ + auto c = display->getConnection(client); + wl_resource *resource = c->createResource(&zwp_pointer_constraints_v1_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 PointerConstraintsUnstableV1Interface::Private::unbind(wl_resource *resource) +{ + Q_UNUSED(resource) + // TODO: implement? +} + +PointerConstraintsUnstableV1Interface::PointerConstraintsUnstableV1Interface(Display *display, QObject *parent) + : PointerConstraintsInterface(new Private(this, display), parent) +{ +} + +PointerConstraintsUnstableV1Interface::~PointerConstraintsUnstableV1Interface() = default; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zwp_locked_pointer_v1_interface LockedPointerUnstableV1Interface::Private::s_interface = { + resourceDestroyedCallback, + setCursorPositionHintCallback, + setRegionCallback +}; +#endif + +void LockedPointerUnstableV1Interface::Private::setCursorPositionHintCallback(wl_client *client, wl_resource *resource, wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + Q_UNUSED(client) + Q_UNUSED(resource) + Q_UNUSED(surface_x) + Q_UNUSED(surface_y) + // double buffered + // TODO: implement +} + +void LockedPointerUnstableV1Interface::Private::setRegionCallback(wl_client *client, wl_resource *resource, wl_resource * region) +{ + Q_UNUSED(client) + auto p = cast(resource); + auto r = RegionInterface::get(region); + p->pendingRegion = r ? r->region() : QRegion(); + p->regionIsSet = true; +} + +void LockedPointerUnstableV1Interface::Private::updateLocked() +{ + if (!resource) { + return; + } + if (locked) { + zwp_locked_pointer_v1_send_locked(resource); + } else { + zwp_locked_pointer_v1_send_unlocked(resource); + } +} + +LockedPointerUnstableV1Interface::Private::Private(LockedPointerUnstableV1Interface *q, PointerConstraintsUnstableV1Interface *c, wl_resource *parentResource) + : LockedPointerInterface::Private(PointerConstraintsInterfaceVersion::UnstableV1, q, c, parentResource, &zwp_locked_pointer_v1_interface, &s_interface) +{ +} + +LockedPointerUnstableV1Interface::LockedPointerUnstableV1Interface(PointerConstraintsUnstableV1Interface *parent, wl_resource *parentResource) + : LockedPointerInterface(new Private(this, parent, parentResource)) +{ +} + +LockedPointerUnstableV1Interface::Private::~Private() = default; + +LockedPointerUnstableV1Interface::~LockedPointerUnstableV1Interface() = default; + +LockedPointerUnstableV1Interface::Private *LockedPointerUnstableV1Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +class ConfinedPointerUnstableV1Interface::Private : public ConfinedPointerInterface::Private +{ +public: + Private(ConfinedPointerUnstableV1Interface *q, PointerConstraintsUnstableV1Interface *c, wl_resource *parentResource); + ~Private(); + + void updateConfined() override; + +private: + static void setRegionCallback(wl_client *client, wl_resource *resource, wl_resource * region); + + ConfinedPointerUnstableV1Interface *q_func() { + return reinterpret_cast(q); + } + + static const struct zwp_confined_pointer_v1_interface s_interface; +}; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +const struct zwp_confined_pointer_v1_interface ConfinedPointerUnstableV1Interface::Private::s_interface = { + resourceDestroyedCallback, + setRegionCallback +}; +#endif + +void ConfinedPointerUnstableV1Interface::Private::setRegionCallback(wl_client *client, wl_resource *resource, wl_resource *region) +{ + Q_UNUSED(client) + auto p = cast(resource); + auto r = RegionInterface::get(region); + p->pendingRegion = r ? r->region() : QRegion(); + p->regionIsSet = true; +} + +ConfinedPointerUnstableV1Interface::Private::Private(ConfinedPointerUnstableV1Interface *q, PointerConstraintsUnstableV1Interface *c, wl_resource *parentResource) + : ConfinedPointerInterface::Private(PointerConstraintsInterfaceVersion::UnstableV1, q, c, parentResource, &zwp_confined_pointer_v1_interface, &s_interface) +{ +} + +ConfinedPointerUnstableV1Interface::ConfinedPointerUnstableV1Interface(PointerConstraintsUnstableV1Interface *parent, wl_resource *parentResource) + : ConfinedPointerInterface(new Private(this, parent, parentResource)) +{ +} + +ConfinedPointerUnstableV1Interface::Private::~Private() = default; + +ConfinedPointerUnstableV1Interface::~ConfinedPointerUnstableV1Interface() = default; + +void ConfinedPointerUnstableV1Interface::Private::updateConfined() +{ + if (!resource) { + return; + } + if (confined) { + zwp_confined_pointer_v1_send_confined(resource); + } else { + zwp_confined_pointer_v1_send_unconfined(resource); + } +} + +ConfinedPointerUnstableV1Interface::Private *ConfinedPointerUnstableV1Interface::d_func() const +{ + return reinterpret_cast(d.data()); +} + +} +} diff --git a/src/server/surface_interface.h b/src/server/surface_interface.h --- a/src/server/surface_interface.h +++ b/src/server/surface_interface.h @@ -36,9 +36,12 @@ class BlurManagerInterface; class BlurInterface; class BufferInterface; +class ConfinedPointerInterface; class ContrastInterface; class ContrastManagerInterface; class CompositorInterface; +class LockedPointerInterface; +class PointerConstraintsUnstableV1Interface; class ShadowManagerInterface; class ShadowInterface; class SlideInterface; @@ -226,6 +229,20 @@ QVector outputs() const; /** + * Pointer confinement installed on this SurfaceInterface. + * @see pointerConstraintsChanged + * @since 5.29 + **/ + QPointer confinedPointer() const; + + /** + * Pointer lock installed on this SurfaceInterface. + * @see pointerConstraintsChanged + * @since 5.29 + **/ + QPointer lockedPointer() const; + + /** * @returns The SurfaceInterface for the @p native resource. **/ static SurfaceInterface *get(wl_resource *native); @@ -279,13 +296,26 @@ **/ void subSurfaceTreeChanged(); + /** + * Emitted whenever a pointer constraint get (un)installed on this SurfaceInterface. + * + * The pointer constraint does not get activated, the compositor needs to activate + * the lock/confinement. + * + * @see confinedPointer + * @see lockedPointer + * @since 5.29 + **/ + void pointerConstraintsChanged(); + private: friend class CompositorInterface; friend class SubSurfaceInterface; friend class ShadowManagerInterface; friend class BlurManagerInterface; friend class SlideManagerInterface; friend class ContrastManagerInterface; + friend class PointerConstraintsUnstableV1Interface; explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource); class Private; diff --git a/src/server/surface_interface.cpp b/src/server/surface_interface.cpp --- a/src/server/surface_interface.cpp +++ b/src/server/surface_interface.cpp @@ -22,6 +22,7 @@ #include "buffer_interface.h" #include "clientconnection.h" #include "compositor_interface.h" +#include "pointerconstraints_interface_p.h" #include "region_interface.h" #include "subcompositor_interface.h" #include "subsurface_interface_p.h" @@ -174,6 +175,82 @@ pending.contrastIsSet = true; } +void SurfaceInterface::Private::installPointerConstraint(LockedPointerInterface *lock) +{ + Q_ASSERT(lockedPointer.isNull()); + Q_ASSERT(confinedPointer.isNull()); + lockedPointer = QPointer(lock); + if (lock->lifeTime() == LockedPointerInterface::LifeTime::OneShot) { + constrainsOneShotConnection = QObject::connect(lock, &LockedPointerInterface::lockedChanged, q_func(), + [this] { + if (lockedPointer.isNull()) { + return; + } + if (!lockedPointer->isLocked()) { + lockedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + } + } + ); + } + constrainsUnboundConnection = QObject::connect(lock, &LockedPointerInterface::unbound, q_func(), + [this] { + if (lockedPointer.isNull()) { + return; + } + lockedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + } + ); + emit q_func()->pointerConstraintsChanged(); +} + +void SurfaceInterface::Private::installPointerConstraint(ConfinedPointerInterface *confinement) +{ + Q_ASSERT(lockedPointer.isNull()); + Q_ASSERT(confinedPointer.isNull()); + confinedPointer = QPointer(confinement); + if (confinement->lifeTime() == ConfinedPointerInterface::LifeTime::OneShot) { + constrainsOneShotConnection = QObject::connect(confinement, &ConfinedPointerInterface::confinedChanged, q_func(), + [this] { + if (confinedPointer.isNull()) { + return; + } + if (!confinedPointer->isConfined()) { + confinedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + } + } + ); + } + constrainsUnboundConnection = QObject::connect(confinement, &ConfinedPointerInterface::unbound, q_func(), + [this] { + if (confinedPointer.isNull()) { + return; + } + confinedPointer.clear(); + disconnect(constrainsOneShotConnection); + constrainsOneShotConnection = QMetaObject::Connection(); + disconnect(constrainsUnboundConnection); + constrainsUnboundConnection = QMetaObject::Connection(); + emit q_func()->pointerConstraintsChanged(); + } + ); + emit q_func()->pointerConstraintsChanged(); +} + #ifndef DOXYGEN_SHOULD_SKIP_THIS const struct wl_surface_interface SurfaceInterface::Private::s_interface = { resourceDestroyedCallback, @@ -324,6 +401,12 @@ target->transform = source->transform; target->transformIsSet = true; } + if (!lockedPointer.isNull()) { + lockedPointer->d_func()->commit(); + } + if (!confinedPointer.isNull()) { + confinedPointer->d_func()->commit(); + } *source = State{}; source->children = target->children; @@ -755,6 +838,18 @@ return nullptr; } +QPointer SurfaceInterface::lockedPointer() const +{ + Q_D(); + return d->lockedPointer; +} + +QPointer SurfaceInterface::confinedPointer() const +{ + Q_D(); + return d->confinedPointer; +} + SurfaceInterface::Private *SurfaceInterface::d_func() const { return reinterpret_cast(d.data()); diff --git a/src/server/surface_interface_p.h b/src/server/surface_interface_p.h --- a/src/server/surface_interface_p.h +++ b/src/server/surface_interface_p.h @@ -75,6 +75,8 @@ void setBlur(const QPointer &blur); void setContrast(const QPointer &contrast); void setSlide(const QPointer &slide); + void installPointerConstraint(LockedPointerInterface *lock); + void installPointerConstraint(ConfinedPointerInterface *confinement); void commitSubSurface(); void commit(); @@ -93,7 +95,13 @@ QVector outputs; + QPointer lockedPointer; + QPointer confinedPointer; + private: + QMetaObject::Connection constrainsOneShotConnection; + QMetaObject::Connection constrainsUnboundConnection; + SurfaceInterface *q_func() { return reinterpret_cast(q); } diff --git a/src/tools/mapping.txt b/src/tools/mapping.txt --- a/src/tools/mapping.txt +++ b/src/tools/mapping.txt @@ -52,3 +52,6 @@ zwp_pointer_gestures_v1;PointerGesturesUnstableV1 zwp_pointer_gesture_swipe_v1;PointerSwipeGestureUnstableV1 zwp_pointer_gesture_pinch_v1;PointerPinchGestureUnstableV1 +zwp_pointer_constraints_v1;PointerConstraints +zwp_locked_pointer_v1;LockedPointer +zwp_confined_pointer_v1;ConfinedPointer