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

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "screenedgeeffect.h"
0010 // KWin
0011 #include "core/rendertarget.h"
0012 #include "core/renderviewport.h"
0013 #include "effect/effecthandler.h"
0014 #include "opengl/gltexture.h"
0015 #include "opengl/glutils.h"
0016 // KDE
0017 #include <KConfigGroup>
0018 #include <KSharedConfig>
0019 #include <KSvg/Svg>
0020 // Qt
0021 #include <QFile>
0022 #include <QPainter>
0023 #include <QTimer>
0024 #include <QVector4D>
0025 
0026 namespace KWin
0027 {
0028 
0029 ScreenEdgeEffect::ScreenEdgeEffect()
0030     : Effect()
0031     , m_cleanupTimer(new QTimer(this))
0032 {
0033     connect(effects, &EffectsHandler::screenEdgeApproaching, this, &ScreenEdgeEffect::edgeApproaching);
0034     m_cleanupTimer->setInterval(5000);
0035     m_cleanupTimer->setSingleShot(true);
0036     connect(m_cleanupTimer, &QTimer::timeout, this, &ScreenEdgeEffect::cleanup);
0037     connect(effects, &EffectsHandler::screenLockingChanged, this, [this](bool locked) {
0038         if (locked) {
0039             cleanup();
0040         }
0041     });
0042 }
0043 
0044 ScreenEdgeEffect::~ScreenEdgeEffect()
0045 {
0046     cleanup();
0047 }
0048 
0049 void ScreenEdgeEffect::ensureGlowSvg()
0050 {
0051     if (!m_glow) {
0052         m_glow = new KSvg::Svg(this);
0053         m_glow->imageSet()->setBasePath(QStringLiteral("plasma/desktoptheme"));
0054 
0055         const QString groupName = QStringLiteral("Theme");
0056         KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("plasmarc"));
0057         KConfigGroup cg = KConfigGroup(config, groupName);
0058         m_glow->imageSet()->setImageSetName(cg.readEntry("name", QStringLiteral("default")));
0059 
0060         m_configWatcher = KConfigWatcher::create(config);
0061 
0062         connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
0063             if (group.name() != QStringLiteral("Theme") || !names.contains(QStringLiteral("name"))) {
0064                 return;
0065             }
0066             m_glow->imageSet()->setImageSetName(group.readEntry("name", QStringLiteral("default")));
0067         });
0068 
0069         m_glow->setImagePath(QStringLiteral("widgets/glowbar"));
0070     }
0071 }
0072 
0073 void ScreenEdgeEffect::cleanup()
0074 {
0075     for (auto &[border, glow] : m_borders) {
0076         effects->addRepaint(glow->geometry);
0077     }
0078     m_borders.clear();
0079 }
0080 
0081 void ScreenEdgeEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0082 {
0083     effects->prePaintScreen(data, presentTime);
0084     for (auto &[border, glow] : m_borders) {
0085         if (glow->strength == 0.0) {
0086             continue;
0087         }
0088         data.paint += glow->geometry;
0089     }
0090 }
0091 
0092 void ScreenEdgeEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
0093 {
0094     effects->paintScreen(renderTarget, viewport, mask, region, screen);
0095     for (auto &[border, glow] : m_borders) {
0096         const qreal opacity = glow->strength;
0097         if (opacity == 0.0) {
0098             continue;
0099         }
0100         if (effects->isOpenGLCompositing()) {
0101             GLTexture *texture = glow->texture.get();
0102             if (!texture) {
0103                 return;
0104             }
0105             glEnable(GL_BLEND);
0106             glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
0107             ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::TransformColorspace);
0108             binder.shader()->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
0109             const QVector4D constant(opacity, opacity, opacity, opacity);
0110             binder.shader()->setUniform(GLShader::Vec4Uniform::ModulationConstant, constant);
0111             const auto scale = viewport.scale();
0112             QMatrix4x4 mvp = viewport.projectionMatrix();
0113             mvp.translate(glow->geometry.x() * scale, glow->geometry.y() * scale);
0114             binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
0115             texture->render(glow->geometry.size() * scale);
0116             glDisable(GL_BLEND);
0117         } else if (effects->compositingType() == QPainterCompositing) {
0118             QImage tmp(glow->image.size(), QImage::Format_ARGB32_Premultiplied);
0119             tmp.fill(Qt::transparent);
0120             QPainter p(&tmp);
0121             p.drawImage(0, 0, glow->image);
0122             QColor color(Qt::transparent);
0123             color.setAlphaF(opacity);
0124             p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0125             p.fillRect(QRect(QPoint(0, 0), tmp.size()), color);
0126             p.end();
0127 
0128             QPainter *painter = effects->scenePainter();
0129             const QRect &rect = glow->geometry;
0130             const QSize &size = glow->pictureSize;
0131             int x = rect.x();
0132             int y = rect.y();
0133             switch (glow->border) {
0134             case ElectricTopRight:
0135                 x = rect.x() + rect.width() - size.width();
0136                 break;
0137             case ElectricBottomRight:
0138                 x = rect.x() + rect.width() - size.width();
0139                 y = rect.y() + rect.height() - size.height();
0140                 break;
0141             case ElectricBottomLeft:
0142                 y = rect.y() + rect.height() - size.height();
0143                 break;
0144             default:
0145                 // nothing
0146                 break;
0147             }
0148             painter->drawImage(QPoint(x, y), tmp);
0149         }
0150     }
0151 }
0152 
0153 void ScreenEdgeEffect::edgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry)
0154 {
0155     auto it = m_borders.find(border);
0156     if (it != m_borders.end()) {
0157         Glow *glow = it->second.get();
0158         // need to update
0159         effects->addRepaint(glow->geometry);
0160         glow->strength = factor;
0161         if (glow->geometry != geometry) {
0162             glow->geometry = geometry;
0163             effects->addRepaint(glow->geometry);
0164             if (border == ElectricLeft || border == ElectricRight || border == ElectricTop || border == ElectricBottom) {
0165                 if (effects->isOpenGLCompositing()) {
0166                     glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
0167                 } else if (effects->compositingType() == QPainterCompositing) {
0168                     glow->image = createEdgeGlow(border, geometry.size());
0169                 }
0170             }
0171         }
0172         if (factor == 0.0) {
0173             m_cleanupTimer->start();
0174         } else {
0175             m_cleanupTimer->stop();
0176         }
0177     } else if (factor != 0.0) {
0178         // need to generate new Glow
0179         std::unique_ptr<Glow> glow = createGlow(border, factor, geometry);
0180         if (glow) {
0181             effects->addRepaint(glow->geometry);
0182             m_borders[border] = std::move(glow);
0183         }
0184     }
0185 }
0186 
0187 std::unique_ptr<Glow> ScreenEdgeEffect::createGlow(ElectricBorder border, qreal factor, const QRect &geometry)
0188 {
0189     auto glow = std::make_unique<Glow>();
0190     glow->border = border;
0191     glow->strength = factor;
0192     glow->geometry = geometry;
0193 
0194     // render the glow image
0195     if (effects->isOpenGLCompositing()) {
0196         effects->makeOpenGLContextCurrent();
0197         if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
0198             glow->texture = GLTexture::upload(createCornerGlow(border));
0199         } else {
0200             glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
0201         }
0202         if (!glow->texture) {
0203             return nullptr;
0204         }
0205         glow->texture->setWrapMode(GL_CLAMP_TO_EDGE);
0206     } else if (effects->compositingType() == QPainterCompositing) {
0207         if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
0208             glow->image = createCornerGlow(border);
0209             glow->pictureSize = cornerGlowSize(border);
0210         } else {
0211             glow->image = createEdgeGlow(border, geometry.size());
0212             glow->pictureSize = geometry.size();
0213         }
0214         if (glow->image.isNull()) {
0215             return nullptr;
0216         }
0217     }
0218 
0219     return glow;
0220 }
0221 
0222 QImage ScreenEdgeEffect::createCornerGlow(ElectricBorder border)
0223 {
0224     ensureGlowSvg();
0225 
0226     switch (border) {
0227     case ElectricTopLeft:
0228         return m_glow->pixmap(QStringLiteral("bottomright")).toImage();
0229     case ElectricTopRight:
0230         return m_glow->pixmap(QStringLiteral("bottomleft")).toImage();
0231     case ElectricBottomRight:
0232         return m_glow->pixmap(QStringLiteral("topleft")).toImage();
0233     case ElectricBottomLeft:
0234         return m_glow->pixmap(QStringLiteral("topright")).toImage();
0235     default:
0236         return QImage{};
0237     }
0238 }
0239 
0240 QSize ScreenEdgeEffect::cornerGlowSize(ElectricBorder border)
0241 {
0242     ensureGlowSvg();
0243 
0244     switch (border) {
0245     case ElectricTopLeft:
0246         return m_glow->elementSize(QStringLiteral("bottomright")).toSize();
0247     case ElectricTopRight:
0248         return m_glow->elementSize(QStringLiteral("bottomleft")).toSize();
0249     case ElectricBottomRight:
0250         return m_glow->elementSize(QStringLiteral("topleft")).toSize();
0251     case ElectricBottomLeft:
0252         return m_glow->elementSize(QStringLiteral("topright")).toSize();
0253     default:
0254         return QSize();
0255     }
0256 }
0257 
0258 QImage ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
0259 {
0260     ensureGlowSvg();
0261 
0262     const bool stretchBorder = m_glow->hasElement(QStringLiteral("hint-stretch-borders"));
0263 
0264     QPoint pixmapPosition(0, 0);
0265     QPixmap l, r, c;
0266     switch (border) {
0267     case ElectricTop:
0268         l = m_glow->pixmap(QStringLiteral("bottomleft"));
0269         r = m_glow->pixmap(QStringLiteral("bottomright"));
0270         c = m_glow->pixmap(QStringLiteral("bottom"));
0271         break;
0272     case ElectricBottom:
0273         l = m_glow->pixmap(QStringLiteral("topleft"));
0274         r = m_glow->pixmap(QStringLiteral("topright"));
0275         c = m_glow->pixmap(QStringLiteral("top"));
0276         pixmapPosition = QPoint(0, size.height() - c.height());
0277         break;
0278     case ElectricLeft:
0279         l = m_glow->pixmap(QStringLiteral("topright"));
0280         r = m_glow->pixmap(QStringLiteral("bottomright"));
0281         c = m_glow->pixmap(QStringLiteral("right"));
0282         break;
0283     case ElectricRight:
0284         l = m_glow->pixmap(QStringLiteral("topleft"));
0285         r = m_glow->pixmap(QStringLiteral("bottomleft"));
0286         c = m_glow->pixmap(QStringLiteral("left"));
0287         pixmapPosition = QPoint(size.width() - c.width(), 0);
0288         break;
0289     default:
0290         return QImage{};
0291     }
0292     QPixmap image(size);
0293     image.fill(Qt::transparent);
0294     QPainter p;
0295     p.begin(&image);
0296     if (border == ElectricBottom || border == ElectricTop) {
0297         p.drawPixmap(pixmapPosition, l);
0298         const QRect cRect(l.width(), pixmapPosition.y(), size.width() - l.width() - r.width(), c.height());
0299         if (stretchBorder) {
0300             p.drawPixmap(cRect, c);
0301         } else {
0302             p.drawTiledPixmap(cRect, c);
0303         }
0304         p.drawPixmap(QPoint(size.width() - r.width(), pixmapPosition.y()), r);
0305     } else {
0306         p.drawPixmap(pixmapPosition, l);
0307         const QRect cRect(pixmapPosition.x(), l.height(), c.width(), size.height() - l.height() - r.height());
0308         if (stretchBorder) {
0309             p.drawPixmap(cRect, c);
0310         } else {
0311             p.drawTiledPixmap(cRect, c);
0312         }
0313         p.drawPixmap(QPoint(pixmapPosition.x(), size.height() - r.height()), r);
0314     }
0315     p.end();
0316     return image.toImage();
0317 }
0318 
0319 bool ScreenEdgeEffect::isActive() const
0320 {
0321     return !m_borders.empty() && !effects->isScreenLocked();
0322 }
0323 
0324 } // namespace
0325 
0326 #include "moc_screenedgeeffect.cpp"