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