Add explicit output cursor manipulation api

Currently, output backends track the cursor behind the scenes. This
results in some amount of code duplication, for example the handling of
hidden cursors, every backend handles in its own unique way, some don't
do it correctly. Another issue is that output backend interact with
other components behind the back. This can be a problem for tasks such
as backing the cursor with an output layer.

This change introduces explicit output cursor manipulation APIs in the
Output class. There's a good chance that it's going to be revised more
in the future as part of streamlining output layer manipulation apis.

With the proposed changes, the workspace would need to call
Output::setCursor() or Output::moveCursor() to set/unset or move the
cursor, respectively.
This commit is contained in:
Vlad Zahorodnii 2022-12-02 23:17:40 +02:00
parent 6e0012a3c9
commit 35a99ec9ad
13 changed files with 143 additions and 118 deletions

View file

@ -780,9 +780,6 @@ void DrmGpu::recreateSurfaces()
for (const auto &output : std::as_const(m_virtualOutputs)) {
output->recreateSurface();
}
for (const auto &output : std::as_const(m_drmOutputs)) {
output->updateCursor();
}
}
DrmLease::DrmLease(DrmGpu *gpu, FileDescriptor &&fd, uint32_t lesseeId, const QVector<DrmOutput *> &outputs)

View file

@ -18,7 +18,6 @@
#include "core/renderloop.h"
#include "core/renderloop_p.h"
#include "core/session.h"
#include "cursor.h"
#include "drm_dumb_buffer.h"
#include "drm_dumb_swapchain.h"
#include "drm_egl_backend.h"
@ -95,12 +94,6 @@ DrmOutput::DrmOutput(DrmPipeline *pipeline)
connect(&m_turnOffTimer, &QTimer::timeout, this, [this] {
setDrmDpmsMode(DpmsMode::Off);
});
if (!conn->isNonDesktop()) {
connect(Cursors::self(), &Cursors::currentCursorChanged, this, &DrmOutput::updateCursor);
connect(Cursors::self(), &Cursors::hiddenChanged, this, &DrmOutput::updateCursor);
connect(Cursors::self(), &Cursors::positionChanged, this, &DrmOutput::moveCursor);
}
}
DrmOutput::~DrmOutput()
@ -139,35 +132,38 @@ DrmLease *DrmOutput::lease() const
return m_lease;
}
void DrmOutput::updateCursor()
bool DrmOutput::setCursor(const QImage &image, const QPoint &hotspot)
{
static bool valid;
static const bool forceSoftwareCursor = qEnvironmentVariableIntValue("KWIN_FORCE_SW_CURSOR", &valid) == 1 && valid;
// hardware cursors are broken with the NVidia proprietary driver
if (forceSoftwareCursor || (!valid && m_gpu->isNVidia())) {
m_setCursorSuccessful = false;
return;
return false;
}
const auto layer = m_pipeline->cursorLayer();
if (!m_pipeline->crtc() || !layer) {
return;
return false;
}
const Cursor *cursor = Cursors::self()->currentCursor();
if (!cursor || cursor->image().isNull() || Cursors::self()->isCursorHidden()) {
m_cursor.image = image;
m_cursor.hotspot = hotspot;
if (m_cursor.image.isNull()) {
if (layer->isVisible()) {
layer->setVisible(false);
m_pipeline->setCursor();
}
return;
return true;
}
bool rendered = false;
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform());
const QRect cursorRect = monitorMatrix.mapRect(cursor->geometry());
if (cursorRect.width() <= m_gpu->cursorSize().width() && cursorRect.height() <= m_gpu->cursorSize().height()) {
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(rect(), scale(), transform());
const QSize cursorSize = m_cursor.image.size() / m_cursor.image.devicePixelRatio();
const QRect cursorRect = QRect(m_cursor.position, cursorSize);
const QRect nativeCursorRect = monitorMatrix.mapRect(cursorRect);
if (nativeCursorRect.width() <= m_gpu->cursorSize().width() && nativeCursorRect.height() <= m_gpu->cursorSize().height()) {
if (const auto beginInfo = layer->beginFrame()) {
const auto &[renderTarget, repaint] = beginInfo.value();
if (dynamic_cast<EglGbmBackend *>(m_gpu->platform()->renderBackend())) {
renderCursorOpengl(renderTarget, cursor->geometry().size() * scale());
renderCursorOpengl(renderTarget, cursorSize * scale());
} else {
renderCursorQPainter(renderTarget);
}
@ -180,43 +176,49 @@ void DrmOutput::updateCursor()
m_pipeline->setCursor();
}
m_setCursorSuccessful = false;
return;
return false;
}
const QSize surfaceSize = m_gpu->cursorSize() / scale();
const QRect layerRect = monitorMatrix.mapRect(QRect(cursor->geometry().topLeft(), surfaceSize));
layer->setPosition(layerRect.topLeft());
layer->setVisible(cursor->geometry().intersects(geometry()));
const QSize layerSize = m_gpu->cursorSize() / scale();
const QRect layerRect = monitorMatrix.mapRect(QRect(m_cursor.position, layerSize));
layer->setVisible(cursorRect.intersects(rect()));
if (layer->isVisible()) {
m_setCursorSuccessful = m_pipeline->setCursor(logicalToNativeMatrix(QRect(QPoint(), layerRect.size()), scale(), transform()).map(cursor->hotspot()));
m_setCursorSuccessful = m_pipeline->setCursor(logicalToNativeMatrix(QRect(QPoint(), layerRect.size()), scale(), transform()).map(m_cursor.hotspot));
layer->setVisible(m_setCursorSuccessful);
}
return m_setCursorSuccessful;
}
void DrmOutput::moveCursor()
bool DrmOutput::moveCursor(const QPoint &position)
{
if (!m_setCursorSuccessful || !m_pipeline->crtc()) {
return;
return false;
}
const auto layer = m_pipeline->cursorLayer();
Cursor *cursor = Cursors::self()->currentCursor();
if (!cursor || cursor->image().isNull() || Cursors::self()->isCursorHidden() || !cursor->geometry().intersects(geometry())) {
m_cursor.position = position;
const QSize cursorSize = m_cursor.image.size() / m_cursor.image.devicePixelRatio();
const QRect cursorRect = QRect(m_cursor.position, cursorSize);
if (m_cursor.image.isNull() || !cursorRect.intersects(rect())) {
const auto layer = m_pipeline->cursorLayer();
if (layer->isVisible()) {
layer->setVisible(false);
m_pipeline->setCursor();
}
return;
return true;
}
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(geometry(), scale(), transform());
const QSize surfaceSize = m_gpu->cursorSize() / scale();
const QRect cursorRect = monitorMatrix.mapRect(QRect(cursor->geometry().topLeft(), surfaceSize));
const QMatrix4x4 monitorMatrix = logicalToNativeMatrix(rect(), scale(), transform());
const QSize layerSize = m_gpu->cursorSize() / scale();
const QRect layerRect = monitorMatrix.mapRect(QRect(m_cursor.position, layerSize));
const auto layer = m_pipeline->cursorLayer();
layer->setVisible(true);
layer->setPosition(cursorRect.topLeft());
layer->setPosition(layerRect.topLeft());
m_moveCursorSuccessful = m_pipeline->moveCursor();
layer->setVisible(m_moveCursorSuccessful);
if (!m_moveCursorSuccessful) {
m_pipeline->setCursor();
}
return m_moveCursorSuccessful;
}
QList<std::shared_ptr<OutputMode>> DrmOutput::getModes() const
@ -439,8 +441,6 @@ void DrmOutput::applyQueuedChanges(const OutputConfiguration &config)
if (isEnabled() && dpmsMode() == DpmsMode::On) {
m_gpu->platform()->turnOutputsOn();
}
updateCursor();
}
void DrmOutput::revertQueuedChanges()
@ -448,11 +448,6 @@ void DrmOutput::revertQueuedChanges()
m_pipeline->revertPendingChanges();
}
bool DrmOutput::usesSoftwareCursor() const
{
return !m_setCursorSuccessful || !m_moveCursorSuccessful;
}
DrmOutputLayer *DrmOutput::primaryLayer() const
{
return m_pipeline->primaryLayer();
@ -472,28 +467,22 @@ void DrmOutput::setColorTransformation(const std::shared_ptr<ColorTransformation
void DrmOutput::renderCursorOpengl(const RenderTarget &renderTarget, const QSize &cursorSize)
{
auto allocateTexture = [this]() {
const QImage img = Cursors::self()->currentCursor()->image();
if (img.isNull()) {
m_cursorTextureDirty = false;
return;
if (m_cursor.image.isNull()) {
m_cursor.texture.reset();
m_cursor.cacheKey = 0;
} else {
m_cursor.texture = std::make_unique<GLTexture>(m_cursor.image);
m_cursor.texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_cursor.cacheKey = m_cursor.image.cacheKey();
}
m_cursorTexture = std::make_unique<GLTexture>(img);
m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
m_cursorTextureDirty = false;
};
if (!m_cursorTexture) {
if (!m_cursor.texture) {
allocateTexture();
// handle shape update on case cursor image changed
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this]() {
m_cursorTextureDirty = true;
});
} else if (m_cursorTextureDirty) {
const QImage image = Cursors::self()->currentCursor()->image();
if (image.size() == m_cursorTexture->size()) {
m_cursorTexture->update(image);
m_cursorTextureDirty = false;
} else if (m_cursor.cacheKey != m_cursor.image.cacheKey()) {
if (m_cursor.image.size() == m_cursor.texture->size()) {
m_cursor.texture->update(m_cursor.image);
m_cursor.cacheKey = m_cursor.image.cacheKey();
} else {
allocateTexture();
}
@ -508,18 +497,17 @@ void DrmOutput::renderCursorOpengl(const RenderTarget &renderTarget, const QSize
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_cursorTexture->bind();
m_cursor.texture->bind();
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp);
m_cursorTexture->render(QRect(0, 0, cursorSize.width(), cursorSize.height()), renderTarget.devicePixelRatio());
m_cursorTexture->unbind();
m_cursor.texture->render(QRect(0, 0, cursorSize.width(), cursorSize.height()), renderTarget.devicePixelRatio());
m_cursor.texture->unbind();
glDisable(GL_BLEND);
}
void DrmOutput::renderCursorQPainter(const RenderTarget &renderTarget)
{
const Cursor *cursor = Cursors::self()->currentCursor();
const QImage cursorImage = cursor->image();
const QRect cursorRect(QPoint(0, 0), m_cursor.image.size() / m_cursor.image.devicePixelRatio());
QImage *c = std::get<QImage *>(renderTarget.nativeHandle());
c->setDevicePixelRatio(scale());
@ -527,9 +515,9 @@ void DrmOutput::renderCursorQPainter(const RenderTarget &renderTarget)
QPainter p;
p.begin(c);
p.setWorldTransform(logicalToNativeMatrix(cursor->rect(), 1, transform()).toTransform());
p.setWorldTransform(logicalToNativeMatrix(cursorRect, 1, transform()).toTransform());
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawImage(QPoint(0, 0), cursorImage);
p.drawImage(QPoint(0, 0), m_cursor.image);
p.end();
}
}

View file

@ -50,9 +50,8 @@ public:
void updateModes();
void updateDpmsMode(DpmsMode dpmsMode);
bool usesSoftwareCursor() const override;
void updateCursor();
void moveCursor();
bool setCursor(const QImage &image, const QPoint &hotspot) override;
bool moveCursor(const QPoint &position) override;
DrmLease *lease() const;
bool addLeaseObjects(QVector<uint32_t> &objectList);
@ -75,10 +74,17 @@ private:
bool m_setCursorSuccessful = false;
bool m_moveCursorSuccessful = false;
bool m_cursorTextureDirty = true;
std::unique_ptr<GLTexture> m_cursorTexture;
QTimer m_turnOffTimer;
DrmLease *m_lease = nullptr;
struct {
QImage image;
QPoint hotspot;
QPoint position;
std::unique_ptr<GLTexture> texture;
qint64 cacheKey = 0;
} m_cursor;
};
}

View file

@ -12,7 +12,6 @@
#include <errno.h>
#include "core/session.h"
#include "cursor.h"
#include "drm_backend.h"
#include "drm_buffer.h"
#include "drm_buffer_gbm.h"

View file

@ -8,7 +8,6 @@
*/
#include "wayland_output.h"
#include "core/renderloop_p.h"
#include "cursor.h"
#include "wayland_backend.h"
#include "wayland_display.h"
#include "wayland_server.h"
@ -60,7 +59,7 @@ void WaylandCursor::enable()
{
if (!m_enabled) {
m_enabled = true;
update();
sync();
}
}
@ -68,23 +67,29 @@ void WaylandCursor::disable()
{
if (m_enabled) {
m_enabled = false;
update();
sync();
}
}
void WaylandCursor::update()
void WaylandCursor::update(KWayland::Client::Buffer::Ptr buffer, qreal scale, const QPoint &hotspot)
{
const QImage image = Cursors::self()->currentCursor()->image();
if (!m_enabled || Cursors::self()->isCursorHidden() || image.isNull()) {
m_buffer = buffer;
m_scale = scale;
m_hotspot = hotspot;
sync();
}
void WaylandCursor::sync()
{
if (!m_enabled) {
m_surface->attachBuffer(KWayland::Client::Buffer::Ptr());
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
m_hotspot = QPoint();
} else {
m_surface->attachBuffer(m_backend->display()->shmPool()->createBuffer(image));
m_surface->setScale(std::ceil(image.devicePixelRatio()));
m_surface->damageBuffer(image.rect());
m_surface->attachBuffer(m_buffer);
m_surface->setScale(std::ceil(m_scale));
m_surface->damageBuffer(QRect(0, 0, INT32_MAX, INT32_MAX));
m_surface->commit(KWayland::Client::Surface::CommitFlag::None);
m_hotspot = Cursors::self()->currentCursor()->hotspot();
}
if (m_pointer) {
@ -128,10 +133,6 @@ WaylandOutput::WaylandOutput(const QString &name, WaylandBackend *backend)
connect(m_xdgShellSurface.get(), &XdgShellSurface::closeRequested, qApp, &QCoreApplication::quit);
connect(this, &WaylandOutput::enabledChanged, this, &WaylandOutput::updateWindowTitle);
connect(this, &WaylandOutput::dpmsModeChanged, this, &WaylandOutput::updateWindowTitle);
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this]() {
m_cursor->update();
});
}
WaylandOutput::~WaylandOutput()
@ -161,9 +162,20 @@ RenderLoop *WaylandOutput::renderLoop() const
return m_renderLoop.get();
}
bool WaylandOutput::usesSoftwareCursor() const
bool WaylandOutput::setCursor(const QImage &image, const QPoint &hotspot)
{
return m_hasPointerLock;
KWayland::Client::Buffer::Ptr buffer;
if (!image.isNull()) {
buffer = m_backend->display()->shmPool()->createBuffer(image);
}
m_cursor->update(buffer, image.devicePixelRatio(), hotspot);
return !m_hasPointerLock;
}
bool WaylandOutput::moveCursor(const QPoint &position)
{
// The cursor position is controlled by the host compositor.
return !m_hasPointerLock;
}
void WaylandOutput::init(const QSize &pixelSize, qreal scale)

