KWin
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
screenedgeeffect.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9#include "screenedgeeffect.h"
10// KWin
11#include "core/rendertarget.h"
12#include "core/renderviewport.h"
14#include "opengl/gltexture.h"
15#include "opengl/glutils.h"
16// KDE
17#include <KConfigGroup>
18#include <KSharedConfig>
19#include <KSvg/Svg>
20// Qt
21#include <QFile>
22#include <QPainter>
23#include <QTimer>
24#include <QVector4D>
25
26namespace KWin
27{
28
30 : Effect()
31 , m_cleanupTimer(new QTimer(this))
32{
33 connect(effects, &EffectsHandler::screenEdgeApproaching, this, &ScreenEdgeEffect::edgeApproaching);
34 m_cleanupTimer->setInterval(5000);
35 m_cleanupTimer->setSingleShot(true);
36 connect(m_cleanupTimer, &QTimer::timeout, this, &ScreenEdgeEffect::cleanup);
37 connect(effects, &EffectsHandler::screenLockingChanged, this, [this](bool locked) {
38 if (locked) {
39 cleanup();
40 }
41 });
42}
43
48
49void ScreenEdgeEffect::ensureGlowSvg()
50{
51 if (!m_glow) {
52 m_glow = new KSvg::Svg(this);
53 m_glow->imageSet()->setBasePath(QStringLiteral("plasma/desktoptheme"));
54
55 const QString groupName = QStringLiteral("Theme");
56 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("plasmarc"));
57 KConfigGroup cg = KConfigGroup(config, groupName);
58 m_glow->imageSet()->setImageSetName(cg.readEntry("name", QStringLiteral("default")));
59
60 m_configWatcher = KConfigWatcher::create(config);
61
62 connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
63 if (group.name() != QStringLiteral("Theme") || !names.contains(QStringLiteral("name"))) {
64 return;
65 }
66 m_glow->imageSet()->setImageSetName(group.readEntry("name", QStringLiteral("default")));
67 });
68
69 m_glow->setImagePath(QStringLiteral("widgets/glowbar"));
70 }
71}
72
73void ScreenEdgeEffect::cleanup()
74{
75 for (auto &[border, glow] : m_borders) {
76 effects->addRepaint(glow->geometry);
77 }
78 m_borders.clear();
79}
80
81void ScreenEdgeEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
82{
83 effects->prePaintScreen(data, presentTime);
84 for (auto &[border, glow] : m_borders) {
85 if (glow->strength == 0.0) {
86 continue;
87 }
88 data.paint += glow->geometry;
89 }
90}
91
92void ScreenEdgeEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
93{
94 effects->paintScreen(renderTarget, viewport, mask, region, screen);
95 for (auto &[border, glow] : m_borders) {
96 const qreal opacity = glow->strength;
97 if (opacity == 0.0) {
98 continue;
99 }
101 GLTexture *texture = glow->texture.get();
102 if (!texture) {
103 return;
104 }
105 glEnable(GL_BLEND);
106 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
107 ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::TransformColorspace);
108 binder.shader()->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
109 const QVector4D constant(opacity, opacity, opacity, opacity);
110 binder.shader()->setUniform(GLShader::Vec4Uniform::ModulationConstant, constant);
111 const auto scale = viewport.scale();
112 QMatrix4x4 mvp = viewport.projectionMatrix();
113 mvp.translate(glow->geometry.x() * scale, glow->geometry.y() * scale);
114 binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mvp);
115 texture->render(glow->geometry.size() * scale);
116 glDisable(GL_BLEND);
118 QImage tmp(glow->image.size(), QImage::Format_ARGB32_Premultiplied);
119 tmp.fill(Qt::transparent);
120 QPainter p(&tmp);
121 p.drawImage(0, 0, glow->image);
122 QColor color(Qt::transparent);
123 color.setAlphaF(opacity);
124 p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
125 p.fillRect(QRect(QPoint(0, 0), tmp.size()), color);
126 p.end();
127
128 QPainter *painter = effects->scenePainter();
129 const QRect &rect = glow->geometry;
130 const QSize &size = glow->pictureSize;
131 int x = rect.x();
132 int y = rect.y();
133 switch (glow->border) {
134 case ElectricTopRight:
135 x = rect.x() + rect.width() - size.width();
136 break;
138 x = rect.x() + rect.width() - size.width();
139 y = rect.y() + rect.height() - size.height();
140 break;
142 y = rect.y() + rect.height() - size.height();
143 break;
144 default:
145 // nothing
146 break;
147 }
148 painter->drawImage(QPoint(x, y), tmp);
149 }
150 }
151}
152
153void ScreenEdgeEffect::edgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry)
154{
155 auto it = m_borders.find(border);
156 if (it != m_borders.end()) {
157 Glow *glow = it->second.get();
158 // need to update
160 glow->strength = factor;
161 if (glow->geometry != geometry) {
162 glow->geometry = geometry;
164 if (border == ElectricLeft || border == ElectricRight || border == ElectricTop || border == ElectricBottom) {
166 glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
167 } else if (effects->compositingType() == QPainterCompositing) {
168 glow->image = createEdgeGlow(border, geometry.size());
169 }
170 }
171 }
172 if (factor == 0.0) {
173 m_cleanupTimer->start();
174 } else {
175 m_cleanupTimer->stop();
176 }
177 } else if (factor != 0.0) {
178 // need to generate new Glow
179 std::unique_ptr<Glow> glow = createGlow(border, factor, geometry);
180 if (glow) {
181 effects->addRepaint(glow->geometry);
182 m_borders[border] = std::move(glow);
183 }
184 }
185}
186
187std::unique_ptr<Glow> ScreenEdgeEffect::createGlow(ElectricBorder border, qreal factor, const QRect &geometry)
188{
189 auto glow = std::make_unique<Glow>();
190 glow->border = border;
191 glow->strength = factor;
192 glow->geometry = geometry;
193
194 // render the glow image
197 if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
198 glow->texture = GLTexture::upload(createCornerGlow(border));
199 } else {
200 glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
201 }
202 if (!glow->texture) {
203 return nullptr;
204 }
205 glow->texture->setWrapMode(GL_CLAMP_TO_EDGE);
206 } else if (effects->compositingType() == QPainterCompositing) {
207 if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
208 glow->image = createCornerGlow(border);
209 glow->pictureSize = cornerGlowSize(border);
210 } else {
211 glow->image = createEdgeGlow(border, geometry.size());
212 glow->pictureSize = geometry.size();
213 }
214 if (glow->image.isNull()) {
215 return nullptr;
216 }
217 }
218
219 return glow;
220}
221
222QImage ScreenEdgeEffect::createCornerGlow(ElectricBorder border)
223{
224 ensureGlowSvg();
225
226 switch (border) {
227 case ElectricTopLeft:
228 return m_glow->pixmap(QStringLiteral("bottomright")).toImage();
229 case ElectricTopRight:
230 return m_glow->pixmap(QStringLiteral("bottomleft")).toImage();
232 return m_glow->pixmap(QStringLiteral("topleft")).toImage();
234 return m_glow->pixmap(QStringLiteral("topright")).toImage();
235 default:
236 return QImage{};
237 }
238}
239
240QSize ScreenEdgeEffect::cornerGlowSize(ElectricBorder border)
241{
242 ensureGlowSvg();
243
244 switch (border) {
245 case ElectricTopLeft:
246 return m_glow->elementSize(QStringLiteral("bottomright")).toSize();
247 case ElectricTopRight:
248 return m_glow->elementSize(QStringLiteral("bottomleft")).toSize();
250 return m_glow->elementSize(QStringLiteral("topleft")).toSize();
252 return m_glow->elementSize(QStringLiteral("topright")).toSize();
253 default:
254 return QSize();
255 }
256}
257
258QImage ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
259{
260 ensureGlowSvg();
261
262 const bool stretchBorder = m_glow->hasElement(QStringLiteral("hint-stretch-borders"));
263
264 QPoint pixmapPosition(0, 0);
265 QPixmap l, r, c;
266 switch (border) {
267 case ElectricTop:
268 l = m_glow->pixmap(QStringLiteral("bottomleft"));
269 r = m_glow->pixmap(QStringLiteral("bottomright"));
270 c = m_glow->pixmap(QStringLiteral("bottom"));
271 break;
272 case ElectricBottom:
273 l = m_glow->pixmap(QStringLiteral("topleft"));
274 r = m_glow->pixmap(QStringLiteral("topright"));
275 c = m_glow->pixmap(QStringLiteral("top"));
276 pixmapPosition = QPoint(0, size.height() - c.height());
277 break;
278 case ElectricLeft:
279 l = m_glow->pixmap(QStringLiteral("topright"));
280 r = m_glow->pixmap(QStringLiteral("bottomright"));
281 c = m_glow->pixmap(QStringLiteral("right"));
282 break;
283 case ElectricRight:
284 l = m_glow->pixmap(QStringLiteral("topleft"));
285 r = m_glow->pixmap(QStringLiteral("bottomleft"));
286 c = m_glow->pixmap(QStringLiteral("left"));
287 pixmapPosition = QPoint(size.width() - c.width(), 0);
288 break;
289 default:
290 return QImage{};
291 }
292 QPixmap image(size);
293 image.fill(Qt::transparent);
294 QPainter p;
295 p.begin(&image);
296 if (border == ElectricBottom || border == ElectricTop) {
297 p.drawPixmap(pixmapPosition, l);
298 const QRect cRect(l.width(), pixmapPosition.y(), size.width() - l.width() - r.width(), c.height());
299 if (stretchBorder) {
300 p.drawPixmap(cRect, c);
301 } else {
302 p.drawTiledPixmap(cRect, c);
303 }
304 p.drawPixmap(QPoint(size.width() - r.width(), pixmapPosition.y()), r);
305 } else {
306 p.drawPixmap(pixmapPosition, l);
307 const QRect cRect(pixmapPosition.x(), l.height(), c.width(), size.height() - l.height() - r.height());
308 if (stretchBorder) {
309 p.drawPixmap(cRect, c);
310 } else {
311 p.drawTiledPixmap(cRect, c);
312 }
313 p.drawPixmap(QPoint(pixmapPosition.x(), size.height() - r.height()), r);
314 }
315 p.end();
316 return image.toImage();
317}
318
319bool ScreenEdgeEffect::isActive() const
320{
321 return !m_borders.empty() && !effects->isScreenLocked();
322}
323
324} // namespace
325
326#include "moc_screenedgeeffect.cpp"
Base class for all KWin effects.
Definition effect.h:535
void screenLockingChanged(bool locked)
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
Q_SCRIPTABLE void addRepaint(const QRectF &r)
bool makeOpenGLContextCurrent()
Makes the OpenGL compositing context current.
CompositingType compositingType
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
void screenEdgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry)
QPainter * scenePainter()
Provides access to the QPainter which is rendering to the back buffer.
bool setColorspaceUniformsFromSRGB(const ColorDescription &dst)
Definition glshader.cpp:457
bool setUniform(const char *name, float value)
Definition glshader.cpp:301
GLuint texture() const
void render(const QSizeF &size)
std::unique_ptr< GLTexture > texture
const ColorDescription & colorDescription() const
QMatrix4x4 projectionMatrix() const
ElectricBorder
Definition globals.h:60
@ ElectricTopLeft
Definition globals.h:68
@ ElectricBottomLeft
Definition globals.h:66
@ ElectricBottom
Definition globals.h:65
@ ElectricTopRight
Definition globals.h:62
@ ElectricTop
Definition globals.h:61
@ ElectricRight
Definition globals.h:63
@ ElectricBottomRight
Definition globals.h:64
@ ElectricLeft
Definition globals.h:67
@ QPainterCompositing
Definition globals.h:39
EffectsHandler * effects