File indexing completed on 2024-05-26 05:33:14

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2007 Lubos Lunak <l.lunak@kde.org>
0006     SPDX-FileCopyrightText: 2007 Christian Nitschkowski <christian.nitschkowski@kdemail.net>
0007     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 // own
0013 #include "diminactive.h"
0014 #include "effect/effecthandler.h"
0015 
0016 // KConfigSkeleton
0017 #include "diminactiveconfig.h"
0018 
0019 namespace KWin
0020 {
0021 
0022 /**
0023  * Checks if two windows belong to the same window group
0024  *
0025  * One possible example of a window group is an app window and app
0026  * preferences window(e.g. Dolphin window and Dolphin Preferences window).
0027  *
0028  * @param w1 The first window
0029  * @param w2 The second window
0030  * @returns @c true if both windows belong to the same window group, @c false otherwise
0031  */
0032 static inline bool belongToSameGroup(const EffectWindow *w1, const EffectWindow *w2)
0033 {
0034     return w1 && w2 && w1->group() && w1->group() == w2->group();
0035 }
0036 
0037 DimInactiveEffect::DimInactiveEffect()
0038 {
0039     DimInactiveConfig::instance(effects->config());
0040     reconfigure(ReconfigureAll);
0041 
0042     connect(effects, &EffectsHandler::windowActivated,
0043             this, &DimInactiveEffect::windowActivated);
0044     connect(effects, &EffectsHandler::windowAdded,
0045             this, &DimInactiveEffect::windowAdded);
0046     connect(effects, &EffectsHandler::windowClosed,
0047             this, &DimInactiveEffect::windowClosed);
0048     connect(effects, &EffectsHandler::windowDeleted,
0049             this, &DimInactiveEffect::windowDeleted);
0050     connect(effects, &EffectsHandler::activeFullScreenEffectChanged,
0051             this, &DimInactiveEffect::activeFullScreenEffectChanged);
0052 
0053     const auto windows = effects->stackingOrder();
0054     for (EffectWindow *window : windows) {
0055         windowAdded(window);
0056     }
0057 }
0058 
0059 DimInactiveEffect::~DimInactiveEffect()
0060 {
0061 }
0062 
0063 void DimInactiveEffect::reconfigure(ReconfigureFlags flags)
0064 {
0065     DimInactiveConfig::self()->read();
0066 
0067     // TODO: Use normalized strength param.
0068     m_dimStrength = DimInactiveConfig::strength() / 100.0;
0069     m_dimPanels = DimInactiveConfig::dimPanels();
0070     m_dimDesktop = DimInactiveConfig::dimDesktop();
0071     m_dimKeepAbove = DimInactiveConfig::dimKeepAbove();
0072     m_dimByGroup = DimInactiveConfig::dimByGroup();
0073     m_dimFullScreen = DimInactiveConfig::dimFullScreen();
0074 
0075     updateActiveWindow(effects->activeWindow());
0076 
0077     m_activeWindowGroup = (m_dimByGroup && m_activeWindow)
0078         ? m_activeWindow->group()
0079         : nullptr;
0080 
0081     m_fullScreenTransition.timeLine.setDuration(
0082         std::chrono::milliseconds(static_cast<int>(animationTime(250))));
0083 
0084     effects->addRepaintFull();
0085 }
0086 
0087 void DimInactiveEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0088 {
0089     if (m_fullScreenTransition.active) {
0090         m_fullScreenTransition.timeLine.advance(presentTime);
0091     }
0092 
0093     auto transitionIt = m_transitions.begin();
0094     while (transitionIt != m_transitions.end()) {
0095         (*transitionIt).advance(presentTime);
0096         ++transitionIt;
0097     }
0098 
0099     effects->prePaintScreen(data, presentTime);
0100 }
0101 
0102 void DimInactiveEffect::paintWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, QRegion region, WindowPaintData &data)
0103 {
0104     auto transitionIt = m_transitions.constFind(w);
0105     if (transitionIt != m_transitions.constEnd()) {
0106         const qreal transitionProgress = (*transitionIt).value();
0107         dimWindow(data, m_dimStrength * transitionProgress);
0108         effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0109         return;
0110     }
0111 
0112     auto forceIt = m_forceDim.constFind(w);
0113     if (forceIt != m_forceDim.constEnd()) {
0114         const qreal forcedStrength = *forceIt;
0115         dimWindow(data, forcedStrength);
0116         effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0117         return;
0118     }
0119 
0120     if (canDimWindow(w)) {
0121         dimWindow(data, m_dimStrength);
0122     }
0123 
0124     effects->paintWindow(renderTarget, viewport, w, mask, region, data);
0125 }
0126 
0127 void DimInactiveEffect::postPaintScreen()
0128 {
0129     if (m_fullScreenTransition.active) {
0130         if (m_fullScreenTransition.timeLine.done()) {
0131             m_fullScreenTransition.active = false;
0132         }
0133         effects->addRepaintFull();
0134     }
0135 
0136     auto transitionIt = m_transitions.begin();
0137     while (transitionIt != m_transitions.end()) {
0138         EffectWindow *w = transitionIt.key();
0139         if ((*transitionIt).done()) {
0140             transitionIt = m_transitions.erase(transitionIt);
0141         } else {
0142             ++transitionIt;
0143         }
0144         w->addRepaintFull();
0145     }
0146 
0147     effects->postPaintScreen();
0148 }
0149 
0150 void DimInactiveEffect::dimWindow(WindowPaintData &data, qreal strength)
0151 {
0152     qreal dimFactor;
0153     if (m_fullScreenTransition.active) {
0154         dimFactor = 1.0 - m_fullScreenTransition.timeLine.value();
0155     } else if (effects->activeFullScreenEffect()) {
0156         dimFactor = 0.0;
0157     } else {
0158         dimFactor = 1.0;
0159     }
0160 
0161     data.multiplyBrightness(1.0 - strength * dimFactor);
0162     data.multiplySaturation(1.0 - strength * dimFactor);
0163 }
0164 
0165 bool DimInactiveEffect::canDimWindow(const EffectWindow *w) const
0166 {
0167     if (m_activeWindow == w) {
0168         return false;
0169     }
0170 
0171     if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) {
0172         return false;
0173     }
0174 
0175     if (w->isDock() && !m_dimPanels) {
0176         return false;
0177     }
0178 
0179     if (w->isDesktop() && !m_dimDesktop) {
0180         return false;
0181     }
0182 
0183     if (w->keepAbove() && !m_dimKeepAbove) {
0184         return false;
0185     }
0186 
0187     if (w->isFullScreen() && !m_dimFullScreen) {
0188         return false;
0189     }
0190 
0191     if (w->isPopupWindow() || w->isInputMethod()) {
0192         return false;
0193     }
0194 
0195     if (w->isX11Client() && !w->isManaged()) {
0196         return false;
0197     }
0198 
0199     return w->isNormalWindow()
0200         || w->isDialog()
0201         || w->isUtility()
0202         || w->isDock()
0203         || w->isDesktop();
0204 }
0205 
0206 void DimInactiveEffect::scheduleInTransition(EffectWindow *w)
0207 {
0208     TimeLine &timeLine = m_transitions[w];
0209     timeLine.setDuration(
0210         std::chrono::milliseconds(static_cast<int>(animationTime(160))));
0211     if (timeLine.done()) {
0212         // If the Out animation is still active, then we're trucating
0213         // duration of the timeline(from 250ms to 160ms). If the timeline
0214         // is about to be finished with the old duration, then after
0215         // changing duration it will be in the "done" state. Thus, we
0216         // have to reset the timeline, otherwise it won't update progress.
0217         timeLine.reset();
0218     }
0219     timeLine.setDirection(TimeLine::Backward);
0220     timeLine.setEasingCurve(QEasingCurve::InOutSine);
0221 }
0222 
0223 void DimInactiveEffect::scheduleGroupInTransition(EffectWindow *w)
0224 {
0225     if (!m_dimByGroup) {
0226         scheduleInTransition(w);
0227         return;
0228     }
0229 
0230     if (!w->group()) {
0231         scheduleInTransition(w);
0232         return;
0233     }
0234 
0235     const auto members = w->group()->members();
0236     for (EffectWindow *member : members) {
0237         scheduleInTransition(member);
0238     }
0239 }
0240 
0241 void DimInactiveEffect::scheduleOutTransition(EffectWindow *w)
0242 {
0243     TimeLine &timeLine = m_transitions[w];
0244     timeLine.setDuration(
0245         std::chrono::milliseconds(static_cast<int>(animationTime(250))));
0246     if (timeLine.done()) {
0247         timeLine.reset();
0248     }
0249     timeLine.setDirection(TimeLine::Forward);
0250     timeLine.setEasingCurve(QEasingCurve::InOutSine);
0251 }
0252 
0253 void DimInactiveEffect::scheduleGroupOutTransition(EffectWindow *w)
0254 {
0255     if (!m_dimByGroup) {
0256         scheduleOutTransition(w);
0257         return;
0258     }
0259 
0260     if (!w->group()) {
0261         scheduleOutTransition(w);
0262         return;
0263     }
0264 
0265     const auto members = w->group()->members();
0266     for (EffectWindow *member : members) {
0267         scheduleOutTransition(member);
0268     }
0269 }
0270 
0271 void DimInactiveEffect::scheduleRepaint(EffectWindow *w)
0272 {
0273     if (!m_dimByGroup) {
0274         w->addRepaintFull();
0275         return;
0276     }
0277 
0278     if (!w->group()) {
0279         w->addRepaintFull();
0280         return;
0281     }
0282 
0283     const auto members = w->group()->members();
0284     for (EffectWindow *member : members) {
0285         member->addRepaintFull();
0286     }
0287 }
0288 
0289 void DimInactiveEffect::windowActivated(EffectWindow *w)
0290 {
0291     if (!w) {
0292         return;
0293     }
0294 
0295     if (m_activeWindow == w) {
0296         return;
0297     }
0298 
0299     if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) {
0300         m_activeWindow = w;
0301         return;
0302     }
0303 
0304     // WORKAROUND: Deleted windows do not belong to any of window groups.
0305     // So, if one of windows in a window group is closed, the In transition
0306     // will be false-triggered for the rest of the window group. In addition
0307     // to the active window, keep track of active window group so we can
0308     // tell whether "focus" moved from a closed window to some other window
0309     // in a window group.
0310     if (m_dimByGroup && w->group() && w->group() == m_activeWindowGroup) {
0311         m_activeWindow = w;
0312         return;
0313     }
0314 
0315     EffectWindow *previousActiveWindow = m_activeWindow;
0316     m_activeWindow = canDimWindow(w) ? w : nullptr;
0317 
0318     m_activeWindowGroup = (m_dimByGroup && m_activeWindow)
0319         ? m_activeWindow->group()
0320         : nullptr;
0321 
0322     if (previousActiveWindow) {
0323         scheduleGroupOutTransition(previousActiveWindow);
0324         scheduleRepaint(previousActiveWindow);
0325     }
0326 
0327     if (m_activeWindow) {
0328         scheduleGroupInTransition(m_activeWindow);
0329         scheduleRepaint(m_activeWindow);
0330     }
0331 }
0332 
0333 void DimInactiveEffect::windowAdded(EffectWindow *w)
0334 {
0335     connect(w, &EffectWindow::windowKeepAboveChanged,
0336             this, &DimInactiveEffect::updateActiveWindow);
0337     connect(w, &EffectWindow::windowFullScreenChanged,
0338             this, &DimInactiveEffect::updateActiveWindow);
0339 }
0340 
0341 void DimInactiveEffect::windowClosed(EffectWindow *w)
0342 {
0343     // When a window is closed, we should force current dim strength that
0344     // is applied to it to avoid flickering when some effect animates
0345     // the disappearing of the window. If there is no such effect then
0346     // it won't be dimmed.
0347     qreal forcedStrength = 0.0;
0348     bool shouldForceDim = false;
0349 
0350     auto transitionIt = m_transitions.find(w);
0351     if (transitionIt != m_transitions.end()) {
0352         forcedStrength = m_dimStrength * (*transitionIt).value();
0353         shouldForceDim = true;
0354         m_transitions.erase(transitionIt);
0355     } else if (m_activeWindow == w) {
0356         forcedStrength = 0.0;
0357         shouldForceDim = true;
0358     } else if (m_dimByGroup && belongToSameGroup(m_activeWindow, w)) {
0359         forcedStrength = 0.0;
0360         shouldForceDim = true;
0361     } else if (canDimWindow(w)) {
0362         forcedStrength = m_dimStrength;
0363         shouldForceDim = true;
0364     }
0365 
0366     if (shouldForceDim) {
0367         m_forceDim.insert(w, forcedStrength);
0368     }
0369 
0370     if (m_activeWindow == w) {
0371         m_activeWindow = nullptr;
0372     }
0373 }
0374 
0375 void DimInactiveEffect::windowDeleted(EffectWindow *w)
0376 {
0377     m_forceDim.remove(w);
0378 
0379     // FIXME: Sometimes we can miss the window close signal because KWin
0380     // can activate a window that is not ready for painting and the window
0381     // gets destroyed immediately. So, we have to remove active transitions
0382     // for that window here, otherwise we'll crash in postPaintScreen.
0383     m_transitions.remove(w);
0384     if (m_activeWindow == w) {
0385         m_activeWindow = nullptr;
0386     }
0387 }
0388 
0389 void DimInactiveEffect::activeFullScreenEffectChanged()
0390 {
0391     if (m_fullScreenTransition.timeLine.done()) {
0392         m_fullScreenTransition.timeLine.reset();
0393     }
0394     m_fullScreenTransition.timeLine.setDirection(
0395         effects->activeFullScreenEffect()
0396             ? TimeLine::Forward
0397             : TimeLine::Backward);
0398     m_fullScreenTransition.active = true;
0399 
0400     effects->addRepaintFull();
0401 }
0402 
0403 void DimInactiveEffect::updateActiveWindow(EffectWindow *w)
0404 {
0405     if (effects->activeWindow() == nullptr) {
0406         return;
0407     }
0408 
0409     if (effects->activeWindow() != w) {
0410         return;
0411     }
0412 
0413     // Need to reset m_activeWindow because canDimWindow depends on it.
0414     m_activeWindow = nullptr;
0415 
0416     m_activeWindow = canDimWindow(w) ? w : nullptr;
0417 }
0418 
0419 } // namespace KWin
0420 
0421 #include "moc_diminactive.cpp"