diff --git a/autotests/test_builtin_effectloader.cpp b/autotests/test_builtin_effectloader.cpp
index de78965776..8100bff978 100644
--- a/autotests/test_builtin_effectloader.cpp
+++ b/autotests/test_builtin_effectloader.cpp
@@ -113,6 +113,7 @@ void TestBuiltInEffectLoader::testHasEffect_data()
QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << true;
QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << true;
QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true;
+ QTest::newRow("Touchpoints") << QStringLiteral("touchpoints") << true;
QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true;
QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true;
QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << true;
@@ -168,6 +169,7 @@ void TestBuiltInEffectLoader::testKnownEffects()
<< QStringLiteral("snaphelper")
<< QStringLiteral("startupfeedback")
<< QStringLiteral("thumbnailaside")
+ << QStringLiteral("touchpoints")
<< QStringLiteral("trackmouse")
<< QStringLiteral("windowgeometry")
<< QStringLiteral("wobblywindows")
@@ -247,6 +249,7 @@ void TestBuiltInEffectLoader::testSupported_data()
QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << false << xc << true;
QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc << true;
QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc << true;
+ QTest::newRow("TouchPoints") << QStringLiteral("touchpoints") << true << xc << true;
QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc << true;
QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc << true;
QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << false << xc << true;
@@ -334,6 +337,7 @@ void TestBuiltInEffectLoader::testLoadEffect_data()
// Tries to load shader and makes our test abort
// QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc;
QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc;
+ QTest::newRow("Touchpoints") << QStringLiteral("touchpoints") << true << xc;
QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc;
// TODO: Accesses EffectFrame and crashes
// QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc;
diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt
index 0e5ed97e44..0fec45556a 100644
--- a/effects/CMakeLists.txt
+++ b/effects/CMakeLists.txt
@@ -90,6 +90,7 @@ set( kwin4_effect_builtins_sources
resize/resize.cpp
showfps/showfps.cpp
thumbnailaside/thumbnailaside.cpp
+ touchpoints/touchpoints.cpp
trackmouse/trackmouse.cpp
windowgeometry/windowgeometry.cpp
wobblywindows/wobblywindows.cpp
diff --git a/effects/effect_builtins.cpp b/effects/effect_builtins.cpp
index d59839bfe1..b09b481851 100644
--- a/effects/effect_builtins.cpp
+++ b/effects/effect_builtins.cpp
@@ -41,6 +41,7 @@ along with this program. If not, see .
#include "slide/slide.h"
#include "slideback/slideback.h"
#include "thumbnailaside/thumbnailaside.h"
+#include "touchpoints/touchpoints.h"
#include "windowgeometry/windowgeometry.h"
#include "zoom/zoom.h"
#include "logout/logout.h"
@@ -605,6 +606,21 @@ EFFECT_FALLBACK
nullptr,
nullptr
#endif
+EFFECT_FALLBACK
+ }, {
+ QStringLiteral("touchpoints"),
+ i18ndc("kwin_effects", "Name of a KWin Effect", "Touch Points"),
+ i18ndc("kwin_effects", "Comment describing the KWin Effect", "Visualize touch points"),
+ QStringLiteral("Appearance"),
+ QString(),
+ QUrl(),
+ false,
+ false,
+#ifdef EFFECT_BUILTINS
+ &createHelper,
+ nullptr,
+ nullptr
+#endif
EFFECT_FALLBACK
}, {
QStringLiteral("trackmouse"),
diff --git a/effects/touchpoints/touchpoints.cpp b/effects/touchpoints/touchpoints.cpp
new file mode 100644
index 0000000000..c8e7948584
--- /dev/null
+++ b/effects/touchpoints/touchpoints.cpp
@@ -0,0 +1,322 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2012 Filip Wieladek
+Copyright (C) 2016 Martin Gräßlin
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+
+#include "touchpoints.h"
+
+#include
+#include
+
+#ifdef KWIN_HAVE_XRENDER_COMPOSITING
+#include
+#include
+#include
+#endif
+
+#include
+#include
+
+#include
+
+#include
+
+namespace KWin
+{
+
+TouchPointsEffect::TouchPointsEffect()
+ : Effect()
+{
+}
+
+TouchPointsEffect::~TouchPointsEffect() = default;
+
+static const Qt::GlobalColor s_colors[] = {
+ Qt::blue,
+ Qt::red,
+ Qt::green,
+ Qt::cyan,
+ Qt::magenta,
+ Qt::yellow,
+ Qt::gray,
+ Qt::darkBlue,
+ Qt::darkRed,
+ Qt::darkGreen
+};
+
+Qt::GlobalColor TouchPointsEffect::colorForId(quint32 id)
+{
+ auto it = m_colors.constFind(id);
+ if (it != m_colors.constEnd()) {
+ return it.value();
+ }
+ static int s_colorIndex = -1;
+ s_colorIndex = (s_colorIndex + 1) % 10;
+ m_colors.insert(id, s_colors[s_colorIndex]);
+ return s_colors[s_colorIndex];
+}
+
+bool TouchPointsEffect::touchDown(quint32 id, const QPointF &pos, quint32 time)
+{
+ TouchPoint point;
+ point.pos = pos;
+ point.press = true;
+ point.color = colorForId(id);
+ m_points << point;
+ m_latestPositions.insert(id, pos);
+ repaint();
+ return false;
+}
+
+bool TouchPointsEffect::touchMotion(quint32 id, const QPointF &pos, quint32 time)
+{
+ TouchPoint point;
+ point.pos = pos;
+ point.press = true;
+ point.color = colorForId(id);
+ m_points << point;
+ m_latestPositions.insert(id, pos);
+ repaint();
+ return false;
+}
+
+bool TouchPointsEffect::touchUp(quint32 id, quint32 time)
+{
+ auto it = m_latestPositions.constFind(id);
+ if (it != m_latestPositions.constEnd()) {
+ TouchPoint point;
+ point.pos = it.value();
+ point.press = false;
+ point.color = colorForId(id);
+ m_points << point;
+ }
+ return false;
+}
+
+void TouchPointsEffect::prePaintScreen(ScreenPrePaintData& data, int time)
+{
+ auto it = m_points.begin();
+ while (it != m_points.end()) {
+ it->time += time;
+ if (it->time > m_ringLife) {
+ it = m_points.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ effects->prePaintScreen(data, time);
+}
+
+void TouchPointsEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data)
+{
+ effects->paintScreen(mask, region, data);
+
+ paintScreenSetup(mask, region, data);
+ for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) {
+ for (int i = 0; i < m_ringCount; ++i) {
+ float alpha = computeAlpha(it->time, i);
+ float size = computeRadius(it->time, it->press, i);
+ if (size > 0 && alpha > 0) {
+ QColor color = it->color;
+ color.setAlphaF(alpha);
+ drawCircle(color, it->pos.x(), it->pos.y(), size);
+ }
+ }
+ }
+ paintScreenFinish(mask, region, data);
+}
+
+void TouchPointsEffect::postPaintScreen()
+{
+ effects->postPaintScreen();
+ repaint();
+}
+
+float TouchPointsEffect::computeRadius(int time, bool press, int ring)
+{
+ float ringDistance = m_ringLife / (m_ringCount * 3);
+ if (press) {
+ return ((time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
+ }
+ return ((m_ringLife - time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
+}
+
+float TouchPointsEffect::computeAlpha(int time, int ring)
+{
+ float ringDistance = m_ringLife / (m_ringCount * 3);
+ return (m_ringLife - (float)time - ringDistance * (ring)) / m_ringLife;
+}
+
+void TouchPointsEffect::repaint()
+{
+ if (!m_points.isEmpty()) {
+ QRegion dirtyRegion;
+ const int radius = m_ringMaxSize + m_lineWidth;
+ for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) {
+ dirtyRegion |= QRect(it->pos.x() - radius, it->pos.y() - radius, 2*radius, 2*radius);
+ }
+ effects->addRepaint(dirtyRegion);
+ }
+}
+
+bool TouchPointsEffect::isActive() const
+{
+ return !m_points.isEmpty();
+}
+
+void TouchPointsEffect::drawCircle(const QColor& color, float cx, float cy, float r)
+{
+ if (effects->isOpenGLCompositing())
+ drawCircleGl(color, cx, cy, r);
+ if (effects->compositingType() == XRenderCompositing)
+ drawCircleXr(color, cx, cy, r);
+ if (effects->compositingType() == QPainterCompositing)
+ drawCircleQPainter(color, cx, cy, r);
+}
+
+void TouchPointsEffect::paintScreenSetup(int mask, QRegion region, ScreenPaintData& data)
+{
+ if (effects->isOpenGLCompositing())
+ paintScreenSetupGl(mask, region, data);
+}
+
+void TouchPointsEffect::paintScreenFinish(int mask, QRegion region, ScreenPaintData& data)
+{
+ if (effects->isOpenGLCompositing())
+ paintScreenFinishGl(mask, region, data);
+}
+
+void TouchPointsEffect::drawCircleGl(const QColor& color, float cx, float cy, float r)
+{
+ static const int num_segments = 80;
+ static const float theta = 2 * 3.1415926 / float(num_segments);
+ static const float c = cosf(theta); //precalculate the sine and cosine
+ static const float s = sinf(theta);
+ float t;
+
+ float x = r;//we start at angle = 0
+ float y = 0;
+
+ GLVertexBuffer* vbo = GLVertexBuffer::streamingBuffer();
+ vbo->reset();
+ vbo->setUseColor(true);
+ vbo->setColor(color);
+ QVector verts;
+ verts.reserve(num_segments * 2);
+
+ for (int ii = 0; ii < num_segments; ++ii) {
+ verts << x + cx << y + cy;//output vertex
+ //apply the rotation matrix
+ t = x;
+ x = c * x - s * y;
+ y = s * t + c * y;
+ }
+ vbo->setData(verts.size() / 2, 2, verts.data(), NULL);
+ vbo->render(GL_LINE_LOOP);
+}
+
+void TouchPointsEffect::drawCircleXr(const QColor& color, float cx, float cy, float r)
+{
+#ifdef KWIN_HAVE_XRENDER_COMPOSITING
+ if (r <= m_lineWidth)
+ return;
+
+ int num_segments = r+8;
+ float theta = 2.0 * 3.1415926 / num_segments;
+ float cos = cosf(theta); //precalculate the sine and cosine
+ float sin = sinf(theta);
+ float x[2] = {r, r-m_lineWidth};
+ float y[2] = {0, 0};
+
+#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536))
+ QVector strip;
+ strip.reserve(2*num_segments+2);
+
+ xcb_render_pointfix_t point;
+ point.x = DOUBLE_TO_FIXED(x[1]+cx);
+ point.y = DOUBLE_TO_FIXED(y[1]+cy);
+ strip << point;
+
+ for (int i = 0; i < num_segments; ++i) {
+ //apply the rotation matrix
+ const float h[2] = {x[0], x[1]};
+ x[0] = cos * x[0] - sin * y[0];
+ x[1] = cos * x[1] - sin * y[1];
+ y[0] = sin * h[0] + cos * y[0];
+ y[1] = sin * h[1] + cos * y[1];
+
+ point.x = DOUBLE_TO_FIXED(x[0]+cx);
+ point.y = DOUBLE_TO_FIXED(y[0]+cy);
+ strip << point;
+
+ point.x = DOUBLE_TO_FIXED(x[1]+cx);
+ point.y = DOUBLE_TO_FIXED(y[1]+cy);
+ strip << point;
+ }
+
+ const float h = x[0];
+ x[0] = cos * x[0] - sin * y[0];
+ y[0] = sin * h + cos * y[0];
+
+ point.x = DOUBLE_TO_FIXED(x[0]+cx);
+ point.y = DOUBLE_TO_FIXED(y[0]+cy);
+ strip << point;
+
+ XRenderPicture fill = xRenderFill(color);
+ xcb_render_tri_strip(xcbConnection(), XCB_RENDER_PICT_OP_OVER,
+ fill, effects->xrenderBufferPicture(), 0,
+ 0, 0, strip.count(), strip.constData());
+#undef DOUBLE_TO_FIXED
+#else
+ Q_UNUSED(color)
+ Q_UNUSED(cx)
+ Q_UNUSED(cy)
+ Q_UNUSED(r)
+#endif
+}
+
+void TouchPointsEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r)
+{
+ QPainter *painter = effects->scenePainter();
+ painter->save();
+ painter->setPen(color);
+ painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760);
+ painter->restore();
+}
+
+void TouchPointsEffect::paintScreenSetupGl(int, QRegion, ScreenPaintData &data)
+{
+ GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor);
+ shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix());
+
+ glLineWidth(m_lineWidth);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void TouchPointsEffect::paintScreenFinishGl(int, QRegion, ScreenPaintData&)
+{
+ glDisable(GL_BLEND);
+
+ ShaderManager::instance()->popShader();
+}
+
+} // namespace
+
diff --git a/effects/touchpoints/touchpoints.h b/effects/touchpoints/touchpoints.h
new file mode 100644
index 0000000000..15296514ec
--- /dev/null
+++ b/effects/touchpoints/touchpoints.h
@@ -0,0 +1,99 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 2012 Filip Wieladek
+Copyright (C) 2016 Martin Gräßlin
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*********************************************************************/
+
+#ifndef KWIN_TOUCHPOINTS_H
+#define KWIN_TOUCHPOINTS_H
+
+#include
+
+namespace KWin
+{
+
+class TouchPointsEffect
+ : public Effect
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal lineWidth READ lineWidth)
+ Q_PROPERTY(int ringLife READ ringLife)
+ Q_PROPERTY(int ringSize READ ringSize)
+ Q_PROPERTY(int ringCount READ ringCount)
+public:
+ TouchPointsEffect();
+ ~TouchPointsEffect();
+ void prePaintScreen(ScreenPrePaintData& data, int time) override;
+ void paintScreen(int mask, QRegion region, ScreenPaintData& data) override;
+ void postPaintScreen() override;
+ bool isActive() const override;
+ bool touchDown(quint32 id, const QPointF &pos, quint32 time) override;
+ bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override;
+ bool touchUp(quint32 id, quint32 time) override;
+
+ // for properties
+ qreal lineWidth() const {
+ return m_lineWidth;
+ }
+ int ringLife() const {
+ return m_ringLife;
+ }
+ int ringSize() const {
+ return m_ringMaxSize;
+ }
+ int ringCount() const {
+ return m_ringCount;
+ }
+
+private:
+ inline void drawCircle(const QColor& color, float cx, float cy, float r);
+ inline void paintScreenSetup(int mask, QRegion region, ScreenPaintData& data);
+ inline void paintScreenFinish(int mask, QRegion region, ScreenPaintData& data);
+
+ void repaint();
+
+ float computeAlpha(int time, int ring);
+ float computeRadius(int time, bool press, int ring);
+ void drawCircleGl(const QColor& color, float cx, float cy, float r);
+ void drawCircleXr(const QColor& color, float cx, float cy, float r);
+ void drawCircleQPainter(const QColor& color, float cx, float cy, float r);
+ void paintScreenSetupGl(int mask, QRegion region, ScreenPaintData& data);
+ void paintScreenFinishGl(int mask, QRegion region, ScreenPaintData& data);
+
+ Qt::GlobalColor colorForId(quint32 id);
+
+ int m_ringCount = 2;
+ float m_lineWidth = 1.0;
+ int m_ringLife = 300;
+ float m_ringMaxSize = 20.0;
+
+ struct TouchPoint {
+ QPointF pos;
+ int time = 0;
+ bool press;
+ QColor color;
+ };
+ QVector m_points;
+ QHash m_latestPositions;
+ QHash m_colors;
+
+};
+
+} // namespace
+
+#endif