Client: Add support for animated cursors

[ChangeLog][QPA plugin] Added support for animated cursors.

Previously we would just show the first frame of the animation.

Fixes: QTBUG-48181
Change-Id: Ie06bff8950678b5ff7b7e2e50915c85905a1200b
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2019-02-06 09:32:22 +01:00 committed by Johan Helsing
parent c6476ae915
commit 5d1b75d9c1
5 changed files with 74 additions and 8 deletions

View File

@ -209,7 +209,7 @@ wl_cursor *QWaylandCursorTheme::requestCursor(WaylandCursor shape)
return nullptr; return nullptr;
} }
struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape) ::wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation)
{ {
struct wl_cursor *waylandCursor = nullptr; struct wl_cursor *waylandCursor = nullptr;
@ -227,8 +227,9 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
return nullptr; return nullptr;
} }
struct wl_cursor_image *image = waylandCursor->images[0]; int frame = wl_cursor_frame(waylandCursor, millisecondsIntoAnimation);
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); ::wl_cursor_image *image = waylandCursor->images[frame];
::wl_buffer *buffer = wl_cursor_image_get_buffer(image);
if (!buffer) { if (!buffer) {
qCWarning(lcQpaWayland) << "Could not find buffer for cursor"; qCWarning(lcQpaWayland) << "Could not find buffer for cursor";
return nullptr; return nullptr;

View File

@ -75,7 +75,7 @@ class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
public: public:
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName); static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
~QWaylandCursorTheme(); ~QWaylandCursorTheme();
struct wl_cursor_image *cursorImage(Qt::CursorShape shape); ::wl_cursor_image *cursorImage(Qt::CursorShape shape, uint millisecondsIntoAnimation = 0);
private: private:
enum WaylandCursor { enum WaylandCursor {

View File

@ -191,6 +191,27 @@ QWaylandInputDevice::Pointer::~Pointer()
#if QT_CONFIG(cursor) #if QT_CONFIG(cursor)
class WlCallback : public QtWayland::wl_callback {
public:
explicit WlCallback(::wl_callback *callback, std::function<void(uint32_t)> fn, bool autoDelete = false)
: QtWayland::wl_callback(callback)
, m_fn(fn)
, m_autoDelete(autoDelete)
{}
~WlCallback() override { wl_callback_destroy(object()); }
bool done() const { return m_done; }
void callback_done(uint32_t callback_data) override {
m_done = true;
m_fn(callback_data);
if (m_autoDelete)
delete this;
}
private:
bool m_done = false;
std::function<void(uint32_t)> m_fn;
bool m_autoDelete = false;
};
class CursorSurface : public QWaylandSurface class CursorSurface : public QWaylandSurface
{ {
public: public:
@ -213,7 +234,7 @@ public:
} }
// Size and hotspot are in surface coordinates // Size and hotspot are in surface coordinates
void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale) void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale, bool animated = false)
{ {
// Calling code needs to ensure buffer scale is supported if != 1 // Calling code needs to ensure buffer scale is supported if != 1
Q_ASSERT(bufferScale == 1 || m_version >= 3); Q_ASSERT(bufferScale == 1 || m_version >= 3);
@ -230,6 +251,13 @@ public:
attach(buffer, 0, 0); attach(buffer, 0, 0);
damage(0, 0, size.width(), size.height()); damage(0, 0, size.width(), size.height());
m_frameCallback.reset();
if (animated) {
m_frameCallback.reset(new WlCallback(frame(), [this](uint32_t time){
Q_UNUSED(time);
m_pointer->updateCursor();
}));
}
commit(); commit();
} }
@ -242,6 +270,7 @@ public:
} }
private: private:
QScopedPointer<WlCallback> m_frameCallback;
QWaylandInputDevice::Pointer *m_pointer = nullptr; QWaylandInputDevice::Pointer *m_pointer = nullptr;
uint m_version = 0; uint m_version = 0;
uint m_setSerial = 0; uint m_setSerial = 0;
@ -320,12 +349,14 @@ void QWaylandInputDevice::Pointer::updateCursor()
updateCursorTheme(); updateCursorTheme();
// Set from shape using theme // Set from shape using theme
if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape)) { uint time = seat()->mCursor.animationTimer.elapsed();
if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape, time)) {
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
int bufferScale = mCursor.themeBufferScale; int bufferScale = mCursor.themeBufferScale;
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
QSize size = QSize(image->width, image->height) / bufferScale; QSize size = QSize(image->width, image->height) / bufferScale;
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale); bool animated = image->delay > 0;
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated);
return; return;
} }
@ -515,6 +546,7 @@ void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer<
mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor; mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor;
mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint(); mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint();
mCursor.fallbackOutputScale = fallbackOutputScale; mCursor.fallbackOutputScale = fallbackOutputScale;
mCursor.animationTimer.start();
if (mCursor.shape == Qt::BitmapCursor) { if (mCursor.shape == Qt::BitmapCursor) {
mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor); mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor);

View File

@ -69,7 +69,8 @@
#endif #endif
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QPointer> #include <QtCore/QElapsedTimer>
#include <QtCore/QPointer>
#if QT_CONFIG(cursor) #if QT_CONFIG(cursor)
struct wl_cursor_image; struct wl_cursor_image;
@ -153,6 +154,7 @@ private:
Qt::CursorShape shape = Qt::ArrowCursor; Qt::CursorShape shape = Qt::ArrowCursor;
int fallbackOutputScale = 1; int fallbackOutputScale = 1;
QPoint hotspot; QPoint hotspot;
QElapsedTimer animationTimer;
} mCursor; } mCursor;
#endif #endif

View File

@ -80,6 +80,7 @@ private slots:
void bitmapCursor(); void bitmapCursor();
void hidpiBitmapCursor(); void hidpiBitmapCursor();
void hidpiBitmapCursorNonInt(); void hidpiBitmapCursorNonInt();
void animatedCursor();
#endif #endif
}; };
@ -554,6 +555,36 @@ void tst_seatv4::hidpiBitmapCursorNonInt()
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25)); QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25));
} }
void tst_seatv4::animatedCursor()
{
QRasterWindow window;
window.resize(64, 64);
window.setCursor(Qt::WaitCursor); // TODO: verify that the theme has an animated wait cursor or skip test
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
// We should get the first buffer without waiting for a frame callback
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QSignalSpy bufferSpy(exec([=] { return cursorSurface(); }), &Surface::bufferCommitted);
exec([&] {
// Make sure no extra buffers have arrived
QVERIFY(bufferSpy.empty());
// The client should send a frame request in order to time animations correctly
QVERIFY(!cursorSurface()->m_waitingFrameCallbacks.empty());
// Tell the client it's time to animate
cursorSurface()->sendFrameCallbacks();
});
// Verify that we get a new cursor buffer
QTRY_COMPARE(bufferSpy.count(), 1);
}
#endif // QT_CONFIG(cursor) #endif // QT_CONFIG(cursor)
QCOMPOSITOR_TEST_MAIN(tst_seatv4) QCOMPOSITOR_TEST_MAIN(tst_seatv4)