1 /*
2     SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org>
3     SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "abstractwindowinterface.h"
9 
10 // local
11 #include "tracker/schemes.h"
12 #include "tracker/windowstracker.h"
13 #include "../lattecorona.h"
14 
15 // Qt
16 #include <QDebug>
17 #include <QtDBus>
18 
19 // KDE
20 #include <KWindowSystem>
21 #include <KActivities/Controller>
22 
23 namespace Latte {
24 namespace WindowSystem {
25 
26 #define MAXPLASMAPANELTHICKNESS 96
27 #define MAXSIDEPANELTHICKNESS 512
28 
29 #define KWINSERVICE "org.kde.KWin"
30 #define KWINVIRTUALDESKTOPMANAGERNAMESPACE "org.kde.KWin.VirtualDesktopManager"
31 
AbstractWindowInterface(QObject * parent)32 AbstractWindowInterface::AbstractWindowInterface(QObject *parent)
33     : QObject(parent),
34       m_kwinServiceWatcher(new QDBusServiceWatcher(this))
35 {
36     m_activities = new KActivities::Consumer(this);
37     m_currentActivity = m_activities->currentActivity();
38 
39     m_corona = qobject_cast<Latte::Corona *>(parent);
40     m_windowsTracker = new Tracker::Windows(this);
41     m_schemesTracker = new Tracker::Schemes(this);
42 
43     rulesConfig = KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc"));
44 
45     m_windowWaitingTimer.setInterval(150);
46     m_windowWaitingTimer.setSingleShot(true);
47 
48     connect(&m_windowWaitingTimer, &QTimer::timeout, this, [&]() {
49         WindowId wid = m_windowChangedWaiting;
50         m_windowChangedWaiting = QVariant();
51         emit windowChanged(wid);
52     });
53 
54     connect(this, &AbstractWindowInterface::windowRemoved, this, &AbstractWindowInterface::windowRemovedSlot);
55 
56     // connect(this, &AbstractWindowInterface::windowChanged, this, [&](WindowId wid) {
57     //     qDebug() << "WINDOW CHANGED ::: " << wid;
58     // });
59 
60     connect(m_activities.data(), &KActivities::Consumer::currentActivityChanged, this, [&](const QString &id) {
61         m_currentActivity = id;
62         emit currentActivityChanged();
63     });
64 
65     connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &AbstractWindowInterface::setIsShowingDesktop);
66 
67     //! KWin Service tracking
68     m_kwinServiceWatcher->setConnection(QDBusConnection::sessionBus());
69     m_kwinServiceWatcher->setWatchedServices(QStringList({KWINSERVICE}));
70     connect(m_kwinServiceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString & serviceName) {
71         if (serviceName == KWINSERVICE && !m_isKWinInterfaceAvailable) {
72             initKWinInterface();
73         }
74     });
75 
76     connect(m_kwinServiceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString & serviceName) {
77         if (serviceName == KWINSERVICE && m_isKWinInterfaceAvailable) {
78             m_isKWinInterfaceAvailable = false;
79         }
80     });
81 
82     initKWinInterface();
83 }
84 
~AbstractWindowInterface()85 AbstractWindowInterface::~AbstractWindowInterface()
86 {
87     m_windowWaitingTimer.stop();
88 
89     m_schemesTracker->deleteLater();
90     m_windowsTracker->deleteLater();
91 }
92 
isShowingDesktop() const93 bool AbstractWindowInterface::isShowingDesktop() const
94 {
95     return m_isShowingDesktop;
96 }
97 
setIsShowingDesktop(const bool & showing)98 void AbstractWindowInterface::setIsShowingDesktop(const bool &showing)
99 {
100     if (m_isShowingDesktop == showing) {
101         return;
102     }
103 
104     m_isShowingDesktop = showing;
105     emit isShowingDesktopChanged();
106 }
107 
currentDesktop()108 QString AbstractWindowInterface::currentDesktop()
109 {
110     return m_currentDesktop;
111 }
112 
currentActivity()113 QString AbstractWindowInterface::currentActivity()
114 {
115     return m_currentActivity;
116 }
117 
corona()118 Latte::Corona *AbstractWindowInterface::corona()
119 {
120     return m_corona;
121 }
122 
schemesTracker()123 Tracker::Schemes *AbstractWindowInterface::schemesTracker()
124 {
125     return m_schemesTracker;
126 }
127 
windowsTracker() const128 Tracker::Windows *AbstractWindowInterface::windowsTracker() const
129 {
130     return m_windowsTracker;
131 }
132 
isIgnored(const WindowId & wid) const133 bool AbstractWindowInterface::isIgnored(const WindowId &wid) const
134 {
135     return m_ignoredWindows.contains(wid);
136 }
137 
isFullScreenWindow(const QRect & wGeometry) const138 bool AbstractWindowInterface::isFullScreenWindow(const QRect &wGeometry) const
139 {
140     if (wGeometry.isEmpty()) {
141         return false;
142     }
143 
144     for (const auto scr : qGuiApp->screens()) {
145         auto screenGeometry = scr->geometry();
146 
147         if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) {
148             //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
149             auto factor = scr->devicePixelRatio();
150             screenGeometry = QRect(qRound(screenGeometry.x() * factor),
151                                    qRound(screenGeometry.y() * factor),
152                                    qRound(screenGeometry.width() * factor),
153                                    qRound(screenGeometry.height() * factor));
154         }
155 
156 
157         if (wGeometry == screenGeometry) {
158             return true;
159         }
160     }
161 
162     return false;
163 }
164 
isPlasmaPanel(const QRect & wGeometry) const165 bool AbstractWindowInterface::isPlasmaPanel(const QRect &wGeometry) const
166 {
167     if (wGeometry.isEmpty()) {
168         return false;
169     }
170 
171     bool isTouchingHorizontalEdge{false};
172     bool isTouchingVerticalEdge{false};
173 
174     for (const auto scr : qGuiApp->screens()) {
175         auto screenGeometry = scr->geometry();
176 
177         if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) {
178             //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
179             auto factor = scr->devicePixelRatio();
180             screenGeometry = QRect(qRound(screenGeometry.x() * factor),
181                                    qRound(screenGeometry.y() * factor),
182                                    qRound(screenGeometry.width() * factor),
183                                    qRound(screenGeometry.height() * factor));
184         }
185 
186         if (screenGeometry.contains(wGeometry.center())) {
187             if (wGeometry.y() == screenGeometry.y() || wGeometry.bottom() == screenGeometry.bottom()) {
188                 isTouchingHorizontalEdge = true;
189             }
190 
191             if (wGeometry.left() == screenGeometry.left() || wGeometry.right() == screenGeometry.right()) {
192                 isTouchingVerticalEdge = true;
193             }
194 
195             if (isTouchingVerticalEdge && isTouchingHorizontalEdge) {
196                 break;
197             }
198         }
199     }
200 
201     if ((isTouchingHorizontalEdge && wGeometry.height() < MAXPLASMAPANELTHICKNESS)
202             || (isTouchingVerticalEdge && wGeometry.width() < MAXPLASMAPANELTHICKNESS)) {
203         return true;
204     }
205 
206     return false;
207 }
208 
isSidepanel(const QRect & wGeometry) const209 bool AbstractWindowInterface::isSidepanel(const QRect &wGeometry) const
210 {
211     bool isVertical = wGeometry.height() > wGeometry.width();
212 
213     int thickness = qMin(wGeometry.width(), wGeometry.height());
214     int length = qMax(wGeometry.width(), wGeometry.height());
215 
216     QRect screenGeometry;
217 
218     for (const auto scr : qGuiApp->screens()) {
219         auto curScrGeometry = scr->geometry();
220 
221         if (KWindowSystem::isPlatformX11() && scr->devicePixelRatio() != 1.0) {
222             //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
223             auto factor = scr->devicePixelRatio();
224             curScrGeometry = QRect(qRound(curScrGeometry.x() * factor),
225                                    qRound(curScrGeometry.y() * factor),
226                                    qRound(curScrGeometry.width() * factor),
227                                    qRound(curScrGeometry.height() * factor));
228         }
229 
230         if (curScrGeometry.contains(wGeometry.center())) {
231             screenGeometry = curScrGeometry;
232             break;
233         }
234     }
235 
236     bool thicknessIsAcccepted = isVertical && ((thickness > MAXPLASMAPANELTHICKNESS) && (thickness < MAXSIDEPANELTHICKNESS));
237     bool lengthIsAccepted = isVertical && !screenGeometry.isEmpty() && (length > 0.6 * screenGeometry.height());
238     float sideRatio = (float)wGeometry.width() / (float)wGeometry.height();
239 
240     return (thicknessIsAcccepted && lengthIsAccepted && sideRatio<0.4);
241 }
242 
hasBlockedTracking(const WindowId & wid) const243 bool AbstractWindowInterface::hasBlockedTracking(const WindowId &wid) const
244 {
245     return (!isWhitelistedWindow(wid) && (isRegisteredPlasmaIgnoredWindow(wid) || isIgnored(wid)));
246 }
247 
isRegisteredPlasmaIgnoredWindow(const WindowId & wid) const248 bool AbstractWindowInterface::isRegisteredPlasmaIgnoredWindow(const WindowId &wid) const
249 {
250     return m_plasmaIgnoredWindows.contains(wid);
251 }
252 
isWhitelistedWindow(const WindowId & wid) const253 bool AbstractWindowInterface::isWhitelistedWindow(const WindowId &wid) const
254 {
255     return m_whitelistedWindows.contains(wid);
256 }
257 
inCurrentDesktopActivity(const WindowInfoWrap & winfo)258 bool AbstractWindowInterface::inCurrentDesktopActivity(const WindowInfoWrap &winfo)
259 {
260     return (winfo.isValid() && winfo.isOnDesktop(currentDesktop()) && winfo.isOnActivity(currentActivity()));
261 }
262 
263 //! KWin Interface
isKWinRunning() const264 bool AbstractWindowInterface::isKWinRunning() const
265 {
266     return m_isKWinInterfaceAvailable;
267 }
268 
initKWinInterface()269 void AbstractWindowInterface::initKWinInterface()
270 {
271     QDBusInterface kwinIface(KWINSERVICE, "/VirtualDesktopManager", KWINVIRTUALDESKTOPMANAGERNAMESPACE, QDBusConnection::sessionBus());
272 
273     if (kwinIface.isValid() && !m_isKWinInterfaceAvailable) {
274         m_isKWinInterfaceAvailable = true;
275         qDebug() << " KWIN SERVICE :: is available...";
276         m_isVirtualDesktopNavigationWrappingAround = kwinIface.property("navigationWrappingAround").toBool();
277 
278         QDBusConnection bus = QDBusConnection::sessionBus();
279         bool signalconnected = bus.connect(KWINSERVICE,
280                                            "/VirtualDesktopManager",
281                                            KWINVIRTUALDESKTOPMANAGERNAMESPACE,
282                                            "navigationWrappingAroundChanged",
283                                            this,
284                                            SLOT(onVirtualDesktopNavigationWrappingAroundChanged(bool)));
285 
286         if (!signalconnected) {
287             qDebug() << " KWIN SERVICE :: Virtual Desktop Manager :: navigationsWrappingSignal is not connected...";
288         }
289     }
290 }
291 
isVirtualDesktopNavigationWrappingAround() const292 bool AbstractWindowInterface::isVirtualDesktopNavigationWrappingAround() const
293 {
294     return m_isVirtualDesktopNavigationWrappingAround;
295 }
296 
onVirtualDesktopNavigationWrappingAroundChanged(bool navigationWrappingAround)297 void AbstractWindowInterface::onVirtualDesktopNavigationWrappingAroundChanged(bool navigationWrappingAround)
298 {
299     m_isVirtualDesktopNavigationWrappingAround = navigationWrappingAround;
300 }
301 
302 //! Register Latte Ignored Windows in order to NOT be tracked
registerIgnoredWindow(WindowId wid)303 void AbstractWindowInterface::registerIgnoredWindow(WindowId wid)
304 {
305     if (!wid.isNull() && !m_ignoredWindows.contains(wid)) {
306         m_ignoredWindows.append(wid);
307         emit windowChanged(wid);
308     }
309 }
310 
unregisterIgnoredWindow(WindowId wid)311 void AbstractWindowInterface::unregisterIgnoredWindow(WindowId wid)
312 {
313     if (m_ignoredWindows.contains(wid)) {
314         m_ignoredWindows.removeAll(wid);
315         emit windowRemoved(wid);
316     }
317 }
318 
registerPlasmaIgnoredWindow(WindowId wid)319 void AbstractWindowInterface::registerPlasmaIgnoredWindow(WindowId wid)
320 {
321     if (!wid.isNull() && !m_plasmaIgnoredWindows.contains(wid)) {
322         m_plasmaIgnoredWindows.append(wid);
323         emit windowChanged(wid);
324     }
325 }
326 
unregisterPlasmaIgnoredWindow(WindowId wid)327 void AbstractWindowInterface::unregisterPlasmaIgnoredWindow(WindowId wid)
328 {
329     if (m_plasmaIgnoredWindows.contains(wid)) {
330         m_plasmaIgnoredWindows.removeAll(wid);
331     }
332 }
333 
registerWhitelistedWindow(WindowId wid)334 void AbstractWindowInterface::registerWhitelistedWindow(WindowId wid)
335 {
336     if (!wid.isNull() && !m_whitelistedWindows.contains(wid)) {
337         m_whitelistedWindows.append(wid);
338         emit windowChanged(wid);
339     }
340 }
341 
unregisterWhitelistedWindow(WindowId wid)342 void AbstractWindowInterface::unregisterWhitelistedWindow(WindowId wid)
343 {
344     if (m_whitelistedWindows.contains(wid)) {
345         m_whitelistedWindows.removeAll(wid);
346     }
347 }
348 
windowRemovedSlot(WindowId wid)349 void AbstractWindowInterface::windowRemovedSlot(WindowId wid)
350 {
351     if (m_plasmaIgnoredWindows.contains(wid)) {
352         unregisterPlasmaIgnoredWindow(wid);
353     }
354 
355     if (m_ignoredWindows.contains(wid)) {
356         unregisterIgnoredWindow(wid);
357     }
358 
359     if (m_whitelistedWindows.contains(wid)) {
360         unregisterWhitelistedWindow(wid);
361     }
362 }
363 
364 //! Activities switching
switchToNextActivity()365 void AbstractWindowInterface::switchToNextActivity()
366 {
367     QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running);
368     if (runningActivities.count() <= 1) {
369         return;
370     }
371 
372     int curPos = runningActivities.indexOf(m_currentActivity);
373     int nextPos = curPos + 1;
374 
375     if (curPos == runningActivities.count() -1) {
376         nextPos = 0;
377     }
378 
379     KActivities::Controller activitiesController;
380     activitiesController.setCurrentActivity(runningActivities.at(nextPos));
381 }
382 
switchToPreviousActivity()383 void AbstractWindowInterface::switchToPreviousActivity()
384 {
385     QStringList runningActivities = m_activities->activities(KActivities::Info::State::Running);
386     if (runningActivities.count() <= 1) {
387         return;
388     }
389 
390     int curPos = runningActivities.indexOf(m_currentActivity);
391     int nextPos = curPos - 1;
392 
393     if (curPos == 0) {
394         nextPos = runningActivities.count() - 1;
395     }
396 
397     KActivities::Controller activitiesController;
398     activitiesController.setCurrentActivity(runningActivities.at(nextPos));
399 }
400 
401 //! Delay window changed trigerring
considerWindowChanged(WindowId wid)402 void AbstractWindowInterface::considerWindowChanged(WindowId wid)
403 {
404     //! Consider if the windowChanged signal should be sent DIRECTLY or WAIT
405 
406     if (m_windowChangedWaiting == wid && m_windowWaitingTimer.isActive()) {
407         //! window should be sent later
408         m_windowWaitingTimer.start();
409         return;
410     }
411 
412     if (m_windowChangedWaiting != wid && !m_windowWaitingTimer.isActive()) {
413         //! window should be sent later
414         m_windowChangedWaiting = wid;
415         m_windowWaitingTimer.start();
416     }
417 
418     if (m_windowChangedWaiting != wid && m_windowWaitingTimer.isActive()) {
419         m_windowWaitingTimer.stop();
420         //! sent previous waiting window
421         emit windowChanged(m_windowChangedWaiting);
422 
423         //! retrigger waiting for the upcoming window
424         m_windowChangedWaiting = wid;
425         m_windowWaitingTimer.start();
426     }
427 }
428 
429 }
430 }
431 
432