diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 41194f61c..8a0a8ab3a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -216,8 +216,10 @@ target_link_libraries(qbt_gui if (DBUS) target_sources(qbt_gui PRIVATE - qtnotify/notifications.h - qtnotify/notifications.cpp + notifications/dbusnotifier.h + notifications/dbusnotifier.cpp + notifications/dbusnotificationsinterface.h + notifications/dbusnotificationsinterface.cpp powermanagement/powermanagement_x11.h powermanagement/powermanagement_x11.cpp ) diff --git a/src/gui/gui.pri b/src/gui/gui.pri index d5ebadae7..4459133b8 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -211,12 +211,14 @@ win32|macx { unix:!macx:dbus { HEADERS += \ - $$PWD/powermanagement/powermanagement_x11.h \ - $$PWD/qtnotify/notifications.h + $$PWD/notifications/dbusnotifier.h \ + $$PWD/notifications/dbusnotificationsinterface.h \ + $$PWD/powermanagement/powermanagement_x11.h SOURCES += \ - $$PWD/powermanagement/powermanagement_x11.cpp \ - $$PWD/qtnotify/notifications.cpp + $$PWD/notifications/dbusnotifier.cpp \ + $$PWD/notifications/dbusnotificationsinterface.cpp \ + $$PWD/powermanagement/powermanagement_x11.cpp } macx { diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index b72ac22ff..e5f16f9f6 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -50,9 +50,8 @@ #include #include -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB) -#include -#include "qtnotify/notifications.h" +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS +#include "notifications/dbusnotifier.h" #endif #include "base/bittorrent/session.h" @@ -147,7 +146,7 @@ MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) , m_storeNotificationEnabled(NOTIFICATIONS_SETTINGS_KEY(u"Enabled"_qs)) , m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_qs)) , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_qs), Log::MsgType::ALL) -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB) +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS , m_storeNotificationTimeOut(NOTIFICATIONS_SETTINGS_KEY(u"Timeout"_qs)) #endif { @@ -203,6 +202,14 @@ MainWindow::MainWindow(IGUIApplication *app, QWidget *parent) m_ui->actionLock->setVisible(true); }); +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS + if (isNotificationsEnabled()) + { + m_notifier = new DBusNotifier(this); + connect(m_notifier, &DBusNotifier::messageClicked, this, &MainWindow::balloonClicked); + } +#endif + // Creating Bittorrent session updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled()); @@ -529,9 +536,25 @@ bool MainWindow::isNotificationsEnabled() const return m_storeNotificationEnabled.get(true); } -void MainWindow::setNotificationsEnabled(bool value) +void MainWindow::setNotificationsEnabled(const bool value) { + if (m_storeNotificationEnabled == value) + return; + m_storeNotificationEnabled = value; + +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS + if (value) + { + m_notifier = new DBusNotifier(this); + connect(m_notifier, &DBusNotifier::messageClicked, this, &MainWindow::balloonClicked); + } + else + { + delete m_notifier; + m_notifier = nullptr; + } +#endif } bool MainWindow::isTorrentAddedNotificationsEnabled() const @@ -1688,27 +1711,8 @@ void MainWindow::showNotificationBalloon(const QString &title, const QString &ms if (!isNotificationsEnabled()) return; -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB) - OrgFreedesktopNotificationsInterface notifications(u"org.freedesktop.Notifications"_qs - , u"/org/freedesktop/Notifications"_qs - , QDBusConnection::sessionBus()); - - // Testing for 'notifications.isValid()' isn't helpful here. - // If the notification daemon is configured to run 'as needed' - // the above check can be false if the daemon wasn't started - // by another application. In this case DBus will be able to - // start the notification daemon and complete our request. Such - // a daemon is xfce4-notifyd, DBus autostarts it and after - // some inactivity shuts it down. Other DEs, like GNOME, choose - // to start their daemons at the session startup and have it sit - // idling for the whole session. - const QVariantMap hints {{u"desktop-entry"_qs, u"org.qbittorrent.qBittorrent"_qs}}; - QDBusPendingReply reply = notifications.Notify(u"qBittorrent"_qs, 0 - , u"qbittorrent"_qs, title, msg, {}, hints, getNotificationTimeout()); - - reply.waitForFinished(); - if (!reply.isError()) - return; +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS + m_notifier->showMessage(title, msg, getNotificationTimeout()); #elif defined(Q_OS_MACOS) MacUtils::displayNotification(title, msg); #else @@ -1755,7 +1759,9 @@ void MainWindow::createTrayIcon(const int retries) m_systrayIcon->setContextMenu(m_trayIconMenu); connect(m_systrayIcon, &QSystemTrayIcon::activated, this, &MainWindow::toggleVisibility); +#ifndef QBT_USES_CUSTOMDBUSNOTIFICATIONS connect(m_systrayIcon, &QSystemTrayIcon::messageClicked, this, &MainWindow::balloonClicked); +#endif m_systrayIcon->show(); emit systemTrayIconCreated(); diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index a890c0478..810d68b59 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -72,6 +72,11 @@ namespace Ui class MainWindow; } +#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB) +#define QBT_USES_CUSTOMDBUSNOTIFICATIONS +class DBusNotifier; +#endif + class MainWindow final : public QMainWindow, public GUIApplicationComponent { Q_OBJECT @@ -266,8 +271,9 @@ private: SettingValue m_storeNotificationTorrentAdded; CachedSettingValue m_storeExecutionLogTypes; -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB) +#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS SettingValue m_storeNotificationTimeOut; + DBusNotifier *m_notifier = nullptr; #endif #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) diff --git a/src/gui/notifications/dbusnotificationsinterface.cpp b/src/gui/notifications/dbusnotificationsinterface.cpp new file mode 100644 index 000000000..cfcecb4a4 --- /dev/null +++ b/src/gui/notifications/dbusnotificationsinterface.cpp @@ -0,0 +1,76 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "dbusnotificationsinterface.h" + +#include +#include +#include + +#include "base/global.h" + +DBusNotificationsInterface::DBusNotificationsInterface(const QString &service + , const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, DBUS_INTERFACE_NAME, connection, parent) +{ +} + +QDBusPendingReply DBusNotificationsInterface::getCapabilities() +{ + return asyncCall(u"GetCapabilities"_qs); +} + +QDBusPendingReply DBusNotificationsInterface::getServerInformation() +{ + return asyncCall(u"GetServerInformation"_qs); +} + +QDBusReply DBusNotificationsInterface::getServerInformation(QString &vendor, QString &version, QString &specVersion) +{ + const QDBusMessage reply = call(QDBus::Block, u"GetServerInformation"_qs); + if ((reply.type() == QDBusMessage::ReplyMessage) && (reply.arguments().count() == 4)) + { + vendor = qdbus_cast(reply.arguments().at(1)); + version = qdbus_cast(reply.arguments().at(2)); + specVersion = qdbus_cast(reply.arguments().at(3)); + } + + return reply; +} + +QDBusPendingReply DBusNotificationsInterface::notify(const QString &appName + , const uint id, const QString &icon, const QString &summary, const QString &body + , const QStringList &actions, const QVariantMap &hints, const int timeout) +{ + return asyncCall(u"Notify"_qs, appName, id, icon, summary, body, actions, hints, timeout); +} + +QDBusPendingReply<> DBusNotificationsInterface::closeNotification(const uint id) +{ + return asyncCall(u"CloseNotification"_qs, id); +} diff --git a/src/gui/notifications/dbusnotificationsinterface.h b/src/gui/notifications/dbusnotificationsinterface.h new file mode 100644 index 000000000..69700b7c2 --- /dev/null +++ b/src/gui/notifications/dbusnotificationsinterface.h @@ -0,0 +1,64 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include +#include +#include + +class QDBusConnection; +class QString; +class QVariant; + +class DBusNotificationsInterface final : public QDBusAbstractInterface +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(DBusNotificationsInterface) + +public: + inline static const char DBUS_INTERFACE_NAME[] = "org.freedesktop.Notifications"; + + DBusNotificationsInterface(const QString &service, const QString &path + , const QDBusConnection &connection, QObject *parent = nullptr); + +public slots: + QDBusPendingReply getCapabilities(); + QDBusPendingReply getServerInformation(); + QDBusReply getServerInformation(QString &vendor, QString &version, QString &specVersion); + QDBusPendingReply notify(const QString &appName + , uint id, const QString &icon, const QString &summary, const QString &body + , const QStringList &actions, const QVariantMap &hints, int timeout); + QDBusPendingReply<> closeNotification(uint id); + +signals: + // Signal names must exactly match the ones from corresponding D-Bus interface + void ActionInvoked(uint id, const QString &action); + void NotificationClosed(uint id, uint reason); +}; diff --git a/src/gui/notifications/dbusnotifier.cpp b/src/gui/notifications/dbusnotifier.cpp new file mode 100644 index 000000000..aecb3eb0a --- /dev/null +++ b/src/gui/notifications/dbusnotifier.cpp @@ -0,0 +1,94 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include "dbusnotifier.h" + +#include +#include +#include + +#include "base/global.h" +#include "dbusnotificationsinterface.h" + +DBusNotifier::DBusNotifier(QObject *parent) + : QObject(parent) + , m_notificationsInterface {new DBusNotificationsInterface(u"org.freedesktop.Notifications"_qs + , u"/org/freedesktop/Notifications"_qs, QDBusConnection::sessionBus(), this)} +{ + // Testing for 'DBusNotificationsInterface::isValid()' isn't helpful here. + // If the notification daemon is configured to run 'as needed' + // the above check can be false if the daemon wasn't started + // by another application. In this case DBus will be able to + // start the notification daemon and complete our request. Such + // a daemon is xfce4-notifyd, DBus autostarts it and after + // some inactivity shuts it down. Other DEs, like GNOME, choose + // to start their daemons at the session startup and have it sit + // idling for the whole session. + + connect(m_notificationsInterface, &DBusNotificationsInterface::ActionInvoked, this, &DBusNotifier::onActionInvoked); + connect(m_notificationsInterface, &DBusNotificationsInterface::NotificationClosed, this, &DBusNotifier::onNotificationClosed); +} + +void DBusNotifier::showMessage(const QString &title, const QString &message, const int timeout) +{ + // Assign "default" action to notification to make it clickable + const QStringList actions {u"default"_qs, {}}; + const QVariantMap hints {{u"desktop-entry"_qs, u"org.qbittorrent.qBittorrent"_qs}}; + const QDBusPendingReply reply = m_notificationsInterface->notify(u"qBittorrent"_qs, 0 + , u"qbittorrent"_qs, title, message, actions, hints, timeout); + auto *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self) + { + const QDBusPendingReply reply = *self; + if (!reply.isError()) + { + const uint messageID = reply.value(); + m_activeMessages.insert(messageID); + } + + self->deleteLater(); + }); +} + +void DBusNotifier::onActionInvoked(const uint messageID, const QString &action) +{ + Q_UNUSED(action); + + // Check whether the notification is sent by qBittorrent + // to avoid reacting to unrelated notifictions + if (m_activeMessages.contains(messageID)) + emit messageClicked(); +} + +void DBusNotifier::onNotificationClosed(const uint messageID, const uint reason) +{ + Q_UNUSED(reason); + + m_activeMessages.remove(messageID); +} diff --git a/src/gui/notifications/dbusnotifier.h b/src/gui/notifications/dbusnotifier.h new file mode 100644 index 000000000..db61d9a6a --- /dev/null +++ b/src/gui/notifications/dbusnotifier.h @@ -0,0 +1,56 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Vladimir Golovnev + * Copyright (C) 2006 Christophe Dumez + * + * 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 the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +#include +#include + +class DBusNotificationsInterface; + +class DBusNotifier final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(DBusNotifier) + +public: + explicit DBusNotifier(QObject *parent = nullptr); + + void showMessage(const QString &title, const QString &message, int timeout); + +signals: + void messageClicked(); + +private: + void onActionInvoked(uint messageID, const QString &action); + void onNotificationClosed(uint messageID, uint reason); + + DBusNotificationsInterface *m_notificationsInterface = nullptr; + QSet m_activeMessages; +}; diff --git a/src/gui/qtnotify/notifications.cpp b/src/gui/qtnotify/notifications.cpp deleted file mode 100644 index a91578b49..000000000 --- a/src/gui/qtnotify/notifications.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml - * - * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. - * - * This is an auto-generated file. - * This file may have been hand-edited. Look for HAND-EDIT comments - * before re-generating it. - */ - -#include "notifications.h" - -/* - * Implementation of interface class OrgFreedesktopNotificationsInterface - */ - -OrgFreedesktopNotificationsInterface::OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) - : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) -{ -} - -OrgFreedesktopNotificationsInterface::~OrgFreedesktopNotificationsInterface() -{ -} diff --git a/src/gui/qtnotify/notifications.h b/src/gui/qtnotify/notifications.h deleted file mode 100644 index 7317b443b..000000000 --- a/src/gui/qtnotify/notifications.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file was generated by qdbusxml2cpp version 0.8 - * Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml - * - * qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd. - * - * This is an auto-generated file. - * Do not edit! All changes made to it will be lost. - */ - -#ifndef NOTIFICATIONS_H -#define NOTIFICATIONS_H - -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * Proxy class for interface org.freedesktop.Notifications - */ -class OrgFreedesktopNotificationsInterface: public QDBusAbstractInterface -{ - Q_OBJECT -public: - static inline const char *staticInterfaceName() - { return "org.freedesktop.Notifications"; } - -public: - OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); - - ~OrgFreedesktopNotificationsInterface(); - -public Q_SLOTS: // METHODS - inline QDBusPendingReply<> CloseNotification(uint id) - { - QList argumentList; - argumentList << QVariant::fromValue(id); - return asyncCallWithArgumentList(QStringLiteral("CloseNotification"), argumentList); - } - - inline QDBusPendingReply GetCapabilities() - { - QList argumentList; - return asyncCallWithArgumentList(QStringLiteral("GetCapabilities"), argumentList); - } - - inline QDBusPendingReply GetServerInformation() - { - QList argumentList; - return asyncCallWithArgumentList(QStringLiteral("GetServerInformation"), argumentList); - } - inline QDBusReply GetServerInformation(QString &return_vendor, QString &return_version, QString &return_spec_version) - { - QList argumentList; - QDBusMessage reply = callWithArgumentList(QDBus::Block, QStringLiteral("GetServerInformation"), argumentList); - if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) { - return_vendor = qdbus_cast(reply.arguments().at(1)); - return_version = qdbus_cast(reply.arguments().at(2)); - return_spec_version = qdbus_cast(reply.arguments().at(3)); - } - return reply; - } - - inline QDBusPendingReply Notify(const QString &app_name, uint id, const QString &icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout) - { - QList argumentList; - argumentList << QVariant::fromValue(app_name) << QVariant::fromValue(id) << QVariant::fromValue(icon) << QVariant::fromValue(summary) << QVariant::fromValue(body) << QVariant::fromValue(actions) << QVariant::fromValue(hints) << QVariant::fromValue(timeout); - return asyncCallWithArgumentList(QStringLiteral("Notify"), argumentList); - } - -Q_SIGNALS: // SIGNALS -}; - -namespace org { - namespace freedesktop { - typedef ::OrgFreedesktopNotificationsInterface Notifications; - } -} -#endif diff --git a/src/gui/qtnotify/notifications.xml b/src/gui/qtnotify/notifications.xml deleted file mode 100644 index 00e85ea53..000000000 --- a/src/gui/qtnotify/notifications.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -