diff --git a/CMakeLists.txt b/CMakeLists.txt index ebdd9ad32..55ce49bc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(VERSION 0.10.76) +set(VERSION 0.10.77) set(AUTHOR "Michail Vourlakos, Smith Ar") set(EMAIL "mvourlakos@gmail.com, audoban@openmailbox.org") set(WEBSITE "https://userbase.kde.org/LatteDock") @@ -11,7 +11,7 @@ set(BUG_ADDRESS "https://bugs.kde.org/enter_bug.cgi?product=lattedock") set(FAQS "https://userbase.kde.org/LatteDock/FAQ") set(QT_MIN_VERSION "5.15.0") -set(KF5_MIN_VERSION "5.71.0") +set(KF5_MIN_VERSION "5.82.0") set(KF5_LOCALE_PREFIX "") @@ -46,6 +46,12 @@ if(X11_FOUND AND XCB_XCB_FOUND) set(HAVE_X11 ON) endif() + +find_package(QtWaylandScanner REQUIRED) +find_package(Qt${QT_MAJOR_VERSION}WaylandClient) +find_package(PlasmaWaylandProtocols 1.6 REQUIRED) +find_package(Wayland REQUIRED COMPONENTS Client) + string(REGEX MATCH "\\.([^]]+)\\." KF5_VERSION_MINOR ${KF5_VERSION}) string(REGEX REPLACE "\\." "" KF5_VERSION_MINOR ${KF5_VERSION_MINOR}) diff --git a/README.md b/README.md index fc83caa86..1a3a68a2f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ Installation ## Requirements We recommend to use at least: - **Plasma >= 5.18.0** + **Plasma >= 5.24.0** + **PlasmaWaylandProtocols >= 1.6.0** **Qt >= 5.15** @@ -41,28 +42,28 @@ Minimum requirements: Qt5Gui >= 5.15.0 Qt5Dbus >= 5.15.0 - KF5Plasma >= 5.71.0 - KF5PlasmaQuick >= 5.71.0 - KF5Activities >= 5.71.0 - KF5CoreAddons >= 5.71.0 - KF5GuiAddons >= 5.71.0 - KF5DBusAddons >= 5.71.0 - KF5Declarative >= 5.71.0 - KF5Kirigami2 >= 5.71.0 - KF5Wayland >= 5.71.0 - KF5Package >= 5.71.0 - KF5XmlGui >= 5.71.0 - KF5IconThemes >= 5.71.0 - KF5KIO >= 5.71.0 - KF5I18n >= 5.71.0 - KF5Notifications >= 5.71.0 - KF5NewStuff >= 5.71.0 - KF5Archive >= 5.71.0 - KF5GlobalAccel >= 5.71.0 - KF5Crash >= 5.71.0 + KF5Plasma >= 5.82.0 + KF5PlasmaQuick >= 5.82.0 + KF5Activities >= 5.82.0 + KF5CoreAddons >= 5.82.0 + KF5GuiAddons >= 5.82.0 + KF5DBusAddons >= 5.82.0 + KF5Declarative >= 5.82.0 + KF5Kirigami2 >= 5.82.0 + KF5Wayland >= 5.82.0 + KF5Package >= 5.82.0 + KF5XmlGui >= 5.82.0 + KF5IconThemes >= 5.82.0 + KF5KIO >= 5.82.0 + KF5I18n >= 5.82.0 + KF5Notifications >= 5.82.0 + KF5NewStuff >= 5.82.0 + KF5Archive >= 5.82.0 + KF5GlobalAccel >= 5.82.0 + KF5Crash >= 5.82.0 For X11 support: - KF5WindowSystem >= 5.71.0 + KF5WindowSystem >= 5.82.0 Qt5X11Extras >= 5.7.0 libxcb libxcb-randr diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index d5b87be1a..3009a597d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -4,7 +4,8 @@ set(lattedock-app_SRCS infoview.cpp lattecorona.cpp screenpool.cpp - main.cpp + primaryoutputwatcher.cpp + main.cpp coretypes.h ) @@ -49,6 +50,11 @@ ki18n_wrap_ui(lattedock-app_SRCS settings/screensdialog/screensdialog.ui) ki18n_wrap_ui(lattedock-app_SRCS settings/settingsdialog/settingsdialog.ui) ki18n_wrap_ui(lattedock-app_SRCS settings/viewsdialog/viewsdialog.ui) +ecm_add_qtwayland_client_protocol(lattedock-app_SRCS + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-primary-output-v1.xml + BASENAME kde-primary-output-v1 +) + add_executable(latte-dock ${lattedock-app_SRCS}) include(FakeTarget.cmake) @@ -75,6 +81,7 @@ target_link_libraries(latte-dock KF5::PlasmaQuick KF5::WaylandClient KF5::XmlGui + Wayland::Client ) diff --git a/app/infoview.h b/app/infoview.h index ea1ee0498..c62e30b2a 100644 --- a/app/infoview.h +++ b/app/infoview.h @@ -32,7 +32,7 @@ class InfoView : public QQuickView Q_PROPERTY(Plasma::FrameSvg::EnabledBorders enabledBorders READ enabledBorders NOTIFY enabledBordersChanged) public: - InfoView(Latte::Corona *corona, QString message, QScreen *screen = qGuiApp->primaryScreen(), QWindow *parent = nullptr); + InfoView(Latte::Corona *corona, QString message, QScreen *screen, QWindow *parent = nullptr); ~InfoView() override; QString validTitle() const; diff --git a/app/lattecorona.cpp b/app/lattecorona.cpp index f59adab80..51d2262b8 100644 --- a/app/lattecorona.cpp +++ b/app/lattecorona.cpp @@ -225,10 +225,9 @@ void Corona::load() m_templatesManager->init(); m_layoutsManager->init(); - connect(this, &Corona::availableScreenRectChangedFrom, this, &Plasma::Corona::availableScreenRectChanged); - connect(this, &Corona::availableScreenRegionChangedFrom, this, &Plasma::Corona::availableScreenRegionChanged); - connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Corona::primaryOutputChanged, Qt::UniqueConnection); - connect(m_screenPool, &ScreenPool::primaryPoolChanged, this, &Corona::screenCountChanged); + connect(this, &Corona::availableScreenRectChangedFrom, this, &Plasma::Corona::availableScreenRectChanged, Qt::UniqueConnection); + connect(this, &Corona::availableScreenRegionChangedFrom, this, &Plasma::Corona::availableScreenRegionChanged, Qt::UniqueConnection); + connect(m_screenPool, &ScreenPool::primaryScreenChanged, this, &Corona::onScreenCountChanged, Qt::UniqueConnection); QString loadLayoutName = ""; @@ -270,7 +269,7 @@ void Corona::load() //! load screens signals such screenGeometryChanged in order to support //! plasmoid.screenGeometry properly for (QScreen *screen : qGuiApp->screens()) { - addOutput(screen); + onScreenAdded(screen); } connect(m_layoutsManager->synchronizer(), &Layouts::Synchronizer::initializationFinished, [this]() { @@ -284,8 +283,8 @@ void Corona::load() m_inStartup = false; - connect(qGuiApp, &QGuiApplication::screenAdded, this, &Corona::addOutput, Qt::UniqueConnection); - connect(qGuiApp, &QGuiApplication::screenRemoved, this, &Corona::screenRemoved, Qt::UniqueConnection); + connect(qGuiApp, &QGuiApplication::screenAdded, this, &Corona::onScreenAdded, Qt::UniqueConnection); + connect(qGuiApp, &QGuiApplication::screenRemoved, this, &Corona::onScreenRemoved, Qt::UniqueConnection); } } @@ -494,7 +493,7 @@ int Corona::numScreens() const QRect Corona::screenGeometry(int id) const { const auto screens = qGuiApp->screens(); - const QScreen *screen{qGuiApp->primaryScreen()}; + const QScreen *screen{m_screenPool->primaryScreen()}; QString screenName; @@ -851,7 +850,7 @@ QRect Corona::availableScreenRectWithCriteria(int id, return available; } -void Corona::addOutput(QScreen *screen) +void Corona::onScreenAdded(QScreen *screen) { Q_ASSERT(screen); @@ -861,35 +860,42 @@ void Corona::addOutput(QScreen *screen) m_screenPool->insertScreenMapping(screen->name()); } - connect(screen, &QScreen::geometryChanged, this, [ = ]() { - const int id = m_screenPool->id(screen->name()); - - if (id >= 0) { - emit screenGeometryChanged(id); - emit availableScreenRegionChanged(); - emit availableScreenRectChanged(); - } - }); + connect(screen, &QScreen::geometryChanged, this, &Corona::onScreenGeometryChanged); emit availableScreenRectChanged(); emit screenAdded(m_screenPool->id(screen->name())); - screenCountChanged(); + onScreenCountChanged(); } -void Corona::primaryOutputChanged() +void Corona::onScreenRemoved(QScreen *screen) { - m_viewsScreenSyncTimer.start(); + disconnect(screen, &QScreen::geometryChanged, this, &Corona::onScreenGeometryChanged); + onScreenCountChanged(); } -void Corona::screenRemoved(QScreen *screen) +void Corona::onScreenCountChanged() { - screenCountChanged(); + m_viewsScreenSyncTimer.start(); } -void Corona::screenCountChanged() +void Corona::onScreenGeometryChanged(const QRect &geometry) { - m_viewsScreenSyncTimer.start(); + Q_UNUSED(geometry); + + QScreen *screen = qobject_cast(sender()); + + if (!screen) { + return; + } + + const int id = m_screenPool->id(screen->name()); + + if (id >= 0) { + emit screenGeometryChanged(id); + emit availableScreenRegionChanged(); + emit availableScreenRectChanged(); + } } //! the central functions that updates loading/unloading latteviews @@ -901,7 +907,7 @@ void Corona::syncLatteViewsToScreens() int Corona::primaryScreenId() const { - return m_screenPool->id(qGuiApp->primaryScreen()->name()); + return m_screenPool->primaryScreenId(); } void Corona::quitApplication() diff --git a/app/lattecorona.h b/app/lattecorona.h index a5ab7bbe4..d268f8ad1 100644 --- a/app/lattecorona.h +++ b/app/lattecorona.h @@ -189,10 +189,10 @@ private slots: void onAboutToQuit(); - void addOutput(QScreen *screen); - void primaryOutputChanged(); - void screenRemoved(QScreen *screen); - void screenCountChanged(); + void onScreenAdded(QScreen *screen); + void onScreenRemoved(QScreen *screen); + void onScreenCountChanged(); + void onScreenGeometryChanged(const QRect &geometry); void syncLatteViewsToScreens(); private: diff --git a/app/layout/genericlayout.cpp b/app/layout/genericlayout.cpp index 8c64d9f20..3fb62872d 100644 --- a/app/layout/genericlayout.cpp +++ b/app/layout/genericlayout.cpp @@ -482,10 +482,11 @@ QList GenericLayout::onlyOriginalViews() QList GenericLayout::sortedLatteViews() { - return sortedLatteViews(latteViews()); + QScreen *primaryScreen = (m_corona ? m_corona->screenPool()->primaryScreen() : nullptr); + return sortedLatteViews(latteViews(), primaryScreen); } -QList GenericLayout::sortedLatteViews(QList views) +QList GenericLayout::sortedLatteViews(QList views, QScreen *primaryScreen) { QList sortedViews = views; @@ -501,7 +502,7 @@ QList GenericLayout::sortedLatteViews(QList views) //! Bottom,Left,Top,Right for (int i = 0; i < sortedViews.size(); ++i) { for (int j = 0; j < sortedViews.size() - i - 1; ++j) { - if (viewAtLowerScreenPriority(sortedViews[j], sortedViews[j + 1]) + if (viewAtLowerScreenPriority(sortedViews[j], sortedViews[j + 1], primaryScreen) || (sortedViews[j]->screen() == sortedViews[j + 1]->screen() && viewAtLowerEdgePriority(sortedViews[j], sortedViews[j + 1]))) { Latte::View *temp = sortedViews[j + 1]; @@ -534,7 +535,7 @@ QList GenericLayout::sortedLatteViews(QList views) return sortedViews; } -bool GenericLayout::viewAtLowerScreenPriority(Latte::View *test, Latte::View *base) +bool GenericLayout::viewAtLowerScreenPriority(Latte::View *test, Latte::View *base, QScreen *primaryScreen) { if (!base || ! test) { return true; @@ -542,9 +543,9 @@ bool GenericLayout::viewAtLowerScreenPriority(Latte::View *test, Latte::View *ba if (base->screen() == test->screen()) { return false; - } else if (base->screen() != qGuiApp->primaryScreen() && test->screen() == qGuiApp->primaryScreen()) { + } else if (base->screen() != primaryScreen && test->screen() == primaryScreen) { return false; - } else if (base->screen() == qGuiApp->primaryScreen() && test->screen() != qGuiApp->primaryScreen()) { + } else if (base->screen() == primaryScreen && test->screen() != primaryScreen) { return true; } else { int basePriority = -1; @@ -873,7 +874,7 @@ void GenericLayout::addView(Plasma::Containment *containment) qDebug() << "Adding View:" << containment->id() << "- Step 3..."; - QScreen *nextScreen{qGuiApp->primaryScreen()}; + QScreen *nextScreen{m_corona->screenPool()->primaryScreen()}; Data::View viewdata = Layouts::Storage::self()->view(this, containment); viewdata.screen = Layouts::Storage::self()->expectedViewScreenId(m_corona, viewdata); @@ -953,7 +954,7 @@ void GenericLayout::toggleHiddenState(QString viewName, QString screenName, Plas return; } - QString validScreenName = qGuiApp->primaryScreen()->name(); + QString validScreenName = m_corona->screenPool()->primaryScreen()->name(); if (!screenName.isEmpty()) { validScreenName = screenName; } @@ -1276,7 +1277,7 @@ Layout::ViewsMap GenericLayout::validViewsMap() return map; } - QString prmScreenName = qGuiApp->primaryScreen()->name(); + QString prmScreenName = m_corona->screenPool()->primaryScreen()->name(); for (const auto containment : m_containments) { if (Layouts::Storage::self()->isLatteContainment(containment) @@ -1338,7 +1339,7 @@ void GenericLayout::syncLatteViewsToScreens() //! use valid views map based on active screens Layout::ViewsMap viewsMap = validViewsMap(); - QString prmScreenName = qGuiApp->primaryScreen()->name(); + QString prmScreenName = m_corona->screenPool()->primaryScreen()->name(); qDebug() << "PRIMARY SCREEN :: " << prmScreenName; qDebug() << "LATTEVIEWS MAP :: " << viewsMap; diff --git a/app/layout/genericlayout.h b/app/layout/genericlayout.h index 9adbc350a..eaa843daa 100644 --- a/app/layout/genericlayout.h +++ b/app/layout/genericlayout.h @@ -88,9 +88,9 @@ public: Plasma::Containment *containmentForId(uint id) const; QList subContainmentsOf(uint id) const; - static bool viewAtLowerScreenPriority(Latte::View *test, Latte::View *base); + static bool viewAtLowerScreenPriority(Latte::View *test, Latte::View *base, QScreen *primaryScreen); static bool viewAtLowerEdgePriority(Latte::View *test, Latte::View *base); - static QList sortedLatteViews(QList views); + static QList sortedLatteViews(QList views, QScreen *primaryScreen); QList sortedLatteViews(); virtual QList viewsWithPlasmaShortcuts(); diff --git a/app/layouts/synchronizer.cpp b/app/layouts/synchronizer.cpp index ad668f4d5..f4a19f390 100644 --- a/app/layouts/synchronizer.cpp +++ b/app/layouts/synchronizer.cpp @@ -10,6 +10,7 @@ #include "importer.h" #include "manager.h" #include "../apptypes.h" +#include "../screenpool.h" #include "../data/layoutdata.h" #include "../lattecorona.h" #include "../layout/centrallayout.h" @@ -383,12 +384,12 @@ QList Synchronizer::currentViewsWithPlasmaShortcuts() const QList Synchronizer::sortedCurrentViews() const { - return Layout::GenericLayout::sortedLatteViews(currentViews()); + return Layout::GenericLayout::sortedLatteViews(currentViews(), m_manager->corona()->screenPool()->primaryScreen()); } QList Synchronizer::sortedCurrentOriginalViews() const { - return Layout::GenericLayout::sortedLatteViews(currentOriginalViews()); + return Layout::GenericLayout::sortedLatteViews(currentOriginalViews(), m_manager->corona()->screenPool()->primaryScreen()); } QList Synchronizer::viewsBasedOnActivityId(const QString &id) const diff --git a/app/plasma/extended/screenpool.cpp b/app/plasma/extended/screenpool.cpp index b4ebc604d..d02efe368 100644 --- a/app/plasma/extended/screenpool.cpp +++ b/app/plasma/extended/screenpool.cpp @@ -6,6 +6,7 @@ #include "screenpool.h" // local +#include "../../primaryoutputwatcher.h" #include "../../tools/commontools.h" // Qt @@ -25,7 +26,8 @@ namespace Latte { namespace PlasmaExtended { ScreenPool::ScreenPool(QObject *parent) - : QObject(parent) + : QObject(parent), + m_primaryWatcher(new PrimaryOutputWatcher(this)) { m_plasmarcConfig = KSharedConfig::openConfig(PLASMARC); m_screensGroup = KConfigGroup(m_plasmarcConfig, "ScreenConnectors"); @@ -108,7 +110,7 @@ int ScreenPool::id(const QString &connector) const { if (!m_idForConnector.contains(connector)) { //! return 0 for primary screen, -1 for not found - return qGuiApp->primaryScreen()->name() == connector ? 0 : -1; + return m_primaryWatcher->primaryScreen()->name() == connector ? 0 : -1; } return m_idForConnector.value(connector); diff --git a/app/plasma/extended/screenpool.h b/app/plasma/extended/screenpool.h index 1324c6aed..c91c9550e 100644 --- a/app/plasma/extended/screenpool.h +++ b/app/plasma/extended/screenpool.h @@ -15,6 +15,8 @@ #include #include +class PrimaryOutputWatcher; + namespace Latte { namespace PlasmaExtended { @@ -45,6 +47,8 @@ private: KSharedConfig::Ptr m_plasmarcConfig; KConfigGroup m_screensGroup; + + PrimaryOutputWatcher *m_primaryWatcher; }; } diff --git a/app/primaryoutputwatcher.cpp b/app/primaryoutputwatcher.cpp new file mode 100644 index 000000000..773e48b5f --- /dev/null +++ b/app/primaryoutputwatcher.cpp @@ -0,0 +1,166 @@ +/* + SPDX-FileCopyrightText: 2013 Marco Martin + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "primaryoutputwatcher.h" + +#include +#include +#include +#include + +#include "qwayland-kde-primary-output-v1.h" +#include +#include + +#include +#if HAVE_X11 +#include //Used only in x11 case +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +#include +#include +#include +#endif + +class WaylandPrimaryOutput : public QObject, public QtWayland::kde_primary_output_v1 +{ + Q_OBJECT +public: + WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version, QObject *parent) + : QObject(parent) + , QtWayland::kde_primary_output_v1(registry, id, version) + { + } + + void kde_primary_output_v1_primary_output(const QString &outputName) override + { + Q_EMIT primaryOutputChanged(outputName); + } + +Q_SIGNALS: + void primaryOutputChanged(const QString &outputName); +}; + +PrimaryOutputWatcher::PrimaryOutputWatcher(QObject *parent) + : QObject(parent) +{ +#if HAVE_X11 + if (KWindowSystem::isPlatformX11()) { + m_primaryOutputName = qGuiApp->primaryScreen()->name(); + qGuiApp->installNativeEventFilter(this); + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); + m_xrandrExtensionOffset = reply->first_event; + setPrimaryOutputName(qGuiApp->primaryScreen()->name()); + connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this](QScreen *newPrimary) { + setPrimaryOutputName(newPrimary->name()); + }); + } +#endif + if (KWindowSystem::isPlatformWayland()) { + setupRegistry(); + } +} + +void PrimaryOutputWatcher::setPrimaryOutputName(const QString &newOutputName) +{ + if (newOutputName != m_primaryOutputName) { + const QString oldOutputName = m_primaryOutputName; + m_primaryOutputName = newOutputName; + Q_EMIT primaryOutputNameChanged(oldOutputName, newOutputName); + } +} + +void PrimaryOutputWatcher::setupRegistry() +{ + auto m_connection = KWayland::Client::ConnectionThread::fromApplication(this); + if (!m_connection) { + return; + } + + // Asking for primaryOutputName() before this happened, will return qGuiApp->primaryScreen()->name() anyways, so set it so the primaryOutputNameChange will + // have parameters that are coherent + m_primaryOutputName = qGuiApp->primaryScreen()->name(); + m_registry = new KWayland::Client::Registry(this); + connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) { + if (interface == WaylandPrimaryOutput::interface()->name) { + auto m_outputManagement = new WaylandPrimaryOutput(m_registry->registry(), name, version, this); + connect(m_outputManagement, &WaylandPrimaryOutput::primaryOutputChanged, this, [this](const QString &outputName) { + m_primaryOutputWayland = outputName; + // Only set the outputName when there's a QScreen attached to it + if (screenForName(outputName)) { + setPrimaryOutputName(outputName); + } + }); + } + }); + + // In case the outputName was received before Qt reported the screen + connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) { + if (screen->name() == m_primaryOutputWayland) { + setPrimaryOutputName(m_primaryOutputWayland); + } + }); + + m_registry->create(m_connection); + m_registry->setup(); +} + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) +#else +bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) +#endif +{ + Q_UNUSED(result); +#if HAVE_X11 + // a particular edge case: when we switch the only enabled screen + // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled + // see https://bugs.kde.org/show_bug.cgi?id=373880 + // if this slot will be invoked many times, their//second time on will do nothing as name and primaryOutputName will be the same by then + if (eventType[0] != 'x') { + return false; + } + + xcb_generic_event_t *ev = static_cast(message); + + const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); + + if (responseType == m_xrandrExtensionOffset + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { + QTimer::singleShot(0, this, [this]() { + setPrimaryOutputName(qGuiApp->primaryScreen()->name()); + }); + } +#endif + return false; +} + +QScreen *PrimaryOutputWatcher::screenForName(const QString &outputName) const +{ + const auto screens = qGuiApp->screens(); + for (auto screen : screens) { + if (screen->name() == outputName) { + return screen; + } + } + return nullptr; +} + +QScreen *PrimaryOutputWatcher::primaryScreen() const +{ + auto screen = screenForName(m_primaryOutputName); + if (!screen) { + qDebug() << "PrimaryOutputWatcher: Could not find primary screen:" << m_primaryOutputName; + return qGuiApp->primaryScreen(); + } + return screen; +} + +#include "primaryoutputwatcher.moc" + diff --git a/app/primaryoutputwatcher.h b/app/primaryoutputwatcher.h new file mode 100644 index 000000000..29cf1ecfd --- /dev/null +++ b/app/primaryoutputwatcher.h @@ -0,0 +1,58 @@ +/* + SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef PRIMARYOUTPUTWATCHER_H +#define PRIMARYOUTPUTWATCHER_H + +#include +#include + +namespace KWayland +{ +namespace Client +{ +class Registry; +class ConnectionThread; +} +} + +class QScreen; + +class PrimaryOutputWatcher : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT +public: + PrimaryOutputWatcher(QObject *parent); + QScreen *primaryScreen() const; + QScreen *screenForName(const QString &outputName) const; + +Q_SIGNALS: + void primaryOutputNameChanged(const QString &oldOutputName, const QString &newOutputName); + +protected: + friend class WaylandOutputDevice; + void setPrimaryOutputName(const QString &outputName); + +private: + void setupRegistry(); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; +#else + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; +#endif + + // All + QString m_primaryOutputName; + + // Wayland + KWayland::Client::Registry *m_registry = nullptr; + QString m_primaryOutputWayland; + + // Xrandr + int m_xrandrExtensionOffset; +}; + +#endif // PRIMARYOUTPUTWATCHER_H diff --git a/app/screenpool.cpp b/app/screenpool.cpp index 3b8869d6a..a87c759a2 100644 --- a/app/screenpool.cpp +++ b/app/screenpool.cpp @@ -8,6 +8,7 @@ // local #include +#include "primaryoutputwatcher.h" // Qt #include @@ -17,6 +18,7 @@ // KDE #include +#include // X11 #if HAVE_X11 @@ -32,10 +34,9 @@ const int ScreenPool::FIRSTSCREENID; ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent) : QObject(parent), - m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))) + m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors"))), + m_primaryWatcher(new PrimaryOutputWatcher(this)) { - qApp->installNativeEventFilter(this); - m_configSaveTimer.setSingleShot(true); connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() { m_configGroup.sync(); @@ -52,15 +53,8 @@ ScreenPool::~ScreenPool() void ScreenPool::load() { - m_lastPrimaryConnector = QString(); m_screensTable.clear(); - QScreen *primary = qGuiApp->primaryScreen(); - - if (primary) { - m_lastPrimaryConnector = primary->name(); - } - //restore the known ids to connector mappings for (const QString &key : m_configGroup.keyList()) { if (key.toInt() <= 0) { @@ -94,6 +88,20 @@ void ScreenPool::load() onScreenAdded(screen); } + + if (KWindowSystem::isPlatformX11()) { + connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &ScreenPool::primaryScreenChanged, Qt::UniqueConnection); + } + + connect(m_primaryWatcher, &PrimaryOutputWatcher::primaryOutputNameChanged, this, &ScreenPool::onPrimaryOutputNameChanged, Qt::UniqueConnection); +} + +void ScreenPool::onPrimaryOutputNameChanged(const QString &oldOutputName, const QString &newOutputName) +{ + Q_UNUSED(oldOutputName); + Q_UNUSED(newOutputName); + + emit primaryScreenChanged(m_primaryWatcher->primaryScreen()); } void ScreenPool::onScreenAdded(const QScreen *screen) @@ -165,15 +173,17 @@ void ScreenPool::removeScreens(const Latte::Data::ScreensTable &obsoleteScreens) int ScreenPool::primaryScreenId() const { - return id(qGuiApp->primaryScreen()->name()); + return id(primaryScreen()->name()); } QList ScreenPool::secondaryScreenIds() const { QList secondaryscreens; + QScreen *primary{primaryScreen()}; + for (const auto scr : qGuiApp->screens()) { - if (scr == qGuiApp->primaryScreen()) { + if (scr == primary) { continue; } @@ -274,10 +284,15 @@ bool ScreenPool::isScreenActive(int screenId) const return false; } +QScreen *ScreenPool::primaryScreen() const +{ + return m_primaryWatcher->primaryScreen(); +} + QScreen *ScreenPool::screenForId(int id) { const auto screens = qGuiApp->screens(); - QScreen *screen{qGuiApp->primaryScreen()}; + QScreen *screen{primaryScreen()}; if (hasScreenId(id)) { QString scrName = connector(id); @@ -292,41 +307,4 @@ QScreen *ScreenPool::screenForId(int id) return screen; } -bool ScreenPool::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) -{ - Q_UNUSED(result); -#if HAVE_X11 - - // a particular edge case: when we switch the only enabled screen - // we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled - // see https://bugs.kde.org/show_bug.cgi?id=373880 - // if this slot will be invoked many times, their//second time on will do nothing as name and primaryconnector will be the same by then - if (eventType != "xcb_generic_event_t") { - return false; - } - - xcb_generic_event_t *ev = static_cast(message); - - const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); - - const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id); - - if (responseType == reply->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { - if (qGuiApp->primaryScreen()->name() != m_lastPrimaryConnector) { - //new screen? - if (id(qGuiApp->primaryScreen()->name()) < 0) { - insertScreenMapping(qGuiApp->primaryScreen()->name()); - } - - m_lastPrimaryConnector = qGuiApp->primaryScreen()->name(); - emit primaryPoolChanged(); - } - } - -#endif - return false; -} - } - -#include "moc_screenpool.cpp" diff --git a/app/screenpool.h b/app/screenpool.h index 8bf92e033..4410c06ef 100644 --- a/app/screenpool.h +++ b/app/screenpool.h @@ -16,15 +16,16 @@ #include #include #include -#include // KDE #include #include +class PrimaryOutputWatcher; + namespace Latte { -class ScreenPool : public QObject, public QAbstractNativeEventFilter +class ScreenPool : public QObject { Q_OBJECT @@ -33,9 +34,10 @@ public: static const int NOSCREENID = -1; ScreenPool(KSharedConfig::Ptr config, QObject *parent = nullptr); - void load(); ~ScreenPool() override; + void load(); + bool hasScreenId(int screenId) const; bool isScreenActive(int screenId) const; int primaryScreenId() const; @@ -50,20 +52,20 @@ public: QString connector(int id) const; QScreen *screenForId(int id); + QScreen *primaryScreen() const; Latte::Data::ScreensTable screensTable(); signals: - void primaryPoolChanged(); + void primaryScreenChanged(QScreen *screen); void screenGeometryChanged(); protected: - bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; - int firstAvailableId() const; private slots: void updateScreenGeometry(const QScreen *screen); + void onPrimaryOutputNameChanged(const QString &oldOutputName, const QString &newOutputName); void onScreenAdded(const QScreen *screen); void onScreenRemoved(const QScreen *screen); @@ -75,10 +77,10 @@ private: Latte::Data::ScreensTable m_screensTable; KConfigGroup m_configGroup; - //! used to workaround a bug under X11 when primary screen changes and no screenChanged signal is emitted - QString m_lastPrimaryConnector; QTimer m_configSaveTimer; + + PrimaryOutputWatcher *m_primaryWatcher; }; } diff --git a/app/view/positioner.cpp b/app/view/positioner.cpp index 8b2234031..c56fcab0a 100644 --- a/app/view/positioner.cpp +++ b/app/view/positioner.cpp @@ -209,7 +209,7 @@ void Positioner::init() }); connect(qGuiApp, &QGuiApplication::screenAdded, this, &Positioner::onScreenChanged); - connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Positioner::onScreenChanged); + connect(m_corona->screenPool(), &ScreenPool::primaryScreenChanged, this, &Positioner::onScreenChanged); connect(m_view, &Latte::View::visibilityChanged, this, &Positioner::initDelayedSignals); @@ -435,11 +435,12 @@ void Positioner::reconsiderScreen() } bool screenExists{false}; + QScreen *primaryScreen{m_corona->screenPool()->primaryScreen()}; //!check if the associated screen is running for (const auto scr : qGuiApp->screens()) { if (m_screenNameToFollow == scr->name() - || (m_view->onPrimary() && scr == qGuiApp->primaryScreen())) { + || (m_view->onPrimary() && scr == primaryScreen)) { screenExists = true; } } @@ -447,12 +448,12 @@ void Positioner::reconsiderScreen() qDebug() << "dock screen exists ::: " << screenExists; //! 1.a primary dock must be always on the primary screen - if (m_view->onPrimary() && (m_screenNameToFollow != qGuiApp->primaryScreen()->name() - || m_screenToFollow != qGuiApp->primaryScreen() - || m_view->screen() != qGuiApp->primaryScreen())) { + if (m_view->onPrimary() && (m_screenNameToFollow != primaryScreen->name() + || m_screenToFollow != primaryScreen + || m_view->screen() != primaryScreen)) { //! case 1 qDebug() << "reached case 1: of updating dock primary screen..."; - setScreenToFollow(qGuiApp->primaryScreen()); + setScreenToFollow(primaryScreen); } else if (!m_view->onPrimary()) { //! 2.an explicit dock must be always on the correct associated screen //! there are cases that window manager misplaces the dock, this function @@ -1028,7 +1029,7 @@ void Positioner::initSignalingForLocationChangeSliding() //! SCREEN if (!m_nextScreenName.isEmpty()) { bool nextonprimary = (m_nextScreenName == Latte::Data::Screen::ONPRIMARYNAME); - m_nextScreen = qGuiApp->primaryScreen(); + m_nextScreen = m_corona->screenPool()->primaryScreen(); if (!nextonprimary) { for (const auto scr : qGuiApp->screens()) { @@ -1210,7 +1211,7 @@ void Positioner::setNextLocation(const QString layoutName, const int screensGrou || (!m_view->onPrimary() && nextonprimary) /*explicit -> primary*/ || (!m_view->onPrimary() && !nextonprimary && screenName != currentScreenName()) ) { /*explicit -> new_explicit*/ - QString nextscreenname = nextonprimary ? qGuiApp->primaryScreen()->name() : screenName; + QString nextscreenname = nextonprimary ? m_corona->screenPool()->primaryScreen()->name() : screenName; if (currentScreenName() == nextscreenname) { m_view->setOnPrimary(nextonprimary); diff --git a/app/view/view.cpp b/app/view/view.cpp index 31acf5fc9..06a647713 100644 --- a/app/view/view.cpp +++ b/app/view/view.cpp @@ -76,6 +76,8 @@ View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassX11WM) //this is disabled because under wayland breaks Views positioning //setVisible(false); + m_corona = qobject_cast(corona); + //! needs to be created after Effects because it catches some of its signals //! and avoid a crash from View::winId() at the same time m_positioner = new ViewPart::Positioner(this); @@ -110,7 +112,8 @@ View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassX11WM) if (targetScreen) { m_positioner->setScreenToFollow(targetScreen); } else { - m_positioner->setScreenToFollow(qGuiApp->primaryScreen()); + qDebug() << "org.kde.view :::: corona was found properly!!!"; + m_positioner->setScreenToFollow(m_corona->screenPool()->primaryScreen()); } m_releaseGrabTimer.setInterval(400); @@ -206,8 +209,6 @@ View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassX11WM) emit containmentActionsChanged(); }, Qt::DirectConnection); - m_corona = qobject_cast(this->corona()); - if (m_corona) { connect(m_corona, &Latte::Corona::viewLocationChanged, this, &View::dockLocationChanged); } diff --git a/containment/package/metadata.desktop b/containment/package/metadata.desktop index e4a0f46f9..9a8db92a3 100644 --- a/containment/package/metadata.desktop +++ b/containment/package/metadata.desktop @@ -63,7 +63,7 @@ NoDisplay=true X-KDE-PluginInfo-Author=Michail Vourlakos, Smith Ar X-KDE-PluginInfo-Email=mvourlakos@gmail.com, audoban@openmailbox.org X-KDE-PluginInfo-Name=org.kde.latte.containment -X-KDE-PluginInfo-Version=0.10.76 +X-KDE-PluginInfo-Version=0.10.77 X-KDE-PluginInfo-Website=https://userbase.kde.org/LatteDock X-KDE-PluginInfo-Category= X-KDE-PluginInfo-Depends= diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop index 3552e8c15..5bd6641ea 100644 --- a/plasmoid/package/metadata.desktop +++ b/plasmoid/package/metadata.desktop @@ -70,7 +70,7 @@ X-Plasma-Provides=org.kde.plasma.multitasking X-KDE-PluginInfo-Author=Michail Vourlakos, Smith Ar X-KDE-PluginInfo-Email=mvourlakos@gmail.com, audoban@openmailbox.org X-KDE-PluginInfo-Name=org.kde.latte.plasmoid -X-KDE-PluginInfo-Version=0.10.76 +X-KDE-PluginInfo-Version=0.10.77 X-KDE-PluginInfo-Website=https://userbase.kde.org/LatteDock X-KDE-PluginInfo-Category=Windows and Tasks X-KDE-PluginInfo-License=GPL v2+ diff --git a/shell/package/metadata.desktop b/shell/package/metadata.desktop index 0762ef86b..6292f8029 100644 --- a/shell/package/metadata.desktop +++ b/shell/package/metadata.desktop @@ -107,5 +107,5 @@ X-KDE-PluginInfo-Author=Michail Vourlakos, Smith Ar X-KDE-PluginInfo-Email=mvourlakos@gmail.com, audoban@openmailbox.org X-KDE-PluginInfo-License=GPLv3+ X-KDE-PluginInfo-Name=org.kde.latte.shell -X-KDE-PluginInfo-Version=0.10.76 +X-KDE-PluginInfo-Version=0.10.77 X-KDE-PluginInfo-Website=https://userbase.kde.org/LatteDock