notificator.cpp raw

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