Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
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/pointer.h>
|
||||
#include <KWayland/Client/pointerconstraints.h>
|
||||
#include <KWayland/Client/region.h>
|
||||
#include <KWayland/Client/registry.h>
|
||||
#include <KWayland/Client/seat.h>
|
||||
#include <KWayland/Client/surface.h>
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#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;
|
||||
std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
|
||||
std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
|
||||
wlRegion->add(region);
|
||||
|
||||
auto *lockedPointer = m_pointerConstraints->lockPointer(winSurface.get(),
|
||||
m_pointer,
|
||||
wlRegion.get(),
|
||||
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;
|
||||
std::unique_ptr<Surface> winSurface(Surface::fromWindow(view()));
|
||||
std::unique_ptr<Region> wlRegion(m_compositor->createRegion(this));
|
||||
wlRegion->add(region);
|
||||
|
||||
auto *confinedPointer = m_pointerConstraints->confinePointer(winSurface.get(),
|
||||
m_pointer,
|
||||
wlRegion.get(),
|
||||
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 (xcb_connection_has_error(m_xcbConn)) {
|
||||
xcb_disconnect(m_xcbConn);
|
||||
qFatal() << "Could not open XCB connection.";
|
||||
}
|
||||
}
|
||||
|
||||
void XBackend::lockRequest(bool persistent, QRect 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
#include "moc_pointerconstraintstest.cpp"
|
||||
Reference in New Issue
Block a user