backends/wayland: Use software cursor when pointer is locked

Currently, if the pointer is locked, the wayland backend will create a
subsurface. However, when the cursor moves to one of the screen edges,
it looks weird because the xdg window geometry is computed as bounding
geometry of the main surface and its subsurfaces. However, even after
calling xdg_surface.set_window_geometry, it still looks weird because
subsurfaces are stacked above window decoration.

With the proposed change, when the pointer is locked, the wayland
backend will hide the host compositor and use the software cursor. If
the pointer is unlocked, it will go back to using host cursor.

Long term goal is to make every output have its own cursor to make
output layer logic simpler.
This commit is contained in:
Vlad Zahorodnii 2022-11-25 20:03:37 +02:00
parent 2fd9cc4fc1
commit c784217eae
6 changed files with 43 additions and 165 deletions

View file

@ -21,7 +21,6 @@
#include "cursor.h"
#include "dpmsinputeventfilter.h"
#include "input.h"
#include "pointer_input.h"
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/compositor.h>
@ -32,8 +31,6 @@
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/shm_pool.h>
#include <KWayland/Client/subcompositor.h>
#include <KWayland/Client/subsurface.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/touch.h>
@ -58,127 +55,59 @@ using namespace KWayland::Client;
WaylandCursor::WaylandCursor(WaylandBackend *backend)
: m_backend(backend)
, m_surface(backend->display()->compositor()->createSurface())
{
resetSurface();
}
WaylandCursor::~WaylandCursor() = default;
void WaylandCursor::resetSurface()
void WaylandCursor::enable()
{
m_surface.reset(backend()->display()->compositor()->createSurface());
Q_ASSERT(m_disableCount > 0);
--m_disableCount;
if (m_disableCount == 0) {
install();
}
}
void WaylandCursor::init()
void WaylandCursor::disable()
{
installImage();
++m_disableCount;
if (m_disableCount == 1) {
uninstall();
}
}
void WaylandCursor::installImage()
void WaylandCursor::install()
{
const QImage image = Cursors::self()->currentCursor()->image();
if (image.isNull() || image.size().isEmpty()) {
doInstallImage(nullptr, QSize(), 1);
if (m_disableCount || image.isNull() || image.size().isEmpty()) {
uninstall();
return;
}
auto *pointer = m_backend->seat()->pointerDevice()->nativePointer();
if (!pointer || !pointer->isValid()) {
return;
}
auto buffer = m_backend->display()->shmPool()->createBuffer(image).toStrongRef();
wl_buffer *imageBuffer = *buffer.data();
doInstallImage(imageBuffer, image.size(), image.devicePixelRatio());
pointer->setCursor(m_surface.get(), imageBuffer ? Cursors::self()->currentCursor()->hotspot() : QPoint());
m_surface->attachBuffer(imageBuffer);
m_surface->setScale(std::ceil(image.devicePixelRatio()));
m_surface->damageBuffer(image.rect());
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
void WaylandCursor::doInstallImage(wl_buffer *image, const QSize &size, qreal scale)
void WaylandCursor::uninstall()
{
auto *pointer = m_backend->seat()->pointerDevice()->nativePointer();
if (!pointer || !pointer->isValid()) {
return;
}
pointer->setCursor(m_surface.get(), image ? Cursors::self()->currentCursor()->hotspot() : QPoint());
drawSurface(image, size, scale);
}
void WaylandCursor::drawSurface(wl_buffer *image, const QSize &size, qreal scale)
{
m_surface->attachBuffer(image);
m_surface->setScale(std::ceil(scale));
m_surface->damageBuffer(QRect(QPoint(0, 0), size));
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
}
WaylandSubSurfaceCursor::WaylandSubSurfaceCursor(WaylandBackend *backend)
: WaylandCursor(backend)
{
}
WaylandSubSurfaceCursor::~WaylandSubSurfaceCursor() = default;
void WaylandSubSurfaceCursor::init()
{
if (auto *pointer = backend()->seat()->pointerDevice()->nativePointer()) {
pointer->hideCursor();
}
}
void WaylandSubSurfaceCursor::changeOutput(WaylandOutput *output)
{
m_subSurface.reset();
m_output = output;
if (!output) {
return;
}
createSubSurface();
surface()->commit();
}
void WaylandSubSurfaceCursor::createSubSurface()
{
if (m_subSurface) {
return;
}
if (!m_output) {
return;
}
resetSurface();
m_subSurface.reset(backend()->display()->subCompositor()->createSubSurface(surface(), m_output->surface()));
m_subSurface->setMode(SubSurface::Mode::Desynchronized);
}
void WaylandSubSurfaceCursor::doInstallImage(wl_buffer *image, const QSize &size, qreal scale)
{
if (!image) {
m_subSurface.reset();
return;
}
createSubSurface();
// cursor position might have changed due to different cursor hot spot
move(input()->pointer()->pos());
drawSurface(image, size, scale);
}
QPointF WaylandSubSurfaceCursor::absoluteToRelativePosition(const QPointF &position)
{
return position - m_output->geometry().topLeft() - Cursors::self()->currentCursor()->hotspot();
}
void WaylandSubSurfaceCursor::move(const QPointF &globalPosition)
{
auto *output = backend()->getOutputAt(globalPosition.toPoint());
if (!m_output || (output && m_output != output)) {
changeOutput(output);
if (!m_output) {
// cursor might be off the grid
return;
}
installImage();
return;
}
if (!m_subSurface) {
return;
}
// place the sub-surface relative to the output it is on and factor in the hotspot
const auto relativePosition = globalPosition.toPoint() - Cursors::self()->currentCursor()->hotspot() - m_output->geometry().topLeft();
m_subSurface->setPosition(relativePosition);
m_output->renderLoop()->scheduleRepaint();
pointer->hideCursor();
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat)
@ -577,26 +506,16 @@ bool WaylandBackend::initialize()
QObject::connect(dispatcher, &QAbstractEventDispatcher::awake, m_display.get(), &WaylandDisplay::flush);
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this]() {
if (!m_seat || !m_waylandCursor) {
return;
}
m_waylandCursor->installImage();
});
connect(Cursors::self(), &Cursors::positionChanged, this, [this](Cursor *cursor, const QPoint &position) {
if (m_waylandCursor) {
m_waylandCursor->move(position);
}
m_waylandCursor->install();
});
connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) {
if (locked) {
m_waylandCursor = std::make_unique<WaylandSubSurfaceCursor>(this);
m_waylandCursor->move(input()->pointer()->pos());
m_waylandCursor->disable();
m_seat->createRelativePointer();
} else {
m_seat->destroyRelativePointer();
m_waylandCursor = std::make_unique<WaylandCursor>(this);
m_waylandCursor->enable();
}
m_waylandCursor->init();
});
return true;

