From 97e843d3ead33fc8f376fe08040fa1f923b831e6 Mon Sep 17 00:00:00 2001 From: Kai Uwe Broulik Date: Mon, 18 May 2020 13:21:43 +0200 Subject: [PATCH] [Free Space Notifer] Use critical notification instead of tray icon and monitor Root, too This refactors the free space notifier module to use a critical (i.e. persistent and always on top) notification for warning of low disk space. The, albeit blinking, tray icon is easy to miss, especially when you're running a full screen terminal, which I typically do while compiling stuff. It now also monitors the Root folder, if it's on a separate partition from the user's home. Furthermore, Filelight is offered to explore the drive (if installed). The overall warning logic remains pretty much the same: * Once the drive goes below the configured threshold a warning notification is shown, it stays on screen until dismissed by the user or when free space is above warning threshold again. * The notification is emitted again when free space drops below half the previous threshold, for added sense of urgency should the drive be rapidly filled up * The notification is also emitted again if free space remains below the threshold for more than one hour (It will only emit again when it was closed, obviously, so you won't end up with a tonne of popups after a few hours ;) I don't think this needs to be separately configurable for Home and Root, since the default threshold is like 200 MiB (it's not a configured percentage), so the absolute free space it warns about will be the same, even if your Home is giant compared to Root. BUG: 340582 FIXED-IN: 5.20.0 Differential Revision: https://phabricator.kde.org/D29770 --- freespacenotifier/CMakeLists.txt | 5 +- freespacenotifier/freespacenotifier.cpp | 244 ++++++------------- freespacenotifier/freespacenotifier.h | 41 ++-- freespacenotifier/freespacenotifier.notifyrc | 1 + freespacenotifier/module.cpp | 62 +++++ freespacenotifier/module.h | 4 +- 6 files changed, 169 insertions(+), 188 deletions(-) diff --git a/freespacenotifier/CMakeLists.txt b/freespacenotifier/CMakeLists.txt index 259bc66f5..0ba68ed9f 100644 --- a/freespacenotifier/CMakeLists.txt +++ b/freespacenotifier/CMakeLists.txt @@ -4,6 +4,8 @@ set(kded_freespacenotifier_SRCS freespacenotifier.cpp module.cpp) ki18n_wrap_ui(kded_freespacenotifier_SRCS freespacenotifier_prefs_base.ui) +qt5_add_dbus_interface(kded_freespacenotifier_SRCS ${KDED_DBUS_INTERFACE} kded_interface) + kconfig_add_kcfg_files(kded_freespacenotifier_SRCS settings.kcfgc) add_library(freespacenotifier MODULE ${kded_freespacenotifier_SRCS}) @@ -14,8 +16,9 @@ target_link_libraries(freespacenotifier KF5::DBusAddons KF5::I18n KF5::KIOCore - KF5::KIOWidgets + KF5::KIOGui KF5::Notifications + KF5::Service ) install(TARGETS freespacenotifier DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded ) diff --git a/freespacenotifier/freespacenotifier.cpp b/freespacenotifier/freespacenotifier.cpp index fee305874..c34b6d001 100644 --- a/freespacenotifier/freespacenotifier.cpp +++ b/freespacenotifier/freespacenotifier.cpp @@ -2,6 +2,7 @@ Copyright (c) 2006 Lukas Tinkl Copyright (c) 2008 Lubos Lunak Copyright (c) 2009 Ivo Anjo + Copyright (c) 2020 Kai Uwe Broulik This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,47 +20,33 @@ #include "freespacenotifier.h" -#include -#include -#include -#include - #include -#include -#include -#include #include +#include +#include +#include #include +#include + +#include #include "settings.h" -#include "ui_freespacenotifier_prefs_base.h" -FreeSpaceNotifier::FreeSpaceNotifier(QObject *parent) +FreeSpaceNotifier::FreeSpaceNotifier(const QString &path, const KLocalizedString ¬ificationText, QObject *parent) : QObject(parent) - , m_lastAvailTimer(nullptr) - , m_notification(nullptr) - , m_sni(nullptr) - , m_lastAvail(-1) + , m_path(path) + , m_notificationText(notificationText) { - // If we are running, notifications are enabled - FreeSpaceNotifierSettings::setEnableNotification(true); - - connect(&timer, &QTimer::timeout, this, &FreeSpaceNotifier::checkFreeDiskSpace); - timer.start(1000 * 60 /* 1 minute */); + connect(&m_timer, &QTimer::timeout, this, &FreeSpaceNotifier::checkFreeDiskSpace); + m_timer.start(std::chrono::minutes(1)); } FreeSpaceNotifier::~FreeSpaceNotifier() { - // The notification is automatically destroyed when it goes away, so we only need to do this if - // it is still being shown if (m_notification) { m_notification->close(); } - - if (m_sni) { - m_sni->deleteLater(); - } } void FreeSpaceNotifier::checkFreeDiskSpace() @@ -67,146 +54,109 @@ void FreeSpaceNotifier::checkFreeDiskSpace() if (!FreeSpaceNotifierSettings::enableNotification()) { // do nothing if notifying is disabled; // also stop the timer that probably got us here in the first place - timer.stop(); - + m_timer.stop(); return; } - auto *job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(QDir::homePath())); + auto *job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(m_path)); connect(job, &KIO::FileSystemFreeSpaceJob::result, this, [this](KIO::Job* job, KIO::filesize_t size, KIO::filesize_t available) { if (job->error()) { return; } - int limit = FreeSpaceNotifierSettings::minimumSpace(); // MiB - qint64 avail = available / (1024 * 1024); // to MiB - bool warn = false; + const int limit = FreeSpaceNotifierSettings::minimumSpace(); // MiB + const qint64 avail = available / (1024 * 1024); // to MiB - if (avail < limit) { - // avail disk space dropped under a limit - if (m_lastAvail < 0 || avail < m_lastAvail / 2) { // always warn the first time or when available dropped to a half of previous one, warn again - m_lastAvail = avail; - warn = true; - } else if (avail > m_lastAvail) { // the user freed some space - m_lastAvail = avail; // so warn if it goes low again - if (m_sni) { - // keep the SNI active, but don't blink - m_sni->setStatus(KStatusNotifierItem::Active); - m_sni->setToolTip(QStringLiteral("drive-harddisk"), i18n("Low Disk Space"), i18n("Remaining space in your Home folder: %1 MiB", QLocale::system().toString(avail))); - } + if (avail >= limit) { + if (m_notification) { + m_notification->close(); } - // do not change lastAvail otherwise, to handle free space slowly going down + return; + } - if (warn) { - int availpct = int(100 * available / size); - if (!m_sni) { - m_sni = new KStatusNotifierItem(QStringLiteral("freespacenotifier")); - m_sni->setIconByName(QStringLiteral("drive-harddisk")); - m_sni->setOverlayIconByName(QStringLiteral("dialog-warning")); - m_sni->setTitle(i18n("Low Disk Space")); - m_sni->setCategory(KStatusNotifierItem::Hardware); + const int availPercent = int(100 * available / size); + const QString text = m_notificationText.subs(avail).subs(availPercent).toString(); - QMenu *sniMenu = new QMenu(); - QAction *action = new QAction(i18nc("Opens a file manager like dolphin", "Open File Manager..."), nullptr); - connect(action, &QAction::triggered, this, &FreeSpaceNotifier::openFileManager); - sniMenu->addAction(action); + // Make sure the notification text is always up to date whenever we checked free space + if (m_notification) { + m_notification->setText(text); + } - action = new QAction(i18nc("Allows the user to configure the warning notification being shown", "Configure Warning..."), nullptr); - connect(action, &QAction::triggered, this, &FreeSpaceNotifier::showConfiguration); - sniMenu->addAction(action); + // User freed some space, warn if it goes low again + if (m_lastAvail > -1 && avail > m_lastAvail) { + m_lastAvail = avail; + return; + } - action = new QAction(i18nc("Allows the user to hide this notifier item", "Hide"), nullptr); - connect(action, &QAction::triggered, this, &FreeSpaceNotifier::hideSni); - sniMenu->addAction(action); + // Always warn the first time or when available space dropped to half of the previous time + const bool warn = (m_lastAvail < 0 || avail < m_lastAvail / 2); + if (!warn) { + return; + } - m_sni->setContextMenu(sniMenu); - m_sni->setStandardActionsEnabled(false); - } + m_lastAvail = avail; - m_sni->setStatus(KStatusNotifierItem::NeedsAttention); - m_sni->setToolTip(QStringLiteral("drive-harddisk"), i18n("Low Disk Space"), i18n("Remaining space in your Home folder: %1 MiB", QLocale::system().toString(avail))); + if (!m_notification) { + m_notification = new KNotification(QStringLiteral("freespacenotif")); + m_notification->setComponentName(QStringLiteral("freespacenotifier")); + m_notification->setText(text); - m_notification = new KNotification(QStringLiteral("freespacenotif")); + QStringList actions = {i18n("Configure Warning...")}; - m_notification->setText(i18nc("Warns the user that the system is running low on space on his home folder, indicating the percentage and absolute MiB size remaining", - "Your Home folder is running out of disk space, you have %1 MiB remaining (%2%)", QLocale::system().toString(avail), availpct)); + auto filelight = filelightService(); + if (filelight) { + actions.prepend(i18n("Open in Filelight")); + } else { + // Do we really want the user opening Root in a file manager? + actions.prepend(i18n("Open in File Manager")); + } - connect(m_notification, &KNotification::closed, this, &FreeSpaceNotifier::cleanupNotification); + m_notification->setActions(actions); - m_notification->setComponentName(QStringLiteral("freespacenotifier")); - m_notification->sendEvent(); - } - } else { - // free space is above limit again, remove the SNI - if (m_sni) { - m_sni->deleteLater(); - m_sni = nullptr; - } - } - }); -} + connect(m_notification, QOverload::of(&KNotification::activated), this, [this](uint actionId) { + if (actionId == 1) { + exploreDrive(); + // TODO once we have "configure" action support in KNotification, wire it up instead of a button + } else if (actionId == 2) { + emit configureRequested(); + } + }); -void FreeSpaceNotifier::hideSni() -{ - if (m_sni) { - m_sni->setStatus(KStatusNotifierItem::Passive); - QAction *action = qobject_cast(sender()); - if (action) { - action->setDisabled(true); + connect(m_notification, &KNotification::closed, this, &FreeSpaceNotifier::onNotificationClosed); + m_notification->sendEvent(); } - } + }); } -void FreeSpaceNotifier::openFileManager() +KService::Ptr FreeSpaceNotifier::filelightService() const { - cleanupNotification(); - new KRun(QUrl::fromLocalFile(QDir::homePath()), nullptr); - - if (m_sni) { - m_sni->setStatus(KStatusNotifierItem::Active); - } + return KService::serviceByDesktopName(QStringLiteral("org.kde.filelight")); } -void FreeSpaceNotifier::showConfiguration() +void FreeSpaceNotifier::exploreDrive() { - cleanupNotification(); - - if (KConfigDialog::showDialog(QStringLiteral("settings"))) { + auto service = filelightService(); + if (!service) { + auto *job = new KIO::OpenUrlJob({QUrl::fromLocalFile(m_path)}); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); + job->start(); return; } - KConfigDialog *dialog = new KConfigDialog(nullptr, QStringLiteral("settings"), FreeSpaceNotifierSettings::self()); - QWidget *generalSettingsDlg = new QWidget(); - - Ui::freespacenotifier_prefs_base preferences; - preferences.setupUi(generalSettingsDlg); - - dialog->addPage(generalSettingsDlg, - i18nc("The settings dialog main page name, as in 'general settings'", "General"), - QStringLiteral("system-run")); - - connect(dialog, &KConfigDialog::finished, this, &FreeSpaceNotifier::configDialogClosed); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->show(); - - if (m_sni) { - m_sni->setStatus(KStatusNotifierItem::Active); - } + auto *job = new KIO::ApplicationLauncherJob(service); + job->setUrls({QUrl::fromLocalFile(m_path)}); + job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled)); + job->start(); } -void FreeSpaceNotifier::cleanupNotification() +void FreeSpaceNotifier::onNotificationClosed() { - if (m_notification) { - m_notification->close(); - } - m_notification = nullptr; - // warn again if constantly below limit for too long if (!m_lastAvailTimer) { m_lastAvailTimer = new QTimer(this); connect(m_lastAvailTimer, &QTimer::timeout, this, &FreeSpaceNotifier::resetLastAvailable); } - m_lastAvailTimer->start(1000 * 60 * 60 /* 1 hour*/); + m_lastAvailTimer->start(std::chrono::hours(1)); } void FreeSpaceNotifier::resetLastAvailable() @@ -215,45 +165,3 @@ void FreeSpaceNotifier::resetLastAvailable() m_lastAvailTimer->deleteLater(); m_lastAvailTimer = nullptr; } - -void FreeSpaceNotifier::configDialogClosed() -{ - if (!FreeSpaceNotifierSettings::enableNotification()) { - disableFSNotifier(); - } -} - -/* The idea here is to disable ourselves by telling kded to stop autostarting us, and - * to kill the current running instance. - */ -void FreeSpaceNotifier::disableFSNotifier() -{ - QDBusInterface iface(QStringLiteral("org.kde.kded5"), - QStringLiteral("/kded"), - QStringLiteral("org.kde.kded5") ); - if (dbusError(iface)) { - return; - } - - // Disable current module autoload - iface.call(QStringLiteral("setModuleAutoloading"), QStringLiteral("freespacenotifier"), false); - if (dbusError(iface)) { - return; - } - - // Unload current module - iface.call(QStringLiteral("unloadModule"), QStringLiteral("freespacenotifier")); - if (dbusError(iface)) { - return; - } -} - -bool FreeSpaceNotifier::dbusError(QDBusInterface &iface) -{ - const QDBusError err = iface.lastError(); - if (err.isValid()) { - qCritical() << "Failed to perform operation on kded [" << err.name() << "]:" << err.message(); - return true; - } - return false; -} diff --git a/freespacenotifier/freespacenotifier.h b/freespacenotifier/freespacenotifier.h index 4a222960b..8ccbeef59 100644 --- a/freespacenotifier/freespacenotifier.h +++ b/freespacenotifier/freespacenotifier.h @@ -2,6 +2,7 @@ Copyright (c) 2006 Lukas Tinkl Copyright (c) 2008 Lubos Lunak Copyright (c) 2009 Ivo Anjo + Copyright (c) 2020 Kai Uwe Broulik This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,37 +22,41 @@ #define _FREESPACENOTIFIER_H_ #include +#include + +#include +#include class KNotification; -class KStatusNotifierItem; -class QDBusInterface; class FreeSpaceNotifier : public QObject { Q_OBJECT public: - explicit FreeSpaceNotifier(QObject *parent = nullptr); + explicit FreeSpaceNotifier(const QString &path, const KLocalizedString ¬ificationText, QObject *parent = nullptr); ~FreeSpaceNotifier() override; -private Q_SLOTS: +signals: + void configureRequested(); + +private: void checkFreeDiskSpace(); void resetLastAvailable(); - void openFileManager(); - void showConfiguration(); - void cleanupNotification(); - void configDialogClosed(); - void hideSni(); -private: - QTimer timer; - QTimer *m_lastAvailTimer; - KNotification *m_notification; - KStatusNotifierItem *m_sni; - qint64 m_lastAvail; // used to suppress repeated warnings when available space hasn't changed - - void disableFSNotifier(); - bool dbusError(QDBusInterface &iface); + KService::Ptr filelightService() const; + void exploreDrive(); + void onNotificationClosed(); + + QString m_path; + KLocalizedString m_notificationText; + + QTimer m_timer; + QTimer *m_lastAvailTimer = nullptr; + QPointer m_notification; + qint64 m_lastAvail = -1; // used to suppress repeated warnings when available space hasn't changed + + }; #endif diff --git a/freespacenotifier/freespacenotifier.notifyrc b/freespacenotifier/freespacenotifier.notifyrc index 511824f6b..3b9da4193 100644 --- a/freespacenotifier/freespacenotifier.notifyrc +++ b/freespacenotifier/freespacenotifier.notifyrc @@ -416,3 +416,4 @@ Comment[zh_CN]=您现在的磁盘空间过低 Comment[zh_TW]=您的磁碟空間快用完了 Contexts=warningnot Action=Popup +Urgency=Critical diff --git a/freespacenotifier/module.cpp b/freespacenotifier/module.cpp index cdfb76155..2771992fd 100644 --- a/freespacenotifier/module.cpp +++ b/freespacenotifier/module.cpp @@ -19,13 +19,75 @@ #include "module.h" +#include +#include #include +#include + +#include "kded_interface.h" + +#include "ui_freespacenotifier_prefs_base.h" + +#include "settings.h" + K_PLUGIN_CLASS_WITH_JSON(FreeSpaceNotifierModule, "freespacenotifier.json") FreeSpaceNotifierModule::FreeSpaceNotifierModule(QObject* parent, const QList&) : KDEDModule(parent) { + // If the module is loaded, notifications are enabled + FreeSpaceNotifierSettings::setEnableNotification(true); + + const QString rootPath = QStringLiteral("/"); + const QString homePath = QDir::homePath(); + + auto *homeNotifier = new FreeSpaceNotifier(homePath, + ki18n("Your Home folder is running out of disk space, you have %1 MiB remaining (%2%)."), + this); + connect(homeNotifier, &FreeSpaceNotifier::configureRequested, this, &FreeSpaceNotifierModule::showConfiguration); + + // If Home is on a separate partition from Root, warn for it, too. + auto homeMountPoint = KMountPoint::currentMountPoints().findByPath(homePath); + if (!homeMountPoint || homeMountPoint->mountPoint() != rootPath) { + auto *rootNotifier = new FreeSpaceNotifier(rootPath, + ki18n("Your Root partition is running out of disk space, you have %1 MiB remaining (%2%)."), + this); + connect(rootNotifier, &FreeSpaceNotifier::configureRequested, this, &FreeSpaceNotifierModule::showConfiguration); + } + +} + +void FreeSpaceNotifierModule::showConfiguration() +{ + if (KConfigDialog::showDialog(QStringLiteral("settings"))) { + return; + } + + KConfigDialog *dialog = new KConfigDialog(nullptr, QStringLiteral("settings"), FreeSpaceNotifierSettings::self()); + QWidget *generalSettingsDlg = new QWidget(); + + Ui::freespacenotifier_prefs_base preferences; + preferences.setupUi(generalSettingsDlg); + + dialog->addPage(generalSettingsDlg, + i18nc("The settings dialog main page name, as in 'general settings'", "General"), + QStringLiteral("system-run")); + + connect(dialog, &KConfigDialog::finished, this, [this] { + if (!FreeSpaceNotifierSettings::enableNotification()) { + // The idea here is to disable ourselves by telling kded to stop autostarting us, and + // to kill the current running instance. + org::kde::kded5 kded(QStringLiteral("org.kde.kded5"), + QStringLiteral("/kded"), + QDBusConnection::sessionBus()); + kded.setModuleAutoloading(QStringLiteral("freespacenotifier"), false); + kded.unloadModule(QStringLiteral("freespacenotifier")); + } + }); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); } #include "module.moc" diff --git a/freespacenotifier/module.h b/freespacenotifier/module.h index 17606e583..aca41466b 100644 --- a/freespacenotifier/module.h +++ b/freespacenotifier/module.h @@ -31,8 +31,10 @@ class FreeSpaceNotifierModule Q_OBJECT public: FreeSpaceNotifierModule(QObject* parent, const QList&); + private: - FreeSpaceNotifier notifier; + void showConfiguration(); + }; #endif