Line data Source code
1 : // Copyright (c) 2011-2018 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.
4 :
5 : #include <qt/notificator.h>
6 :
7 : #include <QApplication>
8 : #include <QByteArray>
9 : #include <QImageWriter>
10 : #include <QMessageBox>
11 : #include <QMetaType>
12 : #include <QStyle>
13 : #include <QSystemTrayIcon>
14 : #include <QTemporaryFile>
15 : #include <QVariant>
16 : #ifdef USE_DBUS
17 : #include <stdint.h>
18 : #include <QtDBus>
19 : #endif
20 : // Include ApplicationServices.h after QtDbus to avoid redefinition of check().
21 : // This affects at least OSX 10.6. See /usr/include/AssertMacros.h for details.
22 : // Note: This could also be worked around using:
23 : // #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
24 : #ifdef Q_OS_MAC
25 : #include <ApplicationServices/ApplicationServices.h>
26 : #include <qt/macnotificationhandler.h>
27 : #endif
28 :
29 :
30 : #ifdef USE_DBUS
31 : // https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least 128
32 : const int FREEDESKTOP_NOTIFICATION_ICON_SIZE = 128;
33 : #endif
34 :
35 0 : Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon, QWidget *_parent) :
36 0 : QObject(_parent),
37 0 : parent(_parent),
38 0 : programName(_programName),
39 0 : mode(None),
40 0 : trayIcon(_trayIcon)
41 : #ifdef USE_DBUS
42 0 : ,interface(nullptr)
43 : #endif
44 0 : {
45 0 : if(_trayIcon && _trayIcon->supportsMessages())
46 : {
47 0 : mode = QSystemTray;
48 0 : }
49 : #ifdef USE_DBUS
50 0 : interface = new QDBusInterface("org.freedesktop.Notifications",
51 0 : "/org/freedesktop/Notifications", "org.freedesktop.Notifications");
52 0 : if(interface->isValid())
53 : {
54 0 : mode = Freedesktop;
55 0 : }
56 : #endif
57 : #ifdef Q_OS_MAC
58 : // check if users OS has support for NSUserNotification
59 0 : if( MacNotificationHandler::instance()->hasUserNotificationCenterSupport()) {
60 0 : mode = UserNotificationCenter;
61 0 : }
62 : #endif
63 0 : }
64 :
65 0 : Notificator::~Notificator()
66 0 : {
67 : #ifdef USE_DBUS
68 0 : delete interface;
69 : #endif
70 0 : }
71 :
72 : #ifdef USE_DBUS
73 :
74 : // Loosely based on http://www.qtcentre.org/archive/index.php/t-25879.html
75 0 : class FreedesktopImage
76 : {
77 : public:
78 0 : FreedesktopImage() {}
79 : explicit FreedesktopImage(const QImage &img);
80 :
81 : static int metaType();
82 :
83 : // Image to variant that can be marshalled 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 0 : 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 0 : FreedesktopImage::FreedesktopImage(const QImage &img):
105 0 : width(img.width()),
106 0 : height(img.height()),
107 0 : stride(img.width() * BYTES_PER_PIXEL),
108 0 : hasAlpha(true),
109 0 : channels(CHANNELS),
110 0 : bitsPerSample(BITS_PER_SAMPLE)
111 0 : {
112 : // Convert 00xAARRGGBB to RGBA bytewise (endian-independent) format
113 0 : QImage tmp = img.convertToFormat(QImage::Format_ARGB32);
114 0 : const uint32_t *data = reinterpret_cast<const uint32_t*>(tmp.bits());
115 :
116 0 : unsigned int num_pixels = width * height;
117 0 : image.resize(num_pixels * BYTES_PER_PIXEL);
118 :
119 0 : for(unsigned int ptr = 0; ptr < num_pixels; ++ptr)
120 : {
121 0 : image[ptr*BYTES_PER_PIXEL+0] = data[ptr] >> 16; // R
122 0 : image[ptr*BYTES_PER_PIXEL+1] = data[ptr] >> 8; // G
123 0 : image[ptr*BYTES_PER_PIXEL+2] = data[ptr]; // B
124 0 : image[ptr*BYTES_PER_PIXEL+3] = data[ptr] >> 24; // A
125 : }
126 0 : }
127 :
128 0 : QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i)
129 : {
130 0 : a.beginStructure();
131 0 : a << i.width << i.height << i.stride << i.hasAlpha << i.bitsPerSample << i.channels << i.image;
132 0 : a.endStructure();
133 0 : return a;
134 : }
135 :
136 0 : const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i)
137 : {
138 0 : a.beginStructure();
139 0 : a >> i.width >> i.height >> i.stride >> i.hasAlpha >> i.bitsPerSample >> i.channels >> i.image;
140 0 : a.endStructure();
141 0 : return a;
142 : }
143 :
144 0 : int FreedesktopImage::metaType()
145 : {
146 0 : return qDBusRegisterMetaType<FreedesktopImage>();
147 : }
148 :
149 0 : QVariant FreedesktopImage::toVariant(const QImage &img)
150 : {
151 0 : FreedesktopImage fimg(img);
152 0 : return QVariant(FreedesktopImage::metaType(), &fimg);
153 0 : }
154 :
155 0 : void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
156 : {
157 : // https://developer.gnome.org/notification-spec/
158 : // Arguments for DBus "Notify" call:
159 0 : QList<QVariant> args;
160 :
161 : // Program Name:
162 0 : args.append(programName);
163 :
164 : // Replaces ID; A value of 0 means that this notification won't replace any existing notifications:
165 0 : args.append(0U);
166 :
167 : // Application Icon, empty string
168 0 : args.append(QString());
169 :
170 : // Summary
171 0 : args.append(title);
172 :
173 : // Body
174 0 : args.append(text);
175 :
176 : // Actions (none, actions are deprecated)
177 0 : QStringList actions;
178 0 : args.append(actions);
179 :
180 : // Hints
181 0 : QVariantMap hints;
182 :
183 : // If no icon specified, set icon based on class
184 0 : QIcon tmpicon;
185 0 : if(icon.isNull())
186 : {
187 : QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion;
188 0 : switch(cls)
189 : {
190 0 : case Information: sicon = QStyle::SP_MessageBoxInformation; break;
191 0 : case Warning: sicon = QStyle::SP_MessageBoxWarning; break;
192 0 : case Critical: sicon = QStyle::SP_MessageBoxCritical; break;
193 : default: break;
194 : }
195 0 : tmpicon = QApplication::style()->standardIcon(sicon);
196 0 : }
197 : else
198 : {
199 0 : tmpicon = icon;
200 : }
201 0 : hints["icon_data"] = FreedesktopImage::toVariant(tmpicon.pixmap(FREEDESKTOP_NOTIFICATION_ICON_SIZE).toImage());
202 0 : args.append(hints);
203 :
204 : // Timeout (in msec)
205 0 : args.append(millisTimeout);
206 :
207 : // "Fire and forget"
208 0 : interface->callWithArgumentList(QDBus::NoBlock, "Notify", args);
209 0 : }
210 : #endif
211 :
212 0 : void Notificator::notifySystray(Class cls, const QString &title, const QString &text, int millisTimeout)
213 : {
214 : QSystemTrayIcon::MessageIcon sicon = QSystemTrayIcon::NoIcon;
215 0 : switch(cls) // Set icon based on class
216 : {
217 0 : case Information: sicon = QSystemTrayIcon::Information; break;
218 0 : case Warning: sicon = QSystemTrayIcon::Warning; break;
219 0 : case Critical: sicon = QSystemTrayIcon::Critical; break;
220 : }
221 0 : trayIcon->showMessage(title, text, sicon, millisTimeout);
222 0 : }
223 :
224 : #ifdef Q_OS_MAC
225 0 : void Notificator::notifyMacUserNotificationCenter(const QString &title, const QString &text)
226 : {
227 : // icon is not supported by the user notification center yet. OSX will use the app icon.
228 0 : MacNotificationHandler::instance()->showNotification(title, text);
229 0 : }
230 : #endif
231 :
232 0 : void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout)
233 : {
234 0 : switch(mode)
235 : {
236 : #ifdef USE_DBUS
237 : case Freedesktop:
238 0 : notifyDBus(cls, title, text, icon, millisTimeout);
239 0 : break;
240 : #endif
241 : case QSystemTray:
242 0 : notifySystray(cls, title, text, millisTimeout);
243 0 : break;
244 : #ifdef Q_OS_MAC
245 : case UserNotificationCenter:
246 0 : notifyMacUserNotificationCenter(title, text);
247 0 : break;
248 : #endif
249 : default:
250 0 : if(cls == Critical)
251 : {
252 : // Fall back to old fashioned pop-up dialog if critical and no other notification available
253 0 : QMessageBox::critical(parent, title, text, QMessageBox::Ok, QMessageBox::Ok);
254 0 : }
255 : break;
256 : }
257 0 : }
|