157 lines
4.6 KiB
C++
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
|