Rework cursor image handling for Wayland

So far updating the cursor image was not really defined. It was possible
to use the cursor image from the wayland seat or have a custom set cursor
image. But there are no rules in place to decide which one to use when.

With this change a dedicated CursorImage class is introduced which tracks
the cursor image changes on the seat, on the decoration, in the effects
and so on. In addition it tracks which is the current source for the
image, that is whether e.g. the cursor from the seat or from effects
override should be used. Whenever the cursor image changes a signal is
emitted, which is connected to the signal in AbstractBackend.

Based on that the backends can directly show the image. The existing
code in the backends to install a cursor shape or to install the cursor
from the server is completely dropped. For the backend it's irrelevant
from where the image comes from.

A new feature added is that the cursor image is marked as rendered. This
is then passed on to the frame rendered in the Surface and thus animated
cursors are finally working. Unfortunately animated cursors are broken in
Qt (see https://bugreports.qt.io/browse/QTBUG-48181 ).
icc-effect-5.14.5
Martin Gräßlin 2016-02-23 12:29:05 +01:00
parent 5acf9abda8
commit a029300ce5
13 changed files with 425 additions and 226 deletions

View File

@ -23,18 +23,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "composite.h" #include "composite.h"
#include "cursor.h" #include "cursor.h"
#include "input.h" #include "input.h"
#include "pointer_input.h"
#include "scene_opengl.h" #include "scene_opengl.h"
#include "wayland_server.h" #include "wayland_server.h"
#include "wayland_cursor_theme.h"
// KWayland
#include <KWayland/Client/buffer.h>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Server/buffer_interface.h>
#include <KWayland/Server/clientconnection.h>
#include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/surface_interface.h>
// Wayland
#include <wayland-cursor.h>
namespace KWin namespace KWin
{ {
@ -50,87 +41,14 @@ AbstractBackend::~AbstractBackend()
WaylandServer::self()->uninstallBackend(this); WaylandServer::self()->uninstallBackend(this);
} }
void AbstractBackend::installCursorFromServer() QImage AbstractBackend::softwareCursor() const
{ {
if (!m_softWareCursor) { return input()->pointer()->cursorImage();
return;
}
triggerCursorRepaint();
updateCursorFromServer();
} }
void AbstractBackend::updateCursorFromServer() QPoint AbstractBackend::softwareCursorHotspot() const
{ {
if (!waylandServer() || !waylandServer()->seat()->focusedPointer()) { return input()->pointer()->cursorHotSpot();
return;
}
auto c = waylandServer()->seat()->focusedPointer()->cursor();
if (!c) {
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
return;
}
auto buffer = cursorSurface.data()->buffer();
if (!buffer) {
return;
}
m_cursor.hotspot = c->hotspot();
m_cursor.image = buffer->data().copy();
emit cursorChanged();
}
void AbstractBackend::installCursorImage(Qt::CursorShape shape)
{
if (!m_softWareCursor) {
return;
}
updateCursorImage(shape);
}
void AbstractBackend::updateCursorImage(Qt::CursorShape shape)
{
if (!m_cursorTheme) {
// check whether we can create it
if (waylandServer() && waylandServer()->internalShmPool()) {
m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this);
connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this,
[this] {
delete m_cursorTheme;
m_cursorTheme = nullptr;
}
);
}
}
if (!m_cursorTheme) {
return;
}
wl_cursor_image *cursor = m_cursorTheme->get(shape);
if (!cursor) {
return;
}
wl_buffer *b = wl_cursor_image_get_buffer(cursor);
if (!b) {
return;
}
waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch();
installThemeCursor(KWayland::Client::Buffer::getId(b), QPoint(cursor->hotspot_x, cursor->hotspot_y));
}
void AbstractBackend::installThemeCursor(quint32 id, const QPoint &hotspot)
{
auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(id));
if (!buffer) {
return;
}
if (m_softWareCursor) {
triggerCursorRepaint();
}
m_cursor.hotspot = hotspot;
m_cursor.image = buffer->data().copy();
emit cursorChanged();
} }
Screens *AbstractBackend::createScreens(QObject *parent) Screens *AbstractBackend::createScreens(QObject *parent)
@ -164,17 +82,24 @@ void AbstractBackend::setSoftWareCursor(bool set)
void AbstractBackend::triggerCursorRepaint() void AbstractBackend::triggerCursorRepaint()
{ {
if (!Compositor::self() || m_cursor.image.isNull()) { if (!Compositor::self()) {
return; return;
} }
Compositor::self()->addRepaint(m_cursor.lastRenderedPosition.x() - m_cursor.hotspot.x(), const QPoint &hotSpot = softwareCursorHotspot();
m_cursor.lastRenderedPosition.y() - m_cursor.hotspot.y(), const QSize &size = softwareCursor().size();
m_cursor.image.width(), m_cursor.image.height()); Compositor::self()->addRepaint(m_cursor.lastRenderedPosition.x() - hotSpot.x(),
m_cursor.lastRenderedPosition.y() - hotSpot.y(),
size.width(), size.height());
} }
void AbstractBackend::markCursorAsRendered() void AbstractBackend::markCursorAsRendered()
{ {
m_cursor.lastRenderedPosition = Cursor::pos(); if (m_softWareCursor) {
m_cursor.lastRenderedPosition = Cursor::pos();
}
if (input()->pointer()) {
input()->pointer()->markCursorAsRendered();
}
} }
void AbstractBackend::keyboardKeyPressed(quint32 key, quint32 time) void AbstractBackend::keyboardKeyPressed(quint32 key, quint32 time)

