Qt Utilities  6.4.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 
41 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) {
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 
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 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(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
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)
QDBusArgument & operator<<(QDBusArgument &argument, const NotificationImage &img)
const QDBusArgument & operator>>(const QDBusArgument &argument, 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.