mirror of https://gitee.com/openkylin/kwin.git
touchclick: add touch trail effect
Signed-off-by: Hongfei Shang <shanghongfei@kylinos.cn>
This commit is contained in:
parent
11bf71e00a
commit
8e3c5e20f4
|
@ -161,6 +161,7 @@ add_subdirectory(snaphelper)
|
|||
add_subdirectory(startupfeedback)
|
||||
add_subdirectory(trackmouse)
|
||||
add_subdirectory(touchclick)
|
||||
add_subdirectory(touchtrail)
|
||||
add_subdirectory(wobblywindows)
|
||||
|
||||
###############################################################################
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#######################################
|
||||
# Effect
|
||||
|
||||
set(touchtrail_SOURCES
|
||||
touchtrail.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
kwin4_add_effect_module(kwin4_effect_touchtrail ${touchtrail_SOURCES})
|
||||
|
||||
|
||||
# Data files
|
||||
install(FILES data/trailing.png DESTINATION ${KDE_INSTALL_DATADIR}/${KWIN_NAME})
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2022 Hongfei Shang <shanghongfei@kylinos.cn>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "touchtrail.h"
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
||||
KWIN_EFFECT_FACTORY_SUPPORTED(TouchTrailEffect,
|
||||
"metadata.json.stripped",
|
||||
return TouchTrailEffect::supported();)
|
||||
|
||||
} // namespace KWin
|
||||
|
||||
#include "main.moc"
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"KPlugin": {
|
||||
"Category": "Appearance",
|
||||
"Description": "Visualize touch points",
|
||||
"Description[ar]": "تصور نقاط اللمس",
|
||||
"Description[az]": "Ekranda toxunan nöqtələri canlandırır",
|
||||
"Description[ca@valencia]": "Visualitza els punts de contacte",
|
||||
"Description[ca]": "Visualitza els punts de contacte",
|
||||
"Description[cs]": "Vizualizovat dotekové body",
|
||||
"Description[en_GB]": "Visualise touch points",
|
||||
"Description[es]": "Visualizar los puntos de contacto",
|
||||
"Description[fi]": "Korosta kosketuspisteet",
|
||||
"Description[fr]": "Afficher les points tactiles",
|
||||
"Description[hu]": "Vizualizálja az érintési pontokat",
|
||||
"Description[ia]": "Visualisa punctos de contacto",
|
||||
"Description[id]": "Memvisualkan titik sentuh",
|
||||
"Description[it]": "Visualizza i punti di tocco",
|
||||
"Description[ko]": "터치 지점 표시",
|
||||
"Description[nl]": "Aanraakpunten visualiseren",
|
||||
"Description[nn]": "Visualiser kontaktpunkt",
|
||||
"Description[pl]": "Uwidacznia punkty dotyku",
|
||||
"Description[pt_BR]": "Visualizar pontos de toque",
|
||||
"Description[ru]": "Визуализация событий касания экрана",
|
||||
"Description[sk]": "Vizualizovať dotykové body",
|
||||
"Description[sl]": "Vizualiziraj točke dotika",
|
||||
"Description[sv]": "Åskådliggör beröringspunkter",
|
||||
"Description[tr]": "Dokunma noktalarını görselleştir",
|
||||
"Description[uk]": "Візуалізувати точки дотику",
|
||||
"Description[x-test]": "xxVisualize touch pointsxx",
|
||||
"Description[zh_CN]": "高亮显示在触控屏幕上的触摸点",
|
||||
"EnabledByDefault": true,
|
||||
"Id": "touchtrail",
|
||||
"License": "GPL",
|
||||
"Name": "Touch Points",
|
||||
"Name[ar]": "نقاط اللمس",
|
||||
"Name[az]": "Toxunma nöqtələri",
|
||||
"Name[ca@valencia]": "Punts de contacte",
|
||||
"Name[ca]": "Punts de contacte",
|
||||
"Name[cs]": "Dotekové body",
|
||||
"Name[en_GB]": "Touch Points",
|
||||
"Name[es]": "Puntos de contacto",
|
||||
"Name[fi]": "Kosketuspisteet",
|
||||
"Name[fr]": "Points tactiles",
|
||||
"Name[hu]": "Érintési pontok",
|
||||
"Name[ia]": "Punctos de contacto",
|
||||
"Name[it]": "Punti di tocco",
|
||||
"Name[ko]": "터치 지점",
|
||||
"Name[nl]": "Aanraakpunten",
|
||||
"Name[nn]": "Kontaktpunkt",
|
||||
"Name[pl]": "Punkty dotyku",
|
||||
"Name[pt_BR]": "Pontos de toque",
|
||||
"Name[ru]": "Точки прикосновения",
|
||||
"Name[sk]": "Dotykové body",
|
||||
"Name[sl]": "Točke dotika",
|
||||
"Name[sv]": "Beröringspunkter",
|
||||
"Name[tr]": "Dokunma Noktaları",
|
||||
"Name[uk]": "Точки дотику",
|
||||
"Name[x-test]": "xxTouch Pointsxx",
|
||||
"Name[zh_CN]": "触摸点高亮"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
#include "touchtrail.h"
|
||||
|
||||
#include <kwinglplatform.h>
|
||||
|
||||
#include <QTime>
|
||||
#include <QByteArray>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace KWin{
|
||||
|
||||
TouchTrailEffect::TouchTrailEffect()
|
||||
: Effect()
|
||||
, m_texture(nullptr)
|
||||
{
|
||||
loadTexture();
|
||||
loadShader();
|
||||
}
|
||||
|
||||
TouchTrailEffect::~TouchTrailEffect()
|
||||
{
|
||||
if (m_texture)
|
||||
delete m_texture;
|
||||
m_texture = nullptr;
|
||||
}
|
||||
|
||||
bool TouchTrailEffect::supported()
|
||||
{
|
||||
return effects->isOpenGLCompositing();
|
||||
}
|
||||
|
||||
void TouchTrailEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
|
||||
{
|
||||
int time = 0;
|
||||
if (m_lastPresentTime.count()) {
|
||||
time = (presentTime - m_lastPresentTime).count();
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_touchPoints.size(); ++i) {
|
||||
QVector<TouchPoint>::iterator it = m_touchPoints[i].begin();
|
||||
while (it != m_touchPoints[i].end()) {
|
||||
it->life += time;
|
||||
++it;
|
||||
}
|
||||
|
||||
while (m_touchPoints[i].size() && m_touchPoints[i].first().life > MAX_LIFE) {
|
||||
if (m_touchPoints[i].size() == 1) {
|
||||
int detail = m_touchPoints[i].first().detail;
|
||||
if (!m_idIsUse[i]) {
|
||||
m_detail2ID.erase(detail);
|
||||
}
|
||||
}
|
||||
m_touchPoints[i].removeFirst();
|
||||
}
|
||||
}
|
||||
calTriPoints();
|
||||
|
||||
if (0 == m_detail2ID.size()) {
|
||||
m_lastPresentTime = std::chrono::milliseconds::zero();
|
||||
} else {
|
||||
m_lastPresentTime = presentTime;
|
||||
}
|
||||
|
||||
effects->prePaintScreen(data, presentTime);
|
||||
}
|
||||
|
||||
void TouchTrailEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data)
|
||||
{
|
||||
// printf("adasdasds\n");
|
||||
effects->paintScreen(mask, region, data);
|
||||
if (effects->isOpenGLCompositing()) {
|
||||
paintScreenGL(data);
|
||||
}
|
||||
}
|
||||
|
||||
void TouchTrailEffect::paintScreenGL(ScreenPaintData &data)
|
||||
{
|
||||
GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
|
||||
shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
|
||||
|
||||
for (int i = 0; i < m_triPoints.size(); ++i)
|
||||
{
|
||||
QVector<float> vert, texCoord;
|
||||
const int &n = m_triPoints[i].size();
|
||||
for (int j = 0; j < n; ++j) {
|
||||
vert << m_triPoints[i][j].x() << m_triPoints[i][j].y();
|
||||
texCoord << ((j & 1) ? 1.0f : 0.0f) << (j >> 1) * 1.0 / (n >> 1);
|
||||
}
|
||||
vbo->reset();
|
||||
m_texture->bind();
|
||||
vbo->setData(vert.size() / 2, 2, vert.data(), texCoord.data());
|
||||
vbo->render(GL_TRIANGLE_STRIP);
|
||||
m_texture->unbind();
|
||||
}
|
||||
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
ShaderManager::instance()->popShader();
|
||||
}
|
||||
|
||||
void TouchTrailEffect::postPaintScreen()
|
||||
{
|
||||
effects->postPaintScreen();
|
||||
repaint();
|
||||
}
|
||||
|
||||
bool TouchTrailEffect::isActive() const
|
||||
{
|
||||
return m_detail2ID.size();
|
||||
}
|
||||
|
||||
bool TouchTrailEffect::touchDown(qint32 id, const QPointF &pos, quint32 time)
|
||||
{
|
||||
// printf("当前手指ID=%d\n", id);
|
||||
insertTouchPoint(id, pos);
|
||||
repaint();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TouchTrailEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time)
|
||||
{
|
||||
updateTouchPoint(id, pos);
|
||||
repaint();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TouchTrailEffect::touchUp(qint32 id, quint32 time)
|
||||
{
|
||||
// printf("离开的手指ID=%d\n", id);
|
||||
removeTouchPoint(id);
|
||||
// 各触摸点开始快速衰减
|
||||
// TODO 加上滑动距离作为衰减因子
|
||||
for (int i = 0; i < m_touchPoints.size(); ++i) {
|
||||
int sub;
|
||||
for (int j = 0; j < m_touchPoints[i].size(); j++) {
|
||||
if (!j) {
|
||||
sub = MAX_LIFE - m_touchPoints[i][j].life;
|
||||
m_touchPoints[i][j].life = MAX_LIFE;
|
||||
|
||||
} else {
|
||||
m_touchPoints[i][j].life += sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TouchTrailEffect::insertTouchPoint(qint32 detail, QPointF pos)
|
||||
{
|
||||
int id = -1;
|
||||
do {
|
||||
++id;
|
||||
if (id == m_touchPoints.size()) {
|
||||
m_touchPoints.append(QVector<TouchPoint>());
|
||||
m_triPoints.append(QVector<QPointF>());
|
||||
m_idIsUse.append(false);
|
||||
}
|
||||
} while (m_idIsUse[id] || m_touchPoints[id].size());
|
||||
|
||||
m_detail2ID[detail] = id;
|
||||
m_idIsUse[id] = true;
|
||||
m_touchPoints[id].append({detail, pos, 0});
|
||||
|
||||
// printf("当前分配的ID为 %d\n", id);
|
||||
}
|
||||
|
||||
void TouchTrailEffect::updateTouchPoint(qint32 detail, QPointF pos)
|
||||
{
|
||||
// 开关混成有可能导致事件不成对
|
||||
if (m_detail2ID.find(detail) == m_detail2ID.end()) {
|
||||
return;
|
||||
}
|
||||
int id = m_detail2ID[detail];
|
||||
m_touchPoints[id].append({detail, pos, 0});
|
||||
}
|
||||
|
||||
void TouchTrailEffect::removeTouchPoint(qint32 detail)
|
||||
{
|
||||
// 开关混成有可能导致事件不成对
|
||||
if (m_detail2ID.find(detail) == m_detail2ID.end()) {
|
||||
return;
|
||||
}
|
||||
int id = m_detail2ID[detail];
|
||||
m_idIsUse[id] = false;
|
||||
if (!m_touchPoints[id].size()) {
|
||||
m_detail2ID.erase(detail);
|
||||
}
|
||||
}
|
||||
|
||||
void TouchTrailEffect::loadTexture()
|
||||
{
|
||||
#if defined(QT_NO_DEBUG)
|
||||
QString file_path = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral(DEFAULT_TEXTURE_IMAGE))};
|
||||
#else
|
||||
QString file_path = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/effects/touchtrail/data/") + QStringLiteral(DEFAULT_TEXTURE_IMAGE);
|
||||
#endif
|
||||
QImage img(file_path);
|
||||
if (img.isNull()) {
|
||||
qWarning() << "load image [" << file_path << "] failed";
|
||||
return;
|
||||
}
|
||||
|
||||
//! TODO 使用KWin中的纹理融合算法
|
||||
//! 没有找到kwin里,opengl的纹理融合
|
||||
//! 先手写个简单的纹理和颜色的融合,简单支持
|
||||
qreal Rd = 139.0 / 255.0;
|
||||
qreal Gd = 134.0 / 255.0;
|
||||
qreal Bd = 130.0 / 255.0;
|
||||
qreal Ad = 30.0 / 255.0;
|
||||
|
||||
for (int i = 0; i < img.width(); ++i) {
|
||||
for (int j = 0; j < img.height(); ++j) {
|
||||
QRgb p = img.pixel(i, j);
|
||||
qreal Rs = qRed(p) / 255.0;
|
||||
qreal Gs = qGreen(p) / 255.0;
|
||||
qreal Bs = qBlue(p) / 255.0;
|
||||
qreal As = qAlpha(p) / 255.0;
|
||||
// printf("%d\n", As);
|
||||
//! 纹理和颜色融合
|
||||
// img.setPixel(i, j, qRgba((Rs * As + Rd * (1 - As)) * 255,
|
||||
// (Gs * As + Gd * (1 - As)) * 255,
|
||||
// (Bs * As + Bd * (1 - As)) * 255,
|
||||
// (fmin(As, Ad)) * 255));
|
||||
//! 纹理仅控制形状
|
||||
img.setPixel(i, j, qRgba(Rd * 255,
|
||||
Gd * 255,
|
||||
Bd * 255,
|
||||
(fmin(As, Ad)) * 255));
|
||||
}
|
||||
}
|
||||
if (!m_texture)
|
||||
m_texture = new GLTexture(img);
|
||||
}
|
||||
|
||||
void TouchTrailEffect::loadShader()
|
||||
{
|
||||
// m_shader.reset(ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("coverswitch-reflection.glsl")));
|
||||
}
|
||||
|
||||
void TouchTrailEffect::calTriPoints()
|
||||
{
|
||||
for (int i = 0; i < m_touchPoints.size(); ++i)
|
||||
{
|
||||
m_triPoints[i].clear();
|
||||
if (m_touchPoints[i].size() < 3) continue;
|
||||
QPointF last(m_touchPoints[i].first().pos);
|
||||
QVector<TouchPoint>::iterator it = m_touchPoints[i].begin();
|
||||
for (++it; it != m_touchPoints[i].end(); ++it) {
|
||||
QPointF &cur = it->pos;
|
||||
qreal d = dist(cur.x(), cur.y(), last.x(), last.y());
|
||||
if (d < MIN_POINT_SEG) continue;
|
||||
if (cur.x() == last.x() || cur.y() == last.y()) {
|
||||
continue;
|
||||
}
|
||||
// 方向为 last->cur 的向量
|
||||
QPointF vec_l2c(cur.x() - last.x(), cur.y() - last.y());
|
||||
// cur last线段上的中点
|
||||
QPointF mid((cur.x() + last.x()) / 2, (cur.y() + last.y()) / 2);
|
||||
d /= 2;
|
||||
// 向量的斜率k
|
||||
qreal k = (cur.y() - last.y()) / (cur.x() - last.x());
|
||||
qreal vlk = 1.0 / k;
|
||||
qreal alpha = atan(fabs(vlk));
|
||||
qreal dx = DEFAULT_STROKE_WIDTH * cos(alpha);
|
||||
qreal dy = DEFAULT_STROKE_WIDTH * sin(alpha);
|
||||
QPointF p1, p2;
|
||||
|
||||
if (k > 0 && vec_l2c.x() > 0) {
|
||||
p1.setX(mid.x() - dx); p1.setY(mid.y() + dy);
|
||||
p2.setX(mid.x() + dx); p2.setY(mid.y() - dy);
|
||||
} else if (k > 0 && vec_l2c.x() < 0) {
|
||||
p1.setX(mid.x() + dx); p1.setY(mid.y() - dy);
|
||||
p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);;
|
||||
} else if (k < 0 && vec_l2c.x() > 0) {
|
||||
p1.setX(mid.x() + dx); p1.setY(mid.y() + dy);
|
||||
p2.setX(mid.x() - dx); p2.setY(mid.y() - dy);
|
||||
} else {
|
||||
p1.setX(mid.x() - dx); p1.setY(mid.y() - dy);
|
||||
p2.setX(mid.x() + dx); p2.setY(mid.y() + dy);
|
||||
}
|
||||
m_triPoints[i].append(p1);
|
||||
m_triPoints[i].append(p2);
|
||||
last = cur;
|
||||
|
||||
if (it + 1 == m_touchPoints[i].end()) {
|
||||
mid = it->pos;
|
||||
if (k > 0 && vec_l2c.x() > 0) {
|
||||
p1.setX(mid.x() - dx); p1.setY(mid.y() + dy);
|
||||
p2.setX(mid.x() + dx); p2.setY(mid.y() - dy);
|
||||
} else if (k > 0 && vec_l2c.x() < 0) {
|
||||
p1.setX(mid.x() + dx); p1.setY(mid.y() - dy);
|
||||
p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);;
|
||||
} else if (k < 0 && vec_l2c.x() > 0) {
|
||||
p1.setX(mid.x() + dx); p1.setY(mid.y() + dy);
|
||||
p2.setX(mid.x() - dx); p2.setY(mid.y() - dy);
|
||||
} else {
|
||||
p1.setX(mid.x() - dx); p1.setY(mid.y() - dy);
|
||||
p2.setX(mid.x() + dx); p2.setY(mid.y() + dy);
|
||||
}
|
||||
m_triPoints[i].append(p1);
|
||||
m_triPoints[i].append(p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TouchTrailEffect::repaint()
|
||||
{
|
||||
// effects->addRepaintFull();
|
||||
|
||||
if (m_triPoints.size()) {
|
||||
QRect dirtyRect;
|
||||
QPoint topLeft(INT32_MAX, INT32_MAX), bottomRight(INT32_MIN, INT32_MIN);
|
||||
for (int i = 0; i < m_triPoints.size(); ++i) {
|
||||
for (int j = 0; j < m_triPoints[i].size(); ++j) {
|
||||
QPointF &p = m_triPoints[i][j];
|
||||
topLeft.setX(fmin(topLeft.x(), p.x()));
|
||||
topLeft.setY(fmin(topLeft.y(), p.y()));
|
||||
bottomRight.setX(fmax(bottomRight.x(), p.x()));
|
||||
bottomRight.setY(fmax(bottomRight.y(), p.y()));
|
||||
}
|
||||
}
|
||||
dirtyRect.setTopLeft(topLeft);
|
||||
dirtyRect.setBottomRight(bottomRight);
|
||||
|
||||
m_dirtyRect = dirtyRect;
|
||||
effects->addRepaint(m_dirtyRect);
|
||||
} else {
|
||||
effects->addRepaintFull();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef KWIN_TOUCHMOTIONSTREAK_H
|
||||
#define KWIN_TOUCHMOTIONSTREAK_H
|
||||
|
||||
#include <kwineffects.h>
|
||||
#include <kwinglutils.h>
|
||||
|
||||
#include <QList>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace KWin {
|
||||
|
||||
#define MAX_FINGER_COUNT 10 // 触摸屏支持最大触摸点的个数
|
||||
#define MIN_POINT_SEG 2/*px*/ // 触摸点之间允许的最小欧氏距离
|
||||
#define DEFAULT_STROKE_WIDTH 3/*px*/ // 条带的宽度
|
||||
#define MAX_POINT_NUMBER 8 // 保存的触摸点的最大数量
|
||||
#define DEFAULT_TEXTURE_IMAGE "trailing.png" // 默认纹理
|
||||
#define MAX_LIFE 200 // 每个触摸点存活的最长时间
|
||||
|
||||
#define dist(x1,y1,x2,y2) sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
|
||||
|
||||
/*!
|
||||
* \brief UKUI Touch Motion Streak Effect
|
||||
* \author Yunpeng Zhu.
|
||||
*/
|
||||
class TouchTrailEffect
|
||||
: public Effect
|
||||
{
|
||||
Q_OBJECT
|
||||
typedef struct _touch_point{
|
||||
int detail;
|
||||
QPointF pos;
|
||||
int life;
|
||||
}TouchPoint;
|
||||
public:
|
||||
TouchTrailEffect();
|
||||
virtual ~TouchTrailEffect();
|
||||
|
||||
void prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime) override;
|
||||
void paintScreen(int mask, const QRegion ®ion, ScreenPaintData& data) override;
|
||||
void postPaintScreen() override;
|
||||
|
||||
bool isActive() const override;
|
||||
bool touchDown(qint32 id, const QPointF &pos, quint32 time) override;
|
||||
bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override;
|
||||
bool touchUp(qint32 id, quint32 time) override;
|
||||
|
||||
static bool supported();
|
||||
|
||||
private:
|
||||
void paintScreenGL(ScreenPaintData &data);
|
||||
void insertTouchPoint(qint32 detail, QPointF pos);
|
||||
void updateTouchPoint(qint32 detail, QPointF pos);
|
||||
void removeTouchPoint(qint32 detail);
|
||||
void loadTexture();
|
||||
void loadShader();
|
||||
/*!
|
||||
* \brief 计算三角形条带顶点的位置
|
||||
*/
|
||||
void calTriPoints();
|
||||
void repaint();
|
||||
|
||||
//! 保存触摸点坐标
|
||||
QVector<QVector<TouchPoint>> m_touchPoints;
|
||||
//! 保存所需要绘制的三角形条带的坐标
|
||||
QVector<QVector<QPointF>> m_triPoints;
|
||||
//! 标记id是否被使用
|
||||
QVector<bool> m_idIsUse;
|
||||
//! 将detail映射到0~MAX_FINGER_COUNT-1
|
||||
std::unordered_map<int,int> m_detail2ID;
|
||||
//! 绘制
|
||||
GLTexture *m_texture;
|
||||
//! 记录需要重绘的区域
|
||||
QRect m_dirtyRect;
|
||||
std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KWIN_TOUCHMOTIONSTREAK_H
|
Loading…
Reference in New Issue