diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 84bc32d9..a83bfcb2 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,105 +1,106 @@ set(lattedock-app_SRCS ../liblatte2/commontools.cpp ../liblatte2/types.cpp alternativeshelper.cpp infoview.cpp lattecorona.cpp screenpool.cpp main.cpp ) add_subdirectory(indicator) add_subdirectory(layout) add_subdirectory(layouts) add_subdirectory(package) add_subdirectory(plasma/extended) add_subdirectory(settings) add_subdirectory(settings/delegates) add_subdirectory(settings/tools) add_subdirectory(shortcuts) add_subdirectory(view) +add_subdirectory(view/helpers) add_subdirectory(view/indicator) add_subdirectory(view/settings) add_subdirectory(view/windowstracker) add_subdirectory(wm) add_subdirectory(wm/tracker) set(latte_dbusXML dbus/org.kde.LatteDock.xml) qt5_add_dbus_adaptor(lattedock-app_SRCS ${latte_dbusXML} lattecorona.h Latte::Corona lattedockadaptor) ki18n_wrap_ui(lattedock-app_SRCS settings/settingsdialog.ui) add_executable(latte-dock ${lattedock-app_SRCS}) include(FakeTarget.cmake) if(${KF5_VERSION_MINOR} LESS "62") target_link_libraries(latte-dock Qt5::DBus Qt5::Quick Qt5::Qml KF5::Activities KF5::Archive KF5::CoreAddons KF5::Crash KF5::DBusAddons KF5::Declarative KF5::GuiAddons KF5::GlobalAccel KF5::I18n KF5::IconThemes KF5::Notifications KF5::NewStuff KF5::QuickAddons KF5::Plasma KF5::PlasmaQuick KF5::ProcessCore KF5::WaylandClient KF5::XmlGui ) else() target_link_libraries(latte-dock Qt5::DBus Qt5::Quick Qt5::Qml KF5::Activities KF5::Archive KF5::CoreAddons KF5::Crash KF5::DBusAddons KF5::Declarative KF5::GuiAddons KF5::GlobalAccel KF5::I18n KF5::IconThemes KF5::Notifications KF5::NewStuff KF5::QuickAddons KF5::Plasma KF5::PlasmaQuick KF5::WaylandClient KF5::XmlGui ) endif() if(HAVE_X11) target_link_libraries(latte-dock Qt5::X11Extras KF5::WindowSystem ${X11_LIBRARIES} ${XCB_LIBRARIES} ) endif() configure_file(org.kde.latte-dock.desktop.cmake org.kde.latte-dock.desktop) configure_file(org.kde.latte-dock.appdata.xml.cmake org.kde.latte-dock.appdata.xml) install(TARGETS latte-dock ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.latte-dock.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.latte-dock.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install(FILES dbus/org.kde.LatteDock.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(FILES lattedock.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR}) install(FILES latte-layouts.knsrc DESTINATION ${CONFIG_INSTALL_DIR}) install(FILES latte-indicators.knsrc DESTINATION ${CONFIG_INSTALL_DIR}) add_subdirectory(packageplugins) diff --git a/app/view/CMakeLists.txt b/app/view/CMakeLists.txt index 059654d6..9717daa4 100644 --- a/app/view/CMakeLists.txt +++ b/app/view/CMakeLists.txt @@ -1,15 +1,11 @@ set(lattedock-app_SRCS ${lattedock-app_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/containmentinterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/contextmenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/effects.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/floatinggapwindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/panelshadows.cpp ${CMAKE_CURRENT_SOURCE_DIR}/positioner.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/screenedgeghostwindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/view.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visibilitymanager.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/settings/primaryconfigview.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/settings/secondaryconfigview.cpp PARENT_SCOPE ) diff --git a/app/view/helpers/CMakeLists.txt b/app/view/helpers/CMakeLists.txt new file mode 100644 index 00000000..af1f9531 --- /dev/null +++ b/app/view/helpers/CMakeLists.txt @@ -0,0 +1,7 @@ +set(lattedock-app_SRCS + ${lattedock-app_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/floatinggapwindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/screenedgeghostwindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/subwindow.cpp + PARENT_SCOPE +) diff --git a/app/view/helpers/floatinggapwindow.cpp b/app/view/helpers/floatinggapwindow.cpp new file mode 100644 index 00000000..244ca3bf --- /dev/null +++ b/app/view/helpers/floatinggapwindow.cpp @@ -0,0 +1,163 @@ +/* +* Copyright 2020 Michail Vourlakos +* +* This file is part of Latte-Dock +* +* Latte-Dock is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* Latte-Dock is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "floatinggapwindow.h" + +// local +#include "../view.h" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// X11 +#include + +namespace Latte { +namespace ViewPart { + +FloatingGapWindow::FloatingGapWindow(Latte::View *view) : + SubWindow(view, QString("Floating Gap Window")) +{ + if (m_debugMode) { + m_showColor = QColor("green"); + m_hideColor = QColor("red"); + } else { + m_showColor = QColor(Qt::transparent); + m_hideColor = QColor(Qt::transparent); + + m_showColor.setAlpha(0); + m_hideColor.setAlpha(1); + } + + setColor(m_showColor); + + //! this timer is used in order to identify if mouse is still present in sensitive floating + //! areas and in such case to prevent a real-floating view to hide itself + m_asyncMouseTimer.setSingleShot(true); + m_asyncMouseTimer.setInterval(200); + connect(&m_asyncMouseTimer, &QTimer::timeout, this, [this]() { + if (m_inAsyncContainsMouse && !m_containsMouse) { + emit asyncContainsMouseChanged(false); + hideWithMask(); + m_inAsyncContainsMouse = false; + } + }); + + + updateGeometry(); + hideWithMask(); +} + +FloatingGapWindow::~FloatingGapWindow() +{ +} + +void FloatingGapWindow::updateGeometry() +{ + if (m_latteView->positioner()->slideOffset() != 0) { + return; + } + + QRect newGeometry; + + m_thickness = m_latteView->screenEdgeMargin(); + + int length = m_latteView->formFactor() == Plasma::Types::Horizontal ? m_latteView->absoluteGeometry().width() : m_latteView->absoluteGeometry().height(); + + if (m_latteView->location() == Plasma::Types::BottomEdge) { + int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left()); + newGeometry.setX(xF); + newGeometry.setY(m_latteView->screenGeometry().bottom() - m_thickness); + } else if (m_latteView->location() == Plasma::Types::TopEdge) { + int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left()); + newGeometry.setX(xF); + newGeometry.setY(m_latteView->screenGeometry().top()); + } else if (m_latteView->location() == Plasma::Types::LeftEdge) { + int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top()); + newGeometry.setX(m_latteView->screenGeometry().left()); + newGeometry.setY(yF); + } else if (m_latteView->location() == Plasma::Types::RightEdge) { + int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top()); + newGeometry.setX(m_latteView->screenGeometry().right() - m_thickness); + newGeometry.setY(yF); + } + + if (m_latteView->formFactor() == Plasma::Types::Horizontal) { + newGeometry.setWidth(length); + newGeometry.setHeight(m_thickness + 1); + } else { + newGeometry.setWidth(m_thickness + 1); + newGeometry.setHeight(length); + } + + m_calculatedGeometry = newGeometry; + + emit calculatedGeometryChanged(); +} + +bool FloatingGapWindow::event(QEvent *e) +{ + if (e->type() == QEvent::DragEnter || e->type() == QEvent::DragMove) { + m_containsMouse = true; + + } else if (e->type() == QEvent::Enter) { + m_containsMouse = true; + + triggerAsyncContainsMouseSignals(); + } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) { + m_containsMouse = false; + + if (m_inAsyncContainsMouse) { + m_asyncMouseTimer.stop(); + m_inAsyncContainsMouse = false; + emit asyncContainsMouseChanged(true); + } + } + + return SubWindow::event(e); +} + +void FloatingGapWindow::callAsyncContainsMouse() +{ + m_inAsyncContainsMouse = true; + m_asyncMouseTimer.start(); + showWithMask(); +} + +void FloatingGapWindow::triggerAsyncContainsMouseSignals() +{ + if (!m_inAsyncContainsMouse) { + return; + } + + //! this function is called QEvent::Enter + m_asyncMouseTimer.stop(); + hideWithMask(); +} + +} +} diff --git a/app/view/floatinggapwindow.h b/app/view/helpers/floatinggapwindow.h similarity index 61% rename from app/view/floatinggapwindow.h rename to app/view/helpers/floatinggapwindow.h index 6fc2bd59..aef64671 100644 --- a/app/view/floatinggapwindow.h +++ b/app/view/helpers/floatinggapwindow.h @@ -1,139 +1,91 @@ /* * Copyright 2020 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef FLOATINGGAPWINDOW_H #define FLOATINGWINDOW_H // local -#include "../lattecorona.h" -#include "../wm/windowinfowrap.h" +#include "subwindow.h" +#include "../../lattecorona.h" +#include "../../wm/windowinfowrap.h" // Qt #include #include #include namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Corona; class View; } namespace Latte { namespace ViewPart { //! What is the importance of this class? //! //! This window is responsible to identify if the mouse is still present //! in the REAL FLOATING GAP between the VIEW and the SCREEN EDGE. //! When VIEWS are REAL FLOATING then the VIEW Window is really placed //! as it is shown to the user. In that case we need a way to check //! where the mouse is even though it is OUTSIDE the VIEW or the //! SCREENEDGEGHOSTWINDOW. The main functionality of FloatingGapWindow //! is that it is TEMPORARILY shown/draw after a MUSTHIDE signal of //! VisibilityManager was sent; in order to check if the mouse is still //! inside the FLOATINGGAP. After it has really identified where mouse //! is present, an FloatingGapWindow::asyncContainsMouse(contains) signal //! is sent. -class FloatingGapWindow : public QQuickView +class FloatingGapWindow : public SubWindow { Q_OBJECT public: FloatingGapWindow(Latte::View *view); ~FloatingGapWindow() override; - int location(); - int thickness() const; - - void hideWithMask(); - void showWithMask(); - - Latte::View *parentView(); - - KWayland::Client::PlasmaShellSurface *surface(); - void callAsyncContainsMouse(); signals: void asyncContainsMouseChanged(bool contains); //called from visibility to check if mouse is in the free sensitive floating area - void dragEntered(); - void forcedShown(); //[workaround] forced shown to avoid a KWin issue that hides windows when activities are stopped protected: bool event(QEvent *ev) override; - -private slots: - void startGeometryTimer(); - void updateGeometry(); - void fixGeometry(); + void updateGeometry() override; private: - bool containsMouse() const; - void setContainsMouse(bool contains); - void setupWaylandIntegration(); - void triggerAsyncContainsMouseSignals(); private: - bool m_debugMode{false}; - bool m_containsMouse{false}; - bool m_inDelete{false}; bool m_inAsyncContainsMouse{false}; //called from visibility to check if mouse is in the free sensitive floating area - int m_thickness{2}; - - QRect m_calculatedGeometry; - - //! [workaround] colors in order to help masking to apply immediately - //! for some reason when the window in with no content the mask is not - //! update immediately - QColor m_hideColor; - QColor m_showColor; - QTimer m_asyncMouseTimer; //called from visibility to check if mouse is in the free sensitive floating area - QTimer m_fixGeometryTimer; - - //! HACK: Timers in order to handle KWin faulty - //! behavior that hides Views when closing Activities - //! with no actual reason - QTimer m_visibleHackTimer1; - QTimer m_visibleHackTimer2; - //! Connections for the KWin visibility hack - QList connectionsHack; - - Latte::View *m_latteView{nullptr}; - - QPointer m_corona; - - Latte::WindowSystem::WindowId m_trackedWindowId; - KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif diff --git a/app/view/helpers/screenedgeghostwindow.cpp b/app/view/helpers/screenedgeghostwindow.cpp new file mode 100644 index 00000000..2416f415 --- /dev/null +++ b/app/view/helpers/screenedgeghostwindow.cpp @@ -0,0 +1,176 @@ +/* +* Copyright 2018 Michail Vourlakos +* +* This file is part of Latte-Dock +* +* Latte-Dock is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* Latte-Dock is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "screenedgeghostwindow.h" + +// local +#include "../view.h" + +// Qt +#include +#include +#include +#include + +// KDE +#include +#include +#include + +// X11 +#include + +namespace Latte { +namespace ViewPart { + +ScreenEdgeGhostWindow::ScreenEdgeGhostWindow(Latte::View *view) : + SubWindow(view, QString("Screen Ghost Window")) +{ + if (m_debugMode) { + m_showColor = QColor("purple"); + m_hideColor = QColor("blue"); + } else { + m_showColor = QColor(Qt::transparent); + m_hideColor = QColor(Qt::transparent); + + m_showColor.setAlpha(0); + m_hideColor.setAlpha(1); + } + + setColor(m_showColor); + + //! this timer is used in order to avoid fast enter/exit signals during first + //! appearing after edge activation + m_delayedMouseTimer.setSingleShot(true); + m_delayedMouseTimer.setInterval(50); + connect(&m_delayedMouseTimer, &QTimer::timeout, this, [this]() { + if (m_delayedContainsMouse) { + setContainsMouse(true); + } else { + setContainsMouse(false); + } + }); + + updateGeometry(); + hideWithMask(); +} + +ScreenEdgeGhostWindow::~ScreenEdgeGhostWindow() +{ +} + +void ScreenEdgeGhostWindow::updateGeometry() +{ + if (m_latteView->positioner()->slideOffset() != 0) { + return; + } + + QRect newGeometry; + + if (KWindowSystem::compositingActive()) { + m_thickness = 6; + } else { + m_thickness = 2; + } + + int length{30}; + int lengthDifference{0}; + + if (m_latteView->formFactor() == Plasma::Types::Horizontal) { + //! set minimum length to be 25% of screen width + length = qMax(m_latteView->screenGeometry().width()/4,qMin(m_latteView->absoluteGeometry().width(), m_latteView->screenGeometry().width() - 1)); + lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().width()); + } else { + //! set minimum length to be 25% of screen height + length = qMax(m_latteView->screenGeometry().height()/4,qMin(m_latteView->absoluteGeometry().height(), m_latteView->screenGeometry().height() - 1)); + lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().height()); + } + + if (m_latteView->location() == Plasma::Types::BottomEdge) { + int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference); + newGeometry.setX(xF); + newGeometry.setY(m_latteView->screenGeometry().bottom() - m_thickness); + } else if (m_latteView->location() == Plasma::Types::TopEdge) { + int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference); + newGeometry.setX(xF); + newGeometry.setY(m_latteView->screenGeometry().top()); + } else if (m_latteView->location() == Plasma::Types::LeftEdge) { + int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference); + newGeometry.setX(m_latteView->screenGeometry().left()); + newGeometry.setY(yF); + } else if (m_latteView->location() == Plasma::Types::RightEdge) { + int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference); + newGeometry.setX(m_latteView->screenGeometry().right() - m_thickness); + newGeometry.setY(yF); + } + + if (m_latteView->formFactor() == Plasma::Types::Horizontal) { + newGeometry.setWidth(length); + newGeometry.setHeight(m_thickness + 1); + } else { + newGeometry.setWidth(m_thickness + 1); + newGeometry.setHeight(length); + } + + m_calculatedGeometry = newGeometry; + + emit calculatedGeometryChanged(); +} + +bool ScreenEdgeGhostWindow::containsMouse() const +{ + return m_containsMouse; +} + +void ScreenEdgeGhostWindow::setContainsMouse(bool contains) +{ + if (m_containsMouse == contains) { + return; + } + + m_containsMouse = contains; + emit containsMouseChanged(contains); +} + +bool ScreenEdgeGhostWindow::event(QEvent *e) +{ + if (e->type() == QEvent::DragEnter || e->type() == QEvent::DragMove) { + if (!m_containsMouse) { + m_delayedContainsMouse = false; + m_delayedMouseTimer.stop(); + setContainsMouse(true); + emit dragEntered(); + } + } else if (e->type() == QEvent::Enter) { + m_delayedContainsMouse = true; + if (!m_delayedMouseTimer.isActive()) { + m_delayedMouseTimer.start(); + } + } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) { + m_delayedContainsMouse = false; + if (!m_delayedMouseTimer.isActive()) { + m_delayedMouseTimer.start(); + } + } + + return SubWindow::event(e); +} + +} +} diff --git a/app/view/screenedgeghostwindow.h b/app/view/helpers/screenedgeghostwindow.h similarity index 66% copy from app/view/screenedgeghostwindow.h copy to app/view/helpers/screenedgeghostwindow.h index 3499f475..4dded771 100644 --- a/app/view/screenedgeghostwindow.h +++ b/app/view/helpers/screenedgeghostwindow.h @@ -1,130 +1,94 @@ /* * Copyright 2018 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SCREENEDGEGHOSTWINDOW_H #define SCREENEDGEGHOSTWINDOW_H // local -#include "../lattecorona.h" -#include "../wm/windowinfowrap.h" +#include "subwindow.h" +#include "../../lattecorona.h" +#include "../../wm/windowinfowrap.h" // Qt #include #include #include namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Corona; class View; } namespace Latte { namespace ViewPart { //! What is the importance of this class? //! //! Plasma is activating the screen edges for the main panel window //! unfortunately this isn't possible for the Latte case. //! When a window is hidden at an edge it becomes NOT visible //! unfortunately that means that all the animations are //! stopped (Qt behaviour) and that creates confusion to the user after the window //! reappears because various animations are played (adding-removing tasks/launchers) //! that aren't relevant any more. //! //! In order to workaround the above behaviour Latte is using a //! fake window to communicate with KWin and the MAIN Latte::View window //! continues to use only mask technique to hide //! //! KDE BUGS: https://bugs.kde.org/show_bug.cgi?id=382219 //! https://bugs.kde.org/show_bug.cgi?id=392464 -class ScreenEdgeGhostWindow : public QQuickView +class ScreenEdgeGhostWindow : public SubWindow { Q_OBJECT public: ScreenEdgeGhostWindow(Latte::View *view); ~ScreenEdgeGhostWindow() override; bool containsMouse() const; - int location(); - int thickness() const; - - void hideWithMask(); - void showWithMask(); - - Latte::View *parentView(); - - KWayland::Client::PlasmaShellSurface *surface(); - signals: void containsMouseChanged(bool contains); void dragEntered(); - void forcedShown(); //[workaround] forced shown to avoid a KWin issue that hides windows when activities are stopped protected: bool event(QEvent *ev) override; - -private slots: - void startGeometryTimer(); - void updateGeometry(); - void fixGeometry(); + void updateGeometry() override; private: void setContainsMouse(bool contains); - void setupWaylandIntegration(); private: bool m_delayedContainsMouse{false}; bool m_containsMouse{false}; - bool m_inDelete{false}; - - int m_thickness{2}; - - QRect m_calculatedGeometry; QTimer m_delayedMouseTimer; - QTimer m_fixGeometryTimer; - - //! HACK: Timers in order to handle KWin faulty - //! behavior that hides Views when closing Activities - //! with no actual reason - QTimer m_visibleHackTimer1; - QTimer m_visibleHackTimer2; - //! Connections for the KWin visibility hack - QList connectionsHack; - - Latte::View *m_latteView{nullptr}; - - QPointer m_corona; - - Latte::WindowSystem::WindowId m_trackedWindowId; - KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif diff --git a/app/view/floatinggapwindow.cpp b/app/view/helpers/subwindow.cpp similarity index 54% rename from app/view/floatinggapwindow.cpp rename to app/view/helpers/subwindow.cpp index d38af30e..4fa3cd38 100644 --- a/app/view/floatinggapwindow.cpp +++ b/app/view/helpers/subwindow.cpp @@ -1,378 +1,273 @@ /* * Copyright 2020 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#include "floatinggapwindow.h" +#include "subwindow.h" // local -#include "view.h" +#include "../view.h" // Qt #include #include #include #include // KDE #include #include #include // X11 #include namespace Latte { namespace ViewPart { -FloatingGapWindow::FloatingGapWindow(Latte::View *view) : +SubWindow::SubWindow(Latte::View *view, QString debugType) : m_latteView(view) { m_corona = qobject_cast(view->corona()); m_debugMode = (qApp->arguments().contains("-d") && qApp->arguments().contains("--kwinedges")); + m_debugType = debugType; - if (m_debugMode) { - m_showColor = QColor("green"); - m_hideColor = QColor("red"); - } else { - m_showColor = QColor(Qt::transparent); - m_hideColor = QColor(Qt::transparent); - - m_showColor.setAlpha(0); - m_hideColor.setAlpha(1); - } + m_showColor = QColor(Qt::transparent); + m_hideColor = QColor(Qt::transparent); setColor(m_showColor); setDefaultAlphaBuffer(true); setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus); m_fixGeometryTimer.setSingleShot(true); m_fixGeometryTimer.setInterval(500); - connect(&m_fixGeometryTimer, &QTimer::timeout, this, &FloatingGapWindow::fixGeometry); - - //! this timer is used in order to identify if mouse is still present in sensitive floating - //! areas and in such case to prevent a real-floating view to hide itself - m_asyncMouseTimer.setSingleShot(true); - m_asyncMouseTimer.setInterval(200); - connect(&m_asyncMouseTimer, &QTimer::timeout, this, [this]() { - if (m_inAsyncContainsMouse && !m_containsMouse) { - emit asyncContainsMouseChanged(false); - hideWithMask(); - m_inAsyncContainsMouse = false; - } - }); + connect(&m_fixGeometryTimer, &QTimer::timeout, this, &SubWindow::fixGeometry); + + connect(this, &QQuickView::xChanged, this, &SubWindow::startGeometryTimer); + connect(this, &QQuickView::yChanged, this, &SubWindow::startGeometryTimer); + connect(this, &QQuickView::widthChanged, this, &SubWindow::startGeometryTimer); + connect(this, &QQuickView::heightChanged, this, &SubWindow::startGeometryTimer); - connect(this, &QQuickView::xChanged, this, &FloatingGapWindow::startGeometryTimer); - connect(this, &QQuickView::yChanged, this, &FloatingGapWindow::startGeometryTimer); - connect(this, &QQuickView::widthChanged, this, &FloatingGapWindow::startGeometryTimer); - connect(this, &QQuickView::heightChanged, this, &FloatingGapWindow::startGeometryTimer); + connect(this, &SubWindow::calculatedGeometryChanged, this, &SubWindow::fixGeometry); - connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &FloatingGapWindow::updateGeometry); - connect(m_latteView, &Latte::View::screenGeometryChanged, this, &FloatingGapWindow::updateGeometry); - connect(m_latteView, &Latte::View::locationChanged, this, &FloatingGapWindow::updateGeometry); + connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &SubWindow::updateGeometry); + connect(m_latteView, &Latte::View::screenGeometryChanged, this, &SubWindow::updateGeometry); + connect(m_latteView, &Latte::View::locationChanged, this, &SubWindow::updateGeometry); connect(m_latteView, &QQuickView::screenChanged, this, [this]() { setScreen(m_latteView->screen()); updateGeometry(); }); if (!KWindowSystem::isPlatformWayland()) { //! IMPORTANT!!! ::: This fixes a bug when closing an Activity all views from all Activities are //! disappearing! With this code parts they reappear!!! m_visibleHackTimer1.setInterval(400); m_visibleHackTimer2.setInterval(2500); m_visibleHackTimer1.setSingleShot(true); m_visibleHackTimer2.setSingleShot(true); connectionsHack << connect(this, &QWindow::visibleChanged, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { m_visibleHackTimer1.start(); m_visibleHackTimer2.start(); } else if (!m_inDelete) { //! For some reason when the window is hidden in the edge under X11 afterwards //! is losing its window flags m_corona->wm()->setViewExtraFlags(this); } }); connectionsHack << connect(&m_visibleHackTimer1, &QTimer::timeout, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { show(); emit forcedShown(); - //qDebug() << "Floating Gap:: Enforce reshow from timer 1..."; + //qDebug() << m_debugType + ":: Enforce reshow from timer 1..."; } else { - //qDebug() << "Floating Gap:: No needed reshow from timer 1..."; + //qDebug() << m_debugType + ":: No needed reshow from timer 1..."; } }); connectionsHack << connect(&m_visibleHackTimer2, &QTimer::timeout, this, [&]() { if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { show(); emit forcedShown(); - //qDebug() << "Floating Gap:: Enforce reshow from timer 2..."; + //qDebug() << m_debugType + ":: Enforce reshow from timer 2..."; } else { - //qDebug() << "Floating Gap:: No needed reshow from timer 2..."; + //qDebug() << m_debugType + ":: No needed reshow from timer 2..."; } }); - connectionsHack << connect(this, &FloatingGapWindow::forcedShown, this, [&]() { + connectionsHack << connect(this, &SubWindow::forcedShown, this, [&]() { m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId); m_trackedWindowId = winId(); m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); }); } setupWaylandIntegration(); if (KWindowSystem::isPlatformX11()) { m_trackedWindowId = winId(); m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); } else { connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, [&]() { if (m_trackedWindowId.isNull()) { m_trackedWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); } }); } setScreen(m_latteView->screen()); show(); - updateGeometry(); hideWithMask(); } -FloatingGapWindow::~FloatingGapWindow() +SubWindow::~SubWindow() { m_inDelete = true; m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_trackedWindowId); m_latteView = nullptr; // clear mode m_visibleHackTimer1.stop(); m_visibleHackTimer2.stop(); for (auto &c : connectionsHack) { disconnect(c); } if (m_shellSurface) { delete m_shellSurface; } } -int FloatingGapWindow::location() +int SubWindow::location() { return (int)m_latteView->location(); } -int FloatingGapWindow::thickness() const +int SubWindow::thickness() const { return m_thickness; } -Latte::View *FloatingGapWindow::parentView() +Latte::View *SubWindow::parentView() { return m_latteView; } -KWayland::Client::PlasmaShellSurface *FloatingGapWindow::surface() +KWayland::Client::PlasmaShellSurface *SubWindow::surface() { return m_shellSurface; } -void FloatingGapWindow::updateGeometry() -{ - if (m_latteView->positioner()->slideOffset() != 0) { - return; - } - - QRect newGeometry; - - m_thickness = m_latteView->screenEdgeMargin(); - - int length = m_latteView->formFactor() == Plasma::Types::Horizontal ? m_latteView->absoluteGeometry().width() : m_latteView->absoluteGeometry().height(); - - if (m_latteView->location() == Plasma::Types::BottomEdge) { - int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left()); - newGeometry.setX(xF); - newGeometry.setY(m_latteView->screenGeometry().bottom() - m_thickness); - } else if (m_latteView->location() == Plasma::Types::TopEdge) { - int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left()); - newGeometry.setX(xF); - newGeometry.setY(m_latteView->screenGeometry().top()); - } else if (m_latteView->location() == Plasma::Types::LeftEdge) { - int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top()); - newGeometry.setX(m_latteView->screenGeometry().left()); - newGeometry.setY(yF); - } else if (m_latteView->location() == Plasma::Types::RightEdge) { - int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top()); - newGeometry.setX(m_latteView->screenGeometry().right() - m_thickness); - newGeometry.setY(yF); - } - - if (m_latteView->formFactor() == Plasma::Types::Horizontal) { - newGeometry.setWidth(length); - newGeometry.setHeight(m_thickness + 1); - } else { - newGeometry.setWidth(m_thickness + 1); - newGeometry.setHeight(length); - } - - m_calculatedGeometry = newGeometry; - - fixGeometry(); -} - -void FloatingGapWindow::fixGeometry() +void SubWindow::fixGeometry() { if (!m_calculatedGeometry.isEmpty() && (m_calculatedGeometry.x() != x() || m_calculatedGeometry.y() != y() || m_calculatedGeometry.width() != width() || m_calculatedGeometry.height() != height())) { setMinimumSize(m_calculatedGeometry.size()); setMaximumSize(m_calculatedGeometry.size()); resize(m_calculatedGeometry.size()); setPosition(m_calculatedGeometry.x(), m_calculatedGeometry.y()); if (m_shellSurface) { m_shellSurface->setPosition(m_calculatedGeometry.topLeft()); } } } -void FloatingGapWindow::startGeometryTimer() +void SubWindow::startGeometryTimer() { m_fixGeometryTimer.start(); } -void FloatingGapWindow::setupWaylandIntegration() +void SubWindow::setupWaylandIntegration() { if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) { // already setup return; } if (m_corona) { using namespace KWayland::Client; PlasmaShell *interface = m_corona->waylandCoronaInterface(); if (!interface) { return; } Surface *s = Surface::fromWindow(this); if (!s) { return; } qDebug() << "wayland screen edge ghost window surface was created..."; m_shellSurface = interface->createSurface(s, this); m_corona->wm()->setViewExtraFlags(m_shellSurface); m_shellSurface->setPanelTakesFocus(false); } } -bool FloatingGapWindow::containsMouse() const +bool SubWindow::event(QEvent *e) { - return m_containsMouse; -} - -void FloatingGapWindow::setContainsMouse(bool contains) -{ - if (m_containsMouse == contains) { - return; - } - - m_containsMouse = contains; -} - -bool FloatingGapWindow::event(QEvent *e) -{ - if (e->type() == QEvent::DragEnter || e->type() == QEvent::DragMove) { - setContainsMouse(true); - emit dragEntered(); - } else if (e->type() == QEvent::Enter) { - setContainsMouse(true); - triggerAsyncContainsMouseSignals(); - } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) { - setContainsMouse(false); - if (m_inAsyncContainsMouse) { - m_asyncMouseTimer.stop(); - m_inAsyncContainsMouse = false; - emit asyncContainsMouseChanged(true); - } - } else if (e->type() == QEvent::Show) { + if (e->type() == QEvent::Show) { m_corona->wm()->setViewExtraFlags(this); } return QQuickView::event(e); } -void FloatingGapWindow::callAsyncContainsMouse() -{ - m_inAsyncContainsMouse = true; - m_asyncMouseTimer.start(); - showWithMask(); -} - -void FloatingGapWindow::triggerAsyncContainsMouseSignals() -{ - if (!m_inAsyncContainsMouse) { - return; - } - - //! this function is called QEvent::Enter - m_asyncMouseTimer.stop(); - hideWithMask(); -} -void FloatingGapWindow::hideWithMask() +void SubWindow::hideWithMask() { if (m_debugMode) { - qDebug() << " Floating Gap Window :: MASK HIDE..."; + qDebug() << m_debugType + " :: MASK HIDE..."; } //! old values: 0,0,1,1 were blocking the top-left corner of the window QRect maskGeometry{-2, 0, 1, 1}; setMask(maskGeometry); //! repaint in order to update mask immediately setColor(m_hideColor); } -void FloatingGapWindow::showWithMask() +void SubWindow::showWithMask() { if (m_debugMode) { - qDebug() << " Floating Gap Window :: MAKS SHOW..."; + qDebug() << m_debugType + " :: MASK SHOW..."; } setMask(QRegion()); //! repaint in order to update mask immediately setColor(m_showColor); } } } diff --git a/app/view/screenedgeghostwindow.h b/app/view/helpers/subwindow.h similarity index 60% rename from app/view/screenedgeghostwindow.h rename to app/view/helpers/subwindow.h index 3499f475..81d981e8 100644 --- a/app/view/screenedgeghostwindow.h +++ b/app/view/helpers/subwindow.h @@ -1,130 +1,121 @@ /* -* Copyright 2018 Michail Vourlakos +* Copyright 2020 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef SCREENEDGEGHOSTWINDOW_H -#define SCREENEDGEGHOSTWINDOW_H +#ifndef VIEWSUBWINDOW_H +#define VIEWSUBWINDOW_H // local -#include "../lattecorona.h" -#include "../wm/windowinfowrap.h" +#include "../../lattecorona.h" +#include "../../wm/windowinfowrap.h" // Qt #include #include #include namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Corona; class View; } namespace Latte { namespace ViewPart { //! What is the importance of this class? //! -//! Plasma is activating the screen edges for the main panel window -//! unfortunately this isn't possible for the Latte case. -//! When a window is hidden at an edge it becomes NOT visible -//! unfortunately that means that all the animations are -//! stopped (Qt behaviour) and that creates confusion to the user after the window -//! reappears because various animations are played (adding-removing tasks/launchers) -//! that aren't relevant any more. -//! -//! In order to workaround the above behaviour Latte is using a -//! fake window to communicate with KWin and the MAIN Latte::View window -//! continues to use only mask technique to hide -//! -//! KDE BUGS: https://bugs.kde.org/show_bug.cgi?id=382219 -//! https://bugs.kde.org/show_bug.cgi?id=392464 +//! This window is responsible to provide a common window base for ViewPart::Helpers -class ScreenEdgeGhostWindow : public QQuickView +class SubWindow : public QQuickView { Q_OBJECT public: - ScreenEdgeGhostWindow(Latte::View *view); - ~ScreenEdgeGhostWindow() override; - - bool containsMouse() const; + SubWindow(Latte::View *view, QString debugType); + ~SubWindow() override; int location(); int thickness() const; void hideWithMask(); void showWithMask(); Latte::View *parentView(); KWayland::Client::PlasmaShellSurface *surface(); signals: - void containsMouseChanged(bool contains); - void dragEntered(); void forcedShown(); //[workaround] forced shown to avoid a KWin issue that hides windows when activities are stopped + void calculatedGeometryChanged(); protected: bool event(QEvent *ev) override; + //! it is used to update m_calculatedGeometry correctly + virtual void updateGeometry() = 0; + private slots: void startGeometryTimer(); - void updateGeometry(); void fixGeometry(); private: - void setContainsMouse(bool contains); void setupWaylandIntegration(); -private: - bool m_delayedContainsMouse{false}; - bool m_containsMouse{false}; +protected: + bool m_debugMode{false}; bool m_inDelete{false}; int m_thickness{2}; + QString m_debugType; + QRect m_calculatedGeometry; - QTimer m_delayedMouseTimer; + //! [workaround] colors in order to help masking to apply immediately + //! for some reason when the window in with no content the mask is not + //! update immediately + QColor m_hideColor; + QColor m_showColor; + QTimer m_fixGeometryTimer; //! HACK: Timers in order to handle KWin faulty //! behavior that hides Views when closing Activities //! with no actual reason QTimer m_visibleHackTimer1; QTimer m_visibleHackTimer2; //! Connections for the KWin visibility hack QList connectionsHack; Latte::View *m_latteView{nullptr}; QPointer m_corona; Latte::WindowSystem::WindowId m_trackedWindowId; KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } } #endif diff --git a/app/view/screenedgeghostwindow.cpp b/app/view/screenedgeghostwindow.cpp deleted file mode 100644 index 6c924256..00000000 --- a/app/view/screenedgeghostwindow.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/* -* Copyright 2018 Michail Vourlakos -* -* This file is part of Latte-Dock -* -* Latte-Dock is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License as -* published by the Free Software Foundation; either version 2 of -* the License, or (at your option) any later version. -* -* Latte-Dock is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#include "screenedgeghostwindow.h" - -// local -#include "view.h" - -// Qt -#include -#include -#include -#include - -// KDE -#include -#include -#include - -// X11 -#include - -namespace Latte { -namespace ViewPart { - -ScreenEdgeGhostWindow::ScreenEdgeGhostWindow(Latte::View *view) : - m_latteView(view) -{ - m_corona = qobject_cast(view->corona()); - - bool debugEdge = (qApp->arguments().contains("-d") && qApp->arguments().contains("--kwinedges")); - - setColor(debugEdge ? QColor("purple") : QColor(Qt::transparent)); - setDefaultAlphaBuffer(true); - - setFlags(Qt::FramelessWindowHint - | Qt::WindowStaysOnTopHint - | Qt::NoDropShadowWindowHint - | Qt::WindowDoesNotAcceptFocus); - - m_fixGeometryTimer.setSingleShot(true); - m_fixGeometryTimer.setInterval(500); - connect(&m_fixGeometryTimer, &QTimer::timeout, this, &ScreenEdgeGhostWindow::fixGeometry); - - //! this timer is used in order to avoid fast enter/exit signals during first - //! appearing after edge activation - m_delayedMouseTimer.setSingleShot(true); - m_delayedMouseTimer.setInterval(50); - connect(&m_delayedMouseTimer, &QTimer::timeout, this, [this]() { - if (m_delayedContainsMouse) { - setContainsMouse(true); - } else { - setContainsMouse(false); - } - }); - - connect(this, &QQuickView::xChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); - connect(this, &QQuickView::yChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); - connect(this, &QQuickView::widthChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); - connect(this, &QQuickView::heightChanged, this, &ScreenEdgeGhostWindow::startGeometryTimer); - - connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry); - connect(m_latteView, &Latte::View::screenGeometryChanged, this, &ScreenEdgeGhostWindow::updateGeometry); - connect(m_latteView, &Latte::View::locationChanged, this, &ScreenEdgeGhostWindow::updateGeometry); - connect(m_latteView, &QQuickView::screenChanged, this, [this]() { - setScreen(m_latteView->screen()); - updateGeometry(); - }); - - if (!KWindowSystem::isPlatformWayland()) { - //! IMPORTANT!!! ::: This fixes a bug when closing an Activity all views from all Activities are - //! disappearing! With this code parts they reappear!!! - m_visibleHackTimer1.setInterval(400); - m_visibleHackTimer2.setInterval(2500); - m_visibleHackTimer1.setSingleShot(true); - m_visibleHackTimer2.setSingleShot(true); - - connectionsHack << connect(this, &QWindow::visibleChanged, this, [&]() { - if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { - m_visibleHackTimer1.start(); - m_visibleHackTimer2.start(); - } else if (!m_inDelete) { - //! For some reason when the window is hidden in the edge under X11 afterwards - //! is losing its window flags - m_corona->wm()->setViewExtraFlags(this); - } - }); - - connectionsHack << connect(&m_visibleHackTimer1, &QTimer::timeout, this, [&]() { - if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { - show(); - emit forcedShown(); - //qDebug() << "Ghost Edge:: Enforce reshow from timer 1..."; - } else { - //qDebug() << "Ghost Edge:: No needed reshow from timer 1..."; - } - }); - - connectionsHack << connect(&m_visibleHackTimer2, &QTimer::timeout, this, [&]() { - if (!m_inDelete && m_latteView && m_latteView->layout() && !isVisible()) { - show(); - emit forcedShown(); - //qDebug() << "Ghost Edge:: Enforce reshow from timer 2..."; - } else { - //qDebug() << "Ghost Edge:: No needed reshow from timer 2..."; - } - }); - - connectionsHack << connect(this, &ScreenEdgeGhostWindow::forcedShown, this, [&]() { - m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId); - m_trackedWindowId = winId(); - m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); - }); - } - - setupWaylandIntegration(); - - if (KWindowSystem::isPlatformX11()) { - m_trackedWindowId = winId(); - m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); - } else { - connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, [&]() { - if (m_trackedWindowId.isNull()) { - m_trackedWindowId = m_corona->wm()->winIdFor("latte-dock", geometry()); - m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); - } - }); - } - - setScreen(m_latteView->screen()); - show(); - updateGeometry(); - hideWithMask(); -} - -ScreenEdgeGhostWindow::~ScreenEdgeGhostWindow() -{ - m_inDelete = true; - - m_corona->wm()->unregisterIgnoredWindow(KWindowSystem::isPlatformX11() ? winId() : m_trackedWindowId); - - m_latteView = nullptr; - - // clear mode - m_visibleHackTimer1.stop(); - m_visibleHackTimer2.stop(); - for (auto &c : connectionsHack) { - disconnect(c); - } - - if (m_shellSurface) { - delete m_shellSurface; - } -} - -int ScreenEdgeGhostWindow::location() -{ - return (int)m_latteView->location(); -} - -int ScreenEdgeGhostWindow::thickness() const -{ - return m_thickness; -} - -Latte::View *ScreenEdgeGhostWindow::parentView() -{ - return m_latteView; -} - -KWayland::Client::PlasmaShellSurface *ScreenEdgeGhostWindow::surface() -{ - return m_shellSurface; -} - -void ScreenEdgeGhostWindow::updateGeometry() -{ - if (m_latteView->positioner()->slideOffset() != 0) { - return; - } - - QRect newGeometry; - - if (KWindowSystem::compositingActive()) { - m_thickness = 6; - } else { - m_thickness = 2; - } - - int length{30}; - int lengthDifference{0}; - - if (m_latteView->formFactor() == Plasma::Types::Horizontal) { - //! set minimum length to be 25% of screen width - length = qMax(m_latteView->screenGeometry().width()/4,qMin(m_latteView->absoluteGeometry().width(), m_latteView->screenGeometry().width() - 1)); - lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().width()); - } else { - //! set minimum length to be 25% of screen height - length = qMax(m_latteView->screenGeometry().height()/4,qMin(m_latteView->absoluteGeometry().height(), m_latteView->screenGeometry().height() - 1)); - lengthDifference = qMax(0,length - m_latteView->absoluteGeometry().height()); - } - - if (m_latteView->location() == Plasma::Types::BottomEdge) { - int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference); - newGeometry.setX(xF); - newGeometry.setY(m_latteView->screenGeometry().bottom() - m_thickness); - } else if (m_latteView->location() == Plasma::Types::TopEdge) { - int xF = qMax(m_latteView->screenGeometry().left(), m_latteView->absoluteGeometry().left() - lengthDifference); - newGeometry.setX(xF); - newGeometry.setY(m_latteView->screenGeometry().top()); - } else if (m_latteView->location() == Plasma::Types::LeftEdge) { - int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference); - newGeometry.setX(m_latteView->screenGeometry().left()); - newGeometry.setY(yF); - } else if (m_latteView->location() == Plasma::Types::RightEdge) { - int yF = qMax(m_latteView->screenGeometry().top(), m_latteView->absoluteGeometry().top() - lengthDifference); - newGeometry.setX(m_latteView->screenGeometry().right() - m_thickness); - newGeometry.setY(yF); - } - - if (m_latteView->formFactor() == Plasma::Types::Horizontal) { - newGeometry.setWidth(length); - newGeometry.setHeight(m_thickness + 1); - } else { - newGeometry.setWidth(m_thickness + 1); - newGeometry.setHeight(length); - } - - m_calculatedGeometry = newGeometry; - - fixGeometry(); -} - -void ScreenEdgeGhostWindow::fixGeometry() -{ - if (!m_calculatedGeometry.isEmpty() - && (m_calculatedGeometry.x() != x() || m_calculatedGeometry.y() != y() - || m_calculatedGeometry.width() != width() || m_calculatedGeometry.height() != height())) { - setMinimumSize(m_calculatedGeometry.size()); - setMaximumSize(m_calculatedGeometry.size()); - resize(m_calculatedGeometry.size()); - setPosition(m_calculatedGeometry.x(), m_calculatedGeometry.y()); - - if (m_shellSurface) { - m_shellSurface->setPosition(m_calculatedGeometry.topLeft()); - } - } -} - -void ScreenEdgeGhostWindow::startGeometryTimer() -{ - m_fixGeometryTimer.start(); -} - -void ScreenEdgeGhostWindow::setupWaylandIntegration() -{ - if (m_shellSurface || !KWindowSystem::isPlatformWayland() || !m_latteView || !m_latteView->containment()) { - // already setup - return; - } - - if (m_corona) { - using namespace KWayland::Client; - - PlasmaShell *interface = m_corona->waylandCoronaInterface(); - - if (!interface) { - return; - } - - Surface *s = Surface::fromWindow(this); - - if (!s) { - return; - } - - qDebug() << "wayland screen edge ghost window surface was created..."; - m_shellSurface = interface->createSurface(s, this); - m_corona->wm()->setViewExtraFlags(m_shellSurface); - - m_shellSurface->setPanelTakesFocus(false); - } -} - -bool ScreenEdgeGhostWindow::containsMouse() const -{ - return m_containsMouse; -} - -void ScreenEdgeGhostWindow::setContainsMouse(bool contains) -{ - if (m_containsMouse == contains) { - return; - } - - m_containsMouse = contains; - emit containsMouseChanged(contains); -} - -bool ScreenEdgeGhostWindow::event(QEvent *e) -{ - if (e->type() == QEvent::DragEnter || e->type() == QEvent::DragMove) { - if (!m_containsMouse) { - m_delayedContainsMouse = false; - m_delayedMouseTimer.stop(); - setContainsMouse(true); - emit dragEntered(); - } - } else if (e->type() == QEvent::Enter) { - m_delayedContainsMouse = true; - if (!m_delayedMouseTimer.isActive()) { - m_delayedMouseTimer.start(); - } - } else if (e->type() == QEvent::Leave || e->type() == QEvent::DragLeave) { - m_delayedContainsMouse = false; - if (!m_delayedMouseTimer.isActive()) { - m_delayedMouseTimer.start(); - } - } else if (e->type() == QEvent::Show) { - m_corona->wm()->setViewExtraFlags(this); - } - - return QQuickView::event(e); -} - -void ScreenEdgeGhostWindow::hideWithMask() -{ - //! old values: 0,0,1,1 were blocking the top-left corner of the window - QRect maskGeometry{-2, 0, 1, 1}; - - setMask(maskGeometry); -} - -void ScreenEdgeGhostWindow::showWithMask() -{ - setMask(QRect()); -} - -} -} diff --git a/app/view/visibilitymanager.cpp b/app/view/visibilitymanager.cpp index bd0756ea..66ced627 100644 --- a/app/view/visibilitymanager.cpp +++ b/app/view/visibilitymanager.cpp @@ -1,908 +1,908 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "visibilitymanager.h" // local #include "positioner.h" -#include "floatinggapwindow.h" -#include "screenedgeghostwindow.h" #include "view.h" +#include "helpers/floatinggapwindow.h" +#include "helpers/screenedgeghostwindow.h" #include "windowstracker/currentscreentracker.h" #include "../lattecorona.h" #include "../screenpool.h" #include "../layouts/manager.h" #include "../wm/abstractwindowinterface.h" #include "../../liblatte2/extras.h" // Qt #include // KDE #include #include #include //! Hide Timer can create cases that when it is low it does not allow the //! view to be show. For example !compositing+kwin_edges+hide inteval<50ms const int HIDEMINIMUMINTERVAL = 50; namespace Latte { namespace ViewPart { //! BEGIN: VisiblityManager implementation VisibilityManager::VisibilityManager(PlasmaQuick::ContainmentView *view) : QObject(view) { qDebug() << "VisibilityManager creating..."; m_latteView = qobject_cast(view); m_corona = qobject_cast(view->corona()); m_wm = m_corona->wm(); connect(this, &VisibilityManager::slideOutFinished, this, &VisibilityManager::updateHiddenState); connect(this, &VisibilityManager::slideInFinished, this, &VisibilityManager::updateHiddenState); connect(this, &VisibilityManager::enableKWinEdgesChanged, this, &VisibilityManager::updateKWinEdgesSupport); connect(this, &VisibilityManager::modeChanged, this, &VisibilityManager::updateKWinEdgesSupport); if (m_latteView) { connect(m_latteView, &Latte::View::eventTriggered, this, &VisibilityManager::viewEventManager); connect(m_latteView, &Latte::View::byPassWMChanged, this, &VisibilityManager::updateKWinEdgesSupport); connect(m_latteView, &Latte::View::inEditModeChanged, this, &VisibilityManager::initViewFlags); connect(m_latteView, &Latte::View::absoluteGeometryChanged, this, [&]() { if (m_mode == Types::AlwaysVisible && m_latteView->screen()) { updateStrutsBasedOnLayoutsAndActivities(); } }); connect(m_latteView, &Latte::View::screenEdgeMarginEnabledChanged, this, [&]() { if (!m_latteView->screenEdgeMarginEnabled()) { deleteFloatingGapWindow(); } }); connect(this, &VisibilityManager::modeChanged, this, [&]() { emit m_latteView->availableScreenRectChangedFrom(m_latteView); }); } m_timerStartUp.setInterval(5000); m_timerStartUp.setSingleShot(true); m_timerShow.setSingleShot(true); m_timerHide.setSingleShot(true); connect(&m_timerShow, &QTimer::timeout, this, [&]() { if (m_isHidden || m_isBelowLayer) { // qDebug() << "must be shown"; emit mustBeShown(); } }); connect(&m_timerHide, &QTimer::timeout, this, [&]() { if (!m_blockHiding && !m_isHidden && !m_isBelowLayer && !m_dragEnter) { if (m_latteView->isFloatingWindow()) { //! first check if mouse is inside the floating gap checkMouseInFloatingArea(); } else { //! immediate call emit mustBeHide(); } } }); restoreConfig(); } VisibilityManager::~VisibilityManager() { qDebug() << "VisibilityManager deleting..."; m_wm->removeViewStruts(*m_latteView); if (m_edgeGhostWindow) { m_edgeGhostWindow->deleteLater(); } if (m_floatingGapWindow) { m_floatingGapWindow->deleteLater(); } } Types::Visibility VisibilityManager::mode() const { return m_mode; } void VisibilityManager::initViewFlags() { if ((m_mode == Types::WindowsCanCover || m_mode == Types::WindowsAlwaysCover) && (!m_latteView->inEditMode())) { setViewOnBackLayer(); } else { setViewOnFrontLayer(); } } void VisibilityManager::setViewOnBackLayer() { m_wm->setViewExtraFlags(m_latteView, false, Types::WindowsAlwaysCover); setIsBelowLayer(true); } void VisibilityManager::setViewOnFrontLayer() { m_wm->setViewExtraFlags(m_latteView, true); setIsBelowLayer(false); } void VisibilityManager::setMode(Latte::Types::Visibility mode) { if (m_mode == mode) return; Q_ASSERT_X(mode != Types::None, staticMetaObject.className(), "set visibility to Types::None"); // clear mode for (auto &c : m_connections) { disconnect(c); } int base{0}; m_publishedStruts = QRect(); if (m_mode == Types::AlwaysVisible) { //! remove struts for old always visible mode m_wm->removeViewStruts(*m_latteView); } m_timerShow.stop(); m_timerHide.stop(); m_mode = mode; initViewFlags(); if (mode != Types::AlwaysVisible && mode != Types::WindowsGoBelow) { m_connections[0] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentDesktopChanged, this, [&] { if (m_raiseOnDesktopChange) { raiseViewTemporarily(); } }); m_connections[1] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentActivityChanged, this, [&]() { if (m_raiseOnActivityChange) { raiseViewTemporarily(); } else { updateHiddenState(); } }); base = 2; } switch (m_mode) { case Types::AlwaysVisible: { if (m_latteView->containment() && m_latteView->screen()) { updateStrutsBasedOnLayoutsAndActivities(); } m_connections[base] = connect(m_latteView, &Latte::View::normalThicknessChanged, this, [&]() { updateStrutsBasedOnLayoutsAndActivities(); }); m_connections[base+1] = connect(m_corona->layoutsManager(), &Layouts::Manager::currentLayoutNameChanged, this, [&]() { if (m_corona && m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { updateStrutsBasedOnLayoutsAndActivities(true); } }); m_connections[base+2] = connect(m_latteView, &Latte::View::activitiesChanged, this, [&]() { if (m_corona && m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { updateStrutsBasedOnLayoutsAndActivities(true); } }); raiseView(true); break; } case Types::AutoHide: { m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged, this, [&]() { raiseView(m_containsMouse); }); raiseView(m_containsMouse); break; } case Types::DodgeActive: { m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged , this, &VisibilityManager::dodgeActive); m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::activeWindowTouchingChanged , this, &VisibilityManager::dodgeActive); dodgeActive(); break; } case Types::DodgeMaximized: { m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged , this, &VisibilityManager::dodgeMaximized); m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::activeWindowMaximizedChanged , this, &VisibilityManager::dodgeMaximized); dodgeMaximized(); break; } case Types::DodgeAllWindows: { m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged , this, &VisibilityManager::dodgeAllWindows); m_connections[base+1] = connect(m_latteView->windowsTracker()->currentScreen(), &TrackerPart::CurrentScreenTracker::existsWindowTouchingChanged , this, &VisibilityManager::dodgeAllWindows); dodgeAllWindows(); break; } case Types::WindowsGoBelow: break; case Types::WindowsCanCover: m_connections[base] = connect(this, &VisibilityManager::containsMouseChanged, this, [&]() { if (m_containsMouse) { emit mustBeShown(); } else { raiseView(false); } }); raiseView(m_containsMouse); break; case Types::WindowsAlwaysCover: break; default: break; } m_latteView->containment()->config().writeEntry("visibility", static_cast(m_mode)); updateKWinEdgesSupport(); emit modeChanged(); } void VisibilityManager::updateStrutsBasedOnLayoutsAndActivities(bool forceUpdate) { bool multipleLayoutsAndCurrent = (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->layout() && !m_latteView->positioner()->inLocationAnimation() && m_latteView->layout()->isCurrent()); if (m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout || multipleLayoutsAndCurrent) { QRect computedStruts = acceptableStruts(); if (m_publishedStruts != computedStruts || forceUpdate) { //! Force update is needed when very important events happen in DE and there is a chance //! that previously even though struts where sent the DE did not accept them. //! Such a case is when STOPPING an Activity and windows faulty become invisible even //! though they should not. In such case setting struts when the windows are hidden //! the struts do not take any effect m_publishedStruts = computedStruts; m_wm->setViewStruts(*m_latteView, m_publishedStruts, m_latteView->location()); } } else { m_publishedStruts = QRect(); m_wm->removeViewStruts(*m_latteView); } } QRect VisibilityManager::acceptableStruts() { QRect calcs; int screenEdgeMargin = (m_latteView->behaveAsPlasmaPanel() && m_latteView->screenEdgeMarginEnabled()) ? m_latteView->screenEdgeMargin() : 0; int shownThickness = m_latteView->normalThickness() + screenEdgeMargin; switch (m_latteView->location()) { case Plasma::Types::TopEdge: { calcs = QRect(m_latteView->x(), m_latteView->y(), m_latteView->width(), shownThickness); break; } case Plasma::Types::BottomEdge: { int y = m_latteView->y() + m_latteView->height() - shownThickness; calcs = QRect(m_latteView->x(), y, m_latteView->width(), shownThickness); break; } case Plasma::Types::LeftEdge: { calcs = QRect(m_latteView->x(), m_latteView->y(), shownThickness, m_latteView->height()); break; } case Plasma::Types::RightEdge: { int x = m_latteView->x() + m_latteView->width() - shownThickness; calcs = QRect(x, m_latteView->y(), shownThickness, m_latteView->height()); break; } } return calcs; } bool VisibilityManager::raiseOnDesktop() const { return m_raiseOnDesktopChange; } void VisibilityManager::setRaiseOnDesktop(bool enable) { if (enable == m_raiseOnDesktopChange) return; m_raiseOnDesktopChange = enable; emit raiseOnDesktopChanged(); } bool VisibilityManager::raiseOnActivity() const { return m_raiseOnActivityChange; } void VisibilityManager::setRaiseOnActivity(bool enable) { if (enable == m_raiseOnActivityChange) return; m_raiseOnActivityChange = enable; emit raiseOnActivityChanged(); } bool VisibilityManager::isBelowLayer() const { return m_isBelowLayer; } void VisibilityManager::setIsBelowLayer(bool below) { if (m_isBelowLayer == below) { return; } m_isBelowLayer = below; updateGhostWindowState(); emit isBelowLayerChanged(); } bool VisibilityManager::isHidden() const { return m_isHidden; } void VisibilityManager::setIsHidden(bool isHidden) { if (m_isHidden == isHidden) return; if (m_blockHiding && isHidden) { qWarning() << "isHidden property is blocked, ignoring update"; return; } m_isHidden = isHidden; updateGhostWindowState(); emit isHiddenChanged(); } bool VisibilityManager::blockHiding() const { return m_blockHiding; } void VisibilityManager::setBlockHiding(bool blockHiding) { if (m_blockHiding == blockHiding) { return; } m_blockHiding = blockHiding; // qDebug() << "blockHiding:" << blockHiding; if (m_blockHiding) { m_timerHide.stop(); if (m_isHidden) { emit mustBeShown(); } } else { updateHiddenState(); } emit blockHidingChanged(); } int VisibilityManager::timerShow() const { return m_timerShow.interval(); } void VisibilityManager::setTimerShow(int msec) { if (m_timerShow.interval() == msec) { return; } m_timerShow.setInterval(msec); emit timerShowChanged(); } int VisibilityManager::timerHide() const { return m_timerHide.interval(); } void VisibilityManager::setTimerHide(int msec) { int interval = qMax(HIDEMINIMUMINTERVAL, msec); if (m_timerHide.interval() == interval) { return; } m_timerHide.setInterval(interval); emit timerHideChanged(); } bool VisibilityManager::supportsKWinEdges() const { return (m_edgeGhostWindow != nullptr); } void VisibilityManager::updateGhostWindowState() { if (supportsKWinEdges()) { bool inCurrentLayout = (m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout || (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->layout() && !m_latteView->positioner()->inLocationAnimation() && m_latteView->layout()->isCurrent())); if (inCurrentLayout) { if (m_mode == Latte::Types::WindowsCanCover) { m_wm->setActiveEdge(m_edgeGhostWindow, m_isBelowLayer && !m_containsMouse); } else { bool activated = (m_isHidden && !windowContainsMouse()); m_wm->setActiveEdge(m_edgeGhostWindow, activated); } } else { m_wm->setActiveEdge(m_edgeGhostWindow, false); } } } void VisibilityManager::hide() { if (KWindowSystem::isPlatformX11()) { m_latteView->hide(); } } void VisibilityManager::show() { if (KWindowSystem::isPlatformX11()) { m_latteView->show(); } } void VisibilityManager::raiseView(bool raise) { if (m_blockHiding) return; if (raise) { m_timerHide.stop(); if (!m_timerShow.isActive()) { m_timerShow.start(); } } else if (!m_dragEnter) { m_timerShow.stop(); if (m_hideNow) { m_hideNow = false; emit mustBeHide(); } else if (!m_timerHide.isActive()) { m_timerHide.start(); } } } void VisibilityManager::raiseViewTemporarily() { if (m_raiseTemporarily) return; m_raiseTemporarily = true; m_timerHide.stop(); m_timerShow.stop(); if (m_isHidden) emit mustBeShown(); QTimer::singleShot(qBound(1800, 2 * m_timerHide.interval(), 3000), this, [&]() { m_raiseTemporarily = false; m_hideNow = true; updateHiddenState(); }); } void VisibilityManager::updateHiddenState() { if (m_dragEnter) return; switch (m_mode) { case Types::AutoHide: case Types::WindowsCanCover: raiseView(m_containsMouse); break; case Types::DodgeActive: dodgeActive(); break; case Types::DodgeMaximized: dodgeMaximized(); break; case Types::DodgeAllWindows: dodgeAllWindows(); break; default: break; } } void VisibilityManager::applyActivitiesToHiddenWindows(const QStringList &activities) { if (m_edgeGhostWindow) { m_wm->setWindowOnActivities(*m_edgeGhostWindow, activities); } if (m_floatingGapWindow) { m_wm->setWindowOnActivities(*m_floatingGapWindow, activities); } } void VisibilityManager::dodgeActive() { if (m_raiseTemporarily) return; //!don't send false raiseView signal when containing mouse if (m_containsMouse) { raiseView(true); return; } raiseView(!m_latteView->windowsTracker()->currentScreen()->activeWindowTouching()); } void VisibilityManager::dodgeMaximized() { if (m_raiseTemporarily) return; //!don't send false raiseView signal when containing mouse if (m_containsMouse) { raiseView(true); return; } raiseView(!m_latteView->windowsTracker()->currentScreen()->activeWindowMaximized()); } void VisibilityManager::dodgeAllWindows() { if (m_raiseTemporarily) return; if (m_containsMouse) { raiseView(true); return; } bool windowIntersects{m_latteView->windowsTracker()->currentScreen()->activeWindowTouching() || m_latteView->windowsTracker()->currentScreen()->existsWindowTouching()}; raiseView(!windowIntersects); } void VisibilityManager::saveConfig() { if (!m_latteView->containment()) return; auto config = m_latteView->containment()->config(); config.writeEntry("enableKWinEdges", m_enableKWinEdgesFromUser); config.writeEntry("timerShow", m_timerShow.interval()); config.writeEntry("timerHide", m_timerHide.interval()); config.writeEntry("raiseOnDesktopChange", m_raiseOnDesktopChange); config.writeEntry("raiseOnActivityChange", m_raiseOnActivityChange); m_latteView->containment()->configNeedsSaving(); } void VisibilityManager::restoreConfig() { if (!m_latteView || !m_latteView->containment()){ return; } auto config = m_latteView->containment()->config(); m_timerShow.setInterval(config.readEntry("timerShow", 0)); m_timerHide.setInterval(qMax(HIDEMINIMUMINTERVAL, config.readEntry("timerHide", 700))); emit timerShowChanged(); emit timerHideChanged(); m_enableKWinEdgesFromUser = config.readEntry("enableKWinEdges", true); emit enableKWinEdgesChanged(); setRaiseOnDesktop(config.readEntry("raiseOnDesktopChange", false)); setRaiseOnActivity(config.readEntry("raiseOnActivityChange", false)); auto storedMode = static_cast(m_latteView->containment()->config().readEntry("visibility", static_cast(Types::DodgeActive))); if (storedMode == Types::AlwaysVisible) { qDebug() << "Loading visibility mode: Always Visible , on startup..."; setMode(Types::AlwaysVisible); } else { connect(&m_timerStartUp, &QTimer::timeout, this, [&]() { if (!m_latteView || !m_latteView->containment()) { return; } auto fMode = static_cast(m_latteView->containment()->config().readEntry("visibility", static_cast(Types::DodgeActive))); qDebug() << "Loading visibility mode:" << fMode << " on startup..."; setMode(fMode); }); connect(m_latteView->containment(), &Plasma::Containment::userConfiguringChanged , this, [&](bool configuring) { if (configuring && m_timerStartUp.isActive()) m_timerStartUp.start(100); }); m_timerStartUp.start(); } connect(m_latteView->containment(), &Plasma::Containment::userConfiguringChanged , this, [&](bool configuring) { if (!configuring) { saveConfig(); } }); } bool VisibilityManager::containsMouse() const { return m_containsMouse; } void VisibilityManager::setContainsMouse(bool contains) { if (m_containsMouse == contains) { return; } m_containsMouse = contains; emit containsMouseChanged(); } bool VisibilityManager::windowContainsMouse() { return m_containsMouse || (m_edgeGhostWindow && m_edgeGhostWindow->containsMouse()); } void VisibilityManager::checkMouseInFloatingArea() { if (m_latteView->isFloatingWindow()) { if (!m_floatingGapWindow) { createFloatingGapWindow(); } m_floatingGapWindow->callAsyncContainsMouse(); } } void VisibilityManager::viewEventManager(QEvent *ev) { switch (ev->type()) { case QEvent::Enter: setContainsMouse(true); break; case QEvent::Leave: m_dragEnter = false; setContainsMouse(false); break; case QEvent::DragEnter: m_dragEnter = true; if (m_isHidden) { emit mustBeShown(); } break; case QEvent::DragLeave: case QEvent::Drop: m_dragEnter = false; updateHiddenState(); break; default: break; } } //! KWin Edges Support functions bool VisibilityManager::enableKWinEdges() const { return m_enableKWinEdgesFromUser; } void VisibilityManager::setEnableKWinEdges(bool enable) { if (m_enableKWinEdgesFromUser == enable) { return; } m_enableKWinEdgesFromUser = enable; emit enableKWinEdgesChanged(); } void VisibilityManager::updateKWinEdgesSupport() { if ((m_mode == Types::AutoHide || m_mode == Types::DodgeActive || m_mode == Types::DodgeAllWindows || m_mode == Types::DodgeMaximized) && !m_latteView->byPassWM()) { if (m_enableKWinEdgesFromUser) { createEdgeGhostWindow(); if (m_latteView->isFloatingWindow()) { createFloatingGapWindow(); } } else if (!m_enableKWinEdgesFromUser) { deleteEdgeGhostWindow(); deleteFloatingGapWindow(); } } else if (m_mode == Types::WindowsCanCover) { createEdgeGhostWindow(); } else { deleteEdgeGhostWindow(); deleteFloatingGapWindow(); } } void VisibilityManager::createEdgeGhostWindow() { if (!m_edgeGhostWindow) { m_edgeGhostWindow = new ScreenEdgeGhostWindow(m_latteView); connect(m_edgeGhostWindow, &ScreenEdgeGhostWindow::containsMouseChanged, this, [ = ](bool contains) { if (contains) { raiseView(true); } else { m_timerShow.stop(); updateGhostWindowState(); } }); connect(m_edgeGhostWindow, &ScreenEdgeGhostWindow::dragEntered, this, [&]() { if (m_isHidden) { emit mustBeShown(); } }); m_connectionsKWinEdges[0] = connect(m_wm, &WindowSystem::AbstractWindowInterface::currentActivityChanged, this, [&]() { bool inCurrentLayout = (m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout || (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->layout() && !m_latteView->positioner()->inLocationAnimation() && m_latteView->layout()->isCurrent())); if (m_edgeGhostWindow) { if (inCurrentLayout) { m_wm->setActiveEdge(m_edgeGhostWindow, m_isHidden); } else { m_wm->setActiveEdge(m_edgeGhostWindow, false); } } }); emit supportsKWinEdgesChanged(); } } void VisibilityManager::deleteEdgeGhostWindow() { if (m_edgeGhostWindow) { m_edgeGhostWindow->deleteLater(); m_edgeGhostWindow = nullptr; for (auto &c : m_connectionsKWinEdges) { disconnect(c); } emit supportsKWinEdgesChanged(); } } void VisibilityManager::createFloatingGapWindow() { if (!m_floatingGapWindow) { m_floatingGapWindow = new FloatingGapWindow(m_latteView); connect(m_floatingGapWindow, &FloatingGapWindow::asyncContainsMouseChanged, this, [ = ](bool contains) { if (contains) { if (m_latteView->isFloatingWindow() && !m_isHidden) { //! immediate call after contains mouse checks for mouse in sensitive floating areas updateHiddenState(); } } else { if (m_latteView->isFloatingWindow() && !m_isHidden) { //! immediate call after contains mouse checks for mouse in sensitive floating areas emit mustBeHide(); } } }); } } void VisibilityManager::deleteFloatingGapWindow() { if (m_floatingGapWindow) { m_floatingGapWindow->deleteLater(); m_floatingGapWindow = nullptr; } } bool VisibilityManager::supportsFloatingGap() const { return (m_floatingGapWindow != nullptr); } //! END: VisibilityManager implementation } } diff --git a/app/wm/waylandinterface.cpp b/app/wm/waylandinterface.cpp index 39889d96..699effea 100644 --- a/app/wm/waylandinterface.cpp +++ b/app/wm/waylandinterface.cpp @@ -1,878 +1,878 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "waylandinterface.h" // local #include "view/positioner.h" -#include "view/screenedgeghostwindow.h" #include "view/view.h" +#include "view/helpers/screenedgeghostwindow.h" #include "../lattecorona.h" #include "../../liblatte2/extras.h" #include "../../liblatte2/types.h" // Qt #include #include #include #include #include // KDE #include #include #include #if KF5_VERSION_MINOR >= 52 #include #endif // X11 #include using namespace KWayland::Client; namespace Latte { class Private::GhostWindow : public QRasterWindow { Q_OBJECT public: WindowSystem::WindowId m_winId; GhostWindow(WindowSystem::WaylandInterface *waylandInterface) : m_waylandInterface(waylandInterface) { setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus); connect(m_waylandInterface, &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, &GhostWindow::identifyWinId); setupWaylandIntegration(); show(); } ~GhostWindow() { m_waylandInterface->unregisterIgnoredWindow(m_winId); delete m_shellSurface; } void setGeometry(const QRect &rect) { if (geometry() == rect) { return; } m_validGeometry = rect; setMinimumSize(rect.size()); setMaximumSize(rect.size()); resize(rect.size()); m_shellSurface->setPosition(rect.topLeft()); } void setupWaylandIntegration() { using namespace KWayland::Client; if (m_shellSurface) return; Surface *s{Surface::fromWindow(this)}; if (!s) return; m_shellSurface = m_waylandInterface->waylandCoronaInterface()->createSurface(s, this); qDebug() << "wayland ghost window surface was created..."; m_shellSurface->setSkipTaskbar(true); m_shellSurface->setPanelTakesFocus(false); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); } KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; WindowSystem::WaylandInterface *m_waylandInterface{nullptr}; //! geometry() function under wayland does not return nice results QRect m_validGeometry; public slots: void identifyWinId() { if (m_winId.isNull()) { m_winId = m_waylandInterface->winIdFor("latte-dock", m_validGeometry); m_waylandInterface->registerIgnoredWindow(m_winId); } } }; namespace WindowSystem { WaylandInterface::WaylandInterface(QObject *parent) : AbstractWindowInterface(parent) { m_corona = qobject_cast(parent); } WaylandInterface::~WaylandInterface() { } void WaylandInterface::init() { } void WaylandInterface::initWindowManagement(KWayland::Client::PlasmaWindowManagement *windowManagement) { if (m_windowManagement == windowManagement) { return; } m_windowManagement = windowManagement; connect(m_windowManagement, &PlasmaWindowManagement::windowCreated, this, &WaylandInterface::windowCreatedProxy); connect(m_windowManagement, &PlasmaWindowManagement::activeWindowChanged, this, [&]() noexcept { auto w = m_windowManagement->activeWindow(); if (!w || (w && (!m_ignoredWindows.contains(w->internalId() && !isPlasmaDesktop(w)))) ) { emit activeWindowChanged(w ? w->internalId() : 0); } }, Qt::QueuedConnection); } #if KF5_VERSION_MINOR >= 52 void WaylandInterface::initVirtualDesktopManagement(KWayland::Client::PlasmaVirtualDesktopManagement *virtualDesktopManagement) { if (m_virtualDesktopManagement == virtualDesktopManagement) { return; } m_virtualDesktopManagement = virtualDesktopManagement; connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopCreated, this, [this](const QString &id, quint32 position) { addDesktop(id, position); }); connect(m_virtualDesktopManagement, &KWayland::Client::PlasmaVirtualDesktopManagement::desktopRemoved, this, [this](const QString &id) { m_desktops.removeAll(id); if (m_currentDesktop == id) { setCurrentDesktop(QString()); } }); } void WaylandInterface::addDesktop(const QString &id, quint32 position) { if (m_desktops.contains(id)) { return; } m_desktops.append(id); const KWayland::Client::PlasmaVirtualDesktop *desktop = m_virtualDesktopManagement->getVirtualDesktop(id); QObject::connect(desktop, &KWayland::Client::PlasmaVirtualDesktop::activated, this, [desktop, this]() { setCurrentDesktop(desktop->id()); } ); if (desktop->isActive()) { setCurrentDesktop(id); } } void WaylandInterface::setCurrentDesktop(QString desktop) { if (m_currentDesktop == desktop) { return; } m_currentDesktop = desktop; emit currentDesktopChanged(); } #endif KWayland::Client::PlasmaShell *WaylandInterface::waylandCoronaInterface() const { return m_corona->waylandCoronaInterface(); } //! Register Latte Ignored Windows in order to NOT be tracked void WaylandInterface::registerIgnoredWindow(WindowId wid) { if (!wid.isNull() && !m_ignoredWindows.contains(wid)) { m_ignoredWindows.append(wid); KWayland::Client::PlasmaWindow *w = windowFor(wid); if (w) { untrackWindow(w); } emit windowChanged(wid); } } void WaylandInterface::unregisterIgnoredWindow(WindowId wid) { if (m_ignoredWindows.contains(wid)) { m_ignoredWindows.removeAll(wid); emit windowRemoved(wid); } } void WaylandInterface::setViewExtraFlags(QObject *view, bool isPanelWindow, Latte::Types::Visibility mode) { KWayland::Client::PlasmaShellSurface *surface = qobject_cast(view); Latte::View *latteView = qobject_cast(view); if (latteView) { surface = latteView->surface(); } if (!surface) { return; } surface->setSkipTaskbar(true); #if KF5_VERSION_MINOR >= 47 surface->setSkipSwitcher(true); #endif bool atBottom{!isPanelWindow && (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover)}; if (isPanelWindow) { surface->setRole(PlasmaShellSurface::Role::Panel); surface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); } else { surface->setRole(PlasmaShellSurface::Role::Normal); } if (latteView) { WindowId winId = latteView->positioner()->trackedWindowId(); auto w = windowFor(winId); if (w && !w->isOnAllDesktops()) { requestToggleIsOnAllDesktops(winId); } //! Layer to be applied if (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover) { setKeepBelow(winId, true); } else if (mode == Latte::Types::NormalWindow) { setKeepBelow(winId, false); setKeepAbove(winId, false); } else { setKeepAbove(winId, true); } } if (atBottom){ //! trying to workaround WM behavior in order //! 1. View at the end MUST NOT HAVE FOCUSABILITY (issue example: clicking a single active task is not minimized) //! 2. View at the end MUST BE AT THE BOTTOM of windows stack QTimer::singleShot(50, [this, surface]() { surface->setRole(PlasmaShellSurface::Role::ToolTip); }); } } void WaylandInterface::setViewStruts(QWindow &view, const QRect &rect, Plasma::Types::Location location) { if (!m_ghostWindows.contains(view.winId())) { m_ghostWindows[view.winId()] = new Private::GhostWindow(this); } auto w = m_ghostWindows[view.winId()]; switch (location) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: w->setGeometry({rect.x() + rect.width() / 2, rect.y(), 1, rect.height()}); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: w->setGeometry({rect.x(), rect.y() + rect.height() / 2, rect.width(), 1}); break; default: break; } } void WaylandInterface::switchToNextVirtualDesktop() const { #if KF5_VERSION_MINOR >= 52 if (!m_virtualDesktopManagement || m_desktops.count() <= 1) { return; } int curPos = m_desktops.indexOf(m_currentDesktop); int nextPos = curPos + 1; if (curPos == m_desktops.count()-1) { nextPos = 0; } KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]); if (desktopObj) { desktopObj->requestActivate(); } #endif } void WaylandInterface::switchToPreviousVirtualDesktop() const { #if KF5_VERSION_MINOR >= 52 if (!m_virtualDesktopManagement || m_desktops.count() <= 1) { return; } int curPos = m_desktops.indexOf(m_currentDesktop); int nextPos = curPos - 1; if (curPos == 0) { nextPos = m_desktops.count()-1; } KWayland::Client::PlasmaVirtualDesktop *desktopObj = m_virtualDesktopManagement->getVirtualDesktop(m_desktops[nextPos]); if (desktopObj) { desktopObj->requestActivate(); } #endif } void WaylandInterface::setWindowOnActivities(QWindow &window, const QStringList &activities) { //! needs to updated to wayland case // KWindowSystem::setOnActivities(view.winId(), activities); } void WaylandInterface::removeViewStruts(QWindow &view) const { delete m_ghostWindows.take(view.winId()); } WindowId WaylandInterface::activeWindow() const { if (!m_windowManagement) { return 0; } auto wid = m_windowManagement->activeWindow(); return wid ? wid->internalId() : 0; } void WaylandInterface::skipTaskBar(const QDialog &dialog) const { KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar); } void WaylandInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location) const { auto slideLocation = KWindowEffects::NoEdge; switch (location) { case Slide::Top: slideLocation = KWindowEffects::TopEdge; break; case Slide::Bottom: slideLocation = KWindowEffects::BottomEdge; break; case Slide::Left: slideLocation = KWindowEffects::LeftEdge; break; case Slide::Right: slideLocation = KWindowEffects::RightEdge; break; default: break; } KWindowEffects::slideWindow(view.winId(), slideLocation, -1); } void WaylandInterface::enableBlurBehind(QWindow &view) const { KWindowEffects::enableBlurBehind(view.winId()); } void WaylandInterface::setActiveEdge(QWindow *view, bool active) const { ViewPart::ScreenEdgeGhostWindow *window = qobject_cast(view); if (!window) { return; } if (window->parentView()->surface() && window->parentView()->visibility() && (window->parentView()->visibility()->mode() == Types::DodgeActive || window->parentView()->visibility()->mode() == Types::DodgeMaximized || window->parentView()->visibility()->mode() == Types::DodgeAllWindows || window->parentView()->visibility()->mode() == Types::AutoHide)) { if (active) { window->showWithMask(); window->surface()->requestHideAutoHidingPanel(); } else { window->hideWithMask(); window->surface()->requestShowAutoHidingPanel(); } } } WindowInfoWrap WaylandInterface::requestInfoActive() const { if (!m_windowManagement) { return {}; } auto w = m_windowManagement->activeWindow(); if (!w) return {}; return requestInfo(w->internalId()); } WindowInfoWrap WaylandInterface::requestInfo(WindowId wid) const { WindowInfoWrap winfoWrap; auto w = windowFor(wid); if (w) { if (isPlasmaDesktop(w)) { winfoWrap.setIsValid(true); winfoWrap.setIsPlasmaDesktop(true); winfoWrap.setWid(wid); //! Window Abilities winfoWrap.setIsClosable(false); winfoWrap.setIsFullScreenable(false); winfoWrap.setIsGroupable(false); winfoWrap.setIsMaximizable(false); winfoWrap.setIsMinimizable(false); winfoWrap.setIsMovable(false); winfoWrap.setIsResizable(false); winfoWrap.setIsShadeable(false); winfoWrap.setIsVirtualDesktopsChangeable(false); //! Window Abilities } else if (isValidWindow(w)) { winfoWrap.setIsValid(true); winfoWrap.setWid(wid); winfoWrap.setParentId(w->parentWindow() ? w->parentWindow()->internalId() : 0); winfoWrap.setIsActive(w->isActive()); winfoWrap.setIsMinimized(w->isMinimized()); winfoWrap.setIsMaxVert(w->isMaximized()); winfoWrap.setIsMaxHoriz(w->isMaximized()); winfoWrap.setIsFullscreen(w->isFullscreen()); winfoWrap.setIsShaded(w->isShaded()); winfoWrap.setIsOnAllDesktops(w->isOnAllDesktops()); winfoWrap.setIsOnAllActivities(true); winfoWrap.setIsKeepAbove(w->isKeepAbove()); winfoWrap.setIsKeepBelow(w->isKeepBelow()); winfoWrap.setGeometry(w->geometry()); winfoWrap.setHasSkipTaskbar(w->skipTaskbar()); //! Window Abilities winfoWrap.setIsClosable(w->isCloseable()); winfoWrap.setIsFullScreenable(w->isFullscreenable()); winfoWrap.setIsMaximizable(w->isMaximizeable()); winfoWrap.setIsMinimizable(w->isMinimizeable()); winfoWrap.setIsMovable(w->isMovable()); winfoWrap.setIsResizable(w->isResizable()); winfoWrap.setIsShadeable(w->isShadeable()); winfoWrap.setIsVirtualDesktopsChangeable(w->isVirtualDesktopChangeable()); //! Window Abilities winfoWrap.setDisplay(w->title()); #if KF5_VERSION_MINOR >= 52 winfoWrap.setDesktops(w->plasmaVirtualDesktops()); #endif winfoWrap.setActivities(QStringList()); } } else { winfoWrap.setIsValid(false); } return winfoWrap; } AppData WaylandInterface::appDataFor(WindowId wid) const { auto window = windowFor(wid); if (window) { const AppData &data = appDataFromUrl(windowUrlFromMetadata(window->appId(), window->pid(), rulesConfig)); return data; } AppData empty; return empty; } KWayland::Client::PlasmaWindow *WaylandInterface::windowFor(WindowId wid) const { auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&wid](PlasmaWindow * w) noexcept { return w->isValid() && w->internalId() == wid; }); if (it == m_windowManagement->windows().constEnd()) { return nullptr; } return *it; } QIcon WaylandInterface::iconFor(WindowId wid) const { auto window = windowFor(wid); if (window) { return window->icon(); } return QIcon(); } WindowId WaylandInterface::winIdFor(QString appId, QRect geometry) const { auto it = std::find_if(m_windowManagement->windows().constBegin(), m_windowManagement->windows().constEnd(), [&appId, &geometry](PlasmaWindow * w) noexcept { return w->isValid() && w->appId() == appId && w->geometry() == geometry; }); if (it == m_windowManagement->windows().constEnd()) { return QVariant(); } return (*it)->internalId(); } bool WaylandInterface::windowCanBeDragged(WindowId wid) const { auto w = windowFor(wid); if (w && isValidWindow(w)) { WindowInfoWrap winfo = requestInfo(wid); return (winfo.isValid() && w->isMovable() && !winfo.isMinimized() && inCurrentDesktopActivity(winfo) && !winfo.isPlasmaDesktop()); } return false; } bool WaylandInterface::windowCanBeMaximized(WindowId wid) const { auto w = windowFor(wid); if (w && isValidWindow(w)) { WindowInfoWrap winfo = requestInfo(wid); return (winfo.isValid() && w->isMaximizeable() && !winfo.isMinimized() && inCurrentDesktopActivity(winfo) && !winfo.isPlasmaDesktop()); } return false; } void WaylandInterface::requestActivate(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestActivate(); } } void WaylandInterface::requestClose(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestClose(); } } void WaylandInterface::requestMoveWindow(WindowId wid, QPoint from) const { WindowInfoWrap wInfo = requestInfo(wid); if (windowCanBeDragged(wid) && inCurrentDesktopActivity(wInfo)) { auto w = windowFor(wid); if (w && isValidWindow(w)) { w->requestMove(); } } } void WaylandInterface::requestToggleIsOnAllDesktops(WindowId wid) const { #if KF5_VERSION_MINOR >= 52 auto w = windowFor(wid); if (w && isValidWindow(w) && m_desktops.count() > 1) { if (w->isOnAllDesktops()) { w->requestEnterVirtualDesktop(m_currentDesktop); } else { const QStringList &now = w->plasmaVirtualDesktops(); foreach (const QString &desktop, now) { w->requestLeaveVirtualDesktop(desktop); } } } #endif } void WaylandInterface::requestToggleKeepAbove(WindowId wid) const { auto w = windowFor(wid); if (w) { w->requestToggleKeepAbove(); } } void WaylandInterface::setKeepAbove(WindowId wid, bool active) const { auto w = windowFor(wid); if (w) { if (active) { setKeepBelow(wid, false); } if ((w->isKeepAbove() && active) || (!w->isKeepAbove() && !active)) { return; } w->requestToggleKeepAbove(); } } void WaylandInterface::setKeepBelow(WindowId wid, bool active) const { auto w = windowFor(wid); if (w) { if (active) { setKeepAbove(wid, false); } if ((w->isKeepBelow() && active) || (!w->isKeepBelow() && !active)) { return; } w->requestToggleKeepBelow(); } } void WaylandInterface::requestToggleMinimized(WindowId wid) const { auto w = windowFor(wid); WindowInfoWrap wInfo = requestInfo(wid); if (w && isValidWindow(w) && inCurrentDesktopActivity(wInfo)) { #if KF5_VERSION_MINOR >= 52 if (!m_currentDesktop.isEmpty()) { w->requestEnterVirtualDesktop(m_currentDesktop); } #endif w->requestToggleMinimized(); } } void WaylandInterface::requestToggleMaximized(WindowId wid) const { auto w = windowFor(wid); WindowInfoWrap wInfo = requestInfo(wid); if (w && isValidWindow(w) && windowCanBeMaximized(wid) && inCurrentDesktopActivity(wInfo)) { #if KF5_VERSION_MINOR >= 52 if (!m_currentDesktop.isEmpty()) { w->requestEnterVirtualDesktop(m_currentDesktop); } #endif w->requestToggleMaximized(); } } bool WaylandInterface::isPlasmaDesktop(const KWayland::Client::PlasmaWindow *w) const { if (!w || (w->appId() != QLatin1String("org.kde.plasmashell"))) { return false; } return AbstractWindowInterface::isPlasmaDesktop(w->geometry()); } bool WaylandInterface::isPlasmaPanel(const KWayland::Client::PlasmaWindow *w) const { if (!w || (w->appId() != QLatin1String("org.kde.plasmashell"))) { return false; } return AbstractWindowInterface::isPlasmaPanel(w->geometry()); } bool WaylandInterface::isValidWindow(const KWayland::Client::PlasmaWindow *w) const { //! DEPRECATED comment is case we must reenable this //! because wayland does not have any way yet to identify the window type //! a trick is to just consider windows as valid when they can be shown in the //! taskbar. Of course that creates issues with plasma native dialogs //! e.g. widgets explorer, Activities etc. that are not used to hide //! the dodge views appropriately return w->isValid() && !isPlasmaDesktop(w) && !m_plasmaPanels.contains(w->internalId()) && !m_ignoredWindows.contains(w->internalId()); } void WaylandInterface::updateWindow() { PlasmaWindow *pW = qobject_cast(QObject::sender()); if (pW && !m_ignoredWindows.contains(pW->internalId() && !isPlasmaDesktop(pW) )) { if (pW->appId() == QLatin1String("org.kde.plasmashell")) { if (isPlasmaDesktop(pW)) { return; } else if (isPlasmaPanel(pW)) { registerIgnoredWindow(pW->internalId()); } } considerWindowChanged(pW->internalId()); } } void WaylandInterface::windowUnmapped() { PlasmaWindow *pW = qobject_cast(QObject::sender()); if (pW) { untrackWindow(pW); emit windowRemoved(pW->internalId()); } } void WaylandInterface::trackWindow(KWayland::Client::PlasmaWindow *w) { if (!w) { return; } connect(w, &PlasmaWindow::activeChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::titleChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::fullscreenChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::geometryChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::maximizedChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::minimizedChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::shadedChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::skipTaskbarChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::onAllDesktopsChanged, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::parentWindowChanged, this, &WaylandInterface::updateWindow); #if KF5_VERSION_MINOR >= 52 connect(w, &PlasmaWindow::plasmaVirtualDesktopEntered, this, &WaylandInterface::updateWindow); connect(w, &PlasmaWindow::plasmaVirtualDesktopLeft, this, &WaylandInterface::updateWindow); #else connect(w, &PlasmaWindow::virtualDesktopChanged, this, &WaylandInterface::updateWindow); #endif connect(w, &PlasmaWindow::unmapped, this, &WaylandInterface::windowUnmapped); } void WaylandInterface::untrackWindow(KWayland::Client::PlasmaWindow *w) { if (!w) { return; } disconnect(w, &PlasmaWindow::activeChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::titleChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::fullscreenChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::geometryChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::maximizedChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::minimizedChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::shadedChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::skipTaskbarChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::onAllDesktopsChanged, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::parentWindowChanged, this, &WaylandInterface::updateWindow); #if KF5_VERSION_MINOR >= 52 disconnect(w, &PlasmaWindow::plasmaVirtualDesktopEntered, this, &WaylandInterface::updateWindow); disconnect(w, &PlasmaWindow::plasmaVirtualDesktopLeft, this, &WaylandInterface::updateWindow); #else disconnect(w, &PlasmaWindow::virtualDesktopChanged, this, &WaylandInterface::updateWindow); #endif disconnect(w, &PlasmaWindow::unmapped, this, &WaylandInterface::windowUnmapped); } void WaylandInterface::windowCreatedProxy(KWayland::Client::PlasmaWindow *w) { if (!isValidWindow(w)) { return; } if ((w->appId() == QLatin1String("org.kde.plasmashell")) && isPlasmaPanel(w)) { registerPlasmaPanel(w->internalId()); } else { trackWindow(w); emit windowAdded(w->internalId()); } if (w->appId() == "latte-dock") { emit latteWindowAdded(); } } } } #include "waylandinterface.moc" diff --git a/app/wm/xwindowinterface.cpp b/app/wm/xwindowinterface.cpp index 2f9d98ad..af7fa811 100644 --- a/app/wm/xwindowinterface.cpp +++ b/app/wm/xwindowinterface.cpp @@ -1,755 +1,755 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "xwindowinterface.h" // local #include "tasktools.h" -#include "view/screenedgeghostwindow.h" #include "view/view.h" +#include "view/helpers/screenedgeghostwindow.h" #include "../../liblatte2/extras.h" #include "../../liblatte2/types.h" // Qt #include #include #include // KDE #include #include #include #include // X11 #include #include namespace Latte { namespace WindowSystem { XWindowInterface::XWindowInterface(QObject *parent) : AbstractWindowInterface(parent) { m_currentDesktop = QString(KWindowSystem::self()->currentDesktop()); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AbstractWindowInterface::activeWindowChanged); connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &AbstractWindowInterface::windowAdded); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &AbstractWindowInterface::windowRemoved); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, [&](int desktop) { m_currentDesktop = QString(desktop); emit currentDesktopChanged(); }); connect(KWindowSystem::self() , static_cast (&KWindowSystem::windowChanged) , this, &XWindowInterface::windowChangedProxy); for(auto wid : KWindowSystem::self()->windows()) { emit windowAdded(wid); windowChangedProxy(wid,0,0); } } XWindowInterface::~XWindowInterface() { } void XWindowInterface::setViewExtraFlags(QObject *view,bool isPanelWindow, Latte::Types::Visibility mode) { WId winId = -1; QQuickView *quickView = qobject_cast(view); if (quickView) { winId = quickView->winId(); } if (!quickView) { QQuickWindow *quickWindow = qobject_cast(view); if (quickWindow) { winId = quickWindow->winId(); } } if (winId < 0) { return; } NETWinInfo winfo(QX11Info::connection() , static_cast(winId) , static_cast(winId) , 0, 0); winfo.setAllowedActions(NET::ActionChangeDesktop); if (isPanelWindow) { KWindowSystem::setType(winId, NET::Dock); } else { KWindowSystem::setType(winId, NET::Normal); } #if KF5_VERSION_MINOR >= 45 KWindowSystem::setState(winId, NET::SkipTaskbar | NET::SkipPager | NET::SkipSwitcher); #else KWindowSystem::setState(winId, NET::SkipTaskbar | NET::SkipPager); #endif KWindowSystem::setOnAllDesktops(winId, true); //! Layer to be applied if (mode == Latte::Types::WindowsCanCover || mode == Latte::Types::WindowsAlwaysCover) { setKeepBelow(winId, true); } else if (mode == Latte::Types::NormalWindow) { setKeepBelow(winId, false); setKeepAbove(winId, false); } else { setKeepAbove(winId, true); } } void XWindowInterface::setViewStruts(QWindow &view, const QRect &rect , Plasma::Types::Location location) { NETExtendedStrut strut; const auto screen = view.screen(); const QRect currentScreen {screen->geometry()}; const QRect wholeScreen {{0, 0}, screen->virtualSize()}; switch (location) { case Plasma::Types::TopEdge: { const int topOffset {screen->geometry().top()}; strut.top_width = rect.height() + topOffset; strut.top_start = rect.x(); strut.top_end = rect.x() + rect.width() - 1; break; } case Plasma::Types::BottomEdge: { const int bottomOffset {wholeScreen.bottom() - currentScreen.bottom()}; strut.bottom_width = rect.height() + bottomOffset; strut.bottom_start = rect.x(); strut.bottom_end = rect.x() + rect.width() - 1; break; } case Plasma::Types::LeftEdge: { const int leftOffset = {screen->geometry().left()}; strut.left_width = rect.width() + leftOffset; strut.left_start = rect.y(); strut.left_end = rect.y() + rect.height() - 1; break; } case Plasma::Types::RightEdge: { const int rightOffset = {wholeScreen.right() - currentScreen.right()}; strut.right_width = rect.width() + rightOffset; strut.right_start = rect.y(); strut.right_end = rect.y() + rect.height() - 1; break; } default: qWarning() << "wrong location:" << qEnumToStr(location); return; } KWindowSystem::setExtendedStrut(view.winId(), strut.left_width, strut.left_start, strut.left_end, strut.right_width, strut.right_start, strut.right_end, strut.top_width, strut.top_start, strut.top_end, strut.bottom_width, strut.bottom_start, strut.bottom_end ); } void XWindowInterface::switchToNextVirtualDesktop() const { int desktops = KWindowSystem::numberOfDesktops(); if (desktops <= 1) { return; } int curPos = KWindowSystem::currentDesktop(); int nextPos = curPos + 1; if (curPos == desktops) { nextPos = 1; } KWindowSystem::setCurrentDesktop(nextPos); } void XWindowInterface::switchToPreviousVirtualDesktop() const { int desktops = KWindowSystem::numberOfDesktops(); if (desktops <= 1) { return; } int curPos = KWindowSystem::currentDesktop(); int nextPos = curPos - 1; if (curPos == 1) { nextPos = desktops; } KWindowSystem::setCurrentDesktop(nextPos); } void XWindowInterface::setWindowOnActivities(QWindow &window, const QStringList &activities) { KWindowSystem::setOnActivities(window.winId(), activities); } void XWindowInterface::removeViewStruts(QWindow &view) const { KWindowSystem::setStrut(view.winId(), 0, 0, 0, 0); } WindowId XWindowInterface::activeWindow() const { return KWindowSystem::self()->activeWindow(); } void XWindowInterface::skipTaskBar(const QDialog &dialog) const { KWindowSystem::setState(dialog.winId(), NET::SkipTaskbar); } void XWindowInterface::slideWindow(QWindow &view, AbstractWindowInterface::Slide location) const { auto slideLocation = KWindowEffects::NoEdge; switch (location) { case Slide::Top: slideLocation = KWindowEffects::TopEdge; break; case Slide::Bottom: slideLocation = KWindowEffects::BottomEdge; break; case Slide::Left: slideLocation = KWindowEffects::LeftEdge; break; case Slide::Right: slideLocation = KWindowEffects::RightEdge; break; default: break; } KWindowEffects::slideWindow(view.winId(), slideLocation, -1); } void XWindowInterface::enableBlurBehind(QWindow &view) const { KWindowEffects::enableBlurBehind(view.winId()); } void XWindowInterface::setActiveEdge(QWindow *view, bool active) const { ViewPart::ScreenEdgeGhostWindow *window = qobject_cast(view); if (!window) { return; } xcb_connection_t *c = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(c, atomCookie, nullptr)); if (!atom) { return; } if (!active) { xcb_delete_property(c, window->winId(), atom->atom); window->hideWithMask(); return; } window->showWithMask(); uint32_t value = 0; switch (window->location()) { case Plasma::Types::TopEdge: value = 0; break; case Plasma::Types::RightEdge: value = 1; break; case Plasma::Types::BottomEdge: value = 2; break; case Plasma::Types::LeftEdge: value = 3; break; case Plasma::Types::Floating: default: value = 4; break; } int hideType = 0; value |= hideType << 8; xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } WindowInfoWrap XWindowInterface::requestInfoActive() const { return requestInfo(KWindowSystem::activeWindow()); } WindowInfoWrap XWindowInterface::requestInfo(WindowId wid) const { const KWindowInfo winfo{wid.value(), NET::WMFrameExtents | NET::WMWindowType | NET::WMGeometry | NET::WMDesktop | NET::WMState | NET::WMName | NET::WMVisibleName, NET::WM2WindowClass | NET::WM2Activities | NET::WM2AllowedActions | NET::WM2TransientFor}; //! update desktop id bool isDesktop{false}; if (winfo.windowClassName() == "plasmashell" && isPlasmaDesktop(winfo.geometry())) { isDesktop = true; windowsTracker()->setPlasmaDesktop(wid); } WindowInfoWrap winfoWrap; if (!winfo.valid()) { winfoWrap.setIsValid(false); } else if (isValidWindow(winfo) && !isDesktop) { winfoWrap.setIsValid(true); winfoWrap.setWid(wid); winfoWrap.setParentId(winfo.transientFor()); winfoWrap.setIsActive(KWindowSystem::activeWindow() == wid.value()); winfoWrap.setIsMinimized(winfo.hasState(NET::Hidden)); winfoWrap.setIsMaxVert(winfo.hasState(NET::MaxVert)); winfoWrap.setIsMaxHoriz(winfo.hasState(NET::MaxHoriz)); winfoWrap.setIsFullscreen(winfo.hasState(NET::FullScreen)); winfoWrap.setIsShaded(winfo.hasState(NET::Shaded)); winfoWrap.setIsOnAllDesktops(winfo.onAllDesktops()); winfoWrap.setIsOnAllActivities(winfo.activities().empty()); winfoWrap.setGeometry(winfo.frameGeometry()); winfoWrap.setIsKeepAbove(winfo.hasState(NET::KeepAbove)); winfoWrap.setIsKeepBelow(winfo.hasState(NET::KeepBelow)); winfoWrap.setHasSkipTaskbar(winfo.hasState(NET::SkipTaskbar)); //! Window Abilities winfoWrap.setIsClosable(winfo.actionSupported(NET::ActionClose)); winfoWrap.setIsFullScreenable(winfo.actionSupported(NET::ActionFullScreen)); winfoWrap.setIsMaximizable(winfo.actionSupported(NET::ActionMax)); winfoWrap.setIsMinimizable(winfo.actionSupported(NET::ActionMinimize)); winfoWrap.setIsMovable(winfo.actionSupported(NET::ActionMove)); winfoWrap.setIsResizable(winfo.actionSupported(NET::ActionResize)); winfoWrap.setIsShadeable(winfo.actionSupported(NET::ActionShade)); winfoWrap.setIsVirtualDesktopsChangeable(winfo.actionSupported(NET::ActionChangeDesktop)); //! Window Abilities winfoWrap.setDisplay(winfo.visibleName()); winfoWrap.setDesktops({QString(winfo.desktop())}); winfoWrap.setActivities(winfo.activities()); } else if (m_desktopId == wid) { winfoWrap.setIsValid(true); winfoWrap.setIsPlasmaDesktop(true); winfoWrap.setWid(wid); winfoWrap.setParentId(0); winfoWrap.setHasSkipTaskbar(true); //! Window Abilities winfoWrap.setIsClosable(false); winfoWrap.setIsFullScreenable(false); winfoWrap.setIsGroupable(false); winfoWrap.setIsMaximizable(false); winfoWrap.setIsMinimizable(false); winfoWrap.setIsMovable(false); winfoWrap.setIsResizable(false); winfoWrap.setIsShadeable(false); winfoWrap.setIsVirtualDesktopsChangeable(false); //! Window Abilities } return winfoWrap; } AppData XWindowInterface::appDataFor(WindowId wid) const { return appDataFromUrl(windowUrl(wid)); } QUrl XWindowInterface::windowUrl(WindowId wid) const { const KWindowInfo info(wid.value(), 0, NET::WM2WindowClass | NET::WM2DesktopFileName); QString desktopFile = QString::fromUtf8(info.desktopFileName()); if (!desktopFile.isEmpty()) { KService::Ptr service = KService::serviceByStorageId(desktopFile); if (service) { const QString &menuId = service->menuId(); // applications: URLs are used to refer to applications by their KService::menuId // (i.e. .desktop file name) rather than the absolute path to a .desktop file. if (!menuId.isEmpty()) { return QUrl(QStringLiteral("applications:") + menuId); } return QUrl::fromLocalFile(service->entryPath()); } if (!desktopFile.endsWith(QLatin1String(".desktop"))) { desktopFile.append(QLatin1String(".desktop")); } if (KDesktopFile::isDesktopFile(desktopFile) && QFile::exists(desktopFile)) { return QUrl::fromLocalFile(desktopFile); } } return windowUrlFromMetadata(info.windowClassClass(), NETWinInfo(QX11Info::connection(), wid.value(), QX11Info::appRootWindow(), NET::WMPid, NET::Properties2()).pid(), rulesConfig, info.windowClassName()); } bool XWindowInterface::windowCanBeDragged(WindowId wid) const { WindowInfoWrap winfo = requestInfo(wid); return (winfo.isValid() && !winfo.isMinimized() && winfo.isMovable() && inCurrentDesktopActivity(winfo) && !winfo.isPlasmaDesktop()); } bool XWindowInterface::windowCanBeMaximized(WindowId wid) const { WindowInfoWrap winfo = requestInfo(wid); return (winfo.isValid() && !winfo.isMinimized() && winfo.isMaximizable() && inCurrentDesktopActivity(winfo) && !winfo.isPlasmaDesktop()); } void XWindowInterface::requestActivate(WindowId wid) const { KWindowSystem::activateWindow(wid.toInt()); } QIcon XWindowInterface::iconFor(WindowId wid) const { QIcon icon; icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeSmall, KIconLoader::SizeSmall, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeMedium, KIconLoader::SizeMedium, false)); icon.addPixmap(KWindowSystem::icon(wid.value(), KIconLoader::SizeLarge, KIconLoader::SizeLarge, false)); return icon; } WindowId XWindowInterface::winIdFor(QString appId, QRect geometry) const { return activeWindow(); } void XWindowInterface::requestClose(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); ri.closeWindowRequest(wInfo.wid().toUInt()); } void XWindowInterface::requestMoveWindow(WindowId wid, QPoint from) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop() || !inCurrentDesktopActivity(wInfo)) { return; } int borderX = wInfo.geometry().width() > 120 ? 60 : 10; int borderY{10}; //! find min/max values for x,y based on active window geometry int minX = wInfo.geometry().x() + borderX; int maxX = wInfo.geometry().x() + wInfo.geometry().width() - borderX; int minY = wInfo.geometry().y() + borderY; int maxY = wInfo.geometry().y() + wInfo.geometry().height() - borderY; //! set the point from which this window will be moved, //! make sure that it is in window boundaries int validX = qBound(minX, from.x(), maxX); int validY = qBound(minY, from.y(), maxY); NETRootInfo ri(QX11Info::connection(), NET::WMMoveResize); ri.moveResizeRequest(wInfo.wid().toUInt(), validX, validY, NET::Move); } void XWindowInterface::requestToggleIsOnAllDesktops(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } if (KWindowSystem::numberOfDesktops() <= 1) { return; } if (wInfo.isOnAllDesktops()) { KWindowSystem::setOnDesktop(wid.toUInt(), KWindowSystem::currentDesktop()); KWindowSystem::forceActiveWindow(wid.toUInt()); } else { KWindowSystem::setOnAllDesktops(wid.toUInt(), true); } } void XWindowInterface::requestToggleKeepAbove(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop()) { return; } NETWinInfo ni(QX11Info::connection(), wid.toUInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); if (wInfo.isKeepAbove()) { ni.setState(NET::States(), NET::StaysOnTop); } else { ni.setState(NET::StaysOnTop, NET::StaysOnTop); } } void XWindowInterface::setKeepAbove(WindowId wid, bool active) const { if (wid.toUInt() <= 0) { return; } if (active) { KWindowSystem::setState(wid.toUInt(), NET::KeepAbove); KWindowSystem::clearState(wid.toUInt(), NET::KeepBelow); } else { KWindowSystem::clearState(wid.toUInt(), NET::KeepAbove); } } void XWindowInterface::setKeepBelow(WindowId wid, bool active) const { if (wid.toUInt() <= 0) { return; } if (active) { KWindowSystem::setState(wid.toUInt(), NET::KeepBelow); KWindowSystem::clearState(wid.toUInt(), NET::KeepAbove); } else { KWindowSystem::clearState(wid.toUInt(), NET::KeepBelow); } } void XWindowInterface::requestToggleMinimized(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!wInfo.isValid() || wInfo.isPlasmaDesktop() || !inCurrentDesktopActivity(wInfo)) { return; } if (wInfo.isMinimized()) { bool onCurrent = wInfo.isOnDesktop(m_currentDesktop); KWindowSystem::unminimizeWindow(wid.toUInt()); if (onCurrent) { KWindowSystem::forceActiveWindow(wid.toUInt()); } } else { KWindowSystem::minimizeWindow(wid.toUInt()); } } void XWindowInterface::requestToggleMaximized(WindowId wid) const { WindowInfoWrap wInfo = requestInfo(wid); if (!windowCanBeMaximized(wid) || !inCurrentDesktopActivity(wInfo)) { return; } bool restore = wInfo.isMaxHoriz() && wInfo.isMaxVert(); if (wInfo.isMinimized()) { KWindowSystem::unminimizeWindow(wid.toUInt()); } NETWinInfo ni(QX11Info::connection(), wid.toInt(), QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); if (restore) { ni.setState(NET::States(), NET::Max); } else { ni.setState(NET::Max, NET::Max); } } bool XWindowInterface::isValidWindow(WindowId wid) const { if (windowsTracker()->isValidFor(wid)) { return true; } const KWindowInfo winfo{wid.value(), NET::WMWindowType}; return isValidWindow(winfo); } bool XWindowInterface::isValidWindow(const KWindowInfo &winfo) const { if (windowsTracker()->isValidFor(winfo.win())) { return true; } constexpr auto types = NET::DockMask | NET::MenuMask | NET::SplashMask | NET::PopupMenuMask | NET::NormalMask | NET::DialogMask; NET::WindowType winType = winfo.windowType(types); const auto winClass = KWindowInfo(winfo.win(), 0, NET::WM2WindowClass).windowClassName(); //! ignored windows from tracking if (m_ignoredWindows.contains(winfo.win())) { return false; } if (m_desktopId == winfo.win()) { return false; } if (winType == -1) { // Trying to get more types for verify if the window have any other type winType = winfo.windowType(~types & NET::AllTypesMask); if (winType == -1) { qWarning() << KWindowInfo(winfo.win(), 0, NET::WM2WindowClass).windowClassName() << "doesn't have any WindowType, assuming as NET::Normal"; return true; } } bool isMenu = ((winType & NET::Menu) == true); bool isDock = ((winType & NET::Dock) == true); bool isPopup = ((winType & NET::PopupMenu) == true); bool isSplash = ((winType & NET::Splash) == true); //! GTK2+ dialogs case e.g. inkscape, gimp2, etc... //! are both Popups and Splash types, this is why //! we can not black list them here return !(isMenu || isDock); } void XWindowInterface::windowChangedProxy(WId wid, NET::Properties prop1, NET::Properties2 prop2) { const KWindowInfo info(wid, NET::WMGeometry, NET::WM2WindowClass); const auto winClass = info.windowClassName(); //! ignored windows do not trackd if (m_ignoredWindows.contains(wid)) { return; } if (winClass == "plasmashell") { //! update desktop id if (isPlasmaDesktop(info.geometry())) { m_desktopId = wid; windowsTracker()->setPlasmaDesktop(wid); considerWindowChanged(wid); return; } else if (isPlasmaPanel(info.geometry())) { registerPlasmaPanel(wid); return; } } //! accept only NET::Properties events, //! ignore when the user presses a key, or a window is sending X events etc. //! without needing to (e.g. Firefox, https://bugzilla.mozilla.org/show_bug.cgi?id=1389953) //! NET::WM2UserTime, NET::WM2IconPixmap etc.... if (prop1 == 0 && !(prop2 & (NET::WM2Activities | NET::WM2TransientFor))) { return; } //! accept only the following NET:Properties changed signals //! NET::WMState, NET::WMGeometry, NET::ActiveWindow if ( !(prop1 & NET::WMState) && !(prop1 & NET::WMGeometry) && !(prop1 & NET::ActiveWindow) && !(prop1 & NET::WMDesktop) && !(prop1 & (NET::WMName | NET::WMVisibleName) && !(prop2 & NET::WM2TransientFor) && !(prop2 & NET::WM2Activities)) ) { return; } //! ignore windows that do not respect normal windows types if (!isValidWindow(wid)) { return; } considerWindowChanged(wid); } } }