File indexing completed on 2024-05-05 05:35:24

0001 //////////////////////////////////////////////////////////////////////////////
0002 // oxygenblurhelper.cpp
0003 // handle regions passed to kwin for blurring
0004 // -------------------
0005 //
0006 // SPDX-FileCopyrightText: 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0007 //
0008 // Loosely inspired (and largely rewritten) from BeSpin style
0009 // SPDX-FileCopyrightText: 2007 Thomas Luebking <thomas.luebking@web.de>
0010 //
0011 // SPDX-License-Identifier: MIT
0012 //////////////////////////////////////////////////////////////////////////////
0013 
0014 #include "oxygenblurhelper.h"
0015 
0016 #include "oxygenstyleconfigdata.h"
0017 
0018 #include <QEvent>
0019 #include <QProgressBar>
0020 #include <QPushButton>
0021 #include <QVector>
0022 
0023 namespace Oxygen
0024 {
0025 //___________________________________________________________
0026 BlurHelper::BlurHelper(QObject *parent, StyleHelper &helper)
0027     : QObject(parent)
0028     , _helper(helper)
0029     , _enabled(false)
0030 {
0031 #if OXYGEN_HAVE_X11
0032 
0033     if (_helper.isX11()) {
0034         // create atom
0035         _blurAtom = _helper.createAtom(QStringLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"));
0036         _opaqueAtom = _helper.createAtom(QStringLiteral("_NET_WM_OPAQUE_REGION"));
0037     } else {
0038         _blurAtom = 0;
0039         _opaqueAtom = 0;
0040     }
0041 
0042 #endif
0043 }
0044 
0045 //___________________________________________________________
0046 void BlurHelper::registerWidget(QWidget *widget)
0047 {
0048     // check if already registered
0049     if (_widgets.contains(widget))
0050         return;
0051 
0052     // install event filter
0053     addEventFilter(widget);
0054 
0055     // add to widgets list
0056     _widgets.insert(widget);
0057 
0058     // cleanup on destruction
0059     connect(widget, SIGNAL(destroyed(QObject *)), SLOT(widgetDestroyed(QObject *)));
0060 
0061     if (enabled()) {
0062         // schedule shadow area repaint
0063         _pendingWidgets.insert(widget, widget);
0064         update();
0065     }
0066 }
0067 
0068 //___________________________________________________________
0069 void BlurHelper::unregisterWidget(QWidget *widget)
0070 {
0071     // remove from widgets
0072     if (!_widgets.remove(widget))
0073         return;
0074 
0075     // remove event filter
0076     widget->removeEventFilter(this);
0077 
0078     if (isTransparent(widget))
0079         clear(widget);
0080 }
0081 
0082 //___________________________________________________________
0083 bool BlurHelper::eventFilter(QObject *object, QEvent *event)
0084 {
0085     // do nothing if not enabled
0086     if (!enabled())
0087         return false;
0088 
0089     switch (event->type()) {
0090     case QEvent::Hide: {
0091         QWidget *widget(qobject_cast<QWidget *>(object));
0092         if (widget && isOpaque(widget) && isTransparent(widget->window())) {
0093             QWidget *window(widget->window());
0094             _pendingWidgets.insert(window, window);
0095             update();
0096         }
0097         break;
0098     }
0099 
0100     case QEvent::Show:
0101     case QEvent::Resize: {
0102         // cast to widget and check
0103         QWidget *widget(qobject_cast<QWidget *>(object));
0104         if (!widget)
0105             break;
0106         if (isTransparent(widget)) {
0107             _pendingWidgets.insert(widget, widget);
0108             update();
0109 
0110         } else if (isOpaque(widget)) {
0111             QWidget *window(widget->window());
0112             if (isTransparent(window)) {
0113                 _pendingWidgets.insert(window, window);
0114                 update();
0115             }
0116         }
0117 
0118         break;
0119     }
0120 
0121     default:
0122         break;
0123     }
0124 
0125     // never eat events
0126     return false;
0127 }
0128 
0129 //___________________________________________________________
0130 QRegion BlurHelper::blurRegion(QWidget *widget) const
0131 {
0132     if (!widget->isVisible())
0133         return QRegion();
0134 
0135     // get main region
0136     QRegion region;
0137     if (qobject_cast<const QDockWidget *>(widget) || qobject_cast<const QMenu *>(widget) || qobject_cast<const QToolBar *>(widget)
0138         || widget->inherits("QComboBoxPrivateContainer")) {
0139         region = _helper.roundedMask(widget->rect());
0140 
0141     } else
0142         region = widget->mask().isEmpty() ? widget->rect() : widget->mask();
0143 
0144     // trim blur region to remove unnecessary areas
0145     trimBlurRegion(widget, widget, region);
0146     return region;
0147 }
0148 
0149 //___________________________________________________________
0150 void BlurHelper::trimBlurRegion(QWidget *parent, QWidget *widget, QRegion &region) const
0151 {
0152     // loop over children
0153     const auto children = widget->children();
0154     for (QObject *childObject : children) {
0155         QWidget *child(qobject_cast<QWidget *>(childObject));
0156         if (!(child && child->isVisible()))
0157             continue;
0158 
0159         if (isOpaque(child)) {
0160             const QPoint offset(child->mapTo(parent, QPoint(0, 0)));
0161             if (child->mask().isEmpty()) {
0162                 const QRect rect(child->rect().translated(offset).adjusted(1, 1, -1, -1));
0163                 region -= rect;
0164 
0165             } else
0166                 region -= child->mask().translated(offset);
0167 
0168         } else {
0169             trimBlurRegion(parent, child, region);
0170         }
0171     }
0172 
0173     return;
0174 }
0175 
0176 //___________________________________________________________
0177 void BlurHelper::update(QWidget *widget) const
0178 {
0179 #if OXYGEN_HAVE_X11
0180     if (!_helper.isX11())
0181         return;
0182 
0183     /*
0184     directly from bespin code. Supposibly prevent playing with some 'pseudo-widgets'
0185     that have winId matching some other -random- window
0186     */
0187     if (!(widget->testAttribute(Qt::WA_WState_Created) || widget->internalWinId())) {
0188         return;
0189     }
0190 
0191     const QRegion blurRegion(this->blurRegion(widget));
0192     const QRegion opaqueRegion = QRegion(0, 0, widget->width(), widget->height()) - blurRegion;
0193     if (blurRegion.isEmpty()) {
0194         clear(widget);
0195 
0196     } else {
0197         QVector<quint32> data;
0198         for (const QRect &rect : blurRegion) {
0199             data << rect.x() << rect.y() << rect.width() << rect.height();
0200         }
0201 
0202         xcb_change_property(_helper.connection(), XCB_PROP_MODE_REPLACE, widget->winId(), _blurAtom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
0203 
0204         data.clear();
0205         for (const QRect &rect : opaqueRegion) {
0206             data << rect.x() << rect.y() << rect.width() << rect.height();
0207         }
0208 
0209         xcb_change_property(_helper.connection(), XCB_PROP_MODE_REPLACE, widget->winId(), _opaqueAtom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData());
0210         xcb_flush(_helper.connection());
0211     }
0212 
0213     // force update
0214     if (widget->isVisible()) {
0215         widget->update();
0216     }
0217 
0218 #else
0219 
0220     Q_UNUSED(widget)
0221 
0222 #endif
0223 }
0224 
0225 //___________________________________________________________
0226 void BlurHelper::clear(QWidget *widget) const
0227 {
0228 #if OXYGEN_HAVE_X11
0229     if (!_helper.isX11())
0230         return;
0231 
0232     xcb_delete_property(_helper.connection(), widget->winId(), _blurAtom);
0233     xcb_delete_property(_helper.connection(), widget->winId(), _opaqueAtom);
0234 #else
0235     Q_UNUSED(widget)
0236 #endif
0237 }
0238 
0239 //___________________________________________________________
0240 bool BlurHelper::isOpaque(const QWidget *widget) const
0241 {
0242     return (!widget->isWindow())
0243         && ((widget->autoFillBackground() && widget->palette().color(widget->backgroundRole()).alpha() == 0xff)
0244             || widget->testAttribute(Qt::WA_OpaquePaintEvent));
0245 }
0246 
0247 //___________________________________________________________
0248 bool BlurHelper::isTransparent(const QWidget *widget) const
0249 {
0250     return widget->isWindow() && widget->testAttribute(Qt::WA_TranslucentBackground) &&
0251 
0252         // widgets using qgraphicsview
0253         !(widget->graphicsProxyWidget() || widget->inherits("Plasma::Dialog")) &&
0254 
0255         // flags and special widgets
0256         (widget->testAttribute(Qt::WA_StyledBackground) || qobject_cast<const QMenu *>(widget) || qobject_cast<const QDockWidget *>(widget)
0257          || qobject_cast<const QToolBar *>(widget) || widget->windowType() == Qt::ToolTip)
0258         && _helper.hasAlphaChannel(widget);
0259 }
0260 }