View File

@ -40,8 +40,6 @@ public:
virtual ~AbstractBackend(); virtual ~AbstractBackend();
virtual void init() = 0; virtual void init() = 0;
virtual void installCursorFromServer();
virtual void installCursorImage(Qt::CursorShape shape);
virtual Screens *createScreens(QObject *parent = nullptr); virtual Screens *createScreens(QObject *parent = nullptr);
virtual OpenGLBackend *createOpenGLBackend(); virtual OpenGLBackend *createOpenGLBackend();
virtual QPainterBackend *createQPainterBackend(); virtual QPainterBackend *createQPainterBackend();
@ -78,12 +76,8 @@ public:
bool usesSoftwareCursor() const { bool usesSoftwareCursor() const {
return m_softWareCursor; return m_softWareCursor;
} }
QImage softwareCursor() const { QImage softwareCursor() const;
return m_cursor.image; QPoint softwareCursorHotspot() const;
}
QPoint softwareCursorHotspot() const {
return m_cursor.hotspot;
}
void markCursorAsRendered(); void markCursorAsRendered();
bool handlesOutputs() const { bool handlesOutputs() const {
@ -143,8 +137,6 @@ Q_SIGNALS:
protected: protected:
explicit AbstractBackend(QObject *parent = nullptr); explicit AbstractBackend(QObject *parent = nullptr);
void setSoftWareCursor(bool set); void setSoftWareCursor(bool set);
void updateCursorFromServer();
void updateCursorImage(Qt::CursorShape shape);
void handleOutputs() { void handleOutputs() {
m_handlesOutputs = true; m_handlesOutputs = true;
} }
@ -162,14 +154,10 @@ protected:
private: private:
void triggerCursorRepaint(); void triggerCursorRepaint();
void installThemeCursor(quint32 id, const QPoint &hotspot);
bool m_softWareCursor = false; bool m_softWareCursor = false;
struct { struct {
QPoint hotspot;
QImage image;
QPoint lastRenderedPosition; QPoint lastRenderedPosition;
} m_cursor; } m_cursor;
WaylandCursorTheme *m_cursorTheme = nullptr;
bool m_handlesOutputs = false; bool m_handlesOutputs = false;
bool m_ready = false; bool m_ready = false;
QSize m_initialWindowSize; QSize m_initialWindowSize;

View File

@ -529,16 +529,6 @@ void DrmBackend::present(DrmBuffer *buffer, DrmOutput *output)
} }
} }
void DrmBackend::installCursorFromServer()
{
updateCursorFromServer();
}
void DrmBackend::installCursorImage(Qt::CursorShape shape)
{
updateCursorImage(shape);
}
void DrmBackend::initCursor() void DrmBackend::initCursor()
{ {
uint64_t capability = 0; uint64_t capability = 0;
@ -562,7 +552,6 @@ void DrmBackend::initCursor()
// now we have screens and can set cursors, so start tracking // now we have screens and can set cursors, so start tracking
connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor); connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor);
connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor); connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor);
installCursorImage(Qt::ArrowCursor);
} }
void DrmBackend::setCursor() void DrmBackend::setCursor()
@ -572,6 +561,7 @@ void DrmBackend::setCursor()
for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) { for (auto it = m_outputs.constBegin(); it != m_outputs.constEnd(); ++it) {
(*it)->showCursor(c); (*it)->showCursor(c);
} }
markCursorAsRendered();
} }
void DrmBackend::updateCursor() void DrmBackend::updateCursor()

View File

