diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ab54f21 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.moc": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 22c94d4..6e8bfed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(QT Core Widgets Concurrent Quick QuickControls2 X11Extras DBus LinguistTools) +set(QT Core Widgets Concurrent Quick QuickControls2 X11Extras DBus Svg LinguistTools) find_package(Qt5 REQUIRED ${QT}) find_package(KF5WindowSystem REQUIRED) find_package(dbusmenu-qt5 REQUIRED) @@ -36,19 +36,36 @@ set(SRCS src/appearance.cpp src/fakewindow.cpp + src/iconitem.cpp + src/managedtexturenode.cpp - src/statusnotifier/dbustypes.cpp - src/statusnotifier/sniasync.cpp - src/statusnotifier/statusnotifieriteminterface.cpp - src/statusnotifier/statusnotifiermodel.cpp - src/statusnotifier/statusnotifierwatcher.cpp - src/statusnotifier/statusnotifieritemsource.cpp + src/systemtray/statusnotifieritemjob.cpp + src/systemtray/statusnotifieritemsource.cpp + src/systemtray/systemtraytypes.cpp + src/systemtray/systemtraytypedefs.h + src/systemtray/systemtraymodel.cpp + src/systemtray/statusnotifierwatcher.cpp ) set(RESOURCES resources.qrc ) +set(statusnotifierwatcher_xml src/systemtray/org.kde.StatusNotifierWatcher.xml) +qt5_add_dbus_interface(SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface) +qt5_add_dbus_interface(SRCS src/systemtray/org.freedesktop.DBus.Properties.xml dbusproperties) + +set(statusnotifieritem_xml src/systemtray/org.kde.StatusNotifierItem.xml) +set_source_files_properties(${statusnotifieritem_xml} PROPERTIES + NO_NAMESPACE false + INCLUDE "src/systemtray/systemtraytypes.h" + CLASSNAME OrgKdeStatusNotifierItem +) +qt5_add_dbus_interface(SRCS ${statusnotifieritem_xml} statusnotifieritem_interface) + +qt5_add_dbus_adaptor(SRCS src/systemtray/org.kde.StatusNotifierWatcher.xml + src/systemtray/statusnotifierwatcher.h StatusNotifierWatcher) + add_executable(${PROJECT_NAME} ${SRCS} ${DBUS_SRCS} ${RESOURCES}) target_link_libraries(${PROJECT_NAME} Qt5::Core @@ -58,6 +75,7 @@ target_link_libraries(${PROJECT_NAME} Qt5::X11Extras Qt5::Concurrent Qt5::DBus + Qt5::Svg MeuiKit diff --git a/qml/DockItem.qml b/qml/DockItem.qml index 7b4f5a4..35ba04a 100644 --- a/qml/DockItem.qml +++ b/qml/DockItem.qml @@ -50,18 +50,12 @@ Item { dragStarted = false } - Image { + IconItem { id: icon anchors.centerIn: parent - source: iconName ? iconName.indexOf("/") === 0 || iconName.indexOf("file://") === 0 || iconName.indexOf("qrc") === 0 - ? iconName : "image://icontheme/" + iconName : iconName - sourceSize.width: control.iconSize - sourceSize.height: control.iconSize - width: sourceSize.width - height: sourceSize.height - asynchronous: false - smooth: true - cache: true + width: control.iconSize + height: control.iconSize + source: iconName visible: !dragStarted diff --git a/qml/StatusNotifierItem.qml b/qml/StatusNotifierItem.qml new file mode 100644 index 0000000..af62393 --- /dev/null +++ b/qml/StatusNotifierItem.qml @@ -0,0 +1,9 @@ +import QtQuick 2.12 + +Loader { + sourceComponent: _item + + Component { + id: _item + } +} diff --git a/qml/main.qml b/qml/main.qml index 1354bd4..09dac29 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -6,6 +6,7 @@ import QtGraphicalEffects 1.0 import Cyber.NetworkManagement 1.0 as NM import Cutefish.Dock 1.0 import MeuiKit 1.0 as Meui +import org.kde.plasma.core 2.0 as PlasmaCore Item { id: root @@ -71,7 +72,7 @@ Item { Behavior on color { ColorAnimation { - duration: 0 + duration: 200 easing.type: Easing.InOutQuad } } @@ -168,34 +169,22 @@ Item { orientation: isHorizontal ? Qt.Horizontal : Qt.Vertical layoutDirection: Qt.RightToLeft interactive: false - model: trayModel + model: SystemTrayModel { id: trayModel } spacing: Meui.Units.smallSpacing / 2 clip: true - StatusNotifierModel { - id: trayModel - } - onCountChanged: delayCalcIconSize() delegate: StandardItem { height: trayView.itemHeight width: trayView.itemWidth - Image { - id: trayIcon + IconItem { + id: iconItem anchors.centerIn: parent -// source: iconName ? "image://icontheme/" + iconName -// : iconBytes ? "data:image/png;base64," + iconBytes -// : "image://icontheme/application-x-desktop" - - source: iconName ? "image://icontheme/" + iconName : "image://icontheme/application-x-desktop" - width: root.trayItemSize height: root.trayItemSize - sourceSize.width: root.trayItemSize - sourceSize.height: root.trayItemSize - asynchronous: true + source: model.icon ? model.icon : model.iconName } onClicked: trayModel.leftButtonClick(id) diff --git a/resources.qrc b/resources.qrc index 754c47a..4360cf5 100644 --- a/resources.qrc +++ b/resources.qrc @@ -102,5 +102,6 @@ svg/light/dark-mode.svg svg/dark/dark-mode.svg svg/dark/bluetooth-symbolic.svg + qml/StatusNotifierItem.qml diff --git a/src/iconitem.cpp b/src/iconitem.cpp new file mode 100644 index 0000000..e0b2116 --- /dev/null +++ b/src/iconitem.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefish + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "iconitem.h" +#include +#include +#include +#include +#include +#include +#include + +#include "managedtexturenode.h" + +template +typename std::enable_if (), bool>::type almost_equal(T x, T y, int ulp) +{ + return std::abs(x - y) ::epsilon() * std::abs(x + y) * ulp + || std::abs(x - y) ::min(); +} + +class IconItemSource +{ +public: + explicit IconItemSource(IconItem *iconItem) + : m_iconItem(iconItem) + { + } + virtual ~IconItemSource() + { + } + + virtual bool isValid() const = 0; + virtual const QSize size() const = 0; + virtual QPixmap pixmap(const QSize &size) = 0; + +protected: + QQuickWindow *window() { + return m_iconItem->window(); + } + + IconItem *m_iconItem; +}; + +class NullSource : public IconItemSource +{ +public: + explicit NullSource(IconItem *iconItem) + : IconItemSource(iconItem) + { + } + + bool isValid() const override + { + return false; + } + + const QSize size() const override + { + return QSize(); + } + + QPixmap pixmap(const QSize &size) override + { + Q_UNUSED(size) + return QPixmap(); + } +}; + +class QIconSource : public IconItemSource +{ +public: + explicit QIconSource(const QIcon &icon, IconItem *iconItem) + : IconItemSource(iconItem) + { + m_icon = icon; + } + + bool isValid() const override + { + return !m_icon.isNull(); + } + + const QSize size() const override + { + return QSize(); + } + + QPixmap pixmap(const QSize &size) override + { + QPixmap result = m_icon.pixmap(window(), m_icon.actualSize(size)); + return result; + } + +private: + QIcon m_icon; +}; + +class QImageSource : public IconItemSource +{ +public: + explicit QImageSource(const QImage &imageIcon, IconItem *iconItem) + : IconItemSource(iconItem) + { + m_imageIcon = imageIcon; + } + + bool isValid() const override + { + return !m_imageIcon.isNull(); + } + + const QSize size() const override + { + const QSize s = m_imageIcon.size(); + if (s.isValid()) { + return s; + } + + return QSize(); + } + + QPixmap pixmap(const QSize &size) override + { + Q_UNUSED(size) + return QPixmap::fromImage(m_imageIcon); + } + +private: + QImage m_imageIcon; +}; + +class SvgSource : public IconItemSource +{ +public: + explicit SvgSource(const QString &sourceString, IconItem *iconItem) + : IconItemSource(iconItem) + { + + } + + const QSize size() const override { + return size(); + } + + QPixmap pixmap(const QSize &size) override { + Q_UNUSED(size); + return QPixmap(); + } + +private: + qreal devicePixelRatio() { + return window() ? window()->devicePixelRatio() : qApp->devicePixelRatio(); + } + + QString m_svgIconName; +}; + +IconItem::IconItem(QQuickItem *parent) + : QQuickItem(parent) + , m_iconItemSource(new NullSource(this)) + , m_active(false) + , m_animated(false) + , m_usesPlasmaTheme(false) + , m_roundToIconSize(true) + , m_textureChanged(false) + , m_sizeChanged(false) +{ + setFlag(ItemHasContents, true); + setSmooth(true); +} + +void IconItem::setSource(const QVariant &source) +{ + if (source == m_source) { + return; + } + + const bool oldValid = isValid(); + + m_source = source; + QString sourceString = source.toString(); + + // If the QIcon was created with QIcon::fromTheme(), try to load it as svg + if (source.canConvert() && !source.value().name().isEmpty()) { + sourceString = source.value().name(); + } + + if (!sourceString.isEmpty()) { + // If a file:// URL or a absolute path is passed, take the image pointed by that from disk + QString localFile; + if (sourceString.startsWith(QLatin1String("file:"))) { + localFile = QUrl(sourceString).toLocalFile(); + } else if (sourceString.startsWith(QLatin1Char('/'))) { + localFile = sourceString; + } else if (sourceString.startsWith("qrc:/")) { + localFile = sourceString.remove(0, 3); + } else if (sourceString.startsWith(":/")) { + localFile = sourceString; + } + + if (!localFile.isEmpty()) { + if (sourceString.endsWith(QLatin1String(".svg")) + || sourceString.endsWith(QLatin1String(".svgz")) + || sourceString.endsWith(QLatin1String(".ico"))) { + QIcon icon = QIcon(localFile); + m_iconItemSource.reset(new QIconSource(icon, this)); + } else { + QImage imageIcon = QImage(localFile); + m_iconItemSource.reset(new QImageSource(imageIcon, this)); + } + } else { + // m_iconItemSource.reset(new SvgSource(sourceString, this)); + + if (!m_iconItemSource->isValid()) { + // if we started with a QIcon use that. + QIcon icon = source.value(); + if (icon.isNull()) { + icon = QIcon::fromTheme(sourceString, QIcon::fromTheme("application-x-desktop")); + } + m_iconItemSource.reset(new QIconSource(icon, this)); + } + } + + } else if (source.canConvert()) { + m_iconItemSource.reset(new QIconSource(source.value(), this)); + } else if (source.canConvert()) { + m_iconItemSource.reset(new QImageSource(source.value(), this)); + } else { + m_iconItemSource.reset(new NullSource(this)); + } + + if (width() > 0 && height() > 0) { + schedulePixmapUpdate(); + } + + updateImplicitSize(); + + emit sourceChanged(); + + if (isValid() != oldValid) { + Q_EMIT validChanged(); + } +} + +QVariant IconItem::source() const +{ + return m_source; +} + +void IconItem::updateImplicitSize() +{ + if (m_iconItemSource->isValid()) { + const QSize s = m_iconItemSource->size(); + + if (s.isValid()) { + if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) { + setImplicitSize(s.width(), s.height()); + } else if (!m_implicitWidthSetByUser) { + setImplicitWidth(s.width()); + } else if (!m_implicitHeightSetByUser) { + setImplicitHeight(s.height()); + } + + return; + } + } + + // Fall back to initializing implicit size to the Dialog size. + const int implicitSize = 16; + + if (!m_implicitWidthSetByUser && !m_implicitHeightSetByUser) { + setImplicitSize(implicitSize, implicitSize); + } else if (!m_implicitWidthSetByUser) { + setImplicitWidth(implicitSize); + } else if (!m_implicitHeightSetByUser) { + setImplicitHeight(implicitSize); + } +} + +bool IconItem::isValid() const +{ + return m_iconItemSource->isValid(); +} + +int IconItem::paintedWidth() const +{ + return boundingRect().size().toSize().width(); +} + +int IconItem::paintedHeight() const +{ + return boundingRect().size().toSize().height(); +} + +void IconItem::updatePolish() +{ + QQuickItem::updatePolish(); + loadPixmap(); +} + +QSGNode *IconItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData) +{ + Q_UNUSED(updatePaintNodeData) + + if (m_iconPixmap.isNull() || width() == 0.0 || height() == 0.0) { + delete oldNode; + return nullptr; + } + + ManagedTextureNode *textureNode = dynamic_cast(oldNode); + + if (!textureNode || m_textureChanged) { + delete oldNode; + textureNode = new ManagedTextureNode; + textureNode->setTexture(QSharedPointer(window()->createTextureFromImage(m_iconPixmap.toImage(), QQuickWindow::TextureCanUseAtlas))); + m_sizeChanged = true; + m_textureChanged = false; + } + textureNode->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); + + if (m_sizeChanged) { + const QSize newSize = QSize(paintedWidth(), paintedHeight()); + const QRect destRect(QPointF(boundingRect().center() - QPointF(newSize.width(), newSize.height()) / 2).toPoint(), newSize); + textureNode->setRect(destRect); + m_sizeChanged = false; + } + return textureNode; +} + +void IconItem::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) +{ + QQuickItem::itemChange(change, value); +} + +void IconItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + if (newGeometry.size() != oldGeometry.size()) { + m_sizeChanged = true; + + if (newGeometry.width() > 1 && newGeometry.height() > 1) { + schedulePixmapUpdate(); + } else { + update(); + } + + const auto oldSize = qMin(oldGeometry.size().width(), oldGeometry.size().height()); + const auto newSize = qMin(newGeometry.size().width(), newGeometry.size().height()); + + if (!almost_equal(oldSize, newSize, 2)) { + emit paintedSizeChanged(); + } + } + + QQuickItem::geometryChanged(newGeometry, oldGeometry); +} + +void IconItem::componentComplete() +{ + QQuickItem::componentComplete(); + schedulePixmapUpdate(); +} + +void IconItem::schedulePixmapUpdate() +{ + polish(); +} + +void IconItem::loadPixmap() +{ + if (!isComponentComplete()) { + return; + } + + const qreal devicePixelRatio = window() ? window()->devicePixelRatio() : qApp->devicePixelRatio(); + int size = qMin(qRound(width()), qRound(height())); + QPixmap result; + + if (size <= 0) { + m_iconPixmap = QPixmap(); + update(); + return; + } + + if (m_iconItemSource->isValid()) { + result = m_iconItemSource->pixmap(QSize(size * devicePixelRatio, size * devicePixelRatio)); + } else { + m_iconPixmap = QPixmap(); + update(); + return; + } + + m_oldIconPixmap = m_iconPixmap; + m_iconPixmap = result; + m_textureChanged = true; + + update(); +} diff --git a/src/iconitem.h b/src/iconitem.h new file mode 100644 index 0000000..89a2cd3 --- /dev/null +++ b/src/iconitem.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefish + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ICONITEM_H +#define ICONITEM_H + +#include +#include +#include +#include + +#include + +class IconItemSource; +class IconItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) + Q_PROPERTY(int paintedWidth READ paintedWidth NOTIFY paintedSizeChanged) + Q_PROPERTY(int paintedHeight READ paintedHeight NOTIFY paintedSizeChanged) + +public: + explicit IconItem(QQuickItem *parent = nullptr); + + void setSource(const QVariant &source); + QVariant source() const; + + void updateImplicitSize(); + + bool isValid() const; + + int paintedWidth() const; + int paintedHeight() const; + + void updatePolish() override; + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; + + void itemChange(ItemChange change, const ItemChangeData &value) override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + void componentComplete() override; + +signals: + void overlaysChanged(); + void activeChanged(); + void sourceChanged(); + void animatedChanged(); + void usesPlasmaThemeChanged(); + void roundToIconSizeChanged(); + void validChanged(); + void colorGroupChanged(); + void paintedSizeChanged(); + void statusChanged(); + void implicitHeightChanged2(); + void implicitWidthChanged2(); + +private slots: + void schedulePixmapUpdate(); + +private: + void loadPixmap(); + +private: + QSharedPointer m_iconItemSource; + QVariant m_source; + + bool m_active; + bool m_animated; + bool m_usesPlasmaTheme; + bool m_roundToIconSize; + + bool m_textureChanged; + bool m_sizeChanged; + bool m_allowNextAnimation; + bool m_blockNextAnimation; + bool m_implicitHeightSetByUser; + bool m_implicitWidthSetByUser; + + QPixmap m_iconPixmap; + QPixmap m_oldIconPixmap; +}; + +#endif // ICONITEM_H diff --git a/src/main.cpp b/src/main.cpp index 3d35b51..63e9c1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,8 +30,9 @@ #include "battery.h" #include "brightness.h" #include "controlcenterdialog.h" -#include "statusnotifier/statusnotifiermodel.h" +#include "systemtray/systemtraymodel.h" #include "appearance.h" +#include "iconitem.h" int main(int argc, char *argv[]) { @@ -43,8 +44,9 @@ int main(int argc, char *argv[]) qmlRegisterType("Cutefish.Dock", 1, 0, "Battery"); qmlRegisterType("Cutefish.Dock", 1, 0, "Brightness"); qmlRegisterType("Cutefish.Dock", 1, 0, "ControlCenterDialog"); - qmlRegisterType("Cutefish.Dock", 1, 0, "StatusNotifierModel"); + qmlRegisterType("Cutefish.Dock", 1, 0, "SystemTrayModel"); qmlRegisterType("Cutefish.Dock", 1, 0, "Appearance"); + qmlRegisterType("Cutefish.Dock", 1, 0, "IconItem"); QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-dock/translations/").arg(QLocale::system().name()); if (QFile::exists(qmFilePath)) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f48dffb..9a7b459 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -51,10 +51,11 @@ MainWindow::MainWindow(QQuickView *parent) engine()->rootContext()->setContextProperty("Settings", m_settings); engine()->rootContext()->setContextProperty("mainWindow", this); + setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); setResizeMode(QQuickView::SizeRootObjectToView); setScreen(qApp->primaryScreen()); - setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); setVisible(true); + initSlideWindow(); resizeWindow(); onVisibilityChanged(); diff --git a/src/mainwindow.h b/src/mainwindow.h index e929f56..89021b9 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -40,9 +40,6 @@ signals: void iconSizeChanged(); void positionChanged(); -protected: - void event(QEvent *e, QObject *obj); - private: QRect windowRect() const; void resizeWindow(); diff --git a/src/managedtexturenode.cpp b/src/managedtexturenode.cpp new file mode 100644 index 0000000..90d9efb --- /dev/null +++ b/src/managedtexturenode.cpp @@ -0,0 +1,16 @@ +/* + SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include "managedtexturenode.h" + +ManagedTextureNode::ManagedTextureNode() +{ +} + +void ManagedTextureNode::setTexture(QSharedPointer texture) +{ + m_texture = texture; + QSGSimpleTextureNode::setTexture(texture.data()); +} diff --git a/src/managedtexturenode.h b/src/managedtexturenode.h new file mode 100644 index 0000000..8adfb82 --- /dev/null +++ b/src/managedtexturenode.h @@ -0,0 +1,43 @@ +/* + SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#ifndef MANAGEDTEXTURENODE_H +#define MANAGEDTEXTURENODE_H + +#include +#include +#include +#include + +/** + * @class ManagedTextureNode managedtexturenode.h KQuickAddons/ManagedTextureNode + * + * @short Node that contains a reference counted texture + * + * Usually when assigning textures within a node, we'll want to delete the + * texture with the node. This class will take a shared texture and display it + * within the node. + * + * It's especially interesting to use this class together with the ImageTexturesCache + * that will offer us shareable textures and cache them transparently, when asking + * it to create the texture. + * + * @see ImageTexturesCache + */ + +class ManagedTextureNode : public QSGSimpleTextureNode +{ + Q_DISABLE_COPY(ManagedTextureNode) + +public: + ManagedTextureNode(); + + void setTexture(QSharedPointer texture); + +private: + QSharedPointer m_texture; +}; + +#endif diff --git a/src/statusnotifier/dbustypes.cpp b/src/statusnotifier/dbustypes.cpp deleted file mode 100644 index a1df260..0000000 --- a/src/statusnotifier/dbustypes.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "dbustypes.h" - -// Marshall the IconPixmap data into a D-Bus argument -QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon) -{ - argument.beginStructure(); - argument << icon.width; - argument << icon.height; - argument << icon.bytes; - argument.endStructure(); - return argument; -} - -// Retrieve the ImageStruct data from the D-Bus argument -const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon) -{ - argument.beginStructure(); - argument >> icon.width; - argument >> icon.height; - argument >> icon.bytes; - argument.endStructure(); - return argument; -} - -// Marshall the ToolTip data into a D-Bus argument -QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip) -{ - argument.beginStructure(); - argument << toolTip.iconName; - argument << toolTip.iconPixmap; - argument << toolTip.title; - argument << toolTip.description; - argument.endStructure(); - return argument; -} - -// Retrieve the ToolTip data from the D-Bus argument -const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip) -{ - argument.beginStructure(); - argument >> toolTip.iconName; - argument >> toolTip.iconPixmap; - argument >> toolTip.title; - argument >> toolTip.description; - argument.endStructure(); - return argument; -} diff --git a/src/statusnotifier/dbustypes.h b/src/statusnotifier/dbustypes.h deleted file mode 100644 index 2a5809f..0000000 --- a/src/statusnotifier/dbustypes.h +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#ifndef DBUSTYPES_H -#define DBUSTYPES_H - -struct IconPixmap { - int width; - int height; - QByteArray bytes; -}; - -typedef QList IconPixmapList; - -struct ToolTip { - QString iconName; - QList iconPixmap; - QString title; - QString description; -}; - -QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon); -const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon); - -QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip); -const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip); - -Q_DECLARE_METATYPE(IconPixmap) -Q_DECLARE_METATYPE(ToolTip) - -#endif // DBUSTYPES_H diff --git a/src/statusnotifier/sniasync.cpp b/src/statusnotifier/sniasync.cpp deleted file mode 100644 index 92691eb..0000000 --- a/src/statusnotifier/sniasync.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "sniasync.h" - -SniAsync::SniAsync(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent/* = 0*/) - : QObject(parent) - , m_sni{service, path, connection} -{ - //forward StatusNotifierItem signals - connect(&m_sni, &org::kde::StatusNotifierItem::NewAttentionIcon, this, &SniAsync::NewAttentionIcon); - connect(&m_sni, &org::kde::StatusNotifierItem::NewIcon, this, &SniAsync::NewIcon); - connect(&m_sni, &org::kde::StatusNotifierItem::NewOverlayIcon, this, &SniAsync::NewOverlayIcon); - connect(&m_sni, &org::kde::StatusNotifierItem::NewStatus, this, &SniAsync::NewStatus); - connect(&m_sni, &org::kde::StatusNotifierItem::NewTitle, this, &SniAsync::NewTitle); - connect(&m_sni, &org::kde::StatusNotifierItem::NewToolTip, this, &SniAsync::NewToolTip); -} - -QDBusPendingReply SniAsync::asyncPropGet(QString const & property) -{ - QDBusMessage msg = QDBusMessage::createMethodCall(m_sni.service(), m_sni.path(), QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get")); - msg << m_sni.interface() << property; - return m_sni.connection().asyncCall(msg); -} diff --git a/src/statusnotifier/sniasync.h b/src/statusnotifier/sniasync.h deleted file mode 100644 index 848d7da..0000000 --- a/src/statusnotifier/sniasync.h +++ /dev/null @@ -1,89 +0,0 @@ -#if !defined(SNIASYNC_H) -#define SNIASYNC_H - -#include -#include "statusnotifieriteminterface.h" - -template -struct remove_class_type { using type = void; }; // bluff -template -struct remove_class_type { using type = R(ArgTypes...); }; -template -struct remove_class_type { using type = R(ArgTypes...); }; - -template -class call_sig_helper -{ - template - static decltype(&L1::operator()) test(int); - template - static void test(...); //bluff -public: - using type = decltype(test(0)); -}; -template -struct call_signature : public remove_class_type::type> {}; -template -struct call_signature { using type = R (ArgTypes...); }; -template -struct call_signature { using type = R (ArgTypes...); }; -template -struct call_signature { using type = R (ArgTypes...); }; -template -struct call_signature { using type = R(ArgTypes...); }; - -template struct is_valid_signature : public std::false_type {}; -template -struct is_valid_signature : public std::true_type {}; - -class SniAsync : public QObject -{ - Q_OBJECT -public: - SniAsync(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); - - template - inline void propertyGetAsync(QString const &name, F finished) - { - static_assert(is_valid_signature::type>::value, "need callable (lambda, *function, callable obj) (Arg) -> void"); - connect(new QDBusPendingCallWatcher{asyncPropGet(name), this}, - &QDBusPendingCallWatcher::finished, - [this, finished, name] (QDBusPendingCallWatcher * call) - { - QDBusPendingReply reply = *call; - if (reply.isError()) - qDebug().noquote().nospace() << "Error on DBus request(" << m_sni.service() << ',' << m_sni.path() << "): " << reply.error(); - finished(qdbus_cast::type>::argument_type>(reply.value())); - call->deleteLater(); - } - ); - } - - //exposed methods from org::kde::StatusNotifierItem - inline QString service() const { return m_sni.service(); } - -public slots: - //Forwarded slots from org::kde::StatusNotifierItem - inline QDBusPendingReply<> Activate(int x, int y) { return m_sni.Activate(x, y); } - inline QDBusPendingReply<> ContextMenu(int x, int y) { return m_sni.ContextMenu(x, y); } - inline QDBusPendingReply<> Scroll(int delta, const QString &orientation) { return m_sni.Scroll(delta, orientation); } - inline QDBusPendingReply<> SecondaryActivate(int x, int y) { return m_sni.SecondaryActivate(x, y); } - -signals: - //Forwarded signals from org::kde::StatusNotifierItem - void NewAttentionIcon(); - void NewIcon(); - void NewOverlayIcon(); - void NewStatus(const QString &status); - void NewTitle(); - void NewToolTip(); - -private: - QDBusPendingReply asyncPropGet(QString const & property); - -private: - org::kde::StatusNotifierItem m_sni; - -}; - -#endif diff --git a/src/statusnotifier/statusnotifieriteminterface.cpp b/src/statusnotifier/statusnotifieriteminterface.cpp deleted file mode 100644 index 65a18a9..0000000 --- a/src/statusnotifier/statusnotifieriteminterface.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "statusnotifieriteminterface.h" - -/* - * Implementation of interface class StatusNotifierItemInterface - */ - -StatusNotifierItemInterface::StatusNotifierItemInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) - : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) -{ -} - -StatusNotifierItemInterface::~StatusNotifierItemInterface() -{ -} diff --git a/src/statusnotifier/statusnotifieriteminterface.h b/src/statusnotifier/statusnotifieriteminterface.h deleted file mode 100644 index baaa3cb..0000000 --- a/src/statusnotifier/statusnotifieriteminterface.h +++ /dev/null @@ -1,136 +0,0 @@ -#ifndef STATUSNOTIFIERITEMINTERFACE_H -#define STATUSNOTIFIERITEMINTERFACE_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dbustypes.h" - -/* - * Proxy class for interface org.kde.StatusNotifierItem - */ -class StatusNotifierItemInterface: public QDBusAbstractInterface -{ - Q_OBJECT -public: - static inline const char *staticInterfaceName() - { return "org.kde.StatusNotifierItem"; } - -public: - StatusNotifierItemInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr); - - ~StatusNotifierItemInterface(); - - Q_PROPERTY(QString AttentionIconName READ attentionIconName) - inline QString attentionIconName() const - { return qvariant_cast< QString >(property("AttentionIconName")); } - - Q_PROPERTY(IconPixmapList AttentionIconPixmap READ attentionIconPixmap) - inline IconPixmapList attentionIconPixmap() const - { return qvariant_cast< IconPixmapList >(property("AttentionIconPixmap")); } - - Q_PROPERTY(QString AttentionMovieName READ attentionMovieName) - inline QString attentionMovieName() const - { return qvariant_cast< QString >(property("AttentionMovieName")); } - - Q_PROPERTY(QString Category READ category) - inline QString category() const - { return qvariant_cast< QString >(property("Category")); } - - Q_PROPERTY(QString IconName READ iconName) - inline QString iconName() const - { return qvariant_cast< QString >(property("IconName")); } - - Q_PROPERTY(IconPixmapList IconPixmap READ iconPixmap) - inline IconPixmapList iconPixmap() const - { return qvariant_cast< IconPixmapList >(property("IconPixmap")); } - - Q_PROPERTY(QString IconThemePath READ iconThemePath) - inline QString iconThemePath() const - { return qvariant_cast< QString >(property("IconThemePath")); } - - Q_PROPERTY(QString Id READ id) - inline QString id() const - { return qvariant_cast< QString >(property("Id")); } - - Q_PROPERTY(bool ItemIsMenu READ itemIsMenu) - inline bool itemIsMenu() const - { return qvariant_cast< bool >(property("ItemIsMenu")); } - - Q_PROPERTY(QDBusObjectPath Menu READ menu) - inline QDBusObjectPath menu() const - { return qvariant_cast< QDBusObjectPath >(property("Menu")); } - - Q_PROPERTY(QString OverlayIconName READ overlayIconName) - inline QString overlayIconName() const - { return qvariant_cast< QString >(property("OverlayIconName")); } - - Q_PROPERTY(IconPixmapList OverlayIconPixmap READ overlayIconPixmap) - inline IconPixmapList overlayIconPixmap() const - { return qvariant_cast< IconPixmapList >(property("OverlayIconPixmap")); } - - Q_PROPERTY(QString Status READ status) - inline QString status() const - { return qvariant_cast< QString >(property("Status")); } - - Q_PROPERTY(QString Title READ title) - inline QString title() const - { return qvariant_cast< QString >(property("Title")); } - - Q_PROPERTY(ToolTip ToolTip READ toolTip) - inline ToolTip toolTip() const - { return qvariant_cast< ToolTip >(property("ToolTip")); } - - Q_PROPERTY(int WindowId READ windowId) - inline int windowId() const - { return qvariant_cast< int >(property("WindowId")); } - -public Q_SLOTS: // METHODS - inline QDBusPendingReply<> Activate(int x, int y) - { - QList argumentList; - argumentList << QVariant::fromValue(x) << QVariant::fromValue(y); - return asyncCallWithArgumentList(QLatin1String("Activate"), argumentList); - } - - inline QDBusPendingReply<> ContextMenu(int x, int y) - { - QList argumentList; - argumentList << QVariant::fromValue(x) << QVariant::fromValue(y); - return asyncCallWithArgumentList(QLatin1String("ContextMenu"), argumentList); - } - - inline QDBusPendingReply<> Scroll(int delta, const QString &orientation) - { - QList argumentList; - argumentList << QVariant::fromValue(delta) << QVariant::fromValue(orientation); - return asyncCallWithArgumentList(QLatin1String("Scroll"), argumentList); - } - - inline QDBusPendingReply<> SecondaryActivate(int x, int y) - { - QList argumentList; - argumentList << QVariant::fromValue(x) << QVariant::fromValue(y); - return asyncCallWithArgumentList(QLatin1String("SecondaryActivate"), argumentList); - } - -Q_SIGNALS: // SIGNALS - void NewAttentionIcon(); - void NewIcon(); - void NewOverlayIcon(); - void NewStatus(const QString &status); - void NewTitle(); - void NewToolTip(); -}; - -namespace org { - namespace kde { - typedef ::StatusNotifierItemInterface StatusNotifierItem; - } -} -#endif diff --git a/src/statusnotifier/statusnotifieritemsource.cpp b/src/statusnotifier/statusnotifieritemsource.cpp deleted file mode 100644 index 5fe7eba..0000000 --- a/src/statusnotifier/statusnotifieritemsource.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "statusnotifieritemsource.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class MenuImporter : public DBusMenuImporter -{ -public: - using DBusMenuImporter::DBusMenuImporter; - -protected: - virtual QIcon iconForName(const QString & name) override { - return QIcon::fromTheme(name); - } -}; - -StatusNotifierItemSource::StatusNotifierItemSource(const QString &id, QObject *parent) - : QObject(parent) - , m_id(id) - , m_refreshing(false) - , m_needsReRefreshing(false) - , m_menuImporter(nullptr) -{ - int slash = id.indexOf('/'); - if (slash == -1) { - qWarning() << "Invalid notifierItemId:" << id; - m_valid = false; - m_statusNotifierItemInterface = nullptr; - return; - } - - QString service = id.left(slash); - QString path = id.mid(slash); - - m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, - QDBusConnection::sessionBus(), this); - m_refreshTimer.setSingleShot(true); - m_refreshTimer.setInterval(10); - connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh); - - m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid(); - if (m_valid) { - connect(m_statusNotifierItemInterface, &StatusNotifierItemInterface::NewTitle, this, &StatusNotifierItemSource::refreshTitle); - connect(m_statusNotifierItemInterface, &StatusNotifierItemInterface::NewIcon, this, &StatusNotifierItemSource::refreshIcons); - connect(m_statusNotifierItemInterface, &StatusNotifierItemInterface::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); - connect(m_statusNotifierItemInterface, &StatusNotifierItemInterface::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); - connect(m_statusNotifierItemInterface, &StatusNotifierItemInterface::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); - refresh(); - } -} - -StatusNotifierItemSource::~StatusNotifierItemSource() -{ - delete m_statusNotifierItemInterface; -} - -void StatusNotifierItemSource::activate(int x, int y) -{ - if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { - m_statusNotifierItemInterface->Activate(x, y); - } -} - -void StatusNotifierItemSource::secondaryActivate(int x, int y) -{ - if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { - m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y); - } -} - -void StatusNotifierItemSource::contextMenu(int x, int y) -{ - if (m_menuImporter) { - m_menuImporter->updateMenu(); - - // Popup menu - if (m_menuImporter->menu()) { - m_menuImporter->menu()->popup(QPoint(x, y)); - } - } else { - qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; - if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { - m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y); - } - } -} - -void StatusNotifierItemSource::refresh() -{ - if (!m_refreshTimer.isActive()) { - m_refreshTimer.start(); - } -} - -void StatusNotifierItemSource::refreshTitle() -{ - refresh(); -} - -void StatusNotifierItemSource::refreshToolTip() -{ - refresh(); -} - -void StatusNotifierItemSource::refreshIcons() -{ - refresh(); -} - -void StatusNotifierItemSource::performRefresh() -{ - if (m_refreshing) { - m_needsReRefreshing = true; - return; - } - - m_refreshing = true; - QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), - m_statusNotifierItemInterface->path(), - QStringLiteral("org.freedesktop.DBus.Properties"), - QStringLiteral("GetAll")); - - message << m_statusNotifierItemInterface->interface(); - QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); - connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback); -} - -void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) -{ - m_refreshing = false; - if (m_needsReRefreshing) { - m_needsReRefreshing = false; - performRefresh(); - call->deleteLater(); - return; - } - - QDBusPendingReply reply = *call; - if (reply.isError()) { - m_valid = false; - } else { - QVariantMap properties = reply.argumentAt<0>(); - QString path = properties[QStringLiteral("IconThemePath")].toString(); - - m_title = properties[QStringLiteral("Title")].toString(); - m_iconName = properties[QStringLiteral("IconName")].toString(); - - // ToolTip - ToolTip toolTip; - properties[QStringLiteral("ToolTip")].value() >> toolTip; - m_tooltip = toolTip.title; - - // Menu - QString menuObjectPath = properties[QStringLiteral("Menu")].value().path(); - if (!menuObjectPath.isEmpty()) { - if (menuObjectPath.startsWith(QLatin1String("/NO_DBUSMENU"))) { - qWarning() << "DBusMenu disabled for this application"; - } else { - m_menuImporter = new MenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, this); - } - } - - // Icon - IconPixmapList iconPixmaps; - properties[QStringLiteral("IconPixmap")].value() >> iconPixmaps; - - QImage image = IconPixmapListToImage(iconPixmaps); - if (!image.isNull()) { - QByteArray byteArray; - QBuffer buffer(&byteArray); - image.save(&buffer, "PNG"); - m_iconBytes = byteArray.toBase64(); - } - - emit updated(this); - } - - call->deleteLater(); -} - -QImage StatusNotifierItemSource::IconPixmapListToImage(const IconPixmapList &list) const -{ - QIcon icon; - - for (IconPixmap iconPixmap: list) { - if (!iconPixmap.bytes.isNull()) { - QImage image((uchar*) iconPixmap.bytes.data(), iconPixmap.width, - iconPixmap.height, QImage::Format_ARGB32); - - const uchar *end = image.constBits() + image.sizeInBytes(); - uchar *dest = reinterpret_cast(iconPixmap.bytes.data()); - for (const uchar *src = image.constBits(); src < end; src += 4, dest += 4) - qToUnaligned(qToBigEndian(qFromUnaligned(src)), dest); - - icon.addPixmap(QPixmap::fromImage(image)); - } - } - - return icon.pixmap(QSize(24, 24)).toImage(); -} diff --git a/src/statusnotifier/statusnotifieritemsource.h b/src/statusnotifier/statusnotifieritemsource.h deleted file mode 100644 index a5cdf20..0000000 --- a/src/statusnotifier/statusnotifieritemsource.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef STATUSNOTIFIERITEMSOURCE_H -#define STATUSNOTIFIERITEMSOURCE_H - -#include -#include - -#include "statusnotifieriteminterface.h" - -class MenuImporter; -class StatusNotifierItemSource : public QObject -{ - Q_OBJECT - -public: - explicit StatusNotifierItemSource(const QString &id, QObject *parent = nullptr); - ~StatusNotifierItemSource(); - - QString id() const { return m_id; } - QString title() const { return m_title; } - QString tooltip() const { return m_tooltip; } - QString iconName() const { return m_iconName; } - QString iconBytes() const { return m_iconBytes; } - - void activate(int x, int y); - void secondaryActivate(int x, int y); - void contextMenu(int x, int y); - - MenuImporter *menuImporter() { return m_menuImporter; } - -signals: - void updated(StatusNotifierItemSource *); - -private slots: - void refresh(); - void refreshTitle(); - void refreshToolTip(); - void refreshIcons(); - void performRefresh(); - void refreshCallback(QDBusPendingCallWatcher *); - QImage IconPixmapListToImage(const IconPixmapList &list) const; - -private: - QString m_id; - QString m_title; - QString m_tooltip; - QString m_iconName; - QString m_iconBytes; - - bool m_valid; - bool m_refreshing; - bool m_needsReRefreshing; - StatusNotifierItemInterface *m_statusNotifierItemInterface; - MenuImporter *m_menuImporter; - QTimer m_refreshTimer; -}; - -#endif // STATUSNOTIFIERITEMSOURCE_H diff --git a/src/statusnotifier/statusnotifiermodel.cpp b/src/statusnotifier/statusnotifiermodel.cpp deleted file mode 100644 index fe84b99..0000000 --- a/src/statusnotifier/statusnotifiermodel.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "statusnotifiermodel.h" -#include -#include -#include -#include - -#include -#include -#include -#include - -StatusNotifierModel::StatusNotifierModel(QObject *parent) - : QAbstractListModel(parent) - , m_watcher(nullptr) -{ - QFutureWatcher * futureWatcher = new QFutureWatcher; - connect(futureWatcher, &QFutureWatcher::finished, this, [this, futureWatcher] { - m_watcher = futureWatcher->future().result(); - - connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemRegistered, - this, &StatusNotifierModel::itemAdded); - connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemUnregistered, - this, &StatusNotifierModel::itemRemoved); - - qDebug() << m_watcher->RegisteredStatusNotifierItems(); - - futureWatcher->deleteLater(); - }); - - QFuture future = QtConcurrent::run([=] { - QString dbusName = QStringLiteral("org.kde.StatusNotifierHost-%1-%2").arg(QApplication::applicationPid()).arg(1); - if (QDBusConnectionInterface::ServiceNotRegistered == QDBusConnection::sessionBus().interface()->registerService(dbusName, QDBusConnectionInterface::DontQueueService)) - qDebug() << "unable to register service for " << dbusName; - - StatusNotifierWatcher * watcher = new StatusNotifierWatcher; - watcher->RegisterStatusNotifierHost(dbusName); - watcher->moveToThread(QApplication::instance()->thread()); - return watcher; - }); - - futureWatcher->setFuture(future); -} - -StatusNotifierModel::~StatusNotifierModel() -{ - delete m_watcher; -} - -int StatusNotifierModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - - return m_items.size(); -} - -QHash StatusNotifierModel::roleNames() const -{ - QHash roles; - roles[IdRole] = "id"; - roles[IconNameRole] = "iconName"; - roles[IconBytesRole] = "iconBytes"; - roles[TitleRole] = "title"; - roles[ToolTipRole] = "toolTip"; - return roles; -} - -QVariant StatusNotifierModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - StatusNotifierItemSource *item = m_items.at(index.row()); - - switch (role) { - case IdRole: - return item->id(); - case IconNameRole: - return item->iconName(); - case IconBytesRole: - return item->iconBytes(); - case TitleRole: - return item->title(); - case ToolTipRole: - return item->tooltip(); - } - - return QVariant(); -} - -int StatusNotifierModel::indexOf(const QString &id) -{ - for (StatusNotifierItemSource *item : m_items) { - if (item->id() == id) - return m_items.indexOf(item); - } - - return -1; -} - -StatusNotifierItemSource *StatusNotifierModel::findItemById(const QString &id) -{ - int index = indexOf(id); - - if (index == -1) - return nullptr; - - return m_items.at(index); -} - -void StatusNotifierModel::leftButtonClick(const QString &id) -{ - StatusNotifierItemSource *item = findItemById(id); - if (item) { - QPoint p(QCursor::pos()); - item->activate(p.x(), p.y()); - } -} - -void StatusNotifierModel::rightButtonClick(const QString &id) -{ - StatusNotifierItemSource *item = findItemById(id); - if (item) { - QPoint p(QCursor::pos()); - item->contextMenu(p.x(), p.y()); - } -} - -void StatusNotifierModel::itemAdded(QString serviceAndPath) -{ - StatusNotifierItemSource *source = new StatusNotifierItemSource(serviceAndPath, this); - - connect(source, &StatusNotifierItemSource::updated, this, &StatusNotifierModel::updated); - - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_items.append(source); - endInsertRows(); -} - -void StatusNotifierModel::itemRemoved(const QString &serviceAndPath) -{ - int index = indexOf(serviceAndPath); - - if (index != -1) { - beginRemoveRows(QModelIndex(), index, index); - StatusNotifierItemSource *item = m_items.at(index); - m_items.removeAll(item); - endRemoveRows(); - } -} - -void StatusNotifierModel::updated(StatusNotifierItemSource *item) -{ - if (!item) - return; - - int idx = indexOf(item->id()); - - // update - if (idx != -1) { - dataChanged(index(idx, 0), index(idx, 0)); - } -} diff --git a/src/statusnotifier/statusnotifierwatcher.cpp b/src/statusnotifier/statusnotifierwatcher.cpp deleted file mode 100644 index d8b5189..0000000 --- a/src/statusnotifier/statusnotifierwatcher.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "statusnotifierwatcher.h" -#include -#include - -StatusNotifierWatcher::StatusNotifierWatcher(QObject *parent) : QObject(parent) -{ - qRegisterMetaType("IconPixmap"); - qDBusRegisterMetaType(); - qRegisterMetaType("IconPixmapList"); - qDBusRegisterMetaType(); - qRegisterMetaType("ToolTip"); - qDBusRegisterMetaType(); - - QDBusConnection dbus = QDBusConnection::sessionBus(); - switch (dbus.interface()->registerService(QStringLiteral("org.kde.StatusNotifierWatcher"), QDBusConnectionInterface::QueueService).value()) - { - case QDBusConnectionInterface::ServiceNotRegistered: - qWarning() << "StatusNotifier: unable to register service for org.kde.StatusNotifierWatcher"; - break; - case QDBusConnectionInterface::ServiceQueued: - qWarning() << "StatusNotifier: registration of service org.kde.StatusNotifierWatcher queued, we can become primary after existing one deregisters"; - break; - case QDBusConnectionInterface::ServiceRegistered: - break; - } - - dbus.registerObject(QStringLiteral("/StatusNotifierWatcher"), this, QDBusConnection::ExportScriptableContents); - -// if (!dbus.registerObject(QStringLiteral("/StatusNotifierWatcher"), this, QDBusConnection::ExportScriptableContents)) -// qDebug() << QDBusConnection::sessionBus().lastError().message(); - - mWatcher = new QDBusServiceWatcher(this); - mWatcher->setConnection(dbus); - mWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); - - connect(mWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &StatusNotifierWatcher::serviceUnregistered); -} - -StatusNotifierWatcher::~StatusNotifierWatcher() -{ - QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.StatusNotifierWatcher")); -} - -void StatusNotifierWatcher::RegisterStatusNotifierItem(const QString &serviceOrPath) -{ - QString service = serviceOrPath; - QString path = QStringLiteral("/StatusNotifierItem"); - - // workaround for sni-qt - if (service.startsWith(QLatin1Char('/'))) { - path = service; - service = message().service(); - } - - QString notifierItemId = service + path; - - if (QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value() - && !mServices.contains(notifierItemId)) { - mServices << notifierItemId; - mWatcher->addWatchedService(service); - emit StatusNotifierItemRegistered(notifierItemId); - } -} - -void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString &service) -{ - if (!mHosts.contains(service)) - { - mHosts.append(service); - mWatcher->addWatchedService(service); - } -} - -void StatusNotifierWatcher::serviceUnregistered(const QString &service) -{ - // qDebug() << "Service" << service << "unregistered"; - - mWatcher->removeWatchedService(service); - - if (mHosts.contains(service)) - { - mHosts.removeAll(service); - return; - } - - QString match = service + QLatin1Char('/'); - QStringList::Iterator it = mServices.begin(); - while (it != mServices.end()) - { - if (it->startsWith(match)) - { - QString name = *it; - it = mServices.erase(it); - emit StatusNotifierItemUnregistered(name); - } - else - ++it; - } -} diff --git a/src/statusnotifier/statusnotifierwatcher.h b/src/statusnotifier/statusnotifierwatcher.h deleted file mode 100644 index a4d2f98..0000000 --- a/src/statusnotifier/statusnotifierwatcher.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef STATUSNOTIFIERWATCHER_H -#define STATUSNOTIFIERWATCHER_H - -#include -#include -#include -#include -#include - -#include "dbustypes.h" - -class StatusNotifierWatcher : public QObject, protected QDBusContext -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.StatusNotifierWatcher") - Q_SCRIPTABLE Q_PROPERTY(bool IsStatusNotifierHostRegistered READ isStatusNotifierHostRegistered) - Q_SCRIPTABLE Q_PROPERTY(int ProtocolVersion READ protocolVersion) - Q_SCRIPTABLE Q_PROPERTY(QStringList RegisteredStatusNotifierItems READ RegisteredStatusNotifierItems) - -public: - explicit StatusNotifierWatcher(QObject *parent = nullptr); - ~StatusNotifierWatcher(); - - bool isStatusNotifierHostRegistered() { return mHosts.count() > 0; } - int protocolVersion() const { return 0; } - QStringList RegisteredStatusNotifierItems() const { return mServices; } - -signals: - Q_SCRIPTABLE void StatusNotifierItemRegistered(const QString &service); - Q_SCRIPTABLE void StatusNotifierItemUnregistered(const QString &service); - Q_SCRIPTABLE void StatusNotifierHostRegistered(); - -public slots: - Q_SCRIPTABLE void RegisterStatusNotifierItem(const QString &serviceOrPath); - Q_SCRIPTABLE void RegisterStatusNotifierHost(const QString &service); - - void serviceUnregistered(const QString &service); - -private: - QStringList mServices; - QStringList mHosts; - QDBusServiceWatcher *mWatcher; -}; - -#endif // STATUSNOTIFIERWATCHER_H diff --git a/src/systemtray/CMakeLists.txt b/src/systemtray/CMakeLists.txt new file mode 100644 index 0000000..1b659b7 --- /dev/null +++ b/src/systemtray/CMakeLists.txt @@ -0,0 +1,18 @@ +set(TRAY_SRCS + statusnotifieritemjob.cpp + statusnotifieritemsource.cpp + systemtraytypes.cpp + systemtraytypedefs.h +) + +set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml) +qt5_add_dbus_interface(SRCS ${statusnotifierwatcher_xml} statusnotifierwatcher_interface) +qt5_add_dbus_interface(SRCS org.freedesktop.DBus.Properties.xml dbusproperties) + +set(statusnotifieritem_xml org.kde.StatusNotifierItem.xml) +set_source_files_properties(${statusnotifieritem_xml} PROPERTIES + NO_NAMESPACE false + INCLUDE "systemtraytypes.h" + CLASSNAME OrgKdeStatusNotifierItem +) +qt5_add_dbus_interface(SRCS ${statusnotifieritem_xml} statusnotifieritem_interface) \ No newline at end of file diff --git a/src/systemtray/org.freedesktop.DBus.Properties.xml b/src/systemtray/org.freedesktop.DBus.Properties.xml new file mode 100644 index 0000000..3bbf826 --- /dev/null +++ b/src/systemtray/org.freedesktop.DBus.Properties.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/org.kde.StatusNotifierItem.xml b/src/systemtray/org.kde.StatusNotifierItem.xml new file mode 100644 index 0000000..d378c74 --- /dev/null +++ b/src/systemtray/org.kde.StatusNotifierItem.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/org.kde.StatusNotifierWatcher.xml b/src/systemtray/org.kde.StatusNotifierWatcher.xml new file mode 100644 index 0000000..2eb1a7a --- /dev/null +++ b/src/systemtray/org.kde.StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/systemtray/statusnotifieritemjob.cpp b/src/systemtray/statusnotifieritemjob.cpp new file mode 100644 index 0000000..e198a65 --- /dev/null +++ b/src/systemtray/statusnotifieritemjob.cpp @@ -0,0 +1,25 @@ +#include "statusnotifieritemjob.h" + +StatusNotifierItemJob::StatusNotifierItemJob(StatusNotifierItemSource *source, QObject *parent) + : QObject(parent) + , m_source(source) +{ + // Queue connection, so that all 'deleteLater' are performed before we use updated menu. + connect(source, SIGNAL(contextMenuReady(QMenu *)), this, SLOT(contextMenuReady(QMenu *)), Qt::QueuedConnection); + connect(source, &StatusNotifierItemSource::activateResult, this, &StatusNotifierItemJob::activateCallback); +} + +void StatusNotifierItemJob::start() +{ + +} + +void StatusNotifierItemJob::activateCallback(bool success) +{ + +} + +void StatusNotifierItemJob::contextMenuReady(QMenu *menu) +{ + +} diff --git a/src/systemtray/statusnotifieritemjob.h b/src/systemtray/statusnotifieritemjob.h new file mode 100644 index 0000000..19bb572 --- /dev/null +++ b/src/systemtray/statusnotifieritemjob.h @@ -0,0 +1,27 @@ +#ifndef STATUSNOTIFIERITEMJOB_H +#define STATUSNOTIFIERITEMJOB_H + +#include +#include + +#include "statusnotifieritemsource.h" + +class StatusNotifierItemJob : public QObject +{ + Q_OBJECT + +public: + explicit StatusNotifierItemJob(StatusNotifierItemSource *source, QObject *parent = nullptr); + +protected: + void start(); + +private Q_SLOTS: + void activateCallback(bool success); + void contextMenuReady(QMenu *menu); + +private: + StatusNotifierItemSource *m_source; +}; + +#endif // STATUSNOTIFIERITEMJOB_H diff --git a/src/systemtray/statusnotifieritemsource.cpp b/src/systemtray/statusnotifieritemsource.cpp new file mode 100644 index 0000000..d40cc92 --- /dev/null +++ b/src/systemtray/statusnotifieritemsource.cpp @@ -0,0 +1,355 @@ +#include "statusnotifieritemsource.h" +#include "systemtraytypes.h" + +#include +#include +#include + +class MenuImporter : public DBusMenuImporter +{ +public: + using DBusMenuImporter::DBusMenuImporter; + +protected: + QIcon iconForName(const QString & name) override { + return QIcon::fromTheme(name); + } +}; + +StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent) + : QObject(parent) + , m_menuImporter(nullptr) + , m_refreshing(false) + , m_needsReRefreshing(false) + , m_titleUpdate(true) + , m_iconUpdate(true) + , m_tooltipUpdate(true) + , m_statusUpdate(true) + , m_id(notifierItemId) +{ + setObjectName(notifierItemId); + + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + m_name = notifierItemId; + + int slash = notifierItemId.indexOf('/'); + if (slash == -1) { + qWarning() << "Invalid notifierItemId:" << notifierItemId; + m_valid = false; + m_statusNotifierItemInterface = nullptr; + return; + } + + QString service = notifierItemId.left(slash); + QString path = notifierItemId.mid(slash); + + m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this); + + m_refreshTimer.setSingleShot(true); + m_refreshTimer.setInterval(10); + connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh); + + m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid(); + + if (m_valid) { + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); + connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus); + refresh(); + } +} + +StatusNotifierItemSource::~StatusNotifierItemSource() +{ + delete m_statusNotifierItemInterface; +} + +QString StatusNotifierItemSource::id() const +{ + return m_id; +} + +QString StatusNotifierItemSource::title() const +{ + return m_title; +} + +QString StatusNotifierItemSource::tooltip() const +{ + return m_tooltip; +} + +QString StatusNotifierItemSource::subtitle() const +{ + return m_subTitle; +} + +QString StatusNotifierItemSource::iconName() const +{ + return m_iconName; +} + +QIcon StatusNotifierItemSource::icon() const +{ + return m_icon; +} + +void StatusNotifierItemSource::activate(int x, int y) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), + m_statusNotifierItemInterface->path(), + m_statusNotifierItemInterface->interface(), + QStringLiteral("Activate")); + + message << x << y; + QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::activateCallback); + } +} + +void StatusNotifierItemSource::secondaryActivate(int x, int y) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y); + } +} + +void StatusNotifierItemSource::scroll(int delta, const QString &direction) +{ + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction); + } +} + +void StatusNotifierItemSource::contextMenu(int x, int y) +{ + if (m_menuImporter) { + m_menuImporter->updateMenu(); + + // Popup menu + if (m_menuImporter->menu()) { + m_menuImporter->menu()->popup(QPoint(x, y)); + } + } else { + qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; + if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { + m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y); + } + } +} + +void StatusNotifierItemSource::contextMenuReady() +{ + emit contextMenuReady(m_menuImporter->menu()); +} + +void StatusNotifierItemSource::refreshTitle() +{ + m_titleUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refreshIcons() +{ + m_iconUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refreshToolTip() +{ + m_tooltipUpdate = true; + refresh(); +} + +void StatusNotifierItemSource::refresh() +{ + if (!m_refreshTimer.isActive()) { + m_refreshTimer.start(); + } +} + +void StatusNotifierItemSource::performRefresh() +{ + if (m_refreshing) { + m_needsReRefreshing = true; + return; + } + + m_refreshing = true; + QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), + m_statusNotifierItemInterface->path(), + QStringLiteral("org.freedesktop.DBus.Properties"), + QStringLiteral("GetAll")); + + message << m_statusNotifierItemInterface->interface(); + QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback); +} + +void StatusNotifierItemSource::syncStatus(QString) +{ + +} + +void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) +{ + m_refreshing = false; + if (m_needsReRefreshing) { + m_needsReRefreshing = false; + performRefresh(); + call->deleteLater(); + return; + } + + QDBusPendingReply reply = *call; + if (reply.isError()) { + m_valid = false; + } else { + QVariantMap properties = reply.argumentAt<0>(); + QString path = properties[QStringLiteral("IconThemePath")].toString(); + + m_title = properties[QStringLiteral("Title")].toString(); + m_iconName = properties[QStringLiteral("IconName")].toString(); + + // ToolTip + KDbusToolTipStruct toolTip; + properties[QStringLiteral("ToolTip")].value() >> toolTip; + m_tooltip = toolTip.title; + + // Icon + KDbusImageVector image; + properties[QStringLiteral("IconPixmap")].value() >> image; + if (!image.isEmpty()) { + m_icon = imageVectorToPixmap(image); + } + +// QString newTitle; +// QString newIconName; +// QString newToolTip; + +// QString overlayIconName = properties[QStringLiteral("OverlayIconName")].toString(); +// QString iconName = properties[QStringLiteral("IconName")].toString(); + +// bool changed = false; + +// newTitle = properties[QStringLiteral("Title")].toString(); + +// if (!overlayIconName.isEmpty()) +// newIconName = iconName; +// if (!iconName.isEmpty()) +// newIconName = iconName; + +// KDbusToolTipStruct toolTip; +// properties[QStringLiteral("ToolTip")].value() >> toolTip; +// // newToolTip = !toolTip.title.isEmpty() ? toolTip.title : toolTip.subTitle; + +// if (newTitle != m_title) { +// m_title = newTitle; +// changed = true; +// } + +// if (newIconName != m_iconName) { +// m_iconName = iconName; +// changed = true; +// } + +// if (newToolTip != m_tooltip) { +// m_tooltip = newToolTip; +// changed = true; +// } + +// // Icon +// KDbusImageVector image; +// properties[QStringLiteral("AttentionIconPixmap")].value() >> image; +// if (!image.isEmpty()) { +// m_icon = imageVectorToPixmap(image); +// changed = true; +// } + +// properties[QStringLiteral("IconPixmap")].value() >> image; +// if (!image.isEmpty()) { +// m_icon = imageVectorToPixmap(image); +// changed = true; +// } + + // Menu + if (!m_menuImporter) { + QString menuObjectPath = properties[QStringLiteral("Menu")].value().path(); + if (!menuObjectPath.isEmpty()) { + if (menuObjectPath.startsWith(QLatin1String("/NO_DBUSMENU"))) { + // This is a hack to make it possible to disable DBusMenu in an + // application. The string "/NO_DBUSMENU" must be the same as in + // KStatusNotifierItem::setContextMenu(). + qWarning() << "DBusMenu disabled for this application"; + } else { + m_menuImporter = new MenuImporter(m_statusNotifierItemInterface->service(), + menuObjectPath, this); + } + } + } + + // qDebug() << newTitle << newIconName << newToolTip << image.isEmpty(); + + emit updated(this); + } + + call->deleteLater(); +} + +void StatusNotifierItemSource::activateCallback(QDBusPendingCallWatcher *call) +{ + QDBusPendingReply reply = *call; + emit activateResult(!reply.isError()); + call->deleteLater(); +} + +QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const +{ + // swap from network byte order if we are little endian + if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + uint *uintBuf = (uint *)image.data.data(); + for (uint i = 0; i < image.data.size() / sizeof(uint); ++i) { + *uintBuf = ntohl(*uintBuf); + ++uintBuf; + } + } + if (image.width == 0 || image.height == 0) { + return QPixmap(); + } + + // avoid a deep copy of the image data + // we need to keep a reference to the image.data alive for the lifespan of the image, even if the image is copied + // we create a new QByteArray with a shallow copy of the original data on the heap, then delete this in the QImage cleanup + auto dataRef = new QByteArray(image.data); + + QImage iconImage( + reinterpret_cast(dataRef->data()), + image.width, + image.height, + QImage::Format_ARGB32, + [](void *ptr) { + delete static_cast(ptr); + }, + dataRef); + return QPixmap::fromImage(iconImage); +} + +QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const +{ + QIcon icon; + + for (int i = 0; i < vector.size(); ++i) { + icon.addPixmap(KDbusImageStructToPixmap(vector[i])); + } + + return icon; +} diff --git a/src/systemtray/statusnotifieritemsource.h b/src/systemtray/statusnotifieritemsource.h new file mode 100644 index 0000000..166fdca --- /dev/null +++ b/src/systemtray/statusnotifieritemsource.h @@ -0,0 +1,72 @@ +#ifndef STATUSNOTIFIERITEMSOURCE_H +#define STATUSNOTIFIERITEMSOURCE_H + +#include +#include +#include + +#include "statusnotifieritem_interface.h" + +class DBusMenuImporter; +class StatusNotifierItemSource : public QObject +{ + Q_OBJECT + +public: + explicit StatusNotifierItemSource(const QString &service, QObject *parent = nullptr); + ~StatusNotifierItemSource(); + + QString id() const; + QString title() const; + QString tooltip() const; + QString subtitle() const; + QString iconName() const; + QIcon icon() const; + + void activate(int x, int y); + void secondaryActivate(int x, int y); + void scroll(int delta, const QString &direction); + void contextMenu(int x, int y); + +signals: + void contextMenuReady(QMenu *menu); + void activateResult(bool success); + void updated(StatusNotifierItemSource *); + +private slots: + void contextMenuReady(); + void refreshTitle(); + void refreshIcons(); + void refreshToolTip(); + void refresh(); + void performRefresh(); + void syncStatus(QString); + void refreshCallback(QDBusPendingCallWatcher *); + void activateCallback(QDBusPendingCallWatcher *); + +private: + QPixmap KDbusImageStructToPixmap(const KDbusImageStruct &image) const; + QIcon imageVectorToPixmap(const KDbusImageVector &vector) const; + +private: + bool m_valid; + QString m_name; + QTimer m_refreshTimer; + DBusMenuImporter *m_menuImporter; + org::kde::StatusNotifierItem *m_statusNotifierItemInterface; + bool m_refreshing : 1; + bool m_needsReRefreshing : 1; + bool m_titleUpdate : 1; + bool m_iconUpdate : 1; + bool m_tooltipUpdate : 1; + bool m_statusUpdate : 1; + + QString m_id; + QString m_title; + QString m_tooltip; + QString m_subTitle; + QString m_iconName; + QIcon m_icon; +}; + +#endif // STATUSNOTIFIERITEMSOURCE_H diff --git a/src/systemtray/statusnotifierwatcher.cpp b/src/systemtray/statusnotifierwatcher.cpp new file mode 100644 index 0000000..90f501d --- /dev/null +++ b/src/systemtray/statusnotifierwatcher.cpp @@ -0,0 +1,104 @@ +#include "statusnotifierwatcher.h" +#include "statusnotifieritem_interface.h" +#include "statusnotifierwatcheradaptor.h" + +#include +#include +#include + +StatusNotifierWatcher::StatusNotifierWatcher(QObject *parent) + : QObject(parent) +{ + new StatusNotifierWatcherAdaptor(this); + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.registerObject(QStringLiteral("/StatusNotifierWatcher"), this); + dbus.registerService(QStringLiteral("org.kde.StatusNotifierWatcher")); + + m_serviceWatcher = new QDBusServiceWatcher(this); + m_serviceWatcher->setConnection(dbus); + m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); + + connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &StatusNotifierWatcher::serviceUnregistered); +} + +StatusNotifierWatcher::~StatusNotifierWatcher() +{ + QDBusConnection dbus = QDBusConnection::sessionBus(); + dbus.unregisterService(QStringLiteral("org.kde.StatusNotifierWatcher")); +} + +QStringList StatusNotifierWatcher::RegisteredStatusNotifierItems() const +{ + return m_registeredServices; +} + +bool StatusNotifierWatcher::IsStatusNotifierHostRegistered() const +{ + return !m_statusNotifierHostServices.isEmpty(); +} + +void StatusNotifierWatcher::RegisterStatusNotifierItem(const QString &serviceOrPath) +{ + QString service; + QString path; + if (serviceOrPath.startsWith(QLatin1Char('/'))) { + service = message().service(); + path = serviceOrPath; + } else { + service = serviceOrPath; + path = QStringLiteral("/StatusNotifierItem"); + } + QString notifierItemId = service + path; + if (m_registeredServices.contains(notifierItemId)) { + return; + } + m_serviceWatcher->addWatchedService(service); + if (QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value()) { + // check if the service has registered a SystemTray object + org::kde::StatusNotifierItem trayclient(service, path, QDBusConnection::sessionBus()); + if (trayclient.isValid()) { + qDebug() << "Registering" << notifierItemId << "to system tray"; + m_registeredServices.append(notifierItemId); + emit StatusNotifierItemRegistered(notifierItemId); + } else { + m_serviceWatcher->removeWatchedService(service); + } + } else { + m_serviceWatcher->removeWatchedService(service); + } +} + +void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString &service) +{ + if (service.contains(QLatin1String("org.kde.StatusNotifierHost-")) && QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value() + && !m_statusNotifierHostServices.contains(service)) { + qDebug() << "Registering" << service << "as system tray"; + + m_statusNotifierHostServices.insert(service); + m_serviceWatcher->addWatchedService(service); + emit StatusNotifierHostRegistered(); + } +} + +void StatusNotifierWatcher::serviceUnregistered(const QString &name) +{ + qDebug() << "Service " << name << "unregistered"; + m_serviceWatcher->removeWatchedService(name); + + QString match = name + QLatin1Char('/'); + QStringList::Iterator it = m_registeredServices.begin(); + while (it != m_registeredServices.end()) { + if (it->startsWith(match)) { + QString name = *it; + it = m_registeredServices.erase(it); + emit StatusNotifierItemUnregistered(name); + } else { + ++it; + } + } + + if (m_statusNotifierHostServices.contains(name)) { + m_statusNotifierHostServices.remove(name); + emit StatusNotifierHostUnregistered(); + } +} diff --git a/src/systemtray/statusnotifierwatcher.h b/src/systemtray/statusnotifierwatcher.h new file mode 100644 index 0000000..7376f8e --- /dev/null +++ b/src/systemtray/statusnotifierwatcher.h @@ -0,0 +1,45 @@ +#ifndef STATUSNOTIFIERWATCHER_H +#define STATUSNOTIFIERWATCHER_H + +#include +#include +#include +#include + +class QDBusServiceWatcher; +class StatusNotifierWatcher : public QObject, protected QDBusContext +{ + Q_OBJECT + Q_SCRIPTABLE Q_PROPERTY(bool IsStatusNotifierHostRegistered READ IsStatusNotifierHostRegistered) + Q_SCRIPTABLE Q_PROPERTY(int ProtocolVersion READ protocolVersion) + Q_SCRIPTABLE Q_PROPERTY(QStringList RegisteredStatusNotifierItems READ RegisteredStatusNotifierItems) + +public: + explicit StatusNotifierWatcher(QObject *parent = nullptr); + ~StatusNotifierWatcher(); + + QStringList RegisteredStatusNotifierItems() const; + bool IsStatusNotifierHostRegistered() const; + int protocolVersion() const { return 0; } + +public slots: + void RegisterStatusNotifierItem(const QString &service); + void RegisterStatusNotifierHost(const QString &service); + +protected Q_SLOTS: + void serviceUnregistered(const QString &name); + +Q_SIGNALS: + void StatusNotifierItemRegistered(const QString &service); + // TODO: decide if this makes sense, the systray itself could notice the vanishing of items, but looks complete putting it here + void StatusNotifierItemUnregistered(const QString &service); + void StatusNotifierHostRegistered(); + void StatusNotifierHostUnregistered(); + +private: + QDBusServiceWatcher *m_serviceWatcher = nullptr; + QStringList m_registeredServices; + QSet m_statusNotifierHostServices; +}; + +#endif // STATUSNOTIFIERWATCHER_H diff --git a/src/systemtray/systemtraymodel.cpp b/src/systemtray/systemtraymodel.cpp new file mode 100644 index 0000000..1bd22c5 --- /dev/null +++ b/src/systemtray/systemtraymodel.cpp @@ -0,0 +1,144 @@ +#include "systemtraymodel.h" + +#include +#include + +SystemTrayModel::SystemTrayModel(QObject *parent) + : QAbstractListModel(parent) +{ + m_hostName = "org.kde.StatusNotifierHost-" + QString::number(QCoreApplication::applicationPid()); + QDBusConnection::sessionBus().interface()->registerService(m_hostName, QDBusConnectionInterface::DontQueueService); + + m_watcher = new StatusNotifierWatcher; + m_watcher->RegisterStatusNotifierHost(m_hostName); + m_watcher->moveToThread(QApplication::instance()->thread()); + + connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemRegistered, this, &SystemTrayModel::onItemAdded); + connect(m_watcher, &StatusNotifierWatcher::StatusNotifierItemUnregistered, this, &SystemTrayModel::onItemRemoved); +} + +SystemTrayModel::~SystemTrayModel() +{ + QDBusConnection::sessionBus().unregisterService(m_hostName); + + delete m_watcher; +} + +int SystemTrayModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_items.size(); +} + +QHash SystemTrayModel::roleNames() const +{ + QHash roles; + roles[IdRole] = "id"; + roles[IconNameRole] = "iconName"; + roles[IconRole] = "icon"; + roles[TitleRole] = "title"; + roles[ToolTipRole] = "toolTip"; + return roles; +} + +QVariant SystemTrayModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + StatusNotifierItemSource *item = m_items.at(index.row()); + + switch (role) { + case IdRole: + return item->id(); + case IconNameRole: + return item->iconName(); + case IconRole: { + if (!item->icon().isNull()) + return item->icon(); + else + return QVariant(); + } + case TitleRole: + return item->title(); + case ToolTipRole: + return item->tooltip(); + } + + return QVariant(); +} + +int SystemTrayModel::indexOf(const QString &id) +{ + for (StatusNotifierItemSource *item : m_items) { + if (item->id() == id) + return m_items.indexOf(item); + } + + return -1; +} + +StatusNotifierItemSource *SystemTrayModel::findItemById(const QString &id) +{ + int index = indexOf(id); + + if (index == -1) + return nullptr; + + return m_items.at(index); +} + +void SystemTrayModel::leftButtonClick(const QString &id) +{ + StatusNotifierItemSource *item = findItemById(id); + + if (item) { + QPoint p(QCursor::pos()); + item->activate(p.x(), p.y()); + } +} + +void SystemTrayModel::rightButtonClick(const QString &id) +{ + StatusNotifierItemSource *item = findItemById(id); + if (item) { + QPoint p(QCursor::pos()); + item->contextMenu(p.x(), p.y()); + } +} + +void SystemTrayModel::onItemAdded(const QString &service) +{ + StatusNotifierItemSource *source = new StatusNotifierItemSource(service, this); + + connect(source, &StatusNotifierItemSource::updated, this, &SystemTrayModel::updated); + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_items.append(source); + endInsertRows(); +} + +void SystemTrayModel::onItemRemoved(const QString &service) +{ + int index = indexOf(service); + + if (index != -1) { + beginRemoveRows(QModelIndex(), index, index); + StatusNotifierItemSource *item = m_items.at(index); + m_items.removeAll(item); + endRemoveRows(); + } +} + +void SystemTrayModel::updated(StatusNotifierItemSource *item) +{ + if (!item) + return; + + int idx = indexOf(item->id()); + + // update + if (idx != -1) { + dataChanged(index(idx, 0), index(idx, 0)); + } +} diff --git a/src/statusnotifier/statusnotifiermodel.h b/src/systemtray/systemtraymodel.h similarity index 63% rename from src/statusnotifier/statusnotifiermodel.h rename to src/systemtray/systemtraymodel.h index 40245e1..3142e2c 100644 --- a/src/statusnotifier/statusnotifiermodel.h +++ b/src/systemtray/systemtraymodel.h @@ -1,17 +1,12 @@ -#ifndef STATUSNOTIFIERMODEL_H -#define STATUSNOTIFIERMODEL_H +#ifndef SYSTEMTRAYMODEL_H +#define SYSTEMTRAYMODEL_H #include -#include -#include -#include - -#include "statusnotifieritemsource.h" #include "statusnotifierwatcher.h" -#include "sniasync.h" +#include "statusnotifieritemsource.h" -class StatusNotifierModel : public QAbstractListModel +class SystemTrayModel : public QAbstractListModel { Q_OBJECT @@ -19,16 +14,14 @@ public: enum Roles { IdRole = Qt::UserRole + 1, IconNameRole, - IconBytesRole, IconRole, TitleRole, ToolTipRole }; - explicit StatusNotifierModel(QObject *parent = nullptr); - ~StatusNotifierModel(); + explicit SystemTrayModel(QObject *parent = nullptr); + ~SystemTrayModel(); - // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; @@ -40,14 +33,15 @@ public: Q_INVOKABLE void leftButtonClick(const QString &id); Q_INVOKABLE void rightButtonClick(const QString &id); -public slots: - void itemAdded(QString serviceAndPath); - void itemRemoved(const QString &serviceAndPath); +private slots: + void onItemAdded(const QString &service); + void onItemRemoved(const QString &service); void updated(StatusNotifierItemSource *item); private: StatusNotifierWatcher *m_watcher; QList m_items; + QString m_hostName; }; -#endif // STATUSNOTIFIERMODEL_H +#endif // SYSTEMTRAYMODEL_H diff --git a/src/systemtray/systemtraytypedefs.h b/src/systemtray/systemtraytypedefs.h new file mode 100644 index 0000000..0024f39 --- /dev/null +++ b/src/systemtray/systemtraytypedefs.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYTYPEDEFS_H +#define SYSTEMTRAYTYPEDEFS_H + +#include +#include +#include +#include + +struct KDbusImageStruct { + int width; + int height; + QByteArray data; +}; + +typedef QVector KDbusImageVector; + +struct KDbusToolTipStruct { + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; +}; + +Q_DECLARE_METATYPE(KDbusImageStruct) +Q_DECLARE_METATYPE(KDbusImageVector) +Q_DECLARE_METATYPE(KDbusToolTipStruct) + +#endif \ No newline at end of file diff --git a/src/systemtray/systemtraytypes.cpp b/src/systemtray/systemtraytypes.cpp new file mode 100644 index 0000000..c690c58 --- /dev/null +++ b/src/systemtray/systemtraytypes.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#include +#include "systemtraytypes.h" + +// Marshall the ImageStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon) +{ + argument.beginStructure(); + argument << icon.width; + argument << icon.height; + argument << icon.data; + argument.endStructure(); + return argument; +} + +// Retrieve the ImageStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon) +{ + qint32 width = 0; + qint32 height = 0; + QByteArray data; + + if (argument.currentType() == QDBusArgument::StructureType) { + argument.beginStructure(); + // qCDebug(DATAENGINE_SNI)() << "begun structure"; + argument >> width; + // qCDebug(DATAENGINE_SNI)() << width; + argument >> height; + // qCDebug(DATAENGINE_SNI)() << height; + argument >> data; + // qCDebug(DATAENGINE_SNI)() << data.size(); + argument.endStructure(); + } + + icon.width = width; + icon.height = height; + icon.data = data; + + return argument; +} + +// Marshall the ImageVector data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector) +{ + argument.beginArray(qMetaTypeId()); + for (int i = 0; i < iconVector.size(); ++i) { + argument << iconVector[i]; + } + argument.endArray(); + return argument; +} + +// Retrieve the ImageVector data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector) +{ + iconVector.clear(); + + if (argument.currentType() == QDBusArgument::ArrayType) { + argument.beginArray(); + + while (!argument.atEnd()) { + KDbusImageStruct element; + argument >> element; + iconVector.append(element); + } + + argument.endArray(); + } + + return argument; +} + +// Marshall the ToolTipStruct data into a D-BUS argument +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip) +{ + argument.beginStructure(); + argument << toolTip.icon; + argument << toolTip.image; + argument << toolTip.title; + argument << toolTip.subTitle; + argument.endStructure(); + + return argument; +} + +// Retrieve the ToolTipStruct data from the D-BUS argument +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip) +{ + QString icon; + KDbusImageVector image; + QString title; + QString subTitle; + + if (argument.currentType() == QDBusArgument::StructureType) { + argument.beginStructure(); + argument >> icon; + argument >> image; + argument >> title; + argument >> subTitle; + argument.endStructure(); + } + + toolTip.icon = icon; + toolTip.image = image; + toolTip.title = title; + toolTip.subTitle = subTitle; + + return argument; +} diff --git a/src/systemtray/systemtraytypes.h b/src/systemtray/systemtraytypes.h new file mode 100644 index 0000000..8e1b64f --- /dev/null +++ b/src/systemtray/systemtraytypes.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * * + * Copyright (C) 2009 Marco Martin * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + +#ifndef SYSTEMTRAYTYPES_H +#define SYSTEMTRAYTYPES_H + +#include + +#include "systemtraytypedefs.h" + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon); + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector); + +const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip); +const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip); + +#endif diff --git a/svg/light/bluetooth-symbolic.svg b/svg/light/bluetooth-symbolic.svg index 7e9605c..e5c5f2f 100644 --- a/svg/light/bluetooth-symbolic.svg +++ b/svg/light/bluetooth-symbolic.svg @@ -1,16 +1,62 @@ - - - - - - image/svg+xml - - - - - - - - - + + + + + + + image/svg+xml + + + + + + + + + diff --git a/svg/light/dark-mode.svg b/svg/light/dark-mode.svg index 79b4c10..7082359 100644 --- a/svg/light/dark-mode.svg +++ b/svg/light/dark-mode.svg @@ -23,7 +23,7 @@ inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="2160" - inkscape:window-height="1307" + inkscape:window-height="1304" id="namedview11" showgrid="false" inkscape:zoom="45.9375" @@ -32,7 +32,8 @@ inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg9" /> + inkscape:current-layer="svg9" + inkscape:document-rotation="0" /> @@ -54,5 +55,5 @@ + style="stroke-width:0.0287544;fill:#363636;fill-opacity:1" />