diff --git a/src/cursorsource.cpp b/src/cursorsource.cpp index ef4ea4bc70..e1b0ec3ae5 100644 --- a/src/cursorsource.cpp +++ b/src/cursorsource.cpp @@ -5,6 +5,7 @@ */ #include "cursorsource.h" +#include "cursor.h" namespace KWin { @@ -36,4 +37,83 @@ void ImageCursorSource::update(const QImage &image, const QPoint &hotspot) Q_EMIT changed(); } +ShapeCursorSource::ShapeCursorSource(QObject *parent) + : CursorSource(parent) +{ + m_delayTimer.setSingleShot(true); + connect(&m_delayTimer, &QTimer::timeout, this, &ShapeCursorSource::selectNextSprite); +} + +QByteArray ShapeCursorSource::shape() const +{ + return m_shape; +} + +void ShapeCursorSource::setShape(const QByteArray &shape) +{ + if (m_shape != shape) { + m_shape = shape; + refresh(); + } +} + +void ShapeCursorSource::setShape(Qt::CursorShape shape) +{ + setShape(CursorShape(shape).name()); +} + +KXcursorTheme ShapeCursorSource::theme() const +{ + return m_theme; +} + +void ShapeCursorSource::setTheme(const KXcursorTheme &theme) +{ + if (m_theme != theme) { + m_theme = theme; + refresh(); + } +} + +void ShapeCursorSource::refresh() +{ + m_currentSprite = -1; + m_delayTimer.stop(); + + m_sprites = m_theme.shape(m_shape); + if (m_sprites.isEmpty()) { + const auto alternativeNames = Cursor::cursorAlternativeNames(m_shape); + for (const QByteArray &alternativeName : alternativeNames) { + m_sprites = m_theme.shape(alternativeName); + if (!m_sprites.isEmpty()) { + break; + } + } + } + + if (!m_sprites.isEmpty()) { + selectSprite(0); + } +} + +void ShapeCursorSource::selectNextSprite() +{ + selectSprite((m_currentSprite + 1) % m_sprites.size()); +} + +void ShapeCursorSource::selectSprite(int index) +{ + if (m_currentSprite == index) { + return; + } + const KXcursorSprite &sprite = m_sprites[index]; + m_currentSprite = index; + m_image = sprite.data(); + m_hotspot = sprite.hotspot(); + if (sprite.delay().count() && m_sprites.size() > 1) { + m_delayTimer.start(sprite.delay()); + } + Q_EMIT changed(); +} + } // namespace KWin diff --git a/src/cursorsource.h b/src/cursorsource.h index 64c24df43a..346ba798a2 100644 --- a/src/cursorsource.h +++ b/src/cursorsource.h @@ -6,11 +6,12 @@ #pragma once -#include +#include "utils/xcursortheme.h" #include #include #include +#include namespace KWin { @@ -50,4 +51,33 @@ public Q_SLOTS: void update(const QImage &image, const QPoint &hotspot); }; +/** + * The ShapeCursorSource class represents the contents of a shape in the cursor theme. + */ +class KWIN_EXPORT ShapeCursorSource : public CursorSource +{ + Q_OBJECT + +public: + explicit ShapeCursorSource(QObject *parent = nullptr); + + QByteArray shape() const; + void setShape(const QByteArray &shape); + void setShape(Qt::CursorShape shape); + + KXcursorTheme theme() const; + void setTheme(const KXcursorTheme &theme); + +private: + void refresh(); + void selectNextSprite(); + void selectSprite(int index); + + KXcursorTheme m_theme; + QByteArray m_shape; + QVector m_sprites; + QTimer m_delayTimer; + int m_currentSprite = -1; +}; + } // namespace KWin diff --git a/src/pointer_input.cpp b/src/pointer_input.cpp index 4dfafdb8aa..26ccbac943 100644 --- a/src/pointer_input.cpp +++ b/src/pointer_input.cpp @@ -886,11 +886,11 @@ CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { - m_effectsCursor = std::make_unique(); - m_fallbackCursor = std::make_unique(); - m_moveResizeCursor = std::make_unique(); - m_windowSelectionCursor = std::make_unique(); - m_decoration.cursor = std::make_unique(); + m_effectsCursor = std::make_unique(); + m_fallbackCursor = std::make_unique(); + m_moveResizeCursor = std::make_unique(); + m_windowSelectionCursor = std::make_unique(); + m_decoration.cursor = std::make_unique(); m_drag.cursor = std::make_unique(); m_serverCursor.cursor = std::make_unique(); @@ -915,13 +915,21 @@ CursorImage::CursorImage(PointerInputRedirection *parent) const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::windowAdded, this, setupMoveResizeConnection); - loadThemeCursor(Qt::ArrowCursor, m_fallbackCursor.get()); + + m_fallbackCursor->setShape(Qt::ArrowCursor); + + m_effectsCursor->setTheme(m_waylandImage.theme()); + m_fallbackCursor->setTheme(m_waylandImage.theme()); + m_moveResizeCursor->setTheme(m_waylandImage.theme()); + m_windowSelectionCursor->setTheme(m_waylandImage.theme()); + m_decoration.cursor->setTheme(m_waylandImage.theme()); connect(&m_waylandImage, &WaylandCursorImage::themeChanged, this, [this] { - loadThemeCursor(Qt::ArrowCursor, m_fallbackCursor.get()); - updateDecorationCursor(); - updateMoveResize(); - // TODO: update effects + m_effectsCursor->setTheme(m_waylandImage.theme()); + m_fallbackCursor->setTheme(m_waylandImage.theme()); + m_moveResizeCursor->setTheme(m_waylandImage.theme()); + m_windowSelectionCursor->setTheme(m_waylandImage.theme()); + m_decoration.cursor->setTheme(m_waylandImage.theme()); }); handlePointerChanged(); @@ -998,7 +1006,7 @@ void CursorImage::updateDecorationCursor() { auto deco = m_pointer->decoration(); if (Window *window = deco ? deco->window() : nullptr) { - loadThemeCursor(window->cursor(), m_decoration.cursor.get()); + m_decoration.cursor->setShape(window->cursor().name()); } reevaluteSource(); } @@ -1006,7 +1014,7 @@ void CursorImage::updateDecorationCursor() void CursorImage::updateMoveResize() { if (Window *window = workspace()->moveResizeWindow()) { - loadThemeCursor(window->cursor(), m_moveResizeCursor.get()); + m_moveResizeCursor->setShape(window->cursor().name()); } reevaluteSource(); } @@ -1040,7 +1048,7 @@ void CursorImage::updateServerCursor() void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { - loadThemeCursor(shape, m_effectsCursor.get()); + m_effectsCursor->setShape(shape); reevaluteSource(); } @@ -1052,9 +1060,9 @@ void CursorImage::removeEffectsOverrideCursor() void CursorImage::setWindowSelectionCursor(const QByteArray &shape) { if (shape.isEmpty()) { - loadThemeCursor(Qt::CrossCursor, m_windowSelectionCursor.get()); + m_windowSelectionCursor->setShape(Qt::CrossCursor); } else { - loadThemeCursor(shape, m_windowSelectionCursor.get()); + m_windowSelectionCursor->setShape(shape); } reevaluteSource(); } @@ -1144,31 +1152,23 @@ void CursorImage::updateDragCursor() m_drag.cursor->update(image, hotspot); } -void CursorImage::loadThemeCursor(CursorShape shape, ImageCursorSource *source) -{ - m_waylandImage.loadThemeCursor(shape, source); -} - -void CursorImage::loadThemeCursor(const QByteArray &shape, ImageCursorSource *source) -{ - m_waylandImage.loadThemeCursor(shape, source); -} - WaylandCursorImage::WaylandCursorImage(QObject *parent) : QObject(parent) { Cursor *pointerCursor = Cursors::self()->mouse(); + updateCursorTheme(); - connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::invalidateCursorTheme); - connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::invalidateCursorTheme); + connect(pointerCursor, &Cursor::themeChanged, this, &WaylandCursorImage::updateCursorTheme); + connect(workspace(), &Workspace::outputsChanged, this, &WaylandCursorImage::updateCursorTheme); } -bool WaylandCursorImage::ensureCursorTheme() +KXcursorTheme WaylandCursorImage::theme() const { - if (!m_cursorTheme.isEmpty()) { - return true; - } + return m_cursorTheme; +} +void WaylandCursorImage::updateCursorTheme() +{ const Cursor *pointerCursor = Cursors::self()->mouse(); qreal targetDevicePixelRatio = 1; @@ -1180,21 +1180,11 @@ bool WaylandCursorImage::ensureCursorTheme() } m_cursorTheme = KXcursorTheme(pointerCursor->themeName(), pointerCursor->themeSize(), targetDevicePixelRatio); - if (!m_cursorTheme.isEmpty()) { - return true; + if (m_cursorTheme.isEmpty()) { + m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio); } - m_cursorTheme = KXcursorTheme(Cursor::defaultThemeName(), Cursor::defaultThemeSize(), targetDevicePixelRatio); - if (!m_cursorTheme.isEmpty()) { - return true; - } - - return false; -} - -void WaylandCursorImage::invalidateCursorTheme() -{ - m_cursorTheme = KXcursorTheme(); + Q_EMIT themeChanged(); } void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSource *source) @@ -1204,10 +1194,6 @@ void WaylandCursorImage::loadThemeCursor(const CursorShape &shape, ImageCursorSo void WaylandCursorImage::loadThemeCursor(const QByteArray &name, ImageCursorSource *source) { - if (!ensureCursorTheme()) { - return; - } - if (loadThemeCursor_helper(name, source)) { return; } @@ -1281,6 +1267,11 @@ void CursorImage::setSource(CursorSource *source) Q_EMIT changed(); } +KXcursorTheme CursorImage::theme() const +{ + return m_waylandImage.theme(); +} + InputRedirectionCursor::InputRedirectionCursor(QObject *parent) : Cursor(parent) , m_currentButtons(Qt::NoButton) diff --git a/src/pointer_input.h b/src/pointer_input.h index 269474519c..3c5533ca73 100644 --- a/src/pointer_input.h +++ b/src/pointer_input.h @@ -35,6 +35,7 @@ class InputDevice; class InputRedirection; class CursorShape; class ImageCursorSource; +class ShapeCursorSource; namespace Decoration { @@ -183,6 +184,8 @@ class WaylandCursorImage : public QObject public: explicit WaylandCursorImage(QObject *parent = nullptr); + KXcursorTheme theme() const; + void loadThemeCursor(const CursorShape &shape, ImageCursorSource *source); void loadThemeCursor(const QByteArray &name, ImageCursorSource *source); @@ -191,8 +194,7 @@ Q_SIGNALS: private: bool loadThemeCursor_helper(const QByteArray &name, ImageCursorSource *source); - bool ensureCursorTheme(); - void invalidateCursorTheme(); + void updateCursorTheme(); KXcursorTheme m_cursorTheme; }; @@ -209,6 +211,7 @@ public: void setWindowSelectionCursor(const QByteArray &shape); void removeWindowSelectionCursor(); + KXcursorTheme theme() const; CursorSource *source() const; void setSource(CursorSource *source); void markAsRendered(std::chrono::milliseconds timestamp); @@ -228,21 +231,18 @@ private: void handlePointerChanged(); void handleFocusedSurfaceChanged(); - void loadThemeCursor(CursorShape shape, ImageCursorSource *source); - void loadThemeCursor(const QByteArray &shape, ImageCursorSource *source); - PointerInputRedirection *m_pointer; CursorSource *m_currentSource = nullptr; WaylandCursorImage m_waylandImage; - std::unique_ptr m_effectsCursor; - std::unique_ptr m_fallbackCursor; - std::unique_ptr m_moveResizeCursor; - std::unique_ptr m_windowSelectionCursor; + std::unique_ptr m_effectsCursor; + std::unique_ptr m_fallbackCursor; + std::unique_ptr m_moveResizeCursor; + std::unique_ptr m_windowSelectionCursor; struct { - std::unique_ptr cursor; + std::unique_ptr cursor; QMetaObject::Connection connection; } m_decoration; struct diff --git a/src/utils/xcursortheme.cpp b/src/utils/xcursortheme.cpp index 2421739679..95ac8bab64 100644 --- a/src/utils/xcursortheme.cpp +++ b/src/utils/xcursortheme.cpp @@ -218,6 +218,16 @@ KXcursorTheme &KXcursorTheme::operator=(const KXcursorTheme &other) return *this; } +bool KXcursorTheme::operator==(const KXcursorTheme &other) +{ + return d == other.d; +} + +bool KXcursorTheme::operator!=(const KXcursorTheme &other) +{ + return !(*this == other); +} + bool KXcursorTheme::isEmpty() const { return d->registry.isEmpty(); diff --git a/src/utils/xcursortheme.h b/src/utils/xcursortheme.h index 8005749825..90eba2226b 100644 --- a/src/utils/xcursortheme.h +++ b/src/utils/xcursortheme.h @@ -106,6 +106,9 @@ public: */ KXcursorTheme &operator=(const KXcursorTheme &other); + bool operator==(const KXcursorTheme &other); + bool operator!=(const KXcursorTheme &other); + /** * Returns @c true if the Xcursor theme is empty; otherwise returns @c false. */