Qt Utilities  6.3.3
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
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 
136 inline 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 
143 inline QVariant NotificationImage::toDBusArgument() const
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 
157 namespace QtUtilities {
158 
162 DBusNotification::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 
176 DBusNotification::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 
190 void 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) {
231  case NotificationIcon::Information:
232  m_icon = QStringLiteral("dialog-information");
233  break;
234  case NotificationIcon::Warning:
235  m_icon = QStringLiteral("dialog-warning");
236  break;
238  m_icon = QStringLiteral("dialog-critical");
239  break;
240  default:;
241  }
242 }
243 
248 const QImage DBusNotification::image() const
249 {
250  return NotificationImage::fromDBusArgument(hint(QStringLiteral("image-data"), QStringLiteral("image_data"))).toQImage();
251 }
252 
259 void 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 
324 bool DBusNotification::show(const QString &message)
325 {
326  m_msg = message;
327  return show();
328 }
329 
342 bool 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 
356 bool 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 watcher = new QDBusPendingCallWatcher(s_dbusInterface->GetCapabilities());
366  connect(watcher, &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(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 
395 void 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 
427 void 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 
442 void 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
QtUtilities::SwappedImage
The SwappedImage struct represents RGB-interved version of the image specified on construction.
Definition: dbusnotification.cpp:53
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:272
QtUtilities::NotificationImage::fromDBusArgument
static NotificationImage fromDBusArgument(const QVariant &variant)
Definition: dbusnotification.cpp:148
QtUtilities::DBusNotification::isAvailable
static bool isAvailable()
Returns whether the notification D-Bus daemon is running.
Definition: dbusnotification.cpp:219
QtUtilities::DBusNotification::deleteOnCloseOrError
void deleteOnCloseOrError()
Makes the notification object delete itself when the notification has been closed or an error occurre...
Definition: dbusnotification.cpp:281
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:356
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:296
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::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:204
QtUtilities::NotificationImage::toQImage
QImage toQImage() const
Definition: dbusnotification.cpp:136
dbusnotification.h
QtUtilities::NotificationIcon::NoIcon
@ NoIcon
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:342
QtUtilities::DBusNotification::hide
bool hide()
Hides the notification (if still visible).
Definition: dbusnotification.cpp:383
QtUtilities::DBusNotification::error
void error()
Emitted when the notification couldn't be shown.
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:143
QtUtilities::DBusNotification::setImage
void setImage(const QImage &image)
Sets the image.
Definition: dbusnotification.cpp:259
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:248
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:162