Qt Utilities  6.2.1
Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models
dbusnotification.cpp
Go to the documentation of this file.
1 #include "./dbusnotification.h"
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 
14 using namespace std;
15 
16 namespace QtUtilities {
17 
40 using IDType = uint;
42 static QMutex pendingNotificationsMutex;
43 static std::map<IDType, DBusNotification *> pendingNotifications;
44 OrgFreedesktopNotificationsInterface *DBusNotification::s_dbusInterface = nullptr;
45 constexpr auto initialId = std::numeric_limits<IDType>::min();
46 constexpr auto pendingId = std::numeric_limits<IDType>::max();
47 constexpr auto pendingId2 = pendingId - 1;
49 
53 struct SwappedImage : public QImage {
54  SwappedImage(const QImage &image);
55 };
56 
57 inline SwappedImage::SwappedImage(const QImage &image)
58  : QImage(image.rgbSwapped())
59 {
60 }
61 
67 struct 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;
77  bool hasAlpha;
78  qint32 channels;
79  qint32 bitsPerSample;
80  QByteArray data;
81  bool isValid;
82 
83 private:
84  NotificationImage(const QImage &image);
85 };
86 
87 QDBusArgument &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 
95 const 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 
113 inline NotificationImage::NotificationImage(const QImage &image)
114  : width(image.width())
115  , height(image.height())
116  , rowstride(image.bytesPerLine())
117  , hasAlpha(image.hasAlphaChannel())
118  , channels(image.isGrayscale() ? 1 : hasAlpha ? 4 : 3)
119  , bitsPerSample(image.depth() / channels)
120 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
121  , data(reinterpret_cast<const char *>(image.bits()), static_cast<int>(image.sizeInBytes()))
122 #else
123  , data(reinterpret_cast<const char *>(image.bits()), image.byteCount())
124 #endif
125  , isValid(!image.isNull())
126 {
127  if (isValid) {
128  // populate QDBusArgument structure
129  // note: Just use the operator overload which is required for qDBusMarshallHelper().
130  *this << *this;
131  }
132 }
133 
134 inline QImage NotificationImage::toQImage() const
135 {
136  return isValid ? QImage(reinterpret_cast<const uchar *>(data.constData()), width, height, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32)
137  .rgbSwapped()
138  : QImage();
139 }
140 
141 inline QVariant NotificationImage::toDBusArgument() const
142 {
143  return QVariant::fromValue(*this);
144 }
145 
147 {
148  return variant.canConvert<NotificationImage>() ? variant.value<NotificationImage>() : NotificationImage();
149 }
150 
151 } // namespace QtUtilities
152 
154 
155 namespace QtUtilities {
156 
160 DBusNotification::DBusNotification(const QString &title, NotificationIcon icon, int timeout, QObject *parent)
161  : QObject(parent)
162  , m_id(initialId)
163  , m_watcher(nullptr)
164  , m_title(title)
165  , m_timeout(timeout)
166 {
167  initInterface();
168  setIcon(icon);
169 }
170 
174 DBusNotification::DBusNotification(const QString &title, const QString &icon, int timeout, QObject *parent)
175  : QObject(parent)
176  , m_id(initialId)
177  , m_watcher(nullptr)
178  , m_title(title)
179  , m_icon(icon)
180  , m_timeout(timeout)
181 {
182  initInterface();
183 }
184 
188 void DBusNotification::initInterface()
189 {
190  if (!s_dbusInterface) {
191  qDBusRegisterMetaType<NotificationImage>();
192  s_dbusInterface = new OrgFreedesktopNotificationsInterface(
193  QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus());
194  connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::ActionInvoked, &DBusNotification::handleActionInvoked);
195  connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, &DBusNotification::handleNotificationClosed);
196  }
197 }
198 
203 {
204  {
205  QMutexLocker lock(&pendingNotificationsMutex);
206  auto i = pendingNotifications.find(m_id);
207  if (i != pendingNotifications.end()) {
208  pendingNotifications.erase(i);
209  }
210  }
211  hide();
212 }
213 
218 {
219  initInterface();
220  return s_dbusInterface->isValid();
221 }
222 
227 {
228  switch (icon) {
230  m_icon = QStringLiteral("dialog-information");
231  break;
233  m_icon = QStringLiteral("dialog-warning");
234  break;
236  m_icon = QStringLiteral("dialog-critical");
237  break;
238  default:;
239  }
240 }
241 
246 const QImage DBusNotification::image() const
247 {
248  return NotificationImage::fromDBusArgument(hint(QStringLiteral("image-data"), QStringLiteral("image_data"))).toQImage();
249 }
250 
257 void DBusNotification::setImage(const QImage &image)
258 {
259  m_hints[QStringLiteral("image-data")] = NotificationImage(SwappedImage(image)).toDBusArgument();
260 }
261 
271 {
272  return m_id == pendingId || m_id == pendingId2;
273 }
274 
280 {
281  connect(this, &DBusNotification::closed, this, &DBusNotification::deleteLater);
282  connect(this, &DBusNotification::error, this, &DBusNotification::deleteLater);
283 }
284 
295 {
296  if (isPending()) {
297  m_id = pendingId2;
298  return true;
299  }
300  if (!s_dbusInterface->isValid()) {
301  emit error();
302  return false;
303  }
304 
305  delete m_watcher;
306  m_watcher
307  = new QDBusPendingCallWatcher(s_dbusInterface->Notify(m_applicationName.isEmpty() ? QCoreApplication::applicationName() : m_applicationName,
308  m_id, m_icon, m_title, m_msg, m_actions, m_hints, m_timeout),
309  this);
310  connect(m_watcher, &QDBusPendingCallWatcher::finished, this, &DBusNotification::handleNotifyResult);
311  m_id = pendingId;
312  return true;
313 }
314 
322 bool DBusNotification::show(const QString &message)
323 {
324  m_msg = message;
325  return show();
326 }
327 
340 bool DBusNotification::update(const QString &line)
341 {
342  if ((!isPending() && !isVisible()) || m_msg.isEmpty()) {
343  m_msg = line;
344  } else {
345  if (!m_msg.startsWith(QStringLiteral("•"))) {
346  m_msg.insert(0, QStringLiteral("• "));
347  }
348  m_msg.append(QStringLiteral("\n• "));
349  m_msg.append(line);
350  }
351  return show();
352 }
353 
354 bool DBusNotification::queryCapabilities(const std::function<void(Capabilities &&capabilities)> &callback)
355 {
356  // ensure DBus-interface is initialized and valid
357  initInterface();
358  if (!s_dbusInterface->isValid()) {
359  return false;
360  }
361 
362  // invoke GetCapabilities() and pass the return value to the callback when available
363  const auto *const watcher = new QDBusPendingCallWatcher(s_dbusInterface->GetCapabilities());
364  connect(watcher, &QDBusPendingCallWatcher::finished, [&callback](QDBusPendingCallWatcher *watcher) {
365  watcher->deleteLater();
366  const QDBusPendingReply<QStringList> returnValue(*watcher);
367  if (returnValue.isError()) {
368  callback(Capabilities());
369  } else {
370  callback(Capabilities(move(returnValue.value())));
371  }
372  });
373  return true;
374 }
375 
382 {
383  if (m_id) {
384  s_dbusInterface->CloseNotification(m_id);
385  return true;
386  }
387  return false;
388 }
389 
393 void DBusNotification::handleNotifyResult(QDBusPendingCallWatcher *watcher)
394 {
395  if (watcher != m_watcher) {
396  return;
397  }
398 
399  watcher->deleteLater();
400  m_watcher = nullptr;
401 
402  QDBusPendingReply<uint> returnValue = *watcher;
403  if (returnValue.isError()) {
404  m_id = initialId;
405  emit error();
406  return;
407  }
408 
409  const auto needsUpdate = m_id == pendingId2;
410  {
411  QMutexLocker lock(&pendingNotificationsMutex);
412  pendingNotifications[m_id = returnValue.argumentAt<0>()] = this;
413  }
414  emit shown();
415 
416  // update the notification again if show() was called before we've got the ID
417  if (needsUpdate) {
418  show();
419  }
420 }
421 
425 void DBusNotification::handleNotificationClosed(IDType id, uint reason)
426 {
427  QMutexLocker lock(&pendingNotificationsMutex);
428  auto i = pendingNotifications.find(id);
429  if (i != pendingNotifications.end()) {
430  DBusNotification *notification = i->second;
431  notification->m_id = initialId;
432  emit notification->closed(reason >= 1 && reason <= 3 ? static_cast<NotificationCloseReason>(reason) : NotificationCloseReason::Undefined);
433  pendingNotifications.erase(i);
434  }
435 }
436 
440 void DBusNotification::handleActionInvoked(IDType id, const QString &action)
441 {
442  QMutexLocker lock(&pendingNotificationsMutex);
443  auto i = pendingNotifications.find(id);
444  if (i != pendingNotifications.end()) {
445  DBusNotification *notification = i->second;
446  emit notification->actionInvoked(action);
447  // Plasma 5 also closes the notification but doesn't emit the
448  // NotificationClose signal
449  // -> just consider the notification closed
450  emit notification->closed(NotificationCloseReason::ActionInvoked);
451  notification->m_id = initialId;
452  pendingNotifications.erase(i);
453  // however, lxqt-notificationd does not close the notification
454  // -> close manually for consistent behaviour
455  s_dbusInterface->CloseNotification(i->first);
456  }
457 }
458 
510 } // namespace QtUtilities
QtUtilities::SwappedImage
The SwappedImage struct represents RGB-interved version of the image specified on construction.
Definition: dbusnotification.cpp:53
QtUtilities::NotificationIcon::Information
@ Information
QtUtilities::NotificationImage::data
QByteArray data
Definition: dbusnotification.cpp:80
QtUtilities::DBusNotification::isPending
bool isPending() const
Returns whether the notification is about to be shown after calling show() or update() but has not be...
Definition: dbusnotification.cpp:270
QtUtilities::NotificationImage::fromDBusArgument
static NotificationImage fromDBusArgument(const QVariant &variant)
Definition: dbusnotification.cpp:146
QtUtilities::DBusNotification::isAvailable
static bool isAvailable()
Returns whether the notification D-Bus daemon is running.
Definition: dbusnotification.cpp:217
QtUtilities::DBusNotification::deleteOnCloseOrError
void deleteOnCloseOrError()
Makes the notification object delete itself when the notification has been closed or an error occurre...
Definition: dbusnotification.cpp:279
QtUtilities::DBusNotification::isVisible
bool isVisible() const
Returns whether the notification is (still) visible.
Definition: dbusnotification.h:387
QtUtilities::DBusNotification::queryCapabilities
static bool queryCapabilities(const std::function< void(Capabilities &&capabilities)> &callback)
Definition: dbusnotification.cpp:354
QtUtilities::NotificationImage::bitsPerSample
qint32 bitsPerSample
Definition: dbusnotification.cpp:79
QtUtilities::NotificationImage::channels
qint32 channels
Definition: dbusnotification.cpp:78
QtUtilities::DBusNotification::show
bool show()
Shows the notification.
Definition: dbusnotification.cpp:294
QtUtilities::DBusNotification::shown
void shown()
Emitted when the notification could be shown successful.
QtUtilities::DBusNotification::Capabilities
Definition: dbusnotification.h:35
QtUtilities::NotificationImage::width
qint32 width
Definition: dbusnotification.cpp:74
QtUtilities::NotificationCloseReason::ActionInvoked
@ ActionInvoked
QtUtilities::NotificationImage
The NotificationImage struct is a raw data image format.
Definition: dbusnotification.cpp:67
QtUtilities::DBusNotification::message
QString message
Returns the assigned message.
Definition: dbusnotification.h:26
QtUtilities::DBusNotification::~DBusNotification
~DBusNotification() override
Closes the notification if still shown and delete the object.
Definition: dbusnotification.cpp:202
QtUtilities::NotificationImage::toQImage
QImage toQImage() const
Definition: dbusnotification.cpp:134
dbusnotification.h
QtUtilities::NotificationCloseReason::Undefined
@ Undefined
QtUtilities::operator<<
QDBusArgument & operator<<(QDBusArgument &argument, const NotificationImage &img)
Definition: dbusnotification.cpp:87
QtUtilities::NotificationImage::rowstride
qint32 rowstride
Definition: dbusnotification.cpp:76
QtUtilities::DBusNotification::update
bool update(const QString &line)
Updates the message and shows/updates the notification.
Definition: dbusnotification.cpp:340
QtUtilities::DBusNotification::hide
bool hide()
Hides the notification (if still visible).
Definition: dbusnotification.cpp:381
QtUtilities::DBusNotification::error
void error()
Emitted when the notification couldn't be shown.
QtUtilities::NotificationIcon::Critical
@ Critical
QtUtilities
!
Definition: trylocker.h:8
QtUtilities::NotificationImage::isValid
bool isValid
Definition: dbusnotification.cpp:81
QtUtilities::DBusNotification::closed
void closed(NotificationCloseReason reason)
Emitted when the notification has been closed.
QtUtilities::NotificationIcon
NotificationIcon
Definition: dbusnotification.h:18
QtUtilities::DBusNotification::setIcon
void setIcon(const QString &icon)
Sets the icon name.
Definition: dbusnotification.h:252
QtUtilities::NotificationCloseReason
NotificationCloseReason
Definition: dbusnotification.h:20
QtUtilities::NotificationImage::toDBusArgument
QVariant toDBusArgument() const
Definition: dbusnotification.cpp:141
QtUtilities::DBusNotification::setImage
void setImage(const QImage &image)
Sets the image.
Definition: dbusnotification.cpp:257
QtUtilities::DBusNotification::hint
QVariant hint(const QString &name) const
Returns the hint with the specified name.
Definition: dbusnotification.h:260
QtUtilities::DBusNotification::image
const QImage image() const
Returns the image.
Definition: dbusnotification.cpp:246
QtUtilities::NotificationIcon::Warning
@ Warning
QtUtilities::operator>>
const QDBusArgument & operator>>(const QDBusArgument &argument, NotificationImage &img)
Definition: dbusnotification.cpp:95
Q_DECLARE_METATYPE
Q_DECLARE_METATYPE(QtUtilities::NotificationImage)
QtUtilities::NotificationImage::NotificationImage
NotificationImage()
Definition: dbusnotification.cpp:103
QtUtilities::NotificationImage::height
qint32 height
Definition: dbusnotification.cpp:75
QtUtilities::NotificationImage::hasAlpha
bool hasAlpha
Definition: dbusnotification.cpp:77
QtUtilities::DBusNotification::icon
QString icon
Definition: dbusnotification.h:27
QtUtilities::DBusNotification::DBusNotification
DBusNotification(const QString &title, NotificationIcon icon=NotificationIcon::Information, int timeout=10000, QObject *parent=nullptr)
Creates a new notification (which is not shown instantly).
Definition: dbusnotification.cpp:160