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