1 /*
2     SPDX-FileCopyrightText: 2019 Michail Vourlakos <mvourlakos@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "windowstracker.h"
7 
8 // local
9 #include "lastactivewindow.h"
10 #include "schemes.h"
11 #include "trackedlayoutinfo.h"
12 #include "trackedviewinfo.h"
13 #include "../abstractwindowinterface.h"
14 #include "../schemecolors.h"
15 #include "../../apptypes.h"
16 #include "../../lattecorona.h"
17 #include "../../layout/genericlayout.h"
18 #include "../../layouts/manager.h"
19 #include "../../view/view.h"
20 #include "../../view/positioner.h"
21 
22 // Qt
23 #include <KWindowSystem>
24 
25 namespace Latte {
26 namespace WindowSystem {
27 namespace Tracker {
28 
Windows(AbstractWindowInterface * parent)29 Windows::Windows(AbstractWindowInterface *parent)
30     : QObject(parent)
31 {
32     m_wm = parent;
33 
34     m_extraViewHintsTimer.setInterval(600);
35     m_extraViewHintsTimer.setSingleShot(true);
36 
37     connect(&m_extraViewHintsTimer, &QTimer::timeout, this, &Windows::updateExtraViewHints);
38 
39     //! delayed application data
40     m_updateApplicationDataTimer.setInterval(1500);
41     m_updateApplicationDataTimer.setSingleShot(true);
42     connect(&m_updateApplicationDataTimer, &QTimer::timeout, this, &Windows::updateApplicationData);
43 
44     //! delayed update all hints
45     m_updateAllHintsTimer.setInterval(300);
46     m_updateAllHintsTimer.setSingleShot(true);
47     connect(&m_updateAllHintsTimer, &QTimer::timeout, this, &Windows::updateAllHints);
48 
49     init();
50 }
51 
~Windows()52 Windows::~Windows()
53 {
54     //! clear all the m_views tracking information
55     for (QHash<Latte::View *, TrackedViewInfo *>::iterator i=m_views.begin(); i!=m_views.end(); ++i) {
56         i.value()->deleteLater();
57         m_views[i.key()] = nullptr;
58     }
59 
60     m_views.clear();
61 
62     //! clear all the m_layouts tracking layouts
63     for (QHash<Latte::Layout::GenericLayout *, TrackedLayoutInfo *>::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) {
64         i.value()->deleteLater();
65         m_layouts[i.key()] = nullptr;
66     }
67 
68     m_layouts.clear();
69 }
70 
init()71 void Windows::init()
72 {
73     connect(m_wm, &AbstractWindowInterface::windowChanged, this, [&](WindowId wid) {
74         m_windows[wid] = m_wm->requestInfo(wid);
75         updateAllHints();
76 
77         emit windowChanged(wid);
78     });
79 
80     connect(m_wm, &AbstractWindowInterface::windowRemoved, this, [&](WindowId wid) {
81         m_windows.remove(wid);
82 
83         //! application data
84         m_initializedApplicationData.removeAll(wid);
85         m_delayedApplicationData.removeAll(wid);
86 
87         updateAllHints();
88 
89         emit windowRemoved(wid);
90     });
91 
92     connect(m_wm, &AbstractWindowInterface::windowAdded, this, [&](WindowId wid) {
93         if (!m_windows.contains(wid)) {
94             m_windows.insert(wid, m_wm->requestInfo(wid));
95         }
96         updateAllHints();
97     });
98 
99     connect(m_wm, &AbstractWindowInterface::activeWindowChanged, this, [&](WindowId wid) {
100         //! for some reason this is needed in order to update properly activeness values
101         //! when the active window changes the previous active windows should be also updated
102         for (const auto view : m_views.keys()) {
103             WindowId lastWinId = m_views[view]->lastActiveWindow()->currentWinId();
104             if ((lastWinId) != wid && m_windows.contains(lastWinId)) {
105                 m_windows[lastWinId] = m_wm->requestInfo(lastWinId);
106             }
107         }
108 
109         m_windows[wid] = m_wm->requestInfo(wid);
110         updateAllHints();
111 
112         emit activeWindowChanged(wid);
113     });
114 
115     connect(m_wm, &AbstractWindowInterface::currentDesktopChanged, this, &Windows::updateAllHints);
116     connect(m_wm, &AbstractWindowInterface::currentActivityChanged,  this, &Windows::updateAllHints);
117     connect(m_wm, &AbstractWindowInterface::isShowingDesktopChanged,  this, &Windows::updateAllHints);
118 }
119 
initLayoutHints(Latte::Layout::GenericLayout * layout)120 void Windows::initLayoutHints(Latte::Layout::GenericLayout *layout)
121 {
122     if (!m_layouts.contains(layout)) {
123         return;
124     }
125 
126     setActiveWindowMaximized(layout, false);
127     setExistsWindowActive(layout, false);
128     setExistsWindowMaximized(layout, false);
129     setActiveWindowScheme(layout, nullptr);
130 }
131 
initViewHints(Latte::View * view)132 void Windows::initViewHints(Latte::View *view)
133 {
134     if (!m_views.contains(view)) {
135         return;
136     }
137 
138     setActiveWindowMaximized(view, false);
139     setActiveWindowTouching(view, false);
140     setActiveWindowTouchingEdge(view, false);
141     setExistsWindowActive(view, false);
142     setExistsWindowTouching(view, false);
143     setExistsWindowTouchingEdge(view, false);
144     setExistsWindowMaximized(view, false);
145     setIsTouchingBusyVerticalView(view, false);
146     setActiveWindowScheme(view, nullptr);
147     setTouchingWindowScheme(view, nullptr);
148 }
149 
wm()150 AbstractWindowInterface *Windows::wm()
151 {
152     return m_wm;
153 }
154 
155 
addView(Latte::View * view)156 void Windows::addView(Latte::View *view)
157 {
158     if (m_views.contains(view)) {
159         return;
160     }
161 
162     m_views[view] = new TrackedViewInfo(this, view);
163 
164     updateScreenGeometries();
165 
166     //! Consider Layouts
167     addRelevantLayout(view);
168 
169     connect(view, &Latte::View::layoutChanged, this, [&, view]() {
170         addRelevantLayout(view);
171     });
172 
173     connect(view, &Latte::View::screenGeometryChanged, this, &Windows::updateScreenGeometries);
174 
175     connect(view, &Latte::View::isTouchingBottomViewAndIsBusyChanged, this, &Windows::updateExtraViewHints);
176     connect(view, &Latte::View::isTouchingTopViewAndIsBusyChanged, this, &Windows::updateExtraViewHints);
177     connect(view, &Latte::View::absoluteGeometryChanged, this, &Windows::updateAllHintsAfterTimer);
178 
179     updateAllHints();
180 
181     emit informationAnnounced(view);
182 }
183 
removeView(Latte::View * view)184 void Windows::removeView(Latte::View *view)
185 {
186     if (!m_views.contains(view)) {
187         return;
188     }
189 
190     m_views[view]->deleteLater();
191     m_views.remove(view);
192 
193     updateRelevantLayouts();
194 }
195 
addRelevantLayout(Latte::View * view)196 void Windows::addRelevantLayout(Latte::View *view)
197 {
198     if (view->layout()) {
199         bool initializing {false};
200 
201         if (!m_layouts.contains(view->layout())) {
202             initializing = true;
203             m_layouts[view->layout()] = new TrackedLayoutInfo(this, view->layout());
204         }
205 
206         //! Update always the AllScreens tracking because there is a chance a view delayed to be assigned in a layout
207         //! and that could create a state the AllScreens tracking will be disabled if there is a View requesting
208         //! tracking and one that it does not during startup
209         updateRelevantLayouts();
210 
211         if (initializing) {
212             updateHints(view->layout());
213             emit informationAnnouncedForLayout(view->layout());
214         }
215     }
216 }
217 
updateRelevantLayouts()218 void Windows::updateRelevantLayouts()
219 {
220     QList<Latte::Layout::GenericLayout*> orphanedLayouts;
221 
222     //! REMOVE Orphaned Relevant layouts that have been removed or they don't contain any Views anymore
223     for (QHash<Latte::Layout::GenericLayout *, TrackedLayoutInfo *>::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) {
224         bool hasView{false};
225         for (QHash<Latte::View *, TrackedViewInfo *>::iterator j=m_views.begin(); j!=m_views.end(); ++j) {
226             if (j.key() && i.key() && i.key() == j.key()->layout()) {
227                 hasView = true;
228                 break;
229             }
230         }
231 
232         if (!hasView)  {
233             if (i.value()) {
234                 i.value()->deleteLater();
235             }
236             orphanedLayouts << i.key();
237         }
238     }
239 
240     for(const auto &layout : orphanedLayouts) {
241         m_layouts.remove(layout);
242     }
243 
244     //! UPDATE Enabled layout window tracking based on the Views that are requesting windows tracking
245     for (QHash<Latte::Layout::GenericLayout *, TrackedLayoutInfo *>::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) {
246         bool hasViewEnabled{false};
247         for (QHash<Latte::View *, TrackedViewInfo *>::iterator j=m_views.begin(); j!=m_views.end(); ++j) {
248             if (i.key() == j.key()->layout() && j.value()->enabled()) {
249                 hasViewEnabled = true;
250                 break;
251             }
252         }
253 
254         if (i.value()) {
255             i.value()->setEnabled(hasViewEnabled);
256 
257             if (!hasViewEnabled) {
258                 initLayoutHints(i.key());
259             }
260         }
261     }
262 }
263 
264 //! Views Properties And Hints
265 
enabled(Latte::View * view)266 bool Windows::enabled(Latte::View *view)
267 {
268     if (!m_views.contains(view)) {
269         return false;
270     }
271 
272     return m_views[view]->enabled();
273 }
274 
setEnabled(Latte::View * view,const bool enabled)275 void Windows::setEnabled(Latte::View *view, const bool enabled)
276 {
277     if (!m_views.contains(view) || m_views[view]->enabled() == enabled) {
278         return;
279     }
280 
281     m_views[view]->setEnabled(enabled);
282 
283     if (enabled) {
284         updateHints(view);
285     } else {
286         initViewHints(view);
287     }
288 
289     updateRelevantLayouts();
290 
291     emit enabledChanged(view);
292 }
293 
activeWindowMaximized(Latte::View * view) const294 bool Windows::activeWindowMaximized(Latte::View *view) const
295 {
296     if (!m_views.contains(view)) {
297         return false;
298     }
299 
300     return m_views[view]->activeWindowMaximized();
301 }
302 
setActiveWindowMaximized(Latte::View * view,bool activeMaximized)303 void Windows::setActiveWindowMaximized(Latte::View *view, bool activeMaximized)
304 {
305     if (!m_views.contains(view) || m_views[view]->activeWindowMaximized() == activeMaximized) {
306         return;
307     }
308 
309     m_views[view]->setActiveWindowMaximized(activeMaximized);
310     emit activeWindowMaximizedChanged(view);
311 }
312 
activeWindowTouching(Latte::View * view) const313 bool Windows::activeWindowTouching(Latte::View *view) const
314 {
315     if (!m_views.contains(view)) {
316         return false;
317     }
318 
319     return m_views[view]->activeWindowTouching();
320 }
321 
setActiveWindowTouching(Latte::View * view,bool activeTouching)322 void Windows::setActiveWindowTouching(Latte::View *view, bool activeTouching)
323 {
324     if (!m_views.contains(view) || m_views[view]->activeWindowTouching() == activeTouching) {
325         return;
326     }
327 
328     m_views[view]->setActiveWindowTouching(activeTouching);
329     emit activeWindowTouchingChanged(view);
330 }
331 
activeWindowTouchingEdge(Latte::View * view) const332 bool Windows::activeWindowTouchingEdge(Latte::View *view) const
333 {
334     if (!m_views.contains(view)) {
335         return false;
336     }
337 
338     return m_views[view]->activeWindowTouchingEdge();
339 }
340 
setActiveWindowTouchingEdge(Latte::View * view,bool activeTouchingEdge)341 void Windows::setActiveWindowTouchingEdge(Latte::View *view, bool activeTouchingEdge)
342 {
343     if (!m_views.contains(view) || m_views[view]->activeWindowTouchingEdge() == activeTouchingEdge) {
344         return;
345     }
346 
347     m_views[view]->setActiveWindowTouchingEdge(activeTouchingEdge);
348     emit activeWindowTouchingEdgeChanged(view);
349 }
350 
existsWindowActive(Latte::View * view) const351 bool Windows::existsWindowActive(Latte::View *view) const
352 {
353     if (!m_views.contains(view)) {
354         return false;
355     }
356 
357     return m_views[view]->existsWindowActive();
358 }
359 
setExistsWindowActive(Latte::View * view,bool windowActive)360 void Windows::setExistsWindowActive(Latte::View *view, bool windowActive)
361 {
362     if (!m_views.contains(view) || m_views[view]->existsWindowActive() == windowActive) {
363         return;
364     }
365 
366     m_views[view]->setExistsWindowActive(windowActive);
367     emit existsWindowActiveChanged(view);
368 }
369 
existsWindowMaximized(Latte::View * view) const370 bool Windows::existsWindowMaximized(Latte::View *view) const
371 {
372     if (!m_views.contains(view)) {
373         return false;
374     }
375 
376     return m_views[view]->existsWindowMaximized();
377 }
378 
setExistsWindowMaximized(Latte::View * view,bool windowMaximized)379 void Windows::setExistsWindowMaximized(Latte::View *view, bool windowMaximized)
380 {
381     if (!m_views.contains(view) || m_views[view]->existsWindowMaximized() == windowMaximized) {
382         return;
383     }
384 
385     m_views[view]->setExistsWindowMaximized(windowMaximized);
386     emit existsWindowMaximizedChanged(view);
387 }
388 
existsWindowTouching(Latte::View * view) const389 bool Windows::existsWindowTouching(Latte::View *view) const
390 {
391     if (!m_views.contains(view)) {
392         return false;
393     }
394 
395     return m_views[view]->existsWindowTouching();
396 }
397 
setExistsWindowTouching(Latte::View * view,bool windowTouching)398 void Windows::setExistsWindowTouching(Latte::View *view, bool windowTouching)
399 {
400     if (!m_views.contains(view) || m_views[view]->existsWindowTouching() == windowTouching) {
401         return;
402     }
403 
404     m_views[view]->setExistsWindowTouching(windowTouching);
405     emit existsWindowTouchingChanged(view);
406 }
407 
existsWindowTouchingEdge(Latte::View * view) const408 bool Windows::existsWindowTouchingEdge(Latte::View *view) const
409 {
410     if (!m_views.contains(view)) {
411         return false;
412     }
413 
414     return m_views[view]->existsWindowTouchingEdge();
415 }
416 
setExistsWindowTouchingEdge(Latte::View * view,bool windowTouchingEdge)417 void Windows::setExistsWindowTouchingEdge(Latte::View *view, bool windowTouchingEdge)
418 {
419     if (!m_views.contains(view) || m_views[view]->existsWindowTouchingEdge() == windowTouchingEdge) {
420         return;
421     }
422 
423     m_views[view]->setExistsWindowTouchingEdge(windowTouchingEdge);
424     emit existsWindowTouchingEdgeChanged(view);
425 }
426 
427 
isTouchingBusyVerticalView(Latte::View * view) const428 bool Windows::isTouchingBusyVerticalView(Latte::View *view) const
429 {
430     if (!m_views.contains(view)) {
431         return false;
432     }
433 
434     return m_views[view]->isTouchingBusyVerticalView();
435 }
436 
setIsTouchingBusyVerticalView(Latte::View * view,bool viewTouching)437 void Windows::setIsTouchingBusyVerticalView(Latte::View *view, bool viewTouching)
438 {
439     if (!m_views.contains(view) || m_views[view]->isTouchingBusyVerticalView() == viewTouching) {
440         return;
441     }
442 
443     m_views[view]->setIsTouchingBusyVerticalView(viewTouching);
444     emit isTouchingBusyVerticalViewChanged(view);
445 }
446 
activeWindowScheme(Latte::View * view) const447 SchemeColors *Windows::activeWindowScheme(Latte::View *view) const
448 {
449     if (!m_views.contains(view)) {
450         return nullptr;
451     }
452 
453     return m_views[view]->activeWindowScheme();
454 }
455 
setActiveWindowScheme(Latte::View * view,WindowSystem::SchemeColors * scheme)456 void Windows::setActiveWindowScheme(Latte::View *view, WindowSystem::SchemeColors *scheme)
457 {
458     if (!m_views.contains(view) || m_views[view]->activeWindowScheme() == scheme) {
459         return;
460     }
461 
462     m_views[view]->setActiveWindowScheme(scheme);
463     emit activeWindowSchemeChanged(view);
464 }
465 
touchingWindowScheme(Latte::View * view) const466 SchemeColors *Windows::touchingWindowScheme(Latte::View *view) const
467 {
468     if (!m_views.contains(view)) {
469         return nullptr;
470     }
471 
472     return m_views[view]->touchingWindowScheme();
473 }
474 
setTouchingWindowScheme(Latte::View * view,WindowSystem::SchemeColors * scheme)475 void Windows::setTouchingWindowScheme(Latte::View *view, WindowSystem::SchemeColors *scheme)
476 {
477     if (!m_views.contains(view) || m_views[view]->touchingWindowScheme() == scheme) {
478         return;
479     }
480 
481     m_views[view]->setTouchingWindowScheme(scheme);
482     emit touchingWindowSchemeChanged(view);
483 }
484 
lastActiveWindow(Latte::View * view)485 LastActiveWindow *Windows::lastActiveWindow(Latte::View *view)
486 {
487     if (!m_views.contains(view)) {
488         return nullptr;
489     }
490 
491     return m_views[view]->lastActiveWindow();
492 }
493 
494 //! Layouts
enabled(Latte::Layout::GenericLayout * layout)495 bool Windows::enabled(Latte::Layout::GenericLayout *layout)
496 {
497     if (!m_layouts.contains(layout)) {
498         return false;
499     }
500 
501     return m_layouts[layout]->enabled();
502 }
503 
activeWindowMaximized(Latte::Layout::GenericLayout * layout) const504 bool Windows::activeWindowMaximized(Latte::Layout::GenericLayout *layout) const
505 {
506     if (!m_layouts.contains(layout)) {
507         return false;
508     }
509 
510     return m_layouts[layout]->activeWindowMaximized();
511 }
512 
setActiveWindowMaximized(Latte::Layout::GenericLayout * layout,bool activeMaximized)513 void Windows::setActiveWindowMaximized(Latte::Layout::GenericLayout *layout, bool activeMaximized)
514 {
515     if (!m_layouts.contains(layout) || m_layouts[layout]->activeWindowMaximized() == activeMaximized) {
516         return;
517     }
518 
519     m_layouts[layout]->setActiveWindowMaximized(activeMaximized);
520     emit activeWindowMaximizedChangedForLayout(layout);
521 }
522 
existsWindowActive(Latte::Layout::GenericLayout * layout) const523 bool Windows::existsWindowActive(Latte::Layout::GenericLayout *layout) const
524 {
525     if (!m_layouts.contains(layout)) {
526         return false;
527     }
528 
529     return m_layouts[layout]->existsWindowActive();
530 }
531 
setExistsWindowActive(Latte::Layout::GenericLayout * layout,bool windowActive)532 void Windows::setExistsWindowActive(Latte::Layout::GenericLayout *layout, bool windowActive)
533 {
534     if (!m_layouts.contains(layout) || m_layouts[layout]->existsWindowActive() == windowActive) {
535         return;
536     }
537 
538     m_layouts[layout]->setExistsWindowActive(windowActive);
539     emit existsWindowActiveChangedForLayout(layout);
540 }
541 
existsWindowMaximized(Latte::Layout::GenericLayout * layout) const542 bool Windows::existsWindowMaximized(Latte::Layout::GenericLayout *layout) const
543 {
544     if (!m_layouts.contains(layout)) {
545         return false;
546     }
547 
548     return m_layouts[layout]->existsWindowMaximized();
549 }
550 
setExistsWindowMaximized(Latte::Layout::GenericLayout * layout,bool windowMaximized)551 void Windows::setExistsWindowMaximized(Latte::Layout::GenericLayout *layout, bool windowMaximized)
552 {
553     if (!m_layouts.contains(layout) || m_layouts[layout]->existsWindowMaximized() == windowMaximized) {
554         return;
555     }
556 
557     m_layouts[layout]->setExistsWindowMaximized(windowMaximized);
558     emit existsWindowMaximizedChangedForLayout(layout);
559 }
560 
activeWindowScheme(Latte::Layout::GenericLayout * layout) const561 SchemeColors *Windows::activeWindowScheme(Latte::Layout::GenericLayout *layout) const
562 {
563     if (!m_layouts.contains(layout)) {
564         return nullptr;
565     }
566 
567     return m_layouts[layout]->activeWindowScheme();
568 }
569 
setActiveWindowScheme(Latte::Layout::GenericLayout * layout,WindowSystem::SchemeColors * scheme)570 void Windows::setActiveWindowScheme(Latte::Layout::GenericLayout *layout, WindowSystem::SchemeColors *scheme)
571 {
572     if (!m_layouts.contains(layout) || m_layouts[layout]->activeWindowScheme() == scheme) {
573         return;
574     }
575 
576     m_layouts[layout]->setActiveWindowScheme(scheme);
577     emit activeWindowSchemeChangedForLayout(layout);
578 }
579 
lastActiveWindow(Latte::Layout::GenericLayout * layout)580 LastActiveWindow *Windows::lastActiveWindow(Latte::Layout::GenericLayout *layout)
581 {
582     if (!m_layouts.contains(layout)) {
583         return nullptr;
584     }
585 
586     return m_layouts[layout]->lastActiveWindow();
587 }
588 
589 
590 //! Windows
isValidFor(const WindowId & wid) const591 bool Windows::isValidFor(const WindowId &wid) const
592 {
593     if (!m_windows.contains(wid)) {
594         return false;
595     }
596 
597     return m_windows[wid].isValid();
598 }
599 
iconFor(const WindowId & wid)600 QIcon Windows::iconFor(const WindowId &wid)
601 {
602     if (!m_windows.contains(wid)) {
603         return QIcon();
604     }
605 
606     if (m_windows[wid].icon().isNull()) {
607         AppData data = m_wm->appDataFor(wid);
608 
609         QIcon icon = data.icon;
610 
611         if (icon.isNull()) {
612             icon = m_wm->iconFor(wid);
613         }
614 
615         m_windows[wid].setIcon(icon);
616         return icon;
617     }
618 
619     return m_windows[wid].icon();
620 }
621 
appNameFor(const WindowId & wid)622 QString Windows::appNameFor(const WindowId &wid)
623 {
624     if (!m_windows.contains(wid)) {
625         return QString();
626     }
627 
628     if(!m_initializedApplicationData.contains(wid) && !m_delayedApplicationData.contains(wid)) {
629         m_delayedApplicationData.append(wid);
630         m_updateApplicationDataTimer.start();
631     }
632 
633     if (m_windows[wid].appName().isEmpty()) {
634         AppData data = m_wm->appDataFor(wid);
635 
636         m_windows[wid].setAppName(data.name);
637 
638         return data.name;
639     }
640 
641     return m_windows[wid].appName();
642 }
643 
updateApplicationData()644 void Windows::updateApplicationData()
645 {
646     if (m_delayedApplicationData.count() > 0) {
647         for(int i=0; i<m_delayedApplicationData.count(); ++i) {
648             auto wid = m_delayedApplicationData[i];
649 
650             if (m_windows.contains(wid)) {
651                 AppData data = m_wm->appDataFor(wid);
652 
653                 QIcon icon = data.icon;
654 
655                 if (icon.isNull()) {
656                     icon = m_wm->iconFor(wid);
657                 }
658 
659                 m_windows[wid].setIcon(icon);
660                 m_windows[wid].setAppName(data.name);
661 
662                 m_initializedApplicationData.append(wid);
663 
664                 emit applicationDataChanged(wid);
665             }
666         }
667     }
668 
669     m_delayedApplicationData.clear();
670 }
671 
infoFor(const WindowId & wid) const672 WindowInfoWrap Windows::infoFor(const WindowId &wid) const
673 {
674     if (!m_windows.contains(wid)) {
675         return WindowInfoWrap();
676     }
677 
678     return m_windows[wid];
679 }
680 
681 
682 
683 //! Windows Criteria Functions
intersects(Latte::View * view,const WindowInfoWrap & winfo)684 bool Windows::intersects(Latte::View *view, const WindowInfoWrap &winfo)
685 {
686     return (!winfo.isMinimized() && !winfo.isShaded() && winfo.geometry().intersects(view->absoluteGeometry()));
687 }
688 
isActive(const WindowInfoWrap & winfo)689 bool Windows::isActive(const WindowInfoWrap &winfo)
690 {
691     return (winfo.isValid() && winfo.isActive() && !winfo.isMinimized());
692 }
693 
isActiveInViewScreen(Latte::View * view,const WindowInfoWrap & winfo)694 bool Windows::isActiveInViewScreen(Latte::View *view, const WindowInfoWrap &winfo)
695 {
696     auto screenGeometry = m_views[view]->screenGeometry();
697 
698     if (KWindowSystem::isPlatformX11() && view->devicePixelRatio() != 1.0) {
699         //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
700         auto factor = view->devicePixelRatio();
701         screenGeometry = QRect(qRound(screenGeometry.x() * factor),
702                                qRound(screenGeometry.y() * factor),
703                                qRound(screenGeometry.width() * factor),
704                                qRound(screenGeometry.height() * factor));
705     }
706 
707     return (winfo.isValid() && winfo.isActive() &&  !winfo.isMinimized()
708             && screenGeometry.contains(winfo.geometry().center()));
709 }
710 
isMaximizedInViewScreen(Latte::View * view,const WindowInfoWrap & winfo)711 bool Windows::isMaximizedInViewScreen(Latte::View *view, const WindowInfoWrap &winfo)
712 {
713     auto screenGeometry = m_views[view]->screenGeometry();
714 
715     if (KWindowSystem::isPlatformX11() && view->devicePixelRatio() != 1.0) {
716         //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
717         auto factor = view->devicePixelRatio();
718         screenGeometry = QRect(qRound(screenGeometry.x() * factor),
719                                qRound(screenGeometry.y() * factor),
720                                qRound(screenGeometry.width() * factor),
721                                qRound(screenGeometry.height() * factor));
722     }
723 
724     //! updated implementation to identify the screen that the maximized window is present
725     //! in order to avoid: https://bugs.kde.org/show_bug.cgi?id=397700
726     return (winfo.isValid() && !winfo.isMinimized()
727             && !winfo.isShaded()
728             && winfo.isMaximized()
729             && screenGeometry.contains(winfo.geometry().center()));
730 }
731 
isTouchingView(Latte::View * view,const WindowSystem::WindowInfoWrap & winfo)732 bool Windows::isTouchingView(Latte::View *view, const WindowSystem::WindowInfoWrap &winfo)
733 {
734     return (winfo.isValid() && intersects(view, winfo));
735 }
736 
isTouchingViewEdge(Latte::View * view,const QRect & windowgeometry)737 bool Windows::isTouchingViewEdge(Latte::View *view, const QRect &windowgeometry)
738 {
739     if (!view) {
740         return false;
741     }
742 
743     bool inViewThicknessEdge{false};
744     bool inViewLengthBoundaries{false};
745 
746     QRect screenGeometry = view->screenGeometry();
747 
748     if (KWindowSystem::isPlatformX11() && view->devicePixelRatio() != 1.0) {
749         //!Fix for X11 Global Scale, I dont think this could be pixel perfect accurate
750         auto factor = view->devicePixelRatio();
751         screenGeometry = QRect(qRound(screenGeometry.x() * factor),
752                                qRound(screenGeometry.y() * factor),
753                                qRound(screenGeometry.width() * factor),
754                                qRound(screenGeometry.height() * factor));
755     }
756 
757     bool inCurrentScreen{screenGeometry.contains(windowgeometry.topLeft()) || screenGeometry.contains(windowgeometry.bottomRight())};
758 
759     if (inCurrentScreen) {
760         if (view->location() == Plasma::Types::TopEdge) {
761             inViewThicknessEdge = (windowgeometry.y() == view->absoluteGeometry().bottom() + 1);
762         } else if (view->location() == Plasma::Types::BottomEdge) {
763             inViewThicknessEdge = (windowgeometry.bottom() == view->absoluteGeometry().top() - 1);
764         } else if (view->location() == Plasma::Types::LeftEdge) {
765             inViewThicknessEdge = (windowgeometry.x() == view->absoluteGeometry().right() + 1);
766         } else if (view->location() == Plasma::Types::RightEdge) {
767             inViewThicknessEdge = (windowgeometry.right() == view->absoluteGeometry().left() - 1);
768         }
769 
770         if (view->formFactor() == Plasma::Types::Horizontal) {
771             int yCenter = view->absoluteGeometry().center().y();
772 
773             QPoint leftChecker(windowgeometry.left(), yCenter);
774             QPoint rightChecker(windowgeometry.right(), yCenter);
775 
776             bool fulloverlap = (windowgeometry.left()<=view->absoluteGeometry().left()) && (windowgeometry.right()>=view->absoluteGeometry().right());
777 
778             inViewLengthBoundaries = fulloverlap || view->absoluteGeometry().contains(leftChecker) || view->absoluteGeometry().contains(rightChecker);
779         } else if (view->formFactor() == Plasma::Types::Vertical) {
780             int xCenter = view->absoluteGeometry().center().x();
781 
782             QPoint topChecker(xCenter, windowgeometry.top());
783             QPoint bottomChecker(xCenter, windowgeometry.bottom());
784 
785             bool fulloverlap = (windowgeometry.top()<=view->absoluteGeometry().top()) && (windowgeometry.bottom()>=view->absoluteGeometry().bottom());
786 
787             inViewLengthBoundaries = fulloverlap || view->absoluteGeometry().contains(topChecker) || view->absoluteGeometry().contains(bottomChecker);
788         }
789     }
790 
791     return (inViewThicknessEdge && inViewLengthBoundaries);
792 }
793 
isTouchingViewEdge(Latte::View * view,const WindowInfoWrap & winfo)794 bool Windows::isTouchingViewEdge(Latte::View *view, const WindowInfoWrap &winfo)
795 {
796     if (winfo.isValid() &&  !winfo.isMinimized()) {
797         return isTouchingViewEdge(view, winfo.geometry());
798     }
799 
800     return false;
801 }
802 
cleanupFaultyWindows()803 void Windows::cleanupFaultyWindows()
804 {
805     for (const auto &key : m_windows.keys()) {
806         auto winfo = m_windows[key];
807 
808         //! garbage windows removing
809         if (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0)) {
810             //qDebug() << "Faulty Geometry ::: " << winfo.wid();
811             m_windows.remove(key);
812         }
813     }
814 }
815 
816 
updateScreenGeometries()817 void Windows::updateScreenGeometries()
818 {
819     for (const auto view : m_views.keys()) {
820         if (m_views[view]->screenGeometry() != view->screenGeometry()) {
821             m_views[view]->setScreenGeometry(view->screenGeometry());
822 
823             if (m_views[view]->enabled()) {
824                 updateHints(view);
825             }
826         }
827     }
828 }
829 
updateAllHintsAfterTimer()830 void Windows::updateAllHintsAfterTimer()
831 {
832     if (!m_updateAllHintsTimer.isActive()) {
833         updateAllHints();
834         m_updateAllHintsTimer.start();
835     }
836 }
837 
updateAllHints()838 void Windows::updateAllHints()
839 {
840     for (const auto view : m_views.keys()) {
841         updateHints(view);
842     }
843 
844     for (const auto layout : m_layouts.keys()) {
845         updateHints(layout);
846     }
847 
848     if (!m_extraViewHintsTimer.isActive()) {
849         m_extraViewHintsTimer.start();
850     }
851 }
852 
updateExtraViewHints()853 void Windows::updateExtraViewHints()
854 {
855     for (const auto horView : m_views.keys()) {
856         if (!m_views.contains(horView) || !m_views[horView]->enabled() || !m_views[horView]->isTrackingCurrentActivity()) {
857             continue;
858         }
859 
860         if (horView->formFactor() == Plasma::Types::Horizontal) {
861             bool touchingBusyVerticalView{false};
862 
863             for (const auto verView : m_views.keys()) {
864                 if (!m_views.contains(verView) || !m_views[verView]->enabled() || !m_views[verView]->isTrackingCurrentActivity()) {
865                     continue;
866                 }
867 
868                 bool sameScreen = (verView->positioner()->currentScreenId() == horView->positioner()->currentScreenId());
869 
870                 if (verView->formFactor() == Plasma::Types::Vertical && sameScreen) {
871                     bool hasEdgeTouch = isTouchingViewEdge(horView, verView->absoluteGeometry());
872 
873                     bool topTouch = horView->location() == Plasma::Types::TopEdge && verView->isTouchingTopViewAndIsBusy() && hasEdgeTouch;
874                     bool bottomTouch = horView->location() == Plasma::Types::BottomEdge && verView->isTouchingBottomViewAndIsBusy() && hasEdgeTouch;
875 
876                     if (topTouch || bottomTouch) {
877                         touchingBusyVerticalView = true;
878                         break;
879                     }
880                 }
881             }
882 
883             //qDebug() << " Touching Busy Vertical View :: " << horView->location() << " - " << horView->positioner()->currentScreenId() << " :: " << touchingBusyVerticalView;
884 
885             setIsTouchingBusyVerticalView(horView, touchingBusyVerticalView);
886         }
887     }
888 }
889 
updateHints(Latte::View * view)890 void Windows::updateHints(Latte::View *view)
891 {
892     if (!m_views.contains(view) || !m_views[view]->enabled() || !m_views[view]->isTrackingCurrentActivity()) {
893         return;
894     }
895 
896     bool foundActive{false};
897     bool foundActiveInCurScreen{false};
898     bool foundActiveTouchInCurScreen{false};
899     bool foundActiveEdgeTouchInCurScreen{false};
900     bool foundTouchInCurScreen{false};
901     bool foundTouchEdgeInCurScreen{false};
902     bool foundMaximizedInCurScreen{false};
903 
904     bool foundActiveGroupTouchInCurScreen{false};
905 
906     //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0),
907     //! maybe a garbage collector here is a good idea!!!
908     bool existsFaultyWindow{false};
909 
910     WindowId maxWinId;
911     WindowId activeWinId;
912     WindowId touchWinId;
913     WindowId touchEdgeWinId;
914     WindowId activeTouchWinId;
915     WindowId activeTouchEdgeWinId;
916 
917     //qDebug() << " -- TRACKING REPORT (SCREEN)--";
918 
919     //! First Pass
920     for (const auto &winfo : m_windows) {
921         if (m_wm->isShowingDesktop()) {
922             break;
923         }
924 
925         if (!existsFaultyWindow && (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0))) {
926             existsFaultyWindow = true;
927         }
928 
929         if ( !m_wm->inCurrentDesktopActivity(winfo)
930              || m_wm->hasBlockedTracking(winfo.wid())
931              || winfo.isMinimized()) {
932             continue;
933         }
934 
935         //qDebug() << " _ _ _ ";
936         //qDebug() << "TRACKING | WINDOW INFO :: " << winfo.wid() << " _ " << winfo.appName() << " _ " << winfo.geometry() << " _ " << winfo.display();
937 
938         if (isActive(winfo)) {
939             foundActive = true;
940         }
941 
942         if (isActiveInViewScreen(view, winfo)) {
943             foundActiveInCurScreen = true;
944             activeWinId = winfo.wid();
945         }
946 
947         //! Maximized windows flags
948         if ((winfo.isActive() && isMaximizedInViewScreen(view, winfo)) //! active maximized windows have higher priority than the rest maximized windows
949                 || (!foundMaximizedInCurScreen && isMaximizedInViewScreen(view, winfo))) {
950             foundMaximizedInCurScreen = true;
951             maxWinId = winfo.wid();
952         }
953 
954         //! Touching windows flags
955 
956         bool touchingViewEdge = isTouchingViewEdge(view, winfo);
957         bool touchingView =  isTouchingView(view, winfo);
958 
959         if (touchingView) {
960             if (winfo.isActive()) {
961                 foundActiveTouchInCurScreen = true;
962                 activeTouchWinId = winfo.wid();
963             } else {
964                 foundTouchInCurScreen = true;
965                 touchWinId = winfo.wid();
966             }
967         }
968 
969         if (touchingViewEdge) {
970             if (winfo.isActive()) {
971                 foundActiveEdgeTouchInCurScreen = true;
972                 activeTouchEdgeWinId = winfo.wid();
973             } else {
974                 foundTouchEdgeInCurScreen = true;
975                 touchEdgeWinId = winfo.wid();
976             }
977         }
978 
979         //qDebug() << "TRACKING |       ACTIVE:"<< foundActive <<  " ACT_TOUCH_CUR_SCR:" << foundActiveTouchInCurScreen << " MAXIM:"<<foundMaximizedInCurScreen;
980         //qDebug() << "TRACKING |       TOUCHING VIEW EDGE:"<< touchingViewEdge << " TOUCHING VIEW:" << foundTouchInCurScreen;
981     }
982 
983     if (existsFaultyWindow) {
984         cleanupFaultyWindows();
985     }
986 
987     //! PASS 2
988     if (!m_wm->isShowingDesktop() && foundActiveInCurScreen && !foundActiveTouchInCurScreen) {
989         //! Second Pass to track also Child windows if needed
990 
991         //qDebug() << "Windows Array...";
992         //for (const auto &winfo : m_windows) {
993         //    qDebug() << " - " << winfo.wid() << " - " << winfo.isValid() << " - " << winfo.display() << " - " << winfo.geometry() << " parent : " << winfo.parentId();
994         //}
995         //qDebug() << " - - - - - ";
996 
997         WindowInfoWrap activeInfo = m_windows[activeWinId];
998         WindowId mainWindowId = activeInfo.isChildWindow() ? activeInfo.parentId() : activeWinId;
999 
1000         for (const auto &winfo : m_windows) {
1001             if (!m_wm->inCurrentDesktopActivity(winfo)
1002                     || m_wm->hasBlockedTracking(winfo.wid())
1003                     || winfo.isMinimized()) {
1004                 continue;
1005             }
1006 
1007             bool inActiveGroup = (winfo.wid() == mainWindowId || winfo.parentId() == mainWindowId);
1008 
1009             //! consider only windows that belong to active window group meaning the main window
1010             //! and its children
1011             if (!inActiveGroup) {
1012                 continue;
1013             }
1014 
1015             if (isTouchingView(view, winfo)) {
1016                 foundActiveGroupTouchInCurScreen = true;
1017                 break;
1018             }
1019         }
1020     }
1021 
1022 
1023     //! HACK: KWin Effects such as ShowDesktop have no way to be identified and as such
1024     //! create issues with identifying properly touching and maximized windows. BUT when
1025     //! they are enabled then NO ACTIVE window is found. This is a way to identify these
1026     //! effects trigerring and disable the touch flags.
1027     //! BUG: 404483
1028     //! Disabled because it has fault identifications, e.g. when a window is maximized and
1029     //! Latte or Plasma are showing their View settings
1030     //foundMaximizedInCurScreen = foundMaximizedInCurScreen && foundActive;
1031     //foundTouchInCurScreen = foundTouchInCurScreen && foundActive;
1032 
1033     //! assign flags
1034     setExistsWindowActive(view, foundActiveInCurScreen);
1035     setActiveWindowTouching(view, foundActiveTouchInCurScreen || foundActiveGroupTouchInCurScreen);
1036     setActiveWindowTouchingEdge(view, foundActiveEdgeTouchInCurScreen);
1037     setActiveWindowMaximized(view, (maxWinId.toInt()>0 && (maxWinId == activeTouchWinId || maxWinId == activeTouchEdgeWinId)));
1038     setExistsWindowMaximized(view, foundMaximizedInCurScreen);
1039     setExistsWindowTouching(view, (foundTouchInCurScreen || foundActiveTouchInCurScreen || foundActiveGroupTouchInCurScreen));
1040     setExistsWindowTouchingEdge(view, (foundActiveEdgeTouchInCurScreen || foundTouchEdgeInCurScreen));
1041 
1042     //! update color schemes for active and touching windows
1043     setActiveWindowScheme(view, (foundActiveInCurScreen ? m_wm->schemesTracker()->schemeForWindow(activeWinId) : nullptr));
1044 
1045     if (foundActiveTouchInCurScreen) {
1046         setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(activeTouchWinId));
1047     } else if (foundActiveEdgeTouchInCurScreen) {
1048         setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(activeTouchEdgeWinId));
1049     } else if (foundMaximizedInCurScreen) {
1050         setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(maxWinId));
1051     } else if (foundTouchInCurScreen) {
1052         setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(touchWinId));
1053     } else if (foundTouchEdgeInCurScreen) {
1054         setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(touchEdgeWinId));
1055     } else {
1056         setTouchingWindowScheme(view, nullptr);
1057     }
1058 
1059     //! update LastActiveWindow
1060     if (foundActiveInCurScreen) {
1061         m_views[view]->setActiveWindow(activeWinId);
1062     }
1063 
1064     //! Debug
1065     //qDebug() << "TRACKING |      _________ FINAL RESULTS ________";
1066     //qDebug() << "TRACKING | SCREEN: " << view->positioner()->currentScreenId() << " , EDGE:" << view->location() << " , ENABLED:" << enabled(view);
1067     //qDebug() << "TRACKING | activeWindowTouching: " << foundActiveTouchInCurScreen << " ,activeWindowMaximized: " << activeWindowMaximized(view);
1068     //qDebug() << "TRACKING | existsWindowActive: " << foundActiveInCurScreen << " , existsWindowMaximized:" << existsWindowMaximized(view)
1069     //         << " , existsWindowTouching:"<<existsWindowTouching(view);
1070     //qDebug() << "TRACKING | activeEdgeWindowTouch: " <<  activeWindowTouchingEdge(view) << " , existsEdgeWindowTouch:" << existsWindowTouchingEdge(view);
1071     //qDebug() << "TRACKING | existsActiveGroupTouching: " << foundActiveGroupTouchInCurScreen;
1072 }
1073 
updateHints(Latte::Layout::GenericLayout * layout)1074 void Windows::updateHints(Latte::Layout::GenericLayout *layout) {
1075     if (!m_layouts.contains(layout) || !m_layouts[layout]->enabled() || !m_layouts[layout]->isTrackingCurrentActivity()) {
1076         return;
1077     }
1078 
1079     bool foundActive{false};
1080     bool foundActiveMaximized{false};
1081     bool foundMaximized{false};
1082 
1083     //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0),
1084     //! maybe a garbage collector here is a good idea!!!
1085     bool existsFaultyWindow{false};
1086 
1087     WindowId activeWinId;
1088     WindowId maxWinId;
1089 
1090     for (const auto &winfo : m_windows) {
1091         if (m_wm->isShowingDesktop()) {
1092             break;
1093         }
1094 
1095         if (!existsFaultyWindow && (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0))) {
1096             existsFaultyWindow = true;
1097         }
1098 
1099         if (!m_wm->inCurrentDesktopActivity(winfo)
1100                 || m_wm->hasBlockedTracking(winfo.wid())
1101                 || winfo.isMinimized()) {
1102             continue;
1103         }
1104 
1105         if (isActive(winfo)) {
1106             foundActive = true;
1107             activeWinId = winfo.wid();
1108 
1109             if (winfo.isMaximized() && !winfo.isMinimized()) {
1110                 foundActiveMaximized = true;
1111                 maxWinId = winfo.wid();
1112             }
1113         }
1114 
1115         if (!foundActiveMaximized && winfo.isMaximized() && !winfo.isMinimized()) {
1116             foundMaximized = true;
1117             maxWinId = winfo.wid();
1118         }
1119 
1120         //qDebug() << "window geometry ::: " << winfo.geometry();
1121     }
1122 
1123     if (existsFaultyWindow) {
1124         cleanupFaultyWindows();
1125     }
1126 
1127     //! HACK: KWin Effects such as ShowDesktop have no way to be identified and as such
1128     //! create issues with identifying properly touching and maximized windows. BUT when
1129     //! they are enabled then NO ACTIVE window is found. This is a way to identify these
1130     //! effects trigerring and disable the touch flags.
1131     //! BUG: 404483
1132     //! Disabled because it has fault identifications, e.g. when a window is maximized and
1133     //! Latte or Plasma are showing their View settings
1134     //foundMaximizedInCurScreen = foundMaximizedInCurScreen && foundActive;
1135     //foundTouchInCurScreen = foundTouchInCurScreen && foundActive;
1136 
1137     //! assign flags
1138     setExistsWindowActive(layout, foundActive);
1139     setActiveWindowMaximized(layout, foundActiveMaximized);
1140     setExistsWindowMaximized(layout, foundActiveMaximized || foundMaximized);
1141 
1142     //! update color schemes for active and touching windows
1143     setActiveWindowScheme(layout, (foundActive ? m_wm->schemesTracker()->schemeForWindow(activeWinId) : nullptr));
1144 
1145     //! update LastActiveWindow
1146     if (foundActive) {
1147         m_layouts[layout]->setActiveWindow(activeWinId);
1148     }
1149 
1150     //! Debug
1151     //qDebug() << " -- TRACKING REPORT (LAYOUT) --";
1152     //qDebug() << "TRACKING | LAYOUT: " << layout->name() << " , ENABLED:" << enabled(layout);
1153     //qDebug() << "TRACKING | existsActiveWindow: " << foundActive << " ,activeWindowMaximized: " << foundActiveMaximized;
1154     //qDebug() << "TRACKING | existsWindowMaximized: " << existsWindowMaximized(layout);
1155 }
1156 
1157 }
1158 }
1159 }
1160