View file

@ -10,6 +10,7 @@
#include "core/output.h"
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/xdgshell.h>
#include <QObject>
@ -43,13 +44,17 @@ public:
void enable();
void disable();
void update();
void update(KWayland::Client::Buffer::Ptr buffer, qreal scale, const QPoint &hotspot);
private:
void sync();
WaylandBackend *const m_backend;
KWayland::Client::Pointer *m_pointer = nullptr;
std::unique_ptr<KWayland::Client::Surface> m_surface;
KWayland::Client::Buffer::Ptr m_buffer;
QPoint m_hotspot;
qreal m_scale = 1;
bool m_enabled = true;
};
@ -61,7 +66,8 @@ public:
~WaylandOutput() override;
RenderLoop *renderLoop() const override;
bool usesSoftwareCursor() const override;
bool setCursor(const QImage &image, const QPoint &hotspot) override;
bool moveCursor(const QPoint &position) override;
void init(const QSize &pixelSize, qreal scale);

View file

@ -58,11 +58,6 @@ void X11Output::setGammaRampSize(int size)
m_gammaRampSize = size;
}
bool X11Output::usesSoftwareCursor() const
{
return false;
}
void X11Output::updateEnabled(bool enabled)
{
State next = m_state;

View file

@ -34,8 +34,6 @@ public:
void updateEnabled(bool enabled);
bool usesSoftwareCursor() const override;
RenderLoop *renderLoop() const override;
void setRenderLoop(RenderLoop *loop);

View file

@ -12,7 +12,6 @@
#include <config-kwin.h>
#include "core/renderloop_p.h"
#include "cursor.h"
#include "softwarevsyncmonitor.h"
#include "x11_windowed_backend.h"
@ -198,10 +197,6 @@ void X11WindowedOutput::init(const QSize &pixelSize, qreal scale)
addIcon(QSize(48, 48));
m_cursor = std::make_unique<X11WindowedCursor>(this);
connect(Cursors::self(), &Cursors::currentCursorChanged, this, [this]() {
KWin::Cursor *c = KWin::Cursors::self()->currentCursor();
m_cursor->update(c->image(), c->hotspot());
});
xcb_map_window(m_backend->connection(), m_window);
}
@ -263,9 +258,16 @@ void X11WindowedOutput::vblank(std::chrono::nanoseconds timestamp)
renderLoopPrivate->notifyFrameCompleted(timestamp);
}
bool X11WindowedOutput::usesSoftwareCursor() const
bool X11WindowedOutput::setCursor(const QImage &image, const QPoint &hotspot)
{
return false;
m_cursor->update(image, hotspot);
return true;
}
bool X11WindowedOutput::moveCursor(const QPoint &position)
{
// The cursor position is controlled by the host compositor.
return true;
}
void X11WindowedOutput::updateEnabled(bool enabled)

