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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2007 Christian Nitschkowski <christian.nitschkowski@kdemail.net>
0007     SPDX-FileCopyrightText: 2023 Andrew Shark <ashark at linuxcomp.ru>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "mousemark.h"
0013 #include "mousemarklogging.h"
0014 
0015 // KConfigSkeleton
0016 #include "mousemarkconfig.h"
0017 
0018 #include "core/rendertarget.h"
0019 #include "core/renderviewport.h"
0020 #include "effect/effecthandler.h"
0021 #include "opengl/glplatform.h"
0022 #include <KGlobalAccel>
0023 #include <KLocalizedString>
0024 #include <QAction>
0025 
0026 #include <QPainter>
0027 
0028 #include <cmath>
0029 
0030 namespace KWin
0031 {
0032 
0033 static consteval QPoint nullPoint()
0034 {
0035     return QPoint(-1, -1);
0036 }
0037 
0038 MouseMarkEffect::MouseMarkEffect()
0039 {
0040     MouseMarkConfig::instance(effects->config());
0041     QAction *a = new QAction(this);
0042     a->setObjectName(QStringLiteral("ClearMouseMarks"));
0043     a->setText(i18n("Clear All Mouse Marks"));
0044     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::SHIFT | Qt::META | Qt::Key_F11));
0045     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::SHIFT | Qt::META | Qt::Key_F11));
0046     connect(a, &QAction::triggered, this, &MouseMarkEffect::clear);
0047     a = new QAction(this);
0048     a->setObjectName(QStringLiteral("ClearLastMouseMark"));
0049     a->setText(i18n("Clear Last Mouse Mark"));
0050     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::SHIFT | Qt::META | Qt::Key_F12));
0051     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::SHIFT | Qt::META | Qt::Key_F12));
0052     connect(a, &QAction::triggered, this, &MouseMarkEffect::clearLast);
0053 
0054     connect(effects, &EffectsHandler::mouseChanged, this, &MouseMarkEffect::slotMouseChanged);
0055     connect(effects, &EffectsHandler::screenLockingChanged, this, &MouseMarkEffect::screenLockingChanged);
0056     reconfigure(ReconfigureAll);
0057     arrow_tail = nullPoint();
0058     effects->startMousePolling(); // We require it to detect activation as well
0059 }
0060 
0061 MouseMarkEffect::~MouseMarkEffect()
0062 {
0063     effects->stopMousePolling();
0064 }
0065 
0066 static int width_2 = 1;
0067 void MouseMarkEffect::reconfigure(ReconfigureFlags)
0068 {
0069     m_freedraw_modifiers = Qt::KeyboardModifiers();
0070     m_arrowdraw_modifiers = Qt::KeyboardModifiers();
0071     MouseMarkConfig::self()->read();
0072     width = MouseMarkConfig::lineWidth();
0073     width_2 = width / 2;
0074     color = MouseMarkConfig::color();
0075     color.setAlphaF(1.0);
0076     if (MouseMarkConfig::freedrawshift()) {
0077         m_freedraw_modifiers |= Qt::ShiftModifier;
0078     }
0079     if (MouseMarkConfig::freedrawalt()) {
0080         m_freedraw_modifiers |= Qt::AltModifier;
0081     }
0082     if (MouseMarkConfig::freedrawcontrol()) {
0083         m_freedraw_modifiers |= Qt::ControlModifier;
0084     }
0085     if (MouseMarkConfig::freedrawmeta()) {
0086         m_freedraw_modifiers |= Qt::MetaModifier;
0087     }
0088 
0089     if (MouseMarkConfig::arrowdrawshift()) {
0090         m_arrowdraw_modifiers |= Qt::ShiftModifier;
0091     }
0092     if (MouseMarkConfig::arrowdrawalt()) {
0093         m_arrowdraw_modifiers |= Qt::AltModifier;
0094     }
0095     if (MouseMarkConfig::arrowdrawcontrol()) {
0096         m_arrowdraw_modifiers |= Qt::ControlModifier;
0097     }
0098     if (MouseMarkConfig::arrowdrawmeta()) {
0099         m_arrowdraw_modifiers |= Qt::MetaModifier;
0100     }
0101 }
0102 
0103 void MouseMarkEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
0104 {
0105     effects->paintScreen(renderTarget, viewport, mask, region, screen); // paint normal screen
0106     if (marks.isEmpty() && drawing.isEmpty()) {
0107         return;
0108     }
0109     if (effects->isOpenGLCompositing()) {
0110         if (!GLPlatform::instance()->isGLES()) {
0111             glEnable(GL_BLEND);
0112             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0113 
0114             glEnable(GL_LINE_SMOOTH);
0115             glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
0116         }
0117         glLineWidth(width);
0118         GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0119         vbo->reset();
0120         const auto scale = viewport.scale();
0121         ShaderBinder binder(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace);
0122         binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix());
0123         binder.shader()->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
0124         binder.shader()->setUniform(GLShader::ColorUniform::Color, color);
0125         QList<QVector2D> verts;
0126         for (const Mark &mark : std::as_const(marks)) {
0127             verts.clear();
0128             verts.reserve(mark.size() * 2);
0129             for (const QPointF &p : std::as_const(mark)) {
0130                 verts.push_back(QVector2D(p.x() * scale, p.y() * scale));
0131             }
0132             vbo->setVertices(verts);
0133             vbo->render(GL_LINE_STRIP);
0134         }
0135         if (!drawing.isEmpty()) {
0136             verts.clear();
0137             verts.reserve(drawing.size() * 2);
0138             for (const QPointF &p : std::as_const(drawing)) {
0139                 verts.push_back(QVector2D(p.x() * scale, p.y() * scale));
0140             }
0141             vbo->setVertices(verts);
0142             vbo->render(GL_LINE_STRIP);
0143         }
0144         glLineWidth(1.0);
0145         if (!GLPlatform::instance()->isGLES()) {
0146             glDisable(GL_LINE_SMOOTH);
0147             glDisable(GL_BLEND);
0148         }
0149     } else if (effects->compositingType() == QPainterCompositing) {
0150         QPainter *painter = effects->scenePainter();
0151         painter->save();
0152         QPen pen(color);
0153         pen.setWidth(width);
0154         painter->setPen(pen);
0155         for (const Mark &mark : std::as_const(marks)) {
0156             drawMark(painter, mark);
0157         }
0158         drawMark(painter, drawing);
0159         painter->restore();
0160     }
0161 }
0162 
0163 void MouseMarkEffect::drawMark(QPainter *painter, const Mark &mark)
0164 {
0165     if (mark.count() <= 1) {
0166         return;
0167     }
0168     for (int i = 0; i < mark.count() - 1; ++i) {
0169         painter->drawLine(mark[i], mark[i + 1]);
0170     }
0171 }
0172 
0173 void MouseMarkEffect::slotMouseChanged(const QPointF &pos, const QPointF &,
0174                                        Qt::MouseButtons, Qt::MouseButtons,
0175                                        Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers)
0176 {
0177     qCDebug(KWIN_MOUSEMARK) << "MouseChanged" << pos;
0178     if (modifiers == m_arrowdraw_modifiers && m_arrowdraw_modifiers != Qt::NoModifier) { // start/finish arrow
0179         if (arrow_tail != nullPoint()) {
0180             if (drawing.length() != 0) {
0181                 clearLast();  // clear our arrow with tail at previous position
0182             }
0183             drawing = createArrow(pos, arrow_tail);
0184             effects->addRepaintFull();
0185             return;
0186         } else {
0187             if (drawing.length() > 0) { // has unfinished freedraw right before arrowdraw
0188                 marks.append(drawing);
0189                 drawing.clear();
0190             }
0191             arrow_tail = pos;
0192         }
0193     } else if (modifiers == m_freedraw_modifiers && m_freedraw_modifiers != Qt::NoModifier ) { // activated
0194         if (arrow_tail != nullPoint()) {
0195             arrow_tail = nullPoint(); // for the case when user started freedraw right after arrowdraw
0196             marks.append(drawing);
0197             drawing.clear();
0198         }
0199         if (drawing.isEmpty()) {
0200             drawing.append(pos);
0201         }
0202         if (drawing.last() == pos) {
0203             return;
0204         }
0205         QPointF pos2 = drawing.last();
0206         drawing.append(pos);
0207         QRect repaint = QRect(std::min(pos.x(), pos2.x()), std::min(pos.y(), pos2.y()),
0208                               std::max(pos.x(), pos2.x()), std::max(pos.y(), pos2.y()));
0209         repaint.adjust(-width, -width, width, width);
0210         effects->addRepaint(repaint);
0211     } else { // neither freedraw, nor arrowdraw modifiers pressed, but mouse moved
0212         if (drawing.length() > 1) {
0213             marks.append(drawing);
0214             drawing.clear();
0215         }
0216         arrow_tail = nullPoint();
0217     }
0218 }
0219 
0220 void MouseMarkEffect::clear()
0221 {
0222     arrow_tail = nullPoint();
0223     drawing.clear();
0224     marks.clear();
0225     effects->addRepaintFull();
0226 }
0227 
0228 void MouseMarkEffect::clearLast()
0229 {
0230     if (drawing.length() > 1) { // just pressing a modifiers already create a drawing with 1 point (so not visible), treat it as non-existent
0231         drawing.clear();
0232         effects->addRepaintFull();
0233     } else if (!marks.isEmpty()) {
0234         marks.pop_back();
0235         effects->addRepaintFull();
0236     }
0237 }
0238 
0239 MouseMarkEffect::Mark MouseMarkEffect::createArrow(QPointF arrow_head, QPointF arrow_tail)
0240 {
0241     Mark ret;
0242     double angle = atan2((double)(arrow_tail.y() - arrow_head.y()), (double)(arrow_tail.x() - arrow_head.x()));
0243     // Arrow is made of connected lines. We make it's last point at tail, so freedraw can begin from the tail
0244     ret += arrow_head;
0245     ret += arrow_head + QPoint(50 * cos(angle + M_PI / 6),
0246                                 50 * sin(angle + M_PI / 6)); // right one
0247     ret += arrow_head;
0248     ret += arrow_head + QPoint(50 * cos(angle - M_PI / 6),
0249                                 50 * sin(angle - M_PI / 6)); // left one
0250     ret += arrow_head;
0251     ret += arrow_tail;
0252     return ret;
0253 }
0254 
0255 void MouseMarkEffect::screenLockingChanged(bool locked)
0256 {
0257     if (!marks.isEmpty() || !drawing.isEmpty()) {
0258         effects->addRepaintFull();
0259     }
0260     // disable mouse polling while screen is locked.
0261     if (locked) {
0262         effects->stopMousePolling();
0263     } else {
0264         effects->startMousePolling();
0265     }
0266 }
0267 
0268 bool MouseMarkEffect::isActive() const
0269 {
0270     return (!marks.isEmpty() || !drawing.isEmpty()) && !effects->isScreenLocked();
0271 }
0272 
0273 int MouseMarkEffect::requestedEffectChainPosition() const
0274 {
0275     return 10;
0276 }
0277 
0278 } // namespace
0279 
0280 #include "moc_mousemark.cpp"