fix(kwin): stabilize rounded windows on Wayland

main
Reion Wong 3 weeks ago
parent 8892335034
commit 48a824939b

@ -18,7 +18,6 @@ add_subdirectory(plugins)
install(FILES config/kglobalshortcutsrc DESTINATION "${CUTEFISH_KWIN_CONFIG_DIR}")
install(FILES config/kwinrc DESTINATION "${CUTEFISH_KWIN_CONFIG_DIR}")
install(FILES config/kwinrulesrc DESTINATION "${CUTEFISH_KWIN_CONFIG_DIR}")
install(DIRECTORY scripts/cutefishlauncher DESTINATION "${CUTEFISH_KWIN_DATA_DIR}/scripts")
install(DIRECTORY scripts/cutefish_squash DESTINATION "${CUTEFISH_KWIN_DATA_DIR}/effects")

@ -1,15 +0,0 @@
[1]
Description=cutefish-dock
desktop=-1
desktoprule=2
wmclass=cutefish-dock cutefish-dock
wmclasscomplete=true
wmclassmatch=1
[2]
Description=cutefish-launcher
strictgeometry=false
strictgeometryrule=2
wmclass=cutefish-launcher cutefish-launcher
wmclasscomplete=true
wmclassmatch=1

@ -54,9 +54,8 @@ void Button::paint(QPainter *painter, const QRectF &repaintArea)
}
const QRectF buttonRect = geometry();
const qreal scale = decoration->devicePixelRatio();
const QRectF hoverRect = buttonRect.adjusted(2 * scale, 2 * scale, -2 * scale, -2 * scale);
const QRectF imageRect = QRectF(0, 0, 24 * scale, 24 * scale);
const QRectF hoverRect = buttonRect.adjusted(2, 2, -2, -2);
const QRectF imageRect = QRectF(0, 0, 24, 24);
painter->save();
painter->setRenderHint(QPainter::Antialiasing);