View file

@ -78,7 +78,8 @@ public:
*/
QPointF mapFromGlobal(const QPointF &pos) const;
bool usesSoftwareCursor() const override;
bool setCursor(const QImage &image, const QPoint &hotspot) override;
bool moveCursor(const QPoint &position) override;
QRegion exposedArea() const;
void addExposedArea(const QRect &rect);

View file

@ -430,15 +430,30 @@ void Compositor::addOutput(Output *output)
auto updateCursorLayer = [output, cursorLayer]() {
const Cursor *cursor = Cursors::self()->currentCursor();
cursorLayer->setVisible(cursor->isOnOutput(output) && output->usesSoftwareCursor());
cursorLayer->setGeometry(output->mapFromGlobal(cursor->geometry()));
const QRect layerRect = output->mapFromGlobal(cursor->geometry());
bool usesHardwareCursor = false;
if (!Cursors::self()->isCursorHidden()) {
usesHardwareCursor = output->setCursor(cursor->image(), cursor->hotspot()) && output->moveCursor(layerRect.topLeft());
} else {
usesHardwareCursor = output->setCursor(QImage(), QPoint());
}
cursorLayer->setVisible(cursor->isOnOutput(output) && !usesHardwareCursor);
cursorLayer->setGeometry(layerRect);
cursorLayer->addRepaintFull();
};
auto moveCursorLayer = [output, cursorLayer]() {
const Cursor *cursor = Cursors::self()->currentCursor();
const QRect layerRect = output->mapFromGlobal(cursor->geometry());
const bool usesHardwareCursor = output->moveCursor(layerRect.topLeft());
cursorLayer->setVisible(cursor->isOnOutput(output) && !usesHardwareCursor);
cursorLayer->setGeometry(layerRect);
cursorLayer->addRepaintFull();
};
updateCursorLayer();
connect(output, &Output::geometryChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::currentCursorChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::hiddenChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, updateCursorLayer);
connect(Cursors::self(), &Cursors::positionChanged, cursorLayer, moveCursorLayer);
addSuperLayer(workspaceLayer);
}

