File indexing completed on 2024-10-13 13:14:57
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 }