Anoncoin  0.9.4
P2P Digital Currency
notificator.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2013 The Bitcoin developers
2 // Copyright (c) 2013-2014 The Anoncoin Core developers
3 // Distributed under the MIT/X11 software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #include "notificator.h"
7 
8 #include <QApplication>
9 #include <QByteArray>
10 #include <QIcon>
11 #include <QImageWriter>
12 #include <QMessageBox>
13 #include <QMetaType>
14 #include <QStyle>
15 #include <QSystemTrayIcon>
16 #include <QTemporaryFile>
17 #include <QVariant>
18 #ifdef USE_DBUS
19 #include <stdint.h>
20 #include <QtDBus>
21 #endif
22 // Include ApplicationServices.h after QtDbus to avoid redefinition of check().
23 // This affects at least OSX 10.6. See /usr/include/AssertMacros.h for details.
24 // Note: This could also be worked around using:
25 // #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
26 #ifdef Q_OS_MAC
27 #include <ApplicationServices/ApplicationServices.h>
28 #include "macnotificationhandler.h"
29 #endif
30 
31 
32 // https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least 128
34 
35 Notificator::Notificator(const QString &programName, QSystemTrayIcon *trayicon, QWidget *parent) :
36  QObject(parent),
37  parent(parent),
38  programName(programName),
39  mode(None),
40  trayIcon(trayicon)
41 #ifdef USE_DBUS
42  ,interface(0)
43 #endif
44 {
45  if(trayicon && trayicon->supportsMessages())
46  {
47  mode = QSystemTray;
48  }
49 #ifdef USE_DBUS
50  interface = new QDBusInterface("org.freedesktop.Notifications",
51  "/org/freedesktop/Notifications", "org.freedesktop.Notifications");
52  if(interface->isValid())
53  {
54  mode = Freedesktop;
55  }
56 #endif
57 #ifdef Q_OS_MAC
58  // check if users OS has support for NSUserNotification
61  }
62  else {
63  // Check if Growl is installed (based on Qt's tray icon implementation)
64  CFURLRef cfurl;
65  OSStatus status = LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, CFSTR("growlTicket"), kLSRolesAll, 0, &cfurl);
66  if (status != kLSApplicationNotFoundErr) {
67  CFBundleRef bundle = CFBundleCreate(0, cfurl);
68  if (CFStringCompare(CFBundleGetIdentifier(bundle), CFSTR("com.Growl.GrowlHelperApp"), kCFCompareCaseInsensitive | kCFCompareBackwards) == kCFCompareEqualTo) {
69  if (CFStringHasSuffix(CFURLGetString(cfurl), CFSTR("/Growl.app/")))
70  mode = Growl13;
71  else
72  mode = Growl12;
73  }
74  CFRelease(cfurl);
75  CFRelease(bundle);
76  }
77  }
78 #endif
79 }
80 
82 {
83 #ifdef USE_DBUS
84  delete interface;
85 #endif
86 }
87 
88 #ifdef USE_DBUS
89 
90 // Loosely based on http://www.qtcentre.org/archive/index.php/t-25879.html
91 class FreedesktopImage
92 {
93 public:
94  FreedesktopImage() {}
95  FreedesktopImage(const QImage &img);
96 
97  static int metaType();
98 
99  // Image to variant that can be marshalled over DBus
100  static QVariant toVariant(const QImage &img);
101 
102 private:
103  int width, height, stride;
104  bool hasAlpha;
105  int channels;
106  int bitsPerSample;
107  QByteArray image;
108 
109  friend QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i);
110  friend const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i);
111 };
112 
113 Q_DECLARE_METATYPE(FreedesktopImage);
114 
115 // Image configuration settings
116 const int CHANNELS = 4;
117 const int BYTES_PER_PIXEL = 4;
118 const int BITS_PER_SAMPLE = 8;
119 
120 FreedesktopImage::FreedesktopImage(const QImage &img):
121  width(img.width()),
122  height(img.height()),
123  stride(img.width() * BYTES_PER_PIXEL),
124  hasAlpha(true),
125  channels(CHANNELS),
126  bitsPerSample(BITS_PER_SAMPLE)
127 {
128  // Convert 00xAARRGGBB to RGBA bytewise (endian-independent) format
129  QImage tmp = img.convertToFormat(QImage::Format_ARGB32);
130  const uint32_t *data = reinterpret_cast<const uint32_t*>(tmp.bits());
131 
132  unsigned int num_pixels = width * height;
133  image.resize(num_pixels * BYTES_PER_PIXEL);
134 
135  for(unsigned int ptr = 0; ptr < num_pixels; ++ptr)
136  {
137  image[ptr*BYTES_PER_PIXEL+0] = data[ptr] >> 16; // R
138  image[ptr*BYTES_PER_PIXEL+1] = data[ptr] >> 8; // G
139  image[ptr*BYTES_PER_PIXEL+2] = data[ptr]; // B
140  image[ptr*BYTES_PER_PIXEL+3] = data[ptr] >> 24; // A
141  }
142 }
143 
144 QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i)
145 {
146  a.beginStructure();
147  a << i.width << i.height << i.stride << i.hasAlpha << i.bitsPerSample << i.channels << i.image;
148  a.endStructure();
149  return a;
150 }
151 
152 const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i)
153 {
154  a.beginStructure();
155  a >> i.width >> i.height >> i.stride >> i.hasAlpha >> i.bitsPerSample >> i.channels >> i.image;
156  a.endStructure();
157  return a;
158 }
159 
160 int FreedesktopImage::metaType()
161 {
162  return qDBusRegisterMetaType<FreedesktopImage>();
163 }
164 
165 QVariant FreedesktopImage::toVariant(const QImage &img)
166 {
167  FreedesktopImage fimg(img);
168  return QVariant(FreedesktopImage::metaType(), &fimg);
169 }
170 
171 void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
172 {
173  Q_UNUSED(cls);
174  // Arguments for DBus call:
175  QList<QVariant> args;
176 
177  // Program Name:
178  args.append(programName);
179 
180  // Unique ID of this notification type:
181  args.append(0U);
182 
183  // Application Icon, empty string
184  args.append(QString());
185 
186  // Summary
187  args.append(title);
188 
189  // Body
190  args.append(text);
191 
192  // Actions (none, actions are deprecated)
193  QStringList actions;
194  args.append(actions);
195 
196  // Hints
197  QVariantMap hints;
198 
199  // If no icon specified, set icon based on class
200  QIcon tmpicon;
201  if(icon.isNull())
202  {
203  QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion;
204  switch(cls)
205  {
206  case Information: sicon = QStyle::SP_MessageBoxInformation; break;
207  case Warning: sicon = QStyle::SP_MessageBoxWarning; break;
208  case Critical: sicon = QStyle::SP_MessageBoxCritical; break;
209  default: break;
210  }
211  tmpicon = QApplication::style()->standardIcon(sicon);
212  }
213  else
214  {
215  tmpicon = icon;
216  }
217  hints["icon_data"] = FreedesktopImage::toVariant(tmpicon.pixmap(FREEDESKTOP_NOTIFICATION_ICON_SIZE).toImage());
218  args.append(hints);
219 
220  // Timeout (in msec)
221  args.append(millisTimeout);
222 
223  // "Fire and forget"
224  interface->callWithArgumentList(QDBus::NoBlock, "Notify", args);
225 }
226 #endif
227 
228 void Notificator::notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
229 {
230  Q_UNUSED(icon);
231  QSystemTrayIcon::MessageIcon sicon = QSystemTrayIcon::NoIcon;
232  switch(cls) // Set icon based on class
233  {
234  case Information: sicon = QSystemTrayIcon::Information; break;
235  case Warning: sicon = QSystemTrayIcon::Warning; break;
236  case Critical: sicon = QSystemTrayIcon::Critical; break;
237  }
238  trayIcon->showMessage(title, text, sicon, millisTimeout);
239 }
240 
241 // Based on Qt's tray icon implementation
242 #ifdef Q_OS_MAC
243 void Notificator::notifyGrowl(Class cls, const QString &title, const QString &text, const QIcon &icon)
244 {
245  const QString script(
246  "tell application \"%5\"\n"
247  " set the allNotificationsList to {\"Notification\"}\n" // -- Make a list of all the notification types (all)
248  " set the enabledNotificationsList to {\"Notification\"}\n" // -- Make a list of the notifications (enabled)
249  " register as application \"%1\" all notifications allNotificationsList default notifications enabledNotificationsList\n" // -- Register our script with Growl
250  " notify with name \"Notification\" title \"%2\" description \"%3\" application name \"%1\"%4\n" // -- Send a Notification
251  "end tell"
252  );
253 
254  QString notificationApp(QApplication::applicationName());
255  if (notificationApp.isEmpty())
256  notificationApp = "Application";
257 
258  QPixmap notificationIconPixmap;
259  if (icon.isNull()) { // If no icon specified, set icon based on class
260  QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion;
261  switch (cls)
262  {
263  case Information: sicon = QStyle::SP_MessageBoxInformation; break;
264  case Warning: sicon = QStyle::SP_MessageBoxWarning; break;
265  case Critical: sicon = QStyle::SP_MessageBoxCritical; break;
266  }
267  notificationIconPixmap = QApplication::style()->standardPixmap(sicon);
268  }
269  else {
270  QSize size = icon.actualSize(QSize(48, 48));
271  notificationIconPixmap = icon.pixmap(size);
272  }
273 
274  QString notificationIcon;
275  QTemporaryFile notificationIconFile;
276  if (!notificationIconPixmap.isNull() && notificationIconFile.open()) {
277  QImageWriter writer(&notificationIconFile, "PNG");
278  if (writer.write(notificationIconPixmap.toImage()))
279  notificationIcon = QString(" image from location \"file://%1\"").arg(notificationIconFile.fileName());
280  }
281 
282  QString quotedTitle(title), quotedText(text);
283  quotedTitle.replace("\\", "\\\\").replace("\"", "\\");
284  quotedText.replace("\\", "\\\\").replace("\"", "\\");
285  QString growlApp(this->mode == Notificator::Growl13 ? "Growl" : "GrowlHelperApp");
286  MacNotificationHandler::instance()->sendAppleScript(script.arg(notificationApp, quotedTitle, quotedText, notificationIcon, growlApp));
287 }
288 
289 void Notificator::notifyMacUserNotificationCenter(Class cls, const QString &title, const QString &text, const QIcon &icon) {
290  // icon is not supported by the user notification center yet. OSX will use the app icon.
292 }
293 
294 #endif
295 
296 void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
297 {
298  switch(mode)
299  {
300 #ifdef USE_DBUS
301  case Freedesktop:
302  notifyDBus(cls, title, text, icon, millisTimeout);
303  break;
304 #endif
305  case QSystemTray:
306  notifySystray(cls, title, text, icon, millisTimeout);
307  break;
308 #ifdef Q_OS_MAC
310  notifyMacUserNotificationCenter(cls, title, text, icon);
311  break;
312  case Growl12:
313  case Growl13:
314  notifyGrowl(cls, title, text, icon);
315  break;
316 #endif
317  default:
318  if(cls == Critical)
319  {
320  // Fall back to old fashioned pop-up dialog if critical and no other notification available
321  QMessageBox::critical(parent, title, text, QMessageBox::Ok, QMessageBox::Ok);
322  }
323  break;
324  }
325 }
QString programName
Definition: notificator.h:66
bool hasUserNotificationCenterSupport(void)
check if OS can handle UserNotifications
Use DBus org.freedesktop.Notifications.
Definition: notificator.h:60
QWidget * parent
Definition: notificator.h:57
Notificator(const QString &programName, QSystemTrayIcon *trayIcon, QWidget *parent)
Create a new notificator.
Definition: notificator.cpp:35
Notify user of potential problem.
Definition: notificator.h:40
const CBigNum operator<<(const CBigNum &a, unsigned int shift)
Definition: bignum.h:574
Use the 10.8+ User Notification Center (Mac only)
Definition: notificator.h:64
const CBigNum operator>>(const CBigNum &a, unsigned int shift)
Definition: bignum.h:582
void notify(Class cls, const QString &title, const QString &text, const QIcon &icon=QIcon(), int millisTimeout=10000)
Show notification message.
Informational message.
Definition: notificator.h:39
static MacNotificationHandler * instance()
An error occurred.
Definition: notificator.h:41
void sendAppleScript(const QString &script)
executes AppleScript
QSystemTrayIcon * trayIcon
Definition: notificator.h:68
const int FREEDESKTOP_NOTIFICATION_ICON_SIZE
Definition: notificator.cpp:33
Use the Growl 1.2 notification system (Mac only)
Definition: notificator.h:62
Use QSystemTray::showMessage.
Definition: notificator.h:61
void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
void showNotification(const QString &title, const QString &text)
shows a 10.8+ UserNotification in the UserNotificationCenter
Use the Growl 1.3 notification system (Mac only)
Definition: notificator.h:63