View file

@ -144,11 +144,6 @@ std::chrono::milliseconds Output::dimAnimationTime()
return std::chrono::milliseconds(KSharedConfig::openConfig()->group("Effect-Kscreen").readEntry("Duration", 250));
}
bool Output::usesSoftwareCursor() const
{
return true;
}
QRect Output::mapFromGlobal(const QRect &rect) const
{
return rect.translated(-geometry().topLeft());
@ -421,4 +416,14 @@ Output::Transform Output::panelOrientation() const
return m_information.panelOrientation;
}
bool Output::setCursor(const QImage &image, const QPoint &hotspot)
{
return false;
}
bool Output::moveCursor(const QPoint &position)
{
return false;
}
} // namespace KWin

View file

@ -229,8 +229,6 @@ public:
Q_ENUM(Transform)
Transform transform() const;
virtual bool usesSoftwareCursor() const;
void applyChanges(const OutputConfiguration &config);
SubPixel subPixel() const;
@ -262,6 +260,9 @@ public:
virtual void setColorTransformation(const std::shared_ptr<ColorTransformation> &transformation);
virtual bool setCursor(const QImage &image, const QPoint &hotspot);
virtual bool moveCursor(const QPoint &position);
Q_SIGNALS:
/**
* This signal is emitted when the geometry of this output has changed.