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