Add class to show notifications via D-Bus

This commit is contained in:
Martchus 2016-12-11 17:31:49 +01:00
parent 31ac09de87
commit e5481f5f43
4 changed files with 516 additions and 0 deletions

View File

@ -133,6 +133,29 @@ else()
endif()
endif()
# configure support for D-Bus notifications
option(DBUS_NOTIFICATIONS "enables support for D-Bus notifications" ${UNIX})
set(DBUS_NOTIFICATIONS_FILE_NAME misc/dbusnotification)
if(DBUS_NOTIFICATIONS)
list(APPEND HEADER_FILES
${DBUS_NOTIFICATIONS_FILE_NAME}.h
)
list(APPEND SRC_FILES
${DBUS_NOTIFICATIONS_FILE_NAME}.cpp
)
list(APPEND DBUS_FILES
dbus/org.freedesktop.Notifications.xml
)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS)
message(STATUS "D-Bus notifications enabled")
else()
list(APPEND DOC_ONLY_FILES
${DBUS_NOTIFICATIONS_FILE_NAME}.h
${DBUS_NOTIFICATIONS_FILE_NAME}.cpp
)
message(STATUS "D-Bus notifications disabled")
endif()
# find c++utilities
find_package(c++utilities 4.0.0 REQUIRED)
use_cpp_utilities()

View File

@ -0,0 +1,37 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.Notifications">
<signal name="NotificationClosed">
<arg name="id" type="u" direction="out"/>
<arg name="reason" type="u" direction="out"/>
</signal>
<signal name="ActionInvoked">
<arg name="id" type="u" direction="out"/>
<arg name="action_key" type="s" direction="out"/>
</signal>
<method name="Notify">
<annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
<arg type="u" direction="out"/>
<arg name="app_name" type="s" direction="in"/>
<arg name="replaces_id" type="u" direction="in"/>
<arg name="app_icon" type="s" direction="in"/>
<arg name="summary" type="s" direction="in"/>
<arg name="body" type="s" direction="in"/>
<arg name="actions" type="as" direction="in"/>
<arg name="hints" type="a{sv}" direction="in"/>
<arg name="timeout" type="i" direction="in"/>
</method>
<method name="CloseNotification">
<arg name="id" type="u" direction="in"/>
</method>
<method name="GetCapabilities">
<arg type="as" name="caps" direction="out"/>
</method>
<method name="GetServerInformation">
<arg type="s" name="name" direction="out"/>
<arg type="s" name="vendor" direction="out"/>
<arg type="s" name="version" direction="out"/>
<arg type="s" name="spec_version" direction="out"/>
</method>
</interface>
</node>

281
misc/dbusnotification.cpp Normal file
View File

@ -0,0 +1,281 @@
#include "./dbusnotification.h"
#include "notificationsinterface.h"
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusPendingReply>
#include <map>
using namespace std;
namespace MiscUtils {
/*!
* \class DBusNotification
* \brief The DBusNotification class emits D-Bus notifications.
*
* D-Bus notifications are only available if the library has been compiled with support for it by specifying
* CMake option `DBUS_NOTIFICATIONS`. If support is available, the macro `QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS`
* is defined.
*
* **Usage**
*
* First create a new instance. The constructor allows to set basic parameters. To set more parameters, use
* setter methods. Call show() to actually show the notification. This method can also be used to update
* the currently shown notification (it will not be updated automatically by just using the setter methods).
*
* \sa https://developer.gnome.org/notification-spec
*/
/// \cond
static std::map<uint, DBusNotification *> pendingNotifications;
OrgFreedesktopNotificationsInterface *DBusNotification::m_dbusInterface = nullptr;
/// \endcond
/*!
* \brief Creates a new notification (which is *not* shown instantly).
*/
DBusNotification::DBusNotification(const QString &title, NotificationIcon icon, int timeout, QObject *parent) :
QObject(parent),
m_id(0),
m_watcher(nullptr),
m_title(title),
m_timeout(timeout)
{
initInterface();
setIcon(icon);
}
/*!
* \brief Creates a new notification (which is *not* shown instantly).
*/
DBusNotification::DBusNotification(const QString &title, const QString &icon, int timeout, QObject *parent) :
QObject(parent),
m_id(0),
m_watcher(nullptr),
m_title(title),
m_icon(icon),
m_timeout(timeout)
{
initInterface();
}
/*!
* \brief Initializes the static interface object if not done yet.
*/
void DBusNotification::initInterface()
{
if(!m_dbusInterface) {
m_dbusInterface = new OrgFreedesktopNotificationsInterface(QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus());
connect(m_dbusInterface, &OrgFreedesktopNotificationsInterface::ActionInvoked, &DBusNotification::handleActionInvoked);
connect(m_dbusInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, &DBusNotification::handleNotificationClosed);
}
}
/*!
* \brief Closes the notification if still shown and delete the object.
*/
DBusNotification::~DBusNotification()
{
auto i = pendingNotifications.find(m_id);
if(i != pendingNotifications.end()) {
pendingNotifications.erase(i);
}
hide();
}
/*!
* \brief Returns whether the notification D-Bus daemon is running.
*/
bool DBusNotification::isAvailable()
{
initInterface();
return m_dbusInterface->isValid();
}
/*!
* \brief Sets the icon to one of the pre-defined notification icons.
*/
void DBusNotification::setIcon(NotificationIcon icon)
{
switch(icon) {
case NotificationIcon::Information:
m_icon = QStringLiteral("dialog-information"); break;
case NotificationIcon::Warning:
m_icon = QStringLiteral("dialog-warning"); break;
case NotificationIcon::Critical:
m_icon = QStringLiteral("dialog-critical"); break;
default:
;
}
}
/*!
* \brief Makes the notification object delete itself when the notification has been closed or an error occured.
*/
void DBusNotification::deleteOnCloseOrError()
{
connect(this, &DBusNotification::closed, this, &DBusNotification::deleteLater);
connect(this, &DBusNotification::error, this, &DBusNotification::deleteLater);
}
/*!
* \brief Shows the notification.
* \remarks If called when a previous notification is still shown, the previous notification is updated.
* \returns Returns false is the D-Bus daemon isn't reachable and true otherwise.
*/
bool DBusNotification::show()
{
if(!m_dbusInterface->isValid()) {
emit error();
return false;
}
delete m_watcher;
m_watcher = new QDBusPendingCallWatcher(m_dbusInterface->Notify(QCoreApplication::applicationName(), m_id, m_icon, m_title, m_msg, m_actions, m_hints, m_timeout), this);
connect(m_watcher, &QDBusPendingCallWatcher::finished, this, &DBusNotification::handleNotifyResult);
return true;
}
/*!
* \brief Updates the message and shows/updates the notification.
* \remarks If called when a previous notification is still shown, the previous notification is updated.
* \returns Returns false is the D-Bus daemon isn't reachable and true otherwise. The message is updated in any case.
*/
bool DBusNotification::show(const QString &message)
{
m_msg = message;
return show();
}
/*!
* \brief Updates the message and shows/updates the notification.
* \remarks
* - If called when a previous notification is still shown, the previous notification is updated. In this
* case the specified \a line will be appended to the current message.
* - If called when no previous notification is still shown, the previous message is completely replaced
* by \a line and shown as a new notification.
* \returns Returns false is the D-Bus daemon isn't reachable and true otherwise. The message is updated in any case.
*/
bool DBusNotification::update(const QString &line)
{
if(!isVisible() || m_msg.isEmpty()) {
m_msg = line;
} else {
if(!m_msg.startsWith(QStringLiteral(""))) {
m_msg.insert(0, QStringLiteral(""));
}
m_msg.append(QStringLiteral("\n"));
m_msg.append(line);
}
return show();
}
/*!
* \brief Hides the notification (if still visible).
* \remarks On success, the signal closed() is emitted with the reason NotificationCloseReason::Manually.
*/
void DBusNotification::hide()
{
if(m_id) {
m_dbusInterface->CloseNotification(m_id);
}
}
/*!
* \brief Handles the results of the Notify D-Bus call.
*/
void DBusNotification::handleNotifyResult(QDBusPendingCallWatcher *watcher)
{
if(watcher != m_watcher) {
return;
}
watcher->deleteLater();
m_watcher = nullptr;
QDBusPendingReply<uint> returnValue = *watcher;
if (returnValue.isError()) {
deleteLater();
emit error();
} else {
pendingNotifications[m_id = returnValue.argumentAt<0>()] = this;
emit shown();
}
}
/*!
* \brief Handles the NotificationClosed D-Bus signal.
*/
void DBusNotification::handleNotificationClosed(uint id, uint reason)
{
auto i = pendingNotifications.find(id);
if(i != pendingNotifications.end()) {
DBusNotification *notification = i->second;
notification->m_id = 0;
emit notification->closed(reason >= 1 && reason <= 3 ? static_cast<NotificationCloseReason>(reason) : NotificationCloseReason::Undefined);
pendingNotifications.erase(i);
}
}
/*!
* \brief Handles the ActionInvoked D-Bus signal.
*/
void DBusNotification::handleActionInvoked(uint id, const QString &action)
{
auto i = pendingNotifications.find(id);
if(i != pendingNotifications.end()) {
emit i->second->actionInvoked(action);
}
}
/*!
* \fn DBusNotification::message()
* \brief Returns the assigned message.
* \sa setMessage() for more details.
*/
/*!
* \fn DBusNotification::setMessage()
* \brief Sets the message to be shown.
* \remarks
* - Might also be set via show() and update().
* - Can contain the following HTML tags: `<b>`, `<i>`, `<u>`, `<a href="...">` and `<img src="..." alt="..."/>`
*/
/*!
* \fn DBusNotification::timeout()
* \brief Returns the number of milliseconds the notification will be visible after calling show().
* \sa setTimeout() for more details.
*/
/*!
* \fn DBusNotification::setTimeout()
* \brief Sets the number of milliseconds the notification will be visible after calling show().
* \remarks
* - Set to 0 for non-expiring notifications.
* - Set to -1 to let the notification daemon decide.
*/
/*!
* \fn DBusNotification::actions()
* \brief Returns the assigned actions.
* \sa setActions() for more details.
*/
/*!
* \fn DBusNotification::setActions()
* \brief Sets the list of available actions.
* \remarks
* The list consists of pairs of action IDs and action labels, eg.
* `QStringList({QStringLiteral("first_id"), tr("First action"), QStringLiteral("second_id"), tr("Second action"), ...})`
* \sa actionInvoked() signal
*/
/*!
* \fn DBusNotification::isVisible()
* \brief Returns whether the notification is (still) visible.
*/
}