@ -71,8 +71,6 @@ public:
Screens *createScreens(QObject *parent = nullptr) override; Screens *createScreens(QObject *parent = nullptr) override;
QPainterBackend *createQPainterBackend() override; QPainterBackend *createQPainterBackend() override;
OpenGLBackend* createOpenGLBackend() override; OpenGLBackend* createOpenGLBackend() override;
void installCursorFromServer() override;
void installCursorImage(Qt::CursorShape shape) override;
void init() override; void init() override;
DrmBuffer *createBuffer(const QSize &size); DrmBuffer *createBuffer(const QSize &size);

View File

@ -68,7 +68,6 @@ WaylandSeat::WaylandSeat(wl_seat *seat, WaylandBackend *backend)
, m_keyboard(NULL) , m_keyboard(NULL)
, m_touch(nullptr) , m_touch(nullptr)
, m_cursor(NULL) , m_cursor(NULL)
, m_theme(new WaylandCursorTheme(backend->shmPool(), this))
, m_enteredSerial(0) , m_enteredSerial(0)
, m_backend(backend) , m_backend(backend)
, m_installCursor(false) , m_installCursor(false)
@ -244,21 +243,15 @@ void WaylandSeat::installCursorImage(wl_buffer *image, const QSize &size, const
m_cursor->attachBuffer(image); m_cursor->attachBuffer(image);
m_cursor->damage(QRect(QPoint(0,0), size)); m_cursor->damage(QRect(QPoint(0,0), size));
m_cursor->commit(Surface::CommitFlag::None); m_cursor->commit(Surface::CommitFlag::None);
} m_backend->flush();
void WaylandSeat::installCursorImage(Qt::CursorShape shape)
{
wl_cursor_image *image = m_theme->get(shape);
if (!image) {
return;
}
installCursorImage(wl_cursor_image_get_buffer(image),
QSize(image->width, image->height),
QPoint(image->hotspot_x, image->hotspot_y));
} }
void WaylandSeat::installCursorImage(const QImage &image, const QPoint &hotSpot) void WaylandSeat::installCursorImage(const QImage &image, const QPoint &hotSpot)
{ {
if (image.isNull()) {
installCursorImage(nullptr, QSize(), QPoint());
return;
}
installCursorImage(*(m_backend->shmPool()->createBuffer(image).data()), image.size(), hotSpot); installCursorImage(*(m_backend->shmPool()->createBuffer(image).data()), image.size(), hotSpot);
} }
@ -337,6 +330,15 @@ void WaylandBackend::init()
if (!deviceIdentifier().isEmpty()) { if (!deviceIdentifier().isEmpty()) {
m_connectionThreadObject->setSocketName(deviceIdentifier()); m_connectionThreadObject->setSocketName(deviceIdentifier());
} }
connect(this, &WaylandBackend::cursorChanged, this,
[this] {
if (m_seat.isNull() || !m_seat->isInstallCursor()) {
return;
}
m_seat->installCursorImage(softwareCursor(), softwareCursorHotspot());
markCursorAsRendered();
}
);
initConnection(); initConnection();
} }
@ -389,35 +391,6 @@ void WaylandBackend::initConnection()
m_connectionThreadObject->initConnection(); m_connectionThreadObject->initConnection();
} }
void WaylandBackend::installCursorImage(Qt::CursorShape shape)
{
if (!m_seat.isNull() && m_seat->isInstallCursor()) {
m_seat->installCursorImage(shape);
}
}
void WaylandBackend::installCursorFromServer()
{
if (!waylandServer() || !waylandServer()->seat()->focusedPointer()) {
return;
}
auto c = waylandServer()->seat()->focusedPointer()->cursor();
if (c) {
auto cursorSurface = c->surface();
if (!cursorSurface.isNull()) {
auto buffer = cursorSurface.data()->buffer();
if (buffer) {
// set cursor
if (!m_seat.isNull() && m_seat->isInstallCursor()) {
m_seat->installCursorImage(buffer->data(), c->hotspot());
}
return;
}
}
}
// TODO: unset cursor
}
void WaylandBackend::createSurface() void WaylandBackend::createSurface()
{ {
m_surface = m_compositor->createSurface(this); m_surface = m_compositor->createSurface(this);
@ -478,6 +451,13 @@ QPainterBackend *WaylandBackend::createQPainterBackend()
return new WaylandQPainterBackend(this); return new WaylandQPainterBackend(this);
} }
void WaylandBackend::flush()
{
if (m_connectionThreadObject) {
m_connectionThreadObject->flush();
}
}
} }
} // KWin } // KWin

View File

