File indexing completed on 2024-11-10 04:56:58
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"