File indexing completed on 2024-11-10 04:57:10
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2009 Marco Martin notmart @gmail.com 0006 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "slidingpopups.h" 0012 #include "slidingpopupsconfig.h" 0013 0014 #include "effect/effecthandler.h" 0015 #include "wayland/display.h" 0016 #include "wayland/slide.h" 0017 #include "wayland/surface.h" 0018 0019 #include <QFontMetrics> 0020 #include <QGuiApplication> 0021 #include <QTimer> 0022 #include <QWindow> 0023 0024 #include <KWindowEffects> 0025 0026 Q_DECLARE_METATYPE(KWindowEffects::SlideFromLocation) 0027 0028 namespace KWin 0029 { 0030 0031 SlideManagerInterface *SlidingPopupsEffect::s_slideManager = nullptr; 0032 QTimer *SlidingPopupsEffect::s_slideManagerRemoveTimer = nullptr; 0033 0034 SlidingPopupsEffect::SlidingPopupsEffect() 0035 { 0036 SlidingPopupsConfig::instance(effects->config()); 0037 0038 Display *display = effects->waylandDisplay(); 0039 if (display) { 0040 if (!s_slideManagerRemoveTimer) { 0041 s_slideManagerRemoveTimer = new QTimer(QCoreApplication::instance()); 0042 s_slideManagerRemoveTimer->setSingleShot(true); 0043 s_slideManagerRemoveTimer->callOnTimeout([]() { 0044 s_slideManager->remove(); 0045 s_slideManager = nullptr; 0046 }); 0047 } 0048 s_slideManagerRemoveTimer->stop(); 0049 if (!s_slideManager) { 0050 s_slideManager = new SlideManagerInterface(display, s_slideManagerRemoveTimer); 0051 } 0052 } 0053 0054 m_slideLength = QFontMetrics(QGuiApplication::font()).height() * 8; 0055 0056 m_atom = effects->announceSupportProperty("_KDE_SLIDE", this); 0057 connect(effects, &EffectsHandler::windowAdded, this, &SlidingPopupsEffect::slotWindowAdded); 0058 connect(effects, &EffectsHandler::windowClosed, this, &SlidingPopupsEffect::slotWindowClosed); 0059 connect(effects, &EffectsHandler::windowDeleted, this, &SlidingPopupsEffect::slotWindowDeleted); 0060 connect(effects, &EffectsHandler::propertyNotify, this, &SlidingPopupsEffect::slotPropertyNotify); 0061 connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() { 0062 m_atom = effects->announceSupportProperty(QByteArrayLiteral("_KDE_SLIDE"), this); 0063 }); 0064 connect(effects, &EffectsHandler::desktopChanged, 0065 this, &SlidingPopupsEffect::stopAnimations); 0066 connect(effects, &EffectsHandler::activeFullScreenEffectChanged, 0067 this, &SlidingPopupsEffect::stopAnimations); 0068 connect(effects, &EffectsHandler::screenLockingChanged, 0069 this, &SlidingPopupsEffect::stopAnimations); 0070 0071 reconfigure(ReconfigureAll); 0072 0073 const QList<EffectWindow *> windows = effects->stackingOrder(); 0074 for (EffectWindow *window : windows) { 0075 setupSlideData(window); 0076 } 0077 } 0078 0079 SlidingPopupsEffect::~SlidingPopupsEffect() 0080 { 0081 // When compositing is restarted, avoid removing the manager immediately. 0082 if (s_slideManager) { 0083 s_slideManagerRemoveTimer->start(1000); 0084 } 0085 0086 // Cancel animations here while both m_animations and m_animationsData are still valid. 0087 // slotWindowDeleted may access m_animationsData when an animation is removed. 0088 m_animations.clear(); 0089 m_animationsData.clear(); 0090 } 0091 0092 bool SlidingPopupsEffect::supported() 0093 { 0094 return effects->animationsSupported(); 0095 } 0096 0097 void SlidingPopupsEffect::reconfigure(ReconfigureFlags flags) 0098 { 0099 SlidingPopupsConfig::self()->read(); 0100 m_slideInDuration = std::chrono::milliseconds( 0101 static_cast<int>(animationTime(SlidingPopupsConfig::slideInTime() != 0 ? SlidingPopupsConfig::slideInTime() : 150))); 0102 m_slideOutDuration = std::chrono::milliseconds( 0103 static_cast<int>(animationTime(SlidingPopupsConfig::slideOutTime() != 0 ? SlidingPopupsConfig::slideOutTime() : 250))); 0104 0105 auto animationIt = m_animations.begin(); 0106 while (animationIt != m_animations.end()) { 0107 const auto duration = ((*animationIt).kind == AnimationKind::In) 0108 ? m_slideInDuration 0109 : m_slideOutDuration; 0110 (*animationIt).timeLine.setDuration(duration); 0111 ++animationIt; 0112 } 0113 0114 auto dataIt = m_animationsData.begin(); 0115 while (dataIt != m_animationsData.end()) { 0116 (*dataIt).slideInDuration = m_slideInDuration; 0117 (*dataIt).slideOutDuration = m_slideOutDuration; 0118 ++dataIt; 0119 } 0120 } 0121 0122 void SlidingPopupsEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) 0123 { 0124 auto animationIt = m_animations.find(w); 0125 if (animationIt == m_animations.end()) { 0126 effects->prePaintWindow(w, data, presentTime); 0127 return; 0128 } 0129 0130 (*animationIt).timeLine.advance(presentTime); 0131 data.setTransformed(); 0132 0133 effects->prePaintWindow(w, data, presentTime); 0134 } 0135 0136 void SlidingPopupsEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data) 0137 { 0138 auto animationIt = m_animations.constFind(w); 0139 if (animationIt == m_animations.constEnd()) { 0140 effects->paintWindow(renderTarget, viewport, w, mask, region, data); 0141 return; 0142 } 0143 0144 const AnimationData &animData = m_animationsData[w]; 0145 const qreal slideLength = (animData.slideLength > 0) ? animData.slideLength : m_slideLength; 0146 0147 const QRectF screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); 0148 int splitPoint = 0; 0149 const QRectF geo = w->expandedGeometry(); 0150 const qreal t = (*animationIt).timeLine.value(); 0151 0152 switch (animData.location) { 0153 case Location::Left: 0154 if (slideLength < geo.width()) { 0155 data.multiplyOpacity(t); 0156 } 0157 data.translate(-interpolate(std::min(geo.width(), slideLength), 0.0, t)); 0158 splitPoint = geo.width() - (geo.x() + geo.width() - screenRect.x() - animData.offset); 0159 region &= QRegion(geo.x() + splitPoint, geo.y(), geo.width() - splitPoint, geo.height()); 0160 break; 0161 case Location::Top: 0162 if (slideLength < geo.height()) { 0163 data.multiplyOpacity(t); 0164 } 0165 data.translate(0.0, -interpolate(std::min(geo.height(), slideLength), 0.0, t)); 0166 splitPoint = geo.height() - (geo.y() + geo.height() - screenRect.y() - animData.offset); 0167 region &= QRegion(geo.x(), geo.y() + splitPoint, geo.width(), geo.height() - splitPoint); 0168 break; 0169 case Location::Right: 0170 if (slideLength < geo.width()) { 0171 data.multiplyOpacity(t); 0172 } 0173 data.translate(interpolate(std::min(geo.width(), slideLength), 0.0, t)); 0174 splitPoint = screenRect.x() + screenRect.width() - geo.x() - animData.offset; 0175 region &= QRegion(geo.x(), geo.y(), splitPoint, geo.height()); 0176 break; 0177 case Location::Bottom: 0178 default: 0179 if (slideLength < geo.height()) { 0180 data.multiplyOpacity(t); 0181 } 0182 data.translate(0.0, interpolate(std::min(geo.height(), slideLength), 0.0, t)); 0183 splitPoint = screenRect.y() + screenRect.height() - geo.y() - animData.offset; 0184 region &= QRegion(geo.x(), geo.y(), geo.width(), splitPoint); 0185 } 0186 0187 effects->paintWindow(renderTarget, viewport, w, mask, region, data); 0188 } 0189 0190 void SlidingPopupsEffect::postPaintWindow(EffectWindow *w) 0191 { 0192 auto animationIt = m_animations.find(w); 0193 if (animationIt != m_animations.end()) { 0194 effects->addRepaint(w->expandedGeometry()); 0195 if ((*animationIt).timeLine.done()) { 0196 if (!w->isDeleted()) { 0197 w->setData(WindowForceBackgroundContrastRole, QVariant()); 0198 w->setData(WindowForceBlurRole, QVariant()); 0199 } 0200 m_animations.erase(animationIt); 0201 } 0202 } 0203 0204 effects->postPaintWindow(w); 0205 } 0206 0207 void SlidingPopupsEffect::setupSlideData(EffectWindow *w) 0208 { 0209 connect(w, &EffectWindow::windowFrameGeometryChanged, this, &SlidingPopupsEffect::slotWindowFrameGeometryChanged); 0210 connect(w, &EffectWindow::windowShown, this, &SlidingPopupsEffect::slideIn); 0211 connect(w, &EffectWindow::windowHidden, this, &SlidingPopupsEffect::slideOut); 0212 0213 // X11 0214 if (m_atom != XCB_ATOM_NONE) { 0215 slotPropertyNotify(w, m_atom); 0216 } 0217 0218 // Wayland 0219 if (auto surf = w->surface()) { 0220 slotWaylandSlideOnShowChanged(w); 0221 connect(surf, &SurfaceInterface::slideOnShowHideChanged, this, [this, surf] { 0222 slotWaylandSlideOnShowChanged(effects->findWindow(surf)); 0223 }); 0224 } 0225 0226 if (auto internal = w->internalWindow()) { 0227 internal->installEventFilter(this); 0228 setupInternalWindowSlide(w); 0229 } 0230 } 0231 0232 void SlidingPopupsEffect::slotWindowAdded(EffectWindow *w) 0233 { 0234 setupSlideData(w); 0235 if (!w->isHidden()) { 0236 slideIn(w); 0237 } 0238 } 0239 0240 void SlidingPopupsEffect::slotWindowClosed(EffectWindow *w) 0241 { 0242 if (!w->isHidden()) { 0243 slideOut(w); 0244 } 0245 } 0246 0247 void SlidingPopupsEffect::slotWindowDeleted(EffectWindow *w) 0248 { 0249 m_animationsData.remove(w); 0250 } 0251 0252 void SlidingPopupsEffect::slotPropertyNotify(EffectWindow *w, long atom) 0253 { 0254 if (!w || atom != m_atom || m_atom == XCB_ATOM_NONE) { 0255 return; 0256 } 0257 0258 // _KDE_SLIDE atom format(each field is an uint32_t): 0259 // <offset> <location> [<slide in duration>] [<slide out duration>] [<slide length>] 0260 // 0261 // If offset is equal to -1, this effect will decide what offset to use 0262 // given edge of the screen, from which the window has to slide. 0263 // 0264 // If slide in duration is equal to 0 milliseconds, the default slide in 0265 // duration will be used. Same with the slide out duration. 0266 // 0267 // NOTE: If only slide in duration has been provided, then it will be 0268 // also used as slide out duration. I.e. if you provided only slide in 0269 // duration, then slide in duration == slide out duration. 0270 0271 const QByteArray rawAtomData = w->readProperty(m_atom, m_atom, 32); 0272 0273 if (rawAtomData.isEmpty()) { 0274 // Property was removed, thus also remove the effect for window 0275 if (w->data(WindowClosedGrabRole).value<void *>() == this) { 0276 w->setData(WindowClosedGrabRole, QVariant()); 0277 } 0278 m_animations.remove(w); 0279 m_animationsData.remove(w); 0280 return; 0281 } 0282 0283 // Offset and location are required. 0284 if (static_cast<size_t>(rawAtomData.size()) < sizeof(uint32_t) * 2) { 0285 return; 0286 } 0287 0288 const auto *atomData = reinterpret_cast<const uint32_t *>(rawAtomData.data()); 0289 AnimationData &animData = m_animationsData[w]; 0290 animData.offset = atomData[0]; 0291 0292 switch (atomData[1]) { 0293 case 0: // West 0294 animData.location = Location::Left; 0295 break; 0296 case 1: // North 0297 animData.location = Location::Top; 0298 break; 0299 case 2: // East 0300 animData.location = Location::Right; 0301 break; 0302 case 3: // South 0303 default: 0304 animData.location = Location::Bottom; 0305 break; 0306 } 0307 0308 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 3) { 0309 animData.slideInDuration = std::chrono::milliseconds(atomData[2]); 0310 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 4) { 0311 animData.slideOutDuration = std::chrono::milliseconds(atomData[3]); 0312 } else { 0313 animData.slideOutDuration = animData.slideInDuration; 0314 } 0315 } else { 0316 animData.slideInDuration = m_slideInDuration; 0317 animData.slideOutDuration = m_slideOutDuration; 0318 } 0319 0320 if (static_cast<size_t>(rawAtomData.size()) >= sizeof(uint32_t) * 5) { 0321 animData.slideLength = atomData[4]; 0322 } else { 0323 animData.slideLength = 0; 0324 } 0325 0326 setupAnimData(w); 0327 } 0328 0329 void SlidingPopupsEffect::slotWindowFrameGeometryChanged(EffectWindow *w, const QRectF &) 0330 { 0331 if (w == effects->inputPanel()) { 0332 setupInputPanelSlide(); 0333 } 0334 } 0335 0336 void SlidingPopupsEffect::setupAnimData(EffectWindow *w) 0337 { 0338 const QRectF screenRect = effects->clientArea(FullScreenArea, w->screen(), effects->currentDesktop()); 0339 const QRectF windowGeo = w->frameGeometry(); 0340 AnimationData &animData = m_animationsData[w]; 0341 0342 if (animData.offset == -1) { 0343 switch (animData.location) { 0344 case Location::Left: 0345 animData.offset = std::max<qreal>(windowGeo.left() - screenRect.left(), 0); 0346 break; 0347 case Location::Top: 0348 animData.offset = std::max<qreal>(windowGeo.top() - screenRect.top(), 0); 0349 break; 0350 case Location::Right: 0351 animData.offset = std::max<qreal>(screenRect.right() - windowGeo.right(), 0); 0352 break; 0353 case Location::Bottom: 0354 default: 0355 animData.offset = std::max<qreal>(screenRect.bottom() - windowGeo.bottom(), 0); 0356 break; 0357 } 0358 } 0359 // sanitize 0360 switch (animData.location) { 0361 case Location::Left: 0362 animData.offset = std::max<qreal>(windowGeo.left() - screenRect.left(), animData.offset); 0363 break; 0364 case Location::Top: 0365 animData.offset = std::max<qreal>(windowGeo.top() - screenRect.top(), animData.offset); 0366 break; 0367 case Location::Right: 0368 animData.offset = std::max<qreal>(screenRect.right() - windowGeo.right(), animData.offset); 0369 break; 0370 case Location::Bottom: 0371 default: 0372 animData.offset = std::max<qreal>(screenRect.bottom() - windowGeo.bottom(), animData.offset); 0373 break; 0374 } 0375 0376 animData.slideInDuration = (animData.slideInDuration.count() != 0) 0377 ? animData.slideInDuration 0378 : m_slideInDuration; 0379 0380 animData.slideOutDuration = (animData.slideOutDuration.count() != 0) 0381 ? animData.slideOutDuration 0382 : m_slideOutDuration; 0383 0384 // Grab the window, so other windowClosed effects will ignore it 0385 w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this))); 0386 } 0387 0388 void SlidingPopupsEffect::slotWaylandSlideOnShowChanged(EffectWindow *w) 0389 { 0390 if (!w) { 0391 return; 0392 } 0393 0394 SurfaceInterface *surf = w->surface(); 0395 if (!surf) { 0396 return; 0397 } 0398 0399 if (surf->slideOnShowHide()) { 0400 AnimationData &animData = m_animationsData[w]; 0401 0402 animData.offset = surf->slideOnShowHide()->offset(); 0403 0404 switch (surf->slideOnShowHide()->location()) { 0405 case SlideInterface::Location::Top: 0406 animData.location = Location::Top; 0407 break; 0408 case SlideInterface::Location::Left: 0409 animData.location = Location::Left; 0410 break; 0411 case SlideInterface::Location::Right: 0412 animData.location = Location::Right; 0413 break; 0414 case SlideInterface::Location::Bottom: 0415 default: 0416 animData.location = Location::Bottom; 0417 break; 0418 } 0419 animData.slideLength = 0; 0420 animData.slideInDuration = m_slideInDuration; 0421 animData.slideOutDuration = m_slideOutDuration; 0422 0423 setupAnimData(w); 0424 } 0425 } 0426 0427 void SlidingPopupsEffect::setupInternalWindowSlide(EffectWindow *w) 0428 { 0429 if (!w) { 0430 return; 0431 } 0432 auto internal = w->internalWindow(); 0433 if (!internal) { 0434 return; 0435 } 0436 const QVariant slideProperty = internal->property("kwin_slide"); 0437 if (!slideProperty.isValid()) { 0438 return; 0439 } 0440 Location location; 0441 switch (slideProperty.value<KWindowEffects::SlideFromLocation>()) { 0442 case KWindowEffects::BottomEdge: 0443 location = Location::Bottom; 0444 break; 0445 case KWindowEffects::TopEdge: 0446 location = Location::Top; 0447 break; 0448 case KWindowEffects::RightEdge: 0449 location = Location::Right; 0450 break; 0451 case KWindowEffects::LeftEdge: 0452 location = Location::Left; 0453 break; 0454 default: 0455 return; 0456 } 0457 AnimationData &animData = m_animationsData[w]; 0458 animData.location = location; 0459 bool intOk = false; 0460 animData.offset = internal->property("kwin_slide_offset").toInt(&intOk); 0461 if (!intOk) { 0462 animData.offset = -1; 0463 } 0464 animData.slideLength = 0; 0465 animData.slideInDuration = m_slideInDuration; 0466 animData.slideOutDuration = m_slideOutDuration; 0467 0468 setupAnimData(w); 0469 } 0470 0471 void SlidingPopupsEffect::setupInputPanelSlide() 0472 { 0473 auto w = effects->inputPanel(); 0474 0475 if (!w || effects->isInputPanelOverlay()) { 0476 return; 0477 } 0478 0479 AnimationData &animData = m_animationsData[w]; 0480 animData.location = Location::Bottom; 0481 animData.offset = 0; 0482 animData.slideLength = 0; 0483 animData.slideInDuration = m_slideInDuration; 0484 animData.slideOutDuration = m_slideOutDuration; 0485 0486 setupAnimData(w); 0487 0488 slideIn(w); 0489 } 0490 0491 bool SlidingPopupsEffect::eventFilter(QObject *watched, QEvent *event) 0492 { 0493 auto internal = qobject_cast<QWindow *>(watched); 0494 if (internal && event->type() == QEvent::DynamicPropertyChange) { 0495 QDynamicPropertyChangeEvent *pe = static_cast<QDynamicPropertyChangeEvent *>(event); 0496 if (pe->propertyName() == "kwin_slide" || pe->propertyName() == "kwin_slide_offset") { 0497 if (auto w = effects->findWindow(internal)) { 0498 setupInternalWindowSlide(w); 0499 } 0500 } 0501 } 0502 return false; 0503 } 0504 0505 void SlidingPopupsEffect::slideIn(EffectWindow *w) 0506 { 0507 if (effects->activeFullScreenEffect()) { 0508 return; 0509 } 0510 0511 if (!w->isVisible()) { 0512 return; 0513 } 0514 0515 auto dataIt = m_animationsData.constFind(w); 0516 if (dataIt == m_animationsData.constEnd()) { 0517 return; 0518 } 0519 0520 Animation &animation = m_animations[w]; 0521 animation.kind = AnimationKind::In; 0522 animation.timeLine.setDirection(TimeLine::Forward); 0523 animation.timeLine.setDuration((*dataIt).slideInDuration); 0524 animation.timeLine.setEasingCurve(QEasingCurve::OutCubic); 0525 0526 // If the opposite animation (Out) was active and it had shorter duration, 0527 // at this point, the timeline can end up in the "done" state. Thus, we have 0528 // to reset it. 0529 if (animation.timeLine.done()) { 0530 animation.timeLine.reset(); 0531 } 0532 0533 w->setData(WindowAddedGrabRole, QVariant::fromValue(static_cast<void *>(this))); 0534 w->setData(WindowForceBackgroundContrastRole, QVariant(true)); 0535 w->setData(WindowForceBlurRole, QVariant(true)); 0536 0537 w->addRepaintFull(); 0538 } 0539 0540 void SlidingPopupsEffect::slideOut(EffectWindow *w) 0541 { 0542 if (effects->activeFullScreenEffect()) { 0543 return; 0544 } 0545 0546 if (!w->isVisible()) { 0547 return; 0548 } 0549 0550 auto dataIt = m_animationsData.constFind(w); 0551 if (dataIt == m_animationsData.constEnd()) { 0552 return; 0553 } 0554 0555 Animation &animation = m_animations[w]; 0556 animation.deletedRef = EffectWindowDeletedRef(w); 0557 animation.visibleRef = EffectWindowVisibleRef(w, EffectWindow::PAINT_DISABLED); 0558 animation.kind = AnimationKind::Out; 0559 animation.timeLine.setDirection(TimeLine::Backward); 0560 animation.timeLine.setDuration((*dataIt).slideOutDuration); 0561 // this is effectively InCubic because the direction is reversed 0562 animation.timeLine.setEasingCurve(QEasingCurve::OutCubic); 0563 0564 // If the opposite animation (In) was active and it had shorter duration, 0565 // at this point, the timeline can end up in the "done" state. Thus, we have 0566 // to reset it. 0567 if (animation.timeLine.done()) { 0568 animation.timeLine.reset(); 0569 } 0570 0571 w->setData(WindowClosedGrabRole, QVariant::fromValue(static_cast<void *>(this))); 0572 w->setData(WindowForceBackgroundContrastRole, QVariant(true)); 0573 w->setData(WindowForceBlurRole, QVariant(true)); 0574 0575 w->addRepaintFull(); 0576 } 0577 0578 void SlidingPopupsEffect::stopAnimations() 0579 { 0580 for (auto it = m_animations.constBegin(); it != m_animations.constEnd(); ++it) { 0581 EffectWindow *w = it.key(); 0582 0583 if (!w->isDeleted()) { 0584 w->setData(WindowForceBackgroundContrastRole, QVariant()); 0585 w->setData(WindowForceBlurRole, QVariant()); 0586 } 0587 } 0588 0589 m_animations.clear(); 0590 } 0591 0592 bool SlidingPopupsEffect::isActive() const 0593 { 0594 return !m_animations.isEmpty(); 0595 } 0596 0597 } // namespace 0598 0599 #include "moc_slidingpopups.cpp"