@ -74,7 +74,6 @@ public:
virtual ~WaylandSeat(); virtual ~WaylandSeat();
void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot); void installCursorImage(wl_buffer *image, const QSize &size, const QPoint &hotspot);
void installCursorImage(Qt::CursorShape shape);
void installCursorImage(const QImage &image, const QPoint &hotspot); void installCursorImage(const QImage &image, const QPoint &hotspot);
void setInstallCursor(bool install); void setInstallCursor(bool install);
bool isInstallCursor() const { bool isInstallCursor() const {
@ -89,7 +88,6 @@ private:
KWayland::Client::Keyboard *m_keyboard; KWayland::Client::Keyboard *m_keyboard;
KWayland::Client::Touch *m_touch; KWayland::Client::Touch *m_touch;
KWayland::Client::Surface *m_cursor; KWayland::Client::Surface *m_cursor;
WaylandCursorTheme *m_theme = nullptr;
uint32_t m_enteredSerial; uint32_t m_enteredSerial;
WaylandBackend *m_backend; WaylandBackend *m_backend;
bool m_installCursor; bool m_installCursor;
@ -116,8 +114,6 @@ public:
KWayland::Client::Surface *surface() const; KWayland::Client::Surface *surface() const;
QSize shellSurfaceSize() const; QSize shellSurfaceSize() const;
void installCursorImage(Qt::CursorShape shape) override;
void installCursorFromServer() override;
Screens *createScreens(QObject *parent = nullptr) override; Screens *createScreens(QObject *parent = nullptr) override;
OpenGLBackend *createOpenGLBackend() override; OpenGLBackend *createOpenGLBackend() override;
@ -127,6 +123,8 @@ public:
return shellSurfaceSize(); return shellSurfaceSize();
} }
void flush();
Q_SIGNALS: Q_SIGNALS:
void shellSurfaceSizeChanged(const QSize &size); void shellSurfaceSizeChanged(const QSize &size);
void systemCompositorDied(); void systemCompositorDied();

View File

@ -93,6 +93,11 @@ void X11WindowedBackend::init()
XRenderUtils::init(m_connection, m_screen->root); XRenderUtils::init(m_connection, m_screen->root);
createWindow(); createWindow();
startEventReading(); startEventReading();
connect(this, &X11WindowedBackend::cursorChanged, this,
[this] {
createCursor(softwareCursor(), softwareCursorHotspot());
}
);
setReady(true); setReady(true);
waylandServer()->seat()->setHasPointer(true); waylandServer()->seat()->setHasPointer(true);
waylandServer()->seat()->setHasKeyboard(true); waylandServer()->seat()->setHasKeyboard(true);
@ -374,25 +379,6 @@ void X11WindowedBackend::updateSize(xcb_configure_notify_event_t *event)
} }
} }
void X11WindowedBackend::installCursorFromServer()
{
if (!waylandServer() || !waylandServer()->seat()->focusedPointer()) {
return;
}
auto c = waylandServer()->seat()->focusedPointer()->cursor();
if (c) {
auto cursorSurface = c->surface();
if (!cursorSurface.isNull()) {
auto buffer = cursorSurface.data()->buffer();
if (buffer) {
createCursor(buffer->data(), c->hotspot());
return;
}
}
}
// TODO: unset cursor
}
void X11WindowedBackend::createCursor(const QImage &img, const QPoint &hotspot) void X11WindowedBackend::createCursor(const QImage &img, const QPoint &hotspot)
{ {
const xcb_pixmap_t pix = xcb_generate_id(m_connection); const xcb_pixmap_t pix = xcb_generate_id(m_connection);
@ -417,13 +403,7 @@ void X11WindowedBackend::createCursor(const QImage &img, const QPoint &hotspot)
} }
m_cursor = cid; m_cursor = cid;
xcb_flush(m_connection); xcb_flush(m_connection);
} markCursorAsRendered();
void X11WindowedBackend::installCursorImage(Qt::CursorShape shape)
{
// TODO: only update if shape changed
updateCursorImage(shape);
createCursor(softwareCursor(), softwareCursorHotspot());
} }
xcb_window_t X11WindowedBackend::rootWindow() const xcb_window_t X11WindowedBackend::rootWindow() const

View File

@ -63,9 +63,6 @@ public:
} }
xcb_window_t rootWindow() const; xcb_window_t rootWindow() const;
void installCursorFromServer() override;
void installCursorImage(Qt::CursorShape shape) override;
Screens *createScreens(QObject *parent = nullptr) override; Screens *createScreens(QObject *parent = nullptr) override;
OpenGLBackend *createOpenGLBackend() override; OpenGLBackend *createOpenGLBackend() override;
QPainterBackend* createQPainterBackend() override; QPainterBackend* createQPainterBackend() override;

