plugins/shakecursor: Display default cursor shape

At the moment, the shake cursor respects the current cursor shape. But
there are couple of drawbacks behind doing it: the first is that it's
very likely that the cursor pixmap has low resolution, the second is
the cursor can be hidden client-side.

This change makes the shake cursor plugin load the Xcursor theme with
a high enough size and display the default cursor shape regardless of
what the client has set in order to address the two aforementioned issues.
This commit is contained in:
Vlad Zahorodnii 2024-06-23 21:44:44 +03:00
parent 6a2bc79dae
commit 28b396f44c
4 changed files with 105 additions and 11 deletions

View file

@ -6,16 +6,39 @@
#include "plugins/shakecursor/shakecursor.h"
#include "cursor.h"
#include "cursorsource.h"
#include "effect/effecthandler.h"
#include "input_event.h"
#include "plugins/shakecursor/shakecursorconfig.h"
#include "pointer_input.h"
#include "scene/cursoritem.h"
#include "scene/imageitem.h"
#include "scene/itemrenderer.h"
#include "scene/workspacescene.h"
namespace KWin
{
ShakeCursorItem::ShakeCursorItem(const KXcursorTheme &theme, Item *parent)
: Item(parent)
{
m_source = std::make_unique<ShapeCursorSource>();
m_source->setTheme(theme);
m_source->setShape(Qt::ArrowCursor);
refresh();
connect(m_source.get(), &CursorSource::changed, this, &ShakeCursorItem::refresh);
}
void ShakeCursorItem::refresh()
{
if (!m_imageItem) {
m_imageItem = scene()->renderer()->createImageItem(this);
}
m_imageItem->setImage(m_source->image());
m_imageItem->setPosition(-m_source->hotspot());
m_imageItem->setSize(m_source->image().deviceIndependentSize());
}
ShakeCursorEffect::ShakeCursorEffect()
: m_cursor(Cursors::self()->mouse())
{
@ -121,7 +144,12 @@ void ShakeCursorEffect::magnify(qreal magnification)
if (!m_cursorItem) {
effects->hideCursor();
m_cursorItem = std::make_unique<CursorItem>(effects->scene()->overlayItem());
const qreal maxScale = ShakeCursorConfig::magnification() + 4 * ShakeCursorConfig::overMagnification();
if (m_cursorTheme.name() != m_cursor->themeName() || m_cursorTheme.size() != m_cursor->themeSize() || m_cursorTheme.devicePixelRatio() != maxScale) {
m_cursorTheme = KXcursorTheme(m_cursor->themeName(), m_cursor->themeSize(), maxScale);
}
m_cursorItem = std::make_unique<ShakeCursorItem>(m_cursorTheme, effects->scene()->overlayItem());
m_cursorItem->setPosition(m_cursor->pos());
connect(m_cursor, &Cursor::posChanged, m_cursorItem.get(), [this]() {
m_cursorItem->setPosition(m_cursor->pos());

View file

@ -9,6 +9,8 @@
#include "effect/effect.h"
#include "input_event_spy.h"
#include "plugins/shakecursor/shakedetector.h"
#include "scene/cursoritem.h"
#include "utils/xcursortheme.h"
#include <QTimer>
#include <QVariantAnimation>
@ -18,6 +20,21 @@ namespace KWin
class Cursor;
class CursorItem;
class ShapeCursorSource;
class ShakeCursorItem : public Item
{
Q_OBJECT
public:
ShakeCursorItem(const KXcursorTheme &theme, Item *parent);
private:
void refresh();
std::unique_ptr<ImageItem> m_imageItem;
std::unique_ptr<ShapeCursorSource> m_source;
};
class ShakeCursorEffect : public Effect, public InputEventSpy
{
@ -45,7 +62,8 @@ private:
ShakeDetector m_shakeDetector;
Cursor *m_cursor;
std::unique_ptr<CursorItem> m_cursorItem;
std::unique_ptr<ShakeCursorItem> m_cursorItem;
KXcursorTheme m_cursorTheme;
qreal m_targetMagnification = 1.0;
qreal m_currentMagnification = 1.0;
};

View file

@ -31,8 +31,15 @@ public:
class KXcursorThemePrivate : public QSharedData
{
public:
void load(const QString &themeName, int size, qreal devicePixelRatio);
void loadCursors(const QString &packagePath, int size, qreal devicePixelRatio);
KXcursorThemePrivate();
KXcursorThemePrivate(const QString &themeName, int size, qreal devicePixelRatio);
void load();
void loadCursors(const QString &packagePath);
QString name;
int size = 0;
qreal devicePixelRatio = 0;
QHash<QByteArray, QList<KXcursorSprite>> registry;
};
@ -81,6 +88,17 @@ std::chrono::milliseconds KXcursorSprite::delay() const
return d->delay;
}
KXcursorThemePrivate::KXcursorThemePrivate()
{
}
KXcursorThemePrivate::KXcursorThemePrivate(const QString &themeName, int size, qreal devicePixelRatio)
: name(themeName)
, size(size)
, devicePixelRatio(devicePixelRatio)
{
}
static QList<KXcursorSprite> loadCursor(const QString &filePath, int desiredSize, qreal devicePixelRatio)
{
QFile file(filePath);
@ -127,7 +145,7 @@ static QList<KXcursorSprite> loadCursor(const QString &filePath, int desiredSize
return sprites;
}
void KXcursorThemePrivate::loadCursors(const QString &packagePath, int size, qreal devicePixelRatio)
void KXcursorThemePrivate::loadCursors(const QString &packagePath)
{
const QDir dir(packagePath);
QFileInfoList entries = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);
@ -177,14 +195,14 @@ static QStringList searchPaths()
return paths;
}
void KXcursorThemePrivate::load(const QString &themeName, int size, qreal devicePixelRatio)
void KXcursorThemePrivate::load()
{
const QStringList paths = searchPaths();
QStack<QString> stack;
QSet<QString> loaded;
stack.push(themeName);
stack.push(name);
while (!stack.isEmpty()) {
const QString themeName = stack.pop();
@ -199,7 +217,7 @@ void KXcursorThemePrivate::load(const QString &themeName, int size, qreal device
if (!dir.exists()) {
continue;
}
loadCursors(dir.filePath(QStringLiteral("cursors")), size, devicePixelRatio);
loadCursors(dir.filePath(QStringLiteral("cursors")));
if (inherits.isEmpty()) {
const KConfig config(dir.filePath(QStringLiteral("index.theme")), KConfig::NoGlobals);
inherits << KConfigGroup(&config, QStringLiteral("Icon Theme")).readEntry("Inherits", QStringList());
@ -219,9 +237,9 @@ KXcursorTheme::KXcursorTheme()
}
KXcursorTheme::KXcursorTheme(const QString &themeName, int size, qreal devicePixelRatio)
: d(new KXcursorThemePrivate)
: d(new KXcursorThemePrivate(themeName, size, devicePixelRatio))
{
d->load(themeName, size, devicePixelRatio);
d->load();
}
KXcursorTheme::KXcursorTheme(const KXcursorTheme &other)
@ -249,6 +267,21 @@ bool KXcursorTheme::operator!=(const KXcursorTheme &other)
return !(*this == other);
}
QString KXcursorTheme::name() const
{
return d->name;
}
int KXcursorTheme::size() const
{
return d->size;
}
qreal KXcursorTheme::devicePixelRatio() const
{
return d->devicePixelRatio;
}
bool KXcursorTheme::isEmpty() const
{
return d->registry.isEmpty();

View file

@ -109,6 +109,21 @@ public:
bool operator==(const KXcursorTheme &other);
bool operator!=(const KXcursorTheme &other);
/**
* The name of the requested Xcursor theme.
*/
QString name() const;
/**
* The size of the requested Xcursor theme.
*/
int size() const;
/**
* The scale factor of the requested Xcursor theme.
*/
qreal devicePixelRatio() const;
/**
* Returns @c true if the Xcursor theme is empty; otherwise returns @c false.
*/