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 ®ion, 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"