Working cursor mirroring

This commit is contained in:
wheaney 2025-07-07 15:47:31 -07:00
parent 757eaed57f
commit 4599a7afab
3 changed files with 66 additions and 71 deletions

View File

@ -13,6 +13,7 @@
#include <KGlobalAccel>
#include <KLocalizedString>
#include <qt/QtCore/qbuffer.h>
Q_LOGGING_CATEGORY(KWIN_XR, "kwin.xr")
@ -47,6 +48,9 @@ BreezyDesktopEffect::BreezyDesktopEffect()
}
});
connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage);
updateCursorImage();
setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/effects/breezy_desktop/qml/main.qml"))));
m_xrRotationTimer = new QTimer(this);
@ -54,14 +58,17 @@ BreezyDesktopEffect::BreezyDesktopEffect()
connect(m_xrRotationTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateXrRotation);
m_xrRotationTimer->start();
toggle();
m_cursorUpdateTimer = new QTimer(this);
connect(m_cursorUpdateTimer, &QTimer::timeout, this, &BreezyDesktopEffect::updateCursorPos);
m_cursorUpdateTimer->setInterval(16); // ~60Hz
m_cursorUpdateTimer->start();
}
QVariantMap BreezyDesktopEffect::initialProperties(Output *screen)
{
return QVariantMap{
{QStringLiteral("effect"), QVariant::fromValue(this)},
{QStringLiteral("targetScreen"), QVariant::fromValue(screen)},
{QStringLiteral("targetScreen"), QVariant::fromValue(screen)}
};
}
@ -111,6 +118,8 @@ void BreezyDesktopEffect::deactivate()
}
m_shutdownTimer->start(animationDuration());
disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::updateCursorImage);
m_cursorUpdateTimer->stop();
showCursor();
}
@ -239,89 +248,58 @@ void BreezyDesktopEffect::updateXrRotation() {
}
}
// show and hide cursor logic pulled from ZoomEffect
GLTexture *BreezyDesktopEffect::ensureCursorTexture()
QString BreezyDesktopEffect::cursorImageSource() const
{
if (!m_cursorTexture || m_cursorTextureDirty) {
m_cursorTexture.reset();
m_cursorTextureDirty = false;
const auto cursor = effects->cursorImage();
if (!cursor.image().isNull()) {
m_cursorTexture = GLTexture::upload(cursor.image());
if (!m_cursorTexture) {
return nullptr;
}
m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
}
}
return m_cursorTexture.get();
return m_cursorImageSource;
}
void BreezyDesktopEffect::markCursorTextureDirty()
QPointF BreezyDesktopEffect::cursorPos() const
{
m_cursorTextureDirty = true;
return m_cursorPos;
}
void BreezyDesktopEffect::showCursor()
{
if (m_isMouseHidden) {
disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::markCursorTextureDirty);
// show the previously hidden mouse-pointer again and free the loaded texture/picture.
effects->showCursor();
m_cursorTexture.reset();
m_isMouseHidden = false;
}
}
void BreezyDesktopEffect::hideCursor()
{
qCCritical(KWIN_XR) << "\t\t\tBreezy - hide cursor";
if (!m_isMouseHidden) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - hide cursor - ensure cursor texture";
// try to load the cursor-theme into a OpenGL texture and if successful then hide the mouse-pointer
GLTexture *texture = nullptr;
if (effects->isOpenGLCompositing()) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - hide cursor - OpenGL compositing";
texture = ensureCursorTexture();
}
if (texture) {
qCCritical(KWIN_XR) << "\t\t\tBreezy - hide cursor - hiding cursor";
effects->hideCursor();
connect(effects, &EffectsHandler::cursorShapeChanged, this, &BreezyDesktopEffect::markCursorTextureDirty);
m_isMouseHidden = true;
}
updateCursorImage();
effects->hideCursor();
m_isMouseHidden = true;
}
}
void BreezyDesktopEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
{
GLTexture *cursorTexture = ensureCursorTexture();
if (cursorTexture) {
const auto cursor = effects->cursorImage();
QSizeF cursorSize = QSizeF(cursor.image().size()) / cursor.image().devicePixelRatio();
// Ensure cursor size doesn't exceed texture dimensions
const QSize textureSize = cursorTexture->size();
cursorSize = cursorSize.boundedTo(QSizeF(textureSize));
void BreezyDesktopEffect::updateCursorImage()
{
const auto cursor = effects->cursorImage();
if (!cursor.image().isNull()) {
QByteArray data;
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
cursor.image().save(&buffer, "PNG");
const QPointF p = (effects->cursorPos() - cursor.hotSpot());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
s->setColorspaceUniforms(ColorDescription::sRGB, renderTarget.colorDescription(), RenderingIntent::Perceptual);
QMatrix4x4 mvp = viewport.projectionMatrix();
mvp.translate(p.x(), p.y());
s->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
cursorTexture->render(cursorSize);
ShaderManager::instance()->popShader();
glDisable(GL_BLEND);
m_cursorImageSource = QStringLiteral("data:image/png;base64,%1").arg(QString::fromLatin1(data.toBase64()));
} else {
m_cursorImageSource = QString();
}
QuickSceneEffect::paintScreen(renderTarget, viewport, mask, region, screen);
Q_EMIT cursorImageChanged();
}
} // namespace KWin
void BreezyDesktopEffect::updateCursorPos()
{
// Update cursor position from effects
QPointF newPos = effects->cursorPos();
if (m_cursorPos != newPos) {
m_cursorPos = newPos;
Q_EMIT cursorPosChanged();
}
}
}
#include "moc_breezydesktopeffect.cpp"

