Qt Utilities  5.11.0
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 
9 #include <map>
10 
11 using namespace std;
12 
13 namespace MiscUtils {
14 
37 static std::map<uint, DBusNotification *> pendingNotifications;
39 OrgFreedesktopNotificationsInterface *DBusNotification::s_dbusInterface = nullptr;
41 
45 struct SwappedImage : public QImage {
46  SwappedImage(const QImage &image);
47 };
48 
49 inline SwappedImage::SwappedImage(const QImage &image)
50  : QImage(image.rgbSwapped())
51 {
52 }
53 
59 struct NotificationImage : public QDBusArgument {
62  QImage toQImage() const;
63  QVariant toDBusArgument() const;
64  static NotificationImage fromDBusArgument(const QVariant &variant);
65 
66  qint32 width;
67  qint32 height;
68  qint32 rowstride;
69  bool hasAlpha;
70  qint32 channels;
71  qint32 bitsPerSample;
72  QByteArray data;
73  bool isValid;
74 
75 private:
76  NotificationImage(const QImage &image);
77 };
78 
79 QDBusArgument &operator<<(QDBusArgument &argument, const NotificationImage &img)
80 {
81  argument.beginStructure();
82  argument << img.width << img.height << img.rowstride << img.hasAlpha << img.bitsPerSample << img.channels << img.data;
83  argument.endStructure();
84  return argument;
85 }
86 
87 const QDBusArgument &operator>>(const QDBusArgument &argument, 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 
96  : isValid(false)
97 {
98 }
99 
101  : NotificationImage(static_cast<const QImage &>(image))
102 {
103 }
104 
105 inline NotificationImage::NotificationImage(const QImage &image)
106  : width(image.width())
107  , height(image.height())
108  , rowstride(image.bytesPerLine())
109  , hasAlpha(image.hasAlphaChannel())
110  , channels(image.isGrayscale() ? 1 : hasAlpha ? 4 : 3)
111  , bitsPerSample(image.depth() / channels)
112  , data(reinterpret_cast<const char *>(image.bits()), image.byteCount())
113  , isValid(!image.isNull())
114 {
115  if (isValid) {
116  // populate QDBusArgument structure
117  // note: Just use the operator overload which is required for qDBusMarshallHelper().
118  *this << *this;
119  }
120 }
121 
122 inline QImage NotificationImage::toQImage() const
123 {
124  return isValid ? QImage(reinterpret_cast<const uchar *>(data.constData()), width, height, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32)
125  .rgbSwapped()
126  : QImage();
127 }
128 
129 inline QVariant NotificationImage::toDBusArgument() const
130 {
131  return QVariant::fromValue(*this);
132 }
133 
135 {
136  return variant.canConvert<NotificationImage>() ? variant.value<NotificationImage>() : NotificationImage();
137 }
138 
139 } // namespace MiscUtils
140 
142 
143 namespace MiscUtils {
144 
148 DBusNotification::DBusNotification(const QString &title, NotificationIcon icon, int timeout, QObject *parent)
149  : QObject(parent)
150  , m_id(0)
151  , m_watcher(nullptr)
152  , m_title(title)
153  , m_timeout(timeout)
154 {
155  initInterface();
156  setIcon(icon);
157 }
158 
162 DBusNotification::DBusNotification(const QString &title, const QString &icon, int timeout, QObject *parent)
163  : QObject(parent)
164  , m_id(0)
165  , m_watcher(nullptr)
166  , m_title(title)
167  , m_icon(icon)
168  , m_timeout(timeout)
169 {
170  initInterface();
171 }
172 
176 void DBusNotification::initInterface()
177 {
178  if (!s_dbusInterface) {
179  qDBusRegisterMetaType<NotificationImage>();
180  s_dbusInterface = new OrgFreedesktopNotificationsInterface(
181  QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus());
182  connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::ActionInvoked, &DBusNotification::handleActionInvoked);
183  connect(s_dbusInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, &DBusNotification::handleNotificationClosed);
184  }
185 }
186 
191 {
192  auto i = pendingNotifications.find(m_id);
193  if (i != pendingNotifications.end()) {
194  pendingNotifications.erase(i);
195  }
196  hide();
197 }
198 
203 {
204  initInterface();
205  return s_dbusInterface->isValid();
206 }
207 
212 {
213  switch (icon) {
215  m_icon = QStringLiteral("dialog-information");
216  break;
218  m_icon = QStringLiteral("dialog-warning");
219  break;
221  m_icon = QStringLiteral("dialog-critical");
222  break;
223  default:;
224  }
225 }
226 
231 const QImage DBusNotification::image() const
232 {
233  return NotificationImage::fromDBusArgument(hint(QStringLiteral("image-data"), QStringLiteral("image_data"))).toQImage();
234 }
235 
242 void DBusNotification::setImage(const QImage &image)
243 {
244  m_hints[QStringLiteral("image-data")] = NotificationImage(SwappedImage(image)).toDBusArgument();
245 }
246 
252 {
253  connect(this, &DBusNotification::closed, this, &DBusNotification::deleteLater);
254  connect(this, &DBusNotification::error, this, &DBusNotification::deleteLater);
255 }
256 
265 {
266  if (!s_dbusInterface->isValid()) {
267  emit error();
268  return false;
269  }
270 
271  delete m_watcher;
272  m_watcher = new QDBusPendingCallWatcher(
273  s_dbusInterface->Notify(QCoreApplication::applicationName(), m_id, m_icon, m_title, m_msg, m_actions, m_hints, m_timeout), this);
274  connect(m_watcher, &QDBusPendingCallWatcher::finished, this, &DBusNotification::handleNotifyResult);
275  return true;
276 }
277 
285 bool DBusNotification::show(const QString &message)
286 {
287  m_msg = message;
288  return show();
289 }
290 
303 bool DBusNotification::update(const QString &line)
304 {
305  if (!isVisible() || m_msg.isEmpty()) {
306  m_msg = line;
307  } else {
308  if (!m_msg.startsWith(QStringLiteral("•"))) {
309  m_msg.insert(0, QStringLiteral("• "));
310  }
311  m_msg.append(QStringLiteral("\n• "));
312  m_msg.append(line);
313  }
314  return show();
315 }
316 
317 bool DBusNotification::queryCapabilities(const std::function<void(Capabilities &&capabilities)> &callback)
318 {
319  // ensure DBus-interface is initialized and valid
320  initInterface();
321  if (!s_dbusInterface->isValid()) {
322  return false;
323  }
324 
325  // invoke GetCapabilities() and pass the return value to the callback when available
326  const auto *const watcher = new QDBusPendingCallWatcher(s_dbusInterface->GetCapabilities());
327  connect(watcher, &QDBusPendingCallWatcher::finished, [&callback](QDBusPendingCallWatcher *watcher) {
328  watcher->deleteLater();
329  const QDBusPendingReply<QStringList> returnValue(*watcher);
330  if (returnValue.isError()) {
331  callback(Capabilities());
332  } else {
333  callback(Capabilities(move(returnValue.value())));
334  }
335  });
336  return true;
337 }
338 
346 {
347  if (m_id) {
348  s_dbusInterface->CloseNotification(m_id);
349  }
350 }
351 
355 void DBusNotification::handleNotifyResult(QDBusPendingCallWatcher *watcher)
356 {
357  if (watcher != m_watcher) {
358  return;
359  }
360 
361  watcher->deleteLater();
362  m_watcher = nullptr;
363 
364  QDBusPendingReply<uint> returnValue = *watcher;
365  if (returnValue.isError()) {
366  deleteLater();
367  emit error();
368  } else {
369  pendingNotifications[m_id = returnValue.argumentAt<0>()] = this;
370  emit shown();
371  }
372 }
373 
377 void DBusNotification::handleNotificationClosed(uint id, uint reason)
378 {
379  auto i = pendingNotifications.find(id);
380  if (i != pendingNotifications.end()) {
381  DBusNotification *notification = i->second;
382  notification->m_id = 0;
383  emit notification->closed(reason >= 1 && reason <= 3 ? static_cast<NotificationCloseReason>(reason) : NotificationCloseReason::Undefined);
384  pendingNotifications.erase(i);
385  }
386 }
387 
391 void DBusNotification::handleActionInvoked(uint id, const QString &action)
392 {
393  auto i = pendingNotifications.find(id);
394  if (i != pendingNotifications.end()) {
395  DBusNotification *notification = i->second;
396  emit notification->actionInvoked(action);
397  // Plasma 5 also closes the notification but doesn't emit the
398  // NotificationClose signal
399  // -> just consider the notification closed
400  emit notification->closed(NotificationCloseReason::ActionInvoked);
401  notification->m_id = 0;
402  pendingNotifications.erase(i);
403  // however, lxqt-notificationd does not close the notification
404  // -> close manually for consistent behaviour
405  s_dbusInterface->CloseNotification(i->first);
406  }
407 }
408 
460 } // namespace MiscUtils
void setIcon(const QString &icon)
Sets the icon name.
static NotificationImage fromDBusArgument(const QVariant &variant)
void closed(NotificationCloseReason reason)
Emitted when the notification has been closed.
bool update(const QString &line)
Updates the message and shows/updates the notification.
const QImage image() const
Returns the image.
STL namespace.
void hide()
Hides the notification (if still visible).
QVariant hint(const QString &name) const
Returns the hint with the specified name.
Q_DECLARE_METATYPE(MiscUtils::NotificationImage)
static bool queryCapabilities(const std::function< void(Capabilities &&capabilities)> &callback)
void shown()
Emitted when the notification could be shown successful.
static bool isAvailable()
Returns whether the notification D-Bus daemon is running.
QDBusArgument & operator<<(QDBusArgument &argument, const NotificationImage &img)
void setImage(const QImage &image)
Sets the image.
bool isVisible() const
Returns whether the notification is (still) visible.
DBusNotification(const QString &title, NotificationIcon icon=NotificationIcon::Information, int timeout=10000, QObject *parent=nullptr)
Creates a new notification (which is not shown instantly).
bool show()
Shows the notification.
void error()
Emitted when the notification couldn&#39;t be shown.
const QDBusArgument & operator>>(const QDBusArgument &argument, NotificationImage &img)
~DBusNotification()
Closes the notification if still shown and delete the object.
The SwappedImage struct represents RGB-interved version of the image specified on construction...
void deleteOnCloseOrError()
Makes the notification object delete itself when the notification has been closed or an error occurre...
The NotificationImage struct is a raw data image format.
const QString & icon() const
const QString & message() const