/* * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "waylandcursorwarp.h" #include #include #include #include #include // QNativeInterface::QWaylandApplication #include #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" namespace katecustom { namespace { // --- registry listener trampoline --------------------------------------- void registryGlobal(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { static_cast(data)->onGlobal(registry, name, interface, version); } void registryGlobalRemove(void *, wl_registry *, uint32_t) {} const wl_registry_listener kRegistryListener = { registryGlobal, registryGlobalRemove, }; // Pull a wl_* handle out of Qt's Wayland application native interface. QNativeInterface::QWaylandApplication *waylandApp() { return qGuiApp->nativeInterface(); } } // namespace WaylandCursorWarp::WaylandCursorWarp(QObject *parent) : QObject(parent) { } WaylandCursorWarp::~WaylandCursorWarp() { if (m_constraints) { zwp_pointer_constraints_v1_destroy(m_constraints); m_constraints = nullptr; } } bool WaylandCursorWarp::isWaylandSession() { return QGuiApplication::platformName().startsWith(QLatin1String("wayland")); } void WaylandCursorWarp::onGlobal(wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (qstrcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) { // Bind at most the version we were built against. const uint32_t bindVersion = qMin(version, zwp_pointer_constraints_v1_interface.version); m_constraints = static_cast( wl_registry_bind(registry, name, &zwp_pointer_constraints_v1_interface, bindVersion)); } } bool WaylandCursorWarp::ensureConstraints() { if (m_constraints) { return true; } if (m_triedBind) { return false; // already looked; the compositor does not offer it } m_triedBind = true; auto *app = waylandApp(); if (!app) { return false; } wl_display *display = app->display(); if (!display) { return false; } wl_registry *registry = wl_display_get_registry(display); if (!registry) { return false; } wl_registry_add_listener(registry, &kRegistryListener, this); // Round-trip so the global advertisement is processed and onGlobal() runs. wl_display_roundtrip(display); wl_registry_destroy(registry); return m_constraints != nullptr; } bool WaylandCursorWarp::warp(QWindow *window, const QPointF &surfaceLocalPos) { if (!window || !isWaylandSession()) { return false; } if (!ensureConstraints()) { return false; } auto *app = waylandApp(); if (!app) { return false; } wl_pointer *pointer = app->pointer(); wl_display *display = app->display(); if (!pointer || !display) { return false; } QPlatformNativeInterface *pni = QGuiApplication::platformNativeInterface(); if (!pni) { return false; } auto *surface = static_cast( pni->nativeResourceForWindow(QByteArrayLiteral("surface"), window)); if (!surface) { return false; } // Lock the pointer to the surface it is already over, hint the target // position (surface-local), commit so the double-buffered hint is pending, // then release the lock — the compositor warps to the hint on unlock. zwp_locked_pointer_v1 *lock = zwp_pointer_constraints_v1_lock_pointer( m_constraints, surface, pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT); if (!lock) { return false; } zwp_locked_pointer_v1_set_cursor_position_hint( lock, wl_fixed_from_double(surfaceLocalPos.x()), wl_fixed_from_double(surfaceLocalPos.y())); wl_surface_commit(surface); wl_display_flush(display); // Release on the next event-loop turn: the lock must have been applied by // the compositor (pointer is over the surface) before we unlock, otherwise // the position hint is discarded. Destroying later keeps the sequence // compatible with compositors that apply the hint strictly on unlock. QTimer::singleShot(0, this, [lock, display]() { zwp_locked_pointer_v1_destroy(lock); wl_display_flush(display); }); return true; } } // namespace katecustom