KWin
Loading...
Searching...
No Matches
shakecursor.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2023 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
8#include "core/rendertarget.h"
10#include "cursor.h"
12#include "input_event.h"
13#include "opengl/gltexture.h"
14#include "opengl/glutils.h"
15#include "plugins/shakecursor/shakecursorconfig.h"
16#include "pointer_input.h"
17
18namespace KWin
19{
20
22 : m_cursor(Cursors::self()->mouse())
23{
25
26 m_resetCursorScaleTimer.setSingleShot(true);
27 connect(&m_resetCursorScaleTimer, &QTimer::timeout, this, [this]() {
28 m_resetCursorScaleAnimation.setStartValue(m_cursorMagnification);
29 m_resetCursorScaleAnimation.setEndValue(1.0);
30 m_resetCursorScaleAnimation.setDuration(animationTime(150));
31 m_resetCursorScaleAnimation.setEasingCurve(QEasingCurve::InOutCubic);
32 m_resetCursorScaleAnimation.start();
33 });
34
35 connect(&m_resetCursorScaleAnimation, &QVariantAnimation::valueChanged, this, [this]() {
36 update(Transaction{
37 .position = m_cursor->pos(),
38 .hotspot = m_cursor->hotspot(),
39 .size = m_cursor->geometry().size(),
40 .magnification = m_resetCursorScaleAnimation.currentValue().toReal(),
41 });
42 });
43
44 ShakeCursorConfig::instance(effects->config());
46}
47
49{
50 showCursor();
51}
52
54{
55 if (!effects->waylandDisplay()) {
56 return false;
57 }
59}
60
61void ShakeCursorEffect::reconfigure(ReconfigureFlags flags)
62{
63 ShakeCursorConfig::self()->read();
64
65 m_shakeDetector.setInterval(ShakeCursorConfig::timeInterval());
66 m_shakeDetector.setSensitivity(ShakeCursorConfig::sensitivity());
67}
68
70{
71 return m_cursorMagnification != 1.0;
72}
73
75{
76 if (event->type() != QEvent::MouseMove || event->buttons() != Qt::NoButton) {
77 return;
78 }
79
80 if (input()->pointer()->isConstrained()) {
81 return;
82 }
83
84 if (const auto shakeFactor = m_shakeDetector.update(event)) {
85 update(Transaction{
86 .position = m_cursor->pos(),
87 .hotspot = m_cursor->hotspot(),
88 .size = m_cursor->geometry().size(),
89 .magnification = std::max(m_cursorMagnification, 1.0 + ShakeCursorConfig::magnification() * shakeFactor.value()),
90 });
91 m_resetCursorScaleTimer.start(animationTime(2000));
92 m_resetCursorScaleAnimation.stop();
93 } else if (m_cursorMagnification != 1.0) {
94 update(Transaction{
95 .position = m_cursor->pos(),
96 .hotspot = m_cursor->hotspot(),
97 .size = m_cursor->geometry().size(),
98 .magnification = m_cursorMagnification,
99 });
100 }
101}
102
103GLTexture *ShakeCursorEffect::ensureCursorTexture()
104{
105 if (!m_cursorTexture || m_cursorTextureDirty) {
106 m_cursorTexture.reset();
107 m_cursorTextureDirty = false;
108 const auto cursor = effects->cursorImage();
109 if (!cursor.image().isNull()) {
110 m_cursorTexture = GLTexture::upload(cursor.image());
111 if (!m_cursorTexture) {
112 return nullptr;
113 }
114 m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
115 m_cursorTexture->setFilter(GL_LINEAR);
116 }
117 }
118 return m_cursorTexture.get();
119}
120
121void ShakeCursorEffect::markCursorTextureDirty()
122{
123 m_cursorTextureDirty = true;
124
125 update(Transaction{
126 .position = m_cursor->pos(),
127 .hotspot = m_cursor->hotspot(),
128 .size = m_cursor->geometry().size(),
129 .magnification = m_cursorMagnification,
130 .damaged = true,
131 });
132}
133
134void ShakeCursorEffect::showCursor()
135{
136 if (m_mouseHidden) {
137 disconnect(effects, &EffectsHandler::cursorShapeChanged, this, &ShakeCursorEffect::markCursorTextureDirty);
139 if (m_cursorTexture) {
141 m_cursorTexture.reset();
142 }
143 m_cursorTextureDirty = false;
144 m_mouseHidden = false;
145 }
146}
147
148void ShakeCursorEffect::hideCursor()
149{
150 if (!m_mouseHidden) {
152 connect(effects, &EffectsHandler::cursorShapeChanged, this, &ShakeCursorEffect::markCursorTextureDirty);
153 m_mouseHidden = true;
154 }
155}
156
157void ShakeCursorEffect::update(const Transaction &transaction)
158{
159 if (transaction.magnification == 1.0) {
160 if (m_cursorMagnification == 1.0) {
161 return;
162 }
163
164 const QRectF oldCursorGeometry = m_cursorGeometry;
165 showCursor();
166
167 m_cursorGeometry = QRectF();
168 m_cursorMagnification = 1.0;
169
170 effects->addRepaint(oldCursorGeometry);
171 } else {
172 const QRectF oldCursorGeometry = m_cursorGeometry;
173 hideCursor();
174
175 m_cursorMagnification = transaction.magnification;
176 m_cursorGeometry = QRectF(transaction.position - transaction.hotspot * transaction.magnification, transaction.size * transaction.magnification);
177
178 if (transaction.damaged || oldCursorGeometry != m_cursorGeometry) {
179 effects->addRepaint(oldCursorGeometry.united(m_cursorGeometry));
180 }
181 }
182}
183
184void ShakeCursorEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
185{
186 effects->paintScreen(renderTarget, viewport, mask, region, screen);
187
188 if (GLTexture *texture = ensureCursorTexture()) {
189 const bool clipping = region != infiniteRegion();
190 const QRegion clipRegion = clipping ? viewport.mapToRenderTarget(region) : infiniteRegion();
191 if (clipping) {
192 glEnable(GL_SCISSOR_TEST);
193 }
194
195 glEnable(GL_BLEND);
196 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
198 shader->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
199 QMatrix4x4 mvp = viewport.projectionMatrix();
200 mvp.translate(m_cursorGeometry.x() * viewport.scale(), m_cursorGeometry.y() * viewport.scale());
202 texture->render(clipRegion, m_cursorGeometry.size() * viewport.scale(), clipping);
204 glDisable(GL_BLEND);
205
206 if (clipping) {
207 glDisable(GL_SCISSOR_TEST);
208 }
209 }
210}
211
212} // namespace KWin
213
214#include "moc_shakecursor.cpp"
QPointF pos()
Definition cursor.cpp:204
QPointF hotspot() const
Definition cursor.cpp:182
QRectF geometry() const
Definition cursor.cpp:190
Display * waylandDisplay() const
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
Q_SCRIPTABLE void addRepaint(const QRectF &r)
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
PlatformCursorImage cursorImage() const
KSharedConfigPtr config() const
bool setColorspaceUniformsFromSRGB(const ColorDescription &dst)
Definition glshader.cpp:457
static std::unique_ptr< GLTexture > upload(const QImage &image)
void installInputEventSpy(InputEventSpy *spy)
const ColorDescription & colorDescription() const
QMatrix4x4 projectionMatrix() const
QRectF mapToRenderTarget(const QRectF &logicalGeometry) const
static ShaderManager * instance()
GLShader * pushShader(ShaderTraits traits)
void pointerEvent(MouseEvent *event) override
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override
void reconfigure(ReconfigureFlags flags) override
bool isActive() const override
void setSensitivity(qreal sensitivity)
std::optional< qreal > update(QMouseEvent *event)
void setInterval(quint64 interval)
static double animationTime(const KConfigGroup &cfg, const QString &key, int defaultTime)
Definition effect.cpp:483
@ ReconfigureAll
Definition effect.h:601
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
InputRedirection * input()
Definition input.h:549
EffectsHandler * effects