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 "waylandinterface.h"
9 
10 // local
11 #include <coretypes.h>
12 #include "../view/positioner.h"
13 #include "../view/view.h"
14 #include "../view/settings/subconfigview.h"
15 #include "../view/helpers/screenedgeghostwindow.h"
16 #include "../lattecorona.h"
17 
18 // Qt
19 #include <QDebug>
20 #include <QTimer>
21 #include <QApplication>
22 #include <QtX11Extras/QX11Info>
23 #include <QQuickView>
24 #include <QLatin1String>
25 
26 // KDE
27 #include <KWindowSystem>
28 #include <KWindowInfo>
29 #include <KWayland/Client/surface.h>
30 
31 #if KF5_VERSION_MINOR >= 52
32 #include <KWayland/Client/plasmavirtualdesktop.h>
33 #endif
34 
35 // X11
36 #include <NETWM>
37 
38 using namespace KWayland::Client;
39 
40 namespace Latte {
41 
42 class Private::GhostWindow : public QQuickView
43 {
44     Q_OBJECT
45 
46 public:
47     WindowSystem::WindowId m_winId;
48 
GhostWindow(WindowSystem::WaylandInterface * waylandInterface)49     GhostWindow(WindowSystem::WaylandInterface *waylandInterface)
50         : m_waylandInterface(waylandInterface) {
51         setFlags(Qt::FramelessWindowHint
52                  | Qt::WindowStaysOnTopHint
53                  | Qt::NoDropShadowWindowHint
54                  | Qt::WindowDoesNotAcceptFocus);
55 
56         setColor(QColor(Qt::transparent));
57         setClearBeforeRendering(true);
58 
59         connect(m_waylandInterface, &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, &GhostWindow::identifyWinId);
60 
61         setupWaylandIntegration();
62         show();
63     }
64 
~GhostWindow()65     ~GhostWindow() {
66         m_waylandInterface->unregisterIgnoredWindow(m_winId);
67         delete m_shellSurface;
68     }
69 
setGeometry(const QRect & rect)70     void setGeometry(const QRect &rect) {
71         if (geometry() == rect) {
72             return;
73         }
74 
75         m_validGeometry = rect;
76 
77         setMinimumSize(rect.size());
78         setMaximumSize(rect.size());
79         resize(rect.size());
80 
81         m_shellSurface->setPosition(rect.topLeft());
82     }
83 
setupWaylandIntegration()84     void setupWaylandIntegration() {
85         using namespace KWayland::Client;
86 
87         if (m_shellSurface)
88             return;
89 
90         Surface *s{Surface::fromWindow(this)};
91 
92         if (!s)
93             return;
94 
95         m_shellSurface = m_waylandInterface->waylandCoronaInterface()->createSurface(s, this);
96         qDebug() << "wayland ghost window surface was created...";
97 
98         m_shellSurface->setSkipTaskbar(true);
99         m_shellSurface->setPanelTakesFocus(false);
100         m_shellSurface->setRole(PlasmaShellSurface::Role::Panel);
101         m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible);
102     }
103 
104     KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr};
105     WindowSystem::WaylandInterface *m_waylandInterface{nullptr};
106 
107     //! geometry() function under wayland does not return nice results
108     QRect m_validGeometry;
109 
110 public slots:
identifyWinId()111     void identifyWinId() {
112         if (m_winId.isNull()) {
113             m_winId = m_waylandInterface->winIdFor("latte-dock", m_validGeometry);
114             m_waylandInterface->registerIgnoredWindow(m_winId);
115         }
116     }
117 };
118 
119 namespace WindowSystem {
120 
WaylandInterface(QObject * parent)121 WaylandInterface::WaylandInterface(QObject *parent)
122     : AbstractWindowInterface(parent)
123 {
124     m_corona = qobject_cast<Latte::Corona *>(parent);
125 }
126 
~WaylandInterface()127 WaylandInterface::~WaylandInterface()
128 {
129 }
130 
init()131 void WaylandInterface::init()
132 {
133 }
134 
initWindowManagement(KWayland::Client::PlasmaWindowManagement * windowManagement)135 void WaylandInterface::initWindowManagement(KWayland::Client::PlasmaWindowManagement *windowManagement)
136 {
137     if (m_windowManagement == windowManagement) {
138         return;
139     }
140 
141     m_windowManagement = windowManagement;
142 
143     connect(m_windowManagement, &PlasmaWindowManagement::windowCreated, this, &WaylandInterface::windowCreatedProxy);
144     connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, this, [&]() noexcept {
145         auto w = m_windowManagement->activeWindow();
146         if (!w || (w && (!m_ignoredWindows.contains(w->internalId()))) ) {
147             emit activeWindowChanged(w ? w->internalId() : 0);
148         }
149 
150     }, Qt::QueuedConnection);
151 }
152 
153 #if KF5_VERSION_MINOR >= 52
initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement * virtualDesktopManagement)154 void WaylandInterface::initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement)
155 {
156     if (m_virtualDesktopManagement == virtualDesktopManagement) {
157         return;
158     }
159 
160     m_virtualDesktopManagement = virtualDesktopManagement;
161 
162     connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, this,
163             [this](const QString &id, quint32 position) {
164         addDesktop(id, position);
165     });
166 
167     connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this,
168             [this](const QString &id) {
169         m_desktops.removeAll(id);
170 
171         if (m_currentDesktop == id) {
172             setCurrentDesktop(QString());
173         }
174     });
175 }
176 
addDesktop(const QString & id,quint32 position)177 void WaylandInterface::addDesktop(const QString &id, quint32 position)
178 {
179     if (m_desktops.contains(id)) {
180         return;
181     }
182 
183     m_desktops.append(id);
184 
185     const KWayland::Client::PlasmaVirtualDesktop *desktop = m_virtualDesktopManagement->getVirtualDesktop(id);
186 
187     QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this,
188                      [desktop, this]() {
189         setCurrentDesktop(desktop->id());
190     }
191     );
192 
193     if (desktop->isActive()) {
194         setCurrentDesktop(id);
195     }
196 }
197 
setCurrentDesktop(QString desktop)198 void WaylandInterface::setCurrentDesktop(QString desktop)
199 {
200     if (m_currentDesktop == desktop) {
201         return;
202     }
203 
204     m_currentDesktop = desktop;
205     emit currentDesktopChanged();
206 }
207 #endif
208 
waylandCoronaInterface() const209 KWayland::Client::PlasmaShell *WaylandInterface::waylandCoronaInterface() const
210 {
211     return m_corona->waylandCoronaInterface();
212 }
213 
214 //! Register Latte Ignored Windows in order to NOT be tracked
registerIgnoredWindow(WindowId wid)215 void WaylandInterface::registerIgnoredWindow(WindowId wid)
216 {
217     if (!wid.isNull() && !m_ignoredWindows.contains(wid)) {
218         m_ignoredWindows.append(wid);
219 
220         KWayland::Client::PlasmaWindow *w = windowFor(wid);
221 
222         if (w) {
223             untrackWindow(w);
224         }
225 
226         emit windowChanged(wid);
227     }
228 }
229 
unregisterIgnoredWindow(WindowId wid)230 void WaylandInterface::unregisterIgnoredWindow(WindowId wid)
231 {
232     if (m_ignoredWindows.contains(wid)) {
233         m_ignoredWindows.removeAll(wid);
234         emit windowRemoved(wid);
235     }
236 }
237 
setViewExtraFlags(QObject * view,bool isPanelWindow,Latte::Types::Visibility mode)238 void WaylandInterface::setViewExtraFlags(QObject *view, bool isPanelWindow, Latte::Types::Visibility mode)
239 {
240     KWayland::Client::PlasmaShellSurface *surface = qobject_cast<KWayland::Client::PlasmaShellSurface *>(view);
241     Latte::View *latteView = qobject_cast<Latte::View *>(view);
242     Latte::ViewPart::SubConfigView *configView = qobject_cast<Latte::ViewPart::SubConfigView *>(view);
243 
244     WindowId winId;
245 
246     if (latteView) {
247         surface = latteView->surface();
248         winId = latteView->positioner()->trackedWindowId();
249     } else if (configView) {
250         surface = configView->surface();
251         winId = configView->trackedWindowId();
252     }
253 
254     if (!surface) {
255         return;
256     }
257 
258     surface->setSkipTaskbar(true);
259 #if KF5_VERSION_MINOR >= 47
260     surface->setSkipSwitcher(true);
261 #endif
262 
263     bool atBottom{!isPanelWindow && (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover)};
264 
265     if (isPanelWindow) {
266         surface->setRole(PlasmaShellSurface::Role::Panel);
267         surface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide);
268     } else {
269         surface->setRole(PlasmaShellSurface::Role::Normal);
270     }
271 
272     if (latteView || configView) {
273         auto w = windowFor(winId);
274         if (w && !w->isOnAllDesktops()) {
275             requestToggleIsOnAllDesktops(winId);
276         }
277 
278         //! Layer to be applied
279         if (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover) {
280             setKeepBelow(winId, true);
281         } else if (mode == Latte::Types::NormalWindow) {
282             setKeepBelow(winId, false);
283             setKeepAbove(winId, false);
284         } else {
285             setKeepAbove(winId, true);
286         }
287     }
288 
289     if (atBottom){
290         //! trying to workaround WM behavior in order
291         //!  1. View at the end MUST NOT HAVE FOCUSABILITY (issue example: clicking a single active task is not minimized)
292         //!  2. View at the end MUST BE AT THE BOTTOM of windows stack
293 
294         QTimer::singleShot(50, [this, surface]() {
295             surface->setRole(PlasmaShellSurface::Role::ToolTip);
296         });
297     }
298 }
299 
setViewStruts(QWindow & view,const QRect & rect,Plasma::Types::Location location)300 void WaylandInterface::setViewStruts(QWindow &view, const QRect &rect, Plasma::Types::Location location)
301 {
302     if (!m_ghostWindows.contains(view.winId())) {
303         m_ghostWindows[view.winId()] = new Private::GhostWindow(this);
304     }
305 
306     auto w = m_ghostWindows[view.winId()];
307 
308     switch (location) {
309     case Plasma::Types::TopEdge:
310     case Plasma::Types::BottomEdge:
311         w->setGeometry({rect.x() + rect.width() / 2, rect.y(), 1, rect.height()});
312         break;
313 
314     case Plasma::Types::LeftEdge:
315     case Plasma::Types::RightEdge:
316         w->setGeometry({rect.x(), rect.y() + rect.height() / 2, rect.width(), 1});
317         break;
318 
319     default:
320         break;
321     }
322 }
323 
switchToNextVirtualDesktop()324 void WaylandInterface::switchToNextVirtualDesktop()
325 {
326 #if KF5_VERSION_MINOR >= 52
327     if (!m_virtualDesktopManagement || m_desktops.count() <= 1) {
328         return;
329     }
330 
331     int curPos = m_desktops.indexOf(m_currentDesktop);
332     int nextPos = curPos + 1;
333 
334     if (curPos >= m_desktops.count()-1) {
335         if (isVirtualDesktopNavigationWrappingAround()) {
336             nextPos = 0;
337         } else {
338             return;
339         }
340     }
341 
342     KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]);
343 
344     if (desktopObj) {
345         desktopObj->requestActivate();
346     }
347 #endif
348 }
349 
switchToPreviousVirtualDesktop()350 void WaylandInterface::switchToPreviousVirtualDesktop()
351 {
352 #if KF5_VERSION_MINOR >= 52
353     if (!m_virtualDesktopManagement || m_desktops.count() <= 1) {
354         return;
355     }
356 
357     int curPos = m_desktops.indexOf(m_currentDesktop);
358     int nextPos = curPos - 1;
359 
360     if (curPos <= 0) {
361         if (isVirtualDesktopNavigationWrappingAround()) {
362             nextPos = m_desktops.count()-1;
363         } else {
364             return;
365         }
366     }
367 
368     KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]);
369 
370     if (desktopObj) {
371         desktopObj->requestActivate();
372     }
373 #endif
374 }
375 
setWindowOnActivities(const WindowId & wid,const QStringList & nextactivities)376 void WaylandInterface::setWindowOnActivities(const WindowId &wid, const QStringList &nextactivities)
377 {
378 #if KF5_VERSION_MINOR >= 81
379     auto winfo = requestInfo(wid);
380     auto w = windowFor(wid);
381 
382     if (!w) {
383         return;
384     }
385 
386     QStringList curactivities = winfo.activities();
387 
388     if (!winfo.isOnAllActivities() && nextactivities.isEmpty()) {
389         //! window must be set to all activities
390         for(int i=0; i<curactivities.count(); ++i) {
391             w->requestLeaveActivity(curactivities[i]);
392         }
393     } else if (curactivities != nextactivities) {
394         QStringList requestenter;
395         QStringList requestleave;
396 
397         for (int i=0; i<nextactivities.count(); ++i) {
398             if (!curactivities.contains(nextactivities[i])) {
399                 requestenter << nextactivities[i];
400             }
401         }
402 
403         for (int i=0; i<curactivities.count(); ++i) {
404             if (!nextactivities.contains(curactivities[i])) {
405                 requestleave << curactivities[i];
406             }
407         }
408 
409         //! leave afterwards from deprecated activities
410         for (int i=0; i<requestleave.count(); ++i) {
411             w->requestLeaveActivity(requestleave[i]);
412         }
413 
414         //! first enter to new activities
415         for (int i=0; i<requestenter.count(); ++i) {
416             w->requestEnterActivity(requestenter[i]);
417         }
418     }
419 #endif
420 }
421 
removeViewStruts(QWindow & view)422 void WaylandInterface::removeViewStruts(QWindow &view)
423 {
424     delete m_ghostWindows.take(view.winId());
425 }
426 
activeWindow()427 WindowId WaylandInterface::activeWindow()
428 {
429     if (!m_windowManagement) {
430         return 0;
431     }
432 
433     auto wid = m_windowManagement->activeWindow();
434 
435     return wid ? wid->internalId() : 0;
436 }
437 
skipTaskBar(const QDialog & dialog)438 void WaylandInterface::skipTaskBar(const QDialog &dialog)
439 {
440     KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar);
441 }
442 
slideWindow(QWindow & view,AbstractWindowInterface::Slide location)443 void WaylandInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location)
444 {
445     auto slideLocation = KWindowEffects::NoEdge;
446 
447     switch (location) {
448     case Slide::Top:
449         slideLocation = KWindowEffects::TopEdge;
450         break;
451 
452     case Slide::Bottom:
453         slideLocation = KWindowEffects::BottomEdge;
454         break;
455 
456     case Slide::Left:
457         slideLocation = KWindowEffects::LeftEdge;
458         break;
459 
460     case Slide::Right:
461         slideLocation = KWindowEffects::RightEdge;
462         break;
463 
464     default:
465         break;
466     }
467 
468     KWindowEffects::slideWindow(view.winId(), slideLocation, -1);
469 }
470 
enableBlurBehind(QWindow & view)471 void WaylandInterface::enableBlurBehind(QWindow &view)
472 {
473     KWindowEffects::enableBlurBehind(view.winId());
474 }
475 
setActiveEdge(QWindow * view,bool active)476 void WaylandInterface::setActiveEdge(QWindow *view, bool active)
477 {
478     ViewPart::ScreenEdgeGhostWindow *window = qobject_cast<ViewPart::ScreenEdgeGhostWindow *>(view);
479 
480     if (!window) {
481         return;
482     }
483 
484     if (window->parentView()->surface() && window->parentView()->visibility()
485             && (window->parentView()->visibility()->mode() == Types::DodgeActive
486                 || window->parentView()->visibility()->mode() == Types::DodgeMaximized
487                 || window->parentView()->visibility()->mode() == Types::DodgeAllWindows
488                 || window->parentView()->visibility()->mode() == Types::AutoHide)) {
489         if (active) {
490             window->showWithMask();
491             window->surface()->requestHideAutoHidingPanel();
492         } else {
493             window->hideWithMask();
494             window->surface()->requestShowAutoHidingPanel();
495         }
496     }
497 }
498 
setFrameExtents(QWindow * view,const QMargins & extents)499 void WaylandInterface::setFrameExtents(QWindow *view, const QMargins &extents)
500 {
501     //! do nothing until there is a wayland way to provide this
502 }
503 
setInputMask(QWindow * window,const QRect & rect)504 void WaylandInterface::setInputMask(QWindow *window, const QRect &rect)
505 {
506     //! do nothins, QWindow::mask() is sufficient enough in order to define Window input mask
507 }
508 
requestInfoActive()509 WindowInfoWrap WaylandInterface::requestInfoActive()
510 {
511     if (!m_windowManagement) {
512         return {};
513     }
514 
515     auto w = m_windowManagement->activeWindow();
516 
517     if (!w) return {};
518 
519     return requestInfo(w->internalId());
520 }
521 
requestInfo(WindowId wid)522 WindowInfoWrap WaylandInterface::requestInfo(WindowId wid)
523 {
524     WindowInfoWrap winfoWrap;
525 
526     auto w = windowFor(wid);
527 
528     //!used to track Plasma DesktopView windows because during startup can not be identified properly
529     bool plasmaBlockedWindow = w && (w->appId() == QLatin1String("org.kde.plasmashell")) && !isAcceptableWindow(w);
530 
531     if (w) {
532         winfoWrap.setIsValid(isValidWindow(w) && !plasmaBlockedWindow);
533         winfoWrap.setWid(wid);
534         winfoWrap.setParentId(w->parentWindow() ? w->parentWindow()->internalId() : 0);
535         winfoWrap.setIsActive(w->isActive());
536         winfoWrap.setIsMinimized(w->isMinimized());
537         winfoWrap.setIsMaxVert(w->isMaximized());
538         winfoWrap.setIsMaxHoriz(w->isMaximized());
539         winfoWrap.setIsFullscreen(w->isFullscreen());
540         winfoWrap.setIsShaded(w->isShaded());
541         winfoWrap.setIsOnAllDesktops(w->isOnAllDesktops());
542 #if KF5_VERSION_MINOR >= 81
543         winfoWrap.setIsOnAllActivities(w->plasmaActivities().isEmpty());
544 #else
545         winfoWrap.setIsOnAllActivities(true);
546 #endif
547         winfoWrap.setIsKeepAbove(w->isKeepAbove());
548         winfoWrap.setIsKeepBelow(w->isKeepBelow());
549         winfoWrap.setGeometry(w->geometry());
550 
551 #if KF5_VERSION_MINOR >= 47
552         winfoWrap.setHasSkipSwitcher(w->skipSwitcher());
553 #endif
554         winfoWrap.setHasSkipTaskbar(w->skipTaskbar());
555 
556         //! BEGIN:Window Abilities
557         winfoWrap.setIsClosable(w->isCloseable());
558         winfoWrap.setIsFullScreenable(w->isFullscreenable());
559         winfoWrap.setIsMaximizable(w->isMaximizeable());
560         winfoWrap.setIsMinimizable(w->isMinimizeable());
561         winfoWrap.setIsMovable(w->isMovable());
562         winfoWrap.setIsResizable(w->isResizable());
563         winfoWrap.setIsShadeable(w->isShadeable());
564         winfoWrap.setIsVirtualDesktopsChangeable(w->isVirtualDesktopChangeable());
565         //! END:Window Abilities
566 
567         winfoWrap.setDisplay(w->title());
568 #if KF5_VERSION_MINOR >= 52
569         winfoWrap.setDesktops(w->plasmaVirtualDesktops());
570 #endif
571 
572 #if KF5_VERSION_MINOR >= 81
573         winfoWrap.setActivities(w->plasmaActivities());
574 #else
575         winfoWrap.setActivities(QStringList());
576 #endif
577     } else {
578         winfoWrap.setIsValid(false);
579     }
580 
581     if (plasmaBlockedWindow) {
582         windowRemoved(w->internalId());
583     }
584 
585     return winfoWrap;
586 }
587 
appDataFor(WindowId wid)588 AppData WaylandInterface::appDataFor(WindowId wid)
589 {
590     auto window = windowFor(wid);
591 
592     if (window) {
593         const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(),
594                                                                    window->pid(), rulesConfig));
595 
596         return data;
597     }
598 
599     AppData empty;
600 
601     return empty;
602 }
603 
windowFor(WindowId wid)604 KWayland::Client::PlasmaWindow *WaylandInterface::windowFor(WindowId wid)
605 {
606     auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&wid](PlasmaWindow * w) noexcept {
607             return w->isValid() && w->internalId() == wid;
608 });
609 
610     if (it == m_windowManagement->windows().constEnd()) {
611         return nullptr;
612     }
613 
614     return *it;
615 }
616 
iconFor(WindowId wid)617 QIcon WaylandInterface::iconFor(WindowId wid)
618 {
619     auto window = windowFor(wid);
620 
621     if (window) {
622         return window->icon();
623     }
624 
625 
626     return QIcon();
627 }
628 
winIdFor(QString appId,QString title)629 WindowId WaylandInterface::winIdFor(QString appId, QString title)
630 {
631     auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&appId, &title](PlasmaWindow * w) noexcept {
632         return w->isValid() && w->appId() == appId && w->title().startsWith(title);
633     });
634 
635     if (it == m_windowManagement->windows().constEnd()) {
636         return QVariant();
637     }
638 
639     return (*it)->internalId();
640 }
641 
winIdFor(QString appId,QRect geometry)642 WindowId WaylandInterface::winIdFor(QString appId, QRect geometry)
643 {
644     auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&appId, &geometry](PlasmaWindow * w) noexcept {
645         return w->isValid() && w->appId() == appId && w->geometry() == geometry;
646     });
647 
648     if (it == m_windowManagement->windows().constEnd()) {
649         return QVariant();
650     }
651 
652     return (*it)->internalId();
653 }
654 
windowCanBeDragged(WindowId wid)655 bool WaylandInterface::windowCanBeDragged(WindowId wid)
656 {
657     auto w = windowFor(wid);
658 
659     if (w && isValidWindow(w)) {
660         WindowInfoWrap winfo = requestInfo(wid);
661         return (winfo.isValid()
662                 && w->isMovable()
663                 && !winfo.isMinimized()
664                 && inCurrentDesktopActivity(winfo));
665     }
666 
667     return false;
668 }
669 
windowCanBeMaximized(WindowId wid)670 bool WaylandInterface::windowCanBeMaximized(WindowId wid)
671 {
672     auto w = windowFor(wid);
673 
674     if (w && isValidWindow(w)) {
675         WindowInfoWrap winfo = requestInfo(wid);
676         return (winfo.isValid()
677                 && w->isMaximizeable()
678                 && !winfo.isMinimized()
679                 && inCurrentDesktopActivity(winfo));
680     }
681 
682     return false;
683 }
684 
requestActivate(WindowId wid)685 void WaylandInterface::requestActivate(WindowId wid)
686 {
687     auto w = windowFor(wid);
688 
689     if (w) {
690         w->requestActivate();
691     }
692 }
693 
requestClose(WindowId wid)694 void WaylandInterface::requestClose(WindowId wid)
695 {
696     auto w = windowFor(wid);
697 
698     if (w) {
699         w->requestClose();
700     }
701 }
702 
703 
requestMoveWindow(WindowId wid,QPoint from)704 void WaylandInterface::requestMoveWindow(WindowId wid, QPoint from)
705 {
706     WindowInfoWrap wInfo = requestInfo(wid);
707 
708     if (windowCanBeDragged(wid) && inCurrentDesktopActivity(wInfo)) {
709         auto w = windowFor(wid);
710 
711         if (w && isValidWindow(w)) {
712             w->requestMove();
713         }
714     }
715 }
716 
requestToggleIsOnAllDesktops(WindowId wid)717 void WaylandInterface::requestToggleIsOnAllDesktops(WindowId wid)
718 {
719 #if KF5_VERSION_MINOR >= 52
720     auto w = windowFor(wid);
721 
722     if (w && isValidWindow(w) && m_desktops.count() > 1) {
723         if (w->isOnAllDesktops()) {
724             w->requestEnterVirtualDesktop(m_currentDesktop);
725         } else {
726             const QStringList &now = w->plasmaVirtualDesktops();
727 
728             foreach (const QString &desktop, now) {
729                 w->requestLeaveVirtualDesktop(desktop);
730             }
731         }
732     }
733 #endif
734 }
735 
requestToggleKeepAbove(WindowId wid)736 void WaylandInterface::requestToggleKeepAbove(WindowId wid)
737 {
738     auto w = windowFor(wid);
739 
740     if (w) {
741         w->requestToggleKeepAbove();
742     }
743 }
744 
setKeepAbove(WindowId wid,bool active)745 void WaylandInterface::setKeepAbove(WindowId wid, bool active)
746 {
747     auto w = windowFor(wid);
748 
749     if (w) {
750         if (active) {
751             setKeepBelow(wid, false);
752         }
753 
754         if ((w->isKeepAbove() && active) || (!w->isKeepAbove() && !active)) {
755             return;
756         }
757 
758         w->requestToggleKeepAbove();
759     }
760 }
761 
setKeepBelow(WindowId wid,bool active)762 void WaylandInterface::setKeepBelow(WindowId wid, bool active)
763 {
764     auto w = windowFor(wid);
765 
766     if (w) {
767         if (active) {
768             setKeepAbove(wid, false);
769         }
770 
771         if ((w->isKeepBelow() && active) || (!w->isKeepBelow() && !active)) {
772             return;
773         }
774 
775         w->requestToggleKeepBelow();
776     }
777 }
778 
requestToggleMinimized(WindowId wid)779 void WaylandInterface::requestToggleMinimized(WindowId wid)
780 {
781     auto w = windowFor(wid);
782     WindowInfoWrap wInfo = requestInfo(wid);
783 
784     if (w && isValidWindow(w) && inCurrentDesktopActivity(wInfo)) {
785 #if KF5_VERSION_MINOR >= 52
786         if (!m_currentDesktop.isEmpty()) {
787             w->requestEnterVirtualDesktop(m_currentDesktop);
788         }
789 #endif
790         w->requestToggleMinimized();
791     }
792 }
793 
requestToggleMaximized(WindowId wid)794 void WaylandInterface::requestToggleMaximized(WindowId wid)
795 {
796     auto w = windowFor(wid);
797     WindowInfoWrap wInfo = requestInfo(wid);
798 
799     if (w && isValidWindow(w) && windowCanBeMaximized(wid) && inCurrentDesktopActivity(wInfo)) {
800 #if KF5_VERSION_MINOR >= 52
801         if (!m_currentDesktop.isEmpty()) {
802             w->requestEnterVirtualDesktop(m_currentDesktop);
803         }
804 #endif
805         w->requestToggleMaximized();
806     }
807 }
808 
isPlasmaPanel(const KWayland::Client::PlasmaWindow * w) const809 bool WaylandInterface::isPlasmaPanel(const KWayland::Client::PlasmaWindow *w) const
810 {
811     if (!w || (w->appId() != QLatin1String("org.kde.plasmashell"))) {
812         return false;
813     }
814 
815     return AbstractWindowInterface::isPlasmaPanel(w->geometry());
816 }
817 
isFullScreenWindow(const KWayland::Client::PlasmaWindow * w) const818 bool WaylandInterface::isFullScreenWindow(const KWayland::Client::PlasmaWindow *w) const
819 {
820     if (!w) {
821         return false;
822     }
823 
824     return w->isFullscreen() || AbstractWindowInterface::isFullScreenWindow(w->geometry());
825 }
826 
isSidepanel(const KWayland::Client::PlasmaWindow * w) const827 bool WaylandInterface::isSidepanel(const KWayland::Client::PlasmaWindow *w) const
828 {
829     if (!w) {
830         return false;
831     }
832 
833     return AbstractWindowInterface::isSidepanel(w->geometry());
834 }
835 
isValidWindow(const KWayland::Client::PlasmaWindow * w)836 bool WaylandInterface::isValidWindow(const KWayland::Client::PlasmaWindow *w)
837 {
838     if (!w || !w->isValid()) {
839         return false;
840     }
841 
842     if (windowsTracker()->isValidFor(w->internalId())) {
843         return true;
844     }
845 
846     return isAcceptableWindow(w);
847 }
848 
isAcceptableWindow(const KWayland::Client::PlasmaWindow * w)849 bool WaylandInterface::isAcceptableWindow(const KWayland::Client::PlasmaWindow *w)
850 {
851     if (!w || !w->isValid()) {
852         return false;
853     }
854 
855     //! ignored windows that are not tracked
856     if (hasBlockedTracking(w->internalId())) {
857         return false;
858     }
859 
860     //! whitelisted/approved windows
861     if (isWhitelistedWindow(w->internalId())) {
862         return true;
863     }
864 
865     //! Window Checks
866     bool hasSkipTaskbar = w->skipTaskbar();
867     bool isSkipped = hasSkipTaskbar;
868 
869 #if KF5_VERSION_MINOR >= 47
870     bool hasSkipSwitcher = w->skipSwitcher();
871     isSkipped = hasSkipTaskbar && hasSkipSwitcher;
872 #endif
873 
874     if (isSkipped
875             && ((w->appId() == QLatin1String("yakuake")
876                  || (w->appId() == QLatin1String("krunner"))) )) {
877         registerWhitelistedWindow(w->internalId());
878     } else if (w->appId() == QLatin1String("org.kde.plasmashell")) {
879         if (isSkipped && isSidepanel(w)) {
880             registerWhitelistedWindow(w->internalId());
881             return true;
882         } else if (isPlasmaPanel(w) || isFullScreenWindow(w)) {
883             registerPlasmaIgnoredWindow(w->internalId());
884             return false;
885         }
886     } else if ((w->appId() == QLatin1String("latte-dock"))
887                || (w->appId().startsWith(QLatin1String("ksmserver")))) {
888         if (isFullScreenWindow(w)) {
889             registerIgnoredWindow(w->internalId());
890             return false;
891         }
892     }
893 
894     return !isSkipped;
895 }
896 
updateWindow()897 void WaylandInterface::updateWindow()
898 {
899     PlasmaWindow *pW = qobject_cast<PlasmaWindow*>(QObject::sender());
900 
901     if (isValidWindow(pW)) {
902         considerWindowChanged(pW->internalId());
903     }
904 }
905 
windowUnmapped()906 void WaylandInterface::windowUnmapped()
907 {
908     PlasmaWindow *pW = qobject_cast<PlasmaWindow*>(QObject::sender());
909 
910     if (pW) {
911         untrackWindow(pW);
912         emit windowRemoved(pW->internalId());
913     }
914 }
915 
trackWindow(KWayland::Client::PlasmaWindow * w)916 void WaylandInterface::trackWindow(KWayland::Client::PlasmaWindow *w)
917 {
918     if (!w) {
919         return;
920     }
921 
922     connect(w, &PlasmaWindow::activeChanged, this, &WaylandInterface::updateWindow);
923     connect(w, &PlasmaWindow::titleChanged, this, &WaylandInterface::updateWindow);
924     connect(w, &PlasmaWindow::fullscreenChanged, this, &WaylandInterface::updateWindow);
925     connect(w, &PlasmaWindow::geometryChanged, this, &WaylandInterface::updateWindow);
926     connect(w, &PlasmaWindow::maximizedChanged, this, &WaylandInterface::updateWindow);
927     connect(w, &PlasmaWindow::minimizedChanged, this, &WaylandInterface::updateWindow);
928     connect(w, &PlasmaWindow::shadedChanged, this, &WaylandInterface::updateWindow);
929     connect(w, &PlasmaWindow::skipTaskbarChanged, this, &WaylandInterface::updateWindow);
930     connect(w, &PlasmaWindow::onAllDesktopsChanged, this, &WaylandInterface::updateWindow);
931     connect(w, &PlasmaWindow::parentWindowChanged, this, &WaylandInterface::updateWindow);
932 
933 #if KF5_VERSION_MINOR >= 52
934     connect(w, &PlasmaWindow::plasmaVirtualDesktopEntered, this, &WaylandInterface::updateWindow);
935     connect(w, &PlasmaWindow::plasmaVirtualDesktopLeft, this, &WaylandInterface::updateWindow);
936 #else
937     connect(w, &PlasmaWindow::virtualDesktopChanged, this, &WaylandInterface::updateWindow);
938 #endif
939 
940 #if KF5_VERSION_MINOR >= 81
941     connect(w, &PlasmaWindow::plasmaActivityEntered, this, &WaylandInterface::updateWindow);
942     connect(w, &PlasmaWindow::plasmaActivityLeft, this, &WaylandInterface::updateWindow);
943 #endif
944 
945 
946     connect(w, &PlasmaWindow::unmapped, this, &WaylandInterface::windowUnmapped);
947 }
948 
untrackWindow(KWayland::Client::PlasmaWindow * w)949 void WaylandInterface::untrackWindow(KWayland::Client::PlasmaWindow *w)
950 {
951     if (!w) {
952         return;
953     }
954 
955     disconnect(w, &PlasmaWindow::activeChanged, this, &WaylandInterface::updateWindow);
956     disconnect(w, &PlasmaWindow::titleChanged, this, &WaylandInterface::updateWindow);
957     disconnect(w, &PlasmaWindow::fullscreenChanged, this, &WaylandInterface::updateWindow);
958     disconnect(w, &PlasmaWindow::geometryChanged, this, &WaylandInterface::updateWindow);
959     disconnect(w, &PlasmaWindow::maximizedChanged, this, &WaylandInterface::updateWindow);
960     disconnect(w, &PlasmaWindow::minimizedChanged, this, &WaylandInterface::updateWindow);
961     disconnect(w, &PlasmaWindow::shadedChanged, this, &WaylandInterface::updateWindow);
962     disconnect(w, &PlasmaWindow::skipTaskbarChanged, this, &WaylandInterface::updateWindow);
963     disconnect(w, &PlasmaWindow::onAllDesktopsChanged, this, &WaylandInterface::updateWindow);
964     disconnect(w, &PlasmaWindow::parentWindowChanged, this, &WaylandInterface::updateWindow);
965 
966 #if KF5_VERSION_MINOR >= 52
967     disconnect(w, &PlasmaWindow::plasmaVirtualDesktopEntered, this, &WaylandInterface::updateWindow);
968     disconnect(w, &PlasmaWindow::plasmaVirtualDesktopLeft, this, &WaylandInterface::updateWindow);
969 #else
970     disconnect(w, &PlasmaWindow::virtualDesktopChanged, this, &WaylandInterface::updateWindow);
971 #endif
972 
973 #if KF5_VERSION_MINOR >= 81
974     disconnect(w, &PlasmaWindow::plasmaActivityEntered, this, &WaylandInterface::updateWindow);
975     disconnect(w, &PlasmaWindow::plasmaActivityLeft, this, &WaylandInterface::updateWindow);
976 #endif
977 
978     disconnect(w, &PlasmaWindow::unmapped, this, &WaylandInterface::windowUnmapped);
979 }
980 
981 
windowCreatedProxy(KWayland::Client::PlasmaWindow * w)982 void WaylandInterface::windowCreatedProxy(KWayland::Client::PlasmaWindow *w)
983 {
984     if (!isAcceptableWindow(w))  {
985         return;
986     }
987 
988     trackWindow(w);
989     emit windowAdded(w->internalId());
990 
991     if (w->appId() == QLatin1String("latte-dock")) {
992         emit latteWindowAdded();
993     }
994 }
995 
996 }
997 }
998 
999 #include "waylandinterface.moc"
1000