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

0001 /*
0002     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003     SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "windowshadow.h"
0009 #include "logging.h"
0010 #include "shm.h"
0011 #include "surfacehelper.h"
0012 
0013 #include <qwayland-shadow.h>
0014 
0015 #include <QDebug>
0016 #include <QExposeEvent>
0017 #include <QWaylandClientExtension>
0018 
0019 #include <private/qwaylandwindow_p.h>
0020 
0021 class ShadowManager : public QWaylandClientExtensionTemplate<ShadowManager>, public QtWayland::org_kde_kwin_shadow_manager
0022 {
0023     Q_OBJECT
0024     static constexpr int version = 2;
0025     explicit ShadowManager(QObject *parent = nullptr)
0026         : QWaylandClientExtensionTemplate(version)
0027     {
0028         setParent(parent);
0029         initialize();
0030 
0031         connect(this, &QWaylandClientExtension::activeChanged, this, [this] {
0032             if (!isActive()) {
0033                 destroy();
0034             }
0035         });
0036     }
0037 
0038 public:
0039     ~ShadowManager()
0040     {
0041         if (isActive()) {
0042             destroy();
0043         }
0044     }
0045     static ShadowManager *instance()
0046     {
0047         static ShadowManager *instance = new ShadowManager(qGuiApp);
0048         return instance;
0049     }
0050 };
0051 
0052 class Shadow : public QtWayland::org_kde_kwin_shadow
0053 {
0054 public:
0055     using QtWayland::org_kde_kwin_shadow::org_kde_kwin_shadow;
0056     ~Shadow()
0057     {
0058         destroy();
0059     }
0060 };
0061 
0062 WindowShadowTile::WindowShadowTile()
0063 {
0064     connect(Shm::instance(), &Shm::activeChanged, this, [this] {
0065         if (Shm::instance()->isActive()) {
0066             buffer.reset();
0067         }
0068     });
0069 }
0070 WindowShadowTile::~WindowShadowTile()
0071 {
0072 }
0073 
0074 bool WindowShadowTile::create()
0075 {
0076     if (!Shm::instance()->isActive()) {
0077         return false;
0078     }
0079 
0080     buffer = Shm::instance()->createBuffer(image);
0081     return true;
0082 }
0083 
0084 void WindowShadowTile::destroy()
0085 {
0086     buffer.reset();
0087 }
0088 
0089 WindowShadowTile *WindowShadowTile::get(const KWindowShadowTile *tile)
0090 {
0091     KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
0092     return static_cast<WindowShadowTile *>(d);
0093 }
0094 
0095 static wl_buffer *bufferForTile(const KWindowShadowTile::Ptr &tile)
0096 {
0097     if (!tile) {
0098         return nullptr;
0099     }
0100     WindowShadowTile *d = WindowShadowTile::get(tile.data());
0101     // Our buffer has been deleted in the meantime, try to create it again
0102     if (!d->buffer && d->isCreated) {
0103         d->buffer = Shm::instance()->createBuffer(d->image);
0104     }
0105     return d->buffer ? d->buffer.get()->object() : nullptr;
0106 }
0107 
0108 WindowShadow::WindowShadow()
0109 {
0110 }
0111 WindowShadow::~WindowShadow()
0112 {
0113 }
0114 
0115 bool WindowShadow::eventFilter(QObject *watched, QEvent *event)
0116 {
0117     Q_UNUSED(watched)
0118     if (event->type() == QEvent::Expose) {
0119         if (auto window = qobject_cast<QWindow *>(watched); window && window->isExposed()) {
0120             if (!internalCreate()) {
0121                 qCWarning(KWAYLAND_KWS) << "Failed to recreate shadow for" << window;
0122             }
0123         }
0124     }
0125     return false;
0126 }
0127 
0128 bool WindowShadow::internalCreate()
0129 {
0130     if (shadow) {
0131         return true;
0132     }
0133     if (!ShadowManager::instance()->isActive()) {
0134         return false;
0135     }
0136     auto surface = surfaceForWindow(window);
0137     if (!surface) {
0138         return false;
0139     }
0140 
0141     shadow = std::make_unique<Shadow>(ShadowManager::instance()->create(surface));
0142     auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0143     if (waylandWindow) {
0144         connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceDestroyed, this, &WindowShadow::internalDestroy, Qt::UniqueConnection);
0145     }
0146 
0147     auto attach = [](const std::unique_ptr<Shadow> &shadow, auto attach_func, const KWindowShadowTile::Ptr &tile) {
0148         if (auto buffer = bufferForTile(tile)) {
0149             (*shadow.*attach_func)(buffer);
0150         }
0151     };
0152     attach(shadow, &Shadow::attach_left, leftTile);
0153     attach(shadow, &Shadow::attach_top_left, topLeftTile);
0154     attach(shadow, &Shadow::attach_top, topTile);
0155     attach(shadow, &Shadow::attach_top_right, topRightTile);
0156     attach(shadow, &Shadow::attach_right, rightTile);
0157     attach(shadow, &Shadow::attach_bottom_right, bottomRightTile);
0158     attach(shadow, &Shadow::attach_bottom, bottomTile);
0159     attach(shadow, &Shadow::attach_bottom_left, bottomLeftTile);
0160 
0161     shadow->set_left_offset(wl_fixed_from_double(padding.left()));
0162     shadow->set_top_offset(wl_fixed_from_double(padding.top()));
0163     shadow->set_right_offset(wl_fixed_from_double(padding.right()));
0164     shadow->set_bottom_offset(wl_fixed_from_double(padding.bottom()));
0165 
0166     shadow->commit();
0167 
0168     // Commit wl_surface at the next available time.
0169     window->requestUpdate();
0170 
0171     return true;
0172 }
0173 
0174 bool WindowShadow::create()
0175 {
0176     if (!ShadowManager::instance()->isActive()) {
0177         return false;
0178     }
0179 
0180     internalCreate();
0181     window->installEventFilter(this);
0182     return true;
0183 }
0184 
0185 void WindowShadow::internalDestroy()
0186 {
0187     if (!shadow) {
0188         return;
0189     }
0190 
0191     // Only call surfaceForWindow and unset the surface if the native window is alive.
0192     // Otherwise window->create() might be called when the window is being destroyed, leading to a crash.
0193     if (window && window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() && ShadowManager::instance()->isActive()) {
0194         if (auto surface = surfaceForWindow(window)) {
0195             ShadowManager::instance()->unset(surface);
0196         }
0197     }
0198 
0199     shadow.reset();
0200 
0201     if (window && window->isVisible()) {
0202         window->requestUpdate();
0203     }
0204 }
0205 
0206 void WindowShadow::destroy()
0207 {
0208     if (window) {
0209         window->removeEventFilter(this);
0210     }
0211     internalDestroy();
0212 }
0213 
0214 #include "windowshadow.moc"