View file

@ -38,7 +38,6 @@ class PointerSwipeGesture;
class PointerPinchGesture;
class RelativePointer;
class Seat;
class SubSurface;
class Surface;
class Touch;
}
@ -61,52 +60,18 @@ class WaylandCursor
{
public:
explicit WaylandCursor(WaylandBackend *backend);
virtual ~WaylandCursor();
~WaylandCursor();
virtual void init();
virtual void move(const QPointF &globalPosition)
{
}
void enable();
void disable();
void installImage();
protected:
void resetSurface();
virtual void doInstallImage(wl_buffer *image, const QSize &size, qreal scale);
void drawSurface(wl_buffer *image, const QSize &size, qreal scale);
KWayland::Client::Surface *surface() const
{
return m_surface.get();
}
WaylandBackend *backend() const
{
return m_backend;
}
void install();
void uninstall();
private:
WaylandBackend *const m_backend;
std::unique_ptr<KWayland::Client::Surface> m_surface;
};
class WaylandSubSurfaceCursor : public WaylandCursor
{
public:
explicit WaylandSubSurfaceCursor(WaylandBackend *backend);
~WaylandSubSurfaceCursor() override;
void init() override;
void move(const QPointF &globalPosition) override;
private:
void changeOutput(WaylandOutput *output);
void doInstallImage(wl_buffer *image, const QSize &size, qreal scale) override;
void createSubSurface();
QPointF absoluteToRelativePosition(const QPointF &position);
WaylandOutput *m_output = nullptr;
std::unique_ptr<KWayland::Client::SubSurface> m_subSurface;
int m_disableCount = 0;
};
class WaylandInputDevice : public InputDevice

View file

@ -162,7 +162,6 @@ WaylandDisplay::~WaylandDisplay()
m_seat.reset();
m_xdgDecorationManager.reset();
m_shmPool.reset();
m_subCompositor.reset();
m_xdgShell.reset();
if (m_registry) {
@ -230,11 +229,6 @@ KWayland::Client::ShmPool *WaylandDisplay::shmPool() const
return m_shmPool.get();
}
KWayland::Client::SubCompositor *WaylandDisplay::subCompositor() const
{
return m_subCompositor.get();
}
KWayland::Client::Seat *WaylandDisplay::seat() const
{
return m_seat.get();
@ -260,9 +254,6 @@ void WaylandDisplay::registry_global(void *data, wl_registry *registry, uint32_t
}
display->m_compositor = std::make_unique<KWayland::Client::Compositor>();
display->m_compositor->setup(static_cast<wl_compositor *>(wl_registry_bind(registry, name, &wl_compositor_interface, std::min(version, 4u))));
} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
display->m_subCompositor = std::make_unique<KWayland::Client::SubCompositor>();
display->m_subCompositor->setup(static_cast<wl_subcompositor *>(wl_registry_bind(registry, name, &wl_subcompositor_interface, std::min(version, 1u))));
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
display->m_shmPool = std::make_unique<KWayland::Client::ShmPool>();
display->m_shmPool->setup(static_cast<wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, std::min(version, 1u))));

View file

@ -23,7 +23,6 @@ class PointerGestures;
class RelativePointerManager;
class Seat;
class ShmPool;
class SubCompositor;
class XdgDecorationManager;
class XdgShell;
}
@ -54,7 +53,6 @@ public:
KWayland::Client::Seat *seat() const;
KWayland::Client::XdgDecorationManager *xdgDecorationManager() const;
KWayland::Client::ShmPool *shmPool() const;
KWayland::Client::SubCompositor *subCompositor() const;
KWayland::Client::XdgShell *xdgShell() const;
public Q_SLOTS:
@ -74,7 +72,6 @@ private:
std::unique_ptr<KWayland::Client::Seat> m_seat;
std::unique_ptr<KWayland::Client::XdgDecorationManager> m_xdgDecorationManager;
std::unique_ptr<KWayland::Client::ShmPool> m_shmPool;
std::unique_ptr<KWayland::Client::SubCompositor> m_subCompositor;
std::unique_ptr<KWayland::Client::XdgShell> m_xdgShell;
};

View file

@ -86,6 +86,11 @@ RenderLoop *WaylandOutput::renderLoop() const
return m_renderLoop.get();
}
bool WaylandOutput::usesSoftwareCursor() const
{
return m_hasPointerLock;
}
void WaylandOutput::init(const QSize &pixelSize, qreal scale)
{
m_renderLoop->setRefreshRate(s_refreshRate);

View file

@ -40,6 +40,7 @@ public:
~WaylandOutput() override;
RenderLoop *renderLoop() const override;
bool usesSoftwareCursor() const override;
void init(const QSize &pixelSize, qreal scale);