View File

@ -30,6 +30,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "client.h" #include "client.h"
#include "cursor.h" #include "cursor.h"
#include "group.h" #include "group.h"
#include "pointer_input.h"
#include "scene_xrender.h" #include "scene_xrender.h"
#include "scene_qpainter.h" #include "scene_qpainter.h"
#include "unmanaged.h" #include "unmanaged.h"
@ -709,9 +710,7 @@ void EffectsHandlerImpl::startMouseInterception(Effect *effect, Qt::CursorShape
return; return;
} }
if (kwinApp()->operationMode() != Application::OperationModeX11) { if (kwinApp()->operationMode() != Application::OperationModeX11) {
if (AbstractBackend *w = waylandServer()->backend()) { input()->pointer()->setEffectsOverrideCursor(shape);
w->installCursorImage(shape);
}
return; return;
} }
// NOTE: it is intended to not perform an XPointerGrab on X11. See documentation in kwineffects.h // NOTE: it is intended to not perform an XPointerGrab on X11. See documentation in kwineffects.h
@ -743,6 +742,7 @@ void EffectsHandlerImpl::stopMouseInterception(Effect *effect)
} }
m_grabbedMouseEffects.removeAll(effect); m_grabbedMouseEffects.removeAll(effect);
if (kwinApp()->operationMode() != Application::OperationModeX11) { if (kwinApp()->operationMode() != Application::OperationModeX11) {
input()->pointer()->removeEffectsOverrideCursor();
return; return;
} }
if (m_grabbedMouseEffects.isEmpty()) { if (m_grabbedMouseEffects.isEmpty()) {
@ -751,6 +751,11 @@ void EffectsHandlerImpl::stopMouseInterception(Effect *effect)
} }
} }
bool EffectsHandlerImpl::isMouseInterception() const
{
return m_grabbedMouseEffects.count() > 0;
}
void EffectsHandlerImpl::registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) void EffectsHandlerImpl::registerGlobalShortcut(const QKeySequence &shortcut, QAction *action)
{ {
input()->registerShortcut(shortcut, action); input()->registerShortcut(shortcut, action);
@ -1225,11 +1230,7 @@ QSize EffectsHandlerImpl::virtualScreenSize() const
void EffectsHandlerImpl::defineCursor(Qt::CursorShape shape) void EffectsHandlerImpl::defineCursor(Qt::CursorShape shape)
{ {
if (!m_mouseInterceptionWindow.isValid()) { if (!m_mouseInterceptionWindow.isValid()) {
if (waylandServer()) { input()->pointer()->setEffectsOverrideCursor(shape);
if (AbstractBackend *w = waylandServer()->backend()) {
w->installCursorImage(shape);
}
}
return; return;
} }
const xcb_cursor_t c = Cursor::x11Cursor(shape); const xcb_cursor_t c = Cursor::x11Cursor(shape);

View File

@ -123,6 +123,7 @@ public:
// not performing XGrabPointer // not performing XGrabPointer
void startMouseInterception(Effect *effect, Qt::CursorShape shape) override; void startMouseInterception(Effect *effect, Qt::CursorShape shape) override;
void stopMouseInterception(Effect *effect) override; void stopMouseInterception(Effect *effect) override;
bool isMouseInterception() const;
void registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) override; void registerGlobalShortcut(const QKeySequence &shortcut, QAction *action) override;
void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) override; void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) override;
void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) override; void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) override;

View File

@ -443,7 +443,6 @@ public:
if (event->type() == QEvent::MouseButtonRelease) { if (event->type() == QEvent::MouseButtonRelease) {
decoration->client()->processDecorationButtonRelease(&e); decoration->client()->processDecorationButtonRelease(&e);
} }
input()->pointer()->installCursorFromDecoration();
return true; return true;
} }
default: default:

View File

