File indexing completed on 2024-05-12 04:00:25

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "windoweffects.h"
0009 
0010 #include <QDebug>
0011 #include <QExposeEvent>
0012 #include <QGuiApplication>
0013 
0014 #include <private/qwaylandwindow_p.h>
0015 
0016 #include <QWaylandClientExtensionTemplate>
0017 #include <qwaylandclientextension.h>
0018 
0019 #include "qwayland-blur.h"
0020 #include "qwayland-contrast.h"
0021 #include "qwayland-slide.h"
0022 
0023 #include "surfacehelper.h"
0024 
0025 static wl_region *createRegion(const QRegion &region)
0026 {
0027     QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
0028     if (!native) {
0029         return nullptr;
0030     }
0031     auto compositor = reinterpret_cast<wl_compositor *>(native->nativeResourceForIntegration(QByteArrayLiteral("compositor")));
0032     if (!compositor) {
0033         return nullptr;
0034     }
0035     auto wl_region = wl_compositor_create_region(compositor);
0036     for (const auto &rect : region) {
0037         wl_region_add(wl_region, rect.x(), rect.y(), rect.width(), rect.height());
0038     }
0039     return wl_region;
0040 }
0041 
0042 class BlurManager : public QWaylandClientExtensionTemplate<BlurManager>, public QtWayland::org_kde_kwin_blur_manager
0043 {
0044 public:
0045     BlurManager()
0046         : QWaylandClientExtensionTemplate<BlurManager>(1)
0047     {
0048     }
0049 };
0050 
0051 class Blur : public QObject, public QtWayland::org_kde_kwin_blur
0052 {
0053 public:
0054     Blur(struct ::org_kde_kwin_blur *object, QObject *parent)
0055         : QObject(parent)
0056         , QtWayland::org_kde_kwin_blur(object)
0057     {
0058     }
0059 
0060     ~Blur() override
0061     {
0062         release();
0063     }
0064 };
0065 
0066 class ContrastManager : public QWaylandClientExtensionTemplate<ContrastManager>, public QtWayland::org_kde_kwin_contrast_manager
0067 {
0068 public:
0069     ContrastManager()
0070         : QWaylandClientExtensionTemplate<ContrastManager>(2)
0071     {
0072     }
0073 };
0074 
0075 class Contrast : public QObject, public QtWayland::org_kde_kwin_contrast
0076 {
0077 public:
0078     Contrast(struct ::org_kde_kwin_contrast *object, QObject *parent)
0079         : QObject(parent)
0080         , QtWayland::org_kde_kwin_contrast(object)
0081     {
0082     }
0083 
0084     ~Contrast() override
0085     {
0086         release();
0087     }
0088 };
0089 
0090 class SlideManager : public QWaylandClientExtensionTemplate<SlideManager>, public QtWayland::org_kde_kwin_slide_manager
0091 {
0092 public:
0093     SlideManager()
0094         : QWaylandClientExtensionTemplate<SlideManager>(1)
0095     {
0096     }
0097 };
0098 
0099 class Slide : public QObject, public QtWayland::org_kde_kwin_slide
0100 {
0101 public:
0102     Slide(struct ::org_kde_kwin_slide *object, QObject *parent)
0103         : QObject(parent)
0104         , QtWayland::org_kde_kwin_slide(object)
0105     {
0106     }
0107 
0108     ~Slide() override
0109     {
0110         release();
0111     }
0112 };
0113 
0114 WindowEffects::WindowEffects()
0115     : QObject()
0116     , KWindowEffectsPrivate()
0117 {
0118     m_blurManager = new BlurManager();
0119     m_contrastManager = new ContrastManager();
0120     m_slideManager = new SlideManager();
0121 
0122     // The KWindowEffects API doesn't provide any signals to notify that the particular
0123     // effect has become unavailable. So we re-install effects when the corresponding globals
0124     // are added.
0125 
0126     connect(m_blurManager, &BlurManager::activeChanged, this, [this] {
0127         for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
0128             installBlur(it.key(), m_blurManager->isActive(), *it);
0129         }
0130     });
0131 
0132     connect(m_contrastManager, &ContrastManager::activeChanged, this, [this] {
0133         for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
0134             if (m_contrastManager->isActive()) {
0135                 installContrast(it.key(), true, it->contrast, it->intensity, it->saturation, it->region);
0136             } else {
0137                 installContrast(it.key(), false);
0138             }
0139         }
0140     });
0141 
0142     connect(m_slideManager, &SlideManager::activeChanged, this, [this] {
0143         for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
0144             if (m_slideManager->isActive()) {
0145                 installSlide(it.key(), it->location, it->offset);
0146             } else {
0147                 installSlide(it.key(), KWindowEffects::SlideFromLocation::NoEdge, 0);
0148             }
0149         }
0150     });
0151 }
0152 
0153 WindowEffects::~WindowEffects()
0154 {
0155     delete m_blurManager;
0156     delete m_contrastManager;
0157     delete m_slideManager;
0158 }
0159 
0160 void WindowEffects::trackWindow(QWindow *window)
0161 {
0162     if (!m_windowWatchers.contains(window)) {
0163         window->installEventFilter(this);
0164         auto conn = connect(window, &QObject::destroyed, this, [this, window]() {
0165             resetBlur(window);
0166             m_blurRegions.remove(window);
0167             resetContrast(window);
0168             m_backgroundConstrastRegions.remove(window);
0169             m_slideMap.remove(window);
0170             m_windowWatchers.remove(window);
0171         });
0172         m_windowWatchers[window] << conn;
0173         auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0174         if (waylandWindow) {
0175             auto conn = connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceDestroyed, this, [this, window]() {
0176                 resetBlur(window);
0177                 resetContrast(window);
0178             });
0179             m_windowWatchers[window] << conn;
0180         }
0181     }
0182 }
0183 
0184 void WindowEffects::releaseWindow(QWindow *window)
0185 {
0186     if (!m_blurRegions.contains(window) && !m_backgroundConstrastRegions.contains(window) && !m_slideMap.contains(window)) {
0187         for (const auto &conn : m_windowWatchers[window]) {
0188             disconnect(conn);
0189         }
0190         window->removeEventFilter(this);
0191         m_windowWatchers.remove(window);
0192     }
0193 }
0194 
0195 // Helper function to replace a QObject value in the map and delete the old one.
0196 template<typename MapType>
0197 void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value)
0198 {
0199     if (auto oldValue = map.take(key)) {
0200         oldValue->deleteLater();
0201     }
0202     if (value) {
0203         map[key] = value;
0204     }
0205 }
0206 
0207 void WindowEffects::resetBlur(QWindow *window, Blur *blur)
0208 {
0209     replaceValue(m_blurs, window, blur);
0210 }
0211 
0212 void WindowEffects::resetContrast(QWindow *window, Contrast *contrast)
0213 {
0214     replaceValue(m_contrasts, window, contrast);
0215 }
0216 
0217 bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
0218 {
0219     if (event->type() == QEvent::Expose) {
0220         auto window = qobject_cast<QWindow *>(watched);
0221         if (!window || !window->isExposed()) {
0222             return false;
0223         }
0224 
0225         {
0226             auto it = m_blurRegions.constFind(window);
0227             if (it != m_blurRegions.constEnd()) {
0228                 installBlur(window, true, *it);
0229             }
0230         }
0231         {
0232             auto it = m_backgroundConstrastRegions.constFind(window);
0233             if (it != m_backgroundConstrastRegions.constEnd()) {
0234                 installContrast(window, true, it->contrast, it->intensity, it->saturation, it->region);
0235             }
0236         }
0237         {
0238             auto it = m_slideMap.constFind(window);
0239             if (it != m_slideMap.constEnd()) {
0240                 installSlide(window, it->location, it->offset);
0241             }
0242         }
0243     }
0244     return false;
0245 }
0246 
0247 bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
0248 {
0249     switch (effect) {
0250     case KWindowEffects::BackgroundContrast:
0251         return m_contrastManager->isActive();
0252     case KWindowEffects::BlurBehind:
0253         return m_blurManager->isActive();
0254     case KWindowEffects::Slide:
0255         return m_slideManager->isActive();
0256     default:
0257         return false;
0258     }
0259 }
0260 
0261 void WindowEffects::slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
0262 {
0263     if (location != KWindowEffects::SlideFromLocation::NoEdge) {
0264         m_slideMap[window] = SlideData{
0265             .location = location,
0266             .offset = offset,
0267         };
0268         trackWindow(window);
0269     } else {
0270         m_slideMap.remove(window);
0271         releaseWindow(window);
0272     }
0273 
0274     installSlide(window, location, offset);
0275 }
0276 
0277 void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
0278 {
0279     if (!m_slideManager->isActive()) {
0280         return;
0281     }
0282     wl_surface *surface = surfaceForWindow(window);
0283     if (surface) {
0284         if (location != KWindowEffects::SlideFromLocation::NoEdge) {
0285             auto slide = new Slide(m_slideManager->create(surface), window);
0286 
0287             Slide::location convertedLoc;
0288             switch (location) {
0289             case KWindowEffects::SlideFromLocation::TopEdge:
0290                 convertedLoc = Slide::location::location_top;
0291                 break;
0292             case KWindowEffects::SlideFromLocation::LeftEdge:
0293                 convertedLoc = Slide::location::location_left;
0294                 break;
0295             case KWindowEffects::SlideFromLocation::RightEdge:
0296                 convertedLoc = Slide::location::location_right;
0297                 break;
0298             case KWindowEffects::SlideFromLocation::BottomEdge:
0299             default:
0300                 convertedLoc = Slide::location::location_bottom;
0301                 break;
0302             }
0303 
0304             slide->set_location(convertedLoc);
0305             slide->set_offset(offset);
0306             slide->commit();
0307         } else {
0308             m_slideManager->unset(surface);
0309         }
0310     }
0311 }
0312 
0313 void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
0314 {
0315     if (enable) {
0316         trackWindow(window);
0317         m_blurRegions[window] = region;
0318     } else {
0319         resetBlur(window);
0320         m_blurRegions.remove(window);
0321         releaseWindow(window);
0322     }
0323 
0324     installBlur(window, enable, region);
0325 }
0326 
0327 void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion &region)
0328 {
0329     if (!m_blurManager->isActive()) {
0330         return;
0331     }
0332 
0333     wl_surface *surface = surfaceForWindow(window);
0334 
0335     if (surface) {
0336         if (enable) {
0337             auto wl_region = createRegion(region);
0338             if (!wl_region) {
0339                 return;
0340             }
0341             auto blur = new Blur(m_blurManager->create(surface), window);
0342             blur->set_region(wl_region);
0343             blur->commit();
0344             wl_region_destroy(wl_region);
0345             resetBlur(window, blur);
0346         } else {
0347             resetBlur(window);
0348             m_blurManager->unset(surface);
0349         }
0350     }
0351 }
0352 
0353 void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
0354 {
0355     if (enable) {
0356         trackWindow(window);
0357         m_backgroundConstrastRegions[window].contrast = contrast;
0358         m_backgroundConstrastRegions[window].intensity = intensity;
0359         m_backgroundConstrastRegions[window].saturation = saturation;
0360         m_backgroundConstrastRegions[window].region = region;
0361     } else {
0362         resetContrast(window);
0363         m_backgroundConstrastRegions.remove(window);
0364         releaseWindow(window);
0365     }
0366 
0367     installContrast(window, enable, contrast, intensity, saturation, region);
0368 }
0369 
0370 void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
0371 {
0372     if (!m_contrastManager->isActive()) {
0373         return;
0374     }
0375 
0376     wl_surface *surface = surfaceForWindow(window);
0377     if (surface) {
0378         if (enable) {
0379             auto wl_region = createRegion(region);
0380             if (!wl_region) {
0381                 return;
0382             }
0383             auto backgroundContrast = new Contrast(m_contrastManager->create(surface), window);
0384             backgroundContrast->set_region(wl_region);
0385             backgroundContrast->set_contrast(wl_fixed_from_double(contrast));
0386             backgroundContrast->set_intensity(wl_fixed_from_double(intensity));
0387             backgroundContrast->set_saturation(wl_fixed_from_double(saturation));
0388             backgroundContrast->commit();
0389             wl_region_destroy(wl_region);
0390             resetContrast(window, backgroundContrast);
0391         } else {
0392             resetContrast(window);
0393             m_contrastManager->unset(surface);
0394         }
0395     }
0396 }