kwin/tests/pointerconstraintstest.cpp
2021-05-14 01:35:33 +02:00

403 lines
12 KiB
C++

/*
SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "pointerconstraintstest.h"
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/region.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointerconstraints.h>
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QCursor>
#include <QDebug>
#include <QScopedPointer>
#include <xcb/xproto.h>
using namespace KWayland::Client;
WaylandBackend::WaylandBackend(QObject *parent)
: Backend(parent)
, m_connectionThreadObject(ConnectionThread::fromApplication(this))
{
setMode(Mode::Wayland);
}
void WaylandBackend::init(QQuickView *view)
{
Backend::init(view);
Registry *registry = new Registry(this);
setupRegistry(registry);
}
void WaylandBackend::setupRegistry(Registry *registry)
{
connect(registry, &Registry::compositorAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_compositor = registry->createCompositor(name, version, this);
}
);
connect(registry, &Registry::seatAnnounced, this,
[this, registry](quint32 name, quint32 version) {
m_seat = registry->createSeat(name, version, this);
if (m_seat->hasPointer()) {
m_pointer = m_seat->createPointer(this);
}
connect(m_seat, &Seat::hasPointerChanged, this,
[this]() {
delete m_pointer;
m_pointer = m_seat->createPointer(this);
}
);
}
);
connect(registry, &Registry::pointerConstraintsUnstableV1Announced, this,
[this, registry](quint32 name, quint32 version) {
m_pointerConstraints = registry->createPointerConstraints(name, version, this);
}
);
connect(registry, &Registry::interfacesAnnounced, this,
[this] {
Q_ASSERT(m_compositor);
Q_ASSERT(m_seat);
Q_ASSERT(m_pointerConstraints);
}
);
registry->create(m_connectionThreadObject);
registry->setup();
}
bool WaylandBackend::isLocked()
{
return m_lockedPointer && m_lockedPointer->isValid();
}
bool WaylandBackend::isConfined()
{
return m_confinedPointer && m_confinedPointer->isValid();
}
static PointerConstraints::LifeTime lifeTime(bool persistent)
{
return persistent ? PointerConstraints::LifeTime::Persistent :
PointerConstraints::LifeTime::OneShot;
}
void WaylandBackend::lockRequest(bool persistent, QRect region)
{
if (isLocked()) {
if (!errorsAllowed()) {
qDebug() << "Abort locking because already locked. Allow errors to test relocking (and crashing).";
return;
}
qDebug() << "Trying to lock although already locked. Crash expected.";
}
if (isConfined()) {
if (!errorsAllowed()) {
qDebug() << "Abort locking because already confined. Allow errors to test locking while being confined (and crashing).";
return;
}
qDebug() << "Trying to lock although already confined. Crash expected.";
}
qDebug() << "------ Lock requested ------";
qDebug() << "Persistent:" << persistent << "| Region:" << region;
QScopedPointer<Surface> winSurface(Surface::fromWindow(view()));
QScopedPointer<Region> wlRegion(m_compositor->createRegion(this));
wlRegion->add(region);
auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.data(),
m_pointer,
wlRegion.data(),
lifeTime(persistent),
this);
if (!lockedPointer) {
qDebug() << "ERROR when receiving locked pointer!";
return;
}
m_lockedPointer = lockedPointer;
m_lockedPointerPersistent = persistent;
connect(lockedPointer, &LockedPointer::locked, this, [this]() {
qDebug() << "------ LOCKED! ------";
if(lockHint()) {
m_lockedPointer->setCursorPositionHint(QPointF(10., 10.));
Q_EMIT forceSurfaceCommit();
}
Q_EMIT lockChanged(true);
});
connect(lockedPointer, &LockedPointer::unlocked, this, [this]() {
qDebug() << "------ UNLOCKED! ------";
if (!m_lockedPointerPersistent) {
cleanupLock();
}
Q_EMIT lockChanged(false);
});
}
void WaylandBackend::unlockRequest()
{
if (!m_lockedPointer) {
qDebug() << "Unlock requested, but there is no lock. Abort.";
return;
}
qDebug() << "------ Unlock requested ------";
cleanupLock();
Q_EMIT lockChanged(false);
}
void WaylandBackend::cleanupLock()
{
if (!m_lockedPointer) {
return;
}
m_lockedPointer->release();
m_lockedPointer->deleteLater();
m_lockedPointer = nullptr;
}
void WaylandBackend::confineRequest(bool persistent, QRect region)
{
if (isConfined()) {
if (!errorsAllowed()) {
qDebug() << "Abort confining because already confined. Allow errors to test reconfining (and crashing).";
return;
}
qDebug() << "Trying to lock although already locked. Crash expected.";
}
if (isLocked()) {
if (!errorsAllowed()) {
qDebug() << "Abort confining because already locked. Allow errors to test confining while being locked (and crashing).";
return;
}
qDebug() << "Trying to confine although already locked. Crash expected.";
}
qDebug() << "------ Confine requested ------";
qDebug() << "Persistent:" << persistent << "| Region:" << region;
QScopedPointer<Surface> winSurface(Surface::fromWindow(view()));
QScopedPointer<Region> wlRegion(m_compositor->createRegion(this));
wlRegion->add(region);
auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.data(),
m_pointer,
wlRegion.data(),
lifeTime(persistent),
this);
if (!confinedPointer) {
qDebug() << "ERROR when receiving confined pointer!";
return;
}
m_confinedPointer = confinedPointer;
m_confinedPointerPersistent = persistent;
connect(confinedPointer, &ConfinedPointer::confined, this, [this]() {
qDebug() << "------ CONFINED! ------";
Q_EMIT confineChanged(true);
});
connect(confinedPointer, &ConfinedPointer::unconfined, this, [this]() {
qDebug() << "------ UNCONFINED! ------";
if (!m_confinedPointerPersistent) {
cleanupConfine();
}
Q_EMIT confineChanged(false);
});
}
void WaylandBackend::unconfineRequest()
{
if (!m_confinedPointer) {
qDebug() << "Unconfine requested, but there is no confine. Abort.";
return;
}
qDebug() << "------ Unconfine requested ------";
cleanupConfine();
Q_EMIT confineChanged(false);
}
void WaylandBackend::cleanupConfine()
{
if (!m_confinedPointer) {
return;
}
m_confinedPointer->release();
m_confinedPointer->deleteLater();
m_confinedPointer = nullptr;
}
XBackend::XBackend(QObject *parent)
: Backend(parent)
{
setMode(Mode::X);
if (m_xcbConn) {
xcb_disconnect(m_xcbConn);
free(m_xcbConn);
}
}
void XBackend::init(QQuickView *view)
{
Backend::init(view);
m_xcbConn = xcb_connect(nullptr, nullptr);
if (!m_xcbConn) {
qDebug() << "Could not open XCB connection.";
}
}
void XBackend::lockRequest(bool persistent, QRect region)
{
Q_UNUSED(persistent);
Q_UNUSED(region);
auto winId = view()->winId();
/* Cursor needs to be hidden such that Xwayland emulates warps. */
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
auto cookie = xcb_warp_pointer_checked(m_xcbConn, /* connection */
XCB_NONE, /* src_w */
winId, /* dest_w */
0, /* src_x */
0, /* src_y */
0, /* src_width */
0, /* src_height */
20, /* dest_x */
20 /* dest_y */
);
xcb_flush(m_xcbConn);
xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
if (error) {
qDebug() << "Lock (warp) failed with XCB error:" << error->error_code;
free(error);
return;
}
qDebug() << "LOCK (warp)";
Q_EMIT lockChanged(true);
}
void XBackend::unlockRequest()
{
/* Xwayland unlocks the pointer, when the cursor is shown again. */
QGuiApplication::restoreOverrideCursor();
qDebug() << "------ Unlock requested ------";
Q_EMIT lockChanged(false);
}
void XBackend::confineRequest(bool persistent, QRect region)
{
Q_UNUSED(persistent);
Q_UNUSED(region);
int error;
if (!tryConfine(error)) {
qDebug() << "Confine (grab) failed with XCB error:" << error;
return;
}
qDebug() << "CONFINE (grab)";
Q_EMIT confineChanged(true);
}
void XBackend::unconfineRequest()
{
auto cookie = xcb_ungrab_pointer_checked(m_xcbConn, XCB_CURRENT_TIME);
xcb_flush(m_xcbConn);
xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
if (error) {
qDebug() << "Unconfine failed with XCB error:" << error->error_code;
free(error);
return;
}
qDebug() << "UNCONFINE (ungrab)";
Q_EMIT confineChanged(false);
}
void XBackend::hideAndConfineRequest(bool confineBeforeHide)
{
if (!confineBeforeHide) {
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
}
int error;
if (!tryConfine(error)) {
qDebug() << "Confine failed with XCB error:" << error;
if (!confineBeforeHide) {
QGuiApplication::restoreOverrideCursor();
}
return;
}
if (confineBeforeHide) {
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
}
qDebug() << "HIDE AND CONFINE (lock)";
Q_EMIT confineChanged(true);
}
void XBackend::undoHideRequest()
{
QGuiApplication::restoreOverrideCursor();
qDebug() << "UNDO HIDE AND CONFINE (unlock)";
}
bool XBackend::tryConfine(int &error)
{
auto winId = view()->winId();
auto cookie = xcb_grab_pointer(m_xcbConn, /* display */
1, /* owner_events */
winId, /* grab_window */
0, /* event_mask */
XCB_GRAB_MODE_ASYNC, /* pointer_mode */
XCB_GRAB_MODE_ASYNC, /* keyboard_mode */
winId, /* confine_to */
XCB_NONE, /* cursor */
XCB_CURRENT_TIME /* time */
);
xcb_flush(m_xcbConn);
xcb_generic_error_t *e = nullptr;
auto *reply = xcb_grab_pointer_reply(m_xcbConn, cookie, &e);
if (!reply) {
error = e->error_code;
free(e);
return false;
}
free(reply);
return true;
}
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
Backend *backend;
if (app.platformName() == QStringLiteral("wayland")) {
qDebug() << "Starting up: Wayland native mode";
backend = new WaylandBackend(&app);
} else {
qDebug() << "Starting up: Xserver/Xwayland legacy mode";
backend = new XBackend(&app);
}
QQuickView view;
QQmlContext* context = view.engine()->rootContext();
context->setContextProperty(QStringLiteral("org_kde_kwin_tests_pointerconstraints_backend"), backend);
view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) +QStringLiteral("/pointerconstraintstest.qml")));
view.show();
backend->init(&view);
return app.exec();
}