@ -19,20 +19,29 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/ *********************************************************************/
#include "pointer_input.h" #include "pointer_input.h"
#include "abstract_backend.h" #include "abstract_backend.h"
#include "effects.h"
#include "screens.h" #include "screens.h"
#include "shell_client.h" #include "shell_client.h"
#include "wayland_cursor_theme.h"
#include "wayland_server.h" #include "wayland_server.h"
#include "workspace.h" #include "workspace.h"
#include "decorations/decoratedclient.h" #include "decorations/decoratedclient.h"
// KDecoration // KDecoration
#include <KDecoration2/Decoration> #include <KDecoration2/Decoration>
// KWayland // KWayland
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/buffer.h>
#include <KWayland/Server/buffer_interface.h>
#include <KWayland/Server/display.h>
#include <KWayland/Server/seat_interface.h> #include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/surface_interface.h>
// screenlocker // screenlocker
#include <KScreenLocker/KsldApp> #include <KScreenLocker/KsldApp>
#include <QHoverEvent> #include <QHoverEvent>
#include <QWindow> #include <QWindow>
// Wayland
#include <wayland-cursor.h>
#include <linux/input.h> #include <linux/input.h>
@ -100,6 +109,7 @@ static bool screenContainsPos(const QPointF &pos)
PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) PointerInputRedirection::PointerInputRedirection(InputRedirection* parent)
: QObject(parent) : QObject(parent)
, m_input(parent) , m_input(parent)
, m_cursor(nullptr)
, m_supportsWarping(Application::usesLibinput()) , m_supportsWarping(Application::usesLibinput())
{ {
} }
@ -109,7 +119,10 @@ PointerInputRedirection::~PointerInputRedirection() = default;
void PointerInputRedirection::init() void PointerInputRedirection::init()
{ {
Q_ASSERT(!m_inited); Q_ASSERT(!m_inited);
m_cursor = new CursorImage(this);
m_inited = true; m_inited = true;
connect(m_cursor, &CursorImage::changed, waylandServer()->backend(), &AbstractBackend::cursorChanged);
emit m_cursor->changed();
connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update);
connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange);
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update);
@ -219,6 +232,9 @@ void PointerInputRedirection::update()
if (m_decoration || m_internalWindow) { if (m_decoration || m_internalWindow) {
t = nullptr; t = nullptr;
} }
if (m_decoration != oldDeco) {
emit decorationChanged();
}
auto oldWindow = m_window; auto oldWindow = m_window;
if (!oldWindow.isNull() && t == m_window.data()) { if (!oldWindow.isNull() && t == m_window.data()) {
return; return;
@ -231,11 +247,6 @@ void PointerInputRedirection::update()
} }
disconnect(m_windowGeometryConnection); disconnect(m_windowGeometryConnection);
m_windowGeometryConnection = QMetaObject::Connection(); m_windowGeometryConnection = QMetaObject::Connection();
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
disconnect(c, &KWayland::Server::Cursor::changed, waylandServer()->backend(), &AbstractBackend::installCursorFromServer);
}
}
} }
if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) { if (AbstractClient *c = qobject_cast<AbstractClient*>(t)) {
// only send enter if it wasn't on deco for the same client before // only send enter if it wasn't on deco for the same client before
@ -263,12 +274,6 @@ void PointerInputRedirection::update()
seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation()); seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation());
} }
); );
waylandServer()->backend()->installCursorFromServer();
if (auto p = seat->focusedPointer()) {
if (auto c = p->cursor()) {
connect(c, &KWayland::Server::Cursor::changed, waylandServer()->backend(), &AbstractBackend::installCursorFromServer);
}
}
} else { } else {
seat->setFocusedPointerSurface(nullptr); seat->setFocusedPointerSurface(nullptr);
t = nullptr; t = nullptr;
@ -372,9 +377,6 @@ void PointerInputRedirection::updateDecoration(Toplevel *t)
// send leave // send leave
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
if (!m_decoration) {
waylandServer()->backend()->installCursorImage(Qt::ArrowCursor);
}
} }
if (m_decoration) { if (m_decoration) {
if (m_decoration->client() != oldWindow) { if (m_decoration->client() != oldWindow) {
@ -385,7 +387,6 @@ void PointerInputRedirection::updateDecoration(Toplevel *t)
QHoverEvent event(QEvent::HoverMove, p, p); QHoverEvent event(QEvent::HoverMove, p, p);
QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event);
m_decoration->client()->processDecorationMove(); m_decoration->client()->processDecorationMove();
installCursorFromDecoration();
} }
} }
@ -445,14 +446,6 @@ bool PointerInputRedirection::supportsWarping() const
return false; return false;
} }
void PointerInputRedirection::installCursorFromDecoration()
{
if (!m_inited || !m_decoration) {
return;
}
waylandServer()->backend()->installCursorImage(m_decoration->client()->cursor());
}
void PointerInputRedirection::updateAfterScreenChange() void PointerInputRedirection::updateAfterScreenChange()
{ {
if (!m_inited) { if (!m_inited) {
@ -468,4 +461,286 @@ void PointerInputRedirection::updateAfterScreenChange()
processMotion(pos, waylandServer()->seat()->timestamp()); processMotion(pos, waylandServer()->seat()->timestamp());
} }
QImage PointerInputRedirection::cursorImage() const
{
if (!m_inited) {
return QImage();
}
return m_cursor->image();
}
QPoint PointerInputRedirection::cursorHotSpot() const
{
if (!m_inited) {
return QPoint();
}
return m_cursor->hotSpot();
}
void PointerInputRedirection::markCursorAsRendered()
{
if (!m_inited) {
return;
}
m_cursor->markAsRendered();
}
void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape)
{
if (!m_inited) {
return;
}
m_cursor->setEffectsOverrideCursor(shape);
}
void PointerInputRedirection::removeEffectsOverrideCursor()
{
if (!m_inited) {
return;
}
m_cursor->removeEffectsOverrideCursor();
}
CursorImage::CursorImage(PointerInputRedirection *parent)
: QObject(parent)
, m_pointer(parent)
{
connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update);
connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource);
connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration);
loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor);
m_surfaceRenderedTimer.start();
}
CursorImage::~CursorImage() = default;
void CursorImage::markAsRendered()
{
if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) {
return;
}
auto p = waylandServer()->seat()->focusedPointer();
if (!p) {
return;
}
auto c = p->cursor();
if (!c) {
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
return;
}
cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed());
}
void CursorImage::update()
{
using namespace KWayland::Server;
disconnect(m_serverCursor.connection);
auto p = waylandServer()->seat()->focusedPointer();
if (p) {
m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor);
} else {
m_serverCursor.connection = QMetaObject::Connection();
}
updateServerCursor();
}
void CursorImage::updateDecoration()
{
disconnect(m_decorationConnection);
auto deco = m_pointer->decoration();
AbstractClient *c = deco.isNull() ? nullptr : deco->client();
if (c) {
m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor);
} else {
m_decorationConnection = QMetaObject::Connection();
}
updateDecorationCursor();
}
void CursorImage::updateDecorationCursor()
{
m_decorationCursor.image = QImage();
m_decorationCursor.hotSpot = QPoint();
auto deco = m_pointer->decoration();
if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) {
loadThemeCursor(c->cursor(), &m_decorationCursor);
if (m_currentSource == CursorSource::Decoration) {
emit changed();
}
}
reevaluteSource();
}
void CursorImage::updateServerCursor()
{
m_serverCursor.image = QImage();
m_serverCursor.hotSpot = QPoint();
reevaluteSource();
const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface;
auto p = waylandServer()->seat()->focusedPointer();
if (!p) {
if (needsEmit) {
emit changed();
}
return;
}
auto c = p->cursor();
if (!c) {
if (needsEmit) {
emit changed();
}
return;
}
auto cursorSurface = c->surface();
if (cursorSurface.isNull()) {
if (needsEmit) {
emit changed();
}
return;
}
auto buffer = cursorSurface.data()->buffer();
if (!buffer) {
if (needsEmit) {
emit changed();
}
return;
}
m_serverCursor.hotSpot = c->hotspot();
m_serverCursor.image = buffer->data().copy();
if (needsEmit) {
emit changed();
}
}
void CursorImage::loadTheme()
{
if (m_cursorTheme) {
return;
}
// check whether we can create it
if (waylandServer()->internalShmPool()) {
m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this);
connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this,
[this] {
delete m_cursorTheme;
m_cursorTheme = nullptr;
}
);
}
}
void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape)
{
loadThemeCursor(shape, &m_effectsCursor);
if (m_currentSource == CursorSource::EffectsOverride) {
emit changed();
}
reevaluteSource();
}
void CursorImage::removeEffectsOverrideCursor()
{
reevaluteSource();
}
void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image)
{
loadTheme();
if (!m_cursorTheme) {
return;
}
auto it = m_cursors.constFind(shape);
if (it == m_cursors.constEnd()) {
image->image = QImage();
image->hotSpot = QPoint();
wl_cursor_image *cursor = m_cursorTheme->get(shape);
if (!cursor) {
return;
}
wl_buffer *b = wl_cursor_image_get_buffer(cursor);
if (!b) {
return;
}
waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch();
auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b)));
if (!buffer) {
return;
}
it = decltype(it)(m_cursors.insert(shape, {buffer->data().copy(), QPoint(cursor->hotspot_x, cursor->hotspot_y)}));
}
image->hotSpot = it.value().hotSpot;
image->image = it.value().image;
}
void CursorImage::reevaluteSource()
{
if (waylandServer()->isScreenLocked()) {
setSource(CursorSource::LockScreen);
return;
}
if (effects && static_cast<EffectsHandlerImpl*>(effects)->isMouseInterception()) {
setSource(CursorSource::EffectsOverride);
return;
}
if (!m_pointer->decoration().isNull()) {
setSource(CursorSource::Decoration);
return;
}
if (!m_pointer->window().isNull()) {
setSource(CursorSource::PointerSurface);
return;
}
setSource(CursorSource::Fallback);
}
void CursorImage::setSource(CursorSource source)
{
if (m_currentSource == source) {
return;
}
m_currentSource = source;
emit changed();
}
QImage CursorImage::image() const
{
switch (m_currentSource) {
case CursorSource::EffectsOverride:
return m_effectsCursor.image;
case CursorSource::LockScreen:
case CursorSource::PointerSurface:
// lockscreen also uses server cursor image
return m_serverCursor.image;
case CursorSource::Decoration:
return m_decorationCursor.image;
case CursorSource::Fallback:
return m_fallbackCursor.image;
default:
Q_UNREACHABLE();
}
}
QPoint CursorImage::hotSpot() const
{
switch (m_currentSource) {
case CursorSource::EffectsOverride:
return m_effectsCursor.hotSpot;
case CursorSource::LockScreen:
case CursorSource::PointerSurface:
// lockscreen also uses server cursor image
return m_serverCursor.hotSpot;
case CursorSource::Decoration:
return m_decorationCursor.hotSpot;
case CursorSource::Fallback:
return m_fallbackCursor.hotSpot;
default:
Q_UNREACHABLE();
}
}
} }

