File indexing completed on 2024-05-12 17:00:19
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 #include "waylandintegration.h" 0010 0011 #include <QDebug> 0012 #include <QExposeEvent> 0013 #include <QGuiApplication> 0014 #include <QWidget> 0015 0016 #include <KWayland/Client/blur.h> 0017 #include <KWayland/Client/compositor.h> 0018 #include <KWayland/Client/connection_thread.h> 0019 #include <KWayland/Client/contrast.h> 0020 #include <KWayland/Client/plasmashell.h> 0021 #include <KWayland/Client/plasmawindowmanagement.h> 0022 #include <KWayland/Client/region.h> 0023 #include <KWayland/Client/registry.h> 0024 #include <KWayland/Client/slide.h> 0025 #include <KWayland/Client/surface.h> 0026 #include <private/qwaylandwindow_p.h> 0027 0028 WindowEffects::WindowEffects() 0029 : QObject() 0030 , KWindowEffectsPrivateV2() 0031 { 0032 auto registry = WaylandIntegration::self()->registry(); 0033 0034 // The KWindowEffects API doesn't provide any signals to notify that the particular 0035 // effect has become unavailable. So we re-install effects when the corresponding globals 0036 // are added. 0037 connect(registry, &KWayland::Client::Registry::blurAnnounced, this, [this]() { 0038 for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) { 0039 installBlur(it.key(), true, *it); 0040 } 0041 }); 0042 connect(registry, &KWayland::Client::Registry::blurRemoved, this, [this]() { 0043 for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) { 0044 installBlur(it.key(), false, *it); 0045 } 0046 }); 0047 0048 connect(registry, &KWayland::Client::Registry::contrastAnnounced, this, [this]() { 0049 for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) { 0050 installContrast(it.key(), true, it->contrast, it->intensity, it->saturation, it->region); 0051 } 0052 }); 0053 connect(registry, &KWayland::Client::Registry::contrastRemoved, this, [this]() { 0054 for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) { 0055 installContrast(it.key(), false); 0056 } 0057 }); 0058 0059 connect(registry, &KWayland::Client::Registry::slideAnnounced, this, [this]() { 0060 for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) { 0061 installSlide(it.key(), it->location, it->offset); 0062 } 0063 }); 0064 connect(registry, &KWayland::Client::Registry::slideRemoved, this, [this]() { 0065 for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) { 0066 installSlide(it.key(), KWindowEffects::SlideFromLocation::NoEdge, 0); 0067 } 0068 }); 0069 } 0070 0071 WindowEffects::~WindowEffects() 0072 { 0073 } 0074 0075 QWindow *WindowEffects::windowForId(WId wid) 0076 { 0077 QWindow *window = nullptr; 0078 0079 for (auto win : qApp->allWindows()) { 0080 if (win->winId() == wid) { 0081 window = win; 0082 break; 0083 } 0084 } 0085 return window; 0086 } 0087 0088 void WindowEffects::trackWindow(QWindow *window) 0089 { 0090 if (!m_windowWatchers.contains(window)) { 0091 window->installEventFilter(this); 0092 auto conn = connect(window, &QObject::destroyed, this, [this, window]() { 0093 resetBlur(window); 0094 m_blurRegions.remove(window); 0095 resetContrast(window); 0096 m_backgroundConstrastRegions.remove(window); 0097 m_slideMap.remove(window); 0098 m_windowWatchers.remove(window); 0099 }); 0100 m_windowWatchers[window] << conn; 0101 auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle()); 0102 if (waylandWindow) { 0103 auto conn = connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceDestroyed, this, [this, window]() { 0104 resetBlur(window); 0105 resetContrast(window); 0106 }); 0107 m_windowWatchers[window] << conn; 0108 } 0109 } 0110 } 0111 0112 void WindowEffects::releaseWindow(QWindow *window) 0113 { 0114 if (!m_blurRegions.contains(window) && !m_backgroundConstrastRegions.contains(window) && !m_slideMap.contains(window)) { 0115 for (const auto &conn : m_windowWatchers[window]) { 0116 disconnect(conn); 0117 } 0118 window->removeEventFilter(this); 0119 m_windowWatchers.remove(window); 0120 } 0121 } 0122 0123 // Helper function to replace a QObject value in the map and delete the old one. 0124 template<typename MapType> 0125 void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value) 0126 { 0127 if (auto oldValue = map.take(key)) { 0128 oldValue->deleteLater(); 0129 } 0130 if (value) { 0131 map[key] = value; 0132 } 0133 } 0134 0135 void WindowEffects::resetBlur(QWindow *window, KWayland::Client::Blur *blur) 0136 { 0137 replaceValue(m_blurs, window, blur); 0138 } 0139 0140 void WindowEffects::resetContrast(QWindow *window, KWayland::Client::Contrast *contrast) 0141 { 0142 replaceValue(m_contrasts, window, contrast); 0143 } 0144 0145 bool WindowEffects::eventFilter(QObject *watched, QEvent *event) 0146 { 0147 if (event->type() == QEvent::Expose) { 0148 auto ee = static_cast<QExposeEvent *>(event); 0149 0150 if ((ee->region().isNull())) { 0151 return false; 0152 } 0153 0154 auto window = qobject_cast<QWindow *>(watched); 0155 if (!window) { 0156 return false; 0157 } 0158 0159 { 0160 auto it = m_blurRegions.constFind(window); 0161 if (it != m_blurRegions.constEnd()) { 0162 installBlur(window, true, *it); 0163 } 0164 } 0165 { 0166 auto it = m_backgroundConstrastRegions.constFind(window); 0167 if (it != m_backgroundConstrastRegions.constEnd()) { 0168 installContrast(window, true, it->contrast, it->intensity, it->saturation, it->region); 0169 } 0170 } 0171 } 0172 return false; 0173 } 0174 0175 bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect) 0176 { 0177 switch (effect) { 0178 case KWindowEffects::BackgroundContrast: 0179 return WaylandIntegration::self()->waylandContrastManager() != nullptr; 0180 case KWindowEffects::BlurBehind: 0181 return WaylandIntegration::self()->waylandBlurManager() != nullptr; 0182 case KWindowEffects::Slide: 0183 return WaylandIntegration::self()->waylandSlideManager() != nullptr; 0184 default: 0185 return false; 0186 } 0187 } 0188 0189 void WindowEffects::slideWindow(WId id, KWindowEffects::SlideFromLocation location, int offset) 0190 { 0191 auto window = windowForId(id); 0192 if (!window) { 0193 return; 0194 } 0195 if (location != KWindowEffects::SlideFromLocation::NoEdge) { 0196 m_slideMap[window] = SlideData{ 0197 .location = location, 0198 .offset = offset, 0199 }; 0200 trackWindow(window); 0201 } else { 0202 m_slideMap.remove(window); 0203 releaseWindow(window); 0204 } 0205 0206 installSlide(window, location, offset); 0207 } 0208 0209 void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset) 0210 { 0211 if (!WaylandIntegration::self()->waylandSlideManager()) { 0212 return; 0213 } 0214 KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window); 0215 if (surface) { 0216 if (location != KWindowEffects::SlideFromLocation::NoEdge) { 0217 auto slide = WaylandIntegration::self()->waylandSlideManager()->createSlide(surface, surface); 0218 0219 KWayland::Client::Slide::Location convertedLoc; 0220 switch (location) { 0221 case KWindowEffects::SlideFromLocation::TopEdge: 0222 convertedLoc = KWayland::Client::Slide::Location::Top; 0223 break; 0224 case KWindowEffects::SlideFromLocation::LeftEdge: 0225 convertedLoc = KWayland::Client::Slide::Location::Left; 0226 break; 0227 case KWindowEffects::SlideFromLocation::RightEdge: 0228 convertedLoc = KWayland::Client::Slide::Location::Right; 0229 break; 0230 case KWindowEffects::SlideFromLocation::BottomEdge: 0231 default: 0232 convertedLoc = KWayland::Client::Slide::Location::Bottom; 0233 break; 0234 } 0235 0236 slide->setLocation(convertedLoc); 0237 slide->setOffset(offset); 0238 slide->commit(); 0239 } else { 0240 WaylandIntegration::self()->waylandSlideManager()->removeSlide(surface); 0241 } 0242 0243 WaylandIntegration::self()->waylandConnection()->flush(); 0244 } 0245 } 0246 0247 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 81) 0248 QList<QSize> WindowEffects::windowSizes(const QList<WId> &ids) 0249 { 0250 Q_UNUSED(ids) 0251 QList<QSize> sizes; 0252 return sizes; 0253 } 0254 #endif 0255 0256 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0257 void WindowEffects::presentWindows(WId controller, const QList<WId> &ids) 0258 { 0259 Q_UNUSED(controller) 0260 Q_UNUSED(ids) 0261 } 0262 #endif 0263 0264 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0265 void WindowEffects::presentWindows(WId controller, int desktop) 0266 { 0267 Q_UNUSED(controller) 0268 Q_UNUSED(desktop) 0269 } 0270 #endif 0271 0272 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82) 0273 void WindowEffects::highlightWindows(WId controller, const QList<WId> &ids) 0274 { 0275 Q_UNUSED(controller) 0276 Q_UNUSED(ids) 0277 } 0278 #endif 0279 0280 void WindowEffects::enableBlurBehind(WId winId, bool enable, const QRegion ®ion) 0281 { 0282 auto window = windowForId(winId); 0283 if (!window) { 0284 return; 0285 } 0286 if (enable) { 0287 trackWindow(window); 0288 m_blurRegions[window] = region; 0289 } else { 0290 resetBlur(window); 0291 m_blurRegions.remove(window); 0292 releaseWindow(window); 0293 } 0294 0295 installBlur(window, enable, region); 0296 } 0297 0298 void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion ®ion) 0299 { 0300 if (!WaylandIntegration::self()->waylandBlurManager()) { 0301 return; 0302 } 0303 KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window); 0304 0305 if (surface) { 0306 if (enable) { 0307 auto blur = WaylandIntegration::self()->waylandBlurManager()->createBlur(surface, surface); 0308 std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr)); 0309 blur->setRegion(wlRegion.get()); 0310 blur->commit(); 0311 resetBlur(window, blur); 0312 } else { 0313 resetBlur(window); 0314 WaylandIntegration::self()->waylandBlurManager()->removeBlur(surface); 0315 } 0316 0317 WaylandIntegration::self()->waylandConnection()->flush(); 0318 } 0319 } 0320 0321 void WindowEffects::enableBackgroundContrast(WId winId, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion ®ion) 0322 { 0323 auto window = windowForId(winId); 0324 if (!window) { 0325 return; 0326 } 0327 if (enable) { 0328 trackWindow(window); 0329 m_backgroundConstrastRegions[window].contrast = contrast; 0330 m_backgroundConstrastRegions[window].intensity = intensity; 0331 m_backgroundConstrastRegions[window].saturation = saturation; 0332 m_backgroundConstrastRegions[window].region = region; 0333 } else { 0334 resetContrast(window); 0335 m_backgroundConstrastRegions.remove(window); 0336 releaseWindow(window); 0337 } 0338 0339 installContrast(window, enable, contrast, intensity, saturation, region); 0340 } 0341 0342 void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion ®ion) 0343 { 0344 if (!WaylandIntegration::self()->waylandContrastManager()) { 0345 return; 0346 } 0347 KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window); 0348 if (surface) { 0349 if (enable) { 0350 auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface); 0351 std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr)); 0352 backgroundContrast->setRegion(wlRegion.get()); 0353 backgroundContrast->setContrast(contrast); 0354 backgroundContrast->setIntensity(intensity); 0355 backgroundContrast->setSaturation(saturation); 0356 backgroundContrast->commit(); 0357 resetContrast(window, backgroundContrast); 0358 } else { 0359 resetContrast(window); 0360 WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface); 0361 } 0362 0363 WaylandIntegration::self()->waylandConnection()->flush(); 0364 } 0365 } 0366 0367 void WindowEffects::setBackgroundFrost(QWindow *window, QColor color, const QRegion ®ion) 0368 { 0369 if (!WaylandIntegration::self()->waylandContrastManager()) { 0370 return; 0371 } 0372 0373 KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window); 0374 if (!surface) { 0375 return; 0376 } 0377 if (!color.isValid()) { 0378 resetContrast(window); 0379 WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface); 0380 return; 0381 } 0382 0383 auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface); 0384 std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr)); 0385 backgroundContrast->setRegion(wlRegion.get()); 0386 backgroundContrast->setFrost(color); 0387 backgroundContrast->commit(); 0388 resetContrast(window, backgroundContrast); 0389 0390 WaylandIntegration::self()->waylandConnection()->flush(); 0391 } 0392 0393 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67) 0394 void WindowEffects::markAsDashboard(WId window) 0395 { 0396 Q_UNUSED(window) 0397 } 0398 #endif