From 9f0918fca1d858523104435690e5094bfe6244b7 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Mon, 11 Jun 2018 21:07:37 +0200 Subject: [PATCH] icons: Fix icon theme support for StatusNotifierItem 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 | 5 +++ src/qtui/qtui.h | 3 ++ src/qtui/statusnotifieritem.cpp | 59 +++++++++++++++++++++++++++------ src/qtui/statusnotifieritem.h | 49 ++++++++++++++++----------- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/qtui/qtui.cpp b/src/qtui/qtui.cpp index 0082bd06..2cfc8102 100644 --- a/src/qtui/qtui.cpp +++ b/src/qtui/qtui.cpp @@ -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 } diff --git a/src/qtui/qtui.h b/src/qtui/qtui.h index 0d13e648..e54fb244 100644 --- a/src/qtui/qtui.h +++ b/src/qtui/qtui.h @@ -89,6 +89,9 @@ public slots: */ void refreshIconTheme(); +signals: + void iconThemeRefreshed(); + protected slots: void connectedToCore() override; void disconnectedFromCore() override; diff --git a/src/qtui/statusnotifieritem.cpp b/src/qtui/statusnotifieritem.cpp index df3eb40d..e23b7d80 100644 --- a/src/qtui/statusnotifieritem.cpp +++ b/src/qtui/statusnotifieritem.cpp @@ -24,10 +24,12 @@ #ifdef HAVE_DBUS #include +#include #include #include #include +#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(); qDBusRegisterMetaType(); + 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) diff --git a/src/qtui/statusnotifieritem.h b/src/qtui/statusnotifieritem.h index 534d35f1..9a805be5 100644 --- a/src/qtui/statusnotifieritem.h +++ b/src/qtui/statusnotifieritem.h @@ -21,11 +21,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#ifndef STATUSNOTIFIERITEM_H_ -#define STATUSNOTIFIERITEM_H_ +#pragma once #ifdef HAVE_DBUS +#include + +#if QT_VERSION >= 0x050000 +# include +#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 _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_ */ -- 2.20.1