Qt Utilities 6.14.0
Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models
Loading...
Searching...
No Matches
dbusnotification.cpp
Go to the documentation of this file.
2#include "notificationsinterface.h"
3
4#include <QCoreApplication>
5#include <QDBusConnection>
6#include <QDBusPendingReply>
7#include <QImage>
8#include <QMutex>
9#include <QMutexLocker>
10
11#include <limits>
12#include <map>
13
14using namespace std;
15
16namespace QtUtilities {
17
41using IDType = uint;
42static QMutex pendingNotificationsMutex;
43static std::map<IDType, DBusNotification *> pendingNotifications;
44OrgFreedesktopNotificationsInterface *DBusNotification::s_dbusInterface = nullptr;
45constexpr auto initialId = std::numeric_limits<IDType>::min();
46constexpr auto pendingId = std::numeric_limits<IDType>::max();
47constexpr auto pendingId2 = pendingId - 1;
49
53struct SwappedImage : public QImage {
54 SwappedImage(const QImage &image);
55};
56
57inline SwappedImage::SwappedImage(const QImage &image)
58 : QImage(image.rgbSwapped())
59{
60}
61
67struct NotificationImage : public QDBusArgument {
70 QImage toQImage() const;
71 QVariant toDBusArgument() const;
72 static NotificationImage fromDBusArgument(const QVariant &variant);
73
74 qint32 width;
75 qint32 height;
76 qint32 rowstride;
78 qint32 channels;
80 QByteArray data;
81 bool isValid;
82
83private:
84 NotificationImage(const QImage &image);
85};
86
87QDBusArgument &operator<<(QDBusArgument &argument, const NotificationImage &img)
88{
89 argument.beginStructure();
90 argument << img.width << img.height << img.rowstride << img.hasAlpha << img.bitsPerSample << img.channels << img.data;
91 argument.endStructure();
92 return argument;
93}
94
95const QDBusArgument &operator>>(const QDBusArgument &argument, NotificationImage &img)
96{
97 argument.beginStructure();
98 argument >> img.width >> img.height >> img.rowstride >> img.hasAlpha >> img.bitsPerSample >> img.channels >> img.data;
99 argument.endStructure();
100 return argument;
101}
102
104 : isValid(false)
105{
106}
107
109 : NotificationImage(static_cast<const QImage &>(image))
110{
111}
112
113inline NotificationImage::NotificationImage(const QImage &image)
114 : width(image.width())
115 , height(image.height())
116 , rowstride(static_cast<qint32>(image.bytesPerLine()))
117 , hasAlpha(image.hasAlphaChannel())
118 , channels(image.isGrayscale() ? 1
119 : hasAlpha ? 4
120 : 3)
121 , bitsPerSample(image.depth() / channels)
122#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
123 , data(reinterpret_cast<const char *>(image.bits()), static_cast<int>(image.sizeInBytes()))
124#else
125 , data(reinterpret_cast<const char *>(image.bits()), image.byteCount())
126#endif
127 , isValid(!image.isNull())
128{
129 if (isValid) {
130 // populate QDBusArgument structure
131 // note: Just use the operator overload which is required for qDBusMarshallHelper().
132 *this << *this;
133 }
134}
135
136inline QImage NotificationImage::toQImage() const
137{
138 return isValid ? QImage(reinterpret_cast<const uchar *>(data.constData()), width, height, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32)
139 .rgbSwapped()
140 : QImage();
141}
142
144{
145 return QVariant::fromValue(*this);
146}
147
149{
150 return variant.canConvert<NotificationImage>() ? variant.value<NotificationImage>() : NotificationImage();
151}
152
153} // namespace QtUtilities
154
156
157namespace QtUtilities {
158
162DBusNotification::DBusNotification(const QString &title, NotificationIcon icon, int timeout, QObject *parent)
163 : QObject(parent)
164 , m_id(initialId)
165 , m_watcher(nullptr)
166 , m_title(title)
167 , m_timeout(timeout)
168{
169 initInterface();
170 setIcon(icon);
171}
172
176DBusNotification::DBusNotification(const QString &title, const QString &icon, int timeout, QObject *parent)
177 : QObject(parent)
178 , m_id(initialId)
179 , m_watcher(nullptr)
180 , m_title(title)
181 , m_icon(icon)
182 , m_timeout(timeout)
183{
184 initInterface();
185}
186
190void DBusNotification::initInterface()
191{
192 if (!s_dbusInterface) {
193 qDBusRegisterMetaType<NotificationImage>();
194 s_dbusInterface = new OrgFreedesktopNotificationsInterface(
195 QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus());
196 connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::ActionInvoked, &DBusNotification::handleActionInvoked);
197 connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, &DBusNotification::handleNotificationClosed);
198 }
199}
200
205{
206 {
207 QMutexLocker lock(&pendingNotificationsMutex);
208 auto i = pendingNotifications.find(m_id);
209 if (i != pendingNotifications.end()) {
210 pendingNotifications.erase(i);
211 }
212 }
213 hide();
214}
215
220{
221 initInterface();
222 return s_dbusInterface->isValid();
223}
224
229{
230 switch (icon) {
232 m_icon = QStringLiteral("dialog-information");
233 break;
235 m_icon = QStringLiteral("dialog-warning");
236 break;
238 m_icon = QStringLiteral("dialog-critical");
239 break;
240 default:;
241 }
242}
243
248const QImage DBusNotification::image() const
249{
250 return NotificationImage::fromDBusArgument(hint(QStringLiteral("image-data"), QStringLiteral("image_data"))).toQImage();
251}
252
259void DBusNotification::setImage(const QImage &image)
260{
261 m_hints[QStringLiteral("image-data")] = NotificationImage(SwappedImage(image)).toDBusArgument();
262}
263
273{
274 return m_id == pendingId || m_id == pendingId2;
275}
276
282{
283 connect(this, &DBusNotification::closed, this, &DBusNotification::deleteLater);
284 connect(this, &DBusNotification::error, this, &DBusNotification::deleteLater);
285}
286
297{
298 if (isPending()) {
299 m_id = pendingId2;
300 return true;
301 }
302 if (!s_dbusInterface->isValid()) {
303 emit error();
304 return false;
305 }
306
307 delete m_watcher;
308 m_watcher
309 = new QDBusPendingCallWatcher(s_dbusInterface->Notify(m_applicationName.isEmpty() ? QCoreApplication::applicationName() : m_applicationName,
310 m_id, m_icon, m_title, m_msg, m_actions, m_hints, m_timeout),
311 this);
312 connect(m_watcher, &QDBusPendingCallWatcher::finished, this, &DBusNotification::handleNotifyResult);
313 m_id = pendingId;
314 return true;
315}
316
324bool DBusNotification::show(const QString &message)
325{
326 m_msg = message;
327 return show();
328}
329
342bool DBusNotification::update(const QString &line)
343{
344 if ((!isPending() && !isVisible()) || m_msg.isEmpty()) {
345 m_msg = line;
346 } else {
347 if (!m_msg.startsWith(QStringLiteral("•"))) {
348 m_msg.insert(0, QStringLiteral("• "));
349 }
350 m_msg.append(QStringLiteral("\n• "));
351 m_msg.append(line);
352 }
353 return show();
354}
355
356bool DBusNotification::queryCapabilities(const std::function<void(Capabilities &&capabilities)> &callback)
357{
358 // ensure DBus-interface is initialized and valid
359 initInterface();
360 if (!s_dbusInterface->isValid()) {
361 return false;
362 }
363
364 // invoke GetCapabilities() and pass the return value to the callback when available
365 const auto *const newWatcher = new QDBusPendingCallWatcher(s_dbusInterface->GetCapabilities());
366 connect(newWatcher, &QDBusPendingCallWatcher::finished, [&callback](QDBusPendingCallWatcher *watcher) {
367 watcher->deleteLater();
368 const QDBusPendingReply<QStringList> returnValue(*watcher);
369 if (returnValue.isError()) {
370 callback(Capabilities());
371 } else {
372 callback(Capabilities(std::move(returnValue.value())));
373 }
374 });
375 return true;
376}
377
384{
385 if (m_id) {
386 s_dbusInterface->CloseNotification(m_id);
387 return true;
388 }
389 return false;
390}
391
395void DBusNotification::handleNotifyResult(QDBusPendingCallWatcher *watcher)
396{
397 if (watcher != m_watcher) {
398 return;
399 }
400
401 watcher->deleteLater();
402 m_watcher = nullptr;
403
404 QDBusPendingReply<uint> returnValue = *watcher;
405 if (returnValue.isError()) {
406 m_id = initialId;
407 emit error();
408 return;
409 }
410
411 const auto needsUpdate = m_id == pendingId2;
412 {
413 QMutexLocker lock(&pendingNotificationsMutex);
414 pendingNotifications[m_id = returnValue.argumentAt<0>()] = this;
415 }
416 emit shown();
417
418 // update the notification again if show() was called before we've got the ID
419 if (needsUpdate) {
420 show();
421 }
422}
423
427void DBusNotification::handleNotificationClosed(IDType id, uint reason)
428{
429 QMutexLocker lock(&pendingNotificationsMutex);
430 auto i = pendingNotifications.find(id);
431 if (i != pendingNotifications.end()) {
432 DBusNotification *notification = i->second;
433 notification->m_id = initialId;
434 emit notification->closed(reason >= 1 && reason <= 3 ? static_cast<NotificationCloseReason>(reason) : NotificationCloseReason::Undefined);
435 pendingNotifications.erase(i);
436 }
437}
438
442void DBusNotification::handleActionInvoked(IDType id, const QString &action)
443{
444 QMutexLocker lock(&pendingNotificationsMutex);
445 auto i = pendingNotifications.find(id);
446 if (i != pendingNotifications.end()) {
447 DBusNotification *notification = i->second;
448 emit notification->actionInvoked(action);
449 // Plasma 5 also closes the notification but doesn't emit the
450 // NotificationClose signal
451 // -> just consider the notification closed
452 emit notification->closed(NotificationCloseReason::ActionInvoked);
453 notification->m_id = initialId;
454 pendingNotifications.erase(i);
455 // however, lxqt-notificationd does not close the notification
456 // -> close manually for consistent behaviour
457 s_dbusInterface->CloseNotification(i->first);
458 }
459}
460
512} // namespace QtUtilities
void deleteOnCloseOrError()
Makes the notification object delete itself when the notification has been closed or an error occurre...
bool update(const QString &line)
Updates the message and shows/updates the notification.
void shown()
Emitted when the notification could be shown successful.
bool isPending() const
Returns whether the notification is about to be shown after calling show() or update() but has not be...
const QImage image() const
Returns the image.
bool isVisible() const
Returns whether the notification is (still) visible.
QString message
Returns the assigned message.
void setIcon(const QString &icon)
Sets the icon name.
static bool queryCapabilities(const std::function< void(Capabilities &&capabilities)> &callback)
DBusNotification(const QString &title, NotificationIcon icon=NotificationIcon::Information, int timeout=10000, QObject *parent=nullptr)
Creates a new notification (which is not shown instantly).
static bool isAvailable()
Returns whether the notification D-Bus daemon is running.
void closed(NotificationCloseReason reason)
Emitted when the notification has been closed.
void error()
Emitted when the notification couldn't be shown.
bool hide()
Hides the notification (if still visible).
QVariant hint(const QString &name) const
Returns the hint with the specified name.
void setImage(const QImage &image)
Sets the image.
~DBusNotification() override
Closes the notification if still shown and delete the object.
bool show()
Shows the notification.
Q_DECLARE_METATYPE(QtUtilities::NotificationImage)
const QDBusArgument & operator>>(const QDBusArgument &argument, NotificationImage &img)
QDBusArgument & operator<<(QDBusArgument &argument, const NotificationImage &img)
The NotificationImage struct is a raw data image format.
static NotificationImage fromDBusArgument(const QVariant &variant)
The SwappedImage struct represents RGB-interved version of the image specified on construction.
SwappedImage(const QImage &image)