kate-deft/src/plugin/waylandcursorwarp.cpp

157 lines
4.6 KiB
C++

/*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "waylandcursorwarp.h"
#include <QGuiApplication>
#include <QTimer>
#include <QWindow>
#include <qpa/qplatformnativeinterface.h>
#include <QtGui/qguiapplication_platform.h> // QNativeInterface::QWaylandApplication
#include <wayland-client.h>
#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<WaylandCursorWarp *>(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<QNativeInterface::QWaylandApplication>();
}
} // 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<uint32_t>(version, zwp_pointer_constraints_v1_interface.version);
m_constraints = static_cast<zwp_pointer_constraints_v1 *>(
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<wl_surface *>(
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