icons: Fix icon theme support for StatusNotifierItem
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 11 Jun 2018 19:07:37 +0000 (21:07 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 15 Jun 2018 23:30:32 +0000 (01:30 +0200)
If SNI is used, the tray icons are passed along to the visualizer
by name rather than as a pixmap (the SNI protocol would support
pixmaps too, however that is a) discouraged and b) not supported
e.g. in Unity).

The SNI interface supports a more or less undocumented attribute
IconThemePath, which may contain an additional path for the
visualizer to look into for specific icons. At least Plasma and
LXQt seem to expect a hicolor theme in the given directory.

Have StatusNotifierItem create a skeleton hicolor theme containing
the tray icons in a temporary directory, and pass that along via
IconThemePath. The tray icons to be saved are dynamically determined
using the current icon theme/fallback.

All available icon sizes are saved as pixmaps; sadly, with the dynamic
approach, scalable icons cannot be supported. Alternatively, one could
hard-code the relevant .svg files from the fallback themes, and copy
those, but that would just be annoying - and kill themeability.

src/qtui/qtui.cpp
src/qtui/qtui.h
src/qtui/statusnotifieritem.cpp
src/qtui/statusnotifieritem.h

index 0082bd0..2cfc810 100644 (file)
@@ -339,6 +339,7 @@ void QtUi::refreshIconTheme()
     if (_systemIconTheme.isEmpty() || _systemIconTheme == fallbackTheme || s.value("Icons/OverrideSystemTheme", false).toBool()) {
         // We have a valid fallback theme and want to override the system theme (if it's even defined), so we're basically done
         QIcon::setThemeName(fallbackTheme);
+        emit iconThemeRefreshed();
         return;
     }
 
@@ -355,6 +356,7 @@ void QtUi::refreshIconTheme()
         if (!_dummyThemeDir->isValid() || !QDir{_dummyThemeDir->path()}.mkpath("icons/quassel-icon-proxy/apps/32")) {
             qWarning() << "Could not create temporary directory for proxying the system icon theme, using fallback";
             QIcon::setThemeName(fallbackTheme);
+            emit iconThemeRefreshed();
             return;
         }
         // Add this to XDG_DATA_DIRS, otherwise KIconLoader complains
@@ -371,6 +373,7 @@ void QtUi::refreshIconTheme()
     if (!indexFile.open(QFile::WriteOnly|QFile::Truncate)) {
         qWarning() << "Could not create index file for proxying the system icon theme, using fallback";
         QIcon::setThemeName(fallbackTheme);
+        emit iconThemeRefreshed();
         return;
     }
 
@@ -385,6 +388,7 @@ void QtUi::refreshIconTheme()
     if (indexFile.write(indexContents.toLatin1()) < 0) {
         qWarning() << "Could not write index file for proxying the system icon theme, using fallback";
         QIcon::setThemeName(fallbackTheme);
+        emit iconThemeRefreshed();
         return;
     }
     indexFile.close();
@@ -393,5 +397,6 @@ void QtUi::refreshIconTheme()
     // Qt4 doesn't support QTemporaryDir. Since it's deprecated and slated to be removed soon anyway, we don't bother
     // writing a replacement and simply don't support not overriding the system theme.
     QIcon::setThemeName(fallbackTheme);
+    emit iconThemeRefreshed();
 #endif
 }
index 0d13e64..e54fb24 100644 (file)
@@ -89,6 +89,9 @@ public slots:
      */
     void refreshIconTheme();
 
+signals:
+    void iconThemeRefreshed();
+
 protected slots:
     void connectedToCore() override;
     void disconnectedFromCore() override;
index df3eb40..e23b7d8 100644 (file)
 #ifdef HAVE_DBUS
 
 #include <QApplication>
+#include <QDir>
 #include <QMenu>
 #include <QMouseEvent>
 #include <QTextDocument>
 
+#include "qtui.h"
 #include "quassel.h"
 #include "statusnotifieritem.h"
 #include "statusnotifieritemdbus.h"
@@ -56,17 +58,26 @@ protected:
     }
 };
 
-
 #endif /* HAVE_DBUSMENU */
 
 StatusNotifierItem::StatusNotifierItem(QWidget *parent)
-    : StatusNotifierItemParent(parent),
-    _statusNotifierItemDBus(0),
-    _statusNotifierWatcher(0),
-    _notificationsClient(0),
-    _notificationsClientSupportsMarkup(true),
-    _lastNotificationsDBusId(0)
+    : StatusNotifierItemParent(parent)
+#if QT_VERSION >= 0x050000
+    , _iconThemeDir{QDir::tempPath() + QLatin1String{"/quassel-sni-XXXXXX"}}
+#endif
 {
+    // Create a temporary directory that holds copies of the tray icons. That way, visualizers can find our icons.
+    // For Qt4 the relevant icons are installed in hicolor already, so nothing to be done.
+#if QT_VERSION >= 0x050000
+    if (_iconThemeDir.isValid()) {
+        _iconThemePath = _iconThemeDir.path();
+    }
+    else {
+        qWarning() << StatusNotifierItem::tr("Could not create temporary directory for themed tray icons: %1").arg(_iconThemeDir.errorString());
+    }
+#endif
+
+    connect(QtUi::instance(), SIGNAL(iconThemeRefreshed()), this, SLOT(refreshIcons()));
 }
 
 
@@ -82,6 +93,8 @@ void StatusNotifierItem::init()
     qDBusRegisterMetaType<DBusImageVector>();
     qDBusRegisterMetaType<DBusToolTipStruct>();
 
+    refreshIcons();
+
     _statusNotifierItemDBus = new StatusNotifierItemDBus(this);
 
     connect(this, SIGNAL(toolTipChanged(QString, QString)), _statusNotifierItemDBus, SIGNAL(NewToolTip()));
