diff --git a/applets/notifications/package/contents/ui/FullRepresentation.qml b/applets/notifications/package/contents/ui/FullRepresentation.qml --- a/applets/notifications/package/contents/ui/FullRepresentation.qml +++ b/applets/notifications/package/contents/ui/FullRepresentation.qml @@ -98,6 +98,8 @@ if (Globals.inhibited) { notificationSettings.notificationsInhibitedUntil = undefined; notificationSettings.revokeApplicationInhibitions(); + // overrules current mirrored screen setup, updates again when screen configuration changes + notificationSettings.screensMirrored = false; notificationSettings.save(); } @@ -222,6 +224,8 @@ var inhibitedUntil = notificationSettings.notificationsInhibitedUntil; var inhibitedByApp = notificationSettings.notificationsInhibitedByApplication; + var inhibitedByMirroredScreens = notificationSettings.inhibitNotificationsWhenScreensMirrored + && notificationSettings.screensMirrored; var sections = []; @@ -247,6 +251,10 @@ } } + if (inhibitedByMirroredScreens) { + sections.push(i18nc("Do not disturb because external mirrored screens connected", "Screens are mirrored")) + } + return sections.join(" ยท "); } visible: text !== "" diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml --- a/applets/notifications/package/contents/ui/global/Globals.qml +++ b/applets/notifications/package/contents/ui/global/Globals.qml @@ -204,6 +204,10 @@ inhibited |= true; } + if (notificationSettings.inhibitNotificationsWhenScreensMirrored) { + inhibited |= notificationSettings.screensMirrored; + } + return inhibited; }); } diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt --- a/libnotificationmanager/CMakeLists.txt +++ b/libnotificationmanager/CMakeLists.txt @@ -9,6 +9,7 @@ server.cpp server_p.cpp settings.cpp + mirroredscreenstracker.cpp notifications.cpp notification.cpp @@ -79,6 +80,7 @@ KF5::KIOFileWidgets KF5::Plasma KF5::ProcessCore + KF5::Screen KF5::Service ) diff --git a/libnotificationmanager/kcfg/donotdisturbsettings.kcfg b/libnotificationmanager/kcfg/donotdisturbsettings.kcfg --- a/libnotificationmanager/kcfg/donotdisturbsettings.kcfg +++ b/libnotificationmanager/kcfg/donotdisturbsettings.kcfg @@ -10,6 +10,10 @@ + + true + + false diff --git a/libnotificationmanager/mirroredscreenstracker.cpp b/libnotificationmanager/mirroredscreenstracker.cpp new file mode 100644 --- /dev/null +++ b/libnotificationmanager/mirroredscreenstracker.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mirroredscreenstracker_p.h" + +#include + +#include +#include +#include + +#include "debug.h" + +using namespace NotificationManager; + +MirroredScreensTracker::MirroredScreensTracker() + : QObject(nullptr) +{ + connect(new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID), &KScreen::ConfigOperation::finished, this, + [this](KScreen::ConfigOperation *op) { + m_screenConfiguration = qobject_cast(op)->config(); + checkScreensMirrored(); + + KScreen::ConfigMonitor::instance()->addConfig(m_screenConfiguration); + connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, + this, &MirroredScreensTracker::checkScreensMirrored); + }); +} + +MirroredScreensTracker::~MirroredScreensTracker() = default; + +MirroredScreensTracker::Ptr MirroredScreensTracker::createTracker() +{ + static QWeakPointer s_instance; + if (!s_instance) { + QSharedPointer ptr(new MirroredScreensTracker()); + s_instance = ptr.toWeakRef(); + return ptr; + } + return s_instance.toStrongRef(); +} + +bool MirroredScreensTracker::screensMirrored() const +{ + return m_screensMirrored; +} + +void MirroredScreensTracker::setScreensMirrored(bool mirrored) +{ + if (m_screensMirrored != mirrored) { + m_screensMirrored = mirrored; + emit screensMirroredChanged(mirrored); + } +} + +void MirroredScreensTracker::checkScreensMirrored() +{ + if (!m_screenConfiguration) { + setScreensMirrored(false); + return; + } + + const auto outputs = m_screenConfiguration->outputs(); + for (const KScreen::OutputPtr &output : outputs) { + if (!output->isConnected() || !output->isEnabled()) { + continue; + } + + for (const KScreen::OutputPtr &checkOutput : outputs) { + if (checkOutput == output) { + continue; + } + + if (output->geometry().intersects(checkOutput->geometry())) { + qCDebug(NOTIFICATIONMANAGER) << "Screen geometry" << checkOutput->geometry() << "intersects" << output->geometry() << "- considering them to be mirrored"; + setScreensMirrored(true); + return; + } + } + } + + setScreensMirrored(false); +} diff --git a/libnotificationmanager/mirroredscreenstracker_p.h b/libnotificationmanager/mirroredscreenstracker_p.h new file mode 100644 --- /dev/null +++ b/libnotificationmanager/mirroredscreenstracker_p.h @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Kai Uwe Broulik + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#pragma once + +#include +#include + +#include + +namespace NotificationManager +{ + +/** + * @short Tracks whether there are any mirrored screens + * + * @author Kai Uwe Broulik + **/ +class MirroredScreensTracker : public QObject +{ + Q_OBJECT + +public: + ~MirroredScreensTracker(); + + using Ptr = QSharedPointer; + static Ptr createTracker(); + + bool screensMirrored() const; + /** + * Set whether screens are mirrored + * + * This is public so that automatic do not disturb mode when screens are mirrored + * can be disabled temporarily until screen configuration changes again. + */ + void setScreensMirrored(bool mirrored); + Q_SIGNAL void screensMirroredChanged(bool mirrored); + +private: + MirroredScreensTracker(); + Q_DISABLE_COPY(MirroredScreensTracker) + + void checkScreensMirrored(); + + KScreen::ConfigPtr m_screenConfiguration; + bool m_screensMirrored = false; + +}; + +} // namespace NotificationManager diff --git a/libnotificationmanager/settings.h b/libnotificationmanager/settings.h --- a/libnotificationmanager/settings.h +++ b/libnotificationmanager/settings.h @@ -169,6 +169,28 @@ READ notificationInhibitionReasons NOTIFY notificationInhibitionApplicationsChanged) + /** + * Whether to enable do not disturb mode when screens are mirrored/overlapping + * + * @since 5.17 + */ + Q_PROPERTY(bool inhibitNotificationsWhenScreensMirrored + READ inhibitNotificationsWhenScreensMirrored + WRITE setInhibitNotificationsWhenScreensMirrored + NOTIFY settingsChanged) + + /** + * Whether there currently are mirrored/overlapping screens + * + * This property is only updated when @c inhibitNotificationsWhenScreensMirrored + * is set to true, otherwise it is always false. + * You can assign false to this property if you want to temporarily revoke automatic do not disturb + * mode when screens are mirrored until the screen configuration changes. + * + * @since 5.17 + */ + Q_PROPERTY(bool screensMirrored READ screensMirrored WRITE setScreensMirrored NOTIFY screensMirroredChanged) + /** * Whether notification sounds should be disabled * @@ -299,6 +321,12 @@ QStringList notificationInhibitionApplications() const; QStringList notificationInhibitionReasons() const; + bool inhibitNotificationsWhenScreensMirrored() const; + void setInhibitNotificationsWhenScreensMirrored(bool mirrored); + + bool screensMirrored() const; + void setScreensMirrored(bool enable); + bool notificationSoundsInhibited() const; void setNotificationSoundsInhibited(bool inhibited); @@ -321,6 +349,8 @@ void notificationsInhibitedByApplicationChanged(bool notificationsInhibitedByApplication); void notificationInhibitionApplicationsChanged(); + void screensMirroredChanged(); + private: class Private; QScopedPointer d; diff --git a/libnotificationmanager/settings.cpp b/libnotificationmanager/settings.cpp --- a/libnotificationmanager/settings.cpp +++ b/libnotificationmanager/settings.cpp @@ -26,6 +26,7 @@ #include #include "server.h" +#include "mirroredscreenstracker_p.h" #include "debug.h" // Settings @@ -59,6 +60,8 @@ KConfigWatcher::Ptr watcher; QMetaObject::Connection watcherConnection; + MirroredScreensTracker::Ptr mirroredScreensTracker; + bool live = false; // set to true initially in constructor bool dirty = false; @@ -180,6 +183,11 @@ this, &Settings::notificationsInhibitedByApplicationChanged); connect(&Server::self(), &Server::inhibitionApplicationsChanged, this, &Settings::notificationInhibitionApplicationsChanged); + + if (DoNotDisturbSettings::whenScreensMirrored()) { + d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); + connect(d->mirroredScreensTracker.data(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); + } } Settings::~Settings() @@ -300,6 +308,22 @@ if (group.name() == QLatin1String("DoNotDisturb")) { DoNotDisturbSettings::self()->load(); + + bool emitScreensMirroredChanged = false; + if (DoNotDisturbSettings::whenScreensMirrored()) { + if (!d->mirroredScreensTracker) { + d->mirroredScreensTracker = MirroredScreensTracker::createTracker(); + emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); + connect(d->mirroredScreensTracker.data(), &MirroredScreensTracker::screensMirroredChanged, this, &Settings::screensMirroredChanged); + } + } else if (d->mirroredScreensTracker) { + emitScreensMirroredChanged = d->mirroredScreensTracker->screensMirrored(); + d->mirroredScreensTracker.reset(); + } + + if (emitScreensMirroredChanged) { + emit screensMirroredChanged(); + } } else if (group.name() == QLatin1String("Notifications")) { NotificationSettings::self()->load(); } else if (group.name() == QLatin1String("Jobs")) { @@ -541,6 +565,38 @@ return Server::self().inhibitionReasons(); } +bool Settings::inhibitNotificationsWhenScreensMirrored() const +{ + return DoNotDisturbSettings::whenScreensMirrored(); +} + +void Settings::setInhibitNotificationsWhenScreensMirrored(bool inhibit) +{ + if (inhibit == inhibitNotificationsWhenScreensMirrored()) { + return; + } + + DoNotDisturbSettings::setWhenScreensMirrored(inhibit); + d->setDirty(true); +} + +bool Settings::screensMirrored() const +{ + return d->mirroredScreensTracker && d->mirroredScreensTracker->screensMirrored(); +} + +void Settings::setScreensMirrored(bool mirrored) +{ + if (mirrored) { + qCWarning(NOTIFICATIONMANAGER) << "Cannot forcefully set screens mirrored"; + return; + } + + if (d->mirroredScreensTracker) { + d->mirroredScreensTracker->setScreensMirrored(mirrored); + } +} + void Settings::revokeApplicationInhibitions() { Server::self().clearInhibitions();