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 "visibilitymanager.h"
9 
10 // local
11 #include "positioner.h"
12 #include "view.h"
13 #include "helpers/floatinggapwindow.h"
14 #include "helpers/screenedgeghostwindow.h"
15 #include "windowstracker/currentscreentracker.h"
16 #include "../apptypes.h"
17 #include "../lattecorona.h"
18 #include "../screenpool.h"
19 #include "../layouts/manager.h"
20 #include "../wm/abstractwindowinterface.h"
21 
22 // Qt
23 #include <QDebug>
24 
25 // KDE
26 #include <KWindowSystem>
27 #include <KWayland/Client/plasmashell.h>
28 #include <KWayland/Client/surface.h>
29 
30 //! Hide Timer can create cases that when it is low it does not allow the
31 //! view to be show. For example !compositing+kwin_edges+hide inteval<50ms
32 //!   FIXED: As it appears because we dont hide any view anymore before its sliding in
33 //!   animation has ended that probably allows to set the hide minimum interval to zero
34 //!   without any further issues, such as to not show the view even though the
35 //!   user is touching the screen edge
36 const int HIDEMINIMUMINTERVAL = 0;
37 //! After calling SidebarAutoHide panel to show for example through Sidebar button
38 //! or global shortcuts we make sure bar will be shown enough time
39 //! in order for the user to observe its contents
40 const int SIDEBARAUTOHIDEMINIMUMSHOW = 1000;
41 
42 
43 namespace Latte {
44 namespace ViewPart {
45 
46 //! BEGIN: VisiblityManager implementation
47 const QRect VisibilityManager::ISHIDDENMASK = QRect(-1, -1, 1, 1);
48 
VisibilityManager(PlasmaQuick::ContainmentView * view)49 VisibilityManager::VisibilityManager(PlasmaQuick::ContainmentView *view)
50     : QObject(view)
51 {
52     qDebug() << "VisibilityManager creating...";
53 
54     m_latteView = qobject_cast<Latte::View *>(view);
55     m_corona = qobject_cast<Latte::Corona *>(view->corona());
56     m_wm = m_corona->wm();
57 
58     connect(this, &VisibilityManager::hidingIsBlockedChanged, this, &VisibilityManager::onHidingIsBlockedChanged);
59 
60     connect(this, &VisibilityManager::slideOutFinished, this, &VisibilityManager::updateHiddenState);
61     connect(this, &VisibilityManager::slideInFinished, this, &VisibilityManager::updateHiddenState);
62 
63     connect(this, &VisibilityManager::enableKWinEdgesChanged, this, &VisibilityManager::updateKWinEdgesSupport);
64     connect(this, &VisibilityManager::modeChanged, this, &VisibilityManager::updateKWinEdgesSupport);
65     connect(this, &VisibilityManager::modeChanged, this, &VisibilityManager::updateSidebarState);
66 
67     connect(this, &VisibilityManager::isFloatingGapWindowEnabledChanged, this, &VisibilityManager::onIsFloatingGapWindowEnabledChanged);
68 
69     connect(this, &VisibilityManager::mustBeShown, this, [&]() {
70         if (m_latteView && !m_latteView->isVisible()) {
71             m_latteView->setVisible(true);
72         }
73     });
74 
75     if (m_latteView) {
76         connect(m_latteView, &Latte::View::eventTriggered, this, &VisibilityManager::viewEventManager);
77         connect(m_latteView, &Latte::View::behaveAsPlasmaPanelChanged , this, &VisibilityManager::updateKWinEdgesSupport);
78         connect(m_latteView, &Latte::View::byPassWMChanged, this, &VisibilityManager::updateKWinEdgesSupport);
79 
80         connect(m_latteView, &Latte::View::inEditModeChanged, this, &VisibilityManager::initViewFlags);
81 
82         //! Frame Extents
83         connect(m_latteView, &Latte::View::headThicknessGapChanged, this, &VisibilityManager::onHeadThicknessChanged);
84         connect(m_latteView, &Latte::View::locationChanged, this, [&]() {
85             if (!m_latteView->behaveAsPlasmaPanel()) {
86                 //! Resend frame extents because their geometry has changed
87                 const bool forceUpdate{true};
88                 publishFrameExtents(forceUpdate);
89             }
90         });
91 
92         connect(m_latteView, &Latte::View::typeChanged, this, [&]() {
93             if (m_latteView->inEditMode()) {
94                 //! Resend frame extents because type has changed
95                 const bool forceUpdate{true};
96                 publishFrameExtents(forceUpdate);
97             }
98         });
99 
100         connect(m_latteView, &Latte::View::forcedShown, this, [&]() {
101             //! Resend frame extents to compositor otherwise because compositor cleared
102             //! them with no reason when the user is closing an activity
103             const bool forceUpdate{true};
104             publishFrameExtents(forceUpdate);
105         });
106 
107         connect(this, &VisibilityManager::modeChanged, this, [&]() {
108             emit m_latteView->availableScreenRectChangedFrom(m_latteView);
109         });
110 
111         //! Send frame extents on startup, this is really necessary when recreating a view.
112         //! Such a case is when toggling byPassWM and a view is recreated after disabling editing mode
113         const bool forceUpdate{true};
114         publishFrameExtents(forceUpdate);
115     }
116 
117     m_timerShow.setSingleShot(true);
118     m_timerHide.setSingleShot(true);
119 
120     connect(&m_timerShow, &QTimer::timeout, this, [&]() {
121         if (m_isHidden ||  m_isBelowLayer) {
122             //   qDebug() << "must be shown";
123             emit mustBeShown();
124         }
125     });
126     connect(&m_timerHide, &QTimer::timeout, this, [&]() {
127         if (!hidingIsBlocked() && !m_isHidden && !m_isBelowLayer && !m_dragEnter) {
128             if (m_isFloatingGapWindowEnabled) {
129                 //! first check if mouse is inside the floating gap
130                 checkMouseInFloatingArea();
131             } else {
132                 //! immediate call
133                 emit mustBeHide();
134             }
135         }
136     });
137 
138     m_timerPublishFrameExtents.setInterval(1500);
139     m_timerPublishFrameExtents.setSingleShot(true);
140     connect(&m_timerPublishFrameExtents, &QTimer::timeout, this, [&]() { publishFrameExtents(); });
141 
142     m_timerBlockStrutsUpdate.setInterval(1000);
143     m_timerBlockStrutsUpdate.setSingleShot(true);
144     connect(&m_timerBlockStrutsUpdate, &QTimer::timeout, this, [&]() { updateStrutsBasedOnLayoutsAndActivities(); });
145 
146     restoreConfig();
147 
148     //! connect save values after they have been restored
149     connect(this, &VisibilityManager::enableKWinEdgesChanged, this, &VisibilityManager::saveConfig);
150     connect(this, &VisibilityManager::modeChanged, this, &VisibilityManager::saveConfig);
151     connect(this, &VisibilityManager::raiseOnDesktopChanged, this, &VisibilityManager::saveConfig);
152     connect(this, &VisibilityManager::raiseOnActivityChanged, this, &VisibilityManager::saveConfig);
153     connect(this, &VisibilityManager::timerShowChanged, this, &VisibilityManager::saveConfig);
154     connect(this, &VisibilityManager::timerHideChanged, this, &VisibilityManager::saveConfig);
155 }
156 
~VisibilityManager()157 VisibilityManager::~VisibilityManager()
158 {
159     qDebug() << "VisibilityManager deleting...";
160     m_wm->removeViewStruts(*m_latteView);
161 
162     if (m_edgeGhostWindow) {
163         m_edgeGhostWindow->deleteLater();
164     }
165 
166     if (m_floatingGapWindow) {
167         m_floatingGapWindow->deleteLater();
168     }
169 }
170 
171 //! Struts
strutsThickness() const172 int VisibilityManager::strutsThickness() const
173 {
174     return m_strutsThickness;
175 }
176 
setStrutsThickness(int thickness)177 void VisibilityManager::setStrutsThickness(int thickness)
178 {
179     if (m_strutsThickness == thickness) {
180         return;
181     }
182 
183     m_strutsThickness = thickness;
184     emit strutsThicknessChanged();
185 }
186 
mode() const187 Types::Visibility VisibilityManager::mode() const
188 {
189     return m_mode;
190 }
191 
initViewFlags()192 void VisibilityManager::initViewFlags()
193 {
194     if ((m_mode == Types::WindowsCanCover || m_mode == Types::WindowsAlwaysCover) && (!m_latteView->inEditMode())) {
195         setViewOnBackLayer();
196     } else {
197         setViewOnFrontLayer();
198     }
199 }
200 
setViewOnBackLayer()201 void VisibilityManager::setViewOnBackLayer()
202 {
203     m_wm->setViewExtraFlags(m_latteView, false, Types::WindowsAlwaysCover);
204     setIsBelowLayer(true);
205 }
206 
setViewOnFrontLayer()207 void VisibilityManager::setViewOnFrontLayer()
208 {
209     m_wm->setViewExtraFlags(m_latteView, true);
210     setIsBelowLayer(false);
211     if (KWindowSystem::isPlatformX11()) {
212         m_latteView->raise();
213     }
214 }
215 
setMode(Latte::Types::Visibility mode)216 void VisibilityManager::setMode(Latte::Types::Visibility mode)
217 {
218     if (m_mode == mode) {
219         return;
220     }
221 
222     qDebug() << "Updating visibility mode to  :::: " << mode;
223 
224     Q_ASSERT_X(mode != Types::None, staticMetaObject.className(), "set visibility to Types::None");
225 
226     // clear mode
227     for (auto &c : m_connections) {
228         disconnect(c);
229     }
230 
231     int base{0};
232 
233     m_publishedStruts = QRect();
234 
235     if (m_mode == Types::AlwaysVisible) {
236         //! remove struts for old always visible mode
237         m_wm->removeViewStruts(*m_latteView);
238     }
239 
240     m_timerShow.stop();
241     m_timerHide.stop();
242     m_mode = mode;
243 
244     initViewFlags();
245 
246     if (mode != Types::AlwaysVisible && mode != Types::WindowsGoBelow) {
247         m_connections[0] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentDesktopChanged, this, [&] {
248             if (m_raiseOnDesktopChange) {
249                 raiseViewTemporarily();
250             }
251         });
252         m_connections[1] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentActivityChanged, this, [&]() {
253             if (m_raiseOnActivityChange) {
254                 raiseViewTemporarily();
255             } else {
256                 updateHiddenState();
257             }
258         });
259 
260         base = 2;
261     }
262 
263     switch (m_mode) {
264     case Types::AlwaysVisible: {
265         if (m_latteView->containment() && m_latteView->screen()) {
266             updateStrutsBasedOnLayoutsAndActivities();
267         }
268 
269         m_connections[base] = connect(this, &VisibilityManager::strutsThicknessChanged, &VisibilityManager::updateStrutsAfterTimer);
270 
271         // disabling this call because it was creating too many struts calls and   ???
272         // could create reduced responsiveness for DynamicStruts Scenario(for example ??
273         // when dragging active window from a floating dock/panel) ???
274         m_connections[base+1] = connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &VisibilityManager::updateStrutsAfterTimer);
275 
276         m_connections[base+2] = connect(m_corona->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged, this, [&]() {
277             if (m_corona && m_corona->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
278                 updateStrutsBasedOnLayoutsAndActivities(true);
279             }
280         });
281 
282         //! respect canSetStrut that must be disabled under x11 when an alwaysvisible screen edge is common between two or more screens
283         m_connections[base+3] = connect(m_corona->screenPool(), &Latte::ScreenPool::screenGeometryChanged, this, &VisibilityManager::updateStrutsAfterTimer);
284 
285         m_connections[base+4] = connect(m_latteView, &Latte::View::activitiesChanged, this, [&]() {
286             updateStrutsBasedOnLayoutsAndActivities(true);
287         });
288 
289         raiseView(true);
290         break;
291     }
292 
293     case Types::AutoHide: {
294         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged, this, [&]() {
295             raiseView(m_containsMouse);
296         });
297 
298         raiseView(m_containsMouse);
299         break;
300     }
301 
302     case Types::DodgeActive: {
303         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged
304                                       , this, &VisibilityManager::dodgeActive);
305         m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::activeWindowTouchingChanged
306                                         , this, &VisibilityManager::dodgeActive);
307 
308         dodgeActive();
309         break;
310     }
311 
312     case Types::DodgeMaximized: {
313         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged
314                                       , this, &VisibilityManager::dodgeMaximized);
315         m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::activeWindowMaximizedChanged
316                                         , this, &VisibilityManager::dodgeMaximized);
317 
318         dodgeMaximized();
319         break;
320     }
321 
322     case Types::DodgeAllWindows: {
323         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged
324                                       , this, &VisibilityManager::dodgeAllWindows);
325 
326         m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::existsWindowTouchingChanged
327                                         , this, &VisibilityManager::dodgeAllWindows);
328 
329         dodgeAllWindows();
330         break;
331     }
332 
333     case Types::WindowsGoBelow:
334         break;
335 
336     case Types::WindowsCanCover:
337         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged, this, [&]() {
338             raiseView(m_containsMouse);
339         });
340 
341         raiseView(m_containsMouse);
342         break;
343 
344     case Types::WindowsAlwaysCover:
345         break;
346 
347     case Types::SidebarOnDemand:
348         m_connections[base] = connect(m_latteView, &Latte::View::inEditModeChanged, this, [&]() {
349             if (!m_latteView->inEditMode()) {
350                 m_isRequestedShownSidebarOnDemand = false;
351                 updateHiddenState();
352             }
353         });
354 
355         m_isRequestedShownSidebarOnDemand = false;
356         updateHiddenState();
357         break;
358 
359     case Types::SidebarAutoHide:
360         m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged, this, [&]() {
361             if (!m_latteView->inEditMode()) {
362                 updateHiddenState();
363             }
364         });
365 
366         m_connections[base+1] = connect(m_latteView, &Latte::View::inEditModeChanged, this, [&]() {
367             if (m_latteView->inEditMode() && !m_isHidden) {
368                 updateHiddenState();
369             }
370         });
371 
372         toggleHiddenState();
373         break;
374 
375     default:
376         break;
377     }
378 
379     emit modeChanged();
380 }
381 
updateStrutsAfterTimer()382 void VisibilityManager::updateStrutsAfterTimer()
383 {
384     bool execute = !m_timerBlockStrutsUpdate.isActive();
385 
386     m_timerBlockStrutsUpdate.start();
387 
388     if (execute) {
389         updateStrutsBasedOnLayoutsAndActivities();
390     }
391 }
392 
updateSidebarState()393 void VisibilityManager::updateSidebarState()
394 {
395     bool cursidebarstate = ((m_mode == Types::SidebarOnDemand)
396                             || (m_mode == Types::SidebarAutoHide));
397 
398     if (m_isSidebar == cursidebarstate) {
399         return;
400     }
401 
402     m_isSidebar == cursidebarstate;
403     emit isSidebarChanged();
404 
405 }
406 
updateStrutsBasedOnLayoutsAndActivities(bool forceUpdate)407 void VisibilityManager::updateStrutsBasedOnLayoutsAndActivities(bool forceUpdate)
408 {
409     bool inMultipleLayoutsAndCurrent = (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts
410                                         && m_latteView->layout() && !m_latteView->positioner()->inRelocationAnimation()
411                                         && m_latteView->layout()->isCurrent());
412 
413     if (m_strutsThickness>0 && canSetStrut() && (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout || inMultipleLayoutsAndCurrent)) {
414         QRect computedStruts = acceptableStruts();
415         if (m_publishedStruts != computedStruts || forceUpdate) {
416             //! Force update is needed when very important events happen in DE and there is a chance
417             //! that previously even though struts where sent the DE did not accept them.
418             //! Such a case is when STOPPING an Activity and windows faulty become invisible even
419             //! though they should not. In such case setting struts when the windows are hidden
420             //! the struts do not take any effect
421             m_publishedStruts = computedStruts;
422             m_wm->setViewStruts(*m_latteView, m_publishedStruts, m_latteView->location());
423         }
424     } else {
425         m_publishedStruts = QRect();
426         m_wm->removeViewStruts(*m_latteView);
427     }
428 }
429 
canSetStrut() const430 bool VisibilityManager::canSetStrut() const
431 {
432     if (m_latteView->positioner()->isOffScreen()) {
433         return false;
434     }
435 
436     if (!KWindowSystem::isPlatformX11() || !m_wm->isKWinRunning()/*alternative de*/) {
437         return true;
438     }
439 
440     // read the wm name, need to do this every time which means a roundtrip unfortunately
441     // but WM might have changed
442     //NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck);
443     //if (qstricmp(rootInfo.wmName(), "KWin") == 0) {
444     // KWin since 5.7 can handle this fine, so only exclude for other window managers
445     //return true;
446     //}
447 
448     if (qGuiApp->screens().count() < 2) {
449         return true;
450     }
451 
452     const QRect thisScreen = m_latteView->screen()->geometry();
453 
454     // Extended struts against a screen edge near to another screen are really harmful, so windows maximized under the panel is a lesser pain
455     // TODO: force "windows can cover" in those cases?
456     for (QScreen *screen : qGuiApp->screens()) {
457         if (!screen || m_latteView->screen() == screen) {
458             continue;
459         }
460 
461         const QRect otherScreen = screen->geometry();
462 
463         switch (m_latteView->location()) {
464         case Plasma::Types::TopEdge:
465             if (otherScreen.bottom() <= thisScreen.top()) {
466                 return false;
467             }
468             break;
469         case Plasma::Types::BottomEdge:
470             if (otherScreen.top() >= thisScreen.bottom()) {
471                 return false;
472             }
473             break;
474         case Plasma::Types::RightEdge:
475             if (otherScreen.left() >= thisScreen.right()) {
476                 return false;
477             }
478             break;
479         case Plasma::Types::LeftEdge:
480             if (otherScreen.right() <= thisScreen.left()) {
481                 return false;
482             }
483             break;
484         default:
485             return false;
486         }
487     }
488 
489     return true;
490 }
491 
acceptableStruts()492 QRect VisibilityManager::acceptableStruts()
493 {
494     QRect calcs;
495 
496     switch (m_latteView->location()) {
497     case Plasma::Types::TopEdge: {
498         calcs = QRect(m_latteView->x(), m_latteView->screenGeometry().top(), m_latteView->width(), m_strutsThickness);
499         break;
500     }
501 
502     case Plasma::Types::BottomEdge: {
503         int y = m_latteView->screenGeometry().bottom() - m_strutsThickness + 1 /* +1, is needed in order to not leave a gap at screen_edge*/;
504         calcs = QRect(m_latteView->x(), y, m_latteView->width(), m_strutsThickness);
505         break;
506     }
507 
508     case Plasma::Types::LeftEdge: {
509         calcs = QRect(m_latteView->screenGeometry().left(), m_latteView->y(), m_strutsThickness, m_latteView->height());
510         break;
511     }
512 
513     case Plasma::Types::RightEdge: {
514         int x = m_latteView->screenGeometry().right() - m_strutsThickness + 1 /* +1, is needed in order to not leave a gap at screen_edge*/;
515         calcs = QRect(x, m_latteView->y(), m_strutsThickness, m_latteView->height());
516         break;
517     }
518     }
519 
520     return calcs;
521 }
522 
raiseOnDesktop() const523 bool VisibilityManager::raiseOnDesktop() const
524 {
525     return m_raiseOnDesktopChange;
526 }
527 
setRaiseOnDesktop(bool enable)528 void VisibilityManager::setRaiseOnDesktop(bool enable)
529 {
530     if (enable == m_raiseOnDesktopChange)
531         return;
532 
533     m_raiseOnDesktopChange = enable;
534     emit raiseOnDesktopChanged();
535 }
536 
raiseOnActivity() const537 bool VisibilityManager::raiseOnActivity() const
538 {
539     return m_raiseOnActivityChange;
540 }
541 
setRaiseOnActivity(bool enable)542 void VisibilityManager::setRaiseOnActivity(bool enable)
543 {
544     if (enable == m_raiseOnActivityChange)
545         return;
546 
547     m_raiseOnActivityChange = enable;
548     emit raiseOnActivityChanged();
549 }
550 
isBelowLayer() const551 bool VisibilityManager::isBelowLayer() const
552 {
553     return m_isBelowLayer;
554 }
555 
setIsBelowLayer(bool below)556 void VisibilityManager::setIsBelowLayer(bool below)
557 {
558     if (m_isBelowLayer == below) {
559         return;
560     }
561 
562     m_isBelowLayer = below;
563 
564     updateGhostWindowState();
565 
566     emit isBelowLayerChanged();
567 }
568 
isHidden() const569 bool VisibilityManager::isHidden() const
570 {
571     return m_isHidden;
572 }
573 
setIsHidden(bool isHidden)574 void VisibilityManager::setIsHidden(bool isHidden)
575 {
576     if (m_isHidden == isHidden)
577         return;
578 
579     m_isHidden = isHidden;
580     updateGhostWindowState();
581 
582     emit isHiddenChanged();
583 }
584 
isShownFully() const585 bool VisibilityManager::isShownFully() const
586 {
587     return m_isShownFully;
588 }
589 
setIsShownFully(bool fully)590 void VisibilityManager::setIsShownFully(bool fully)
591 {
592     if (m_isShownFully == fully) {
593         return;
594     }
595 
596     m_isShownFully = fully;
597     emit isShownFullyChanged();
598 }
599 
hidingIsBlocked() const600 bool VisibilityManager::hidingIsBlocked() const
601 {
602     return (m_blockHidingEvents.count() > 0);
603 }
604 
isFloatingGapWindowEnabled() const605 bool VisibilityManager::isFloatingGapWindowEnabled() const
606 {
607     return m_isFloatingGapWindowEnabled;
608 }
609 
setIsFloatingGapWindowEnabled(bool enabled)610 void VisibilityManager::setIsFloatingGapWindowEnabled(bool enabled)
611 {
612     if (m_isFloatingGapWindowEnabled == enabled) {
613         return;
614     }
615 
616     m_isFloatingGapWindowEnabled = enabled;
617     emit isFloatingGapWindowEnabledChanged();
618 }
619 
hasBlockHidingEvent(const QString & type)620 bool VisibilityManager::hasBlockHidingEvent(const QString &type)
621 {
622     return (!type.isEmpty() && m_blockHidingEvents.contains(type));
623 }
624 
addBlockHidingEvent(const QString & type)625 void VisibilityManager::addBlockHidingEvent(const QString &type)
626 {
627     if (m_blockHidingEvents.contains(type) || type.isEmpty()) {
628         return;
629     }
630     //qDebug() << " org.kde.late {{ ++++ adding block hiding event :: " << type;
631 
632     bool prevHidingIsBlocked = hidingIsBlocked();
633 
634     m_blockHidingEvents << type;
635 
636     if (prevHidingIsBlocked != hidingIsBlocked()) {
637         emit hidingIsBlockedChanged();
638     }
639 }
640 
removeBlockHidingEvent(const QString & type)641 void VisibilityManager::removeBlockHidingEvent(const QString &type)
642 {
643     if (!m_blockHidingEvents.contains(type) || type.isEmpty()) {
644         return;
645     }
646     //qDebug() << " org.kde.latte {{ ---- remove block hiding event :: " << type;
647 
648     bool prevHidingIsBlocked = hidingIsBlocked();
649 
650     m_blockHidingEvents.removeAll(type);
651 
652     if (prevHidingIsBlocked != hidingIsBlocked()) {
653         emit hidingIsBlockedChanged();
654     }
655 }
656 
onHidingIsBlockedChanged()657 void VisibilityManager::onHidingIsBlockedChanged()
658 {
659     if (hidingIsBlocked()) {
660         m_timerHide.stop();
661         emit mustBeShown();
662     } else {
663         updateHiddenState();
664     }
665 }
666 
onHeadThicknessChanged()667 void VisibilityManager::onHeadThicknessChanged()
668 {
669     if (!m_timerPublishFrameExtents.isActive()) {
670         m_timerPublishFrameExtents.start();
671     }
672 }
673 
publishFrameExtents(bool forceUpdate)674 void VisibilityManager::publishFrameExtents(bool forceUpdate)
675 {
676     if (m_frameExtentsHeadThicknessGap != m_latteView->headThicknessGap()
677             || m_frameExtentsLocation != m_latteView->location()
678             || forceUpdate) {
679 
680         m_frameExtentsLocation = m_latteView->location();
681         m_frameExtentsHeadThicknessGap = m_latteView->headThicknessGap();
682 
683         if (KWindowSystem::isPlatformX11() && m_latteView->devicePixelRatio()!=1.0) {
684             //!Fix for X11 Global Scale
685             m_frameExtentsHeadThicknessGap = qRound(m_frameExtentsHeadThicknessGap * m_latteView->devicePixelRatio());
686         }
687 
688         QMargins frameExtents(0, 0, 0, 0);
689 
690         if (m_latteView->location() == Plasma::Types::LeftEdge) {
691             frameExtents.setRight(m_frameExtentsHeadThicknessGap);
692         } else if (m_latteView->location() == Plasma::Types::TopEdge) {
693             frameExtents.setBottom(m_frameExtentsHeadThicknessGap);
694         } else if (m_latteView->location() == Plasma::Types::RightEdge) {
695             frameExtents.setLeft(m_frameExtentsHeadThicknessGap);
696         } else {
697             frameExtents.setTop(m_frameExtentsHeadThicknessGap);
698         }
699 
700         bool bypasswm{m_latteView->byPassWM() && KWindowSystem::isPlatformX11()};
701 
702         qDebug() << " -> Frame Extents :: " << m_frameExtentsLocation << " __ " << " extents :: " << frameExtents << " bypasswm :: " << bypasswm;
703 
704         if (!frameExtents.isNull() && !m_latteView->behaveAsPlasmaPanel() && !bypasswm) {
705             //! When a view returns its frame extents to zero then that triggers a compositor
706             //! strange behavior that moves/hides the view totally and freezes entire Latte
707             //! this is why we have blocked that setting
708             m_wm->setFrameExtents(m_latteView, frameExtents);
709         } else if (m_latteView->behaveAsPlasmaPanel() || bypasswm) {
710             QMargins panelExtents(0, 0, 0, 0);
711             m_wm->setFrameExtents(m_latteView, panelExtents);
712             emit frameExtentsCleared();
713         }
714     }
715 }
716 
timerShow() const717 int VisibilityManager::timerShow() const
718 {
719     return m_timerShow.interval();
720 }
721 
setTimerShow(int msec)722 void VisibilityManager::setTimerShow(int msec)
723 {
724     if (m_timerShow.interval() == msec) {
725         return;
726     }
727 
728     m_timerShow.setInterval(msec);
729     emit timerShowChanged();
730 }
731 
timerHide() const732 int VisibilityManager::timerHide() const
733 {
734     return m_timerHideInterval;
735 }
736 
setTimerHide(int msec)737 void VisibilityManager::setTimerHide(int msec)
738 {
739     int interval = qMax(HIDEMINIMUMINTERVAL, msec);
740 
741     if (m_timerHideInterval == interval) {
742         return;
743     }
744 
745     m_timerHideInterval = interval;
746     m_timerHide.setInterval(interval);
747     emit timerHideChanged();
748 }
749 
isSidebar() const750 bool VisibilityManager::isSidebar() const
751 {
752     return m_mode == Latte::Types::SidebarOnDemand || m_mode == Latte::Types::SidebarAutoHide;
753 }
754 
supportsKWinEdges() const755 bool VisibilityManager::supportsKWinEdges() const
756 {
757     return (m_edgeGhostWindow != nullptr);
758 }
759 
updateGhostWindowState()760 void VisibilityManager::updateGhostWindowState()
761 {
762     if (supportsKWinEdges()) {
763         bool inCurrentLayout = (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout ||
764                                 (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts
765                                  && m_latteView->layout() && !m_latteView->positioner()->inRelocationAnimation()
766                                  && m_latteView->layout()->isCurrent()));
767 
768         if (inCurrentLayout) {
769             if (m_mode == Latte::Types::WindowsCanCover) {
770                 m_wm->setActiveEdge(m_edgeGhostWindow, m_isBelowLayer && !m_containsMouse);
771             } else {
772                 bool activated = (m_isHidden && !windowContainsMouse());
773 
774                 m_wm->setActiveEdge(m_edgeGhostWindow, activated);
775             }
776         } else {
777             m_wm->setActiveEdge(m_edgeGhostWindow, false);
778         }
779     }
780 }
781 
hide()782 void VisibilityManager::hide()
783 {
784     if (KWindowSystem::isPlatformX11()) {
785         m_latteView->setVisible(false);
786     }
787 }
788 
show()789 void VisibilityManager::show()
790 {
791     if (KWindowSystem::isPlatformX11()) {
792         m_latteView->setVisible(true);
793     }
794 }
795 
toggleHiddenState()796 void VisibilityManager::toggleHiddenState()
797 {
798     if (!m_latteView->inEditMode()) {
799         if (isSidebar()) {
800             // if (m_blockHidingEvents.contains(Q_FUNC_INFO)) {
801             //    removeBlockHidingEvent(Q_FUNC_INFO);
802             // }
803 
804             if (m_mode == Latte::Types::SidebarOnDemand) {
805                 m_isRequestedShownSidebarOnDemand = !m_isRequestedShownSidebarOnDemand;
806                 updateHiddenState();
807             } else if (m_mode == Latte::Types::SidebarAutoHide) {
808                 if (m_isHidden) {
809                     emit mustBeShown();
810                     startTimerHide(SIDEBARAUTOHIDEMINIMUMSHOW + m_timerHideInterval);
811                 } else {
812                     emit mustBeHide();
813                 }
814             }
815         } else {
816             /*    if (!m_isHidden && !m_blockHidingEvents.contains(Q_FUNC_INFO)) {
817                 addBlockHidingEvent(Q_FUNC_INFO);
818             } else if (m_isHidden) {
819                 removeBlockHidingEvent(Q_FUNC_INFO);
820             }*/
821         }
822     }
823 }
824 
updateHiddenState()825 void VisibilityManager::updateHiddenState()
826 {
827     if (m_dragEnter)
828         return;
829 
830     switch (m_mode) {
831     case Types::AutoHide:
832     case Types::WindowsCanCover:
833         raiseView(m_containsMouse);
834         break;
835 
836     case Types::DodgeActive:
837         dodgeActive();
838         break;
839 
840     case Types::DodgeMaximized:
841         dodgeMaximized();
842         break;
843 
844     case Types::DodgeAllWindows:
845         dodgeAllWindows();
846         break;
847 
848     case Types::SidebarOnDemand:
849         raiseView(m_isRequestedShownSidebarOnDemand);
850         break;
851 
852     case Types::SidebarAutoHide:
853         raiseView(m_latteView->inEditMode() || (m_containsMouse && !m_isHidden));
854         break;
855 
856     default:
857         break;
858     }
859 }
860 
raiseView(bool raise)861 void VisibilityManager::raiseView(bool raise)
862 {
863     if (m_mode == Latte::Types::SidebarOnDemand) {
864         if (raise && m_isHidden) {
865             emit mustBeShown();
866         } else if (!raise && !m_isHidden && !m_dragEnter && !hidingIsBlocked()) {
867             emit mustBeHide();
868         }
869         return;
870     }
871 
872     if (raise) {
873         m_timerHide.stop();
874 
875         if (!m_timerShow.isActive()) {
876             m_timerShow.start();
877         }
878     } else if (!m_dragEnter && !hidingIsBlocked()) {
879         m_timerShow.stop();
880 
881         if (m_hideNow) {
882             m_hideNow = false;
883             emit mustBeHide();
884         } else if (!m_timerHide.isActive()) {
885             startTimerHide();
886         }
887     }
888 }
889 
raiseViewTemporarily()890 void VisibilityManager::raiseViewTemporarily()
891 {
892     if (m_raiseTemporarily)
893         return;
894 
895     m_raiseTemporarily = true;
896     m_timerHide.stop();
897     m_timerShow.stop();
898 
899     if (m_isHidden)
900         emit mustBeShown();
901 
902     QTimer::singleShot(qBound(1800, 2 * m_timerHide.interval(), 3000), this, [&]() {
903         m_raiseTemporarily = false;
904         m_hideNow = true;
905         updateHiddenState();
906     });
907 }
908 
isValidMode() const909 bool VisibilityManager::isValidMode() const
910 {
911     return (m_mode != Types::None && m_mode != Types::NormalWindow);
912 }
913 
applyActivitiesToHiddenWindows(const QStringList & activities)914 void VisibilityManager::applyActivitiesToHiddenWindows(const QStringList &activities)
915 {
916     if (m_edgeGhostWindow) {
917         m_wm->setWindowOnActivities(m_edgeGhostWindow->trackedWindowId(), activities);
918     }
919 
920     if (m_floatingGapWindow) {
921         m_wm->setWindowOnActivities(m_floatingGapWindow->trackedWindowId(), activities);
922     }
923 }
924 
startTimerHide(const int & msec)925 void VisibilityManager::startTimerHide(const int &msec)
926 {
927     if (msec == 0) {
928         int secs = m_timerHideInterval;
929 
930         if (!KWindowSystem::compositingActive()) {
931             //! this is needed in order to give view time to show and
932             //! for floating case to give time to user to reach the view with its mouse
933             secs = qMax(m_timerHideInterval, m_latteView->screenEdgeMargin() > 0 ? 700 : 200);
934         }
935 
936         m_timerHide.start(secs);
937     } else {
938         m_timerHide.start(msec);
939     }
940 }
941 
dodgeActive()942 void VisibilityManager::dodgeActive()
943 {
944     if (m_raiseTemporarily)
945         return;
946 
947     //!don't send false raiseView signal when containing mouse
948     if (m_containsMouse) {
949         raiseView(true);
950         return;
951     }
952 
953     raiseView(!m_latteView->windowsTracker()->currentScreen()->activeWindowTouching());
954 }
955 
dodgeMaximized()956 void VisibilityManager::dodgeMaximized()
957 {
958     if (m_raiseTemporarily)
959         return;
960 
961     //!don't send false raiseView signal when containing mouse
962     if (m_containsMouse) {
963         raiseView(true);
964         return;
965     }
966 
967     raiseView(!m_latteView->windowsTracker()->currentScreen()->activeWindowMaximized());
968 }
969 
dodgeAllWindows()970 void VisibilityManager::dodgeAllWindows()
971 {
972     if (m_raiseTemporarily)
973         return;
974 
975     if (m_containsMouse) {
976         raiseView(true);
977         return;
978     }
979 
980     bool windowIntersects{m_latteView->windowsTracker()->currentScreen()->activeWindowTouching() || m_latteView->windowsTracker()->currentScreen()->existsWindowTouching()};
981 
982     raiseView(!windowIntersects);
983 }
984 
saveConfig()985 void VisibilityManager::saveConfig()
986 {
987     if (!m_latteView->containment()) {
988         return;
989     }
990 
991     auto config = m_latteView->containment()->config();
992 
993     config.writeEntry("enableKWinEdges", m_enableKWinEdgesFromUser);
994     config.writeEntry("timerShow", m_timerShow.interval());
995     config.writeEntry("timerHide", m_timerHideInterval);
996     config.writeEntry("raiseOnDesktopChange", m_raiseOnDesktopChange);
997     config.writeEntry("raiseOnActivityChange", m_raiseOnActivityChange);
998     config.writeEntry("visibility", static_cast<int>(m_mode));
999 
1000 }
1001 
restoreConfig()1002 void VisibilityManager::restoreConfig()
1003 {
1004     auto config = m_latteView->containment()->config();
1005     setTimerHide(qMax(HIDEMINIMUMINTERVAL, config.readEntry("timerHide", 700)));
1006     setTimerShow(config.readEntry("timerShow", 0));
1007     setEnableKWinEdges(config.readEntry("enableKWinEdges", true));
1008     setRaiseOnDesktop(config.readEntry("raiseOnDesktopChange", false));
1009     setRaiseOnActivity(config.readEntry("raiseOnActivityChange", false));
1010 
1011     setMode((Types::Visibility)(config.readEntry("visibility", (int)(Types::DodgeActive))));
1012 }
1013 
containsMouse() const1014 bool VisibilityManager::containsMouse() const
1015 {
1016     return m_containsMouse;
1017 }
1018 
setContainsMouse(bool contains)1019 void VisibilityManager::setContainsMouse(bool contains)
1020 {
1021     if (m_containsMouse == contains) {
1022         return;
1023     }
1024 
1025     m_containsMouse = contains;
1026     emit containsMouseChanged();
1027 }
1028 
windowContainsMouse()1029 bool VisibilityManager::windowContainsMouse()
1030 {
1031     return m_containsMouse || (m_edgeGhostWindow && m_edgeGhostWindow->containsMouse());
1032 }
1033 
checkMouseInFloatingArea()1034 void VisibilityManager::checkMouseInFloatingArea()
1035 {
1036     if (m_isFloatingGapWindowEnabled) {
1037         if (!m_floatingGapWindow) {
1038             createFloatingGapWindow();
1039         }
1040 
1041         m_floatingGapWindow->callAsyncContainsMouse();
1042     }
1043 }
1044 
viewEventManager(QEvent * ev)1045 void VisibilityManager::viewEventManager(QEvent *ev)
1046 {
1047     switch (ev->type()) {
1048     case QEvent::Enter:
1049         setContainsMouse(true);
1050         break;
1051 
1052     case QEvent::Leave:
1053         m_dragEnter = false;
1054         setContainsMouse(false);
1055         break;
1056 
1057     case QEvent::DragEnter:
1058         m_dragEnter = true;
1059 
1060         if (m_isHidden && !isSidebar()) {
1061             emit mustBeShown();
1062         }
1063 
1064         break;
1065 
1066     case QEvent::DragLeave:
1067     case QEvent::Drop:
1068         m_dragEnter = false;
1069         updateHiddenState();
1070         break;
1071 
1072     default:
1073         break;
1074     }
1075 }
1076 
1077 //! KWin Edges Support functions
enableKWinEdges() const1078 bool VisibilityManager::enableKWinEdges() const
1079 {
1080     return m_enableKWinEdgesFromUser;
1081 }
1082 
setEnableKWinEdges(bool enable)1083 void VisibilityManager::setEnableKWinEdges(bool enable)
1084 {
1085     if (m_enableKWinEdgesFromUser == enable) {
1086         return;
1087     }
1088 
1089     m_enableKWinEdgesFromUser = enable;
1090 
1091     emit enableKWinEdgesChanged();
1092 }
1093 
updateKWinEdgesSupport()1094 void VisibilityManager::updateKWinEdgesSupport()
1095 {
1096     if ((m_mode == Types::AutoHide
1097          || m_mode == Types::DodgeActive
1098          || m_mode == Types::DodgeAllWindows
1099          || m_mode == Types::DodgeMaximized)
1100             && !m_latteView->byPassWM()) {
1101 
1102         if (m_enableKWinEdgesFromUser || m_latteView->behaveAsPlasmaPanel()) {
1103             createEdgeGhostWindow();
1104         } else if (!m_enableKWinEdgesFromUser) {
1105             deleteEdgeGhostWindow();
1106         }
1107     } else if (m_mode == Types::WindowsCanCover) {
1108         createEdgeGhostWindow();
1109     } else {
1110         deleteEdgeGhostWindow();
1111     }
1112 }
1113 
onIsFloatingGapWindowEnabledChanged()1114 void VisibilityManager::onIsFloatingGapWindowEnabledChanged()
1115 {
1116     if (m_isFloatingGapWindowEnabled) {
1117         createFloatingGapWindow();
1118     } else {
1119         deleteFloatingGapWindow();
1120     }
1121 }
1122 
createEdgeGhostWindow()1123 void VisibilityManager::createEdgeGhostWindow()
1124 {
1125     if (!m_edgeGhostWindow) {
1126         m_edgeGhostWindow = new ScreenEdgeGhostWindow(m_latteView);
1127 
1128         connect(m_edgeGhostWindow, &ScreenEdgeGhostWindow::containsMouseChanged, this, [ = ](bool contains) {
1129             if (contains) {
1130                 raiseView(true);
1131             } else {
1132                 m_timerShow.stop();
1133                 updateGhostWindowState();
1134             }
1135         });
1136 
1137         connect(m_edgeGhostWindow, &ScreenEdgeGhostWindow::dragEntered, this, [&]() {
1138             if (m_isHidden) {
1139                 emit mustBeShown();
1140             }
1141         });
1142 
1143         m_connectionsKWinEdges[0] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentActivityChanged,
1144                                             this, [&]() {
1145             bool inCurrentLayout = (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::SingleLayout ||
1146                                     (m_corona->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts
1147                                      && m_latteView->layout() && !m_latteView->positioner()->inRelocationAnimation()
1148                                      && m_latteView->layout()->isCurrent()));
1149 
1150             if (m_edgeGhostWindow) {
1151                 if (inCurrentLayout) {
1152                     m_wm->setActiveEdge(m_edgeGhostWindow, m_isHidden);
1153                 } else {
1154                     m_wm->setActiveEdge(m_edgeGhostWindow, false);
1155                 }
1156             }
1157         });
1158 
1159         emit supportsKWinEdgesChanged();
1160     }
1161 }
1162 
deleteEdgeGhostWindow()1163 void VisibilityManager::deleteEdgeGhostWindow()
1164 {
1165     if (m_edgeGhostWindow) {
1166         m_edgeGhostWindow->deleteLater();
1167         m_edgeGhostWindow = nullptr;
1168 
1169         for (auto &c : m_connectionsKWinEdges) {
1170             disconnect(c);
1171         }
1172 
1173         emit supportsKWinEdgesChanged();
1174     }
1175 }
1176 
createFloatingGapWindow()1177 void VisibilityManager::createFloatingGapWindow()
1178 {
1179     if (!m_floatingGapWindow) {
1180         m_floatingGapWindow = new FloatingGapWindow(m_latteView);
1181 
1182         connect(m_floatingGapWindow, &FloatingGapWindow::asyncContainsMouseChanged, this, [ = ](bool contains) {
1183             if (contains) {
1184                 if (m_isFloatingGapWindowEnabled && !m_isHidden) {
1185                     //! immediate call after contains mouse checks for mouse in sensitive floating areas
1186                     updateHiddenState();
1187                 }
1188             } else {
1189                 if (m_isFloatingGapWindowEnabled && !m_isHidden) {
1190                     //! immediate call after contains mouse checks for mouse in sensitive floating areas
1191                     emit mustBeHide();
1192                 }
1193             }
1194         });
1195     }
1196 }
1197 
deleteFloatingGapWindow()1198 void VisibilityManager::deleteFloatingGapWindow()
1199 {
1200     if (m_floatingGapWindow) {
1201         m_floatingGapWindow->deleteLater();
1202         m_floatingGapWindow = nullptr;
1203     }
1204 }
1205 
supportsFloatingGap() const1206 bool VisibilityManager::supportsFloatingGap() const
1207 {
1208     return (m_floatingGapWindow != nullptr);
1209 }
1210 
1211 
1212 //! END: VisibilityManager implementation
1213 
1214 }
1215 }
1216