/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "screenedgeeffect.h" // KWin #include #include #include // KDE #include // Qt #include #include #include // xcb #ifdef KWIN_HAVE_XRENDER_COMPOSITING #include #endif namespace KWin { ScreenEdgeEffect::ScreenEdgeEffect() : Effect() , m_cleanupTimer(new QTimer(this)) { connect(effects, &EffectsHandler::screenEdgeApproaching, this, &ScreenEdgeEffect::edgeApproaching); m_cleanupTimer->setInterval(5000); m_cleanupTimer->setSingleShot(true); connect(m_cleanupTimer, &QTimer::timeout, this, &ScreenEdgeEffect::cleanup); connect(effects, &EffectsHandler::screenLockingChanged, this, [this] (bool locked) { if (locked) { cleanup(); } } ); } ScreenEdgeEffect::~ScreenEdgeEffect() { cleanup(); } void ScreenEdgeEffect::ensureGlowSvg() { if (!m_glow) { m_glow = new Plasma::Svg(this); m_glow->setImagePath(QStringLiteral("widgets/glowbar")); } } void ScreenEdgeEffect::cleanup() { for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { effects->addRepaint((*it)->geometry); } qDeleteAll(m_borders); m_borders.clear(); } void ScreenEdgeEffect::prePaintScreen(ScreenPrePaintData &data, int time) { effects->prePaintScreen(data, time); for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { if ((*it)->strength == 0.0) { continue; } data.paint += (*it)->geometry; } } void ScreenEdgeEffect::paintScreen(int mask, const QRegion ®ion, ScreenPaintData &data) { effects->paintScreen(mask, region, data); for (QHash::iterator it = m_borders.begin(); it != m_borders.end(); ++it) { const qreal opacity = (*it)->strength; if (opacity == 0.0) { continue; } if (effects->isOpenGLCompositing()) { GLTexture *texture = (*it)->texture.data(); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); texture->bind(); ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate); const QVector4D constant(opacity, opacity, opacity, opacity); binder.shader()->setUniform(GLShader::ModulationConstant, constant); QMatrix4x4 mvp = data.projectionMatrix(); mvp.translate((*it)->geometry.x(), (*it)->geometry.y()); binder.shader()->setUniform(GLShader::ModelViewProjectionMatrix, mvp); texture->render(infiniteRegion(), (*it)->geometry); texture->unbind(); glDisable(GL_BLEND); } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING const QRect &rect = (*it)->geometry; const QSize &size = (*it)->pictureSize; int x = rect.x(); int y = rect.y(); int width = rect.width(); int height = rect.height(); switch ((*it)->border) { case ElectricTopRight: x = rect.x() + rect.width() - size.width(); break; case ElectricBottomRight: x = rect.x() + rect.width() - size.width(); y = rect.y() + rect.height() - size.height(); break; case ElectricBottomLeft: y = rect.y() + rect.height() - size.height(); break; default: // nothing break; } xcb_render_composite(xcbConnection(), XCB_RENDER_PICT_OP_OVER, *(*it)->picture.data(), xRenderBlendPicture(opacity), effects->xrenderBufferPicture(), 0, 0, 0, 0, x, y, width, height); #endif } else if (effects->compositingType() == QPainterCompositing) { QImage tmp((*it)->image->size(), QImage::Format_ARGB32_Premultiplied); tmp.fill(Qt::transparent); QPainter p(&tmp); p.drawImage(0, 0, *(*it)->image.data()); QColor color(Qt::transparent); color.setAlphaF(opacity); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(QRect(QPoint(0, 0), tmp.size()), color); p.end(); QPainter *painter = effects->scenePainter(); const QRect &rect = (*it)->geometry; const QSize &size = (*it)->pictureSize; int x = rect.x(); int y = rect.y(); switch ((*it)->border) { case ElectricTopRight: x = rect.x() + rect.width() - size.width(); break; case ElectricBottomRight: x = rect.x() + rect.width() - size.width(); y = rect.y() + rect.height() - size.height(); break; case ElectricBottomLeft: y = rect.y() + rect.height() - size.height(); break; default: // nothing break; } painter->drawImage(QPoint(x, y), tmp); } } } void ScreenEdgeEffect::edgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry) { QHash::iterator it = m_borders.find(border); if (it != m_borders.end()) { // need to update effects->addRepaint((*it)->geometry); (*it)->strength = factor; if ((*it)->geometry != geometry) { (*it)->geometry = geometry; effects->addRepaint((*it)->geometry); if (border == ElectricLeft || border == ElectricRight || border == ElectricTop || border == ElectricBottom) { if (effects->isOpenGLCompositing()) { (*it)->texture.reset(createEdgeGlow(border, geometry.size())); } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING (*it)->picture.reset(createEdgeGlow(border, geometry.size())); #endif } else if (effects->compositingType() == QPainterCompositing) { (*it)->image.reset(createEdgeGlow(border, geometry.size())); } } } if (factor == 0.0) { m_cleanupTimer->start(); } else { m_cleanupTimer->stop(); } } else if (factor != 0.0) { // need to generate new Glow Glow *glow = createGlow(border, factor, geometry); if (glow) { m_borders.insert(border, glow); effects->addRepaint(glow->geometry); } } } Glow *ScreenEdgeEffect::createGlow(ElectricBorder border, qreal factor, const QRect &geometry) { Glow *glow = new Glow(); glow->border = border; glow->strength = factor; glow->geometry = geometry; // render the glow image if (effects->isOpenGLCompositing()) { effects->makeOpenGLContextCurrent(); if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->texture.reset(createCornerGlow(border)); } else { glow->texture.reset(createEdgeGlow(border, geometry.size())); } if (!glow->texture.isNull()) { glow->texture->setWrapMode(GL_CLAMP_TO_EDGE); } if (glow->texture.isNull()) { delete glow; return nullptr; } } else if (effects->compositingType() == XRenderCompositing) { #ifdef KWIN_HAVE_XRENDER_COMPOSITING if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->pictureSize = cornerGlowSize(border); glow->picture.reset(createCornerGlow(border)); } else { glow->pictureSize = geometry.size(); glow->picture.reset(createEdgeGlow(border, geometry.size())); } if (glow->picture.isNull()) { delete glow; return nullptr; } #endif } else if (effects->compositingType() == QPainterCompositing) { if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) { glow->image.reset(createCornerGlow(border)); glow->pictureSize = cornerGlowSize(border); } else { glow->image.reset(createEdgeGlow(border, geometry.size())); glow->pictureSize = geometry.size(); } if (glow->image.isNull()) { delete glow; return nullptr; } } return glow; } template T *ScreenEdgeEffect::createCornerGlow(ElectricBorder border) { ensureGlowSvg(); switch (border) { case ElectricTopLeft: return new T(m_glow->pixmap(QStringLiteral("bottomright")).toImage()); case ElectricTopRight: return new T(m_glow->pixmap(QStringLiteral("bottomleft")).toImage()); case ElectricBottomRight: return new T(m_glow->pixmap(QStringLiteral("topleft")).toImage()); case ElectricBottomLeft: return new T(m_glow->pixmap(QStringLiteral("topright")).toImage()); default: return nullptr; } } QSize ScreenEdgeEffect::cornerGlowSize(ElectricBorder border) { ensureGlowSvg(); switch (border) { case ElectricTopLeft: return m_glow->elementSize(QStringLiteral("bottomright")); case ElectricTopRight: return m_glow->elementSize(QStringLiteral("bottomleft")); case ElectricBottomRight: return m_glow->elementSize(QStringLiteral("topleft")); case ElectricBottomLeft: return m_glow->elementSize(QStringLiteral("topright")); default: return QSize(); } } template T *ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size) { ensureGlowSvg(); const bool stretchBorder = m_glow->hasElement(QStringLiteral("hint-stretch-borders")); QPoint pixmapPosition(0, 0); QPixmap l, r, c; switch (border) { case ElectricTop: l = m_glow->pixmap(QStringLiteral("bottomleft")); r = m_glow->pixmap(QStringLiteral("bottomright")); c = m_glow->pixmap(QStringLiteral("bottom")); break; case ElectricBottom: l = m_glow->pixmap(QStringLiteral("topleft")); r = m_glow->pixmap(QStringLiteral("topright")); c = m_glow->pixmap(QStringLiteral("top")); pixmapPosition = QPoint(0, size.height() - c.height()); break; case ElectricLeft: l = m_glow->pixmap(QStringLiteral("topright")); r = m_glow->pixmap(QStringLiteral("bottomright")); c = m_glow->pixmap(QStringLiteral("right")); break; case ElectricRight: l = m_glow->pixmap(QStringLiteral("topleft")); r = m_glow->pixmap(QStringLiteral("bottomleft")); c = m_glow->pixmap(QStringLiteral("left")); pixmapPosition = QPoint(size.width() - c.width(), 0); break; default: return nullptr; } QPixmap image(size); image.fill(Qt::transparent); QPainter p; p.begin(&image); if (border == ElectricBottom || border == ElectricTop) { p.drawPixmap(pixmapPosition, l); const QRect cRect(l.width(), pixmapPosition.y(), size.width() - l.width() - r.width(), c.height()); if (stretchBorder) { p.drawPixmap(cRect, c); } else { p.drawTiledPixmap(cRect, c); } p.drawPixmap(QPoint(size.width() - r.width(), pixmapPosition.y()), r); } else { p.drawPixmap(pixmapPosition, l); const QRect cRect(pixmapPosition.x(), l.height(), c.width(), size.height() - l.height() - r.height()); if (stretchBorder) { p.drawPixmap(cRect, c); } else { p.drawTiledPixmap(cRect, c); } p.drawPixmap(QPoint(pixmapPosition.x(), size.height() - r.height()), r); } p.end(); return new T(image.toImage()); } bool ScreenEdgeEffect::isActive() const { return !m_borders.isEmpty() && !effects->isScreenLocked(); } } // namespace