File indexing completed on 2024-04-21 05:31:12

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