diff --git a/README.md b/README.md index bad8506d67..136ce4fd0a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Better Blur (formerly kwin-effects-forceblur) is a fork the KWin Blur effect for - X11 and Wayland support - Force blur - Rounded corners with optional anti-aliasing -- Optional blur texture caching for much lower GPU usage, works best with tiling +- Static blur for much lower GPU usage, works best with tiling ### Bug fixes Fixes for blur-related Plasma bugs that haven't been patched yet. diff --git a/docs/configuration.md b/docs/configuration.md index 98126a8eeb..2362ef5ae1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,42 +1,131 @@ -# General -### Window opacity affects blur -Since Plasma 6, window opacity now affects blur opacity with no option to disable it in the stock blur effect. +# Simple +A simple configuration system with a UI. For more complex configuration (for example blur menus but not firefox's menus), use window rules. -Enabled (default): -![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/525a3611-62f0-4c7e-b01c-253a05cbd3ca) +Obvious options are not explained here. -Disabled: -![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/b4f35a24-e288-4c51-9707-494942abdaa0) +## General -# Force blur -### Blur window decorations -Whether to blur window decorations, including borders. Enable this if your window decoration doesn't support blur, or you want rounded top corners. +| Option | Description | +|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Noise strength** | Noise is used to mask color banding. | +| **Window opacity affects blur** | Whether the blur opacity should be affected by the window's opacity. Window fading animations will still affect the blur opacity.

Enabled:

Disabled:
| -This option will override the blur region specified by the decoration. +## Force blur +| Option | Description | +|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Window classes** | A list of classes of windows to force blur, separated by new line.

Regular expressions are supported and can be specified by using the ``$regex:`` prefix. Example: ``$regex:org\.kde\..*`` | +| **Blur window decorations as well** | Whether to blur the window decorations (titlebar and borders) in addition to the content. This will override the decoration's blur region, possibly breaking rounded corners, in which case you need to manually specify the corner radius in the *Rounded corners* section. | +| **Treat windows as transparent** | Whether force-blurred windows should be considered as transparent in order to fix transparency for applications that don't mark their windows as transparent. See #38 for an example. | -### Paint windows as non-opaque -Fixes transparency for some applications by marking their windows as transparent. This will only be done for force-blurred windows. +## Rounded corners +| Option | Description | +|------------------------------|----------------------------------------------------------------------------------------| +| **Window top corner radius** | Requires *Blur window decorations as well* to be enabled for windows with decorations. | +| **Anti-aliasing** | Makes rounded corners appear smoother. | -# Fake blur -When enabled, the blur texture will be cached and reused. The blurred areas of the window will be marked as opaque, resulting in KWin not painting anything behind them. -Only one image per screen is cached at a time. +## Static blur +When enabled, a cached texture will be painted behind the window instead of actually blurring the background (dynamic blur). +The blurred areas of the window will also be marked as opaque, causing KWin to not paint anything behind the window. +Both things result in lower resource usage. -Fake blur is mainly intended for laptop users who want longer battery life while still having blur everywhere. +Static blur is mainly intended for laptop users who want longer battery life while still having blur everywhere. -### Use real blur for windows that are in front of other windows -By default, when two windows overlap, you won't be able to see the window behind. -![image](https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/e581b5c1-7b2c-41c4-b180-4da5306747e1) +| Option | Description | +|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Use dynamic blur for windows with another window behind** | Whether to switch to dynamic blur when a window has another window behind it, allowing you to see the one behind.

Enabled:

Disabled:
| +| **Texture source** | The image to use for static blur.
- Desktop wallpaper - A screenshot of the desktop is taken for every screen. Icons and widgets will be included. The cached texture is invalidated when the entire desktop is repainted, which can happen when the wallpaper changes, icons are interacted with or when widgets update.
- Custom - The specified image is scaled for every screen without respecting the aspect ratio. Supported formats are JPEG and PNG. | +| **Blur texture** | Whether to blur the texture. This is only done once during texture creation. | -If this option is enabled, the effect will automatically switch to real blur when necessary. At very high blur strengths, there may be a slight difference in the texture. +## Window rules -https://github.com/taj-ny/kwin-effects-forceblur/assets/79316397/7bae6a16-6c78-4889-8df1-feb24005dabc +| Option | Description | +|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Convert simple config to window rules** | Whether to automatically convert simple configuration into window rules. Disabling this will cause most options in the simple configuration UI to not have an effect anymore. | -### Image source -The image to use for fake blur. +# Window rules +Configuration UI for window rules will be added in v2. The configuration file is located at ``~/.config/kwinbetterblurrc`` and isn't created automatically. -- Desktop wallpaper - A screenshot of the desktop is taken for every screen. Icons and widgets will be included. The cached texture is invalidated when the entire desktop is repainted, -which can happen when the wallpaper changes, icons are interacted with or when widgets update. -- Custom - The specified image is scaled for every screen without respecting the aspect ratio. Supported formats are JPEG and PNG. +Simple configuration is converted into multiple rules with priorities lower than 0. They are not added into the configuration file. This behavior can be disabled in the configuration UI in the *Window Rules* tab. -### Blur image -Whether to blur the image used for fake blur. This is only done once. +An example is provided at the end. + +## File structure +### Window rule +**Group**: [WindowRules][\$RuleName], where **$RuleName (string)** is the unique name of this rule. + +Rules are not evaluated in order as they appear in the file. + +| Property | Type | Description | +|--------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| **Priority** | ``int (>=0)`` | Properties of rules with higher priority override properties of rules with lower priority.

Priorities lower than 0 are reserved. | + +### Conditions +**Group**: [WindowRules][\$RuleName][Conditions][\$ConditionName], where **$ConditionName (string)** is the unique name of this condition in the rule. + +At least 1 (or 0 if none specified) condition must be satisfied in order for this rule to be applied. All specified subconditions in a condition must be satisfied. + +| Property | Type | Description | +|-----------------------------|--------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Negate** | ``enum (WindowClass, WindowState, WindowType)``
Multiple values are allowed. | Which subconditions should be negated.

