diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 59a988d5..c8cb3009 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,90 +1,91 @@ set(lattedock-app_SRCS ../liblatte2/commontools.cpp ../liblatte2/types.cpp alternativeshelper.cpp importer.cpp infoview.cpp lattecorona.cpp launcherssignals.cpp layoutmanager.cpp schemecolors.cpp screenpool.cpp layout/layout.cpp layout/shortcuts.cpp package/lattepackage.cpp plasma/extended/screenpool.cpp plasma/extended/theme.cpp settings/settingsdialog.cpp settings/universalsettings.cpp settings/delegates/activitycmbboxdelegate.cpp settings/delegates/checkboxdelegate.cpp settings/delegates/colorcmbboxdelegate.cpp settings/delegates/colorcmbboxitemdelegate.cpp settings/delegates/layoutnamedelegate.cpp shortcuts/globalshortcuts.cpp shortcuts/modifiertracker.cpp shortcuts/shortcutstracker.cpp view/contextmenu.cpp view/effects.cpp view/panelshadows.cpp view/positioner.cpp view/screenedgeghostwindow.cpp view/view.cpp view/visibilitymanager.cpp + view/windowstracker.cpp view/settings/primaryconfigview.cpp view/settings/secondaryconfigview.cpp wm/abstractwindowinterface.cpp wm/waylandinterface.cpp wm/windowinfowrap.cpp wm/xwindowinterface.cpp main.cpp ) 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) target_link_libraries(latte-dock Qt5::DBus Qt5::Quick Qt5::Qml KF5::I18n KF5::DBusAddons KF5::Declarative KF5::CoreAddons KF5::GuiAddons KF5::GlobalAccel KF5::Archive KF5::Crash KF5::XmlGui KF5::Plasma KF5::PlasmaQuick KF5::Activities KF5::Notifications KF5::NewStuff KF5::QuickAddons KF5::WaylandClient ) 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}) diff --git a/app/view/view.cpp b/app/view/view.cpp index f15a05cd..c43e87ca 100644 --- a/app/view/view.cpp +++ b/app/view/view.cpp @@ -1,1084 +1,1098 @@ /* * 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 "view.h" // local #include "contextmenu.h" #include "effects.h" #include "positioner.h" #include "visibilitymanager.h" #include "settings/primaryconfigview.h" #include "../lattecorona.h" #include "../layoutmanager.h" #include "../layout/layout.h" #include "../plasma/extended/theme.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../shortcuts/globalshortcuts.h" #include "../shortcuts/shortcutstracker.h" #include "../../liblatte2/extras.h" // Qt #include #include #include #include #include #include // KDe #include #include #include #include #include // Plasma #include #include #include namespace Latte { //! both alwaysVisible and byPassWM are passed through corona because //! during the view window creation containment hasn't been set, but these variables //! are needed in order for window flags to be set correctly View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassWM) : PlasmaQuick::ContainmentView(corona), m_contextMenu(new ViewPart::ContextMenu(this)), m_effects(new ViewPart::Effects(this)), m_positioner(new ViewPart::Positioner(this)) //needs to be created after Effects because it catches some of its signals { setTitle(corona->kPackage().metadata().name()); setIcon(qGuiApp->windowIcon()); setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); setColor(QColor(Qt::transparent)); setClearBeforeRendering(true); const auto flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus; if (byPassWM) { setFlags(flags | Qt::BypassWindowManagerHint); } else { setFlags(flags); } KWindowSystem::setOnAllDesktops(winId(), true); if (targetScreen) m_positioner->setScreenToFollow(targetScreen); else m_positioner->setScreenToFollow(qGuiApp->primaryScreen()); connect(this, &View::containmentChanged , this, [ &, byPassWM]() { qDebug() << "dock view c++ containment changed 1..."; if (!this->containment()) return; qDebug() << "dock view c++ containment changed 2..."; //! First load default values from file restoreConfig(); //! Afterwards override that values in case during creation something different is needed setByPassWM(byPassWM); //! Check the screen assigned to this dock reconsiderScreen(); if (!m_visibility) { m_visibility = new ViewPart::VisibilityManager(this); connect(m_visibility, &ViewPart::VisibilityManager::isHiddenChanged, this, [&]() { if (m_visibility->isHidden()) { deactivateApplets(); } }); } + if (!m_windowsTracker) { + m_windowsTracker = new ViewPart::WindowsTracker(this); + } + connect(this->containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); }, Qt::DirectConnection); auto *latteCorona = qobject_cast(this->corona()); if (latteCorona) { connect(latteCorona, &Latte::Corona::viewLocationChanged, this, &View::dockLocationChanged); } } View::~View() { m_inDelete = true; disconnect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &View::availableScreenRectChanged); disconnect(containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(statusChanged(Plasma::Types::ItemStatus))); qDebug() << "dock view deleting..."; rootContext()->setContextProperty(QStringLiteral("dock"), nullptr); rootContext()->setContextProperty(QStringLiteral("layoutManager"), nullptr); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), nullptr); rootContext()->setContextProperty(QStringLiteral("themeExtended"), nullptr); rootContext()->setContextProperty(QStringLiteral("universalSettings"), nullptr); //! this disconnect does not free up connections correctly when //! latteView is deleted. A crash for this example is the following: //! switch to Alternative Session and disable compositing, //! the signal creating the crash was probably from deleted //! windows. //! this->disconnect(); if (m_configView) { m_configView->setVisible(false);//hide(); } if (m_contextMenu) { delete m_contextMenu; } //needs to be deleted before Effects because it catches some of its signals if (m_positioner) { delete m_positioner; } if (m_effects) { delete m_effects; } - if (m_visibility) + if (m_windowsTracker) { + delete m_windowsTracker; + } + + if (m_visibility) { delete m_visibility; + } } void View::init() { connect(this, &QQuickWindow::xChanged, this, &View::xChanged); connect(this, &QQuickWindow::xChanged, this, &View::updateAbsDockGeometry); connect(this, &QQuickWindow::yChanged, this, &View::yChanged); connect(this, &QQuickWindow::yChanged, this, &View::updateAbsDockGeometry); connect(this, &QQuickWindow::widthChanged, this, &View::widthChanged); connect(this, &QQuickWindow::widthChanged, this, &View::updateAbsDockGeometry); connect(this, &QQuickWindow::heightChanged, this, &View::heightChanged); connect(this, &QQuickWindow::heightChanged, this, &View::updateAbsDockGeometry); connect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &View::availableScreenRectChanged); connect(this, &View::byPassWMChanged, this, &View::saveConfig); connect(this, &View::onPrimaryChanged, this, &View::saveConfig); connect(this, &View::isPreferredForShortcutsChanged, this, &View::saveConfig); connect(this, SIGNAL(normalThicknessChanged()), corona(), SIGNAL(availableScreenRectChanged())); connect(m_positioner, &ViewPart::Positioner::onHideWindowsForSlidingOut, this, &View::hideWindowsForSlidingOut); connect(m_positioner, &ViewPart::Positioner::screenGeometryChanged, this, &View::screenGeometryChanged); connect(m_contextMenu, &ViewPart::ContextMenu::menuChanged, this, &View::contextMenuIsShownChanged); ///!!!!! rootContext()->setContextProperty(QStringLiteral("latteView"), this); auto *latteCorona = qobject_cast(this->corona()); if (latteCorona) { rootContext()->setContextProperty(QStringLiteral("layoutManager"), latteCorona->layoutManager()); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), latteCorona->globalShortcuts()->shortcutsTracker()); rootContext()->setContextProperty(QStringLiteral("themeExtended"), latteCorona->themeExtended()); rootContext()->setContextProperty(QStringLiteral("universalSettings"), latteCorona->universalSettings()); } setSource(corona()->kPackage().filePath("lattedockui")); // setVisible(true); m_positioner->syncGeometry(); if (!KWindowSystem::isPlatformWayland()) { setVisible(true); } qDebug() << "SOURCE:" << source(); } bool View::inDelete() const { return m_inDelete; } void View::disconnectSensitiveSignals() { disconnect(corona(), &Plasma::Corona::availableScreenRectChanged, this, &View::availableScreenRectChanged); setManagedLayout(nullptr); - if (visibility()) { - visibility()->setEnabledDynamicBackground(false); + if (m_windowsTracker) { + m_windowsTracker->setEnabled(false); } } void View::availableScreenRectChanged() { if (m_inDelete) return; if (formFactor() == Plasma::Types::Vertical) { m_positioner->syncGeometry(); } } void View::setupWaylandIntegration() { if (m_shellSurface) return; if (Latte::Corona *c = qobject_cast(corona())) { using namespace KWayland::Client; PlasmaShell *interface {c->waylandCoronaInterface()}; if (!interface) return; Surface *s{Surface::fromWindow(this)}; if (!s) return; m_shellSurface = interface->createSurface(s, this); qDebug() << "WAYLAND dock window surface was created..."; m_shellSurface->setSkipTaskbar(true); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsGoBelow); } } KWayland::Client::PlasmaShellSurface *View::surface() { return m_shellSurface; } //! the main function which decides if this dock is at the //! correct screen void View::reconsiderScreen() { m_positioner->reconsiderScreen(); } void View::copyView() { m_managedLayout->copyView(containment()); } void View::removeView() { if (m_managedLayout && m_managedLayout->viewsCount() > 1) { QAction *removeAct = this->containment()->actions()->action(QStringLiteral("remove")); if (removeAct) { removeAct->trigger(); } } } bool View::settingsWindowIsShown() { auto configView = qobject_cast(m_configView); return (configView != nullptr); } void View::showSettingsWindow() { if (!settingsWindowIsShown()) { emit m_visibility->mustBeShown(); showConfigurationInterface(containment()); applyActivitiesToWindows(); } } void View::showConfigurationInterface(Plasma::Applet *applet) { if (!applet || !applet->containment()) return; Plasma::Containment *c = qobject_cast(applet); if (m_configView && c && c->isContainment() && c == this->containment()) { if (m_configView->isVisible()) { m_configView->setVisible(false); //m_configView->hide(); } else { m_configView->setVisible(true); //m_configView->show(); } return; } else if (m_configView) { if (m_configView->applet() == applet) { m_configView->setVisible(true); //m_configView->show(); m_configView->requestActivate(); return; } else { m_configView->setVisible(false); //m_configView->hide(); m_configView->deleteLater(); } } bool delayConfigView = false; if (c && containment() && c->isContainment() && c->id() == this->containment()->id()) { m_configView = new ViewPart::PrimaryConfigView(c, this); delayConfigView = true; } else { m_configView = new PlasmaQuick::ConfigView(applet); } m_configView.data()->init(); if (!delayConfigView) { m_configView->setVisible(true); //m_configView.data()->show(); } else { //add a timer for showing the configuration window the first time it is //created in order to give the containment's layouts the time to //calculate the window's height if (!KWindowSystem::isPlatformWayland()) { QTimer::singleShot(150, m_configView, SLOT(show())); } else { QTimer::singleShot(150, [this]() { m_configView->setVisible(true); }); } } } QRect View::localGeometry() const { return m_localGeometry; } void View::setLocalGeometry(const QRect &geometry) { if (m_localGeometry == geometry) { return; } m_localGeometry = geometry; emit localGeometryChanged(); updateAbsDockGeometry(); } void View::updateAbsDockGeometry(bool bypassChecks) { //! there was a -1 in height and width here. The reason of this //! if I remember correctly was related to multi-screen but I cant //! remember exactly the reason, something related to right edge in //! multi screen environment. BUT this was breaking the entire AlwaysVisible //! experience with struts. Removing them in order to restore correct //! behavior and keeping this comment in order to check for //! multi-screen breakage QRect absGeometry {x() + m_localGeometry.x(), y() + m_localGeometry.y() , m_localGeometry.width(), m_localGeometry.height()}; if (m_absGeometry == absGeometry && !bypassChecks) return; m_absGeometry = absGeometry; emit absGeometryChanged(m_absGeometry); //! this is needed in order to update correctly the screenGeometries if (visibility() && corona() && visibility()->mode() == Types::AlwaysVisible) { emit corona()->availableScreenRectChanged(); emit corona()->availableScreenRegionChanged(); } } void View::statusChanged(Plasma::Types::ItemStatus status) { if (containment()) { if (containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { setBlockHiding(true); } else if (!containment()->isUserConfiguring()){ setBlockHiding(false); } } } bool View::alternativesIsShown() const { return m_alternativesIsShown; } void View::setAlternativesIsShown(bool show) { if (m_alternativesIsShown == show) { return; } m_alternativesIsShown = show; setBlockHiding(show); emit alternativesIsShownChanged(); } bool View::contextMenuIsShown() const { if (!m_contextMenu) { return false; } return m_contextMenu->menu(); } int View::currentThickness() const { if (formFactor() == Plasma::Types::Vertical) { return m_effects->mask().isNull() ? width() : m_effects->mask().width() - m_effects->innerShadow(); } else { return m_effects->mask().isNull() ? height() : m_effects->mask().height() - m_effects->innerShadow(); } } int View::normalThickness() const { return m_normalThickness; } void View::setNormalThickness(int thickness) { if (m_normalThickness == thickness) { return; } m_normalThickness = thickness; emit normalThicknessChanged(); } bool View::byPassWM() const { return m_byPassWM; } void View::setByPassWM(bool bypass) { if (m_byPassWM == bypass) { return; } m_byPassWM = bypass; emit byPassWMChanged(); } bool View::behaveAsPlasmaPanel() const { return m_behaveAsPlasmaPanel; } void View::setBehaveAsPlasmaPanel(bool behavior) { if (m_behaveAsPlasmaPanel == behavior) { return; } m_behaveAsPlasmaPanel = behavior; emit behaveAsPlasmaPanelChanged(); } bool View::inEditMode() const { return m_inEditMode; } void View::setInEditMode(bool edit) { if (m_inEditMode == edit) { return; } m_inEditMode = edit; emit inEditModeChanged(); } bool View::isPreferredForShortcuts() const { return m_isPreferredForShortcuts; } void View::setIsPreferredForShortcuts(bool preferred) { if (m_isPreferredForShortcuts == preferred) { return; } m_isPreferredForShortcuts = preferred; emit isPreferredForShortcutsChanged(); if (m_isPreferredForShortcuts && m_managedLayout) { emit m_managedLayout->preferredViewForShortcutsChanged(this); } } void View::preferredViewForShortcutsChangedSlot(Latte::View *view) { if (view != this) { setIsPreferredForShortcuts(false); } } bool View::onPrimary() const { return m_onPrimary; } void View::setOnPrimary(bool flag) { if (m_onPrimary == flag) { return; } m_onPrimary = flag; emit onPrimaryChanged(); } float View::maxLength() const { return m_maxLength; } void View::setMaxLength(float length) { if (m_maxLength == length) { return; } m_maxLength = length; emit maxLengthChanged(); } int View::maxThickness() const { return m_maxThickness; } void View::setMaxThickness(int thickness) { if (m_maxThickness == thickness) return; m_maxThickness = thickness; emit maxThicknessChanged(); } int View::alignment() const { return m_alignment; } void View::setAlignment(int alignment) { Types::Alignment align = static_cast(alignment); if (m_alignment == alignment) { return; } m_alignment = align; emit alignmentChanged(); } QRect View::absGeometry() const { return m_absGeometry; } QRect View::screenGeometry() const { if (this->screen()) { QRect geom = this->screen()->geometry(); return geom; } return QRect(); } int View::offset() const { return m_offset; } void View::setOffset(int offset) { if (m_offset == offset) { return; } m_offset = offset; emit offsetChanged(); } int View::fontPixelSize() const { return m_fontPixelSize; } void View::setFontPixelSize(int size) { if (m_fontPixelSize == size) { return; } m_fontPixelSize = size; emit fontPixelSizeChanged(); } void View::applyActivitiesToWindows() { if (m_visibility) { QStringList activities = m_managedLayout->appliedActivities(); - m_visibility->setWindowOnActivities(*this, activities); + m_windowsTracker->setWindowOnActivities(*this, activities); if (m_configView) { - m_visibility->setWindowOnActivities(*m_configView, activities); + m_windowsTracker->setWindowOnActivities(*m_configView, activities); auto configView = qobject_cast(m_configView); if (configView && configView->secondaryWindow()) { - m_visibility->setWindowOnActivities(*configView->secondaryWindow(), activities); + m_windowsTracker->setWindowOnActivities(*configView->secondaryWindow(), activities); } } if (m_visibility->supportsKWinEdges()) { m_visibility->applyActivitiesToHiddenWindows(activities); } } } Layout *View::managedLayout() const { return m_managedLayout; } void View::setManagedLayout(Layout *layout) { if (m_managedLayout == layout) { return; } // clear mode for (auto &c : connectionsManagedLayout) { disconnect(c); } m_managedLayout = layout; if (m_managedLayout) { //! Sometimes the activity isnt completely ready, by adding a delay //! we try to catch up QTimer::singleShot(100, [this]() { if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[0] = connect(m_managedLayout, &Layout::preferredViewForShortcutsChanged, this, &View::preferredViewForShortcutsChangedSlot); } Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona->layoutManager()->memoryUsage() == Types::MultipleLayouts) { connectionsManagedLayout[1] = connect(latteCorona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT (runningActivitiesChanged) ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[2] = connect(m_managedLayout, &Layout::activitiesChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[3] = connect(latteCorona->layoutManager(), &LayoutManager::layoutsChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); //!IMPORTANT!!! ::: This fixes a bug when closing an Activity all docks from all Activities are //! disappearing! With this they reappear!!! connectionsManagedLayout[4] = connect(this, &QWindow::visibleChanged, this, [&]() { if (!isVisible() && m_managedLayout) { QTimer::singleShot(100, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); QTimer::singleShot(1500, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); } }); } emit managedLayoutChanged(); } void View::moveToLayout(QString layoutName) { if (!m_managedLayout) { return; } QList containments = m_managedLayout->unassignFromLayout(this); Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona && containments.size() > 0) { Layout *newLayout = latteCorona->layoutManager()->activeLayout(layoutName); if (newLayout) { newLayout->assignToLayout(this, containments); } } } void View::setBlockHiding(bool block) { if (!block) { auto *configView = qobject_cast(m_configView); if (m_alternativesIsShown || (configView && configView->sticker() && configView->isVisible())) { return; } if (m_visibility) { m_visibility->setBlockHiding(false); } } else { if (m_visibility) { m_visibility->setBlockHiding(true); } } } void View::hideWindowsForSlidingOut() { setBlockHiding(false); if (m_configView) { auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } //! remove latte tasks plasmoid void View::removeTasksPlasmoid() { if (!tasksPresent() || !containment()) { return; } foreach (Plasma::Applet *applet, containment()->applets()) { KPluginMetaData meta = applet->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.plasmoid") { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); if (closeApplet) { closeApplet->trigger(); //! remove only the first found return; } } } } //! check if the tasks plasmoid exist in the dock bool View::tasksPresent() { if (!this->containment()) { return false; } foreach (Plasma::Applet *applet, this->containment()->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { return true; } } return false; } //! check if the tasks plasmoid exist in the dock bool View::latteTasksPresent() { if (!this->containment()) { return false; } foreach (Plasma::Applet *applet, this->containment()->applets()) { KPluginMetaData metadata = applet->pluginMetaData(); if (metadata.pluginId() == "org.kde.latte.plasmoid") { return true; } } return false; } //!check if the plasmoid with _name_ exists in the midedata bool View::mimeContainsPlasmoid(QMimeData *mimeData, QString name) { if (!mimeData) { return false; } if (mimeData->hasFormat(QStringLiteral("text/x-plasmoidservicename"))) { QString data = mimeData->data(QStringLiteral("text/x-plasmoidservicename")); const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); foreach (const QString &appletName, appletNames) { if (appletName == name) return true; } } return false; } ViewPart::Effects *View::effects() const { return m_effects; } ViewPart::Positioner *View::positioner() const { return m_positioner; } ViewPart::VisibilityManager *View::visibility() const { return m_visibility; } +ViewPart::WindowsTracker *View::windowsTracker() const +{ + return m_windowsTracker; +} + bool View::event(QEvent *e) { if (!m_inDelete) { emit eventTriggered(e); switch (e->type()) { case QEvent::Leave: engine()->trimComponentCache(); break; case QEvent::PlatformSurface: if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); if (m_shellSurface) { m_positioner->syncGeometry(); m_effects->updateShadows(); } break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND dock window surface was deleted..."; m_effects->clearShadows(); } break; } } break; default: break; } } return ContainmentView::event(e);; } void View::deactivateApplets() { if (!containment()) { return; } foreach (auto applet, containment()->applets()) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { ai->setExpanded(false); } } } void View::toggleAppletExpanded(const int id) { if (!containment()) { return; } foreach (auto applet, containment()->applets()) { if (applet->id() == id) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { if (!ai->isActivationTogglesExpanded()) { ai->setActivationTogglesExpanded(true); } emit applet->activated(); } } } } QVariantList View::containmentActions() { QVariantList actions; /*if (containment()->corona()->immutability() != Plasma::Types::Mutable) { return actions; }*/ //FIXME: the trigger string it should be better to be supported this way //const QString trigger = Plasma::ContainmentActions::eventToString(event); const QString trigger = "RightButton;NoModifier"; Plasma::ContainmentActions *plugin = this->containment()->containmentActions().value(trigger); if (!plugin) { return actions; } if (plugin->containment() != this->containment()) { plugin->setContainment(this->containment()); // now configure it KConfigGroup cfg(this->containment()->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(this->containment()->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } foreach (QAction *ac, plugin->contextualActions()) { actions << QVariant::fromValue(ac); } return actions; } void View::disableGrabItemBehavior() { setMouseGrabEnabled(false); } void View::restoreGrabItemBehavior() { if (mouseGrabberItem()) { mouseGrabberItem()->ungrabMouse(); } } bool View::isHighestPriorityView() { if (m_managedLayout) { return this == m_managedLayout->highestPriorityView(); } return false; } //!BEGIN overriding context menus behavior void View::mousePressEvent(QMouseEvent *event) { bool result = m_contextMenu->mousePressEvent(event); emit contextMenuIsShownChanged(); if (result) { PlasmaQuick::ContainmentView::mousePressEvent(event); } } //!END overriding context menus behavior //!BEGIN configuration functions void View::saveConfig() { if (!this->containment()) return; auto config = this->containment()->config(); config.writeEntry("onPrimary", onPrimary()); config.writeEntry("byPassWM", byPassWM()); config.writeEntry("isPreferredForShortcuts", isPreferredForShortcuts()); config.sync(); } void View::restoreConfig() { if (!this->containment()) return; auto config = this->containment()->config(); m_onPrimary = config.readEntry("onPrimary", true); m_byPassWM = config.readEntry("byPassWM", false); m_isPreferredForShortcuts = config.readEntry("isPreferredForShortcuts", false); //! Send changed signals at the end in order to be sure that saveConfig //! wont rewrite default/invalid values emit onPrimaryChanged(); emit byPassWMChanged(); } //!END configuration functions } //!END namespace diff --git a/app/view/view.h b/app/view/view.h index caa9f177..7699971e 100644 --- a/app/view/view.h +++ b/app/view/view.h @@ -1,289 +1,294 @@ /* * 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 . */ #ifndef VIEW_H #define VIEW_H // local #include "effects.h" #include "positioner.h" #include "visibilitymanager.h" +#include "windowstracker.h" #include "settings/primaryconfigview.h" #include "../shortcuts/globalshortcuts.h" #include "../layout/layout.h" #include "../plasma/quick/containmentview.h" #include "../plasma/quick/configview.h" #include "../../liblatte2/types.h" // C++ #include // Qt #include #include #include #include #include #include namespace Plasma { class Types; class Corona; class Containment; } namespace KWayland { namespace Client { class PlasmaShellSurface; } } namespace Latte { class Layout; namespace ViewPart { class ContextMenu; } } namespace Latte { class View : public PlasmaQuick::ContainmentView { Q_OBJECT Q_PROPERTY(bool alternativesIsShown READ alternativesIsShown NOTIFY alternativesIsShownChanged) Q_PROPERTY(bool behaveAsPlasmaPanel READ behaveAsPlasmaPanel WRITE setBehaveAsPlasmaPanel NOTIFY behaveAsPlasmaPanelChanged) Q_PROPERTY(bool byPassWM READ byPassWM WRITE setByPassWM NOTIFY byPassWMChanged) Q_PROPERTY(bool contextMenuIsShown READ contextMenuIsShown NOTIFY contextMenuIsShownChanged) //! Because Latte uses animations, changing to edit mode it may be different than //! when the isUserConfiguring changes value Q_PROPERTY(bool inEditMode READ inEditMode WRITE setInEditMode NOTIFY inEditModeChanged) Q_PROPERTY(bool isPreferredForShortcuts READ isPreferredForShortcuts WRITE setIsPreferredForShortcuts NOTIFY isPreferredForShortcutsChanged) Q_PROPERTY(bool onPrimary READ onPrimary WRITE setOnPrimary NOTIFY onPrimaryChanged) Q_PROPERTY(int alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) Q_PROPERTY(int fontPixelSize READ fontPixelSize WRITE setFontPixelSize NOTIFY fontPixelSizeChanged) Q_PROPERTY(int x READ x NOTIFY xChanged) Q_PROPERTY(int y READ y NOTIFY yChanged) Q_PROPERTY(int width READ width NOTIFY widthChanged) Q_PROPERTY(int height READ height NOTIFY heightChanged) Q_PROPERTY(int maxThickness READ maxThickness WRITE setMaxThickness NOTIFY maxThicknessChanged) Q_PROPERTY(int normalThickness READ normalThickness WRITE setNormalThickness NOTIFY normalThicknessChanged) Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) Q_PROPERTY(float maxLength READ maxLength WRITE setMaxLength NOTIFY maxLengthChanged) Q_PROPERTY(Latte::ViewPart::Effects *effects READ effects NOTIFY effectsChanged) Q_PROPERTY(Layout *managedLayout READ managedLayout WRITE setManagedLayout NOTIFY managedLayoutChanged) Q_PROPERTY(Latte::ViewPart::Positioner *positioner READ positioner NOTIFY positionerChanged) Q_PROPERTY(Latte::ViewPart::VisibilityManager *visibility READ visibility NOTIFY visibilityChanged) + Q_PROPERTY(Latte::ViewPart::WindowsTracker *windowsTracker READ windowsTracker NOTIFY windowsTrackerChanged) Q_PROPERTY(QRect absoluteGeometry READ absGeometry NOTIFY absGeometryChanged) Q_PROPERTY(QRect localGeometry READ localGeometry WRITE setLocalGeometry NOTIFY localGeometryChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry NOTIFY screenGeometryChanged) public: View(Plasma::Corona *corona, QScreen *targetScreen = nullptr, bool byPassWM = false); virtual ~View(); void init(); bool alternativesIsShown() const; void setAlternativesIsShown(bool show); bool inDelete() const; bool onPrimary() const; void setOnPrimary(bool flag); int currentThickness() const; bool behaveAsPlasmaPanel() const; void setBehaveAsPlasmaPanel(bool behavior); bool contextMenuIsShown() const; bool byPassWM() const; void setByPassWM(bool bypass); bool inEditMode() const; void setInEditMode(bool edit); bool isPreferredForShortcuts() const; void setIsPreferredForShortcuts(bool preferred); float maxLength() const; void setMaxLength(float length); int fontPixelSize() const; void setFontPixelSize(int size); int maxThickness() const; void setMaxThickness(int thickness); int normalThickness() const; void setNormalThickness(int thickness); int offset() const; void setOffset(int offset); int alignment() const; void setAlignment(int alignment); QRect absGeometry() const; QRect screenGeometry() const; QRect localGeometry() const; void setLocalGeometry(const QRect &geometry); bool settingsWindowIsShown(); void showSettingsWindow(); ViewPart::Effects *effects() const; ViewPart::Positioner *positioner() const; ViewPart::VisibilityManager *visibility() const; + ViewPart::WindowsTracker *windowsTracker() const; Layout *managedLayout() const; void setManagedLayout(Layout *layout); KWayland::Client::PlasmaShellSurface *surface(); void reconsiderScreen(); //! these are signals that create crashes, such a example is the availableScreenRectChanged from corona //! when its containment is destroyed void disconnectSensitiveSignals(); public slots: Q_INVOKABLE void copyView(); Q_INVOKABLE void removeView(); Q_INVOKABLE QVariantList containmentActions(); Q_INVOKABLE void deactivateApplets(); Q_INVOKABLE void moveToLayout(QString layoutName); Q_INVOKABLE void removeTasksPlasmoid(); Q_INVOKABLE void setBlockHiding(bool block); Q_INVOKABLE void toggleAppletExpanded(const int id); Q_INVOKABLE bool mimeContainsPlasmoid(QMimeData *mimeData, QString name); Q_INVOKABLE bool tasksPresent(); Q_INVOKABLE bool latteTasksPresent(); void updateAbsDockGeometry(bool bypassChecks = false); Q_INVOKABLE void disableGrabItemBehavior(); Q_INVOKABLE void restoreGrabItemBehavior(); Q_INVOKABLE bool isHighestPriorityView(); protected slots: void showConfigurationInterface(Plasma::Applet *applet) override; protected: bool event(QEvent *ev) override; void mousePressEvent(QMouseEvent *event) override; signals: void addInternalViewSplitter(); void removeInternalViewSplitter(); void eventTriggered(QEvent *ev); void activitiesChanged(); void alternativesIsShownChanged(); void alignmentChanged(); void behaveAsPlasmaPanelChanged(); void byPassWMChanged(); void contextMenuIsShownChanged(); void dockLocationChanged(); void effectsChanged(); void fontPixelSizeChanged(); void widthChanged(); void heightChanged(); void inEditModeChanged(); void isPreferredForShortcutsChanged(); void localGeometryChanged(); void managedLayoutChanged(); void maxLengthChanged(); void maxThicknessChanged(); void normalThicknessChanged(); void offsetChanged(); void onPrimaryChanged(); - void visibilityChanged(); void positionerChanged(); void screenGeometryChanged(); + void visibilityChanged(); + void windowsTrackerChanged(); void xChanged(); void yChanged(); void absGeometryChanged(const QRect &geometry); private slots: void availableScreenRectChanged(); void hideWindowsForSlidingOut(); void preferredViewForShortcutsChangedSlot(Latte::View *view); void statusChanged(Plasma::Types::ItemStatus); void restoreConfig(); void saveConfig(); private: void applyActivitiesToWindows(); void initSignalingForLocationChangeSliding(); void setupWaylandIntegration(); void updateAppletContainsMethod(); private: Plasma::Containment *containmentById(uint id); bool m_alternativesIsShown{false}; bool m_behaveAsPlasmaPanel{false}; bool m_byPassWM{true}; bool m_inDelete{false}; bool m_inEditMode{false}; bool m_isPreferredForShortcuts{false}; bool m_onPrimary{true}; int m_fontPixelSize{ -1}; int m_maxThickness{24}; int m_normalThickness{24}; int m_offset{0}; float m_maxLength{1}; Types::Alignment m_alignment{Types::Center}; QRect m_localGeometry; QRect m_absGeometry; Layout *m_managedLayout{nullptr}; QPointer m_configView; QPointer m_contextMenu; QPointer m_effects; QPointer m_positioner; QPointer m_visibility; + QPointer m_windowsTracker; //! Connections to release and bound for the managed layout std::array connectionsManagedLayout; KWayland::Client::PlasmaShellSurface *m_shellSurface{nullptr}; }; } #endif diff --git a/app/view/visibilitymanager.cpp b/app/view/visibilitymanager.cpp index e54e4e94..5b90d69c 100644 --- a/app/view/visibilitymanager.cpp +++ b/app/view/visibilitymanager.cpp @@ -1,1112 +1,834 @@ /* * 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 "screenedgeghostwindow.h" #include "view.h" #include "../lattecorona.h" #include "../layoutmanager.h" #include "../screenpool.h" #include "../wm/windowinfowrap.h" #include "../../liblatte2/extras.h" // Qt #include // KDE #include #include 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()); wm = m_corona->wm(); if (m_latteView) { connect(m_latteView, &Latte::View::absGeometryChanged, this, &VisibilityManager::setViewGeometry); connect(m_latteView, &Latte::View::eventTriggered, this, &VisibilityManager::viewEventManager); } if (m_corona) { connect(this, &VisibilityManager::modeChanged, m_corona, &Plasma::Corona::availableScreenRectChanged); } m_timerStartUp.setInterval(5000); m_timerStartUp.setSingleShot(true); m_timerCheckWindows.setInterval(350); m_timerCheckWindows.setSingleShot(true); m_timerShow.setSingleShot(true); m_timerHide.setSingleShot(true); connect(&m_timerCheckWindows, &QTimer::timeout, this, &VisibilityManager::checkAllWindows); connect(&m_timerShow, &QTimer::timeout, this, [&]() { if (m_isHidden) { // qDebug() << "must be shown"; emit mustBeShown(); } }); connect(&m_timerHide, &QTimer::timeout, this, [&]() { if (!m_blockHiding && !m_isHidden && !dragEnter) { // qDebug() << "must be hide"; emit mustBeHide(); } }); wm->setViewExtraFlags(*m_latteView); wm->addView(m_latteView->winId()); restoreConfig(); } VisibilityManager::~VisibilityManager() { qDebug() << "VisibilityManager deleting..."; wm->removeViewStruts(*m_latteView); wm->removeView(m_latteView->winId()); if (edgeGhostWindow) { edgeGhostWindow->deleteLater(); } } Types::Visibility VisibilityManager::mode() const { return m_mode; } void VisibilityManager::setMode(Latte::Types::Visibility mode) { if (m_mode == mode) return; Q_ASSERT_X(m_mode != Types::None, staticMetaObject.className(), "set visibility to Types::None"); // clear mode for (auto &c : connections) { disconnect(c); } - if (m_mode != Types::DodgeAllWindows && !enabledDynamicBackgroundFlag) { + if (m_mode != Types::DodgeAllWindows) { windows.clear(); } if (m_mode == Types::AlwaysVisible) { wm->removeViewStruts(*m_latteView); } else { connections[3] = connect(wm, &WindowSystem::currentDesktopChanged , this, [&] { if (raiseOnDesktopChange) raiseViewTemporarily(); }); connections[4] = connect(wm, &WindowSystem::currentActivityChanged , this, [&]() { if (raiseOnActivityChange) raiseViewTemporarily(); else updateHiddenState(); }); } m_timerShow.stop(); m_timerHide.stop(); m_timerCheckWindows.stop(); m_mode = mode; switch (m_mode) { case Types::AlwaysVisible: { //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow); } if (m_latteView->containment() && !m_latteView->inEditMode() && m_latteView->screen()) { updateStrutsBasedOnLayoutsAndActivities(); } connections[0] = connect(m_latteView->containment(), &Plasma::Containment::locationChanged , this, [&]() { if (m_latteView->inEditMode()) wm->removeViewStruts(*m_latteView); }); connections[1] = connect(m_latteView, &Latte::View::inEditModeChanged , this, [&]() { if (!m_latteView->inEditMode() && !m_latteView->positioner()->inLocationChangeAnimation() && m_latteView->screen()) wm->setViewStruts(*m_latteView, m_viewGeometry, m_latteView->containment()->location()); }); if (m_corona && m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts) { connections[2] = connect(m_corona->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged, this, [&]() { updateStrutsBasedOnLayoutsAndActivities(); }); connections[3] = connect(m_latteView, &Latte::View::activitiesChanged, this, [&]() { updateStrutsBasedOnLayoutsAndActivities(); }); } raiseView(true); } break; case Types::AutoHide: { //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide); } raiseView(m_containsMouse); } break; case Types::DodgeActive: { //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide); } connections[0] = connect(wm, &WindowSystem::activeWindowChanged , this, &VisibilityManager::dodgeActive); connections[1] = connect(wm, &WindowSystem::windowChanged , this, &VisibilityManager::dodgeActive); dodgeActive(wm->activeWindow()); } break; case Types::DodgeMaximized: { //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide); } connections[0] = connect(wm, &WindowSystem::activeWindowChanged , this, &VisibilityManager::dodgeMaximized); connections[1] = connect(wm, &WindowSystem::windowChanged , this, &VisibilityManager::dodgeMaximized); dodgeMaximized(wm->activeWindow()); } break; case Types::DodgeAllWindows: { //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide); } for (const auto &wid : wm->windows()) { windows.insert(wid, wm->requestInfo(wid)); } connections[0] = connect(wm, &WindowSystem::windowChanged , this, &VisibilityManager::dodgeWindows); connections[1] = connect(wm, &WindowSystem::windowRemoved , this, [&](WindowId wid) { windows.remove(wid); m_timerCheckWindows.start(); }); connections[2] = connect(wm, &WindowSystem::windowAdded , this, [&](WindowId wid) { windows.insert(wid, wm->requestInfo(wid)); m_timerCheckWindows.start(); }); m_timerCheckWindows.start(); } break; case Types::WindowsGoBelow: //set wayland visibility mode if (m_latteView->surface()) { m_latteView->surface()->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow); } break; default: break; } m_latteView->containment()->config().writeEntry("visibility", static_cast(m_mode)); updateKWinEdgesSupport(); emit modeChanged(); } void VisibilityManager::updateStrutsBasedOnLayoutsAndActivities() { bool multipleLayoutsAndCurrent = (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->managedLayout() && !m_latteView->positioner()->inLocationChangeAnimation() && m_latteView->managedLayout()->name() == m_corona->layoutManager()->currentLayoutName()); if (m_corona->layoutManager()->memoryUsage() == Types::SingleLayout || multipleLayoutsAndCurrent) { wm->setViewStruts(*m_latteView, m_viewGeometry, m_latteView->location()); } else { wm->removeViewStruts(*m_latteView); } } bool VisibilityManager::raiseOnDesktop() const { return raiseOnDesktopChange; } void VisibilityManager::setRaiseOnDesktop(bool enable) { if (enable == raiseOnDesktopChange) return; raiseOnDesktopChange = enable; emit raiseOnDesktopChanged(); } bool VisibilityManager::raiseOnActivity() const { return raiseOnActivityChange; } void VisibilityManager::setRaiseOnActivity(bool enable) { if (enable == raiseOnActivityChange) return; raiseOnActivityChange = enable; emit raiseOnActivityChanged(); } 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; if (supportsKWinEdges()) { bool inCurrentLayout = (m_corona->layoutManager()->memoryUsage() == Types::SingleLayout || (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->managedLayout() && !m_latteView->positioner()->inLocationChangeAnimation() && m_latteView->managedLayout()->name() == m_corona->layoutManager()->currentLayoutName())); if (inCurrentLayout) { wm->setEdgeStateFor(edgeGhostWindow, m_isHidden); } else { wm->setEdgeStateFor(edgeGhostWindow, false); } } 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) { m_timerShow.setInterval(msec); emit timerShowChanged(); } int VisibilityManager::timerHide() const { return m_timerHide.interval(); } void VisibilityManager::setTimerHide(int msec) { m_timerHide.setInterval(msec); emit timerHideChanged(); } bool VisibilityManager::supportsKWinEdges() const { return (edgeGhostWindow != nullptr); } void VisibilityManager::raiseView(bool raise) { if (m_blockHiding) return; if (raise) { m_timerHide.stop(); if (!m_timerShow.isActive()) { m_timerShow.start(); } } else if (!dragEnter) { m_timerShow.stop(); if (hideNow) { hideNow = false; emit mustBeHide(); } else if (!m_timerHide.isActive()) { m_timerHide.start(); } } } void VisibilityManager::raiseViewTemporarily() { if (raiseTemporarily) return; raiseTemporarily = true; m_timerHide.stop(); m_timerShow.stop(); if (m_isHidden) emit mustBeShown(); QTimer::singleShot(qBound(1800, 2 * m_timerHide.interval(), 3000), this, [&]() { raiseTemporarily = false; hideNow = true; updateHiddenState(); }); } void VisibilityManager::updateHiddenState() { if (dragEnter) return; switch (m_mode) { case Types::AutoHide: raiseView(m_containsMouse); break; case Types::DodgeActive: dodgeActive(wm->activeWindow()); break; case Types::DodgeMaximized: dodgeMaximized(wm->activeWindow()); break; case Types::DodgeAllWindows: dodgeWindows(wm->activeWindow()); break; default: break; } } void VisibilityManager::setViewGeometry(const QRect &geometry) { if (!m_latteView->containment()) return; m_viewGeometry = geometry; if (m_mode == Types::AlwaysVisible && !m_latteView->inEditMode() && m_latteView->screen()) { updateStrutsBasedOnLayoutsAndActivities(); } } -void VisibilityManager::setWindowOnActivities(QWindow &window, const QStringList &activities) -{ - wm->setWindowOnActivities(window, activities); -} - void VisibilityManager::applyActivitiesToHiddenWindows(const QStringList &activities) { if (edgeGhostWindow) { wm->setWindowOnActivities(*edgeGhostWindow, activities); } } void VisibilityManager::dodgeActive(WindowId wid) { if (raiseTemporarily) return; //!don't send false raiseView signal when containing mouse if (m_containsMouse) { raiseView(true); return; } auto winfo = wm->requestInfo(wid); if (!winfo.isValid() || !winfo.isActive()) { winfo = wm->requestInfo(wm->activeWindow()); if (!winfo.isValid()) { //! very rare case that window manager doesn't have any active window at all raiseView(true); return; } } //! don't send false raiseView signal when containing mouse, // Johan comment //! I don't know why that wasn't winfo.wid() //active window, but just wid//the window that made the call if (wm->isOnCurrentDesktop(winfo.wid()) && wm->isOnCurrentActivity(winfo.wid())) { bool overlaps{intersects(winfo)}; raiseView(!overlaps); } } void VisibilityManager::dodgeMaximized(WindowId wid) { if (raiseTemporarily) return; //!don't send false raiseView signal when containing mouse if (m_containsMouse) { raiseView(true); return; } auto winfo = wm->requestInfo(wid); if (!winfo.isValid() || !winfo.isActive()) { winfo = wm->requestInfo(wm->activeWindow()); if (!winfo.isValid()) { //! very rare case that window manager doesn't have any active window at all raiseView(true); return; } } auto intersectsMaxVert = [&]() noexcept -> bool { return ((winfo.isMaxVert() || (m_latteView->screen() && m_latteView->screen()->availableSize().height() <= winfo.geometry().height())) && intersects(winfo)); }; auto intersectsMaxHoriz = [&]() noexcept -> bool { return ((winfo.isMaxHoriz() || (m_latteView->screen() && m_latteView->screen()->availableSize().width() <= winfo.geometry().width())) && intersects(winfo)); }; //! don't send false raiseView signal when containing mouse, // Johan comment //! I don't know why that wasn't winfo.wid() //active window, but just wid//the window that made the call if (wm->isOnCurrentDesktop(winfo.wid()) && wm->isOnCurrentActivity(winfo.wid())) { bool overlapsMaximized{m_latteView->formFactor() == Plasma::Types::Vertical ? intersectsMaxHoriz() : intersectsMaxVert()}; raiseView(!overlapsMaximized); } } void VisibilityManager::dodgeWindows(WindowId wid) { if (raiseTemporarily) return; if (windows.find(wid) == std::end(windows)) return; //!don't send false raiseView signal when containing mouse if (m_containsMouse) { raiseView(true); return; } windows[wid] = wm->requestInfo(wid); auto &winfo = windows[wid]; if (!winfo.isValid() || !wm->isOnCurrentDesktop(wid) || !wm->isOnCurrentActivity(wid)) return; if (intersects(winfo)) raiseView(false); else m_timerCheckWindows.start(); } void VisibilityManager::checkAllWindows() { if (raiseTemporarily) return; bool raise{true}; bool existsFaultyWindow{false}; for (const auto &winfo : windows) { // if (winfo.geometry() == QRect(0, 0, 0, 0)) { existsFaultyWindow = true; } if (!winfo.isValid() || !wm->isOnCurrentDesktop(winfo.wid()) || !wm->isOnCurrentActivity(winfo.wid())) continue; if (winfo.isFullscreen()) { raise = false; break; } else if (intersects(winfo)) { raise = false; break; } } cleanupFaultyWindows(); raiseView(raise); } bool VisibilityManager::intersects(const WindowInfoWrap &winfo) { return (!winfo.isMinimized() && wm->isOnCurrentDesktop(winfo.wid()) && wm->isOnCurrentActivity(winfo.wid()) && winfo.geometry().intersects(m_viewGeometry) && !winfo.isShaded()); } void VisibilityManager::saveConfig() { if (!m_latteView->containment()) return; auto config = m_latteView->containment()->config(); config.writeEntry("enableKWinEdges", enableKWinEdgesFromUser); config.writeEntry("timerShow", m_timerShow.interval()); config.writeEntry("timerHide", m_timerHide.interval()); config.writeEntry("raiseOnDesktopChange", raiseOnDesktopChange); config.writeEntry("raiseOnActivityChange", 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(config.readEntry("timerHide", 700)); emit timerShowChanged(); emit timerHideChanged(); 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, [&]() { 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(); if (contains && m_mode != Types::AlwaysVisible) { raiseView(true); } } void VisibilityManager::viewEventManager(QEvent *ev) { switch (ev->type()) { case QEvent::Enter: setContainsMouse(true); break; case QEvent::Leave: setContainsMouse(false); updateHiddenState(); break; case QEvent::DragEnter: dragEnter = true; if (m_isHidden) emit mustBeShown(); break; case QEvent::DragLeave: case QEvent::Drop: dragEnter = false; updateHiddenState(); break; case QEvent::Show: wm->setViewExtraFlags(*m_latteView); break; default: break; } } void VisibilityManager::cleanupFaultyWindows() { foreach (auto key, windows.keys()) { auto winfo = windows[key]; //! garbage windows removing if (winfo.geometry() == QRect(0, 0, 0, 0)) { //qDebug() << "Faulty Geometry ::: " << winfo.wid(); windows.remove(key); } } } -//! Dynamic Background functions -bool VisibilityManager::enabledDynamicBackground() const -{ - return enabledDynamicBackgroundFlag; -} - -void VisibilityManager::setEnabledDynamicBackground(bool active) -{ - if (enabledDynamicBackgroundFlag == active) { - return; - } - - enabledDynamicBackgroundFlag = active; - - if (active) { - if (m_mode != Types::DodgeAllWindows) { - for (const auto &wid : wm->windows()) { - windows.insert(wid, wm->requestInfo(wid)); - } - } - - connectionsDynBackground[0] = connect(m_latteView->corona(), &Plasma::Corona::availableScreenRectChanged, - this, &VisibilityManager::updateAvailableScreenGeometry); - - connectionsDynBackground[1] = connect(wm, &WindowSystem::windowChanged, this, [&](WindowId wid) { - windows[wid] = wm->requestInfo(wid); - updateDynamicBackgroundWindowFlags(); - }); - - connectionsDynBackground[2] = connect(wm, &WindowSystem::windowRemoved, this, [&](WindowId wid) { - windows.remove(wid); - }); - - connectionsDynBackground[3] = connect(wm, &WindowSystem::windowAdded, this, [&](WindowId wid) { - windows.insert(wid, wm->requestInfo(wid)); - updateDynamicBackgroundWindowFlags(); - }); - - connectionsDynBackground[4] = connect(wm, &WindowSystem::activeWindowChanged, this, [&](WindowId wid) { - if (windows.contains(lastActiveWindowWid)) { - windows[lastActiveWindowWid] = wm->requestInfo(lastActiveWindowWid); - } - - windows[wid] = wm->requestInfo(wid); - lastActiveWindowWid = wid; - - updateDynamicBackgroundWindowFlags(); - }); - - connectionsDynBackground[5] = connect(wm, &WindowSystem::currentDesktopChanged, this, [&] { - updateDynamicBackgroundWindowFlags(); - }); - - connectionsDynBackground[6] = connect(wm, &WindowSystem::currentActivityChanged, this, [&] { - updateDynamicBackgroundWindowFlags(); - }); - - updateAvailableScreenGeometry(); - updateDynamicBackgroundWindowFlags(); - } else { - // clear mode - for (auto &c : connectionsDynBackground) { - disconnect(c); - } - - if (m_mode != Types::DodgeAllWindows) { - windows.clear(); - } - - // ATTENTION: this was creating a crash under wayland environment through the blur effect - // setExistsWindowMaximized(false); - // setExistsWindowTouching(false); - } - - emit enabledDynamicBackgroundChanged(); -} - -bool VisibilityManager::existsWindowMaximized() const -{ - return windowIsMaximizedFlag; -} - -void VisibilityManager::setExistsWindowMaximized(bool windowMaximized) -{ - if (windowIsMaximizedFlag == windowMaximized) { - return; - } - - windowIsMaximizedFlag = windowMaximized; - - emit existsWindowMaximizedChanged(); -} - -bool VisibilityManager::existsWindowTouching() const -{ - return windowIsTouchingFlag; -} - -void VisibilityManager::setExistsWindowTouching(bool windowTouching) -{ - if (windowIsTouchingFlag == windowTouching) { - return; - } - - windowIsTouchingFlag = windowTouching; - - emit existsWindowTouchingChanged(); -} - -SchemeColors *VisibilityManager::touchingWindowScheme() const -{ - return touchingScheme; -} - -void VisibilityManager::setTouchingWindowScheme(SchemeColors *scheme) -{ - if (touchingScheme == scheme) { - return; - } - - touchingScheme = scheme; - - emit touchingWindowSchemeChanged(); -} - -void VisibilityManager::updateAvailableScreenGeometry() -{ - if (!m_latteView || !m_latteView->containment()) { - return; - } - - int currentScrId = m_latteView->positioner()->currentScreenId(); - QRect tempAvailableScreenGeometry = m_corona->availableScreenRectWithCriteria(currentScrId, {Types::AlwaysVisible}, {}); - - if (tempAvailableScreenGeometry != availableScreenGeometry) { - availableScreenGeometry = tempAvailableScreenGeometry; - - updateDynamicBackgroundWindowFlags(); - } -} - -bool VisibilityManager::isMaximizedInCurrentScreen(const WindowInfoWrap &winfo) -{ - //! updated implementation to identify the screen that the maximized window is present - //! in order to avoid: https://bugs.kde.org/show_bug.cgi?id=397700 - - if (winfo.isValid() && !winfo.isMinimized() && wm->isOnCurrentDesktop(winfo.wid()) && wm->isOnCurrentActivity(winfo.wid())) { - if (winfo.isMaximized() && availableScreenGeometry.contains(winfo.geometry().center())) { - return true; - } - } - - return false; -} - -bool VisibilityManager::isTouchingPanelEdge(const WindowInfoWrap &winfo) -{ - if (winfo.isValid() && !winfo.isMinimized() && wm->isOnCurrentDesktop(winfo.wid()) && wm->isOnCurrentActivity(winfo.wid())) { - bool touchingPanelEdge{false}; - - QRect screenGeometry = m_latteView->screenGeometry(); - bool inCurrentScreen{screenGeometry.contains(winfo.geometry().topLeft()) || screenGeometry.contains(winfo.geometry().bottomRight())}; - - if (inCurrentScreen) { - if (m_latteView->location() == Plasma::Types::TopEdge) { - touchingPanelEdge = (winfo.geometry().y() == availableScreenGeometry.y()); - } else if (m_latteView->location() == Plasma::Types::BottomEdge) { - touchingPanelEdge = (winfo.geometry().bottom() == availableScreenGeometry.bottom()); - } else if (m_latteView->location() == Plasma::Types::LeftEdge) { - touchingPanelEdge = (winfo.geometry().x() == availableScreenGeometry.x()); - } else if (m_latteView->location() == Plasma::Types::RightEdge) { - touchingPanelEdge = (winfo.geometry().right() == availableScreenGeometry.right()); - } - } - - return touchingPanelEdge; - } - - return false; -} - -void VisibilityManager::updateDynamicBackgroundWindowFlags() -{ - bool foundTouch{false}; - bool foundMaximized{false}; - - //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), - //! maybe a garbage collector here is a good idea!!! - bool existsFaultyWindow{false}; - WindowId maxWinId; - WindowId touchWinId; - - for (const auto &winfo : windows) { - if (isMaximizedInCurrentScreen(winfo)) { - foundMaximized = true; - maxWinId = winfo.wid(); - } - - if (winfo.isActive() && (isTouchingPanelEdge(winfo) || (intersects(winfo)))) { - foundTouch = true; - touchWinId = winfo.wid(); - } - - if (!existsFaultyWindow && winfo.geometry() == QRect(0, 0, 0, 0)) { - existsFaultyWindow = true; - } - - //qDebug() << "window geometry ::: " << winfo.geometry(); - } - - if (existsFaultyWindow) { - cleanupFaultyWindows(); - } - - setExistsWindowMaximized(foundMaximized); - setExistsWindowTouching(foundTouch); - - //! update color scheme for touching window - - if (foundTouch) { - //! first the touching one because that would mean it is active - setTouchingWindowScheme(wm->schemeForWindow(touchWinId)); - } else if (foundMaximized) { - setTouchingWindowScheme(wm->schemeForWindow(maxWinId)); - } else { - setTouchingWindowScheme(nullptr); - } -} - //! KWin Edges Support functions bool VisibilityManager::enableKWinEdges() const { return enableKWinEdgesFromUser; } void VisibilityManager::setEnableKWinEdges(bool enable) { if (enableKWinEdgesFromUser == enable) { return; } enableKWinEdgesFromUser = enable; emit enableKWinEdgesChanged(); updateKWinEdgesSupport(); } void VisibilityManager::updateKWinEdgesSupport() { if (m_mode == Types::AutoHide || m_mode == Types::DodgeActive || m_mode == Types::DodgeAllWindows || m_mode == Types::DodgeMaximized) { if (enableKWinEdgesFromUser) { createEdgeGhostWindow(); } else if (!enableKWinEdgesFromUser) { deleteEdgeGhostWindow(); } } else if (m_mode == Types::AlwaysVisible || m_mode == Types::WindowsGoBelow) { deleteEdgeGhostWindow(); } } void VisibilityManager::createEdgeGhostWindow() { if (!edgeGhostWindow) { edgeGhostWindow = new ScreenEdgeGhostWindow(m_latteView); wm->setViewExtraFlags(*edgeGhostWindow); connect(edgeGhostWindow, &ScreenEdgeGhostWindow::containsMouseChanged, this, [ = ](bool contains) { if (contains) { emit mustBeShown(); } }); connectionsKWinEdges[0] = connect(wm, &WindowSystem::currentActivityChanged, this, [&]() { bool inCurrentLayout = (m_corona->layoutManager()->memoryUsage() == Types::SingleLayout || (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts && m_latteView->managedLayout() && !m_latteView->positioner()->inLocationChangeAnimation() && m_latteView->managedLayout()->name() == m_corona->layoutManager()->currentLayoutName())); if (edgeGhostWindow) { if (inCurrentLayout) { wm->setEdgeStateFor(edgeGhostWindow, m_isHidden); } else { wm->setEdgeStateFor(edgeGhostWindow, false); } } }); emit supportsKWinEdgesChanged(); } } void VisibilityManager::deleteEdgeGhostWindow() { if (edgeGhostWindow) { edgeGhostWindow->deleteLater(); edgeGhostWindow = nullptr; for (auto &c : connectionsKWinEdges) { disconnect(c); } emit supportsKWinEdgesChanged(); } } -//! Window Functions -void VisibilityManager::requestToggleMaximizeForActiveWindow() -{ - WindowInfoWrap actInfo = wm->requestInfoActive(); - - //active window can be toggled only when it is in the same screen - if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { - wm->requestToggleMaximized(actInfo.wid()); - } -} - -void VisibilityManager::requestMoveActiveWindow(int localX, int localY) -{ - WindowInfoWrap actInfo = wm->requestInfoActive(); - - //active window can be dragged only when it is in the same screen - if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { - QPoint globalPoint{m_latteView->x() + localX, m_latteView->y() + localY}; - - wm->requestMoveWindow(actInfo.wid(), globalPoint); - - //! This timer is needed because otherwise the mouse position - //! in the dragged window changes to TopLeft corner - QTimer::singleShot(250, this, [&, actInfo, globalPoint]() { - wm->releaseMouseEventFor(m_latteView->winId()); - }); - - setContainsMouse(false); - updateHiddenState(); - } -} - -bool VisibilityManager::activeWindowCanBeDragged() -{ - WindowInfoWrap actInfo = wm->requestInfoActive(); - - //active window can be dragged only when it is in the same screen - if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { - return wm->windowCanBeDragged(actInfo.wid()); - } - - return false; -} - //! END: VisibilityManager implementation } } diff --git a/app/view/visibilitymanager.h b/app/view/visibilitymanager.h index dc4d7611..2bb7a5de 100644 --- a/app/view/visibilitymanager.h +++ b/app/view/visibilitymanager.h @@ -1,230 +1,185 @@ /* * 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 . */ #ifndef VISIBILITYMANAGER_H #define VISIBILITYMANAGER_H // local #include "../plasma/quick/containmentview.h" #include "../schemecolors.h" #include "../wm/abstractwindowinterface.h" #include "../wm/windowinfowrap.h" #include "../../liblatte2/types.h" // Qt #include #include // Plasma #include namespace Latte { class Corona; class View; namespace ViewPart { class ScreenEdgeGhostWindow; } } namespace Latte { namespace ViewPart { class VisibilityManager : public QObject { Q_OBJECT Q_PROPERTY(Latte::Types::Visibility mode READ mode WRITE setMode NOTIFY modeChanged) Q_PROPERTY(bool raiseOnDesktop READ raiseOnDesktop WRITE setRaiseOnDesktop NOTIFY raiseOnDesktopChanged) Q_PROPERTY(bool raiseOnActivity READ raiseOnActivity WRITE setRaiseOnActivity NOTIFY raiseOnActivityChanged) Q_PROPERTY(bool isHidden READ isHidden WRITE setIsHidden NOTIFY isHiddenChanged) Q_PROPERTY(bool blockHiding READ blockHiding WRITE setBlockHiding NOTIFY blockHidingChanged) Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged) - //! Dynamic Background Feature (needed options) - Q_PROPERTY(bool enabledDynamicBackground READ enabledDynamicBackground WRITE setEnabledDynamicBackground NOTIFY enabledDynamicBackgroundChanged) - Q_PROPERTY(bool existsWindowMaximized READ existsWindowMaximized NOTIFY existsWindowMaximizedChanged) - Q_PROPERTY(bool existsWindowTouching READ existsWindowTouching NOTIFY existsWindowTouchingChanged) - Q_PROPERTY(SchemeColors *touchingWindowScheme READ touchingWindowScheme NOTIFY touchingWindowSchemeChanged) - //! KWin Edges Support Options Q_PROPERTY(bool enableKWinEdges READ enableKWinEdges WRITE setEnableKWinEdges NOTIFY enableKWinEdgesChanged) Q_PROPERTY(bool supportsKWinEdges READ supportsKWinEdges NOTIFY supportsKWinEdgesChanged) Q_PROPERTY(int timerShow READ timerShow WRITE setTimerShow NOTIFY timerShowChanged) Q_PROPERTY(int timerHide READ timerHide WRITE setTimerHide NOTIFY timerHideChanged) public: explicit VisibilityManager(PlasmaQuick::ContainmentView *view); virtual ~VisibilityManager(); Latte::Types::Visibility mode() const; void setMode(Latte::Types::Visibility mode); - void setWindowOnActivities(QWindow &window, const QStringList &activities); void applyActivitiesToHiddenWindows(const QStringList &activities); bool raiseOnDesktop() const; void setRaiseOnDesktop(bool enable); bool raiseOnActivity() const; void setRaiseOnActivity(bool enable); bool isHidden() const; void setIsHidden(bool isHidden); bool blockHiding() const; void setBlockHiding(bool blockHiding); bool containsMouse() const; int timerShow() const; void setTimerShow(int msec); int timerHide() const; void setTimerHide(int msec); - //! Dynamic Background functions - bool enabledDynamicBackground() const; - void setEnabledDynamicBackground(bool active); - - bool existsWindowMaximized() const; - bool existsWindowTouching() const; - - SchemeColors *touchingWindowScheme() const; + bool intersects(const WindowInfoWrap &winfo); //! KWin Edges Support functions bool enableKWinEdges() const; void setEnableKWinEdges(bool enable); bool supportsKWinEdges() const; -public slots: - Q_INVOKABLE void requestToggleMaximizeForActiveWindow(); - Q_INVOKABLE void requestMoveActiveWindow(int localX, int localY); - Q_INVOKABLE bool activeWindowCanBeDragged(); - signals: void mustBeShown(); void mustBeHide(); void modeChanged(); void raiseOnDesktopChanged(); void raiseOnActivityChanged(); void isHiddenChanged(); void blockHidingChanged(); void containsMouseChanged(); void timerShowChanged(); void timerHideChanged(); void touchingWindowSchemeChanged(); - //! Dynamic Background signals (from options) - void enabledDynamicBackgroundChanged(); - void existsWindowMaximizedChanged(); - void existsWindowTouchingChanged(); - //! KWin Edges Support signals void enableKWinEdgesChanged(); void supportsKWinEdgesChanged(); private slots: void saveConfig(); void restoreConfig(); private: void setContainsMouse(bool contains); void raiseView(bool raise); void raiseViewTemporarily(); void updateHiddenState(); //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), //! this is a garbage collector to collect such windows in order to not break the windows array validity. void cleanupFaultyWindows(); - //! Dynamic Background Feature - void setExistsWindowMaximized(bool windowMaximized); - void setExistsWindowTouching(bool windowTouching); - void setTouchingWindowScheme(SchemeColors *scheme); - void updateAvailableScreenGeometry(); - void updateDynamicBackgroundWindowFlags(); - //! KWin Edges Support functions void createEdgeGhostWindow(); void deleteEdgeGhostWindow(); void updateKWinEdgesSupport(); void setViewGeometry(const QRect &rect); void windowAdded(WindowId id); void dodgeActive(WindowId id); void dodgeMaximized(WindowId id); void dodgeWindows(WindowId id); void checkAllWindows(); - bool intersects(const WindowInfoWrap &winfo); - bool isMaximizedInCurrentScreen(const WindowInfoWrap &winfo); - bool isTouchingPanelEdge(const WindowInfoWrap &winfo); - void updateStrutsBasedOnLayoutsAndActivities(); void viewEventManager(QEvent *ev); private: AbstractWindowInterface *wm; Types::Visibility m_mode{Types::None}; std::array connections; QMap windows; QTimer m_timerShow; QTimer m_timerHide; QTimer m_timerCheckWindows; QTimer m_timerStartUp; QRect m_viewGeometry; bool m_isHidden{false}; bool dragEnter{false}; bool m_blockHiding{false}; bool m_containsMouse{false}; bool raiseTemporarily{false}; bool raiseOnDesktopChange{false}; bool raiseOnActivityChange{false}; bool hideNow{false}; - //! Dynamic Background flags and needed information - bool enabledDynamicBackgroundFlag{false}; - bool windowIsTouchingFlag{false}; - bool windowIsMaximizedFlag{false}; - QRect availableScreenGeometry; - std::array connectionsDynBackground; - WindowId lastActiveWindowWid; - SchemeColors *touchingScheme{nullptr}; - - //! KWin Edges bool enableKWinEdgesFromUser{true}; std::array connectionsKWinEdges; ScreenEdgeGhostWindow *edgeGhostWindow{nullptr}; Latte::Corona *m_corona{nullptr}; Latte::View *m_latteView{nullptr}; }; } } #endif // VISIBILITYMANAGER_H diff --git a/app/view/windowstracker.cpp b/app/view/windowstracker.cpp new file mode 100644 index 00000000..01f18c67 --- /dev/null +++ b/app/view/windowstracker.cpp @@ -0,0 +1,331 @@ +/* +* Copyright 2019 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 "windowstracker.h" + +// local +#include "positioner.h" +#include "view.h" +#include "../lattecorona.h" +#include "../../liblatte2/types.h" + +namespace Latte { +namespace ViewPart { + +WindowsTracker::WindowsTracker(Latte::View *parent) + : QObject(parent), + m_latteView(parent) +{ + qDebug() << "WindowsTracker creating..."; + + m_corona = qobject_cast(m_latteView->corona()); + m_wm = m_corona->wm(); +} + +WindowsTracker::~WindowsTracker() +{ + qDebug() << "WindowsTracker removing..."; +} + +bool WindowsTracker::existsWindowMaximized() const +{ + return m_windowIsMaximizedFlag; +} + +void WindowsTracker::setExistsWindowMaximized(bool windowMaximized) +{ + if (m_windowIsMaximizedFlag == windowMaximized) { + return; + } + + m_windowIsMaximizedFlag = windowMaximized; + + emit existsWindowMaximizedChanged(); +} + +bool WindowsTracker::existsWindowTouching() const +{ + return m_windowIsTouchingFlag; +} + +void WindowsTracker::setExistsWindowTouching(bool windowTouching) +{ + if (m_windowIsTouchingFlag == windowTouching) { + return; + } + + m_windowIsTouchingFlag = windowTouching; + + emit existsWindowTouchingChanged(); +} + +SchemeColors *WindowsTracker::touchingWindowScheme() const +{ + return m_touchingScheme; +} + +void WindowsTracker::setTouchingWindowScheme(SchemeColors *scheme) +{ + if (m_touchingScheme == scheme) { + return; + } + + m_touchingScheme = scheme; + + emit touchingWindowSchemeChanged(); +} + +bool WindowsTracker::enabled() const +{ + return m_enabled; +} + +void WindowsTracker::setEnabled(bool active) +{ + if (m_enabled == active) { + return; + } + + m_enabled = active; + + if (m_enabled) { + m_windows.clear(); + + for (const auto &wid : m_wm->windows()) { + m_windows.insert(wid, m_wm->requestInfo(wid)); + } + + m_connections[0] = connect(m_corona, &Plasma::Corona::availableScreenRectChanged, + this, &WindowsTracker::updateAvailableScreenGeometry); + + m_connections[1] = connect(m_wm, &WindowSystem::windowChanged, this, [&](WindowId wid) { + m_windows[wid] = m_wm->requestInfo(wid); + updateFlags(); + }); + + m_connections[2] = connect(m_wm, &WindowSystem::windowRemoved, this, [&](WindowId wid) { + m_windows.remove(wid); + }); + + m_connections[3] = connect(m_wm, &WindowSystem::windowAdded, this, [&](WindowId wid) { + m_windows.insert(wid, m_wm->requestInfo(wid)); + updateFlags(); + }); + + m_connections[4] = connect(m_wm, &WindowSystem::activeWindowChanged, this, [&](WindowId wid) { + if (m_windows.contains(m_lastActiveWindowWid)) { + m_windows[m_lastActiveWindowWid] = m_wm->requestInfo(m_lastActiveWindowWid); + } + + m_windows[wid] = m_wm->requestInfo(wid); + m_lastActiveWindowWid = wid; + + updateFlags(); + }); + + m_connections[5] = connect(m_wm, &WindowSystem::currentDesktopChanged, this, [&] { + updateFlags(); + }); + + m_connections[6] = connect(m_wm, &WindowSystem::currentActivityChanged, this, [&] { + updateFlags(); + }); + + updateAvailableScreenGeometry(); + updateFlags(); + } else { + // clear mode + for (auto &c : m_connections) { + disconnect(c); + } + + m_windows.clear(); + } + + emit enabledChanged(); +} + +void WindowsTracker::updateAvailableScreenGeometry() +{ + if (!m_latteView || !m_latteView->containment()) { + return; + } + + int currentScrId = m_latteView->positioner()->currentScreenId(); + QRect tempAvailableScreenGeometry = m_corona->availableScreenRectWithCriteria(currentScrId, {Types::AlwaysVisible}, {}); + + if (tempAvailableScreenGeometry != m_availableScreenGeometry) { + m_availableScreenGeometry = tempAvailableScreenGeometry; + + updateFlags(); + } +} + +void WindowsTracker::updateFlags() +{ + bool foundTouch{false}; + bool foundMaximized{false}; + + //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), + //! maybe a garbage collector here is a good idea!!! + bool existsFaultyWindow{false}; + WindowId maxWinId; + WindowId touchWinId; + + for (const auto &winfo : m_windows) { + if (isMaximizedInCurrentScreen(winfo)) { + foundMaximized = true; + maxWinId = winfo.wid(); + } + + if (winfo.isActive() && (isTouchingPanelEdge(winfo) || (m_latteView->visibility()->intersects(winfo)))) { + foundTouch = true; + touchWinId = winfo.wid(); + } + + if (!existsFaultyWindow && winfo.geometry() == QRect(0, 0, 0, 0)) { + existsFaultyWindow = true; + } + + //qDebug() << "window geometry ::: " << winfo.geometry(); + } + + if (existsFaultyWindow) { + cleanupFaultyWindows(); + } + + setExistsWindowMaximized(foundMaximized); + setExistsWindowTouching(foundTouch); + + //! update color scheme for touching window + + if (foundTouch) { + //! first the touching one because that would mean it is active + setTouchingWindowScheme(m_wm->schemeForWindow(touchWinId)); + } else if (foundMaximized) { + setTouchingWindowScheme(m_wm->schemeForWindow(maxWinId)); + } else { + setTouchingWindowScheme(nullptr); + } +} + +bool WindowsTracker::isMaximizedInCurrentScreen(const WindowInfoWrap &winfo) +{ + //! updated implementation to identify the screen that the maximized window is present + //! in order to avoid: https://bugs.kde.org/show_bug.cgi?id=397700 + + if (winfo.isValid() && !winfo.isMinimized() && m_wm->isOnCurrentDesktop(winfo.wid()) && m_wm->isOnCurrentActivity(winfo.wid())) { + if (winfo.isMaximized() && m_availableScreenGeometry.contains(winfo.geometry().center())) { + return true; + } + } + + return false; +} + +bool WindowsTracker::isTouchingPanelEdge(const WindowInfoWrap &winfo) +{ + if (winfo.isValid() && !winfo.isMinimized() && m_wm->isOnCurrentDesktop(winfo.wid()) && m_wm->isOnCurrentActivity(winfo.wid())) { + bool touchingPanelEdge{false}; + + QRect screenGeometry = m_latteView->screenGeometry(); + bool inCurrentScreen{screenGeometry.contains(winfo.geometry().topLeft()) || screenGeometry.contains(winfo.geometry().bottomRight())}; + + if (inCurrentScreen) { + if (m_latteView->location() == Plasma::Types::TopEdge) { + touchingPanelEdge = (winfo.geometry().y() == m_availableScreenGeometry.y()); + } else if (m_latteView->location() == Plasma::Types::BottomEdge) { + touchingPanelEdge = (winfo.geometry().bottom() == m_availableScreenGeometry.bottom()); + } else if (m_latteView->location() == Plasma::Types::LeftEdge) { + touchingPanelEdge = (winfo.geometry().x() == m_availableScreenGeometry.x()); + } else if (m_latteView->location() == Plasma::Types::RightEdge) { + touchingPanelEdge = (winfo.geometry().right() == m_availableScreenGeometry.right()); + } + } + + return touchingPanelEdge; + } + + return false; +} + +void WindowsTracker::cleanupFaultyWindows() +{ + foreach (auto key, m_windows.keys()) { + auto winfo = m_windows[key]; + + //! garbage windows removing + if (winfo.geometry() == QRect(0, 0, 0, 0)) { + //qDebug() << "Faulty Geometry ::: " << winfo.wid(); + m_windows.remove(key); + } + } +} + +//! Window Functions +void WindowsTracker::setWindowOnActivities(QWindow &window, const QStringList &activities) +{ + m_wm->setWindowOnActivities(window, activities); +} + +void WindowsTracker::requestToggleMaximizeForActiveWindow() +{ + WindowInfoWrap actInfo = m_wm->requestInfoActive(); + + //active window can be toggled only when it is in the same screen + if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { + m_wm->requestToggleMaximized(actInfo.wid()); + } +} + +void WindowsTracker::requestMoveActiveWindow(int localX, int localY) +{ + WindowInfoWrap actInfo = m_wm->requestInfoActive(); + + //active window can be dragged only when it is in the same screen + if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { + QPoint globalPoint{m_latteView->x() + localX, m_latteView->y() + localY}; + + m_wm->requestMoveWindow(actInfo.wid(), globalPoint); + + //! This timer is needed because otherwise the mouse position + //! in the dragged window changes to TopLeft corner + QTimer::singleShot(250, this, [&, actInfo, globalPoint]() { + m_wm->releaseMouseEventFor(m_latteView->winId()); + }); + + // setContainsMouse(false); + // updateHiddenState(); + } +} + +bool WindowsTracker::activeWindowCanBeDragged() +{ + WindowInfoWrap actInfo = m_wm->requestInfoActive(); + + //active window can be dragged only when it is in the same screen + if (actInfo.isValid() && !actInfo.geometry().isNull() && m_latteView->screenGeometry().contains(actInfo.geometry().center())) { + return m_wm->windowCanBeDragged(actInfo.wid()); + } + + return false; +} + +} +} diff --git a/app/view/windowstracker.h b/app/view/windowstracker.h new file mode 100644 index 00000000..86972389 --- /dev/null +++ b/app/view/windowstracker.h @@ -0,0 +1,108 @@ +/* +* Copyright 2019 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 WINDOWSTRACKER_H +#define WINDOWSTRACKER_H + +// local +#include "../schemecolors.h" +#include "../wm/abstractwindowinterface.h" +#include "../wm/windowinfowrap.h" + +// Qt +#include + +namespace Latte{ + class AbstractWindowInterface; + class Corona; + class View; +} + +namespace Latte { +namespace ViewPart { + +class WindowsTracker : public QObject { + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + Q_PROPERTY(bool existsWindowMaximized READ existsWindowMaximized NOTIFY existsWindowMaximizedChanged) + Q_PROPERTY(bool existsWindowTouching READ existsWindowTouching NOTIFY existsWindowTouchingChanged) + Q_PROPERTY(SchemeColors *touchingWindowScheme READ touchingWindowScheme NOTIFY touchingWindowSchemeChanged) + +public: + explicit WindowsTracker(Latte::View *parent); + virtual ~WindowsTracker(); + + bool enabled() const; + void setEnabled(bool active); + + bool existsWindowMaximized() const; + bool existsWindowTouching() const; + + SchemeColors *touchingWindowScheme() const; + + void setWindowOnActivities(QWindow &window, const QStringList &activities); + +signals: + void enabledChanged(); + void existsWindowMaximizedChanged(); + void existsWindowTouchingChanged(); + void touchingWindowSchemeChanged(); + +public slots: + Q_INVOKABLE void requestToggleMaximizeForActiveWindow(); + Q_INVOKABLE void requestMoveActiveWindow(int localX, int localY); + Q_INVOKABLE bool activeWindowCanBeDragged(); + +private: + void setExistsWindowMaximized(bool windowMaximized); + void setExistsWindowTouching(bool windowTouching); + void setTouchingWindowScheme(SchemeColors *scheme); + void updateAvailableScreenGeometry(); + void updateFlags(); + + //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), + //! this is a garbage collector to collect such windows in order to not break the windows array validity. + void cleanupFaultyWindows(); + + bool isMaximizedInCurrentScreen(const WindowInfoWrap &winfo); + bool isTouchingPanelEdge(const WindowInfoWrap &winfo); + +private: + bool m_enabled{false}; + bool m_windowIsTouchingFlag{false}; + bool m_windowIsMaximizedFlag{false}; + + QRect m_availableScreenGeometry; + + WindowId m_lastActiveWindowWid; + + std::array m_connections; + QMap m_windows; + + SchemeColors *m_touchingScheme{nullptr}; + + Latte::AbstractWindowInterface *m_wm; + Latte::Corona *m_corona{nullptr}; + Latte::View *m_latteView{nullptr}; +}; + +} +} + +#endif