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