Qt Utilities  5.9.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::m_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 
61  NotificationImage(const QVariant &imageData);
63  QImage toQImage() const;
64  QVariant toDBusArgument() const;
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;
83  argument << img.height;
84  argument << img.rowstride;
85  argument << img.hasAlpha;
86  argument << img.bitsPerSample;
87  argument << img.channels;
88  argument << img.data;
89  argument.endStructure();
90  return argument;
91 }
92 
93 const QDBusArgument &operator>>(const QDBusArgument &argument, NotificationImage &img)
94 {
95  argument.beginStructure();
96  argument >> img.width;
97  argument >> img.height;
98  argument >> img.rowstride;
99  argument >> img.hasAlpha;
100  argument >> img.bitsPerSample;
101  argument >> img.channels;
102  argument >> img.data;
103  argument.endStructure();
104  return argument;
105 }
106 
108  : isValid(false)
109 {
110 }
111 
112 inline NotificationImage::NotificationImage(const QVariant &imageData)
113  : isValid(imageData.canConvert<QDBusArgument>())
114 {
115  if (isValid) {
116  imageData.value<QDBusArgument>() >> *this;
117  }
118 }
119 
121  : NotificationImage(static_cast<const QImage &>(image))
122 {
123 }
124 
125 inline NotificationImage::NotificationImage(const QImage &image)
126  : width(image.width())
127  , height(image.height())
128  , rowstride(image.bytesPerLine())
129  , hasAlpha(image.hasAlphaChannel())
130  , channels(image.isGrayscale() ? 1 : hasAlpha ? 4 : 3)
131  , bitsPerSample(image.depth() / channels)
132  , data(reinterpret_cast<const char *>(image.bits()), image.byteCount())
133  , isValid(!image.isNull())
134 {
135 }
136 
137 inline QImage NotificationImage::toQImage() const
138 {
139  return isValid ? QImage(reinterpret_cast<const uchar *>(data.constData()), width, height, hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32)
140  .rgbSwapped()
141  : QImage();
142 }
143 
144 inline QVariant NotificationImage::toDBusArgument() const
145 {
146  QDBusArgument arg;
147  return QVariant::fromValue(isValid ? arg << *this : arg);
148 }
149 
150 } // namespace MiscUtils
151 
153 
154 namespace MiscUtils {
155 
159 DBusNotification::DBusNotification(const QString &title, NotificationIcon icon, int timeout, QObject *parent)
160  : QObject(parent)
161  , m_id(0)
162  , m_watcher(nullptr)
163  , m_title(title)
164  , m_timeout(timeout)
165 {
166  initInterface();
167  setIcon(icon);
168 }
169 
173 DBusNotification::DBusNotification(const QString &title, const QString &icon, int timeout, QObject *parent)
174  : QObject(parent)
175  , m_id(0)
176  , m_watcher(nullptr)
177  , m_title(title)
178  , m_icon(icon)
179  , m_timeout(timeout)
180 {
181  initInterface();
182 }
183 
187 void DBusNotification::initInterface()
188 {
189  if (!m_dbusInterface) {
190  m_dbusInterface = new OrgFreedesktopNotificationsInterface(
191  QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus());
192  connect(m_dbusInterface, &OrgFreedesktopNotificationsInterface::ActionInvoked, &DBusNotification::handleActionInvoked);
193  connect(m_dbusInterface, &OrgFreedesktopNotificationsInterface::NotificationClosed, &DBusNotification::handleNotificationClosed);
194  }
195 }
196 
201 {
202  auto i = pendingNotifications.find(m_id);
203  if (i != pendingNotifications.end()) {
204  pendingNotifications.erase(i);
205  }
206  hide();
207 }
208 
213 {
214  initInterface();
215  return m_dbusInterface->isValid();
216 }
217 
222 {
223  switch (icon) {
225  m_icon = QStringLiteral("dialog-information");
226  break;
228  m_icon = QStringLiteral("dialog-warning");
229  break;
231  m_icon = QStringLiteral("dialog-critical");
232  break;
233  default:;
234  }
235 }
236 
241 const QImage DBusNotification::image() const
242 {
243  return NotificationImage(hint(QStringLiteral("image-data"), QStringLiteral("image_data"))).toQImage();
244 }
245 
252 void DBusNotification::setImage(const QImage &image)
253 {
254  m_hints[QStringLiteral("image-data")] = NotificationImage(SwappedImage(image)).toDBusArgument();
255 }
256 
262 {
263  connect(this, &DBusNotification::closed, this, &DBusNotification::deleteLater);
264  connect(this, &DBusNotification::error, this, &DBusNotification::deleteLater);
265 }
266 
275 {
276  if (!m_dbusInterface->isValid()) {
277  emit error();
278  return false;
279  }
280 
281  delete m_watcher;
282  m_watcher = new QDBusPendingCallWatcher(
283  m_dbusInterface->Notify(QCoreApplication::applicationName(), m_id, m_icon, m_title, m_msg, m_actions, m_hints, m_timeout), this);
284  connect(m_watcher, &QDBusPendingCallWatcher::finished, this, &DBusNotification::handleNotifyResult);
285  return true;
286 }
287 
295 bool DBusNotification::show(const QString &message)
296 {
297  m_msg = message;
298  return show();
299 }
300 
313 bool DBusNotification::update(const QString &line)
314 {
315  if (!isVisible() || m_msg.isEmpty()) {
316  m_msg = line;
317  } else {
318  if (!m_msg.startsWith(QStringLiteral("•"))) {
319  m_msg.insert(0, QStringLiteral("• "));
320  }
321  m_msg.append(QStringLiteral("\n• "));
322  m_msg.append(line);
323  }
324  return show();
325 }
326 
333 {
334  if (m_id) {
335  m_dbusInterface->CloseNotification(m_id);
336  }
337 }
338 
342 void DBusNotification::handleNotifyResult(QDBusPendingCallWatcher *watcher)
343 {
344  if (watcher != m_watcher) {
345  return;
346  }
347 
348  watcher->deleteLater();
349  m_watcher = nullptr;
350 
351  QDBusPendingReply<uint> returnValue = *watcher;
352  if (returnValue.isError()) {
353  deleteLater();
354  emit error();
355  } else {
356  pendingNotifications[m_id = returnValue.argumentAt<0>()] = this;
357  emit shown();
358  }
359 }
360 
364 void DBusNotification::handleNotificationClosed(uint id, uint reason)
365 {
366  auto i = pendingNotifications.find(id);
367  if (i != pendingNotifications.end()) {
368  DBusNotification *notification = i->second;
369  notification->m_id = 0;
370  emit notification->closed(reason >= 1 && reason <= 3 ? static_cast<NotificationCloseReason>(reason) : NotificationCloseReason::Undefined);
371  pendingNotifications.erase(i);
372  }
373 }
374 
378 void DBusNotification::handleActionInvoked(uint id, const QString &action)
379 {
380  auto i = pendingNotifications.find(id);
381  if (i != pendingNotifications.end()) {
382  DBusNotification *notification = i->second;
383  emit notification->actionInvoked(action);
384  // Plasma 5 also closes the notification but doesn't emit the
385  // NotificationClose signal
386  // -> just consider the notification closed
387  emit notification->closed(NotificationCloseReason::ActionInvoked);
388  notification->m_id = 0;
389  pendingNotifications.erase(i);
390  // however, lxqt-notificationd does not close the notification
391  // -> close manually for consistent behaviour
392  m_dbusInterface->CloseNotification(i->first);
393  }
394 }
395 
447 } // namespace MiscUtils
void setIcon(const QString &icon)
Sets the icon name.
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)
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 occured...
The ImageData struct is a raw data image format.
const QString & icon() const
const QString & message() const