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
This commit is contained in:
Martin Gräßlin 2016-11-08 14:17:15 +01:00
parent c72510c932
commit b44a8fb556
15 changed files with 1517 additions and 0 deletions

View file

@ -21,6 +21,8 @@ set(SERVER_LIB_SRCS
pointer_interface.cpp pointer_interface.cpp
plasmashell_interface.cpp plasmashell_interface.cpp
plasmawindowmanagement_interface.cpp plasmawindowmanagement_interface.cpp
pointerconstraints_interface.cpp
pointerconstraints_interface_v1.cpp
pointergestures_interface.cpp pointergestures_interface.cpp
pointergestures_interface_v1.cpp pointergestures_interface_v1.cpp
qtsurfaceextension_interface.cpp qtsurfaceextension_interface.cpp
@ -134,6 +136,11 @@ ecm_add_wayland_server_protocol(SERVER_LIB_SRCS
BASENAME pointer-gestures-unstable-v1 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}) add_library(KF5WaylandServer ${SERVER_LIB_SRCS})
generate_export_header(KF5WaylandServer generate_export_header(KF5WaylandServer
BASE_NAME BASE_NAME
@ -185,6 +192,7 @@ install(FILES
outputmanagement_interface.h outputmanagement_interface.h
output_interface.h output_interface.h
pointer_interface.h pointer_interface.h
pointerconstraints_interface.h
pointergestures_interface.h pointergestures_interface.h
plasmashell_interface.h plasmashell_interface.h
plasmawindowmanagement_interface.h plasmawindowmanagement_interface.h

View file

@ -348,3 +348,10 @@ target_link_libraries( testXdgShellV5 Qt5::Test Qt5::Gui KF5::WaylandServer KF5:
add_test(kwayland-testXdgShellV5 testXdgShellV5) add_test(kwayland-testXdgShellV5 testXdgShellV5)
ecm_mark_as_test(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)

View file

@ -0,0 +1,433 @@
/********************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
// Qt
#include <QtTest/QtTest>
// 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(&registry, &Registry::interfacesAnnounced);
QVERIFY(interfacesAnnouncedSpy.isValid());
QSignalSpy interfaceAnnouncedSpy(&registry, &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<PointerConstraints::LifeTime>("clientLifeTime");
QTest::addColumn<LockedPointerInterface::LifeTime>("serverLifeTime");
QTest::addColumn<bool>("hasConstraintAfterUnlock");
QTest::addColumn<int>("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> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
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> 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<PointerConstraints::LifeTime>("clientLifeTime");
QTest::addColumn<ConfinedPointerInterface::LifeTime>("serverLifeTime");
QTest::addColumn<bool>("hasConstraintAfterUnlock");
QTest::addColumn<int>("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> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
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> 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<Constraint>("firstConstraint");
QTest::addColumn<Constraint>("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> surface(m_compositor->createSurface());
QVERIFY(surface->isValid());
QFETCH(Constraint, firstConstraint);
QScopedPointer<ConfinedPointer> confinedPointer;
QScopedPointer<LockedPointer> 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<ConfinedPointer> confinedPointer2;
QScopedPointer<LockedPointer> 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"

View file

@ -26,6 +26,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/client/event_queue.h" #include "../../src/client/event_queue.h"
#include "../../src/client/registry.h" #include "../../src/client/registry.h"
#include "../../src/client/output.h" #include "../../src/client/output.h"
#include "../../src/client/pointerconstraints.h"
#include "../../src/client/pointergestures.h" #include "../../src/client/pointergestures.h"
#include "../../src/client/seat.h" #include "../../src/client/seat.h"
#include "../../src/client/relativepointer.h" #include "../../src/client/relativepointer.h"
@ -47,6 +48,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "../../src/server/subcompositor_interface.h" #include "../../src/server/subcompositor_interface.h"
#include "../../src/server/outputmanagement_interface.h" #include "../../src/server/outputmanagement_interface.h"
#include "../../src/server/outputdevice_interface.h" #include "../../src/server/outputdevice_interface.h"
#include "../../src/server/pointerconstraints_interface.h"
#include "../../src/server/pointergestures_interface.h" #include "../../src/server/pointergestures_interface.h"
#include "../../src/server/textinput_interface.h" #include "../../src/server/textinput_interface.h"
#include "../../src/server/xdgshell_interface.h" #include "../../src/server/xdgshell_interface.h"
@ -60,6 +62,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include <wayland-xdg-shell-v5-client-protocol.h> #include <wayland-xdg-shell-v5-client-protocol.h>
#include <wayland-relativepointer-unstable-v1-client-protocol.h> #include <wayland-relativepointer-unstable-v1-client-protocol.h>
#include <wayland-pointer-gestures-unstable-v1-client-protocol.h> #include <wayland-pointer-gestures-unstable-v1-client-protocol.h>
#include <wayland-pointer-constraints-unstable-v1-client-protocol.h>
class TestWaylandRegistry : public QObject class TestWaylandRegistry : public QObject
{ {
@ -88,6 +91,7 @@ private Q_SLOTS:
void testBindXdgShellUnstableV5(); void testBindXdgShellUnstableV5();
void testBindRelativePointerManagerUnstableV1(); void testBindRelativePointerManagerUnstableV1();
void testBindPointerGesturesUnstableV1(); void testBindPointerGesturesUnstableV1();
void testBindPointerConstraintsUnstableV1();
void testGlobalSync(); void testGlobalSync();
void testGlobalSyncThreaded(); void testGlobalSyncThreaded();
void testRemoval(); void testRemoval();
@ -111,6 +115,7 @@ private:
KWayland::Server::XdgShellInterface *m_xdgShellUnstableV5; KWayland::Server::XdgShellInterface *m_xdgShellUnstableV5;
KWayland::Server::RelativePointerManagerInterface *m_relativePointerV1; KWayland::Server::RelativePointerManagerInterface *m_relativePointerV1;
KWayland::Server::PointerGesturesInterface *m_pointerGesturesV1; KWayland::Server::PointerGesturesInterface *m_pointerGesturesV1;
KWayland::Server::PointerConstraintsInterface *m_pointerConstraintsV1;
}; };
static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0"); static const QString s_socketName = QStringLiteral("kwin-test-wayland-registry-0");
@ -132,6 +137,7 @@ TestWaylandRegistry::TestWaylandRegistry(QObject *parent)
, m_xdgShellUnstableV5(nullptr) , m_xdgShellUnstableV5(nullptr)
, m_relativePointerV1(nullptr) , m_relativePointerV1(nullptr)
, m_pointerGesturesV1(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 = m_display->createPointerGestures(KWayland::Server::PointerGesturesInterfaceVersion::UnstableV1);
m_pointerGesturesV1->create(); m_pointerGesturesV1->create();
QCOMPARE(m_pointerGesturesV1->interfaceVersion(), KWayland::Server::PointerGesturesInterfaceVersion::UnstableV1); 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() 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) 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 #undef TEST_BIND
void TestWaylandRegistry::testRemoval() void TestWaylandRegistry::testRemoval()

View file

@ -30,6 +30,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "output_interface.h" #include "output_interface.h"
#include "plasmashell_interface.h" #include "plasmashell_interface.h"
#include "plasmawindowmanagement_interface.h" #include "plasmawindowmanagement_interface.h"
#include "pointerconstraints_interface_p.h"
#include "pointergestures_interface_p.h" #include "pointergestures_interface_p.h"
#include "qtsurfaceextension_interface.h" #include "qtsurfaceextension_interface.h"
#include "seat_interface.h" #include "seat_interface.h"
@ -395,6 +396,18 @@ PointerGesturesInterface *Display::createPointerGestures(const PointerGesturesIn
return p; 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() void Display::createShm()
{ {
Q_ASSERT(d->display); Q_ASSERT(d->display);

View file

@ -78,6 +78,8 @@ enum class RelativePointerInterfaceVersion;
class RelativePointerManagerInterface; class RelativePointerManagerInterface;
enum class PointerGesturesInterfaceVersion; enum class PointerGesturesInterfaceVersion;
class PointerGesturesInterface; class PointerGesturesInterface;
enum class PointerConstraintsInterfaceVersion;
class PointerConstraintsInterface;
/** /**
* @brief Class holding the Wayland server display loop. * @brief Class holding the Wayland server display loop.
@ -209,6 +211,14 @@ public:
**/ **/
PointerGesturesInterface *createPointerGestures(const PointerGesturesInterfaceVersion &version, QObject *parent = nullptr); 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. * Gets the ClientConnection for the given @p client.
* If there is no ClientConnection yet for the given @p client, it will be created. * If there is no ClientConnection yet for the given @p client, it will be created.

View file

@ -19,6 +19,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/ *********************************************************************/
#include "pointer_interface.h" #include "pointer_interface.h"
#include "pointer_interface_p.h" #include "pointer_interface_p.h"
#include "pointerconstraints_interface.h"
#include "pointergestures_interface_p.h" #include "pointergestures_interface_p.h"
#include "resource_p.h" #include "resource_p.h"
#include "relativepointer_interface_p.h" #include "relativepointer_interface_p.h"
@ -228,6 +229,9 @@ PointerInterface::PointerInterface(SeatInterface *parent, wl_resource *parentRes
return; return;
} }
if (d->focusedSurface && d->resource) { 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()); const QPointF pos = d->seat->focusedPointerSurfaceTransformation().map(d->seat->pointerPos());
auto targetSurface = d->focusedSurface->surfaceAt(pos); auto targetSurface = d->focusedSurface->surfaceAt(pos);
if (!targetSurface) { if (!targetSurface) {

View file

@ -0,0 +1,201 @@
/****************************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "pointerconstraints_interface_p.h"
#include <functional>
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<Private*>(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<Private*>(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<Private*>(d.data());
}
}
}

View file

@ -0,0 +1,268 @@
/****************************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#ifndef KWAYLAND_SERVER_POINTERCONSTRAINTS_H
#define KWAYLAND_SERVER_POINTERCONSTRAINTS_H
#include "global.h"
#include "resource.h"
#include <KWayland/Server/kwaylandserver_export.h>
#include <QRegion>
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

View file

@ -0,0 +1,136 @@
/****************************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#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 <QRegion>
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<LockedPointerInterface *>(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<ConfinedPointerInterface *>(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

View file

@ -0,0 +1,287 @@
/****************************************************************************
Copyright 2016 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "pointerconstraints_interface_p.h"
#include "display.h"
#include "pointer_interface.h"
#include "region_interface.h"
#include "surface_interface_p.h"
#include <wayland-pointer-constraints-unstable-v1-server-protocol.h>
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 <class T>
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<Private*>(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<LockedPointerUnstableV1Interface *>(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 <class T>
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<LockedPointerUnstableV1Interface>(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<ConfinedPointerUnstableV1Interface>(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<Private>(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<Private*>(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<ConfinedPointerUnstableV1Interface *>(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<Private>(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<Private*>(d.data());
}
}
}

View file

@ -22,6 +22,7 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "buffer_interface.h" #include "buffer_interface.h"
#include "clientconnection.h" #include "clientconnection.h"
#include "compositor_interface.h" #include "compositor_interface.h"
#include "pointerconstraints_interface_p.h"
#include "region_interface.h" #include "region_interface.h"
#include "subcompositor_interface.h" #include "subcompositor_interface.h"
#include "subsurface_interface_p.h" #include "subsurface_interface_p.h"
@ -174,6 +175,82 @@ void SurfaceInterface::Private::setContrast(const QPointer<ContrastInterface> &c
pending.contrastIsSet = true; pending.contrastIsSet = true;
} }
void SurfaceInterface::Private::installPointerConstraint(LockedPointerInterface *lock)
{
Q_ASSERT(lockedPointer.isNull());
Q_ASSERT(confinedPointer.isNull());
lockedPointer = QPointer<LockedPointerInterface>(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<ConfinedPointerInterface>(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 #ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct wl_surface_interface SurfaceInterface::Private::s_interface = { const struct wl_surface_interface SurfaceInterface::Private::s_interface = {
resourceDestroyedCallback, resourceDestroyedCallback,
@ -324,6 +401,12 @@ void SurfaceInterface::Private::swapStates(State *source, State *target, bool em
target->transform = source->transform; target->transform = source->transform;
target->transformIsSet = true; target->transformIsSet = true;
} }
if (!lockedPointer.isNull()) {
lockedPointer->d_func()->commit();
}
if (!confinedPointer.isNull()) {
confinedPointer->d_func()->commit();
}
*source = State{}; *source = State{};
source->children = target->children; source->children = target->children;
@ -755,6 +838,18 @@ SurfaceInterface *SurfaceInterface::surfaceAt(const QPointF &position)
return nullptr; return nullptr;
} }
QPointer<LockedPointerInterface> SurfaceInterface::lockedPointer() const
{
Q_D();
return d->lockedPointer;
}
QPointer<ConfinedPointerInterface> SurfaceInterface::confinedPointer() const
{
Q_D();
return d->confinedPointer;
}
SurfaceInterface::Private *SurfaceInterface::d_func() const SurfaceInterface::Private *SurfaceInterface::d_func() const
{ {
return reinterpret_cast<Private*>(d.data()); return reinterpret_cast<Private*>(d.data());

View file

@ -36,9 +36,12 @@ namespace Server
class BlurManagerInterface; class BlurManagerInterface;
class BlurInterface; class BlurInterface;
class BufferInterface; class BufferInterface;
class ConfinedPointerInterface;
class ContrastInterface; class ContrastInterface;
class ContrastManagerInterface; class ContrastManagerInterface;
class CompositorInterface; class CompositorInterface;
class LockedPointerInterface;
class PointerConstraintsUnstableV1Interface;
class ShadowManagerInterface; class ShadowManagerInterface;
class ShadowInterface; class ShadowInterface;
class SlideInterface; class SlideInterface;
@ -225,6 +228,20 @@ public:
**/ **/
QVector<OutputInterface *> outputs() const; QVector<OutputInterface *> outputs() const;
/**
* Pointer confinement installed on this SurfaceInterface.
* @see pointerConstraintsChanged
* @since 5.29
**/
QPointer<ConfinedPointerInterface> confinedPointer() const;
/**
* Pointer lock installed on this SurfaceInterface.
* @see pointerConstraintsChanged
* @since 5.29
**/
QPointer<LockedPointerInterface> lockedPointer() const;
/** /**
* @returns The SurfaceInterface for the @p native resource. * @returns The SurfaceInterface for the @p native resource.
**/ **/
@ -279,6 +296,18 @@ Q_SIGNALS:
**/ **/
void subSurfaceTreeChanged(); 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: private:
friend class CompositorInterface; friend class CompositorInterface;
friend class SubSurfaceInterface; friend class SubSurfaceInterface;
@ -286,6 +315,7 @@ private:
friend class BlurManagerInterface; friend class BlurManagerInterface;
friend class SlideManagerInterface; friend class SlideManagerInterface;
friend class ContrastManagerInterface; friend class ContrastManagerInterface;
friend class PointerConstraintsUnstableV1Interface;
explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource); explicit SurfaceInterface(CompositorInterface *parent, wl_resource *parentResource);
class Private; class Private;

View file

@ -75,6 +75,8 @@ public:
void setBlur(const QPointer<BlurInterface> &blur); void setBlur(const QPointer<BlurInterface> &blur);
void setContrast(const QPointer<ContrastInterface> &contrast); void setContrast(const QPointer<ContrastInterface> &contrast);
void setSlide(const QPointer<SlideInterface> &slide); void setSlide(const QPointer<SlideInterface> &slide);
void installPointerConstraint(LockedPointerInterface *lock);
void installPointerConstraint(ConfinedPointerInterface *confinement);
void commitSubSurface(); void commitSubSurface();
void commit(); void commit();
@ -93,7 +95,13 @@ public:
QVector<OutputInterface *> outputs; QVector<OutputInterface *> outputs;
QPointer<LockedPointerInterface> lockedPointer;
QPointer<ConfinedPointerInterface> confinedPointer;
private: private:
QMetaObject::Connection constrainsOneShotConnection;
QMetaObject::Connection constrainsUnboundConnection;
SurfaceInterface *q_func() { SurfaceInterface *q_func() {
return reinterpret_cast<SurfaceInterface *>(q); return reinterpret_cast<SurfaceInterface *>(q);
} }

View file

@ -52,3 +52,6 @@ zwp_relative_pointer_v1;RelativePointerUnstableV1
zwp_pointer_gestures_v1;PointerGesturesUnstableV1 zwp_pointer_gestures_v1;PointerGesturesUnstableV1
zwp_pointer_gesture_swipe_v1;PointerSwipeGestureUnstableV1 zwp_pointer_gesture_swipe_v1;PointerSwipeGestureUnstableV1
zwp_pointer_gesture_pinch_v1;PointerPinchGestureUnstableV1 zwp_pointer_gesture_pinch_v1;PointerPinchGestureUnstableV1
zwp_pointer_constraints_v1;PointerConstraints
zwp_locked_pointer_v1;LockedPointer
zwp_confined_pointer_v1;ConfinedPointer