View File

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "input.h" #include "input.h"
#include <QElapsedTimer>
#include <QObject> #include <QObject>
#include <QPointer> #include <QPointer>
#include <QPointF> #include <QPointF>
@ -31,8 +32,10 @@ class QWindow;
namespace KWin namespace KWin
{ {
class CursorImage;
class InputRedirection; class InputRedirection;
class Toplevel; class Toplevel;
class WaylandCursorTheme;
namespace Decoration namespace Decoration
{ {
@ -69,7 +72,11 @@ public:
return m_internalWindow; return m_internalWindow;
} }
void installCursorFromDecoration(); QImage cursorImage() const;
QPoint cursorHotSpot() const;
void markCursorAsRendered();
void setEffectsOverrideCursor(Qt::CursorShape shape);
void removeEffectsOverrideCursor();
/** /**
* @internal * @internal
@ -84,12 +91,16 @@ public:
*/ */
void processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time); void processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time);
Q_SIGNALS:
void decorationChanged();
private: private:
void updatePosition(const QPointF &pos); void updatePosition(const QPointF &pos);
void updateButton(uint32_t button, InputRedirection::PointerButtonState state); void updateButton(uint32_t button, InputRedirection::PointerButtonState state);
void updateInternalWindow(); void updateInternalWindow();
void updateDecoration(Toplevel *t); void updateDecoration(Toplevel *t);
InputRedirection *m_input; InputRedirection *m_input;
CursorImage *m_cursor;
bool m_inited = false; bool m_inited = false;
bool m_supportsWarping; bool m_supportsWarping;
QPointF m_pos; QPointF m_pos;
@ -109,6 +120,62 @@ private:
QMetaObject::Connection m_internalWindowConnection; QMetaObject::Connection m_internalWindowConnection;
}; };
class CursorImage : public QObject
{
Q_OBJECT
public:
explicit CursorImage(PointerInputRedirection *parent = nullptr);
virtual ~CursorImage();
void setEffectsOverrideCursor(Qt::CursorShape shape);
void removeEffectsOverrideCursor();
QImage image() const;
QPoint hotSpot() const;
void markAsRendered();
Q_SIGNALS:
void changed();
private:
void reevaluteSource();
void update();
void updateServerCursor();
void updateDecoration();
void updateDecorationCursor();
void loadTheme();
struct Image {
QImage image;
QPoint hotSpot;
};
void loadThemeCursor(Qt::CursorShape shape, Image *image);
enum class CursorSource {
LockScreen,
EffectsOverride,
PointerSurface,
Decoration,
Fallback
};
void setSource(CursorSource source);
PointerInputRedirection *m_pointer;
CursorSource m_currentSource = CursorSource::Fallback;
WaylandCursorTheme *m_cursorTheme = nullptr;
struct {
QMetaObject::Connection connection;
QImage image;
QPoint hotSpot;
} m_serverCursor;
Image m_effectsCursor;
Image m_decorationCursor;
QMetaObject::Connection m_decorationConnection;
Image m_fallbackCursor;
QHash<Qt::CursorShape, Image> m_cursors;
QElapsedTimer m_surfaceRenderedTimer;
};
} }
#endif #endif