File indexing completed on 2024-11-10 04:57:08

0001 /*
0002     SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "plugins/shakecursor/shakecursor.h"
0008 #include "core/rendertarget.h"
0009 #include "core/renderviewport.h"
0010 #include "cursor.h"
0011 #include "effect/effecthandler.h"
0012 #include "input_event.h"
0013 #include "opengl/gltexture.h"
0014 #include "opengl/glutils.h"
0015 #include "plugins/shakecursor/shakecursorconfig.h"
0016 #include "pointer_input.h"
0017 
0018 namespace KWin
0019 {
0020 
0021 ShakeCursorEffect::ShakeCursorEffect()
0022     : m_cursor(Cursors::self()->mouse())
0023 {
0024     input()->installInputEventSpy(this);
0025 
0026     m_resetCursorScaleTimer.setSingleShot(true);
0027     connect(&m_resetCursorScaleTimer, &QTimer::timeout, this, [this]() {
0028         m_resetCursorScaleAnimation.setStartValue(m_cursorMagnification);
0029         m_resetCursorScaleAnimation.setEndValue(1.0);
0030         m_resetCursorScaleAnimation.setDuration(animationTime(150));
0031         m_resetCursorScaleAnimation.setEasingCurve(QEasingCurve::InOutCubic);
0032         m_resetCursorScaleAnimation.start();
0033     });
0034 
0035     connect(&m_resetCursorScaleAnimation, &QVariantAnimation::valueChanged, this, [this]() {
0036         update(Transaction{
0037             .position = m_cursor->pos(),
0038             .hotspot = m_cursor->hotspot(),
0039             .size = m_cursor->geometry().size(),
0040             .magnification = m_resetCursorScaleAnimation.currentValue().toReal(),
0041         });
0042     });
0043 
0044     ShakeCursorConfig::instance(effects->config());
0045     reconfigure(ReconfigureAll);
0046 }
0047 
0048 ShakeCursorEffect::~ShakeCursorEffect()
0049 {
0050     showCursor();
0051 }
0052 
0053 bool ShakeCursorEffect::supported()
0054 {
0055     if (!effects->waylandDisplay()) {
0056         return false;
0057     }
0058     return effects->isOpenGLCompositing();
0059 }
0060 
0061 void ShakeCursorEffect::reconfigure(ReconfigureFlags flags)
0062 {
0063     ShakeCursorConfig::self()->read();
0064 
0065     m_shakeDetector.setInterval(ShakeCursorConfig::timeInterval());
0066     m_shakeDetector.setSensitivity(ShakeCursorConfig::sensitivity());
0067 }
0068 
0069 bool ShakeCursorEffect::isActive() const
0070 {
0071     return m_cursorMagnification != 1.0;
0072 }
0073 
0074 void ShakeCursorEffect::pointerEvent(MouseEvent *event)
0075 {
0076     if (event->type() != QEvent::MouseMove || event->buttons() != Qt::NoButton) {
0077         return;
0078     }
0079 
0080     if (input()->pointer()->isConstrained()) {
0081         return;
0082     }
0083 
0084     if (const auto shakeFactor = m_shakeDetector.update(event)) {
0085         update(Transaction{
0086             .position = m_cursor->pos(),
0087             .hotspot = m_cursor->hotspot(),
0088             .size = m_cursor->geometry().size(),
0089             .magnification = std::max(m_cursorMagnification, 1.0 + ShakeCursorConfig::magnification() * shakeFactor.value()),
0090         });
0091         m_resetCursorScaleTimer.start(animationTime(2000));
0092         m_resetCursorScaleAnimation.stop();
0093     } else if (m_cursorMagnification != 1.0) {
0094         update(Transaction{
0095             .position = m_cursor->pos(),
0096             .hotspot = m_cursor->hotspot(),
0097             .size = m_cursor->geometry().size(),
0098             .magnification = m_cursorMagnification,
0099         });
0100     }
0101 }
0102 
0103 GLTexture *ShakeCursorEffect::ensureCursorTexture()
0104 {
0105     if (!m_cursorTexture || m_cursorTextureDirty) {
0106         m_cursorTexture.reset();
0107         m_cursorTextureDirty = false;
0108         const auto cursor = effects->cursorImage();
0109         if (!cursor.image().isNull()) {
0110             m_cursorTexture = GLTexture::upload(cursor.image());
0111             if (!m_cursorTexture) {
0112                 return nullptr;
0113             }
0114             m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
0115             m_cursorTexture->setFilter(GL_LINEAR);
0116         }
0117     }
0118     return m_cursorTexture.get();
0119 }
0120 
0121 void ShakeCursorEffect::markCursorTextureDirty()
0122 {
0123     m_cursorTextureDirty = true;
0124 
0125     update(Transaction{
0126         .position = m_cursor->pos(),
0127         .hotspot = m_cursor->hotspot(),
0128         .size = m_cursor->geometry().size(),
0129         .magnification = m_cursorMagnification,
0130         .damaged = true,
0131     });
0132 }
0133 
0134 void ShakeCursorEffect::showCursor()
0135 {
0136     if (m_mouseHidden) {
0137         disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &ShakeCursorEffect::markCursorTextureDirty);
0138         effects->showCursor();
0139         if (m_cursorTexture) {
0140             effects->makeOpenGLContextCurrent();
0141             m_cursorTexture.reset();
0142         }
0143         m_cursorTextureDirty = false;
0144         m_mouseHidden = false;
0145     }
0146 }
0147 
0148 void ShakeCursorEffect::hideCursor()
0149 {
0150     if (!m_mouseHidden) {
0151         effects->hideCursor();
0152         connect(effects, &EffectsHandler::cursorShapeChanged, this, &ShakeCursorEffect::markCursorTextureDirty);
0153         m_mouseHidden = true;
0154     }
0155 }
0156 
0157 void ShakeCursorEffect::update(const Transaction &transaction)
0158 {
0159     if (transaction.magnification == 1.0) {
0160         if (m_cursorMagnification == 1.0) {
0161             return;
0162         }
0163 
0164         const QRectF oldCursorGeometry = m_cursorGeometry;
0165         showCursor();
0166 
0167         m_cursorGeometry = QRectF();
0168         m_cursorMagnification = 1.0;
0169 
0170         effects->addRepaint(oldCursorGeometry);
0171     } else {
0172         const QRectF oldCursorGeometry = m_cursorGeometry;
0173         hideCursor();
0174 
0175         m_cursorMagnification = transaction.magnification;
0176         m_cursorGeometry = QRectF(transaction.position - transaction.hotspot * transaction.magnification, transaction.size * transaction.magnification);
0177 
0178         if (transaction.damaged || oldCursorGeometry != m_cursorGeometry) {
0179             effects->addRepaint(oldCursorGeometry.united(m_cursorGeometry));
0180         }
0181     }
0182 }
0183 
0184 void ShakeCursorEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
0185 {
0186     effects->paintScreen(renderTarget, viewport, mask, region, screen);
0187 
0188     if (GLTexture *texture = ensureCursorTexture()) {
0189         const bool clipping = region != infiniteRegion();
0190         const QRegion clipRegion = clipping ? viewport.mapToRenderTarget(region) : infiniteRegion();
0191         if (clipping) {
0192             glEnable(GL_SCISSOR_TEST);
0193         }
0194 
0195         glEnable(GL_BLEND);
0196         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0197         auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace);
0198         shader->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
0199         QMatrix4x4 mvp = viewport.projectionMatrix();
0200         mvp.translate(m_cursorGeometry.x() * viewport.scale(), m_cursorGeometry.y() * viewport.scale());
0201         shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
0202         texture->render(clipRegion, m_cursorGeometry.size() * viewport.scale(), clipping);
0203         ShaderManager::instance()->popShader();
0204         glDisable(GL_BLEND);
0205 
0206         if (clipping) {
0207             glDisable(GL_SCISSOR_TEST);
0208         }
0209     }
0210 }
0211 
0212 } // namespace KWin
0213 
0214 #include "moc_shakecursor.cpp"