1 // Copyright (c) 2011-2016 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #include "notificator.h"
7 #include <QApplication>
10 #include <QImageWriter>
11 #include <QMessageBox>
14 #include <QSystemTrayIcon>
15 #include <QTemporaryFile>
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
26 #include <ApplicationServices/ApplicationServices.h>
27 #include "macnotificationhandler.h"
32 // https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least 128
33 const int FREEDESKTOP_NOTIFICATION_ICON_SIZE
= 128;
36 Notificator::Notificator(const QString
&_programName
, QSystemTrayIcon
*_trayIcon
, QWidget
*_parent
) :
39 programName(_programName
),
46 if(_trayIcon
&& _trayIcon
->supportsMessages())
51 interface
= new QDBusInterface("org.freedesktop.Notifications",
52 "/org/freedesktop/Notifications", "org.freedesktop.Notifications");
53 if(interface
->isValid())
59 // check if users OS has support for NSUserNotification
60 if( MacNotificationHandler::instance()->hasUserNotificationCenterSupport()) {
61 mode
= UserNotificationCenter
;
64 // Check if Growl is installed (based on Qt's tray icon implementation)
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/")))
82 Notificator::~Notificator()
91 // Loosely based on http://www.qtcentre.org/archive/index.php/t-25879.html
92 class FreedesktopImage
96 FreedesktopImage(const QImage
&img
);
98 static int metaType();
100 // Image to variant that can be marshalled over DBus
101 static QVariant
toVariant(const QImage
&img
);
104 int width
, height
, stride
;
110 friend QDBusArgument
&operator<<(QDBusArgument
&a
, const FreedesktopImage
&i
);
111 friend const QDBusArgument
&operator>>(const QDBusArgument
&a
, FreedesktopImage
&i
);
114 Q_DECLARE_METATYPE(FreedesktopImage
);
116 // Image configuration settings
117 const int CHANNELS
= 4;
118 const int BYTES_PER_PIXEL
= 4;
119 const int BITS_PER_SAMPLE
= 8;
121 FreedesktopImage::FreedesktopImage(const QImage
&img
):
123 height(img
.height()),
124 stride(img
.width() * BYTES_PER_PIXEL
),
127 bitsPerSample(BITS_PER_SAMPLE
)
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());
133 unsigned int num_pixels
= width
* height
;
134 image
.resize(num_pixels
* BYTES_PER_PIXEL
);
136 for(unsigned int ptr
= 0; ptr
< num_pixels
; ++ptr
)
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
145 QDBusArgument
&operator<<(QDBusArgument
&a
, const FreedesktopImage
&i
)
148 a
<< i
.width
<< i
.height
<< i
.stride
<< i
.hasAlpha
<< i
.bitsPerSample
<< i
.channels
<< i
.image
;
153 const QDBusArgument
&operator>>(const QDBusArgument
&a
, FreedesktopImage
&i
)
156 a
>> i
.width
>> i
.height
>> i
.stride
>> i
.hasAlpha
>> i
.bitsPerSample
>> i
.channels
>> i
.image
;
161 int FreedesktopImage::metaType()
163 return qDBusRegisterMetaType
<FreedesktopImage
>();
166 QVariant
FreedesktopImage::toVariant(const QImage
&img
)
168 FreedesktopImage
fimg(img
);
169 return QVariant(FreedesktopImage::metaType(), &fimg
);
172 void Notificator::notifyDBus(Class cls
, const QString
&title
, const QString
&text
, const QIcon
&icon
, int millisTimeout
)
175 // Arguments for DBus call:
176 QList
<QVariant
> args
;
179 args
.append(programName
);
181 // Unique ID of this notification type:
184 // Application Icon, empty string
185 args
.append(QString());
193 // Actions (none, actions are deprecated)
195 args
.append(actions
);
200 // If no icon specified, set icon based on class
204 QStyle::StandardPixmap sicon
= QStyle::SP_MessageBoxQuestion
;
207 case Information
: sicon
= QStyle::SP_MessageBoxInformation
; break;
208 case Warning
: sicon
= QStyle::SP_MessageBoxWarning
; break;
209 case Critical
: sicon
= QStyle::SP_MessageBoxCritical
; break;
212 tmpicon
= QApplication::style()->standardIcon(sicon
);
218 hints
["icon_data"] = FreedesktopImage::toVariant(tmpicon
.pixmap(FREEDESKTOP_NOTIFICATION_ICON_SIZE
).toImage());
222 args
.append(millisTimeout
);
225 interface
->callWithArgumentList(QDBus::NoBlock
, "Notify", args
);
229 void Notificator::notifySystray(Class cls
, const QString
&title
, const QString
&text
, const QIcon
&icon
, int millisTimeout
)
232 QSystemTrayIcon::MessageIcon sicon
= QSystemTrayIcon::NoIcon
;
233 switch(cls
) // Set icon based on class
235 case Information
: sicon
= QSystemTrayIcon::Information
; break;
236 case Warning
: sicon
= QSystemTrayIcon::Warning
; break;
237 case Critical
: sicon
= QSystemTrayIcon::Critical
; break;
239 trayIcon
->showMessage(title
, text
, sicon
, millisTimeout
);
242 // Based on Qt's tray icon implementation
244 void Notificator::notifyGrowl(Class cls
, const QString
&title
, const QString
&text
, const QIcon
&icon
)
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
255 QString
notificationApp(QApplication::applicationName());
256 if (notificationApp
.isEmpty())
257 notificationApp
= "Application";
259 QPixmap notificationIconPixmap
;
260 if (icon
.isNull()) { // If no icon specified, set icon based on class
261 QStyle::StandardPixmap sicon
= QStyle::SP_MessageBoxQuestion
;
264 case Information
: sicon
= QStyle::SP_MessageBoxInformation
; break;
265 case Warning
: sicon
= QStyle::SP_MessageBoxWarning
; break;
266 case Critical
: sicon
= QStyle::SP_MessageBoxCritical
; break;
268 notificationIconPixmap
= QApplication::style()->standardPixmap(sicon
);
271 QSize size
= icon
.actualSize(QSize(48, 48));
272 notificationIconPixmap
= icon
.pixmap(size
);
275 QString notificationIcon
;
276 QTemporaryFile notificationIconFile
;
277 if (!notificationIconPixmap
.isNull() && notificationIconFile
.open()) {
278 QImageWriter
writer(¬ificationIconFile
, "PNG");
279 if (writer
.write(notificationIconPixmap
.toImage()))
280 notificationIcon
= QString(" image from location \"file://%1\"").arg(notificationIconFile
.fileName());
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
));
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
);
297 void Notificator::notify(Class cls
, const QString
&title
, const QString
&text
, const QIcon
&icon
, int millisTimeout
)
303 notifyDBus(cls
, title
, text
, icon
, millisTimeout
);
307 notifySystray(cls
, title
, text
, icon
, millisTimeout
);
310 case UserNotificationCenter
:
311 notifyMacUserNotificationCenter(cls
, title
, text
, icon
);
315 notifyGrowl(cls
, title
, text
, icon
);
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
);