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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2007 Christian Nitschkowski <christian.nitschkowski@kdemail.net>
0007     SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "magnifier.h"
0013 // KConfigSkeleton
0014 #include "magnifierconfig.h"
0015 
0016 #include <QAction>
0017 #include <kstandardaction.h>
0018 
0019 #include "core/renderviewport.h"
0020 #include "effect/effecthandler.h"
0021 #include "opengl/glutils.h"
0022 #include <KGlobalAccel>
0023 
0024 namespace KWin
0025 {
0026 
0027 const int FRAME_WIDTH = 5;
0028 
0029 MagnifierEffect::MagnifierEffect()
0030     : m_zoom(1)
0031     , m_targetZoom(1)
0032     , m_polling(false)
0033     , m_lastPresentTime(std::chrono::milliseconds::zero())
0034     , m_texture(nullptr)
0035     , m_fbo(nullptr)
0036 {
0037     MagnifierConfig::instance(effects->config());
0038     QAction *a;
0039     a = KStandardAction::zoomIn(this, &MagnifierEffect::zoomIn, this);
0040     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Plus) << (Qt::META | Qt::Key_Equal));
0041     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Plus) << (Qt::META | Qt::Key_Equal));
0042 
0043     a = KStandardAction::zoomOut(this, &MagnifierEffect::zoomOut, this);
0044     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Minus));
0045     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Minus));
0046 
0047     a = KStandardAction::actualSize(this, &MagnifierEffect::toggle, this);
0048     KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_0));
0049     KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_0));
0050 
0051     connect(effects, &EffectsHandler::mouseChanged, this, &MagnifierEffect::slotMouseChanged);
0052     connect(effects, &EffectsHandler::windowAdded, this, &MagnifierEffect::slotWindowAdded);
0053 
0054     const auto windows = effects->stackingOrder();
0055     for (EffectWindow *window : windows) {
0056         slotWindowAdded(window);
0057     }
0058 
0059     reconfigure(ReconfigureAll);
0060 }
0061 
0062 MagnifierEffect::~MagnifierEffect()
0063 {
0064     // Save the zoom value.
0065     MagnifierConfig::setInitialZoom(m_targetZoom);
0066     MagnifierConfig::self()->save();
0067 }
0068 
0069 bool MagnifierEffect::supported()
0070 {
0071     return effects->isOpenGLCompositing() && GLFramebuffer::blitSupported();
0072 }
0073 
0074 void MagnifierEffect::reconfigure(ReconfigureFlags)
0075 {
0076     MagnifierConfig::self()->read();
0077     int width, height;
0078     width = MagnifierConfig::width();
0079     height = MagnifierConfig::height();
0080     m_magnifierSize = QSize(width, height);
0081     // Load the saved zoom value.
0082     m_targetZoom = MagnifierConfig::initialZoom();
0083     if (m_targetZoom != m_zoom) {
0084         toggle();
0085     }
0086 }
0087 
0088 void MagnifierEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0089 {
0090     const int time = m_lastPresentTime.count() ? (presentTime - m_lastPresentTime).count() : 0;
0091 
0092     if (m_zoom != m_targetZoom) {
0093         double diff = time / animationTime(500.0);
0094         if (m_targetZoom > m_zoom) {
0095             m_zoom = std::min(m_zoom * std::max(1 + diff, 1.2), m_targetZoom);
0096         } else {
0097             m_zoom = std::max(m_zoom * std::min(1 - diff, 0.8), m_targetZoom);
0098             if (m_zoom == 1.0) {
0099                 // zoom ended - delete FBO and texture
0100                 m_fbo.reset();
0101                 m_texture.reset();
0102             }
0103         }
0104     }
0105 
0106     if (m_zoom != m_targetZoom) {
0107         m_lastPresentTime = presentTime;
0108     } else {
0109         m_lastPresentTime = std::chrono::milliseconds::zero();
0110     }
0111 
0112     effects->prePaintScreen(data, presentTime);
0113     if (m_zoom != 1.0) {
0114         data.paint += magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH);
0115     }
0116 }
0117 
0118 void MagnifierEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
0119 {
0120     effects->paintScreen(renderTarget, viewport, mask, region, screen); // paint normal screen
0121     if (m_zoom != 1.0) {
0122         // get the right area from the current rendered screen
0123         const QRect area = magnifierArea();
0124         const QPointF cursor = cursorPos();
0125         const auto scale = viewport.scale();
0126 
0127         QRectF srcArea(cursor.x() - (double)area.width() / (m_zoom * 2),
0128                        cursor.y() - (double)area.height() / (m_zoom * 2),
0129                        (double)area.width() / m_zoom, (double)area.height() / m_zoom);
0130         if (effects->isOpenGLCompositing()) {
0131             m_fbo->blitFromRenderTarget(renderTarget, viewport, srcArea.toRect(), QRect(QPoint(), m_fbo->size()));
0132             // paint magnifier
0133             auto s = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
0134             QMatrix4x4 mvp = viewport.projectionMatrix();
0135             mvp.translate(area.x() * scale, area.y() * scale);
0136             s->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
0137             m_texture->render(area.size() * scale);
0138             ShaderManager::instance()->popShader();
0139 
0140             GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0141             vbo->reset();
0142 
0143             QRectF areaF = scaledRect(area, scale);
0144             const QRectF frame = scaledRect(area.adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH), scale);
0145             QList<QVector2D> verts;
0146             verts.reserve(4 * 6 * 2);
0147             // top frame
0148             verts.push_back(QVector2D(frame.right(), frame.top()));
0149             verts.push_back(QVector2D(frame.left(), frame.top()));
0150             verts.push_back(QVector2D(frame.left(), areaF.top()));
0151             verts.push_back(QVector2D(frame.left(), areaF.top()));
0152             verts.push_back(QVector2D(frame.right(), areaF.top()));
0153             verts.push_back(QVector2D(frame.right(), frame.top()));
0154             // left frame
0155             verts.push_back(QVector2D(areaF.left(), frame.top()));
0156             verts.push_back(QVector2D(frame.left(), frame.top()));
0157             verts.push_back(QVector2D(frame.left(), frame.bottom()));
0158             verts.push_back(QVector2D(frame.left(), frame.bottom()));
0159             verts.push_back(QVector2D(areaF.left(), frame.bottom()));
0160             verts.push_back(QVector2D(areaF.left(), frame.top()));
0161             // right frame
0162             verts.push_back(QVector2D(frame.right(), frame.top()));
0163             verts.push_back(QVector2D(areaF.right(), frame.top()));
0164             verts.push_back(QVector2D(areaF.right(), frame.bottom()));
0165             verts.push_back(QVector2D(areaF.right(), frame.bottom()));
0166             verts.push_back(QVector2D(frame.right(), frame.bottom()));
0167             verts.push_back(QVector2D(frame.right(), frame.top()));
0168             // bottom frame
0169             verts.push_back(QVector2D(frame.right(), areaF.bottom()));
0170             verts.push_back(QVector2D(frame.left(), areaF.bottom()));
0171             verts.push_back(QVector2D(frame.left(), frame.bottom()));
0172             verts.push_back(QVector2D(frame.left(), frame.bottom()));
0173             verts.push_back(QVector2D(frame.right(), frame.bottom()));
0174             verts.push_back(QVector2D(frame.right(), areaF.bottom()));
0175             vbo->setVertices(verts);
0176 
0177             ShaderBinder binder(ShaderTrait::UniformColor);
0178             binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, viewport.projectionMatrix());
0179             binder.shader()->setUniform(GLShader::ColorUniform::Color, QColor(0, 0, 0));
0180             vbo->render(GL_TRIANGLES);
0181         }
0182     }
0183 }
0184 
0185 void MagnifierEffect::postPaintScreen()
0186 {
0187     if (m_zoom != m_targetZoom) {
0188         QRect framedarea = magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH);
0189         effects->addRepaint(framedarea);
0190     }
0191     effects->postPaintScreen();
0192 }
0193 
0194 QRect MagnifierEffect::magnifierArea(QPointF pos) const
0195 {
0196     return QRect(pos.x() - m_magnifierSize.width() / 2, pos.y() - m_magnifierSize.height() / 2,
0197                  m_magnifierSize.width(), m_magnifierSize.height());
0198 }
0199 
0200 void MagnifierEffect::zoomIn()
0201 {
0202     m_targetZoom *= 1.2;
0203     if (!m_polling) {
0204         m_polling = true;
0205         effects->startMousePolling();
0206     }
0207     if (effects->isOpenGLCompositing() && !m_texture) {
0208         effects->makeOpenGLContextCurrent();
0209         m_texture = GLTexture::allocate(GL_RGBA16F, m_magnifierSize);
0210         if (!m_texture) {
0211             return;
0212         }
0213         m_texture->setContentTransform(OutputTransform());
0214         m_fbo = std::make_unique<GLFramebuffer>(m_texture.get());
0215     }
0216     effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH));
0217 }
0218 
0219 void MagnifierEffect::zoomOut()
0220 {
0221     m_targetZoom /= 1.2;
0222     if (m_targetZoom <= 1) {
0223         m_targetZoom = 1;
0224         if (m_polling) {
0225             m_polling = false;
0226             effects->stopMousePolling();
0227         }
0228         if (m_zoom == m_targetZoom) {
0229             effects->makeOpenGLContextCurrent();
0230             m_fbo.reset();
0231             m_texture.reset();
0232         }
0233     }
0234     effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH));
0235 }
0236 
0237 void MagnifierEffect::toggle()
0238 {
0239     if (m_zoom == 1.0) {
0240         if (m_targetZoom == 1.0) {
0241             m_targetZoom = 2;
0242         }
0243         if (!m_polling) {
0244             m_polling = true;
0245             effects->startMousePolling();
0246         }
0247         if (effects->isOpenGLCompositing() && !m_texture) {
0248             effects->makeOpenGLContextCurrent();
0249             m_texture = GLTexture::allocate(GL_RGBA16F, m_magnifierSize);
0250             if (!m_texture) {
0251                 return;
0252             }
0253             m_texture->setContentTransform(OutputTransform());
0254             m_fbo = std::make_unique<GLFramebuffer>(m_texture.get());
0255         }
0256     } else {
0257         m_targetZoom = 1;
0258         if (m_polling) {
0259             m_polling = false;
0260             effects->stopMousePolling();
0261         }
0262     }
0263     effects->addRepaint(magnifierArea().adjusted(-FRAME_WIDTH, -FRAME_WIDTH, FRAME_WIDTH, FRAME_WIDTH));
0264 }
0265 
0266 void MagnifierEffect::slotMouseChanged(const QPointF &pos, const QPointF &old,
0267                                        Qt::MouseButtons, Qt::MouseButtons, Qt::KeyboardModifiers, Qt::KeyboardModifiers)
0268 {
0269     if (pos != old && m_zoom != 1) {
0270         // need full repaint as we might lose some change events on fast mouse movements
0271         // see Bug 187658
0272         effects->addRepaintFull();
0273     }
0274 }
0275 
0276 void MagnifierEffect::slotWindowAdded(EffectWindow *w)
0277 {
0278     connect(w, &EffectWindow::windowDamaged, this, &MagnifierEffect::slotWindowDamaged);
0279 }
0280 
0281 void MagnifierEffect::slotWindowDamaged()
0282 {
0283     if (isActive()) {
0284         effects->addRepaint(magnifierArea());
0285     }
0286 }
0287 
0288 bool MagnifierEffect::isActive() const
0289 {
0290     return m_zoom != 1.0 || m_zoom != m_targetZoom;
0291 }
0292 
0293 QSize MagnifierEffect::magnifierSize() const
0294 {
0295     return m_magnifierSize;
0296 }
0297 
0298 qreal MagnifierEffect::targetZoom() const
0299 {
0300     return m_targetZoom;
0301 }
0302 
0303 } // namespace
0304 
0305 #include "moc_magnifier.cpp"