diff --git a/src/qt/macnotificationhandler.h b/src/qt/macnotificationhandler.h index fc15e6827..8c54b1d9c 100644 --- a/src/qt/macnotificationhandler.h +++ b/src/qt/macnotificationhandler.h @@ -1,29 +1,25 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_MACNOTIFICATIONHANDLER_H #define BITCOIN_QT_MACNOTIFICATIONHANDLER_H #include <QObject> -/** Macintosh-specific notification handler (supports UserNotificationCenter and - * Growl). +/** Macintosh-specific notification handler (supports UserNotificationCenter). */ class MacNotificationHandler : public QObject { Q_OBJECT public: - /** shows a 10.8+ UserNotification in the UserNotificationCenter + /** shows a macOS 10.8+ UserNotification in the UserNotificationCenter */ void showNotification(const QString &title, const QString &text); - /** executes AppleScript */ - void sendAppleScript(const QString &script); - /** check if OS can handle UserNotifications */ bool hasUserNotificationCenterSupport(void); static MacNotificationHandler *instance(); }; #endif // BITCOIN_QT_MACNOTIFICATIONHANDLER_H diff --git a/src/qt/macnotificationhandler.mm b/src/qt/macnotificationhandler.mm index 9a4b17463..bafab0955 100644 --- a/src/qt/macnotificationhandler.mm +++ b/src/qt/macnotificationhandler.mm @@ -1,91 +1,77 @@ // Copyright (c) 2011-2013 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "macnotificationhandler.h" #undef slots #import <objc/runtime.h> #include <Cocoa/Cocoa.h> // Add an obj-c category (extension) to return the expected bundle identifier @implementation NSBundle(returnCorrectIdentifier) - (NSString *)__bundleIdentifier { if (self == [NSBundle mainBundle]) { return @"org.bitcoinabc.BitcoinABC-Qt"; } else { return [self __bundleIdentifier]; } } @end void MacNotificationHandler::showNotification(const QString &title, const QString &text) { // check if users OS has support for NSUserNotification if(this->hasUserNotificationCenterSupport()) { // okay, seems like 10.8+ QByteArray utf8 = title.toUtf8(); char* cString = (char *)utf8.constData(); NSString *titleMac = [[NSString alloc] initWithUTF8String:cString]; utf8 = text.toUtf8(); cString = (char *)utf8.constData(); NSString *textMac = [[NSString alloc] initWithUTF8String:cString]; // do everything weak linked (because we will keep <10.8 compatibility) id userNotification = [[NSClassFromString(@"NSUserNotification") alloc] init]; [userNotification performSelector:@selector(setTitle:) withObject:titleMac]; [userNotification performSelector:@selector(setInformativeText:) withObject:textMac]; id notificationCenterInstance = [NSClassFromString(@"NSUserNotificationCenter") performSelector:@selector(defaultUserNotificationCenter)]; [notificationCenterInstance performSelector:@selector(deliverNotification:) withObject:userNotification]; [titleMac release]; [textMac release]; [userNotification release]; } } -// sendAppleScript just take a QString and executes it as apple script -void MacNotificationHandler::sendAppleScript(const QString &script) -{ - QByteArray utf8 = script.toUtf8(); - char* cString = (char *)utf8.constData(); - NSString *scriptApple = [[NSString alloc] initWithUTF8String:cString]; - - NSAppleScript *as = [[NSAppleScript alloc] initWithSource:scriptApple]; - NSDictionary *err = nil; - [as executeAndReturnError:&err]; - [as release]; - [scriptApple release]; -} - bool MacNotificationHandler::hasUserNotificationCenterSupport(void) { Class possibleClass = NSClassFromString(@"NSUserNotificationCenter"); // check if users OS has support for NSUserNotification if(possibleClass!=nil) { return true; } return false; } MacNotificationHandler *MacNotificationHandler::instance() { static MacNotificationHandler *s_instance = nullptr; if (!s_instance) { s_instance = new MacNotificationHandler(); Class aPossibleClass = objc_getClass("NSBundle"); if (aPossibleClass) { // change NSBundle -bundleIdentifier method to return a correct bundle identifier // a bundle identifier is required to use OSXs User Notification Center method_exchangeImplementations(class_getInstanceMethod(aPossibleClass, @selector(bundleIdentifier)), class_getInstanceMethod(aPossibleClass, @selector(__bundleIdentifier))); } } return s_instance; } diff --git a/src/qt/notificator.cpp b/src/qt/notificator.cpp index 69c5f420a..a3110ca5d 100644 --- a/src/qt/notificator.cpp +++ b/src/qt/notificator.cpp @@ -1,354 +1,268 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "notificator.h" #include <QApplication> #include <QByteArray> #include <QIcon> #include <QImageWriter> #include <QMessageBox> #include <QMetaType> #include <QStyle> #include <QSystemTrayIcon> #include <QTemporaryFile> #include <QVariant> #ifdef USE_DBUS #include <QtDBus> #include <cstdint> #endif // Include ApplicationServices.h after QtDbus to avoid redefinition of check(). // This affects at least OSX 10.6. See /usr/include/AssertMacros.h for details. // Note: This could also be worked around using: // #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 #ifdef Q_OS_MAC #include "macnotificationhandler.h" #include <ApplicationServices/ApplicationServices.h> #endif #ifdef USE_DBUS // https://wiki.ubuntu.com/NotificationDevelopmentGuidelines recommends at least // 128 const int FREEDESKTOP_NOTIFICATION_ICON_SIZE = 128; #endif Notificator::Notificator(const QString &_programName, QSystemTrayIcon *_trayIcon, QWidget *_parent) : QObject(_parent), parent(_parent), programName(_programName), mode(None), trayIcon(_trayIcon) #ifdef USE_DBUS , interface(0) #endif { if (_trayIcon && _trayIcon->supportsMessages()) { mode = QSystemTray; } #ifdef USE_DBUS interface = new QDBusInterface("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications"); if (interface->isValid()) { mode = Freedesktop; } #endif #ifdef Q_OS_MAC // check if users OS has support for NSUserNotification if (MacNotificationHandler::instance() ->hasUserNotificationCenterSupport()) { mode = UserNotificationCenter; - } else { - // Check if Growl is installed (based on Qt's tray icon implementation) - CFURLRef cfurl; - OSStatus status = LSGetApplicationForInfo( - kLSUnknownType, kLSUnknownCreator, CFSTR("growlTicket"), - kLSRolesAll, 0, &cfurl); - if (status != kLSApplicationNotFoundErr) { - CFBundleRef bundle = CFBundleCreate(0, cfurl); - if (CFStringCompare(CFBundleGetIdentifier(bundle), - CFSTR("com.Growl.GrowlHelperApp"), - kCFCompareCaseInsensitive | - kCFCompareBackwards) == kCFCompareEqualTo) { - if (CFStringHasSuffix(CFURLGetString(cfurl), - CFSTR("/Growl.app/"))) - mode = Growl13; - else - mode = Growl12; - } - CFRelease(cfurl); - CFRelease(bundle); - } } #endif } Notificator::~Notificator() { #ifdef USE_DBUS delete interface; #endif } #ifdef USE_DBUS // Loosely based on http://www.qtcentre.org/archive/index.php/t-25879.html class FreedesktopImage { public: FreedesktopImage() {} FreedesktopImage(const QImage &img); static int metaType(); // Image to variant that can be marshalled over DBus static QVariant toVariant(const QImage &img); private: int width, height, stride; bool hasAlpha; int channels; int bitsPerSample; QByteArray image; friend QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i); friend const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i); }; Q_DECLARE_METATYPE(FreedesktopImage); // Image configuration settings const int CHANNELS = 4; const int BYTES_PER_PIXEL = 4; const int BITS_PER_SAMPLE = 8; FreedesktopImage::FreedesktopImage(const QImage &img) : width(img.width()), height(img.height()), stride(img.width() * BYTES_PER_PIXEL), hasAlpha(true), channels(CHANNELS), bitsPerSample(BITS_PER_SAMPLE) { // Convert 00xAARRGGBB to RGBA bytewise (endian-independent) format QImage tmp = img.convertToFormat(QImage::Format_ARGB32); const uint32_t *data = reinterpret_cast<const uint32_t *>(tmp.bits()); unsigned int num_pixels = width * height; image.resize(num_pixels * BYTES_PER_PIXEL); for (unsigned int ptr = 0; ptr < num_pixels; ++ptr) { image[ptr * BYTES_PER_PIXEL + 0] = data[ptr] >> 16; // R image[ptr * BYTES_PER_PIXEL + 1] = data[ptr] >> 8; // G image[ptr * BYTES_PER_PIXEL + 2] = data[ptr]; // B image[ptr * BYTES_PER_PIXEL + 3] = data[ptr] >> 24; // A } } QDBusArgument &operator<<(QDBusArgument &a, const FreedesktopImage &i) { a.beginStructure(); a << i.width << i.height << i.stride << i.hasAlpha << i.bitsPerSample << i.channels << i.image; a.endStructure(); return a; } const QDBusArgument &operator>>(const QDBusArgument &a, FreedesktopImage &i) { a.beginStructure(); a >> i.width >> i.height >> i.stride >> i.hasAlpha >> i.bitsPerSample >> i.channels >> i.image; a.endStructure(); return a; } int FreedesktopImage::metaType() { return qDBusRegisterMetaType<FreedesktopImage>(); } QVariant FreedesktopImage::toVariant(const QImage &img) { FreedesktopImage fimg(img); return QVariant(FreedesktopImage::metaType(), &fimg); } void Notificator::notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) { Q_UNUSED(cls); // Arguments for DBus call: QList<QVariant> args; // Program Name: args.append(programName); // Unique ID of this notification type: args.append(0U); // Application Icon, empty string args.append(QString()); // Summary args.append(title); // Body args.append(text); // Actions (none, actions are deprecated) QStringList actions; args.append(actions); // Hints QVariantMap hints; // If no icon specified, set icon based on class QIcon tmpicon; if (icon.isNull()) { QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion; switch (cls) { case Information: sicon = QStyle::SP_MessageBoxInformation; break; case Warning: sicon = QStyle::SP_MessageBoxWarning; break; case Critical: sicon = QStyle::SP_MessageBoxCritical; break; default: break; } tmpicon = QApplication::style()->standardIcon(sicon); } else { tmpicon = icon; } hints["icon_data"] = FreedesktopImage::toVariant( tmpicon.pixmap(FREEDESKTOP_NOTIFICATION_ICON_SIZE).toImage()); args.append(hints); // Timeout (in msec) args.append(millisTimeout); // "Fire and forget" interface->callWithArgumentList(QDBus::NoBlock, "Notify", args); } #endif void Notificator::notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) { Q_UNUSED(icon); QSystemTrayIcon::MessageIcon sicon = QSystemTrayIcon::NoIcon; switch (cls) // Set icon based on class { case Information: sicon = QSystemTrayIcon::Information; break; case Warning: sicon = QSystemTrayIcon::Warning; break; case Critical: sicon = QSystemTrayIcon::Critical; break; } trayIcon->showMessage(title, text, sicon, millisTimeout); } // Based on Qt's tray icon implementation #ifdef Q_OS_MAC -void Notificator::notifyGrowl(Class cls, const QString &title, - const QString &text, const QIcon &icon) { - const QString script("tell application \"%5\"\n" - " set the allNotificationsList to " - "{\"Notification\"}\n" // -- Make a list of all the - // notification types (all) - " set the enabledNotificationsList to " - "{\"Notification\"}\n" // -- Make a list of the - // notifications (enabled) - " register as application \"%1\" all notifications " - "allNotificationsList default notifications " - "enabledNotificationsList\n" // -- Register our script - // with Growl - " notify with name \"Notification\" title \"%2\" " - "description \"%3\" application name \"%1\"%4\n" // -- - // Send - // a - // Notification - "end tell"); - - QString notificationApp(QApplication::applicationName()); - if (notificationApp.isEmpty()) notificationApp = "Application"; - - QPixmap notificationIconPixmap; - if (icon.isNull()) { // If no icon specified, set icon based on class - QStyle::StandardPixmap sicon = QStyle::SP_MessageBoxQuestion; - switch (cls) { - case Information: - sicon = QStyle::SP_MessageBoxInformation; - break; - case Warning: - sicon = QStyle::SP_MessageBoxWarning; - break; - case Critical: - sicon = QStyle::SP_MessageBoxCritical; - break; - } - notificationIconPixmap = QApplication::style()->standardPixmap(sicon); - } else { - QSize size = icon.actualSize(QSize(48, 48)); - notificationIconPixmap = icon.pixmap(size); - } - - QString notificationIcon; - QTemporaryFile notificationIconFile; - if (!notificationIconPixmap.isNull() && notificationIconFile.open()) { - QImageWriter writer(¬ificationIconFile, "PNG"); - if (writer.write(notificationIconPixmap.toImage())) - notificationIcon = QString(" image from location \"file://%1\"") - .arg(notificationIconFile.fileName()); - } - - QString quotedTitle(title), quotedText(text); - quotedTitle.replace("\\", "\\\\").replace("\"", "\\"); - quotedText.replace("\\", "\\\\").replace("\"", "\\"); - QString growlApp(this->mode == Notificator::Growl13 ? "Growl" - : "GrowlHelperApp"); - MacNotificationHandler::instance()->sendAppleScript(script.arg( - notificationApp, quotedTitle, quotedText, notificationIcon, growlApp)); -} - void Notificator::notifyMacUserNotificationCenter(Class cls, const QString &title, const QString &text, const QIcon &icon) { // icon is not supported by the user notification center yet. OSX will use // the app icon. MacNotificationHandler::instance()->showNotification(title, text); } #endif void Notificator::notify(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout) { switch (mode) { #ifdef USE_DBUS case Freedesktop: notifyDBus(cls, title, text, icon, millisTimeout); break; #endif case QSystemTray: notifySystray(cls, title, text, icon, millisTimeout); break; #ifdef Q_OS_MAC case UserNotificationCenter: notifyMacUserNotificationCenter(cls, title, text, icon); break; - case Growl12: - case Growl13: - notifyGrowl(cls, title, text, icon); - break; #endif default: if (cls == Critical) { // Fall back to old fashioned pop-up dialog if critical and no // other notification available QMessageBox::critical(parent, title, text, QMessageBox::Ok, QMessageBox::Ok); } break; } } diff --git a/src/qt/notificator.h b/src/qt/notificator.h index a0e88cea8..93e65e50a 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -1,88 +1,84 @@ // Copyright (c) 2011-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_NOTIFICATOR_H #define BITCOIN_QT_NOTIFICATOR_H #if defined(HAVE_CONFIG_H) #include "config/bitcoin-config.h" #endif #include <QIcon> #include <QObject> QT_BEGIN_NAMESPACE class QSystemTrayIcon; #ifdef USE_DBUS class QDBusInterface; #endif QT_END_NAMESPACE /** Cross-platform desktop notification client. */ class Notificator : public QObject { Q_OBJECT public: /** Create a new notificator. @note Ownership of trayIcon is not transferred to this object. */ Notificator(const QString &programName, QSystemTrayIcon *trayIcon, QWidget *parent); ~Notificator(); // Message class enum Class { Information, /**< Informational message */ Warning, /**< Notify user of potential problem */ Critical /**< An error occurred */ }; public Q_SLOTS: /** Show notification message. @param[in] cls general message class @param[in] title title shown with message @param[in] text message content @param[in] icon optional icon to show with message @param[in] millisTimeout notification timeout in milliseconds (defaults to 10 seconds) @note Platform implementations are free to ignore any of the provided fields except for \a text. */ void notify(Class cls, const QString &title, const QString &text, const QIcon &icon = QIcon(), int millisTimeout = 10000); private: QWidget *parent; enum Mode { None, /**< Ignore informational notifications, and show a modal pop-up dialog for Critical notifications. */ Freedesktop, /**< Use DBus org.freedesktop.Notifications */ QSystemTray, /**< Use QSystemTray::showMessage */ - Growl12, /**< Use the Growl 1.2 notification system (Mac only) */ - Growl13, /**< Use the Growl 1.3 notification system (Mac only) */ UserNotificationCenter /**< Use the 10.8+ User Notification Center (Mac only) */ }; QString programName; Mode mode; QSystemTrayIcon *trayIcon; #ifdef USE_DBUS QDBusInterface *interface; void notifyDBus(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #endif void notifySystray(Class cls, const QString &title, const QString &text, const QIcon &icon, int millisTimeout); #ifdef Q_OS_MAC - void notifyGrowl(Class cls, const QString &title, const QString &text, - const QIcon &icon); void notifyMacUserNotificationCenter(Class cls, const QString &title, const QString &text, const QIcon &icon); #endif }; #endif // BITCOIN_QT_NOTIFICATOR_H