File indexing completed on 2025-04-20 05:01:03
0001 /* 0002 SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org> 0003 SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "abstractwindowinterface.h" 0009 0010 // local 0011 #include "tracker/schemes.h" 0012 #include "tracker/windowstracker.h" 0013 #include "../lattecorona.h" 0014 0015 // Qt 0016 #include <QDebug> 0017 #include <QtDBus> 0018 0019 // KDE 0020 #include <KWindowSystem> 0021 #include <KActivities/Controller> 0022 0023 namespace Latte { 0024 namespace WindowSystem { 0025 0026 #define MAXPLASMAPANELTHICKNESS 96 0027 #define MAXSIDEPANELTHICKNESS 512 0028 0029 #define KWINSERVICE "org.kde.KWin" 0030 #define KWINVIRTUALDESKTOPMANAGERNAMESPACE "org.kde.KWin.VirtualDesktopManager" 0031 0032 AbstractWindowInterface::AbstractWindowInterface(QObject *parent) 0033 : QObject(parent), 0034 m_kwinServiceWatcher(new QDBusServiceWatcher(this)) 0035 { 0036 m_activities = new KActivities::Consumer(this); 0037 m_currentActivity = m_activities->currentActivity(); 0038 0039 m_corona = qobject_cast<Latte::Corona *>(parent); 0040 m_windowsTracker = new Tracker::Windows(this); 0041 m_schemesTracker = new Tracker::Schemes(this); 0042 0043 rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")); 0044 0045 m_windowWaitingTimer.setInterval(150); 0046 m_windowWaitingTimer.setSingleShot(true); 0047 0048 connect(&m_windowWaitingTimer, &QTimer::timeout, this, [&]() { 0049 WindowId wid = m_windowChangedWaiting; 0050 m_windowChangedWaiting = QVariant(); 0051 emit windowChanged(wid); 0052 }); 0053 0054 connect(this, &AbstractWindowInterface::windowRemoved, this, &AbstractWindowInterface::windowRemovedSlot); 0055 0056 // connect(this, &AbstractWindowInterface::windowChanged, this, [&](WindowId wid) { 0057 // qDebug() << "WINDOW CHANGED ::: " << wid; 0058 // }); 0059 0060 connect(m_activities.data(), &KActivities::Consumer::currentActivityChanged, this, [&](const QString &id) { 0061 m_currentActivity = id; 0062 emit currentActivityChanged(); 0063 }); 0064 0065 connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &AbstractWindowInterface::setIsShowingDesktop); 0066 0067 //! KWin Service tracking 0068 m_kwinServiceWatcher->setConnection(QDBusConnection::sessionBus()); 0069 m_kwinServiceWatcher->setWatchedServices(QStringList({KWINSERVICE})); 0070 connect(m_kwinServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString & serviceName) { 0071 if (serviceName == KWINSERVICE && !m_isKWinInterfaceAvailable) { 0072 initKWinInterface(); 0073 } 0074 }); 0075 0076 connect(m_kwinServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString & serviceName) { 0077 if (serviceName == KWINSERVICE && m_isKWinInterfaceAvailable) { 0078 m_isKWinInterfaceAvailable = false; 0079 } 0080 }); 0081 0082 initKWinInterface(); 0083 } 0084 0085 AbstractWindowInterface::~AbstractWindowInterface() 0086 { 0087 m_windowWaitingTimer.stop(); 0088 0089 m_schemesTracker->deleteLater(); 0090 m_windowsTracker->deleteLater(); 0091 } 0092 0093 bool AbstractWindowInterface::isShowingDesktop() const 0094 { 0095 return m_isShowingDesktop; 0096 } 0097 0098 void AbstractWindowInterface::setIsShowingDesktop(const bool &showing) 0099 { 0100 if (m_isShowingDesktop == showing) { 0101 return; 0102 } 0103 0104 m_isShowingDesktop = showing; 0105 emit isShowingDesktopChanged(); 0106 } 0107 0108 QString AbstractWindowInterface::currentDesktop() 0109 { 0110 return m_currentDesktop; 0111 } 0112 0113 QString AbstractWindowInterface::currentActivity() 0114 { 0115 return m_currentActivity; 0116 } 0117 0118 Latte::Corona *AbstractWindowInterface::corona() 0119 { 0120 return m_corona; 0121 } 0122 0123 Tracker::Schemes *AbstractWindowInterface::schemesTracker() 0124 { 0125 return m_schemesTracker; 0126 } 0127 0128 Tracker::Windows *AbstractWindowInterface::windowsTracker() const 0129 { 0130 return m_windowsTracker; 0131 } 0132 0133 bool AbstractWindowInterface::isIgnored(const WindowId &wid) const 0134 { 0135 return m_ignoredWindows.contains(wid); 0136 } 0137 0138 bool AbstractWindowInterface::isFullScreenWindow(const QRect &wGeometry) const 0139 { 0140 if (wGeometry.isEmpty()) { 0141 return false; 0142 } 0143 0144 for (const auto scr : qGuiApp->screens()) { 0145 auto screenGeometry = scr->geometry(); 0146 0147 if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) { 0148 //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate 0149 auto factor = scr->devicePixelRatio(); 0150 screenGeometry = QRect(qRound(screenGeometry.x() * factor), 0151 qRound(screenGeometry.y() * factor), 0152 qRound(screenGeometry.width() * factor), 0153 qRound(screenGeometry.height() * factor)); 0154 } 0155 0156 0157 if (wGeometry == screenGeometry) { 0158 return true; 0159 } 0160 } 0161 0162 return false; 0163 } 0164 0165 bool AbstractWindowInterface::isPlasmaPanel(const QRect &wGeometry) const 0166 { 0167 if (wGeometry.isEmpty()) { 0168 return false; 0169 } 0170 0171 bool isTouchingHorizontalEdge{false}; 0172 bool isTouchingVerticalEdge{false}; 0173 0174 for (const auto scr : qGuiApp->screens()) { 0175 auto screenGeometry = scr->geometry(); 0176 0177 if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) { 0178 //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate 0179 auto factor = scr->devicePixelRatio(); 0180 screenGeometry = QRect(qRound(screenGeometry.x() * factor), 0181 qRound(screenGeometry.y() * factor), 0182 qRound(screenGeometry.width() * factor), 0183 qRound(screenGeometry.height() * factor)); 0184 } 0185 0186 if (screenGeometry.contains(wGeometry.center())) { 0187 if (wGeometry.y() == screenGeometry.y() || wGeometry.bottom() == screenGeometry.bottom()) { 0188 isTouchingHorizontalEdge = true; 0189 } 0190 0191 if (wGeometry.left() == screenGeometry.left() || wGeometry.right() == screenGeometry.right()) { 0192 isTouchingVerticalEdge = true; 0193 } 0194 0195 if (isTouchingVerticalEdge && isTouchingHorizontalEdge) { 0196 break; 0197 } 0198 } 0199 } 0200 0201 if ((isTouchingHorizontalEdge && wGeometry.height() < MAXPLASMAPANELTHICKNESS) 0202 || (isTouchingVerticalEdge && wGeometry.width() < MAXPLASMAPANELTHICKNESS)) { 0203 return true; 0204 } 0205 0206 return false; 0207 } 0208 0209 bool AbstractWindowInterface::isSidepanel(const QRect &wGeometry) const 0210 { 0211 bool isVertical = wGeometry.height() > wGeometry.width(); 0212 0213 int thickness = qMin(wGeometry.width(), wGeometry.height()); 0214 int length = qMax(wGeometry.width(), wGeometry.height()); 0215 0216 QRect screenGeometry; 0217 0218 for (const auto scr : qGuiApp->screens()) { 0219 auto curScrGeometry = scr->geometry(); 0220 0221 if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) { 0222 //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate 0223 auto factor = scr->devicePixelRatio(); 0224 curScrGeometry = QRect(qRound(curScrGeometry.x() * factor), 0225 qRound(curScrGeometry.y() * factor), 0226 qRound(curScrGeometry.width() * factor), 0227 qRound(curScrGeometry.height() * factor)); 0228 } 0229 0230 if (curScrGeometry.contains(wGeometry.center())) { 0231 screenGeometry = curScrGeometry; 0232 break; 0233 } 0234 } 0235 0236 bool thicknessIsAcccepted = isVertical && ((thickness > MAXPLASMAPANELTHICKNESS) && (thickness < MAXSIDEPANELTHICKNESS)); 0237 bool lengthIsAccepted = isVertical && !screenGeometry.isEmpty() && (length > 0.6 * screenGeometry.height()); 0238 float sideRatio = (float)wGeometry.width() / (float)wGeometry.height(); 0239 0240 return (thicknessIsAcccepted && lengthIsAccepted && sideRatio<0.4); 0241 } 0242 0243 bool AbstractWindowInterface::hasBlockedTracking(const WindowId &wid) const 0244 { 0245 return (!isWhitelistedWindow(wid) && (isRegisteredPlasmaIgnoredWindow(wid) || isIgnored(wid))); 0246 } 0247 0248 bool AbstractWindowInterface::isRegisteredPlasmaIgnoredWindow(const WindowId &wid) const 0249 { 0250 return m_plasmaIgnoredWindows.contains(wid); 0251 } 0252 0253 bool AbstractWindowInterface::isWhitelistedWindow(const WindowId &wid) const 0254 { 0255 return m_whitelistedWindows.contains(wid); 0256 } 0257 0258 bool AbstractWindowInterface::inCurrentDesktopActivity(const WindowInfoWrap &winfo) 0259 { 0260 return (winfo.isValid() && winfo.isOnDesktop(currentDesktop()) && winfo.isOnActivity(currentActivity())); 0261 } 0262 0263 //! KWin Interface 0264 bool AbstractWindowInterface::isKWinRunning() const 0265 { 0266 return m_isKWinInterfaceAvailable; 0267 } 0268 0269 void AbstractWindowInterface::initKWinInterface() 0270 { 0271 QDBusInterface kwinIface(KWINSERVICE, "/VirtualDesktopManager", KWINVIRTUALDESKTOPMANAGERNAMESPACE, QDBusConnection::sessionBus()); 0272 0273 if (kwinIface.isValid() && !m_isKWinInterfaceAvailable) { 0274 m_isKWinInterfaceAvailable = true; 0275 qDebug() << " KWIN SERVICE :: is available..."; 0276 m_isVirtualDesktopNavigationWrappingAround = kwinIface.property("navigationWrappingAround").toBool(); 0277 0278 QDBusConnection bus = QDBusConnection::sessionBus(); 0279 bool signalconnected = bus.connect(KWINSERVICE, 0280 "/VirtualDesktopManager", 0281 KWINVIRTUALDESKTOPMANAGERNAMESPACE, 0282 "navigationWrappingAroundChanged", 0283 this, 0284 SLOT(onVirtualDesktopNavigationWrappingAroundChanged(bool))); 0285 0286 if (!signalconnected) { 0287 qDebug() << " KWIN SERVICE :: Virtual Desktop Manager :: navigationsWrappingSignal is not connected..."; 0288 } 0289 } 0290 } 0291 0292 bool AbstractWindowInterface::isVirtualDesktopNavigationWrappingAround() const 0293 { 0294 return m_isVirtualDesktopNavigationWrappingAround; 0295 } 0296 0297 void AbstractWindowInterface::onVirtualDesktopNavigationWrappingAroundChanged(bool navigationWrappingAround) 0298 { 0299 m_isVirtualDesktopNavigationWrappingAround = navigationWrappingAround; 0300 } 0301 0302 //! Register Latte Ignored Windows in order to NOT be tracked 0303 void AbstractWindowInterface::registerIgnoredWindow(WindowId wid) 0304 { 0305 if (!wid.isNull() && !m_ignoredWindows.contains(wid)) { 0306 m_ignoredWindows.append(wid); 0307 emit windowChanged(wid); 0308 } 0309 } 0310 0311 void AbstractWindowInterface::unregisterIgnoredWindow(WindowId wid) 0312 { 0313 if (m_ignoredWindows.contains(wid)) { 0314 m_ignoredWindows.removeAll(wid); 0315 emit windowRemoved(wid); 0316 } 0317 } 0318 0319 void AbstractWindowInterface::registerPlasmaIgnoredWindow(WindowId wid) 0320 { 0321 if (!wid.isNull() && !m_plasmaIgnoredWindows.contains(wid)) { 0322 m_plasmaIgnoredWindows.append(wid); 0323 emit windowChanged(wid); 0324 } 0325 } 0326 0327 void AbstractWindowInterface::unregisterPlasmaIgnoredWindow(WindowId wid) 0328 { 0329 if (m_plasmaIgnoredWindows.contains(wid)) { 0330 m_plasmaIgnoredWindows.removeAll(wid); 0331 } 0332 } 0333 0334 void AbstractWindowInterface::registerWhitelistedWindow(WindowId wid) 0335 { 0336 if (!wid.isNull() && !m_whitelistedWindows.contains(wid)) { 0337 m_whitelistedWindows.append(wid); 0338 emit windowChanged(wid); 0339 } 0340 } 0341 0342 void AbstractWindowInterface::unregisterWhitelistedWindow(WindowId wid) 0343 { 0344 if (m_whitelistedWindows.contains(wid)) { 0345 m_whitelistedWindows.removeAll(wid); 0346 } 0347 } 0348 0349 void AbstractWindowInterface::windowRemovedSlot(WindowId wid) 0350 { 0351 if (m_plasmaIgnoredWindows.contains(wid)) { 0352 unregisterPlasmaIgnoredWindow(wid); 0353 } 0354 0355 if (m_ignoredWindows.contains(wid)) { 0356 unregisterIgnoredWindow(wid); 0357 } 0358 0359 if (m_whitelistedWindows.contains(wid)) { 0360 unregisterWhitelistedWindow(wid); 0361 } 0362 } 0363 0364 //! Activities switching 0365 void AbstractWindowInterface::switchToNextActivity() 0366 { 0367 QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running); 0368 if (runningActivities.count() <= 1) { 0369 return; 0370 } 0371 0372 int curPos = runningActivities.indexOf(m_currentActivity); 0373 int nextPos = curPos + 1; 0374 0375 if (curPos == runningActivities.count() -1) { 0376 nextPos = 0; 0377 } 0378 0379 KActivities::Controller activitiesController; 0380 activitiesController.setCurrentActivity(runningActivities.at(nextPos)); 0381 } 0382 0383 void AbstractWindowInterface::switchToPreviousActivity() 0384 { 0385 QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running); 0386 if (runningActivities.count() <= 1) { 0387 return; 0388 } 0389 0390 int curPos = runningActivities.indexOf(m_currentActivity); 0391 int nextPos = curPos - 1; 0392 0393 if (curPos == 0) { 0394 nextPos = runningActivities.count() - 1; 0395 } 0396 0397 KActivities::Controller activitiesController; 0398 activitiesController.setCurrentActivity(runningActivities.at(nextPos)); 0399 } 0400 0401 //! Delay window changed trigerring 0402 void AbstractWindowInterface::considerWindowChanged(WindowId wid) 0403 { 0404 //! Consider if the windowChanged signal should be sent DIRECTLY or WAIT 0405 0406 if (m_windowChangedWaiting == wid && m_windowWaitingTimer.isActive()) { 0407 //! window should be sent later 0408 m_windowWaitingTimer.start(); 0409 return; 0410 } 0411 0412 if (m_windowChangedWaiting != wid && !m_windowWaitingTimer.isActive()) { 0413 //! window should be sent later 0414 m_windowChangedWaiting = wid; 0415 m_windowWaitingTimer.start(); 0416 } 0417 0418 if (m_windowChangedWaiting != wid && m_windowWaitingTimer.isActive()) { 0419 m_windowWaitingTimer.stop(); 0420 //! sent previous waiting window 0421 emit windowChanged(m_windowChangedWaiting); 0422 0423 //! retrigger waiting for the upcoming window 0424 m_windowChangedWaiting = wid; 0425 m_windowWaitingTimer.start(); 0426 } 0427 } 0428 0429 } 0430 } 0431