@@ -110,9 +123,6 @@ void StatusNotifierItem::init()
     StatusNotifierItemParent::init();
     trayMenu()->installEventFilter(this);
 
-    // use the appdata icon folder for now
-    _iconThemePath = Quassel::findDataFilePath("icons");
-
 #ifdef HAVE_DBUSMENU
     _menuObjectPath = "/MenuBar";
     new QuasselDBusMenuExporter(menuObjectPath(), trayMenu(), _statusNotifierItemDBus->dbusConnection()); // will be added as menu child
@@ -148,7 +158,7 @@ void StatusNotifierItem::serviceChange(const QString &name, const QString &oldOw
         //unregistered
         //qDebug() << "Connection to the StatusNotifierWatcher lost";
         delete _statusNotifierWatcher;
-        _statusNotifierWatcher = 0;
+        _statusNotifierWatcher = nullptr;
         setMode(Legacy);
     }
     else if (oldOwner.isEmpty()) {
@@ -167,6 +177,33 @@ void StatusNotifierItem::checkForRegisteredHosts()
 }
 
 
+void StatusNotifierItem::refreshIcons()
+{
+#if QT_VERSION >= 0x050000
+    if (!_iconThemePath.isEmpty()) {
+        QDir baseDir{_iconThemePath + "/hicolor"};
+        baseDir.removeRecursively();
+        for (auto &&trayState : { State::Active, State::Passive, State::NeedsAttention }) {
+            const QIcon &icon = SystemTray::stateIcon(trayState);
+            if (!icon.name().isEmpty()) {
+                for (auto &&size : icon.availableSizes()) {
+                    auto pixDir = QString{"%1/%2x%3/status"}.arg(baseDir.absolutePath()).arg(size.width()).arg(size.height());
+                    QDir{}.mkpath(pixDir);
+                    if (!icon.pixmap(size).save(pixDir + "/" + icon.name() + ".png")) {
+                        qWarning() << "Could not save tray icon" << icon.name() << "for size" << size;
+                    }
+                }
+            }
+        }
+    }
+#endif
+    if (_statusNotifierItemDBus) {
+        emit _statusNotifierItemDBus->NewIcon();
+        emit _statusNotifierItemDBus->NewAttentionIcon();
+    }
+}
+
+
 bool StatusNotifierItem::isSystemTrayAvailable() const
 {
     if (mode() == StatusNotifier)
index 534d35f..9a805be 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef STATUSNOTIFIERITEM_H_
-#define STATUSNOTIFIERITEM_H_
+#pragma once
 
 #ifdef HAVE_DBUS
 
+#include <QtGlobal>
+
+#if QT_VERSION >= 0x050000
+#  include <QTemporaryDir>
+#endif
+
 #include "notificationsclient.h"
 #include "systemtray.h"
 #include "statusnotifierwatcher.h"
@@ -45,20 +50,20 @@ class StatusNotifierItem : public StatusNotifierItemParent
 
 public:
     explicit StatusNotifierItem(QWidget *parent);
-    virtual ~StatusNotifierItem();
+    ~StatusNotifierItem() override;
 
-    virtual bool isSystemTrayAvailable() const;
-    virtual bool isVisible() const;
+    bool isSystemTrayAvailable() const override;
+    bool isVisible() const override;
 
 public slots:
-    virtual void setState(State state);
-    virtual void setVisible(bool visible);
-    virtual void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int msTimeout = 10000, uint notificationId = 0);
-    virtual void closeMessage(uint notificationId);
+    void setState(State state) override;
+    void setVisible(bool visible) override;
+    void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int msTimeout = 10000, uint notificationId = 0) override;
+    void closeMessage(uint notificationId) override;
 
 protected:
-    virtual void init();
-    virtual void setMode(Mode mode);
+    void init() override;
+    void setMode(Mode mode) override;
 
     QString title() const;
     QString iconName() const;
@@ -67,7 +72,7 @@ protected:
     QString iconThemePath() const;
     QString menuObjectPath() const;
 
-    virtual bool eventFilter(QObject *watched, QEvent *event);
+    bool eventFilter(QObject *watched, QEvent *event) override;
 
 private slots:
     void activated(const QPoint &pos);
@@ -77,26 +82,30 @@ private slots:
     void notificationClosed(uint id, uint reason);
     void notificationInvoked(uint id, const QString &action);
 
+    void refreshIcons();
+
 private:
     void registerToDaemon();
 
     static const int _protocolVersion;
     static const QString _statusNotifierWatcherServiceName;
-    StatusNotifierItemDBus *_statusNotifierItemDBus;
+    StatusNotifierItemDBus *_statusNotifierItemDBus{nullptr};
 
-    org::kde::StatusNotifierWatcher *_statusNotifierWatcher;
-    org::freedesktop::Notifications *_notificationsClient;
-    bool _notificationsClientSupportsMarkup;
-    bool _notificationsClientSupportsActions;
-    quint32 _lastNotificationsDBusId;
+    org::kde::StatusNotifierWatcher *_statusNotifierWatcher{nullptr};
+    org::freedesktop::Notifications *_notificationsClient{nullptr};
+    bool _notificationsClientSupportsMarkup{false};
+    bool _notificationsClientSupportsActions{false};
+    quint32 _lastNotificationsDBusId{0};
     QHash<uint, uint> _notificationsIdMap; ///< Maps our own notification ID to the D-Bus one
 
     QString _iconThemePath;
     QString _menuObjectPath;
 
+#if QT_VERSION >= 0x050000
+    QTemporaryDir _iconThemeDir;
+#endif
+
     friend class StatusNotifierItemDBus;
 };
 
-
 #endif /* HAVE_DBUS */
-#endif /* STATUSNOTIFIERITEM_H_ */