View File

@ -3,14 +3,12 @@
#include <effect/quickeffect.h>
#include <QAction>
#include <QImage>
#include <QKeySequence>
#include <QQuaternion>
namespace KWin
{
class GLTexture;
class GLShader;
class BreezyDesktopEffect : public QuickSceneEffect
{
Q_OBJECT
@ -22,6 +20,8 @@ namespace KWin
Q_PROPERTY(BackgroundMode backgroundMode READ backgroundMode NOTIFY backgroundModeChanged)
Q_PROPERTY(QColor backgroundColor READ backgroundColor NOTIFY backgroundColorChanged)
Q_PROPERTY(QQuaternion xrRotation READ xrRotation NOTIFY xrRotationChanged)
Q_PROPERTY(QString cursorImageSource READ cursorImageSource NOTIFY cursorImageChanged)
Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged)
public:
enum class BackgroundMode
@ -33,8 +33,6 @@ namespace KWin
BreezyDesktopEffect();
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override;
int requestedEffectChainPosition() const override;
int animationDuration() const;
@ -44,11 +42,11 @@ namespace KWin
bool mouseInvertedY() const;
BackgroundMode backgroundMode() const;
QColor backgroundColor() const;
QString cursorImageSource() const;
QPointF cursorPos() const;
void showCursor();
void hideCursor();
GLTexture *ensureCursorTexture();
void markCursorTextureDirty();
QQuaternion xrRotation() const;
@ -57,6 +55,8 @@ namespace KWin
void deactivate();
void toggle();
void updateXrRotation();
void updateCursorImage();
void updateCursorPos();
Q_SIGNALS:
void faceDisplacementChanged();
@ -68,6 +68,8 @@ namespace KWin
void backgroundModeChanged();
void backgroundColorChanged();
void xrRotationChanged();
void cursorImageChanged();
void cursorPosChanged();
protected:
QVariantMap initialProperties(Output *screen) override;
@ -80,12 +82,13 @@ namespace KWin
QList<QKeySequence> m_toggleShortcut;
QList<ElectricBorder> m_borderActivate;
QList<ElectricBorder> m_touchBorderActivate;
std::unique_ptr<GLTexture> m_cursorTexture;
bool m_cursorTextureDirty = false;
QString m_cursorImageSource;
bool m_isMouseHidden = false;
QQuaternion m_xrRotation;
QTimer *m_xrRotationTimer = nullptr;
QPointF m_cursorPos;
QTimer *m_cursorUpdateTimer = nullptr;
};
} // namespace KWin

View File

@ -22,4 +22,18 @@ Item {
visible: !model.window.minimized
}
}
Image {
id: cursorImg
source: effect.cursorImageSource
cache: false
visible: true // TODO - cursor position bounds check?
x: effect.cursorPos.x - desktopView.screen.geometry.x
y: effect.cursorPos.y - desktopView.screen.geometry.y
z: 9999 // ensure on top
anchors.centerIn: undefined
layer.enabled: true
layer.smooth: true
}
}