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