175
misc/dbusnotification.h Normal file
View File

@ -0,0 +1,175 @@
#ifndef MISC_UTILS_NOTIFICATION_H
#define MISC_UTILS_NOTIFICATION_H
#include "../global.h"
#include <QObject>
#include <QVariantMap>
QT_FORWARD_DECLARE_CLASS(QDBusPendingCallWatcher)
class OrgFreedesktopNotificationsInterface;
namespace MiscUtils {
enum class NotificationIcon
{
NoIcon,
Information,
Warning,
Critical
};
enum class NotificationCloseReason
{
Undefined,
Expired,
Dismissed,
Manually
};
class QT_UTILITIES_EXPORT DBusNotification : public QObject
{
Q_OBJECT
Q_PROPERTY(QString title READ title WRITE setTitle)
Q_PROPERTY(QString message READ message WRITE setMessage)
Q_PROPERTY(QString icon READ icon WRITE setIcon)
Q_PROPERTY(int timeout READ timeout WRITE setTimeout)
Q_PROPERTY(QStringList actions READ actions WRITE setActions)
Q_PROPERTY(bool visible READ isVisible)
public:
explicit DBusNotification(const QString &title, NotificationIcon icon = NotificationIcon::Information, int timeout = 10000, QObject *parent = nullptr);
explicit DBusNotification(const QString &title, const QString &icon, int timeout = 10000, QObject *parent = nullptr);
~DBusNotification();
static bool isAvailable();
const QString &title() const;
void setTitle(const QString &title);
const QString &message() const;
void setMessage(const QString &message);
const QString &icon() const;
void setIcon(const QString &icon);
void setIcon(NotificationIcon icon);
int timeout() const;
void setTimeout(int timeout);
const QStringList &actions() const;
void setActions(const QStringList &actions);
const QVariantMap &hints() const;
QVariantMap &hints();
bool isVisible() const;
void deleteOnCloseOrError();
public Q_SLOTS:
bool show();
bool show(const QString &message);
bool update(const QString &line);
void hide();
Q_SIGNALS:
/// \brief Emitted when the notification could be shown successful.
void shown();
/// \brief Emitted when the notification couldn't be shown.
void error();
/// \brief Emitted when the notification has been closed.
void closed(NotificationCloseReason reason);
/// \brief Emitted when \a action has been invoked.
void actionInvoked(const QString &action);
private Q_SLOTS:
void handleNotifyResult(QDBusPendingCallWatcher *);
static void handleNotificationClosed(uint id, uint reason);
static void handleActionInvoked(uint id, const QString &action);
private:
static void initInterface();
uint m_id;
QDBusPendingCallWatcher *m_watcher;
QString m_title;
QString m_msg;
QString m_icon;
int m_timeout;
QStringList m_actions;
QVariantMap m_hints;
static OrgFreedesktopNotificationsInterface *m_dbusInterface;
};
inline const QString &DBusNotification::title() const
{
return m_title;
}
inline void DBusNotification::setTitle(const QString &title)
{
m_title = title;
}
inline const QString &DBusNotification::message() const
{
return m_msg;
}
inline void DBusNotification::setMessage(const QString &message)
{
m_msg = message;
}
/*!
* \brief Returns the icon name.
* \sa setIcon() for more details
*/
inline const QString &DBusNotification::icon() const
{
return m_icon;
}
/*!
* \brief Sets the icon name.
* \remarks
* The specified \a icon should be either an URI (file:// is the only URI schema supported
* right now) or a name in an icon theme.
*/
inline void DBusNotification::setIcon(const QString &icon)
{
m_icon = icon;
}
inline int DBusNotification::timeout() const
{
return m_timeout;
}
inline void DBusNotification::setTimeout(int timeout)
{
m_timeout = timeout;
}
inline const QStringList &DBusNotification::actions() const
{
return m_actions;
}
inline void DBusNotification::setActions(const QStringList &actions)
{
m_actions = actions;
}
inline const QVariantMap &DBusNotification::hints() const
{
return m_hints;
}
inline QVariantMap &DBusNotification::hints()
{
return m_hints;
}
inline bool DBusNotification::isVisible() const
{
return m_id != 0;
}
}
#endif // MISC_UTILS_NOTIFICATION_H