From b44a8fb556c4f35b8f0b208afb7e71d929b0a6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Tue, 8 Nov 2016 14:17:15 +0100 Subject: [PATCH] Implementation of PointerConstraints protcol Summary: The pointer constraints protocol is an unstable protocol and thus the implementation follows the semantics of unstable protocols. The protocol allows to create a constraint on the pointer - either a lock or a confinement on a surface. Those are not activated at once, but when the compositor actively grants it. During lock no further pointer motion is emitted, during confinement the pointer is kept in a certain area. This implements T4451. Reviewers: #plasma_on_wayland Subscribers: plasma-devel Tags: #plasma_on_wayland Differential Revision: https://phabricator.kde.org/D3466 --- src/wayland/CMakeLists.txt | 8 + src/wayland/autotests/client/CMakeLists.txt | 7 + .../client/test_pointer_constraints.cpp | 433 ++++++++++++++++++ .../client/test_wayland_registry.cpp | 14 + src/wayland/display.cpp | 13 + src/wayland/display.h | 10 + src/wayland/pointer_interface.cpp | 4 + .../server/pointerconstraints_interface.cpp | 201 ++++++++ .../server/pointerconstraints_interface.h | 268 +++++++++++ .../server/pointerconstraints_interface_p.h | 136 ++++++ .../pointerconstraints_interface_v1.cpp | 287 ++++++++++++ src/wayland/surface_interface.cpp | 95 ++++ src/wayland/surface_interface.h | 30 ++ src/wayland/surface_interface_p.h | 8 + src/wayland/tools/mapping.txt | 3 + 15 files changed, 1517 insertions(+) create mode 100644 src/wayland/autotests/client/test_pointer_constraints.cpp create mode 100644 src/wayland/server/pointerconstraints_interface.cpp create mode 100644 src/wayland/server/pointerconstraints_interface.h create mode 100644 src/wayland/server/pointerconstraints_interface_p.h create mode 100644 src/wayland/server/pointerconstraints_interface_v1.cpp diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index c59cbeb611..e4c47ba075 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -21,6 +21,8 @@ set(SERVER_LIB_SRCS 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 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS 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 @@ install(FILES 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/wayland/autotests/client/CMakeLists.txt b/src/wayland/autotests/client/CMakeLists.txt index a52e297e4f..64cda34bab 100644 --- a/src/wayland/autotests/client/CMakeLists.txt +++ b/src/wayland/autotests/client/CMakeLists.txt @@ -348,3 +348,10 @@ target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5: 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/src/wayland/autotests/client/test_pointer_constraints.cpp b/src/wayland/autotests/client/test_pointer_constraints.cpp new file mode 100644 index 0000000000..d2f2e754fb --- /dev/null +++ b/src/wayland/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/src/wayland/autotests/client/test_wayland_registry.cpp b/src/wayland/autotests/client/test_wayland_registry.cpp index b75b184d3e..f109d7ded5 100644 --- a/src/wayland/autotests/client/test_wayland_registry.cpp +++ b/src/wayland/autotests/client/test_wayland_registry.cpp @@ -26,6 +26,7 @@ License along with this library. If not, see . #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 @@ License along with this library. If not, see . #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 @@ License along with this library. If not, see . #include #include #include +#include class TestWaylandRegistry : public QObject { @@ -88,6 +91,7 @@ private Q_SLOTS: void testBindXdgShellUnstableV5(); void testBindRelativePointerManagerUnstableV1(); void testBindPointerGesturesUnstableV1(); + void testBindPointerConstraintsUnstableV1(); void testGlobalSync(); void testGlobalSyncThreaded(); void testRemoval(); @@ -111,6 +115,7 @@ private: 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 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent) , m_xdgShellUnstableV5(nullptr) , m_relativePointerV1(nullptr) , m_pointerGesturesV1(nullptr) + , m_pointerConstraintsV1(nullptr) { } @@ -179,6 +185,9 @@ void TestWaylandRegistry::init() 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 @@ void TestWaylandRegistry::testBindPointerGesturesUnstableV1() 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/wayland/display.cpp b/src/wayland/display.cpp index e1a9710eda..ad2a8b97a8 100644 --- a/src/wayland/display.cpp +++ b/src/wayland/display.cpp @@ -30,6 +30,7 @@ License along with this library. If not, see . #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 @@ PointerGesturesInterface *Display::createPointerGestures(const PointerGesturesIn 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/wayland/display.h b/src/wayland/display.h index 742c067d35..1ae6685ef4 100644 --- a/src/wayland/display.h +++ b/src/wayland/display.h @@ -78,6 +78,8 @@ enum class RelativePointerInterfaceVersion; class RelativePointerManagerInterface; enum class PointerGesturesInterfaceVersion; class PointerGesturesInterface; +enum class PointerConstraintsInterfaceVersion; +class PointerConstraintsInterface; /** * @brief Class holding the Wayland server display loop. @@ -209,6 +211,14 @@ public: **/ 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. diff --git a/src/wayland/pointer_interface.cpp b/src/wayland/pointer_interface.cpp index 2b0097d1f4..2900631faf 100644 --- a/src/wayland/pointer_interface.cpp +++ b/src/wayland/pointer_interface.cpp @@ -19,6 +19,7 @@ License along with this library. If not, see . *********************************************************************/ #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 @@ PointerInterface::PointerInterface(SeatInterface *parent, wl_resource *parentRes 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/wayland/server/pointerconstraints_interface.cpp b/src/wayland/server/pointerconstraints_interface.cpp new file mode 100644 index 0000000000..158b0d00c7 --- /dev/null +++ b/src/wayland/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/wayland/server/pointerconstraints_interface.h b/src/wayland/server/pointerconstraints_interface.h new file mode 100644 index 0000000000..0e9cce0718 --- /dev/null +++ b/src/wayland/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/wayland/server/pointerconstraints_interface_p.h b/src/wayland/server/pointerconstraints_interface_p.h new file mode 100644 index 0000000000..c194cdd3fc --- /dev/null +++ b/src/wayland/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/wayland/server/pointerconstraints_interface_v1.cpp b/src/wayland/server/pointerconstraints_interface_v1.cpp new file mode 100644 index 0000000000..4973fe6d16 --- /dev/null +++ b/src/wayland/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/wayland/surface_interface.cpp b/src/wayland/surface_interface.cpp index 49b8e71378..faac902881 100644 --- a/src/wayland/surface_interface.cpp +++ b/src/wayland/surface_interface.cpp @@ -22,6 +22,7 @@ License along with this library. If not, see . #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 @@ void SurfaceInterface::Private::setContrast(const QPointer &c 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 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em 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 @@ SurfaceInterface *SurfaceInterface::surfaceAt(const QPointF &position) 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/wayland/surface_interface.h b/src/wayland/surface_interface.h index c3f38c8384..509e536273 100644 --- a/src/wayland/surface_interface.h +++ b/src/wayland/surface_interface.h @@ -36,9 +36,12 @@ namespace Server class BlurManagerInterface; class BlurInterface; class BufferInterface; +class ConfinedPointerInterface; class ContrastInterface; class ContrastManagerInterface; class CompositorInterface; +class LockedPointerInterface; +class PointerConstraintsUnstableV1Interface; class ShadowManagerInterface; class ShadowInterface; class SlideInterface; @@ -225,6 +228,20 @@ public: **/ 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. **/ @@ -279,6 +296,18 @@ Q_SIGNALS: **/ 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; @@ -286,6 +315,7 @@ private: 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/wayland/surface_interface_p.h b/src/wayland/surface_interface_p.h index 4958373b20..484e2f7d26 100644 --- a/src/wayland/surface_interface_p.h +++ b/src/wayland/surface_interface_p.h @@ -75,6 +75,8 @@ public: 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 @@ public: QVector outputs; + QPointer lockedPointer; + QPointer confinedPointer; + private: + QMetaObject::Connection constrainsOneShotConnection; + QMetaObject::Connection constrainsUnboundConnection; + SurfaceInterface *q_func() { return reinterpret_cast(q); } diff --git a/src/wayland/tools/mapping.txt b/src/wayland/tools/mapping.txt index 7e50ce9d02..6700bb8ceb 100644 --- a/src/wayland/tools/mapping.txt +++ b/src/wayland/tools/mapping.txt @@ -52,3 +52,6 @@ zwp_relative_pointer_v1;RelativePointerUnstableV1 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