File indexing completed on 2024-04-28 16:49:18

0001 /*
0002     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 #include "pointerconstraintstest.h"
0007 
0008 #include <KWayland/Client/compositor.h>
0009 #include <KWayland/Client/connection_thread.h>
0010 #include <KWayland/Client/pointer.h>
0011 #include <KWayland/Client/pointerconstraints.h>
0012 #include <KWayland/Client/region.h>
0013 #include <KWayland/Client/registry.h>
0014 #include <KWayland/Client/seat.h>
0015 #include <KWayland/Client/surface.h>
0016 
0017 #include <QCursor>
0018 #include <QGuiApplication>
0019 #include <QQmlContext>
0020 #include <QQmlEngine>
0021 
0022 #include <QDebug>
0023 
0024 #include <xcb/xproto.h>
0025 
0026 using namespace KWayland::Client;
0027 
0028 WaylandBackend::WaylandBackend(QObject *parent)
0029     : Backend(parent)
0030     , m_connectionThreadObject(ConnectionThread::fromApplication(this))
0031 {
0032     setMode(Mode::Wayland);
0033 }
0034 
0035 void WaylandBackend::init(QQuickView *view)
0036 {
0037     Backend::init(view);
0038 
0039     Registry *registry = new Registry(this);
0040     setupRegistry(registry);
0041 }
0042 
0043 void WaylandBackend::setupRegistry(Registry *registry)
0044 {
0045     connect(registry, &Registry::compositorAnnounced, this,
0046             [this, registry](quint32 name, quint32 version) {
0047                 m_compositor = registry->createCompositor(name, version, this);
0048             });
0049     connect(registry, &Registry::seatAnnounced, this,
0050             [this, registry](quint32 name, quint32 version) {
0051                 m_seat = registry->createSeat(name, version, this);
0052                 if (m_seat->hasPointer()) {
0053                     m_pointer = m_seat->createPointer(this);
0054                 }
0055                 connect(m_seat, &Seat::hasPointerChanged, this,
0056                         [this]() {
0057                             delete m_pointer;
0058                             m_pointer = m_seat->createPointer(this);
0059                         });
0060             });
0061     connect(registry, &Registry::pointerConstraintsUnstableV1Announced, this,
0062             [this, registry](quint32 name, quint32 version) {
0063                 m_pointerConstraints = registry->createPointerConstraints(name, version, this);
0064             });
0065     connect(registry, &Registry::interfacesAnnounced, this,
0066             [this] {
0067                 Q_ASSERT(m_compositor);
0068                 Q_ASSERT(m_seat);
0069                 Q_ASSERT(m_pointerConstraints);
0070             });
0071     registry->create(m_connectionThreadObject);
0072     registry->setup();
0073 }
0074 
0075 bool WaylandBackend::isLocked()
0076 {
0077     return m_lockedPointer && m_lockedPointer->isValid();
0078 }
0079 
0080 bool WaylandBackend::isConfined()
0081 {
0082     return m_confinedPointer && m_confinedPointer->isValid();
0083 }
0084 
0085 static PointerConstraints::LifeTime lifeTime(bool persistent)
0086 {
0087     return persistent ? PointerConstraints::LifeTime::Persistent : PointerConstraints::LifeTime::OneShot;
0088 }
0089 
0090 void WaylandBackend::lockRequest(bool persistent, QRect region)
0091 {
0092     if (isLocked()) {
0093         if (!errorsAllowed()) {
0094             qDebug() << "Abort locking because already locked. Allow errors to test relocking (and crashing).";
0095             return;
0096         }
0097         qDebug() << "Trying to lock although already locked. Crash expected.";
0098     }
0099     if (isConfined()) {
0100         if (!errorsAllowed()) {
0101             qDebug() << "Abort locking because already confined. Allow errors to test locking while being confined (and crashing).";
0102             return;
0103         }
0104         qDebug() << "Trying to lock although already confined. Crash expected.";
0105     }
0106     qDebug() << "------ Lock requested ------";
0107     qDebug() << "Persistent:" << persistent << "| Region:" << region;
0108     std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
0109     std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
0110     wlRegion->add(region);
0111 
0112     auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.get(),
0113                                                             m_pointer,
0114                                                             wlRegion.get(),
0115                                                             lifeTime(persistent),
0116                                                             this);
0117 
0118     if (!lockedPointer) {
0119         qDebug() << "ERROR when receiving locked pointer!";
0120         return;
0121     }
0122     m_lockedPointer = lockedPointer;
0123     m_lockedPointerPersistent = persistent;
0124 
0125     connect(lockedPointer, &LockedPointer::locked, this, [this]() {
0126         qDebug() << "------ LOCKED! ------";
0127         if (lockHint()) {
0128             m_lockedPointer->setCursorPositionHint(QPointF(10., 10.));
0129             Q_EMIT forceSurfaceCommit();
0130         }
0131 
0132         Q_EMIT lockChanged(true);
0133     });
0134     connect(lockedPointer, &LockedPointer::unlocked, this, [this]() {
0135         qDebug() << "------ UNLOCKED! ------";
0136         if (!m_lockedPointerPersistent) {
0137             cleanupLock();
0138         }
0139         Q_EMIT lockChanged(false);
0140     });
0141 }
0142 
0143 void WaylandBackend::unlockRequest()
0144 {
0145     if (!m_lockedPointer) {
0146         qDebug() << "Unlock requested, but there is no lock. Abort.";
0147         return;
0148     }
0149     qDebug() << "------ Unlock requested ------";
0150     cleanupLock();
0151     Q_EMIT lockChanged(false);
0152 }
0153 void WaylandBackend::cleanupLock()
0154 {
0155     if (!m_lockedPointer) {
0156         return;
0157     }
0158     m_lockedPointer->release();
0159     m_lockedPointer->deleteLater();
0160     m_lockedPointer = nullptr;
0161 }
0162 
0163 void WaylandBackend::confineRequest(bool persistent, QRect region)
0164 {
0165     if (isConfined()) {
0166         if (!errorsAllowed()) {
0167             qDebug() << "Abort confining because already confined. Allow errors to test reconfining (and crashing).";
0168             return;
0169         }
0170         qDebug() << "Trying to lock although already locked. Crash expected.";
0171     }
0172     if (isLocked()) {
0173         if (!errorsAllowed()) {
0174             qDebug() << "Abort confining because already locked. Allow errors to test confining while being locked (and crashing).";
0175             return;
0176         }
0177         qDebug() << "Trying to confine although already locked. Crash expected.";
0178     }
0179     qDebug() << "------ Confine requested ------";
0180     qDebug() << "Persistent:" << persistent << "| Region:" << region;
0181     std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
0182     std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
0183     wlRegion->add(region);
0184 
0185     auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.get(),
0186                                                                  m_pointer,
0187                                                                  wlRegion.get(),
0188                                                                  lifeTime(persistent),
0189                                                                  this);
0190 
0191     if (!confinedPointer) {
0192         qDebug() << "ERROR when receiving confined pointer!";
0193         return;
0194     }
0195     m_confinedPointer = confinedPointer;
0196     m_confinedPointerPersistent = persistent;
0197     connect(confinedPointer, &ConfinedPointer::confined, this, [this]() {
0198         qDebug() << "------ CONFINED! ------";
0199         Q_EMIT confineChanged(true);
0200     });
0201     connect(confinedPointer, &ConfinedPointer::unconfined, this, [this]() {
0202         qDebug() << "------ UNCONFINED! ------";
0203         if (!m_confinedPointerPersistent) {
0204             cleanupConfine();
0205         }
0206         Q_EMIT confineChanged(false);
0207     });
0208 }
0209 void WaylandBackend::unconfineRequest()
0210 {
0211     if (!m_confinedPointer) {
0212         qDebug() << "Unconfine requested, but there is no confine. Abort.";
0213         return;
0214     }
0215     qDebug() << "------ Unconfine requested ------";
0216     cleanupConfine();
0217     Q_EMIT confineChanged(false);
0218 }
0219 void WaylandBackend::cleanupConfine()
0220 {
0221     if (!m_confinedPointer) {
0222         return;
0223     }
0224     m_confinedPointer->release();
0225     m_confinedPointer->deleteLater();
0226     m_confinedPointer = nullptr;
0227 }
0228 
0229 XBackend::XBackend(QObject *parent)
0230     : Backend(parent)
0231 {
0232     setMode(Mode::X);
0233     if (m_xcbConn) {
0234         xcb_disconnect(m_xcbConn);
0235         free(m_xcbConn);
0236     }
0237 }
0238 
0239 void XBackend::init(QQuickView *view)
0240 {
0241     Backend::init(view);
0242     m_xcbConn = xcb_connect(nullptr, nullptr);
0243     if (!m_xcbConn) {
0244         qDebug() << "Could not open XCB connection.";
0245     }
0246 }
0247 
0248 void XBackend::lockRequest(bool persistent, QRect region)
0249 {
0250     auto winId = view()->winId();
0251 
0252     /* Cursor needs to be hidden such that Xwayland emulates warps. */
0253     QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
0254 
0255     auto cookie = xcb_warp_pointer_checked(m_xcbConn, /* connection */
0256                                            XCB_NONE, /* src_w */
0257                                            winId, /* dest_w */
0258                                            0, /* src_x */
0259                                            0, /* src_y */
0260                                            0, /* src_width */
0261                                            0, /* src_height */
0262                                            20, /* dest_x */
0263                                            20 /* dest_y */
0264     );
0265     xcb_flush(m_xcbConn);
0266 
0267     xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
0268     if (error) {
0269         qDebug() << "Lock (warp) failed with XCB error:" << error->error_code;
0270         free(error);
0271         return;
0272     }
0273     qDebug() << "LOCK (warp)";
0274     Q_EMIT lockChanged(true);
0275 }
0276 
0277 void XBackend::unlockRequest()
0278 {
0279     /* Xwayland unlocks the pointer, when the cursor is shown again. */
0280     QGuiApplication::restoreOverrideCursor();
0281     qDebug() << "------ Unlock requested ------";
0282     Q_EMIT lockChanged(false);
0283 }
0284 
0285 void XBackend::confineRequest(bool persistent, QRect region)
0286 {
0287     int error;
0288     if (!tryConfine(error)) {
0289         qDebug() << "Confine (grab) failed with XCB error:" << error;
0290         return;
0291     }
0292     qDebug() << "CONFINE (grab)";
0293     Q_EMIT confineChanged(true);
0294 }
0295 
0296 void XBackend::unconfineRequest()
0297 {
0298     auto cookie = xcb_ungrab_pointer_checked(m_xcbConn, XCB_CURRENT_TIME);
0299     xcb_flush(m_xcbConn);
0300 
0301     xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
0302     if (error) {
0303         qDebug() << "Unconfine failed with XCB error:" << error->error_code;
0304         free(error);
0305         return;
0306     }
0307     qDebug() << "UNCONFINE (ungrab)";
0308     Q_EMIT confineChanged(false);
0309 }
0310 
0311 void XBackend::hideAndConfineRequest(bool confineBeforeHide)
0312 {
0313     if (!confineBeforeHide) {
0314         QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
0315     }
0316 
0317     int error;
0318     if (!tryConfine(error)) {
0319         qDebug() << "Confine failed with XCB error:" << error;
0320         if (!confineBeforeHide) {
0321             QGuiApplication::restoreOverrideCursor();
0322         }
0323         return;
0324     }
0325     if (confineBeforeHide) {
0326         QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
0327     }
0328     qDebug() << "HIDE AND CONFINE (lock)";
0329     Q_EMIT confineChanged(true);
0330 }
0331 
0332 void XBackend::undoHideRequest()
0333 {
0334     QGuiApplication::restoreOverrideCursor();
0335     qDebug() << "UNDO HIDE AND CONFINE (unlock)";
0336 }
0337 
0338 bool XBackend::tryConfine(int &error)
0339 {
0340     auto winId = view()->winId();
0341 
0342     auto cookie = xcb_grab_pointer(m_xcbConn, /* display */
0343                                    1, /* owner_events */
0344                                    winId, /* grab_window */
0345                                    0, /* event_mask */
0346                                    XCB_GRAB_MODE_ASYNC, /* pointer_mode */
0347                                    XCB_GRAB_MODE_ASYNC, /* keyboard_mode */
0348                                    winId, /* confine_to */
0349                                    XCB_NONE, /* cursor */
0350                                    XCB_CURRENT_TIME /* time */
0351     );
0352     xcb_flush(m_xcbConn);
0353 
0354     xcb_generic_error_t *e = nullptr;
0355     auto *reply = xcb_grab_pointer_reply(m_xcbConn, cookie, &e);
0356     if (!reply) {
0357         error = e->error_code;
0358         free(e);
0359         return false;
0360     }
0361     free(reply);
0362     return true;
0363 }
0364 
0365 int main(int argc, char **argv)
0366 {
0367     QGuiApplication app(argc, argv);
0368 
0369     Backend *backend;
0370     if (app.platformName() == QStringLiteral("wayland")) {
0371         qDebug() << "Starting up: Wayland native mode";
0372         backend = new WaylandBackend(&app);
0373     } else {
0374         qDebug() << "Starting up: Xserver/Xwayland legacy mode";
0375         backend = new XBackend(&app);
0376     }
0377 
0378     QQuickView view;
0379 
0380     QQmlContext *context = view.engine()->rootContext();
0381     context->setContextProperty(QStringLiteral("org_kde_kwin_tests_pointerconstraints_backend"), backend);
0382 
0383     view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) + QStringLiteral("/pointerconstraintstest.qml")));
0384     view.show();
0385 
0386     backend->init(&view);
0387 
0388     return app.exec();
0389 }