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