Separated by space. | +| **HasWindowBehind** | ``bool`` | Whether only windows that (don't) have another window (excluding desktops) behind them should satisfy this condition. | +| **WindowClass** | ``string`` or ``regex`` | A regular expression executed on the window class. Only a partial match is required.

For an exact match, use ``^class$``.
To specify multiple classes, separate them by ``\|``: ``class1\|class2``.
A dot matches any character, which can be prevented by escaping it: ``\.``. | - | +| **WindowState** | ``enum (Fullscreen, Maximized)``
Multiple values are allowed. | Separated by space. | +| **WindowType** | ``enum (Dialog, Dock, Normal, Menu, Toolbar, Tooltip, Utility)``
Multiple values are allowed. | Separated by space. + +### Properties +**Group**: [WindowRules][\$RuleName][Properties] + +Properties don't have default values. Unset properties don't override properties of other rules. + +| Property | Type | Description | +|:-----------------------------|------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **BlurContent** | ``bool`` | Whether to force blur the window's content, excluding decorations. | +| **BlurDecorations** | ``bool`` | See *Blur window decorations as well* in the *Simple* section. The difference is that in window rules it's possible to blur only the decorations and the corner radius is specified in the *CornerRadius* property. | +| **CornerRadius** | ``float (>= 0)`` | Corner radius for the blur region.

A single number specifies the same radius for both top and bottom corners. Example: ``12.5``
Two numbers separated by a space specify different radii for top and bottom corners respectively. Example: ``0 12.5``

Top corner radius will only be applied to windows with force-blurred decorations or no decorations. | +| **CornerAntialiasing** | ``float (>= 0)`` | Makes rounded corners appear smoother. Recommended value is 1. | +| **ForceTransparency** | ``bool`` | See *Treat windows as transparent* in the *Simple* section. | +| **StaticBlur** | ``bool`` | Whether to use static blur instead of dynamic blur for the window. +| **WindowOpacityAffectsBlur** | ``bool`` | See *Window opacity affects blur* in the *Simple* section. | + +## Example +``` +[WindowRules][AllWindows][Properties] +CornerAntialiasing = true +WindowOpacityAffectsBlur = false + + +[WindowRules][ForceBlur][Conditions][0] +WindowClass = clementine|kate|kwrite|org\.freedesktop\.impl\.portal\.desktop\.kde|plasmashell +WindowType = Dialog Normal Menu Toolbar Tooltip Utility + +[WindowRules][ForceBlur][Conditions][1] +WindowClass = firefox +WindowType = Normal + +[WindowRules][ForceBlur][Properties] +BlurContent = true +BlurDecorations = true + + +[WindowRules][StaticBlur][Conditions][0] +HasWindowBehind = false + +[WindowRules][StaticBlur][Properties] +StaticBlur = true + + +[WindowRules][WindowCorners][Conditions][0] +Negate = WindowState +WindowState = Fullscreen Maximized +WindowType = Normal + +[WindowRules][WindowCorners][Properties] +CornerRadius = 15 + + +[WindowRules][MenuCorners][Conditions][0] +WindowType = Menu + +[WindowRules][MenuCorners][Properties] +CornerRadius = 10 +``` \ No newline at end of file diff --git a/flake.lock b/flake.lock index 71f15f8dba..5a7324ca64 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1730200266, - "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", + "lastModified": 1731139594, + "narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", + "rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2", "type": "github" }, "original": { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ce5f865c7..641f3eb292 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,11 +16,15 @@ set(forceblur_SOURCES blur.cpp blur.qrc main.cpp - settings.cpp + windowrules/windowrulecondition.cpp + windowrules/windowrule.cpp + windowrules/windowrulelist.cpp + windowrules/windowproperties.cpp + blurwindow.cpp ) kconfig_add_kcfg_files(forceblur_SOURCES - blurconfig.kcfgc + config/config.kcfgc ) add_library(forceblur MODULE ${forceblur_SOURCES}) @@ -31,5 +35,6 @@ target_link_libraries(forceblur PRIVATE KDecoration2::KDecoration ) +target_compile_definitions(forceblur PRIVATE CONFIG_KWIN) install(TARGETS forceblur DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/plugins) diff --git a/src/blur.cpp b/src/blur.cpp index d156dc4e0f..d86c93e47e 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -8,7 +8,7 @@ #include "blur.h" // KConfigSkeleton -#include "blurconfig.h" +#include "config.h" #include "core/pixelgrid.h" #include "core/rendertarget.h" @@ -16,11 +16,13 @@ #include "effect/effecthandler.h" #include "opengl/glutils.h" #include "opengl/glplatform.h" +#include "scene/windowitem.h" #include "utils.h" #include "utils/xcbutils.h" #include "wayland/blur.h" #include "wayland/display.h" #include "wayland/surface.h" +#include "kwin/window.h" #include #include @@ -53,10 +55,12 @@ static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEH BlurManagerInterface *BlurEffect::s_blurManager = nullptr; QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr; +std::vector BlurEffect::s_allWindows; BlurEffect::BlurEffect() { - BlurConfig::instance(effects->config()); + m_config = std::make_unique(); + BetterBlur::Config::instance(effects->config()); ensureResources(); m_downsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, @@ -221,131 +225,42 @@ void BlurEffect::initBlurStrengthValues() void BlurEffect::reconfigure(ReconfigureFlags flags) { - m_settings.read(); + Q_UNUSED(flags) - m_iterationCount = blurStrengthValues[m_settings.general.blurStrength].iteration; - m_offset = blurStrengthValues[m_settings.general.blurStrength].offset; + BetterBlur::Config::self()->read(); + m_config->load(); + + int blurStrength = BetterBlur::Config::blurStrength() - 1; + m_iterationCount = blurStrengthValues[blurStrength].iteration; + m_offset = blurStrengthValues[blurStrength].offset; m_expandSize = blurOffsets[m_iterationCount - 1].expandSize; - m_fakeBlurTextures.clear(); + m_noiseStrength = BetterBlur::Config::noiseStrength(); + m_staticBlurImage = QImage(BetterBlur::Config::fakeBlurImage()); + m_staticBlurTextures.clear(); - for (EffectWindow *w : effects->stackingOrder()) { - updateBlurRegion(w); + for (const auto &[_, w] : m_windows) { + w->updateProperties(); + w->updateBlurRegion(); } // Update all windows for the blur to take effect effects->addRepaintFull(); } -void BlurEffect::updateBlurRegion(EffectWindow *w, bool geometryChanged) -{ - std::optional content; - std::optional frame; - - if (net_wm_blur_region != XCB_ATOM_NONE) { - const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); - QRegion region; - if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { - const uint32_t *cardinals = reinterpret_cast(value.constData()); - for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { - int x = cardinals[i++]; - int y = cardinals[i++]; - int w = cardinals[i++]; - int h = cardinals[i++]; - region += Xcb::fromXNative(QRect(x, y, w, h)).toRect(); - } - } - if (!value.isNull()) { - content = region; - } - } - - SurfaceInterface *surf = w->surface(); - - if (surf && surf->blur()) { - content = surf->blur()->region(); - } - - if (auto internal = w->internalWindow()) { - const auto property = internal->property("kwin_blur"); - if (property.isValid()) { - content = property.value(); - } - } - - if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) { - frame = decorationBlurRegion(w); - } - - // Don't override blur region for menus that already have one. The window geometry could include shadows. - if (shouldForceBlur(w) && !((isMenu(w) || w->isTooltip()) && (content.has_value() || geometryChanged))) { - content = w->expandedGeometry().translated(-w->x(), -w->y()).toRect(); - if (m_settings.forceBlur.blurDecorations && w->decoration()) { - frame = w->frameGeometry().translated(-w->x(), -w->y()).toRect(); - } - } - - if (content.has_value() || frame.has_value()) { - BlurEffectData &data = m_windows[w]; - data.content = content; - data.frame = frame; - } else if (!geometryChanged) { // Blur may disappear if this method is called when window geometry changes - if (auto it = m_windows.find(w); it != m_windows.end()) { - effects->makeOpenGLContextCurrent(); - m_windows.erase(it); - } - } -} - -bool BlurEffect::hasFakeBlur(EffectWindow *w) -{ - if (!m_settings.fakeBlur.enable) { - return false; - } - - if (m_settings.fakeBlur.disableWhenWindowBehind) { - if (auto it = m_windows.find(w); it != m_windows.end()) { - return !it->second.hasWindowBehind; - } - } - - return true; -} - void BlurEffect::slotWindowAdded(EffectWindow *w) { - SurfaceInterface *surf = w->surface(); - - if (surf) { - windowBlurChangedConnections[w] = connect(surf, &SurfaceInterface::blurChanged, this, [this, w]() { - if (w) { - updateBlurRegion(w); - } - }); - } + s_allWindows.push_back(w); + m_windows[w] = std::make_unique(m_config.get(), w, net_wm_blur_region); windowExpandedGeometryChangedConnections[w] = connect(w, &EffectWindow::windowExpandedGeometryChanged, this, [this,w]() { - if (!w) { - return; - } - - if (w->isDesktop() && !effects->waylandDisplay()) { - m_fakeBlurTextures.erase(nullptr); - return; + if (w && w->isDesktop() && !effects->waylandDisplay()) { + m_staticBlurTextures.erase(nullptr); } - - updateBlurRegion(w, true); }); if (auto internal = w->internalWindow()) { internal->installEventFilter(this); } - - connect(w, &EffectWindow::windowDecorationChanged, this, &BlurEffect::setupDecorationConnections); - setupDecorationConnections(w); - - updateBlurRegion(w); - - m_allWindows.push_back(w); } void BlurEffect::slotWindowDeleted(EffectWindow *w) @@ -354,41 +269,35 @@ void BlurEffect::slotWindowDeleted(EffectWindow *w) effects->makeOpenGLContextCurrent(); m_windows.erase(it); } - if (auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) { - disconnect(*it); - windowBlurChangedConnections.erase(it); - } if (auto it = windowExpandedGeometryChangedConnections.find(w); it != windowExpandedGeometryChangedConnections.end()) { disconnect(*it); windowExpandedGeometryChangedConnections.erase(it); } - if (auto it = std::find(m_allWindows.begin(), m_allWindows.end(), w); it != m_allWindows.end()) { - m_allWindows.erase(it); + if (auto it = std::find(s_allWindows.begin(), s_allWindows.end(), w); it != s_allWindows.end()) { + s_allWindows.erase(it); } - if (m_blurWhenTransformed.contains(w)) { - m_blurWhenTransformed.removeOne(w); - } + m_windows.erase(w); } void BlurEffect::slotScreenAdded(KWin::Output *screen) { screenChangedConnections[screen] = connect(screen, &Output::changed, this, [this, screen]() { - if (!m_settings.fakeBlur.enable) { + if (m_staticBlurTextures.empty()) { return; } - m_fakeBlurTextures.erase(screen); + m_staticBlurTextures.erase(screen); effects->addRepaintFull(); }); } void BlurEffect::slotScreenRemoved(KWin::Output *screen) { - for (auto &[window, data] : m_windows) { - if (auto it = data.render.find(screen); it != data.render.end()) { + for (auto &[_, w] : m_windows) { + if (auto it = w->render.find(screen); it != w->render.end()) { effects->makeOpenGLContextCurrent(); - data.render.erase(it); + w->render.erase(it); } } @@ -401,21 +310,10 @@ void BlurEffect::slotScreenRemoved(KWin::Output *screen) void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) { if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) { - updateBlurRegion(w); + m_windows[w]->updateBlurRegion(); } } -void BlurEffect::setupDecorationConnections(EffectWindow *w) -{ - if (!w->decoration()) { - return; - } - - connect(w->decoration(), &KDecoration2::Decoration::blurRegionChanged, this, [this, w]() { - updateBlurRegion(w); - }); -} - bool BlurEffect::eventFilter(QObject *watched, QEvent *event) { auto internal = qobject_cast(watched); @@ -423,7 +321,7 @@ bool BlurEffect::eventFilter(QObject *watched, QEvent *event) QDynamicPropertyChangeEvent *pe = static_cast(event); if (pe->propertyName() == "kwin_blur") { if (auto w = effects->findWindow(internal)) { - updateBlurRegion(w); + m_windows[w]->updateBlurRegion(); } } } @@ -444,52 +342,11 @@ bool BlurEffect::supported() #endif } -bool BlurEffect::decorationSupportsBlurBehind(const EffectWindow *w) const -{ - return w->decoration() && !w->decoration()->blurRegion().isNull(); -} - -QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const -{ - if (!decorationSupportsBlurBehind(w)) { - return QRegion(); - } - - QRegion decorationRegion = QRegion(w->decoration()->rect()) - w->contentsRect().toRect(); - //! we return only blurred regions that belong to decoration region - return decorationRegion.intersected(w->decoration()->blurRegion()); -} - -QRegion BlurEffect::blurRegion(EffectWindow *w) const -{ - QRegion region; - - if (auto it = m_windows.find(w); it != m_windows.end()) { - const std::optional &content = it->second.content; - const std::optional &frame = it->second.frame; - if (content.has_value()) { - if (content->isEmpty()) { - // An empty region means that the blur effect should be enabled - // for the whole window. - region = w->rect().toRect(); - } else { - if (frame.has_value()) { - region = frame.value(); - } - region += content->translated(w->contentsRect().topLeft().toPoint()) & w->contentsRect().toRect(); - } - } else if (frame.has_value()) { - region = frame.value(); - } - } - - return region; -} - void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) { - m_paintedArea = QRegion(); - m_currentBlur = QRegion(); + m_paintedArea = {}; + m_currentBlur = {}; + m_windowGeometriesSum = {}; m_currentScreen = effects->waylandDisplay() ? data.screen : nullptr; effects->prePaintScreen(data, presentTime); @@ -499,24 +356,26 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: { // this effect relies on prePaintWindow being called in the bottom to top order - // in case this window has regions to be blurred - const QRegion blurArea = blurRegion(w).translated(w->pos().toPoint()); - - bool fakeBlur = hasFakeBlur(w) && m_fakeBlurTextures.contains(m_currentScreen) && !blurArea.isEmpty(); - if (fakeBlur) { - data.opaque += blurArea; - - int topCornerRadius; - int bottomCornerRadius; - if (isMenu(w)) { - topCornerRadius = bottomCornerRadius = std::ceil(m_settings.roundedCorners.menuRadius); - } else if (w->isDock()) { - topCornerRadius = bottomCornerRadius = std::ceil(m_settings.roundedCorners.dockRadius); - } else { - topCornerRadius = std::ceil(m_settings.roundedCorners.windowTopRadius); - bottomCornerRadius = std::ceil(m_settings.roundedCorners.windowBottomRadius); + const auto &blurWindow = m_windows[w]; + + if (!w->isDesktop()) { + const auto hadWindowBehind = blurWindow->hasWindowBehind(); + blurWindow->setHasWindowBehind(m_windowGeometriesSum.intersects(w->frameGeometry().toRect())); + if (hadWindowBehind != blurWindow->hasWindowBehind()) { + data.paint += w->windowItem()->boundingRect().toRect(); + } + } + + const QRegion blurArea = blurWindow->blurRegion().translated(w->pos().toPoint()); + + bool staticBlur = blurWindow->properties()->staticBlur() && m_staticBlurTextures.contains(m_currentScreen) && !blurArea.isEmpty(); + if (staticBlur) { + if (!blurWindow->properties()->windowOpacityAffectsBlur()) { + data.opaque += blurArea; } + const int topCornerRadius = std::ceil(blurWindow->properties()->topCornerRadius()); + const int bottomCornerRadius = std::ceil(blurWindow->properties()->bottomCornerRadius()); if (!w->isDock() || (w->isDock() && isDockFloating(w, blurArea))) { const QRect blurRect = blurArea.boundingRect(); data.opaque -= QRect(blurRect.x(), blurRect.y(), topCornerRadius, topCornerRadius); @@ -526,46 +385,17 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: } } - if (m_settings.fakeBlur.enable) { - if (m_settings.fakeBlur.disableWhenWindowBehind) { - if (auto it = m_windows.find(w); it != m_windows.end()) { - const bool hadWindowBehind = it->second.hasWindowBehind; - it->second.hasWindowBehind = false; - for (EffectWindow *other : m_allWindows) { - if (w->window()->stackingOrder() <= other->window()->stackingOrder() - || other->isDesktop() - || !other->isOnCurrentDesktop() - || !other->isOnCurrentActivity() - || other->window()->resourceClass() == "xwaylandvideobridge" - || other->isMinimized()) { - continue; - } - - if (w->frameGeometry().intersects(other->frameGeometry())) { - it->second.hasWindowBehind = true; - break; - } - } - - if (hadWindowBehind != it->second.hasWindowBehind) { - data.paint += blurArea; - data.opaque -= blurArea; - } - } - } - - if (m_settings.fakeBlur.imageSource == FakeBlurImageSource::DesktopWallpaper && w->isDesktop() && w->frameGeometry() == data.paint.boundingRect()) { - m_fakeBlurTextures.erase(m_currentScreen); - } + if (!m_staticBlurTextures.empty() && w->isDesktop() && w->frameGeometry() == data.paint.boundingRect()) { + m_staticBlurTextures.erase(m_currentScreen); } - if (m_settings.forceBlur.markWindowAsTranslucent && !fakeBlur && shouldForceBlur(w)) { + if (!staticBlur && blurWindow->properties()->forceTransparency()) { data.setTranslucent(); } effects->prePaintWindow(w, data, presentTime); - if (!fakeBlur) { + if (!staticBlur) { const QRegion oldOpaque = data.opaque; if (data.opaque.intersects(m_currentBlur)) { // to blur an area partially we have to shrink the opaque area of a window @@ -601,67 +431,26 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: m_paintedArea -= data.opaque; m_paintedArea += data.paint; -} - -bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) -{ - const bool hasForceBlurRole = w->data(WindowForceBlurRole).toBool(); - if ((effects->activeFullScreenEffect() && !hasForceBlurRole) || w->isDesktop()) { - return false; - } - - bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); - bool translated = data.xTranslation() || data.yTranslation(); - if (!(scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED)))) { - if (m_blurWhenTransformed.contains(w)) { - m_blurWhenTransformed.removeOne(w); - } - - return true; - } - - // The force blur role may be removed while the window is still transformed, causing the blur to disappear for - // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore. - if (m_blurWhenTransformed.contains(w)) { - return true; - } else if (hasForceBlurRole) { - m_blurWhenTransformed.append(w); + if (!w->isDesktop() && w->window()->resourceClass() != "xwaylandvideobridge") { + m_windowGeometriesSum += w->frameGeometry().toRect(); } - - return hasForceBlurRole; -} - -bool BlurEffect::shouldForceBlur(const EffectWindow *w) const -{ - if (w->isDesktop() || (!m_settings.forceBlur.blurDocks && w->isDock()) || (!m_settings.forceBlur.blurMenus && isMenu(w))) { - return false; - } - - bool matches = m_settings.forceBlur.windowClasses.contains(w->window()->resourceName()) - || m_settings.forceBlur.windowClasses.contains(w->window()->resourceClass()); - return (matches && m_settings.forceBlur.windowClassMatchingMode == WindowClassMatchingMode::Whitelist) - || (!matches && m_settings.forceBlur.windowClassMatchingMode == WindowClassMatchingMode::Blacklist); } void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) { - auto it = m_windows.find(w); - if (it != m_windows.end()) { - BlurEffectData &blurInfo = it->second; - BlurRenderData &renderInfo = blurInfo.render[m_currentScreen]; - if (shouldBlur(w, mask, data)) { - blur(renderInfo, renderTarget, viewport, w, mask, region, data); - } + auto blurWindow = m_windows[w].get(); + if (blurWindow->shouldBlur(mask, data)) { + blur(blurWindow, blurWindow->render[m_currentScreen], renderTarget, viewport, mask, region, data); } // Draw the window over the blurred area effects->drawWindow(renderTarget, viewport, w, mask, region, data); } -GLTexture *BlurEffect::ensureFakeBlurTexture(const Output *output, const RenderTarget &renderTarget) +GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const RenderTarget &renderTarget) { - if (m_fakeBlurTextures.contains(output)) { - return m_fakeBlurTextures[output].get(); + if (m_staticBlurTextures.contains(output)) { + return m_staticBlurTextures[output].get(); } if (effects->waylandDisplay() && !output) { @@ -673,23 +462,23 @@ GLTexture *BlurEffect::ensureFakeBlurTexture(const Output *output, const RenderT textureFormat = renderTarget.texture()->internalFormat(); } GLTexture *texture = effects->waylandDisplay() - ? createFakeBlurTextureWayland(output, renderTarget, textureFormat) - : createFakeBlurTextureX11(textureFormat); + ? createStaticBlurTextureWayland(output, renderTarget, textureFormat) + : createStaticBlurTextureX11(textureFormat); if (!texture) { return nullptr; } - return (m_fakeBlurTextures[output] = std::unique_ptr(texture)).get(); + return (m_staticBlurTextures[output] = std::unique_ptr(texture)).get(); } GLTexture *BlurEffect::ensureNoiseTexture() { - if (m_settings.general.noiseStrength == 0) { + if (m_noiseStrength == 0) { return nullptr; } const qreal scale = std::max(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0); - if (!noiseTexture || noiseTextureScale != scale || noiseTextureStength != m_settings.general.noiseStrength) { + if (!noiseTexture || noiseTextureScale != scale || noiseTextureStength != m_noiseStrength) { // Init randomness based on time std::srand((uint)QTime::currentTime().msec()); @@ -699,7 +488,7 @@ GLTexture *BlurEffect::ensureNoiseTexture() uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y); for (int x = 0; x < noiseImage.width(); x++) { - noiseImageLine[x] = std::rand() % m_settings.general.noiseStrength; + noiseImageLine[x] = std::rand() % m_noiseStrength; } } @@ -712,16 +501,18 @@ GLTexture *BlurEffect::ensureNoiseTexture() noiseTexture->setFilter(GL_NEAREST); noiseTexture->setWrapMode(GL_REPEAT); noiseTextureScale = scale; - noiseTextureStength = m_settings.general.noiseStrength; + noiseTextureStength = m_noiseStrength; } return noiseTexture.get(); } -void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) +void BlurEffect::blur(BetterBlur::Window *w, BetterBlur::BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, WindowPaintData &data) { + Q_UNUSED(mask) + // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. - QRegion blurShape = w ? blurRegion(w).translated(w->pos().toPoint()) : region; + QRegion blurShape = w ? w->blurRegion().translated(w->window()->pos().toPoint()) : region; if (data.xScale() != 1 || data.yScale() != 1) { QPoint pt = blurShape.boundingRect().topLeft(); QRegion scaledShape; @@ -739,8 +530,8 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg const QRect backgroundRect = blurShape.boundingRect(); const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); - const auto opacity = w && m_settings.general.windowOpacityAffectsBlur - ? w->opacity() * data.opacity() + const auto opacity = w && w->properties()->windowOpacityAffectsBlur() + ? w->window()->opacity() * data.opacity() : data.opacity(); QList effectiveShape; @@ -767,20 +558,11 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg float topCornerRadius = 0; float bottomCornerRadius = 0; - if (w && !(w->isDock() && !isDockFloating(w, blurShape))) { - const bool isMaximized = effects->clientArea(MaximizeArea, effects->activeScreen(), effects->currentDesktop()) == w->frameGeometry(); - if (isMenu(w)) { - topCornerRadius = bottomCornerRadius = m_settings.roundedCorners.menuRadius; - } else if (w->isDock()) { - topCornerRadius = bottomCornerRadius = m_settings.roundedCorners.dockRadius; - } else if ((!w->isFullScreen() && !isMaximized) || m_settings.roundedCorners.roundMaximized) { - if (!w->decoration() || (w->decoration() && m_settings.forceBlur.blurDecorations)) { - topCornerRadius = m_settings.roundedCorners.windowTopRadius; - } - bottomCornerRadius = m_settings.roundedCorners.windowBottomRadius; + if (w && !(w->window()->isDock() && !isDockFloating(w->window(), blurShape))) { + if (!w->window()->decoration() || (w->window()->decoration() && w->properties()->blurDecorations())) { + topCornerRadius = w->properties()->topCornerRadius() * viewport.scale(); } - topCornerRadius = topCornerRadius * viewport.scale(); - bottomCornerRadius = bottomCornerRadius * viewport.scale(); + bottomCornerRadius = w->properties()->bottomCornerRadius() * viewport.scale(); } // Maybe reallocate offscreen render targets. Keep in mind that the first one contains @@ -792,12 +574,12 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg // Since the VBO is shared, the texture needs to be blurred before the geometry is uploaded, otherwise it will be // reset. - GLTexture *fakeBlurTexture = nullptr; - if (w && hasFakeBlur(w)) { - fakeBlurTexture = ensureFakeBlurTexture(m_currentScreen, renderTarget); + GLTexture *staticBlurTexture = nullptr; + if (w && w->properties()->staticBlur()) { + staticBlurTexture = ensureStaticBlurTexture(m_currentScreen, renderTarget); } - if (!fakeBlurTexture + if (!staticBlurTexture && (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat)) { @@ -824,7 +606,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg } // Fetch the pixels behind the shape that is going to be blurred. - if (!fakeBlurTexture) { + if (!staticBlurTexture) { const QRegion dirtyRegion = region & backgroundRect; for (const QRect &dirtyRect: dirtyRegion) { renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft())); @@ -935,7 +717,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg vbo->bindArrays(); - if (fakeBlurTexture) { + if (staticBlurTexture) { ShaderManager::instance()->pushShader(m_texture.shader.get()); QMatrix4x4 projectionMatrix; @@ -948,16 +730,16 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg } m_texture.shader->setUniform(m_texture.mvpMatrixLocation, projectionMatrix); - m_texture.shader->setUniform(m_texture.textureSizeLocation, QVector2D(fakeBlurTexture->size().width(), fakeBlurTexture->size().height())); + m_texture.shader->setUniform(m_texture.textureSizeLocation, QVector2D(staticBlurTexture->size().width(), staticBlurTexture->size().height())); m_texture.shader->setUniform(m_texture.texStartPosLocation, QVector2D(backgroundRect.x() - screenGeometry.x(), backgroundRect.y() - screenGeometry.y())); m_texture.shader->setUniform(m_texture.blurSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height())); m_texture.shader->setUniform(m_texture.scaleLocation, (float)viewport.scale()); m_texture.shader->setUniform(m_texture.topCornerRadiusLocation, topCornerRadius); m_texture.shader->setUniform(m_texture.bottomCornerRadiusLocation, bottomCornerRadius); - m_texture.shader->setUniform(m_texture.antialiasingLocation, m_settings.roundedCorners.antialiasing); + m_texture.shader->setUniform(m_texture.antialiasingLocation, w->properties()->cornerAntialiasing()); m_texture.shader->setUniform(m_texture.opacityLocation, static_cast(opacity)); - fakeBlurTexture->bind(); + staticBlurTexture->bind(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -1023,7 +805,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg GLFramebuffer::popFramebuffer(); const auto &read = renderInfo.framebuffers[1]; - if (m_settings.general.noiseStrength > 0) { + if (m_noiseStrength > 0) { if (const auto *noiseTexture = ensureNoiseTexture()) { m_upsamplePass.shader->setUniform(m_upsamplePass.noiseLocation, true); m_upsamplePass.shader->setUniform(m_upsamplePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); @@ -1039,7 +821,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg m_upsamplePass.shader->setUniform(m_upsamplePass.topCornerRadiusLocation, topCornerRadius); m_upsamplePass.shader->setUniform(m_upsamplePass.bottomCornerRadiusLocation, bottomCornerRadius); - m_upsamplePass.shader->setUniform(m_upsamplePass.antialiasingLocation, m_settings.roundedCorners.antialiasing); + m_upsamplePass.shader->setUniform(m_upsamplePass.antialiasingLocation, w ? w->properties()->cornerAntialiasing() : 0.0); m_upsamplePass.shader->setUniform(m_upsamplePass.blurSizeLocation, QVector2D(backgroundRect.width(), backgroundRect.height())); m_upsamplePass.shader->setUniform(m_upsamplePass.opacityLocation, static_cast(opacity)); @@ -1068,13 +850,13 @@ void BlurEffect::blur(GLTexture *texture) const QRect textureRect = QRect(0, 0, texture->width(), texture->height()); auto blurredFramebuffer = std::make_unique(texture); - BlurRenderData renderData; + BetterBlur::BlurRenderData renderInfo; const RenderTarget renderTarget(blurredFramebuffer.get()); const RenderViewport renderViewport(textureRect, 1.0, renderTarget); WindowPaintData data; GLFramebuffer::pushFramebuffer(blurredFramebuffer.get()); - blur(renderData, renderTarget, renderViewport, nullptr, 0, textureRect, data); + blur(nullptr, renderInfo, renderTarget, renderViewport, 0, textureRect, data); GLFramebuffer::popFramebuffer(); } @@ -1110,7 +892,7 @@ GLTexture *BlurEffect::wallpaper(EffectWindow *desktop, const qreal &scale, cons return texture.release(); } -GLTexture *BlurEffect::createFakeBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat) +GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat) { EffectWindow *desktop = nullptr; for (EffectWindow *w : effects->stackingOrder()) { @@ -1124,10 +906,10 @@ GLTexture *BlurEffect::createFakeBlurTextureWayland(const Output *output, const } std::unique_ptr texture; - if (m_settings.fakeBlur.imageSource == FakeBlurImageSource::DesktopWallpaper) { + if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) { texture.reset(wallpaper(desktop, output->scale(), textureFormat)); - } else if (m_settings.fakeBlur.imageSource == FakeBlurImageSource::Custom) { - texture = GLTexture::upload(m_settings.fakeBlur.customImage.scaled(output->pixelSize(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation)); + } else if (BetterBlur::Config::fakeBlurImageSourceCustom()) { + texture = GLTexture::upload(m_staticBlurImage.scaled(output->pixelSize(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation)); } if (!texture) { return nullptr; @@ -1162,14 +944,14 @@ GLTexture *BlurEffect::createFakeBlurTextureWayland(const Output *output, const GLFramebuffer::popFramebuffer(); ShaderManager::instance()->popShader(); - if (m_settings.fakeBlur.blurCustomImage) { + if (BetterBlur::Config::fakeBlurCustomImageBlur()) { blur(texture.get()); } return texture.release(); } -GLTexture *BlurEffect::createFakeBlurTextureX11(const GLenum &textureFormat) +GLTexture *BlurEffect::createStaticBlurTextureX11(const GLenum &textureFormat) { std::vector desktops; QRegion desktopGeometries; @@ -1196,16 +978,16 @@ GLTexture *BlurEffect::createFakeBlurTextureX11(const GLenum &textureFormat) const auto geometry = desktop->frameGeometry(); std::unique_ptr texture; - if (m_settings.fakeBlur.imageSource == FakeBlurImageSource::DesktopWallpaper) { + if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) { texture.reset(wallpaper(desktop, 1, textureFormat)); - } else if (m_settings.fakeBlur.imageSource == FakeBlurImageSource::Custom) { - texture = GLTexture::upload(m_settings.fakeBlur.customImage.scaled(geometry.width(), geometry.height(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation)); + } else if (BetterBlur::Config::fakeBlurImageSourceCustom()) { + texture = GLTexture::upload(m_staticBlurImage.scaled(geometry.width(), geometry.height(), Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation)); } if (!texture) { return nullptr; } - if (m_settings.fakeBlur.blurCustomImage) { + if (BetterBlur::Config::fakeBlurCustomImageBlur()) { blur(texture.get()); } @@ -1222,6 +1004,11 @@ GLTexture *BlurEffect::createFakeBlurTextureX11(const GLenum &textureFormat) return compositeTexture.release(); } +const std::vector BlurEffect::allWindows() +{ + return BlurEffect::s_allWindows; +} + bool BlurEffect::isActive() const { return m_valid && !effects->isScreenLocked(); diff --git a/src/blur.h b/src/blur.h index bb06547f25..961d5b367a 100644 --- a/src/blur.h +++ b/src/blur.h @@ -7,10 +7,11 @@ #pragma once +#include "windowrules/windowrulelist.h" + #include "effect/effect.h" #include "opengl/glutils.h" -#include "settings.h" -#include "window.h" +#include "blurwindow.h" #include @@ -21,28 +22,6 @@ namespace KWin class BlurManagerInterface; -struct BlurRenderData -{ - /// Temporary render targets needed for the Dual Kawase algorithm, the first texture - /// contains not blurred background behind the window, it's cached. - std::vector> textures; - std::vector> framebuffers; -}; - -struct BlurEffectData -{ - /// The region that should be blurred behind the window - std::optional content; - - /// The region that should be blurred behind the frame - std::optional frame; - - /// The render data per screen. Screens can have different color spaces. - std::unordered_map render; - - bool hasWindowBehind; -}; - class BlurEffect : public KWin::Effect { Q_OBJECT @@ -54,6 +33,8 @@ class BlurEffect : public KWin::Effect static bool supported(); static bool enabledByDefault(); + static const std::vector allWindows(); + void reconfigure(ReconfigureFlags flags) override; void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override; void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override; @@ -77,30 +58,22 @@ public Q_SLOTS: void slotScreenAdded(KWin::Output *screen); void slotScreenRemoved(KWin::Output *screen); void slotPropertyNotify(KWin::EffectWindow *w, long atom); - void setupDecorationConnections(EffectWindow *w); private: void initBlurStrengthValues(); - QRegion blurRegion(EffectWindow *w) const; - QRegion decorationBlurRegion(const EffectWindow *w) const; - bool decorationSupportsBlurBehind(const EffectWindow *w) const; - bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data); - bool shouldForceBlur(const EffectWindow *w) const; - void updateBlurRegion(EffectWindow *w, bool geometryChanged = false); - bool hasFakeBlur(EffectWindow *w); /* * @param w The pointer to the window being blurred, nullptr if an image is being blurred. */ - void blur(BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); + void blur(BetterBlur::Window *w, BetterBlur::BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, WindowPaintData &data); void blur(GLTexture *texture); /** * @param output Can be nullptr. * @remark This method shall not be called outside of BlurEffect::blur. - * @return The cached fake blur texture. The texture will be created if it doesn't exist. + * @return The cached static blur texture. The texture will be created if it doesn't exist. */ - GLTexture *ensureFakeBlurTexture(const Output *output, const RenderTarget &renderTarget); + GLTexture *ensureStaticBlurTexture(const Output *output, const RenderTarget &renderTarget); GLTexture *ensureNoiseTexture(); /** @@ -111,17 +84,17 @@ public Q_SLOTS: GLTexture *wallpaper(EffectWindow *desktop, const qreal &scale, const GLenum &textureFormat); /** - * Creates a fake blur texture for the specified screen. + * Creates a static blur texture for the specified screen. * @remark This method shall not be called outside of BlurEffect::blur. * @return A pointer to the texture, or nullptr if an error occurred. */ - GLTexture *createFakeBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat); + GLTexture *createStaticBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat); /** - * Creates a composite fake blur texture containing images for all screens. + * Creates a composite static blur texture containing images for all screens. * @return A pointer to the texture, or nullptr if an error occurred. */ - GLTexture *createFakeBlurTextureX11(const GLenum &textureFormat); + GLTexture *createStaticBlurTextureX11(const GLenum &textureFormat); private: struct @@ -173,17 +146,17 @@ public Q_SLOTS: QRegion m_paintedArea; // keeps track of all painted areas (from bottom to top) QRegion m_currentBlur; // keeps track of the currently blured area of the windows(from bottom to top) Output *m_currentScreen = nullptr; + QRegion m_windowGeometriesSum; size_t m_iterationCount; // number of times the texture will be downsized to half size int m_offset; int m_expandSize; + int m_noiseStrength; std::unique_ptr noiseTexture; qreal noiseTextureScale = 1.0; int noiseTextureStength = 0; - BlurSettings m_settings; - struct OffsetStruct { float minOffset; @@ -201,15 +174,14 @@ public Q_SLOTS: QList blurStrengthValues; - std::unordered_map> m_fakeBlurTextures; - - // Windows to blur even when transformed. - QList m_blurWhenTransformed; + std::unordered_map> m_staticBlurTextures; + QImage m_staticBlurImage; - QMap windowBlurChangedConnections; QMap windowExpandedGeometryChangedConnections; QMap screenChangedConnections; - std::unordered_map m_windows; + std::unordered_map> m_windows; + + std::unique_ptr m_config; /** * Stores all currently open windows, even those that aren't blurred. Used for determining whether windows are @@ -218,7 +190,7 @@ public Q_SLOTS: * Objects retrieved from effects->stackingOrder() and workspace()->stackingOrder() appear to be deleted when * BlurEffect::prePaintWindow is running, so that can't be used. */ - std::vector m_allWindows; + static std::vector s_allWindows; static BlurManagerInterface *s_blurManager; static QTimer *s_blurManagerRemoveTimer; diff --git a/src/blurconfig.kcfgc b/src/blurconfig.kcfgc deleted file mode 100644 index 4fb3fe58ed..0000000000 --- a/src/blurconfig.kcfgc +++ /dev/null @@ -1,5 +0,0 @@ -File=blur.kcfg -ClassName=BlurConfig -NameSpace=KWin -Singleton=true -Mutators=true diff --git a/src/blurwindow.cpp b/src/blurwindow.cpp new file mode 100644 index 0000000000..fc2319b3ce --- /dev/null +++ b/src/blurwindow.cpp @@ -0,0 +1,256 @@ +#include "utils.h" +#include "blurwindow.h" + +#include "effect/effecthandler.h" +#include "effect/globals.h" +#include "effect/effectwindow.h" +#include "utils/xcbutils.h" +#include "wayland/blur.h" +#include "wayland/surface.h" +#include "window.h" + +#include + +namespace BetterBlur +{ + +Window::Window(const WindowRuleList *windowRules, KWin::EffectWindow *w, long net_wm_blur_region) + : m_windowRules(windowRules), + w(w), + net_wm_blur_region(net_wm_blur_region) +{ + connect(w, &KWin::EffectWindow::windowDecorationChanged, this, [this]() { + setupDecorationConnections(); + }); + windowExpandedGeometryChangedConnection = connect(w, &KWin::EffectWindow::windowExpandedGeometryChanged, this, &Window::slotWindowExpandedGeometryChanged); + setupDecorationConnections(); + + if (auto surf = w->surface()) { + windowBlurChangedConnection = connect(surf, &KWin::SurfaceInterface::blurChanged, [this]() { + updateBlurRegion(); + }); + } + + updateProperties(); + updateBlurRegion(); +} + +Window::~Window() +{ + disconnect(windowBlurChangedConnection); + disconnect(windowExpandedGeometryChangedConnection); +} + +bool Window::isMaximized() const +{ + return KWin::effects->clientArea(KWin::MaximizeArea, KWin::effects->activeScreen(), KWin::effects->currentDesktop()) == w->window()->frameGeometry(); +} + +bool Window::isMenu() const +{ + return w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow(); +} + +void Window::updateBlurRegion(bool geometryChanged) +{ + std::optional content; + std::optional frame; + + if (net_wm_blur_region != XCB_ATOM_NONE) { + const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); + QRegion region; + if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { + const uint32_t *cardinals = reinterpret_cast(value.constData()); + for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { + int x = cardinals[i++]; + int y = cardinals[i++]; + int w = cardinals[i++]; + int h = cardinals[i++]; + region += KWin::Xcb::fromXNative(QRect(x, y, w, h)).toRect(); + } + } + if (!value.isNull()) { + content = region; + } + } + + KWin::SurfaceInterface *surf = w->surface(); + + if (surf && surf->blur()) { + content = surf->blur()->region(); + } + + if (auto internal = w->internalWindow()) { + const auto property = internal->property("kwin_blur"); + if (property.isValid()) { + content = property.value(); + } + } + + if (w->decorationHasAlpha() && decorationSupportsBlurBehind()) { + frame = decorationBlurRegion(); + } + + // Don't override blur region for menus that already have one. The window geometry could include shadows. + if (!((isMenu() || w->isTooltip()) && (content.has_value() || geometryChanged))) { + if (m_properties->blurContent()) { + content = w->contentsRect().toRect(); + } + if (m_properties->blurDecorations() && w->decoration()) { + frame = QRegion(w->frameGeometry().translated(-w->x(), -w->y()).toRect()) - w->contentsRect().toRect(); + } + } + + if (content.has_value() || frame.has_value()) { + m_contentBlurRegion = content; + m_frameBlurRegion = frame; + } else if (!geometryChanged) { // Blur may disappear if this method is called when window geometry changes + m_contentBlurRegion = {}; + m_frameBlurRegion = {}; + render.clear(); + } +} + +QRegion Window::blurRegion() const +{ + QRegion region; + + if (m_contentBlurRegion.has_value()) { + if (m_contentBlurRegion->isEmpty()) { + // An empty region means that the blur effect should be enabled + // for the whole window. + region = w->rect().toRect(); + } else { + if (m_frameBlurRegion.has_value()) { + region = m_frameBlurRegion.value(); + } + region += m_contentBlurRegion->translated(w->contentsRect().topLeft().toPoint()) & w->contentsRect().toRect(); + } + } else if (m_frameBlurRegion.has_value()) { + region = m_frameBlurRegion.value(); + } + + return region; +} + +bool Window::shouldBlur(int mask, const KWin::WindowPaintData &data) +{ + const bool hasForceBlurRole = w->data(KWin::WindowForceBlurRole).toBool(); + if ((KWin::effects->activeFullScreenEffect() && !hasForceBlurRole) || w->isDesktop()) { + return false; + } + + bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); + bool translated = data.xTranslation() || data.yTranslation(); + if (!(scaled || (translated || (mask & KWin::Effect::PAINT_WINDOW_TRANSFORMED)))) { + m_blurWhenTransformed = false; + return true; + } + + // The force blur role may be removed while the window is still transformed, causing the blur to disappear for + // a short time. To avoid that, we allow the window to be blurred until it's not transformed anymore. + if (m_blurWhenTransformed) { + return true; + } else if (hasForceBlurRole) { + m_blurWhenTransformed = true; + } + + return hasForceBlurRole; +} + +QRegion Window::decorationBlurRegion() const +{ + if (!decorationSupportsBlurBehind()) { + return {}; + } + + QRegion decorationRegion = QRegion(w->decoration()->rect()) - w->contentsRect().toRect(); + //! we return only blurred regions that belong to decoration region + return decorationRegion.intersected(w->decoration()->blurRegion()); +} + +bool Window::decorationSupportsBlurBehind() const +{ + return w->decoration() && !w->decoration()->blurRegion().isNull(); +} + +void Window::slotWindowExpandedGeometryChanged(KWin::EffectWindow *w) +{ + if (w->size() != m_size) { + updateProperties(); + } + + updateBlurRegion(true); + m_size = w->size(); +} + +void Window::setupDecorationConnections() +{ + if (!w->decoration()) { + return; + } + + connect(w->decoration(), &KDecoration2::Decoration::blurRegionChanged, this, [this]() { + updateBlurRegion(); + }); +} + +void Window::updateProperties() +{ + // TODO Rule evaluation needs to be optimized. Sometimes it's not necessary to evaluate a rule. + + auto staticBlur = false; + if (m_properties) { + staticBlur = m_properties->staticBlur(); + } + + m_properties = std::make_unique(); + for (const auto &rule : m_windowRules->rules()) { + if (!rule->matches(this)) { + continue; + } + + auto ruleProperties = rule->properties(); + if (ruleProperties->m_blurContent) { + m_properties->setBlurContent(*ruleProperties->m_blurContent); + } + if (ruleProperties->m_blurDecorations) { + m_properties->setBlurDecorations(*ruleProperties->m_blurDecorations); + } + if (ruleProperties->m_forceTransparency) { + m_properties->setForceTransparency(*ruleProperties->m_forceTransparency); + } + if (ruleProperties->m_bottomCornerRadius) { + m_properties->setBottomCornerRadius(*ruleProperties->m_bottomCornerRadius); + } + if (ruleProperties->m_topCornerRadius) { + m_properties->setTopCornerRadius(*ruleProperties->m_topCornerRadius); + } + if (ruleProperties->m_cornerAntialiasing) { + m_properties->setCornerAntialiasing(*ruleProperties->m_cornerAntialiasing); + } + if (ruleProperties->m_staticBlur) { + m_properties->setStaticBlur(*ruleProperties->m_staticBlur); + } + if (ruleProperties->m_windowOpacityAffectsBlur) { + m_properties->setWindowOpacityAffectsBlur(*ruleProperties->m_windowOpacityAffectsBlur); + } + } + + if (staticBlur != m_properties->staticBlur()) { + w->addRepaintFull(); + } +} + +void Window::setHasWindowBehind(const bool &hasWindowBehind) +{ + const auto old = m_hasWindowBehind; + m_hasWindowBehind = hasWindowBehind; + if (old != m_hasWindowBehind) { + updateProperties(); + } +} + +} + +#include "moc_blurwindow.cpp" \ No newline at end of file diff --git a/src/blurwindow.h b/src/blurwindow.h new file mode 100644 index 0000000000..fd163bd770 --- /dev/null +++ b/src/blurwindow.h @@ -0,0 +1,83 @@ +#pragma once + +#include "windowrules/windowproperties.h" +#include "windowrules/windowrulelist.h" + +#include "effect/effect.h" +#include "effect/effectwindow.h" +#include "opengl/glutils.h" + +namespace BetterBlur +{ + +class WindowRuleList; + +struct BlurRenderData +{ + /// Temporary render targets needed for the Dual Kawase algorithm, the first texture + /// contains not blurred background behind the window, it's cached. + std::vector> textures; + std::vector> framebuffers; +}; + +class Window : public QObject +{ + Q_OBJECT +public: + Window(const WindowRuleList *windowRules, KWin::EffectWindow *w, long net_wm_blur_region); + ~Window() override; + + bool isMaximized() const; + bool isMenu() const; + + void updateBlurRegion(bool geometryChanged = false); + QRegion blurRegion() const; + bool shouldBlur(int mask, const KWin::WindowPaintData &data); + + const WindowProperties *properties() const + { + return m_properties.get(); + } + void updateProperties(); + + KWin::EffectWindow *window() const + { + return w; + } + + bool hasWindowBehind() const + { + return m_hasWindowBehind; + } + void setHasWindowBehind(const bool &hasWindowBehind); + + /// The render data per screen. Screens can have different color spaces. + std::unordered_map render; + +public Q_SLOTS: + void slotWindowExpandedGeometryChanged(KWin::EffectWindow *w); +// void slotDecorationBlurRegionChanged(); + +private: + void setupDecorationConnections(); + + QRegion decorationBlurRegion() const; + bool decorationSupportsBlurBehind() const; + + QMetaObject::Connection windowBlurChangedConnection; + QMetaObject::Connection windowExpandedGeometryChangedConnection; + + std::optional m_contentBlurRegion; + std::optional m_frameBlurRegion; + long net_wm_blur_region = 0; + bool m_blurWhenTransformed = false; + + QSizeF m_size; + bool m_hasWindowBehind = false; + std::unique_ptr m_properties; + KWin::EffectWindow *w; + + const WindowRuleList *m_windowRules; +}; + +} \ No newline at end of file diff --git a/src/blur.kcfg b/src/config/config.kcfg similarity index 95% rename from src/blur.kcfg rename to src/config/config.kcfg index 32512e8735..9fa9296b8b 100644 --- a/src/blur.kcfg +++ b/src/config/config.kcfg @@ -17,7 +17,7 @@ class1 class2 -class3 +$regex:class3 true @@ -73,5 +73,8 @@ class3 true + + true + diff --git a/src/config/config.kcfgc b/src/config/config.kcfgc new file mode 100644 index 0000000000..d56f762df0 --- /dev/null +++ b/src/config/config.kcfgc @@ -0,0 +1,5 @@ +File=config.kcfg +ClassName=Config +NameSpace=BetterBlur +Singleton=true +Mutators=true diff --git a/src/kcm/CMakeLists.txt b/src/kcm/CMakeLists.txt index 7072ba56b9..698a4a9cda 100644 --- a/src/kcm/CMakeLists.txt +++ b/src/kcm/CMakeLists.txt @@ -1,7 +1,7 @@ set(kwin_better_blur_config_SRCS blur_config.cpp blur_config.h) ki18n_wrap_ui(kwin_better_blur_config_SRCS blur_config.ui) -kconfig_add_kcfg_files(kwin_better_blur_config_SRCS ../blurconfig.kcfgc) +kconfig_add_kcfg_files(kwin_better_blur_config_SRCS ../config/config.kcfgc) qt_add_dbus_interface(kwin_better_blur_config_SRCS ${KWIN_EFFECTS_INTERFACE} kwineffects_interface) diff --git a/src/kcm/blur_config.cpp b/src/kcm/blur_config.cpp index 0f5fab180e..ee4ad6ee4c 100644 --- a/src/kcm/blur_config.cpp +++ b/src/kcm/blur_config.cpp @@ -8,7 +8,7 @@ //#include // KConfigSkeleton -#include "blurconfig.h" +#include "config.h" #include #include "kwineffects_interface.h" @@ -19,11 +19,11 @@ namespace KWin K_PLUGIN_CLASS(BlurEffectConfig) BlurEffectConfig::BlurEffectConfig(QObject *parent, const KPluginMetaData &data) - : KCModule(parent, data) + : KCModule(parent, data) { ui.setupUi(widget()); - BlurConfig::instance("kwinrc"); - addConfig(BlurConfig::self(), widget()); + BetterBlur::Config::instance("kwinrc"); + addConfig(BetterBlur::Config::self(), widget()); } BlurEffectConfig::~BlurEffectConfig() @@ -43,5 +43,4 @@ void BlurEffectConfig::save() } // namespace KWin #include "blur_config.moc" - #include "moc_blur_config.cpp" diff --git a/src/kcm/blur_config.ui b/src/kcm/blur_config.ui index cfd50e11ab..5787866491 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -199,7 +199,8 @@ - Classes of windows to force blur (one per line): + Classes of windows to force blur (one per line). +Use the "$regex:" prefix to specify regular expressions. @@ -391,14 +392,14 @@ - Fake Blur + Static Blur - When enabled, the blur texture will be cached and reused, resulting in much lower GPU usage. -Works best with tiling. + When enabled, a cached texture will be painted behind the window instead of +actually blurring the background, resulting in much lower resource usage. Works best with tiling. @@ -425,7 +426,7 @@ Works best with tiling. - Use real blur for windows that are in front of other windows + Use dynamic blur for windows with another window behind @@ -451,7 +452,7 @@ Works best with tiling. - Blur image + Blur texture (only done once) @@ -460,14 +461,14 @@ Works best with tiling. - Image source: + Texture source: - Desktop wallpaper + Desktop wallpaper (do not use with frequently-changing wallpapers) @@ -561,6 +562,45 @@ Works best with tiling. + + + Window Rules + + + + + + There is currently no configuration UI for window rules. +See the configuration documentation for more information. + + + + + + + + 0 + 0 + + + + Convert simple config to window rules (if disabled, most options won't have an effect +anymore and will need to be manually specified in window rules) + + + + + + + + 0 + 1 + + + + + + diff --git a/src/settings.cpp b/src/settings.cpp deleted file mode 100644 index 88eb0db6d5..0000000000 --- a/src/settings.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "settings.h" -#include "blurconfig.h" - -namespace KWin -{ - -void BlurSettings::read() -{ - BlurConfig::self()->read(); - - general.blurStrength = BlurConfig::blurStrength() - 1; - general.noiseStrength = BlurConfig::noiseStrength(); - general.windowOpacityAffectsBlur = BlurConfig::transparentBlur(); - - forceBlur.windowClasses = BlurConfig::windowClasses().split("\n"); - forceBlur.windowClassMatchingMode = BlurConfig::blurMatching() ? WindowClassMatchingMode::Whitelist : WindowClassMatchingMode::Blacklist; - forceBlur.blurDecorations = BlurConfig::blurDecorations(); - forceBlur.blurMenus = BlurConfig::blurMenus(); - forceBlur.blurDocks = BlurConfig::blurDocks(); - forceBlur.markWindowAsTranslucent = BlurConfig::paintAsTranslucent(); - - roundedCorners.windowTopRadius = BlurConfig::topCornerRadius(); - roundedCorners.windowBottomRadius = BlurConfig::bottomCornerRadius(); - roundedCorners.menuRadius = BlurConfig::menuCornerRadius(); - roundedCorners.dockRadius = BlurConfig::dockCornerRadius(); - roundedCorners.antialiasing = BlurConfig::roundedCornersAntialiasing(); - roundedCorners.roundMaximized = BlurConfig::roundCornersOfMaximizedWindows(); - - fakeBlur.enable = BlurConfig::fakeBlur(); - fakeBlur.disableWhenWindowBehind = BlurConfig::fakeBlurDisableWhenWindowBehind(); - fakeBlur.customImage = QImage(BlurConfig::fakeBlurImage()); - if (BlurConfig::fakeBlurImageSourceDesktopWallpaper()) { - fakeBlur.imageSource = FakeBlurImageSource::DesktopWallpaper; - } else { - fakeBlur.imageSource = FakeBlurImageSource::Custom; - } - fakeBlur.blurCustomImage = BlurConfig::fakeBlurCustomImageBlur(); -} - -} \ No newline at end of file diff --git a/src/settings.h b/src/settings.h deleted file mode 100644 index aa72cba940..0000000000 --- a/src/settings.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#include - -namespace KWin -{ - -enum class FakeBlurImageSource -{ - Custom, - DesktopWallpaper -}; - -enum class WindowClassMatchingMode -{ - Blacklist, - Whitelist -}; - - -struct GeneralSettings -{ - int blurStrength; - int noiseStrength; - bool windowOpacityAffectsBlur; -}; - -struct ForceBlurSettings -{ - QStringList windowClasses; - WindowClassMatchingMode windowClassMatchingMode; - bool blurDecorations; - bool blurMenus; - bool blurDocks; - bool markWindowAsTranslucent; -}; - -struct RoundedCornersSettings -{ - float windowTopRadius; - float windowBottomRadius; - float menuRadius; - float dockRadius; - float antialiasing; - bool roundMaximized; -}; - -struct FakeBlurSettings -{ - bool enable; - bool disableWhenWindowBehind; - FakeBlurImageSource imageSource; - QImage customImage; - bool blurCustomImage; -}; - -class BlurSettings -{ -public: - GeneralSettings general{}; - ForceBlurSettings forceBlur{}; - RoundedCornersSettings roundedCorners{}; - FakeBlurSettings fakeBlur{}; - - void read(); -}; - -} diff --git a/src/utils.h b/src/utils.h index b6ca4375f6..b578dcf54a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,12 +1,9 @@ #pragma once -namespace KWin -{ +#include "effect/effectwindow.h" -inline bool isMenu(const EffectWindow *w) +namespace KWin { - return w->isMenu() || w->isDropdownMenu() || w->isPopupMenu() || w->isPopupWindow(); -} inline bool isDockFloating(const EffectWindow *dock, const QRegion blurRegion) { diff --git a/src/windowrules/windowproperties.cpp b/src/windowrules/windowproperties.cpp new file mode 100644 index 0000000000..e33a282f29 --- /dev/null +++ b/src/windowrules/windowproperties.cpp @@ -0,0 +1,46 @@ +#include "windowproperties.h" + +namespace BetterBlur +{ + +void WindowProperties::setWindowOpacityAffectsBlur(const bool &windowOpacityAffectsBlur) +{ + m_windowOpacityAffectsBlur = windowOpacityAffectsBlur; +}; + +void WindowProperties::setBlurContent(const bool &blurContent) +{ + m_blurContent = blurContent; +} + +void WindowProperties::setBlurDecorations(const bool &blurDecorations) +{ + m_blurDecorations = blurDecorations; +} + +void WindowProperties::setForceTransparency(const bool &forceTransparency) +{ + m_forceTransparency = forceTransparency; +} + +void WindowProperties::setBottomCornerRadius(const float &radius) +{ + m_bottomCornerRadius = radius; +} + +void WindowProperties::setTopCornerRadius(const float &radius) +{ + m_topCornerRadius = radius; +} + +void WindowProperties::setCornerAntialiasing(const float &antialiasing) +{ + m_cornerAntialiasing = antialiasing; +} + +void WindowProperties::setStaticBlur(const bool &staticBlur) +{ + m_staticBlur = staticBlur; +} + +} \ No newline at end of file diff --git a/src/windowrules/windowproperties.h b/src/windowrules/windowproperties.h new file mode 100644 index 0000000000..a92264b285 --- /dev/null +++ b/src/windowrules/windowproperties.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +namespace BetterBlur +{ + +class WindowProperties +{ + Q_GADGET + + Q_PROPERTY(bool blurContent READ blurContent WRITE setBlurContent) + Q_PROPERTY(bool blurDecorations READ blurDecorations WRITE setBlurDecorations) + Q_PROPERTY(bool forceTransparency READ forceTransparency WRITE setForceTransparency) + + Q_PROPERTY(float bottomCornerRadius READ bottomCornerRadius WRITE setBottomCornerRadius) + Q_PROPERTY(float topCornerRadius READ topCornerRadius WRITE setTopCornerRadius) + Q_PROPERTY(float cornerAntialiasing READ cornerAntialiasing WRITE setCornerAntialiasing) +public: + bool windowOpacityAffectsBlur() const + { + return m_windowOpacityAffectsBlur.value_or(false); + } + void setWindowOpacityAffectsBlur(const bool &windowOpacityAffectsBlur); + + bool blurContent() const + { + return m_blurContent.value_or(false); + }; + void setBlurContent(const bool &blurContent); + + bool blurDecorations() const + { + return m_blurDecorations.value_or(false); + }; + void setBlurDecorations(const bool &blurDecorations); + + bool forceTransparency() const + { + return m_forceTransparency.value_or(false); + }; + void setForceTransparency(const bool &forceTransparency); + + float bottomCornerRadius() const + { + return m_bottomCornerRadius.value_or(0); + }; + void setBottomCornerRadius(const float &radius); + + float topCornerRadius() const + { + return m_topCornerRadius.value_or(0); + }; + void setTopCornerRadius(const float &radius); + + float cornerAntialiasing() const + { + return m_cornerAntialiasing.value_or(0); + }; + void setCornerAntialiasing(const float &antialiasing); + + bool staticBlur() const + { + return m_staticBlur.value_or(false); + } + void setStaticBlur(const bool &staticBlur); + +private: + std::optional m_windowOpacityAffectsBlur; + + std::optional m_blurContent; + std::optional m_blurDecorations; + std::optional m_forceTransparency; + + std::optional m_bottomCornerRadius; + std::optional m_topCornerRadius; + std::optional m_cornerAntialiasing; + + std::optional m_staticBlur; + + friend class Window; +}; + +} \ No newline at end of file diff --git a/src/windowrules/windowrule.cpp b/src/windowrules/windowrule.cpp new file mode 100644 index 0000000000..3b1435fcc8 --- /dev/null +++ b/src/windowrules/windowrule.cpp @@ -0,0 +1,21 @@ +#include "windowrule.h" + +namespace BetterBlur +{ + +WindowRule::WindowRule(const int &priority) + : m_priority(priority) +{ +} + +#ifdef CONFIG_KWIN +bool WindowRule::matches(const Window *w) const +{ + return m_conditions.empty() + || std::find_if(m_conditions.cbegin(), m_conditions.cend(), [w](const std::unique_ptr &condition) { + return condition->isSatisfied(w); + }) != m_conditions.cend(); +} +#endif + +} \ No newline at end of file diff --git a/src/windowrules/windowrule.h b/src/windowrules/windowrule.h new file mode 100644 index 0000000000..1b78e03b47 --- /dev/null +++ b/src/windowrules/windowrule.h @@ -0,0 +1,49 @@ +#pragma once + +#include "windowrulecondition.h" +#include "windowproperties.h" + +#ifdef CONFIG_KWIN +#include "blurwindow.h" +#endif + +#include + +namespace BetterBlur +{ + +#ifdef CONFIG_KWIN +class Window; +#endif +class WindowRuleCondition; + +class WindowRule +{ +public: + explicit WindowRule(const int &priority); + +#ifdef CONFIG_KWIN + bool matches(const Window *w) const; +#endif + + const int &priority() const + { + return m_priority; + } + + std::vector> &conditions() + { + return m_conditions; + } + + WindowProperties *properties() + { + return m_properties.get(); + } +private: + const int m_priority; + std::unique_ptr m_properties = std::make_unique(); + std::vector> m_conditions; +}; + +} \ No newline at end of file diff --git a/src/windowrules/windowrulecondition.cpp b/src/windowrules/windowrulecondition.cpp new file mode 100644 index 0000000000..7282055803 --- /dev/null +++ b/src/windowrules/windowrulecondition.cpp @@ -0,0 +1,107 @@ +#include "windowrulecondition.h" + +#ifdef CONFIG_KWIN +#include "blur.h" +#include "window.h" +#endif + +namespace BetterBlur +{ + +#ifdef CONFIG_KWIN +bool WindowRuleCondition::isSatisfied(const Window *w) const +{ + return isHasWindowBehindSubConditionSatisfied(w) + && isWindowClassSubConditionSatisfied(w) + && isWindowStateSubConditionSatisfied(w) + && isWindowTypeSubConditionSatisfied(w); +} + +bool WindowRuleCondition::isHasWindowBehindSubConditionSatisfied(const Window *w) const +{ + if (!m_hasWindowBehind.has_value()) { + return true; + } + + return w->hasWindowBehind() == *m_hasWindowBehind; +} + +bool WindowRuleCondition::isWindowClassSubConditionSatisfied(const Window *w) const +{ + if (!m_windowClass) { + return true; + } + + return ((*m_windowClass).match(w->window()->window()->resourceClass()).hasMatch() + || (*m_windowClass).match(w->window()->window()->resourceName()).hasMatch()) + == !m_negateWindowClasses; +} + +bool WindowRuleCondition::isWindowStateSubConditionSatisfied(const Window *w) const +{ + if (m_windowStates == WindowState::All) { + return true; + } + + const auto effectWindow = w->window(); + const bool satisfied = + ((m_windowStates & WindowState::Fullscreen) && effectWindow->isFullScreen()) + || ((m_windowStates & WindowState::Maximized) && w->isMaximized()); + return m_negateWindowStates == !satisfied; +} + +bool WindowRuleCondition::isWindowTypeSubConditionSatisfied(const Window *w) const +{ + if (m_windowTypes == WindowType::All) + return true; + + const auto effectWindow = w->window(); + const bool satisfied = + ((m_windowTypes & WindowType::Normal) && effectWindow->isNormalWindow()) + || ((m_windowTypes & WindowType::Dock) && effectWindow->isDock()) + || ((m_windowTypes & WindowType::Toolbar) && effectWindow->isToolbar()) + || ((m_windowTypes & WindowType::Menu) + && (effectWindow->isMenu() || effectWindow->isDropdownMenu() || effectWindow->isPopupMenu() || effectWindow->isPopupWindow())) + || ((m_windowTypes & WindowType::Dialog) && effectWindow->isDialog()) + || ((m_windowTypes & WindowType::Utility) && effectWindow->isUtility()) + || ((m_windowTypes & WindowType::Tooltip) && effectWindow->isTooltip()); + return m_negateWindowTypes == !satisfied; +} +#endif + +void WindowRuleCondition::setNegateWindowClasses(const bool &negate) +{ + m_negateWindowClasses = negate; +} + +void WindowRuleCondition::setNegateWindowStates(const bool &negate) +{ + m_negateWindowStates = negate; +} + +void WindowRuleCondition::setNegateWindowTypes(const bool &negate) +{ + m_negateWindowTypes = negate; +} + +void WindowRuleCondition::setHasWindowBehind(const bool &hasWindowBehind) +{ + m_hasWindowBehind = hasWindowBehind; +} + +void WindowRuleCondition::setWindowClass(const QRegularExpression &windowClass) +{ + m_windowClass = windowClass; +} + +void WindowRuleCondition::setWindowStates(const WindowStates &windowStates) +{ + m_windowStates = windowStates; +} + +void WindowRuleCondition::setWindowTypes(const WindowTypes &windowTypes) +{ + m_windowTypes = windowTypes; +} + +} \ No newline at end of file diff --git a/src/windowrules/windowrulecondition.h b/src/windowrules/windowrulecondition.h new file mode 100644 index 0000000000..76276d4543 --- /dev/null +++ b/src/windowrules/windowrulecondition.h @@ -0,0 +1,105 @@ +#pragma once + +#ifdef CONFIG_KWIN +#include "blurwindow.h" +#endif + +#include + +#include + +namespace BetterBlur +{ + +#ifdef CONFIG_KWIN +class Window; +#endif + +enum class WindowClassMatchingMode +{ + Exact, + Substring, + Regex +}; + +enum class WindowState : uint32_t +{ + Maximized = 1u << 0, + Fullscreen = 1u << 1, + All = 0U - 1, +}; +Q_DECLARE_FLAGS(WindowStates, WindowState) +Q_DECLARE_OPERATORS_FOR_FLAGS(WindowStates) + +enum class WindowType : uint32_t +{ + Normal = 1u << 0, + Dock = 1u << 1, + Toolbar = 1u << 2, + Menu = 1u << 3, + Dialog = 1u << 4, + Utility = 1u << 5, + Tooltip = 1u << 6, + All = 0U - 1, +}; +Q_DECLARE_FLAGS(WindowTypes, WindowType) + +class WindowRuleCondition +{ + Q_GADGET + +public: +#ifdef CONFIG_KWIN + bool isSatisfied(const Window *w) const; +#endif + + void setNegateWindowClasses(const bool &negate); + void setNegateWindowStates(const bool &negate); + void setNegateWindowTypes(const bool &negate); + + void setHasWindowBehind(const bool &hasWindowBehind); + void setWindowClass(const QRegularExpression &windowClass); + void setWindowStates(const WindowStates &windowStates); + void setWindowTypes(const WindowTypes &windowTypes); + + std::optional &windowClass() + { + return m_windowClass; + } + + const WindowStates &windowStates() const + { + return m_windowStates; + } + + const WindowTypes &windowTypes() const + { + return m_windowTypes; + } +private: +#ifdef CONFIG_KWIN + bool isHasWindowBehindSubConditionSatisfied(const Window *w) const; + bool isWindowClassSubConditionSatisfied(const Window *w) const; + bool isWindowStateSubConditionSatisfied(const Window *w) const; + bool isWindowTypeSubConditionSatisfied(const Window *w) const; +#endif + + std::optional m_hasWindowBehind; + + std::optional m_windowClass; + bool m_negateWindowClasses = false; + + /** + * Ignored if set to WindowState::All. + */ + WindowStates m_windowStates = WindowState::All; + bool m_negateWindowStates = false; + + /** + * Ignored if set to WindowType::All. + */ + WindowTypes m_windowTypes = WindowType::All; + bool m_negateWindowTypes = false; +}; + +} diff --git a/src/windowrules/windowrulelist.cpp b/src/windowrules/windowrulelist.cpp new file mode 100644 index 0000000000..d2ad746583 --- /dev/null +++ b/src/windowrules/windowrulelist.cpp @@ -0,0 +1,224 @@ +#include "windowrulelist.h" + +#include "config.h" + +#include + +namespace BetterBlur +{ + +void WindowRuleList::load() +{ + m_windowRules.clear(); + + if (Config::convertSimpleConfigToRules()) { + loadSimple(); + } + + KConfig config("kwinbetterblurrc", KConfig::SimpleConfig); + const auto windowRulesGroup = config.group("WindowRules"); + for (const auto &windowRuleGroupName : windowRulesGroup.groupList()) { + const auto windowRuleGroup = windowRulesGroup.group(windowRuleGroupName); + + const auto priority = windowRuleGroup.readEntry("Priority", 0); + auto rule = std::make_unique(priority); + auto ruleProperties = rule->properties(); + + auto conditionsGroup = windowRuleGroup.group("Conditions"); + for (const auto &conditionGroupName : conditionsGroup.groupList()) { + const auto conditionGroup = conditionsGroup.group(conditionGroupName); + auto condition = std::make_unique(); + + for (const auto &subcondition : conditionGroup.readEntry("Negate", "").split(" ")) { + if (subcondition == "WindowClass") { + condition->setNegateWindowClasses(true); + } else if (subcondition == "WindowState") { + condition->setNegateWindowStates(true); + } else if (subcondition == "WindowType") { + condition->setNegateWindowTypes(true); + } + } + + if (conditionGroup.hasKey("HasWindowBehind")) { + condition->setHasWindowBehind(conditionGroup.readEntry("HasWindowBehind", false)); + } + + if (conditionGroup.hasKey("WindowClass")) { + condition->setWindowClass(QRegularExpression(conditionGroup.readEntry("WindowClass", ""))); + } + + if (conditionGroup.hasKey("WindowState")) { + WindowStates states; + for (const auto &stateStr : conditionGroup.readEntry("WindowState", "").split(" ")) { + if (stateStr == "Fullscreen") { + states |= WindowState::Fullscreen; + } else if (stateStr == "Maximized") { + states |= WindowState::Maximized; + } + } + condition->setWindowStates(states); + } + + if (conditionGroup.hasKey("WindowType")) { + WindowTypes types; + for (const auto &typeStr : conditionGroup.readEntry("WindowType", "").split(" ")) { + if (typeStr == "Dialog") { + types |= WindowType::Dialog; + } else if (typeStr == "Dock") { + types |= WindowType::Dock; + } else if (typeStr == "Normal") { + types |= WindowType::Normal; + } else if (typeStr == "Menu") { + types |= WindowType::Menu; + } else if (typeStr == "Toolbar") { + types |= WindowType::Toolbar; + } else if (typeStr == "Tooltip") { + types |= WindowType::Tooltip; + } else if (typeStr == "Utility") { + types |= WindowType::Utility; + } + } + condition->setWindowTypes(types); + } + + rule->conditions().push_back(std::move(condition)); + } + + auto propertiesGroup = windowRuleGroup.group("Properties"); + if (propertiesGroup.hasKey("BlurContent")) { + ruleProperties->setBlurContent(propertiesGroup.readEntry("BlurContent", false)); + } + if (propertiesGroup.hasKey("BlurDecorations")) { + ruleProperties->setBlurDecorations(propertiesGroup.readEntry("BlurDecorations", false)); + } + if (propertiesGroup.hasKey("CornerRadius")) { + float topCornerRadius = 0; + float bottomCornerRadius = 0; + + auto cornerRadius = propertiesGroup.readEntry("CornerRadius", ""); + if (cornerRadius.contains(" ")) { + topCornerRadius = cornerRadius.split(" ")[0].toFloat(); + bottomCornerRadius = cornerRadius.split(" ")[1].toFloat(); + } else { + topCornerRadius = bottomCornerRadius = cornerRadius.toFloat(); + } + + if (topCornerRadius >= 0) { + ruleProperties->setTopCornerRadius(topCornerRadius); + } + if (bottomCornerRadius >= 0) { + ruleProperties->setBottomCornerRadius(bottomCornerRadius); + } + } + if (propertiesGroup.hasKey("CornerAntialiasing")) { + ruleProperties->setCornerAntialiasing(propertiesGroup.readEntry("CornerAntialiasing", 0.0)); + } + if (propertiesGroup.hasKey("ForceTransparency")) { + ruleProperties->setForceTransparency(propertiesGroup.readEntry("ForceTransparency", false)); + } + if (propertiesGroup.hasKey("StaticBlur")) { + ruleProperties->setStaticBlur(propertiesGroup.readEntry("StaticBlur", false)); + } + if (propertiesGroup.hasKey("WindowOpacityAffectsBlur")) { + ruleProperties->setWindowOpacityAffectsBlur(propertiesGroup.readEntry("WindowOpacityAffectsBlur", false)); + } + + if (m_windowRules.empty()) { + m_windowRules.push_back(std::move(rule)); + continue; + } + auto it = std::lower_bound(m_windowRules.begin(), m_windowRules.end(), rule, [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->priority() < b->priority(); + }); + m_windowRules.insert(it, std::move(rule)); + } +} + +void WindowRuleList::loadSimple() +{ + m_windowRules.clear(); + + KConfig kwinrc("kwinrc", KConfig::SimpleConfig); + auto betterBlurV1Group = kwinrc.group("Effect-blurplus"); + + // Force blur + auto forceBlurRule = std::make_unique(-1); + forceBlurRule->properties()->setBlurContent(true); + forceBlurRule->properties()->setBlurDecorations(betterBlurV1Group.readEntry("BlurDecorations", false)); + forceBlurRule->properties()->setForceTransparency(betterBlurV1Group.readEntry("PaintAsTranslucent", false)); + auto forceBlurRuleCondition = std::make_unique(); + forceBlurRuleCondition->setNegateWindowClasses(!betterBlurV1Group.readEntry("BlurMatching", true)); + QStringList regexes; + for (const auto &windowClass : betterBlurV1Group.readEntry("WindowClasses", "").split("\n")) { + regexes << (windowClass.startsWith("$regex:") + ? windowClass.mid(7) + : "^" + QRegularExpression::escape(windowClass) + "$"); + } + if (!regexes.empty()) { + forceBlurRuleCondition->setWindowClass(QRegularExpression(regexes.join("|"))); + } + WindowTypes types = WindowType::Normal; + if (betterBlurV1Group.readEntry("BlurMenus", false)) { + types |= WindowType::Menu; + } + if (betterBlurV1Group.readEntry("BlurDocks", false)) { + types |= WindowType::Dock; + } + forceBlurRuleCondition->setWindowTypes(types); + forceBlurRule->conditions().push_back(std::move(forceBlurRuleCondition)); + m_windowRules.push_back(std::move(forceBlurRule)); + + auto cornerAntialiasingRule = std::make_unique(-1); + cornerAntialiasingRule->properties()->setCornerAntialiasing(betterBlurV1Group.readEntry("RoundedCornersAntialiasing", static_cast(0.0))); + m_windowRules.push_back(std::move(cornerAntialiasingRule)); + + auto windowCornerRadiusRule = std::make_unique(-1); + windowCornerRadiusRule->properties()->setTopCornerRadius(betterBlurV1Group.readEntry("TopCornerRadius", static_cast(0.0))); + windowCornerRadiusRule->properties()->setBottomCornerRadius(betterBlurV1Group.readEntry("BottomCornerRadius", static_cast(0.0))); + if (!betterBlurV1Group.readEntry("RoundCornersOfMaximizedWindows", false)) { + auto dontRoundMaximizedWindowsCondition = std::make_unique(); + dontRoundMaximizedWindowsCondition->setWindowStates(WindowState::Fullscreen | WindowState::Maximized); + dontRoundMaximizedWindowsCondition->setNegateWindowStates(true); + windowCornerRadiusRule->conditions().push_back(std::move(dontRoundMaximizedWindowsCondition)); + } + m_windowRules.push_back(std::move(windowCornerRadiusRule)); + + // Menu corner radius + auto roundedCornersRule3 = std::make_unique(-1); + auto radius = betterBlurV1Group.readEntry("MenuCornerRadius", static_cast(0.0)); + roundedCornersRule3->properties()->setTopCornerRadius(radius); + roundedCornersRule3->properties()->setBottomCornerRadius(radius); + auto roundedCornersRule3Condition = std::make_unique(); + roundedCornersRule3Condition->setWindowTypes(WindowType::Menu); + roundedCornersRule3->conditions().push_back(std::move(roundedCornersRule3Condition)); + m_windowRules.push_back(std::move(roundedCornersRule3)); + + // Dock corner radius + auto roundedCornersRule4 = std::make_unique(-1); + radius = betterBlurV1Group.readEntry("DockCornerRadius", static_cast(0.0)); + roundedCornersRule4->properties()->setTopCornerRadius(radius); + roundedCornersRule4->properties()->setBottomCornerRadius(radius); + auto roundedCornersRule4Condition = std::make_unique(); + roundedCornersRule4Condition->setWindowTypes(WindowType::Dock); + roundedCornersRule4->conditions().push_back(std::move(roundedCornersRule4Condition)); + m_windowRules.push_back(std::move(roundedCornersRule4)); + + if (betterBlurV1Group.readEntry("FakeBlur", false)) { + auto staticBlurRule = std::make_unique(-1); + staticBlurRule->properties()->setStaticBlur(true); + if (betterBlurV1Group.readEntry("FakeBlurDisableWhenWindowBehind", true)) { + auto condition = std::make_unique(); + condition->setHasWindowBehind(false); + staticBlurRule->conditions().push_back(std::move(condition)); + } + m_windowRules.push_back(std::move(staticBlurRule)); + } + + if (betterBlurV1Group.readEntry("TransparentBlur", true)) { + auto blurOpacityRule = std::make_unique(-1); + blurOpacityRule->properties()->setWindowOpacityAffectsBlur(true); + m_windowRules.push_back(std::move(blurOpacityRule)); + } +} + +} \ No newline at end of file diff --git a/src/windowrules/windowrulelist.h b/src/windowrules/windowrulelist.h new file mode 100644 index 0000000000..5c58b10ab1 --- /dev/null +++ b/src/windowrules/windowrulelist.h @@ -0,0 +1,32 @@ +#pragma once + +#include "windowrule.h" + +#include +#include + +namespace BetterBlur +{ + +class WindowRule; + +class WindowRuleList +{ +public: + void load(); + + std::vector> &rules() + { + return m_windowRules; + } + const std::vector> &rules() const + { + return m_windowRules; + } +private: + void loadSimple(); + + std::vector> m_windowRules; +}; + +} \ No newline at end of file