KWin
Loading...
Searching...
No Matches
pointerconstraintstest.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
7
8#include <KWayland/Client/compositor.h>
9#include <KWayland/Client/connection_thread.h>
10#include <KWayland/Client/pointer.h>
11#include <KWayland/Client/pointerconstraints.h>
12#include <KWayland/Client/region.h>
13#include <KWayland/Client/registry.h>
14#include <KWayland/Client/seat.h>
15#include <KWayland/Client/surface.h>
16
17#include <QCursor>
18#include <QGuiApplication>
19#include <QQmlContext>
20#include <QQmlEngine>
21
22#include <QDebug>
23
24#include <xcb/xproto.h>
25
26using namespace KWayland::Client;
27
29 : Backend(parent)
30 , m_connectionThreadObject(ConnectionThread::fromApplication(this))
31{
33}
34
35void WaylandBackend::init(QQuickView *view)
36{
38
39 Registry *registry = new Registry(this);
40 setupRegistry(registry);
41}
42
43void WaylandBackend::setupRegistry(Registry *registry)
44{
45 connect(registry, &Registry::compositorAnnounced, this,
46 [this, registry](quint32 name, quint32 version) {
47 m_compositor = registry->createCompositor(name, version, this);
48 });
49 connect(registry, &Registry::seatAnnounced, this,
50 [this, registry](quint32 name, quint32 version) {
51 m_seat = registry->createSeat(name, version, this);
52 if (m_seat->hasPointer()) {
53 m_pointer = m_seat->createPointer(this);
54 }
55 connect(m_seat, &Seat::hasPointerChanged, this,
56 [this]() {
57 delete m_pointer;
58 m_pointer = m_seat->createPointer(this);
59 });
60 });
61 connect(registry, &Registry::pointerConstraintsUnstableV1Announced, this,
62 [this, registry](quint32 name, quint32 version) {
63 m_pointerConstraints = registry->createPointerConstraints(name, version, this);
64 });
65 connect(registry, &Registry::interfacesAnnounced, this,
66 [this] {
67 Q_ASSERT(m_compositor);
68 Q_ASSERT(m_seat);
69 Q_ASSERT(m_pointerConstraints);
70 });
71 registry->create(m_connectionThreadObject);
72 registry->setup();
73}
74
75bool WaylandBackend::isLocked()
76{
77 return m_lockedPointer && m_lockedPointer->isValid();
78}
79
80bool WaylandBackend::isConfined()
81{
82 return m_confinedPointer && m_confinedPointer->isValid();
83}
84
85static PointerConstraints::LifeTime lifeTime(bool persistent)
86{
87 return persistent ? PointerConstraints::LifeTime::Persistent : PointerConstraints::LifeTime::OneShot;
88}
89
90void WaylandBackend::lockRequest(bool persistent, QRect region)
91{
92 if (isLocked()) {
93 if (!errorsAllowed()) {
94 qDebug() << "Abort locking because already locked. Allow errors to test relocking (and crashing).";
95 return;
96 }
97 qDebug() << "Trying to lock although already locked. Crash expected.";
98 }
99 if (isConfined()) {
100 if (!errorsAllowed()) {
101 qDebug() << "Abort locking because already confined. Allow errors to test locking while being confined (and crashing).";
102 return;
103 }
104 qDebug() << "Trying to lock although already confined. Crash expected.";
105 }
106 qDebug() << "------ Lock requested ------";
107 qDebug() << "Persistent:" << persistent << "| Region:" << region;
108 std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
109 std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
110 wlRegion->add(region);
111
112 auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.get(),
113 m_pointer,
114 wlRegion.get(),
115 lifeTime(persistent),
116 this);
117
118 if (!lockedPointer) {
119 qDebug() << "ERROR when receiving locked pointer!";
120 return;
121 }
122 m_lockedPointer = lockedPointer;
123 m_lockedPointerPersistent = persistent;
124
125 connect(lockedPointer, &LockedPointer::locked, this, [this]() {
126 qDebug() << "------ LOCKED! ------";
127 if (lockHint()) {
128 m_lockedPointer->setCursorPositionHint(QPointF(10., 10.));
129 Q_EMIT forceSurfaceCommit();
130 }
131
132 Q_EMIT lockChanged(true);
133 });
134 connect(lockedPointer, &LockedPointer::unlocked, this, [this]() {
135 qDebug() << "------ UNLOCKED! ------";
136 if (!m_lockedPointerPersistent) {
137 cleanupLock();
138 }
139 Q_EMIT lockChanged(false);
140 });
141}
142
144{
145 if (!m_lockedPointer) {
146 qDebug() << "Unlock requested, but there is no lock. Abort.";
147 return;
148 }
149 qDebug() << "------ Unlock requested ------";
150 cleanupLock();
151 Q_EMIT lockChanged(false);
152}
153void WaylandBackend::cleanupLock()
154{
155 if (!m_lockedPointer) {
156 return;
157 }
158 m_lockedPointer->release();
159 m_lockedPointer->deleteLater();
160 m_lockedPointer = nullptr;
161}
162
163void WaylandBackend::confineRequest(bool persistent, QRect region)
164{
165 if (isConfined()) {
166 if (!errorsAllowed()) {
167 qDebug() << "Abort confining because already confined. Allow errors to test reconfining (and crashing).";
168 return;
169 }
170 qDebug() << "Trying to lock although already locked. Crash expected.";
171 }
172 if (isLocked()) {
173 if (!errorsAllowed()) {
174 qDebug() << "Abort confining because already locked. Allow errors to test confining while being locked (and crashing).";
175 return;
176 }
177 qDebug() << "Trying to confine although already locked. Crash expected.";
178 }
179 qDebug() << "------ Confine requested ------";
180 qDebug() << "Persistent:" << persistent << "| Region:" << region;
181 std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
182 std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
183 wlRegion->add(region);
184
185 auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.get(),
186 m_pointer,
187 wlRegion.get(),
188 lifeTime(persistent),
189 this);
190
191 if (!confinedPointer) {
192 qDebug() << "ERROR when receiving confined pointer!";
193 return;
194 }
195 m_confinedPointer = confinedPointer;
196 m_confinedPointerPersistent = persistent;
197 connect(confinedPointer, &ConfinedPointer::confined, this, [this]() {
198 qDebug() << "------ CONFINED! ------";
199 Q_EMIT confineChanged(true);
200 });
201 connect(confinedPointer, &ConfinedPointer::unconfined, this, [this]() {
202 qDebug() << "------ UNCONFINED! ------";
203 if (!m_confinedPointerPersistent) {
204 cleanupConfine();
205 }
206 Q_EMIT confineChanged(false);
207 });
208}
210{
211 if (!m_confinedPointer) {
212 qDebug() << "Unconfine requested, but there is no confine. Abort.";
213 return;
214 }
215 qDebug() << "------ Unconfine requested ------";
216 cleanupConfine();
217 Q_EMIT confineChanged(false);
218}
219void WaylandBackend::cleanupConfine()
220{
221 if (!m_confinedPointer) {
222 return;
223 }
224 m_confinedPointer->release();
225 m_confinedPointer->deleteLater();
226 m_confinedPointer = nullptr;
227}
228
229XBackend::XBackend(QObject *parent)
230 : Backend(parent)
231{
233 if (m_xcbConn) {
234 xcb_disconnect(m_xcbConn);
235 free(m_xcbConn);
236 }
237}
238
239void XBackend::init(QQuickView *view)
240{
242 m_xcbConn = xcb_connect(nullptr, nullptr);
243 if (!m_xcbConn) {
244 qDebug() << "Could not open XCB connection.";
245 }
246}
247
248void XBackend::lockRequest(bool persistent, QRect region)
249{
250 auto winId = view()->winId();
251
252 /* Cursor needs to be hidden such that Xwayland emulates warps. */
253 QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
254
255 auto cookie = xcb_warp_pointer_checked(m_xcbConn, /* connection */
256 XCB_NONE, /* src_w */
257 winId, /* dest_w */
258 0, /* src_x */
259 0, /* src_y */
260 0, /* src_width */
261 0, /* src_height */
262 20, /* dest_x */
263 20 /* dest_y */
264 );
265 xcb_flush(m_xcbConn);
266
267 xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
268 if (error) {
269 qDebug() << "Lock (warp) failed with XCB error:" << error->error_code;
270 free(error);
271 return;
272 }
273 qDebug() << "LOCK (warp)";
274 Q_EMIT lockChanged(true);
275}
276
278{
279 /* Xwayland unlocks the pointer, when the cursor is shown again. */
280 QGuiApplication::restoreOverrideCursor();
281 qDebug() << "------ Unlock requested ------";
282 Q_EMIT lockChanged(false);
283}
284
285void XBackend::confineRequest(bool persistent, QRect region)
286{
287 int error;
288 if (!tryConfine(error)) {
289 qDebug() << "Confine (grab) failed with XCB error:" << error;
290 return;
291 }
292 qDebug() << "CONFINE (grab)";
293 Q_EMIT confineChanged(true);
294}
295
297{
298 auto cookie = xcb_ungrab_pointer_checked(m_xcbConn, XCB_CURRENT_TIME);
299 xcb_flush(m_xcbConn);
300
301 xcb_generic_error_t *error = xcb_request_check(m_xcbConn, cookie);
302 if (error) {
303 qDebug() << "Unconfine failed with XCB error:" << error->error_code;
304 free(error);
305 return;
306 }
307 qDebug() << "UNCONFINE (ungrab)";
308 Q_EMIT confineChanged(false);
309}
310
311void XBackend::hideAndConfineRequest(bool confineBeforeHide)
312{
313 if (!confineBeforeHide) {
314 QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
315 }
316
317 int error;
318 if (!tryConfine(error)) {
319 qDebug() << "Confine failed with XCB error:" << error;
320 if (!confineBeforeHide) {
321 QGuiApplication::restoreOverrideCursor();
322 }
323 return;
324 }
325 if (confineBeforeHide) {
326 QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
327 }
328 qDebug() << "HIDE AND CONFINE (lock)";
329 Q_EMIT confineChanged(true);
330}
331
333{
334 QGuiApplication::restoreOverrideCursor();
335 qDebug() << "UNDO HIDE AND CONFINE (unlock)";
336}
337
338bool XBackend::tryConfine(int &error)
339{
340 auto winId = view()->winId();
341
342 auto cookie = xcb_grab_pointer(m_xcbConn, /* display */
343 1, /* owner_events */
344 winId, /* grab_window */
345 0, /* event_mask */
346 XCB_GRAB_MODE_ASYNC, /* pointer_mode */
347 XCB_GRAB_MODE_ASYNC, /* keyboard_mode */
348 winId, /* confine_to */
349 XCB_NONE, /* cursor */
350 XCB_CURRENT_TIME /* time */
351 );
352 xcb_flush(m_xcbConn);
353
354 xcb_generic_error_t *e = nullptr;
355 auto *reply = xcb_grab_pointer_reply(m_xcbConn, cookie, &e);
356 if (!reply) {
357 error = e->error_code;
358 free(e);
359 return false;
360 }
361 free(reply);
362 return true;
363}
364
365int main(int argc, char **argv)
366{
367 QGuiApplication app(argc, argv);
368
369 Backend *backend;
370 if (app.platformName() == QStringLiteral("wayland")) {
371 qDebug() << "Starting up: Wayland native mode";
372 backend = new WaylandBackend(&app);
373 } else {
374 qDebug() << "Starting up: Xserver/Xwayland legacy mode";
375 backend = new XBackend(&app);
376 }
377
378 QQuickView view;
379
380 QQmlContext *context = view.engine()->rootContext();
381 context->setContextProperty(QStringLiteral("org_kde_kwin_tests_pointerconstraints_backend"), backend);
382
383 view.setSource(QUrl::fromLocalFile(QStringLiteral(DIR) + QStringLiteral("/pointerconstraintstest.qml")));
384 view.show();
385
386 backend->init(&view);
387
388 return app.exec();
389}
390
391#include "moc_pointerconstraintstest.cpp"
QQuickView * view() const
void lockChanged(bool locked)
void forceSurfaceCommit()
virtual void init(QQuickView *view)
void confineChanged(bool confined)
void setMode(Mode set)
void confineRequest(bool persistent, QRect region) override
void init(QQuickView *view) override
void unlockRequest() override
WaylandBackend(QObject *parent=nullptr)
void unconfineRequest() override
void lockRequest(bool persistent, QRect region) override
void undoHideRequest() override
void unlockRequest() override
void confineRequest(bool persistent, QRect region) override
void init(QQuickView *view) override
void unconfineRequest() override
XBackend(QObject *parent=nullptr)
void hideAndConfineRequest(bool confineBeforeHide) override
void lockRequest(bool persistent, QRect region) override
KWayland::Client::Registry * registry
constexpr int version