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