@ -70,6 +70,10 @@ bool Decoration::init()
updateButtonsGeometry();
});
connect(window, &KDecoration3::DecoratedWindow::heightChanged, this, [this] { updateGeometry(); });
connect(window, &KDecoration3::DecoratedWindow::nextScaleChanged, this, [this] {
updateButtonPixmaps();
update();
});
if (settings()) {
connect(settings().get(), &KDecoration3::DecorationSettings::fontChanged, this, [this] {
@ -92,7 +96,7 @@ bool Decoration::init()
});
}
// Follow the CutefishOS theme settings (dark mode, scaling) while the window is open.
// Follow the CutefishOS theme settings while the window is open.
if (!m_themeSettingsFile.isEmpty()) {
m_themeWatcher.addPath(m_themeSettingsFile);
connect(&m_themeWatcher, &QFileSystemWatcher::fileChanged, this, [this] {
@ -120,19 +124,9 @@ void Decoration::paint(QPainter *painter, const QRectF &repaintArea)
}
if (!window()->isShaded()) {
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->setPen(Qt::NoPen);
painter->setBrush(titleBarBackgroundColor());
const bool rounded = !window()->isMaximized()
&& (!settings() || settings()->isAlphaChannelSupported());
if (rounded) {
painter->drawRoundedRect(rect(), m_frameRadius, m_frameRadius);
} else {
painter->drawRect(rect());
}
painter->restore();
// The rounded-window effect owns the final frame shape. Paint an opaque,
// square decoration here so the same edge is not anti-aliased twice.
painter->fillRect(rect(), titleBarBackgroundColor());
if (m_leftButtons) {
m_leftButtons->paint(painter, repaintArea);
@ -215,16 +209,13 @@ void Decoration::updateShadow()
2 * shadowOverlap,
shadowOffset + 2 * shadowOverlap);
// Contrast pixel around the window.
painter.setPen(gradientStopColor(shadowColor, shadowStrength * 0.5));
painter.setBrush(Qt::NoBrush);
painter.drawRoundedRect(innerRect, -0.5 + m_frameRadius, -0.5 + m_frameRadius);
// Mask out the area covered by the window itself.
// Mask out the area covered by the window itself. Keep the cutout on the
// integer pixel grid; the old +/-0.5 radii produced a one-pixel contrast
// fringe around the whole window once KWin applied its own rounded mask.
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.drawRoundedRect(innerRect, 0.5 + m_frameRadius, 0.5 + m_frameRadius);
painter.drawRoundedRect(innerRect, m_frameRadius, m_frameRadius);
painter.end();
s_shadow = std::make_shared<KDecoration3::DecorationShadow>();
@ -243,12 +234,7 @@ void Decoration::reloadTheme()
{
m_themeSettings.sync();
m_devicePixelRatio = m_themeSettings.value(QStringLiteral("PixelRatio"), 1.0).toReal();
if (m_devicePixelRatio <= 0) {
m_devicePixelRatio = 1.0;
}
m_darkMode = m_themeSettings.value(QStringLiteral("DarkMode"), false).toBool();
m_frameRadius = qRound(11 * m_devicePixelRatio);
updateButtonPixmaps();
updateShadow();
@ -269,11 +255,24 @@ QPixmap Decoration::loadPixmap(const QString &path) const
return {};
}
// The icons are SVGs, render them at the size the buttons are painted with.
reader.setScaledSize(QSize(24, 24) * m_devicePixelRatio);
// Decoration geometry is expressed in logical coordinates on Wayland.
// Rasterize SVGs at the output scale for sharp icons, but keep their
// logical paint size at 24x24.
const qreal scale = outputScale();
reader.setScaledSize(QSize(qRound(24 * scale), qRound(24 * scale)));
return QPixmap::fromImage(reader.read());
}
qreal Decoration::outputScale() const
{
if (!window()) {
return 1.0;
}
const qreal scale = window()->nextScale();
return scale > 0 ? scale : 1.0;
}
QPixmap Decoration::buttonPixmap(KDecoration3::DecorationButtonType type, bool checked) const
{
switch (type) {
@ -310,7 +309,7 @@ QString Decoration::pixmapPath(KDecoration3::DecorationButtonType type, bool che
int Decoration::titleBarHeight() const
{
return qMax(1, qRound(m_titleBarHeight * m_devicePixelRatio));
return m_titleBarHeight;
}
QColor Decoration::titleBarBackgroundColor() const

@ -22,7 +22,7 @@ public:
void paint(QPainter *painter, const QRectF &repaintArea) override;
bool darkMode() const { return m_darkMode; }
qreal devicePixelRatio() const { return m_devicePixelRatio; }
qreal outputScale() const;
QPixmap buttonPixmap(KDecoration3::DecorationButtonType type, bool checked = false) const;
private:
@ -42,7 +42,6 @@ private:
KDecoration3::DecorationButtonGroup *m_leftButtons = nullptr;
KDecoration3::DecorationButtonGroup *m_rightButtons = nullptr;
qreal m_devicePixelRatio = 1.0;
bool m_darkMode = false;
int m_titleBarHeight = 30;
int m_frameRadius = 11;

@ -14,6 +14,5 @@
"License": "GPL",
"Name": "CutefishOS Rounded Corners",
"Version": "1"
},
"X-KDE-Ordering": 5
}
}

@ -1,12 +1,14 @@
#include "roundedwindow.h"
#include <core/output.h>
#include <core/pixelgrid.h>
#include <core/renderviewport.h>
#include <effect/effecthandler.h>
#include <opengl/glshader.h>
#include <opengl/glshadermanager.h>
#include <opengl/openglcontext.h>
#include <utils/version.h>
#include <QSettings>
#include <QVector2D>
#include <QVector4D>
@ -64,7 +66,7 @@ static QByteArray fragmentShaderSource()
"uniform vec4 modulation;\n"
"uniform float saturation;\n"
"\n"
// Size of the offscreen texture, in logical pixels.
// Size of the offscreen texture, in device pixels.
"uniform vec2 textureSize;\n"
// Frame of the window inside the texture (x, y, width, height).
"uniform vec4 frameRect;\n"
@ -77,34 +79,126 @@ static QByteArray fragmentShaderSource()
}
source += "\n"
// Signed distance to a rounded rectangle centred on the origin.
"float roundedBoxDistance(vec2 point, vec2 halfSize, float r)\n"
// Convert a point expressed relative to the window frame back to
// the offscreen texture coordinate system. KWin's normalized
// GLTexture matrix flips Y.
"vec2 framePixelToTex(vec2 framePixel)\n"
"{\n"
" vec2 q = abs(point) - halfSize + vec2(r);\n"
" return length(max(q, vec2(0.0))) + min(max(q.x, q.y), 0.0) - r;\n"
" vec2 texturePixel = frameRect.xy + framePixel;\n"
" return vec2(texturePixel.x / textureSize.x,\n"
" 1.0 - texturePixel.y / textureSize.y);\n"
"}\n"
"\n";
source += "vec4 sampleFramePixel(vec2 framePixel)\n"
"{\n"
" return " + textureLookup + "(sampler, framePixelToTex(framePixel));\n"
"}\n"
"\n"
// The decoration shadow is already part of KWin's offscreen
// texture. Once the opaque frame has been rendered, however, the
// shadow underneath the frame is no longer recoverable by merely
// lowering the frame alpha. Reconstruct each clipped corner from
// the two adjacent native-shadow edges.
"vec4 nativeCornerShadow(vec2 point, bool right, bool bottom)\n"
"{\n"
" const float margin = 3.0;\n"
" vec2 size = frameRect.zw;\n"
" float leftMargin = frameRect.x;\n"
" float topMargin = frameRect.y;\n"
" float rightMargin = textureSize.x - frameRect.x - size.x;\n"
" float bottomMargin = textureSize.y - frameRect.y - size.y;\n"
" bool hasVerticalShadow = right ? rightMargin >= margin : leftMargin >= margin;\n"
" bool hasHorizontalShadow = bottom ? bottomMargin >= margin : topMargin >= margin;\n"
" if (!hasVerticalShadow || !hasHorizontalShadow) {\n"
// CSD windows can have no expanded shadow texture. Transparent
// black keeps the output valid for premultiplied-alpha blending.
" return vec4(0.0);\n"
" }\n"
"\n"
" vec2 a;\n"
" vec2 b;\n"
" if (!right && !bottom) {\n"
" a = vec2(-margin, point.y + point.x + margin);\n"
" b = vec2(point.x + point.y + margin, -margin);\n"
" } else if (right && !bottom) {\n"
" a = vec2(size.x + margin, point.y + (size.x - point.x) + margin);\n"
" b = vec2(point.x - point.y - margin, -margin);\n"
" } else if (!right && bottom) {\n"
" a = vec2(-margin, point.y - point.x - margin);\n"
" b = vec2(point.x + (size.y - point.y) + margin, size.y + margin);\n"
" } else {\n"
" a = vec2(size.x + margin, point.y - (size.x - point.x) - margin);\n"
" b = vec2(point.x - (size.y - point.y) - margin, size.y + margin);\n"
" }\n"
"\n"
" vec4 aColor = sampleFramePixel(a);\n"
" vec4 bColor = sampleFramePixel(b);\n"
" float segment = max(distance(a, b), 0.001);\n"
" return mix(aColor, bColor, clamp(distance(a, point) / segment, 0.0, 1.0));\n"
"}\n"
"\n"
"float cornerCoverage(vec2 point, vec2 center, float r)\n"
"{\n"
// Pixel centres are half a pixel away from the mathematical edge.
// This half-pixel coverage convention matches KWin rounded-corner
// effects and avoids a dark or bright one-pixel halo.
" return clamp(r - distance(point, center) + 0.5, 0.0, 1.0);\n"
"}\n"
"\n"
"void main(void)\n"
"{\n"
" vec4 texel = " + textureLookup + "(sampler, texcoord0);\n"
" texel *= modulation;\n"
" texel.rgb = mix(vec3(dot(texel.rgb, vec3(0.2126, 0.7152, 0.0722))), texel.rgb, saturation);\n"
" vec4 result = texel;\n"
"\n"
// KWin's GLTexture normalized-coordinate matrix flips the Y axis.
" vec2 texturePoint = vec2(texcoord0.x, 1.0 - texcoord0.y) * textureSize;\n"
" vec2 local = texturePoint - frameRect.xy;\n"
" vec2 size = frameRect.zw;\n"
" float r = min(radius, 0.5 * min(size.x, size.y));\n"
"\n"
" vec2 point = texcoord0 * textureSize;\n"
" vec2 halfSize = frameRect.zw * 0.5;\n"
" vec2 centered = point - (frameRect.xy + halfSize);\n"
" if (r > 0.0 && local.x >= 0.0 && local.y >= 0.0\n"
" && local.x <= size.x && local.y <= size.y) {\n"
// Only touch the four radius-by-radius corner squares. Straight
// edges must remain byte-for-byte identical to the captured
// texture; applying a rounded-rectangle SDF to the whole frame can
// introduce a translucent fringe along those edges.
" bool corner = false;\n"
" bool right = false;\n"
" bool bottom = false;\n"
" vec2 center = vec2(r, r);\n"
"\n"
// Everything outside of the window frame - the decoration shadow -
// has to be left alone.
" vec2 outside = abs(centered) - halfSize;\n"
" if (max(outside.x, outside.y) > 0.0) {\n"
" " + output + " = texel;\n"
" return;\n"
" if (local.x < r && local.y < r) {\n"
" corner = true;\n"
" } else if (local.x > size.x - r && local.y < r) {\n"
" corner = true;\n"
" right = true;\n"
" center = vec2(size.x - r, r);\n"
" } else if (local.x < r && local.y > size.y - r) {\n"
" corner = true;\n"
" bottom = true;\n"
" center = vec2(r, size.y - r);\n"
" } else if (local.x > size.x - r && local.y > size.y - r) {\n"
" corner = true;\n"
" right = true;\n"
" bottom = true;\n"
" center = vec2(size.x - r, size.y - r);\n"
" }\n"
"\n"
" if (corner) {\n"
" float coverage = cornerCoverage(local, center, r);\n"
" if (coverage < 1.0) {\n"
" vec4 shadow = nativeCornerShadow(local, right, bottom);\n"
" result = mix(shadow, texel, coverage);\n"
" }\n"
" }\n"
" }\n"
"\n"
" float dist = roundedBoxDistance(centered, halfSize, radius);\n"
" float aa = max(length(fwidth(point)) * 0.5, 0.5);\n"
" " + output + " = texel * (1.0 - smoothstep(-aa, aa, dist));\n"
// Apply KWin's paint modulation after the corner reconstruction so
// both the frame and sampled native shadow fade together.
" result *= modulation;\n"
" result.rgb = mix(vec3(dot(result.rgb, vec3(0.2126, 0.7152, 0.0722))), result.rgb, saturation);\n"
" " + output + " = result;\n"
"}\n";
return source;
@ -113,15 +207,54 @@ static QByteArray fragmentShaderSource()
RoundedWindow::RoundedWindow()
: KWin::OffscreenEffect()
{
reconfigure(ReconfigureAll);
connect(KWin::effects, &KWin::EffectsHandler::windowAdded, this, &RoundedWindow::handleWindowAdded);
connect(KWin::effects, &KWin::EffectsHandler::windowDeleted, this, &RoundedWindow::handleWindowDeleted);
const auto windows = KWin::effects->stackingOrder();
for (KWin::EffectWindow *window : windows) {
handleWindowAdded(window);
auto watchWindow = [this](KWin::EffectWindow *window) {
connect(window, &KWin::EffectWindow::windowMaximizedStateAboutToChange, this,
[this](KWin::EffectWindow *w, bool horizontal, bool vertical) {
// Drop the current offscreen texture before either direction
// of the maximize transition. In particular, a restore must
// not reuse the full-screen texture captured while maximized.
unredirect(w);
if (horizontal && vertical) {
m_maximizingWindows.insert(w);
m_restoringWindows.remove(w);
} else if (isMaximized(w)) {
m_restoringWindows.insert(w);
m_maximizingWindows.remove(w);
} else {
m_maximizingWindows.remove(w);
m_restoringWindows.remove(w);
}
w->addRepaintFull();
});
};
connect(KWin::effects, &KWin::EffectsHandler::windowAdded, this, watchWindow);
for (KWin::EffectWindow *window : KWin::effects->stackingOrder()) {
watchWindow(window);
}
connect(KWin::effects, &KWin::EffectsHandler::windowClosed, this,
[this](KWin::EffectWindow *window) {
// A closed EffectWindow becomes "deleted" before the close
// animation has finished. Remember that it was rounded so the
// scale/fade animation keeps drawing the already-rounded FBO
// instead of switching to a square window for its last frames.
if (m_roundedWindows.contains(window)) {
m_closingWindows.insert(window);
}
m_maximizingWindows.remove(window);
m_restoringWindows.remove(window);
});
connect(KWin::effects, &KWin::EffectsHandler::windowDeleted, this,
[this](KWin::EffectWindow *window) {
m_closingWindows.remove(window);
m_maximizingWindows.remove(window);
m_restoringWindows.remove(window);
m_roundedWindows.remove(window);
});
}
RoundedWindow::~RoundedWindow() = default;
@ -136,87 +269,22 @@ bool RoundedWindow::enabledByDefault()
return supported();
}
void RoundedWindow::reconfigure(ReconfigureFlags flags)
{
Q_UNUSED(flags)
QSettings settings(QSettings::UserScope, QStringLiteral("cutefishos"), QStringLiteral("theme"));
qreal devicePixelRatio = settings.value(QStringLiteral("PixelRatio"), 1.0).toReal();
if (devicePixelRatio <= 0) {
devicePixelRatio = 1.0;
}
m_frameRadius = 11 * devicePixelRatio;
const auto windows = m_redirected;
for (KWin::EffectWindow *window : windows) {
window->addRepaintFull();
}
}
void RoundedWindow::handleWindowAdded(KWin::EffectWindow *window)
{
if (!window) {
return;
}
connect(window, &KWin::EffectWindow::windowMaximizedStateChanged, this,
[this, window] { updateWindow(window); });
connect(window, &KWin::EffectWindow::windowFullScreenChanged, this,
[this, window] { updateWindow(window); });
connect(window, &KWin::EffectWindow::windowFrameGeometryChanged, this,
[this, window] { updateWindow(window); });
connect(window, &KWin::EffectWindow::windowDecorationChanged, this,
[this, window] { updateWindow(window); });
updateWindow(window);
}
void RoundedWindow::handleWindowDeleted(KWin::EffectWindow *window)
{
m_redirected.remove(window);
}
void RoundedWindow::updateWindow(KWin::EffectWindow *window)
{
const bool wanted = shouldRound(window);
const bool redirected = m_redirected.contains(window);
if (wanted == redirected) {
return;
}
if (wanted) {
if (KWin::GLShader *s = shader()) {
redirect(window);
setShader(window, s);
m_redirected.insert(window);
}
} else {
unredirect(window);
m_redirected.remove(window);
}
window->addRepaintFull();
}
bool RoundedWindow::shouldRound(KWin::EffectWindow *window) const
{
if (!window || window->isDeleted() || !window->isOnCurrentDesktop()) {
if (!window || !window->isManaged()) {
return false;
}
if (s_allowList.contains(window->windowClass())) {
return true;
}
if (!window->isManaged() || window->isFullScreen() || isMaximized(window)) {
return false;
// Deleted windows can remain visible while KWin runs the close animation.
// Only keep rounding if this exact window was already rounded before close.
if (window->isDeleted()) {
return m_closingWindows.contains(window);
}
// Client side decorated windows - the CutefishOS applications themselves -
// paint their own rounded corners and shadow. Cutting into them would only
// produce artefacts, so this effect is for windows KWin decorates.
if (!window->hasDecoration()) {
// A fully maximized window is square, but as soon as a restore starts we
// need the rounded path available from the first transformed frame.
if (window->isFullScreen()
|| (isMaximized(window) && !m_restoringWindows.contains(window))) {
return false;
}
@ -227,13 +295,31 @@ bool RoundedWindow::shouldRound(KWin::EffectWindow *window) const
return false;
}
const bool allowListed = s_allowList.contains(window->windowClass());
if (allowListed) {
return true;
}
if (!window->hasDecoration()) {
return false;
}
return window->isNormalWindow() || window->isDialog() || window->isUtility();
}
bool RoundedWindow::isMaximized(KWin::EffectWindow *window) const
{
const QRectF frame = window->frameGeometry();
const QRectF maximizedArea = KWin::effects->clientArea(KWin::MaximizeArea, window);
return window->frameGeometry() == maximizedArea;
// Wayland geometry can differ from the maximize area by a fractional pixel
// while output scaling and decoration geometry settle. Exact QRectF equality
// makes the effect oscillate on/off at the end of maximize/restore.
constexpr qreal tolerance = 1.0;
return qAbs(frame.x() - maximizedArea.x()) <= tolerance
&& qAbs(frame.y() - maximizedArea.y()) <= tolerance
&& qAbs(frame.width() - maximizedArea.width()) <= tolerance
&& qAbs(frame.height() - maximizedArea.height()) <= tolerance;
}
KWin::GLShader *RoundedWindow::shader()
@ -255,11 +341,15 @@ void RoundedWindow::prePaintWindow(KWin::EffectWindow *window,
KWin::WindowPrePaintData &data,
std::chrono::milliseconds presentTime)
{
if (m_redirected.contains(window)) {
const bool round = shouldRound(window);
if (round) {
// The corners are cut out of the window, so it can no longer be treated
// as an opaque window - otherwise nothing would be blended with what is
// behind it and the cut out corners would stay black.
// as opaque. This also applies while open/close/restore animations
// transform the rounded offscreen texture.
data.setTranslucent();
if (!window->isDeleted()) {
m_roundedWindows.insert(window);
}
}
KWin::OffscreenEffect::prePaintWindow(window, data, presentTime);
@ -272,23 +362,79 @@ void RoundedWindow::drawWindow(const KWin::RenderTarget &renderTarget,
const QRegion &region,
KWin::WindowPaintData &data)
{
if (m_redirected.contains(window)) {
if (KWin::GLShader *s = shader()) {
const QRectF expanded = window->expandedGeometry();
const QRectF frame = window->frameGeometry();
// The offscreen texture covers the expanded geometry of the window,
// texcoord0 runs from (0, 0) to (1, 1) over it.
KWin::ShaderBinder binder(s);
s->setUniform("textureSize", QVector2D(expanded.width(), expanded.height()));
s->setUniform("frameRect", QVector4D(frame.x() - expanded.x(),
frame.y() - expanded.y(),
frame.width(),
frame.height()));
s->setUniform("radius", float(m_frameRadius));
const bool transformed = mask & KWin::Effect::PAINT_WINDOW_TRANSFORMED;
// Transformation itself is not a reason to drop rounded corners: opening,
// closing, minimizing and restoring all legitimately transform the window.
// The problematic case is specifically the transition *into* maximized
// state, where KWin cross-fades the previous buffer. Keep that direction on
// the normal path so our offscreen texture cannot be captured recursively.
if (transformed && m_maximizingWindows.contains(window)) {
unredirect(window);
KWin::OffscreenEffect::drawWindow(renderTarget, viewport, window, mask, region, data);
return;
}
// Clear transition bookkeeping only after the target geometry has settled.
// This avoids losing the state if KWin happens to paint one ordinary frame
// between the about-to-change signal and the scripted animation.
if (!transformed) {
if (m_maximizingWindows.contains(window) && isMaximized(window)) {
m_maximizingWindows.remove(window);
}
if (m_restoringWindows.contains(window) && !isMaximized(window)) {
m_restoringWindows.remove(window);
}
}
// Keep KWin's offscreen state tied to the current paint pass. Persisting our
// own redirect state across maximize/restore lets an old FBO survive a geometry
// transition and is especially fragile when the Maximize effect cross-fades a
// previous buffer. This is the same model used by maintained KWin rounded-corner
// effects: redirect only while the window currently needs the effect.
if (!shouldRound(window)) {
if (!window->isDeleted()) {
m_roundedWindows.remove(window);
}
unredirect(window);
KWin::OffscreenEffect::drawWindow(renderTarget, viewport, window, mask, region, data);
return;
}
if (!window->isDeleted()) {
m_roundedWindows.insert(window);
}
KWin::GLShader *s = shader();
if (!s) {
unredirect(window);
KWin::OffscreenEffect::drawWindow(renderTarget, viewport, window, mask, region, data);
return;
}
redirect(window);
setShader(window, s);
// OffscreenEffect allocates its texture in device pixels. Keep every shader
// geometry in that same coordinate space; using logical sizes here causes the
// mask and shadow boundary to drift at fractional scale.
const qreal scale = window->screen() ? window->screen()->scale() : viewport.scale();
const QRectF expanded = KWin::snapToPixels(window->expandedGeometry(), scale);
const QRectF frame = KWin::snapToPixels(window->frameGeometry(), scale);
const QSizeF textureSize(expanded.width() * scale, expanded.height() * scale);
const QPointF frameOffset((frame.x() - expanded.x()) * scale,
(frame.y() - expanded.y()) * scale);
const QSizeF frameSize(frame.width() * scale, frame.height() * scale);
KWin::ShaderBinder binder(s);
s->setUniform("textureSize", QVector2D(textureSize.width(), textureSize.height()));
s->setUniform("frameRect", QVector4D(frameOffset.x(),
frameOffset.y(),
frameSize.width(),
frameSize.height()));
s->setUniform("radius", float(m_frameRadius * scale));
KWin::OffscreenEffect::drawWindow(renderTarget, viewport, window, mask, region, data);
}

@ -30,7 +30,7 @@ public:
static bool supported();
static bool enabledByDefault();
void reconfigure(ReconfigureFlags flags) override;
int requestedEffectChainPosition() const override { return 99; }
void prePaintWindow(KWin::EffectWindow *window,
KWin::WindowPrePaintData &data,
@ -44,14 +44,14 @@ public:
KWin::WindowPaintData &data) override;
private:
void handleWindowAdded(KWin::EffectWindow *window);
void handleWindowDeleted(KWin::EffectWindow *window);
void updateWindow(KWin::EffectWindow *window);
bool shouldRound(KWin::EffectWindow *window) const;
bool isMaximized(KWin::EffectWindow *window) const;
KWin::GLShader *shader();
std::unique_ptr<KWin::GLShader> m_shader;
QSet<KWin::EffectWindow *> m_redirected;
QSet<KWin::EffectWindow *> m_roundedWindows;
QSet<KWin::EffectWindow *> m_closingWindows;
QSet<KWin::EffectWindow *> m_maximizingWindows;
QSet<KWin::EffectWindow *> m_restoringWindows;
qreal m_frameRadius = 11;
};

Loading…
Cancel
Save