diff --git a/docs/configuration.md b/docs/configuration.md index ef265d2dee..bbf8b9c844 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,42 +1,148 @@ -# 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. | -# Static 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. 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 static 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 static 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. | +| **Internal** | ``bool`` | Internal KWin windows. | +| **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. | +| **BlurStrength** | ``int (1 - 15)`` | | +| **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][Global][Properties] +BlurStrength = 9 +CornerAntialiasing = 1 +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 + + +# Reduce blur strength for windows on top of other windows and panels +[WindowRules][LowBlurStrength] +Priority = 1 + +[WindowRules][LowBlurStrength][Conditions][0] +HasWindowBehind = true + +[WindowRules][LowBlurStrength][Conditions][1] +WindowType = Dock + +[WindowRules][LowBlurStrength][Properties] +BlurStrength = 3 + + +[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..0be498cd7b 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1730200266, - "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", + "lastModified": 1735834308, + "narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", + "rev": "6df24922a1400241dae323af55f30e4318a6ca65", "type": "github" }, "original": { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e659aa2a5e..6a0e660a5d 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}) @@ -29,6 +33,8 @@ target_link_libraries(forceblur PRIVATE KF6::ConfigGui ) + +target_compile_definitions(forceblur PRIVATE CONFIG_KWIN) if (${KDecoration3_FOUND}) target_link_libraries(forceblur PRIVATE KDecoration3::KDecoration) else() diff --git a/src/blur.cpp b/src/blur.cpp index 8771028e94..1eed7166a4 100644 --- a/src/blur.cpp +++ b/src/blur.cpp @@ -8,12 +8,13 @@ #include "blur.h" // KConfigSkeleton -#include "blurconfig.h" +#include "config.h" #include "core/pixelgrid.h" #include "core/rendertarget.h" #include "core/renderviewport.h" #include "effect/effecthandler.h" +#include "internalwindow.h" #include "opengl/glutils.h" #include "opengl/glplatform.h" #include "utils.h" @@ -21,12 +22,7 @@ #include "wayland/blur.h" #include "wayland/display.h" #include "wayland/surface.h" - -#ifdef KWIN_6_2_OR_GREATER -#include "scene/decorationitem.h" -#include "scene/surfaceitem.h" -#include "scene/windowitem.h" -#endif +#include "kwin/window.h" #include #include @@ -41,12 +37,6 @@ #include #include -#ifdef KDECORATION3 -#include -#else -#include -#endif - #include Q_LOGGING_CATEGORY(KWIN_BLUR, "kwin_better_blur", QtWarningMsg) @@ -67,7 +57,8 @@ QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr; BlurEffect::BlurEffect() { - BlurConfig::instance(effects->config()); + m_windowRules = std::make_unique(); + BetterBlur::Config::instance(effects->config()); ensureResources(); m_downsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, @@ -234,141 +225,38 @@ 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; - m_expandSize = blurOffsets[m_iterationCount - 1].expandSize; + BetterBlur::Config::self()->read(); + m_windowRules->load(); + m_colorMatrix = colorMatrix(BetterBlur::Config::brightness(), BetterBlur::Config::saturation(), BetterBlur::Config::contrast()); + + m_noiseStrength = BetterBlur::Config::noiseStrength(); + m_staticBlurImage = QImage(BetterBlur::Config::fakeBlurImage()); m_staticBlurTextures.clear(); - m_colorMatrix = colorMatrix(m_settings.general.brightness, m_settings.general.saturation, m_settings.general.contrast); - 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))) { - // On X11, EffectWindow::contentsRect() includes GTK's client-side shadows, while on Wayland, it doesn't. - // The content region is translated by EffectWindow::contentsRect() in BlurEffect::blurRegion, causing the - // blur region to be off on X11. The frame region is not translated, so it is used instead. - const auto isX11WithCSD = w->isX11Client() && w->frameGeometry() != w->bufferGeometry(); - if (!isX11WithCSD) { - content = w->contentsRect().translated(-w->contentsRect().topLeft()).toRect(); - } - if (isX11WithCSD || (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; -#ifdef KWIN_6_2_OR_GREATER - data.windowEffect = ItemEffect(w->windowItem()); -#endif - } 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::hasStaticBlur(EffectWindow *w) -{ - if (!m_settings.staticBlur.enable) { - return false; - } - - if (m_settings.staticBlur.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); - } - }); - } + m_windows[w] = std::make_unique(w, m_windowRules.get(), &net_wm_blur_region); windowFrameGeometryChangedConnections[w] = connect(w, &EffectWindow::windowFrameGeometryChanged, this, [this,w]() { - if (!w) { - return; - } - - if (w->isDesktop() && !effects->waylandDisplay()) { - m_staticBlurTextures.erase(nullptr); - return; + if (w && w->isDesktop() && !effects->waylandDisplay()) { + m_staticBlurTextures.clear(); } - - 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) @@ -377,27 +265,18 @@ 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 = windowFrameGeometryChangedConnections.find(w); it != windowFrameGeometryChangedConnections.end()) { disconnect(*it); windowFrameGeometryChangedConnections.erase(it); } - if (auto it = std::find(m_allWindows.begin(), m_allWindows.end(), w); it != m_allWindows.end()) { - m_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.staticBlur.enable) { + if (m_staticBlurTextures.empty()) { return; } @@ -408,10 +287,10 @@ void BlurEffect::slotScreenAdded(KWin::Output *screen) 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); } } @@ -424,27 +303,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(), -#ifdef KDECORATION3 - &KDecoration3::Decoration::blurRegionChanged -#else - &KDecoration2::Decoration::blurRegionChanged -#endif - , this, [this, w]() { - updateBlurRegion(w); - }); -} - bool BlurEffect::eventFilter(QObject *watched, QEvent *event) { auto internal = qobject_cast(watched); @@ -452,7 +314,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(); } } } @@ -473,57 +335,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(); - } - - QRect decorationRect = w->decoration()->rect() -#ifdef KDECORATION3 - .toAlignedRect() -#endif - ; - QRegion decorationRegion = QRegion(decorationRect) - 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); @@ -533,26 +349,29 @@ 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()); + const auto &blurWindow = m_windows[w]; - bool staticBlur = hasStaticBlur(w) && m_staticBlurTextures.contains(m_currentScreen) && !blurArea.isEmpty(); - if (staticBlur) { - if (!m_settings.general.windowOpacityAffectsBlur) { - data.opaque += blurArea; + if (!w->isDesktop()) { + const auto hadWindowBehind = blurWindow->hasWindowBehind(); + blurWindow->setHasWindowBehind(m_windowGeometriesSum.intersects(w->frameGeometry().toRect())); + if (hadWindowBehind != blurWindow->hasWindowBehind()) { + data.paint += w->expandedGeometry().toRect(); } + } - 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 QRegion blurArea = blurWindow->blurRegion().translated(w->pos().toPoint()); + + bool staticBlur = blurWindow->properties()->staticBlur() + && !blurArea.isEmpty() + && m_staticBlurTextures.contains(m_currentScreen) + && m_staticBlurTextures[m_currentScreen].contains(blurWindow->properties()->blurStrength()); + 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); @@ -562,40 +381,14 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: } } - if (m_settings.staticBlur.enable) { - if (m_settings.staticBlur.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.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper && w->isDesktop() && w->frameGeometry() == data.paint.boundingRect()) { - m_staticBlurTextures.erase(m_currentScreen); - } + if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper() + && !m_staticBlurTextures.empty() + && w->isDesktop() + && w->frameGeometry() == data.paint.boundingRect()) { + m_staticBlurTextures.erase(m_currentScreen); } - if (m_settings.forceBlur.markWindowAsTranslucent && !staticBlur && shouldForceBlur(w)) { + if (!staticBlur && blurWindow->properties()->forceTransparency()) { data.setTranslucent(); } @@ -606,8 +399,10 @@ void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std:: if (data.opaque.intersects(m_currentBlur)) { // to blur an area partially we have to shrink the opaque area of a window QRegion newOpaque; + auto iterationCount = blurStrengthValues[blurWindow->properties()->blurStrength() - 1].iteration; + auto expandSize = blurOffsets[iterationCount - 1].expandSize; for (const QRect &rect : data.opaque) { - newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize); + newOpaque += rect.adjusted(expandSize, expandSize, -expandSize, -expandSize); } data.opaque = newOpaque; @@ -637,67 +432,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; + if (!w->isDesktop() && w->window()->resourceClass() != "xwaylandvideobridge") { + m_windowGeometriesSum += w->frameGeometry().toRect(); } - - 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); - } - - 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->properties()->blurStrength() - 1, 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::ensureStaticBlurTexture(const Output *output, const RenderTarget &renderTarget) +GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const int &strength, const RenderTarget &renderTarget) { - if (m_staticBlurTextures.contains(output)) { - return m_staticBlurTextures[output].get(); + if (m_staticBlurTextures.contains(output) && m_staticBlurTextures[output].contains(strength)) { + return m_staticBlurTextures[output][strength].get(); } if (effects->waylandDisplay() && !output) { @@ -709,23 +463,23 @@ GLTexture *BlurEffect::ensureStaticBlurTexture(const Output *output, const Rende textureFormat = renderTarget.texture()->internalFormat(); } GLTexture *texture = effects->waylandDisplay() - ? createStaticBlurTextureWayland(output, renderTarget, textureFormat) - : createStaticBlurTextureX11(textureFormat); + ? createStaticBlurTextureWayland(output, strength, renderTarget, textureFormat) + : createStaticBlurTextureX11(strength, textureFormat); if (!texture) { return nullptr; } - return (m_staticBlurTextures[output] = std::unique_ptr(texture)).get(); + return (m_staticBlurTextures[output][strength] = 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()); @@ -735,7 +489,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; } } @@ -748,16 +502,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, const int &strength, 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; @@ -775,8 +531,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; @@ -803,20 +559,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 @@ -829,22 +576,25 @@ 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 *staticBlurTexture = nullptr; - if (w && hasStaticBlur(w)) { - staticBlurTexture = ensureStaticBlurTexture(m_currentScreen, renderTarget); + if (w && w->properties()->staticBlur()) { + staticBlurTexture = ensureStaticBlurTexture(m_currentScreen, strength, renderTarget); if (staticBlurTexture) { renderInfo.textures.clear(); renderInfo.framebuffers.clear(); } } + size_t iterationCount = blurStrengthValues[strength].iteration; + auto offset = blurStrengthValues[strength].offset; + if (!staticBlurTexture - && (renderInfo.framebuffers.size() != (m_iterationCount + 1) + && (renderInfo.framebuffers.size() != (iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat)) { renderInfo.framebuffers.clear(); renderInfo.textures.clear(); - for (size_t i = 0; i <= m_iterationCount; ++i) { + for (size_t i = 0; i <= iterationCount; ++i) { auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i)); if (!texture) { qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture"; @@ -994,7 +744,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg 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)); staticBlurTexture->bind(); @@ -1015,7 +765,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix); - m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset)); + m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, offset); m_downsamplePass.shader->setUniform(m_downsamplePass.colorMatrixLocation, m_colorMatrix); m_downsamplePass.shader->setUniform(m_downsamplePass.transformColorsLocation, true); @@ -1050,7 +800,7 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg m_upsamplePass.shader->setUniform(m_upsamplePass.bottomCornerRadiusLocation, static_cast(0)); m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); m_upsamplePass.shader->setUniform(m_upsamplePass.noiseLocation, false); - m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset)); + m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, offset); for (size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) { GLFramebuffer::popFramebuffer(); @@ -1069,7 +819,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 (auto *noiseTexture = ensureNoiseTexture()) { m_upsamplePass.shader->setUniform(m_upsamplePass.noiseLocation, true); m_upsamplePass.shader->setUniform(m_upsamplePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); @@ -1086,7 +836,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, static_cast(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)); @@ -1110,18 +860,18 @@ void BlurEffect::blur(BlurRenderData &renderInfo, const RenderTarget &renderTarg vbo->unbindArrays(); } -void BlurEffect::blur(GLTexture *texture) +void BlurEffect::blur(GLTexture *texture, const int &strength) { 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, strength, renderInfo, renderTarget, renderViewport, 0, textureRect, data); GLFramebuffer::popFramebuffer(); } @@ -1157,7 +907,7 @@ GLTexture *BlurEffect::wallpaper(EffectWindow *desktop, const qreal &scale, cons return texture.release(); } -GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat) +GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, const int &strength, const RenderTarget &renderTarget, const GLenum &textureFormat) { EffectWindow *desktop = nullptr; for (EffectWindow *w : effects->stackingOrder()) { @@ -1171,10 +921,10 @@ GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, cons } std::unique_ptr texture; - if (m_settings.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper) { + if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) { texture.reset(wallpaper(desktop, output->scale(), textureFormat)); - } else if (m_settings.staticBlur.imageSource == StaticBlurImageSource::Custom) { - texture = GLTexture::upload(m_settings.staticBlur.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; @@ -1209,14 +959,14 @@ GLTexture *BlurEffect::createStaticBlurTextureWayland(const Output *output, cons GLFramebuffer::popFramebuffer(); ShaderManager::instance()->popShader(); - if (m_settings.staticBlur.blurCustomImage) { - blur(texture.get()); + if (BetterBlur::Config::fakeBlurCustomImageBlur()) { + blur(texture.get(), strength); } return texture.release(); } -GLTexture *BlurEffect::createStaticBlurTextureX11(const GLenum &textureFormat) +GLTexture *BlurEffect::createStaticBlurTextureX11(const int &strength, const GLenum &textureFormat) { std::vector desktops; QRegion desktopGeometries; @@ -1243,17 +993,17 @@ GLTexture *BlurEffect::createStaticBlurTextureX11(const GLenum &textureFormat) const auto geometry = desktop->frameGeometry(); std::unique_ptr texture; - if (m_settings.staticBlur.imageSource == StaticBlurImageSource::DesktopWallpaper) { + if (BetterBlur::Config::fakeBlurImageSourceDesktopWallpaper()) { texture.reset(wallpaper(desktop, 1, textureFormat)); - } else if (m_settings.staticBlur.imageSource == StaticBlurImageSource::Custom) { - texture = GLTexture::upload(m_settings.staticBlur.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.staticBlur.blurCustomImage) { - blur(texture.get()); + if (BetterBlur::Config::fakeBlurCustomImageBlur()) { + blur(texture.get(), strength); } QMatrix4x4 projectionMatrix; diff --git a/src/blur.h b/src/blur.h index bc32511794..c9b96faa33 100644 --- a/src/blur.h +++ b/src/blur.h @@ -7,51 +7,21 @@ #pragma once +#include "blurwindow.h" +#include "windowrules/windowrulelist.h" + #include "effect/effect.h" #include "opengl/glutils.h" -#ifdef KWIN_6_2_OR_GREATER -#include "scene/item.h" -#endif - -#include "settings.h" -#include "window.h" #include #include - 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; - -#ifdef KWIN_6_2_OR_GREATER - ItemEffect windowEffect; -#endif - - bool hasWindowBehind; -}; - class BlurEffect : public KWin::Effect { Q_OBJECT @@ -86,31 +56,23 @@ 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 hasStaticBlur(EffectWindow *w); QMatrix4x4 colorMatrix(const float &brightness, const float &saturation, const float &contrast) const; /* * @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(GLTexture *texture); + void blur(BetterBlur::Window *w, const int &strength, BetterBlur::BlurRenderData &renderInfo, const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, WindowPaintData &data); + void blur(GLTexture *texture, const int &strength); /** * @param output Can be nullptr. * @remark This method shall not be called outside of BlurEffect::blur. * @return The cached static blur texture. The texture will be created if it doesn't exist. */ - GLTexture *ensureStaticBlurTexture(const Output *output, const RenderTarget &renderTarget); + GLTexture *ensureStaticBlurTexture(const Output *output, const int &strength, const RenderTarget &renderTarget); GLTexture *ensureNoiseTexture(); /** @@ -125,13 +87,13 @@ public Q_SLOTS: * @remark This method shall not be called outside of BlurEffect::blur. * @return A pointer to the texture, or nullptr if an error occurred. */ - GLTexture *createStaticBlurTextureWayland(const Output *output, const RenderTarget &renderTarget, const GLenum &textureFormat); + GLTexture *createStaticBlurTextureWayland(const Output *output, const int &strength, const RenderTarget &renderTarget, const GLenum &textureFormat); /** * Creates a composite static blur texture containing images for all screens. * @return A pointer to the texture, or nullptr if an error occurred. */ - GLTexture *createStaticBlurTextureX11(const GLenum &textureFormat); + GLTexture *createStaticBlurTextureX11(const int &strength, const GLenum &textureFormat); private: struct @@ -183,17 +145,14 @@ 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; @@ -211,26 +170,16 @@ public Q_SLOTS: QList blurStrengthValues; - std::unordered_map> m_staticBlurTextures; - - // Windows to blur even when transformed. - QList m_blurWhenTransformed; - + std::unordered_map>> m_staticBlurTextures; + QImage m_staticBlurImage; QMatrix4x4 m_colorMatrix; - QMap windowBlurChangedConnections; QMap windowFrameGeometryChangedConnections; + QMap screenChangedConnections; - std::unordered_map m_windows; + std::unordered_map> m_windows; - /** - * Stores all currently open windows, even those that aren't blurred. Used for determining whether windows are - * overlapping. - * - * 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; + std::unique_ptr m_windowRules; 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..500049ee04 --- /dev/null +++ b/src/blurwindow.cpp @@ -0,0 +1,290 @@ +#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" + +#ifdef KWIN_6_2_OR_GREATER +#include "scene/windowitem.h" +#endif + +#ifdef KDECORATION3 +#include +#else +#include +#endif + +namespace BetterBlur +{ + +Window::Window(KWin::EffectWindow *w, const WindowRuleList *windowRules, long *net_wm_blur_region) + : w(w), + m_windowRules(windowRules), + net_wm_blur_region(net_wm_blur_region) +{ + connect(w, &KWin::EffectWindow::windowDecorationChanged, this, [this]() { + setupDecorationConnections(); + }); + windowFrameGeometryChangedConnection = connect(w, &KWin::EffectWindow::windowFrameGeometryChanged, this, &Window::slotWindowFrameGeometryChanged); + setupDecorationConnections(); + + if (auto surf = w->surface()) { + windowBlurChangedConnection = connect(surf, &KWin::SurfaceInterface::blurChanged, [this]() { + updateBlurRegion(); + }); + } + + updateProperties(); + updateBlurRegion(); +} + +Window::~Window() +{ + disconnect(windowBlurChangedConnection); + disconnect(windowFrameGeometryChangedConnection); +} + +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))) { + const auto blurContent = m_properties->blurContent(); + const auto blurFrame = m_properties->blurDecorations(); + + // On X11, EffectWindow::contentsRect() includes GTK's client-side shadows, while on Wayland, it doesn't. + // The content region is translated by EffectWindow::contentsRect() in BlurEffect::blurRegion, causing the + // blur region to be off on X11. The frame region is not translated, so it is used instead. + const auto isX11WithCSD = w->isX11Client() && w->frameGeometry() != w->bufferGeometry(); + if (!isX11WithCSD && blurContent) { + content = w->contentsRect().translated(-w->contentsRect().topLeft()).toRect(); + } + if ((isX11WithCSD && (blurContent || blurFrame)) || (blurFrame && w->decoration())) { + const auto frameRect = w->frameGeometry().translated(-w->x(), -w->y()).toRect(); + frame = isX11WithCSD + ? frameRect + : QRegion(frameRect) - w->contentsRect().toRect(); + } + } + + if (content.has_value() || frame.has_value()) { + m_contentBlurRegion = content; + m_frameBlurRegion = frame; +#ifdef KWIN_6_2_OR_GREATER + m_windowEffect = std::make_unique(w->windowItem()); +#endif + } else if (!geometryChanged) { // Blur may disappear if this method is called when window geometry changes + m_contentBlurRegion = {}; + m_frameBlurRegion = {}; + render.clear(); +#ifdef KWIN_6_2_OR_GREATER + m_windowEffect.reset(); +#endif + } +} + +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 {}; + } + + QRect decorationRect = w->decoration()->rect() +#ifdef KDECORATION3 + .toAlignedRect() +#endif + ; + QRegion decorationRegion = QRegion(decorationRect) - 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::slotWindowFrameGeometryChanged() +{ + updateBlurRegion(true); +} + +void Window::setupDecorationConnections() +{ + if (!w->decoration()) { + return; + } + + connect(w->decoration(), +#ifdef KDECORATION3 + &KDecoration3::Decoration::blurRegionChanged +#else + &KDecoration2::Decoration::blurRegionChanged +#endif + , this, [this]() { + updateBlurRegion(w); + }); +} + +void Window::updateProperties() +{ + // TODO Rule evaluation needs to be optimized. Sometimes it's not necessary to evaluate a rule. + + auto staticBlur = false; + uint8_t blurStrength = 0; + if (m_properties) { + staticBlur = m_properties->staticBlur(); + blurStrength = m_properties->blurStrength(); + } + + 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_blurStrength) { + m_properties->setBlurStrength(*ruleProperties->m_blurStrength); + } + 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() || blurStrength != m_properties->blurStrength()) { + 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..0403fcc475 --- /dev/null +++ b/src/blurwindow.h @@ -0,0 +1,90 @@ +#pragma once + +#include "windowrules/windowproperties.h" +#include "windowrules/windowrulelist.h" + +#include "effect/effect.h" +#include "effect/effectwindow.h" +#include "opengl/glutils.h" + +#ifdef KWIN_6_2_OR_GREATER +#include "scene/item.h" +#endif + +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(KWin::EffectWindow *w, const WindowRuleList *windowRules, 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 slotWindowFrameGeometryChanged(); +// void slotDecorationBlurRegionChanged(); + +private: + void setupDecorationConnections(); + + QRegion decorationBlurRegion() const; + bool decorationSupportsBlurBehind() const; + + QMetaObject::Connection windowBlurChangedConnection; + QMetaObject::Connection windowFrameGeometryChangedConnection; + + std::optional m_contentBlurRegion; + std::optional m_frameBlurRegion; + bool m_blurWhenTransformed = false; + +#ifdef KWIN_6_2_OR_GREATER + std::unique_ptr m_windowEffect; +#endif + + bool m_hasWindowBehind = false; + std::unique_ptr m_properties; + + KWin::EffectWindow *w; + const WindowRuleList *m_windowRules; + long *net_wm_blur_region; +}; + +} \ 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 9d62fa9d4f..659e542737 100644 --- a/src/blur.kcfg +++ b/src/config/config.kcfg @@ -17,7 +17,7 @@ class1 class2 -class3 +$regex:class3 true @@ -73,6 +73,9 @@ class3 true + + true + 1.0 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 26ff095174..aaa32e3aae 100644 --- a/src/kcm/CMakeLists.txt +++ b/src/kcm/CMakeLists.txt @@ -5,7 +5,7 @@ set(kwin_better_blur_config_SRCS ) 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 c4c00a634b..70ef55a59b 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()); QFile about(":/effects/forceblur/kcm/about.html"); if (about.open(QIODevice::ReadOnly)) { @@ -51,5 +51,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 502e0294f7..e6e10728f8 100644 --- a/src/kcm/blur_config.ui +++ b/src/kcm/blur_config.ui @@ -275,7 +275,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. @@ -527,7 +528,7 @@ the background, resulting in much lower GPU usage. - Blur image + Blur texture (only done once) @@ -536,14 +537,14 @@ the background, resulting in much lower GPU usage. - Image source: + Texture source: - Desktop wallpaper + Desktop wallpaper (do not use with frequently-changing wallpapers) @@ -637,6 +638,51 @@ the background, resulting in much lower GPU usage. + + + Window Rules + + + + + + Window rules allow for doing things that aren't possible +with this UI, such as reducing blur strength for certain windows. + +By default, most settings here are automatically converted into internal +window rules. This can be disabled below. + +There is currently no configuration UI for window rules. The file is located +at ~/.config/kwinbetterblurrc. See the configuration documentation for more +information. + + + + + + + + 0 + 0 + + + + Convert simple config to window rules + + + + + + + + 0 + 1 + + + + + + About diff --git a/src/settings.cpp b/src/settings.cpp deleted file mode 100644 index 5cbfda97de..0000000000 --- a/src/settings.cpp +++ /dev/null @@ -1,43 +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(); - general.brightness = BlurConfig::brightness(); - general.saturation = BlurConfig::saturation(); - general.contrast = BlurConfig::contrast(); - - 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(); - - staticBlur.enable = BlurConfig::fakeBlur(); - staticBlur.disableWhenWindowBehind = BlurConfig::fakeBlurDisableWhenWindowBehind(); - staticBlur.customImage = QImage(BlurConfig::fakeBlurImage()); - if (BlurConfig::fakeBlurImageSourceDesktopWallpaper()) { - staticBlur.imageSource = StaticBlurImageSource::DesktopWallpaper; - } else { - staticBlur.imageSource = StaticBlurImageSource::Custom; - } - staticBlur.blurCustomImage = BlurConfig::fakeBlurCustomImageBlur(); -} - -} \ No newline at end of file diff --git a/src/settings.h b/src/settings.h deleted file mode 100644 index fa047780e7..0000000000 --- a/src/settings.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include -#include - -namespace KWin -{ - -enum class StaticBlurImageSource -{ - Custom, - DesktopWallpaper -}; - -enum class WindowClassMatchingMode -{ - Blacklist, - Whitelist -}; - - -struct GeneralSettings -{ - int blurStrength; - int noiseStrength; - bool windowOpacityAffectsBlur; - float brightness; - float saturation; - float contrast; -}; - -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 StaticBlurSettings -{ - bool enable; - bool disableWhenWindowBehind; - StaticBlurImageSource imageSource; - QImage customImage; - bool blurCustomImage; -}; - -class BlurSettings -{ -public: - GeneralSettings general{}; - ForceBlurSettings forceBlur{}; - RoundedCornersSettings roundedCorners{}; - StaticBlurSettings staticBlur{}; - - 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..52b11e2e60 --- /dev/null +++ b/src/windowrules/windowproperties.cpp @@ -0,0 +1,51 @@ +#include "windowproperties.h" + +namespace BetterBlur +{ + +void WindowProperties::setBlurStrength(const uint8_t &blurStrength) +{ + m_blurStrength = blurStrength; +} + +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..8db72fbc1e --- /dev/null +++ b/src/windowrules/windowproperties.h @@ -0,0 +1,93 @@ +#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: + uint8_t blurStrength() const + { + return m_blurStrength.value_or(1); + } + void setBlurStrength(const uint8_t &blurStrength); + + 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_blurStrength; + 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..c93d7438e6 --- /dev/null +++ b/src/windowrules/windowrulecondition.cpp @@ -0,0 +1,124 @@ +#include "windowrulecondition.h" + +#ifdef CONFIG_KWIN +#include "blur.h" + +#include "internalwindow.h" +#include "window.h" +#endif + +namespace BetterBlur +{ + +#ifdef CONFIG_KWIN +bool WindowRuleCondition::isSatisfied(const Window *w) const +{ + return isHasWindowBehindSubConditionSatisfied(w) + && isInternalSubConditionSatisfied(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::isInternalSubConditionSatisfied(const Window *w) const +{ + if (!m_internal.has_value()) { + return true; + } + + return static_cast(qobject_cast(w->window()->window())) == *m_internal; +} + +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::setInternal(const bool &internal) +{ + m_internal = internal; +} + +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..878cb180e2 --- /dev/null +++ b/src/windowrules/windowrulecondition.h @@ -0,0 +1,107 @@ +#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 setInternal(const bool &internal); + 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 isInternalSubConditionSatisfied(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_internal; + + 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..8902ee7c89 --- /dev/null +++ b/src/windowrules/windowrulelist.cpp @@ -0,0 +1,235 @@ +#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("Internal")) { + condition->setInternal(conditionGroup.readEntry("Internal", 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("BlurStrength")) { + ruleProperties->setBlurStrength(propertiesGroup.readEntry("BlurStrength", 15)); + } + 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"); + + auto blurStrengthRule = std::make_unique(-1); + blurStrengthRule->properties()->setBlurStrength(betterBlurV1Group.readEntry("BlurStrength", 15)); + m_windowRules.push_back(std::move(blurStrengthRule)); + + // 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 = static_cast(WindowType::Normal) | WindowType::Dialog; + 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