Fix the switch between dark mode

This commit is contained in:
cutefishd 2021-03-17 21:33:06 +08:00
parent 4e2003f36f
commit 360764be8d
41 changed files with 1873 additions and 986 deletions

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"files.associations": {
"*.moc": "cpp"
}
}

View file

@ -10,7 +10,7 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 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(Qt5 REQUIRED ${QT})
find_package(KF5WindowSystem REQUIRED) find_package(KF5WindowSystem REQUIRED)
find_package(dbusmenu-qt5 REQUIRED) find_package(dbusmenu-qt5 REQUIRED)
@ -36,19 +36,36 @@ set(SRCS
src/appearance.cpp src/appearance.cpp
src/fakewindow.cpp src/fakewindow.cpp
src/iconitem.cpp
src/managedtexturenode.cpp
src/statusnotifier/dbustypes.cpp src/systemtray/statusnotifieritemjob.cpp
src/statusnotifier/sniasync.cpp src/systemtray/statusnotifieritemsource.cpp
src/statusnotifier/statusnotifieriteminterface.cpp src/systemtray/systemtraytypes.cpp
src/statusnotifier/statusnotifiermodel.cpp src/systemtray/systemtraytypedefs.h
src/statusnotifier/statusnotifierwatcher.cpp src/systemtray/systemtraymodel.cpp
src/statusnotifier/statusnotifieritemsource.cpp src/systemtray/statusnotifierwatcher.cpp
) )
set(RESOURCES set(RESOURCES
resources.qrc 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}) add_executable(${PROJECT_NAME} ${SRCS} ${DBUS_SRCS} ${RESOURCES})
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
Qt5::Core Qt5::Core
@ -58,6 +75,7 @@ target_link_libraries(${PROJECT_NAME}
Qt5::X11Extras Qt5::X11Extras
Qt5::Concurrent Qt5::Concurrent
Qt5::DBus Qt5::DBus
Qt5::Svg
MeuiKit MeuiKit

View file

@ -50,18 +50,12 @@ Item {
dragStarted = false dragStarted = false
} }
Image { IconItem {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
source: iconName ? iconName.indexOf("/") === 0 || iconName.indexOf("file://") === 0 || iconName.indexOf("qrc") === 0 width: control.iconSize
? iconName : "image://icontheme/" + iconName : iconName height: control.iconSize
sourceSize.width: control.iconSize source: iconName
sourceSize.height: control.iconSize
width: sourceSize.width
height: sourceSize.height
asynchronous: false
smooth: true
cache: true
visible: !dragStarted visible: !dragStarted

View file

@ -0,0 +1,9 @@
import QtQuick 2.12
Loader {
sourceComponent: _item
Component {
id: _item
}
}

View file

@ -6,6 +6,7 @@ import QtGraphicalEffects 1.0
import Cyber.NetworkManagement 1.0 as NM import Cyber.NetworkManagement 1.0 as NM
import Cutefish.Dock 1.0 import Cutefish.Dock 1.0
import MeuiKit 1.0 as Meui import MeuiKit 1.0 as Meui
import org.kde.plasma.core 2.0 as PlasmaCore
Item { Item {
id: root id: root
@ -71,7 +72,7 @@ Item {
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: 0 duration: 200
easing.type: Easing.InOutQuad easing.type: Easing.InOutQuad
} }
} }
@ -168,34 +169,22 @@ Item {
orientation: isHorizontal ? Qt.Horizontal : Qt.Vertical orientation: isHorizontal ? Qt.Horizontal : Qt.Vertical
layoutDirection: Qt.RightToLeft layoutDirection: Qt.RightToLeft
interactive: false interactive: false
model: trayModel model: SystemTrayModel { id: trayModel }
spacing: Meui.Units.smallSpacing / 2 spacing: Meui.Units.smallSpacing / 2
clip: true clip: true
StatusNotifierModel {
id: trayModel
}
onCountChanged: delayCalcIconSize() onCountChanged: delayCalcIconSize()
delegate: StandardItem { delegate: StandardItem {
height: trayView.itemHeight height: trayView.itemHeight
width: trayView.itemWidth width: trayView.itemWidth
Image { IconItem {
id: trayIcon id: iconItem
anchors.centerIn: parent 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 width: root.trayItemSize
height: root.trayItemSize height: root.trayItemSize
sourceSize.width: root.trayItemSize source: model.icon ? model.icon : model.iconName
sourceSize.height: root.trayItemSize
asynchronous: true
} }
onClicked: trayModel.leftButtonClick(id) onClicked: trayModel.leftButtonClick(id)

View file

@ -102,5 +102,6 @@
<file>svg/light/dark-mode.svg</file> <file>svg/light/dark-mode.svg</file>
<file>svg/dark/dark-mode.svg</file> <file>svg/dark/dark-mode.svg</file>
<file>svg/dark/bluetooth-symbolic.svg</file> <file>svg/dark/bluetooth-symbolic.svg</file>
<file>qml/StatusNotifierItem.qml</file>
</qresource> </qresource>
</RCC> </RCC>

414
src/iconitem.cpp Normal file
View file

@ -0,0 +1,414 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: cutefish <cutefishos@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "iconitem.h"
#include <QDebug>
#include <QPainter>
#include <QPaintEngine>
#include <QApplication>
#include <QQuickWindow>
#include <QPixmap>
#include <QImageReader>
#include "managedtexturenode.h"
template<class T>
typename std::enable_if <!std::is_integral<T>(), bool>::type almost_equal(T x, T y, int ulp)
{
return std::abs(x - y) <std::numeric_limits<T>::epsilon() * std::abs(x + y) * ulp
|| std::abs(x - y) <std::numeric_limits<T>::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<QIcon>() && !source.value<QIcon>().name().isEmpty()) {
sourceString = source.value<QIcon>().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<QIcon>();
if (icon.isNull()) {
icon = QIcon::fromTheme(sourceString, QIcon::fromTheme("application-x-desktop"));
}
m_iconItemSource.reset(new QIconSource(icon, this));
}
}
} else if (source.canConvert<QIcon>()) {
m_iconItemSource.reset(new QIconSource(source.value<QIcon>(), this));
} else if (source.canConvert<QImage>()) {
m_iconItemSource.reset(new QImageSource(source.value<QImage>(), 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<ManagedTextureNode *>(oldNode);
if (!textureNode || m_textureChanged) {
delete oldNode;
textureNode = new ManagedTextureNode;
textureNode->setTexture(QSharedPointer<QSGTexture>(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();
}

100
src/iconitem.h Normal file
View file

@ -0,0 +1,100 @@
/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: cutefish <cutefishos@foxmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ICONITEM_H
#define ICONITEM_H
#include <QQuickItem>
#include <QIcon>
#include <QImage>
#include <QPixmap>
#include <QSharedPointer>
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<IconItemSource> 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

View file

@ -30,8 +30,9 @@
#include "battery.h" #include "battery.h"
#include "brightness.h" #include "brightness.h"
#include "controlcenterdialog.h" #include "controlcenterdialog.h"
#include "statusnotifier/statusnotifiermodel.h" #include "systemtray/systemtraymodel.h"
#include "appearance.h" #include "appearance.h"
#include "iconitem.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -43,8 +44,9 @@ int main(int argc, char *argv[])
qmlRegisterType<Battery>("Cutefish.Dock", 1, 0, "Battery"); qmlRegisterType<Battery>("Cutefish.Dock", 1, 0, "Battery");
qmlRegisterType<Brightness>("Cutefish.Dock", 1, 0, "Brightness"); qmlRegisterType<Brightness>("Cutefish.Dock", 1, 0, "Brightness");
qmlRegisterType<ControlCenterDialog>("Cutefish.Dock", 1, 0, "ControlCenterDialog"); qmlRegisterType<ControlCenterDialog>("Cutefish.Dock", 1, 0, "ControlCenterDialog");
qmlRegisterType<StatusNotifierModel>("Cutefish.Dock", 1, 0, "StatusNotifierModel"); qmlRegisterType<SystemTrayModel>("Cutefish.Dock", 1, 0, "SystemTrayModel");
qmlRegisterType<Appearance>("Cutefish.Dock", 1, 0, "Appearance"); qmlRegisterType<Appearance>("Cutefish.Dock", 1, 0, "Appearance");
qmlRegisterType<IconItem>("Cutefish.Dock", 1, 0, "IconItem");
QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-dock/translations/").arg(QLocale::system().name()); QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-dock/translations/").arg(QLocale::system().name());
if (QFile::exists(qmFilePath)) { if (QFile::exists(qmFilePath)) {

View file

@ -51,10 +51,11 @@ MainWindow::MainWindow(QQuickView *parent)
engine()->rootContext()->setContextProperty("Settings", m_settings); engine()->rootContext()->setContextProperty("Settings", m_settings);
engine()->rootContext()->setContextProperty("mainWindow", this); engine()->rootContext()->setContextProperty("mainWindow", this);
setSource(QUrl(QStringLiteral("qrc:/qml/main.qml")));
setResizeMode(QQuickView::SizeRootObjectToView); setResizeMode(QQuickView::SizeRootObjectToView);
setScreen(qApp->primaryScreen()); setScreen(qApp->primaryScreen());
setSource(QUrl(QStringLiteral("qrc:/qml/main.qml")));
setVisible(true); setVisible(true);
initSlideWindow(); initSlideWindow();
resizeWindow(); resizeWindow();
onVisibilityChanged(); onVisibilityChanged();

View file

@ -40,9 +40,6 @@ signals:
void iconSizeChanged(); void iconSizeChanged();
void positionChanged(); void positionChanged();
protected:
void event(QEvent *e, QObject *obj);
private: private:
QRect windowRect() const; QRect windowRect() const;
void resizeWindow(); void resizeWindow();

View file

@ -0,0 +1,16 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "managedtexturenode.h"
ManagedTextureNode::ManagedTextureNode()
{
}
void ManagedTextureNode::setTexture(QSharedPointer<QSGTexture> texture)
{
m_texture = texture;
QSGSimpleTextureNode::setTexture(texture.data());
}

43
src/managedtexturenode.h Normal file
View file

@ -0,0 +1,43 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef MANAGEDTEXTURENODE_H
#define MANAGEDTEXTURENODE_H
#include <QSGSimpleTextureNode>
#include <QSGTexture>
#include <QSharedPointer>
#include <qglobal.h>
/**
* @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<QSGTexture> texture);
private:
QSharedPointer<QSGTexture> m_texture;
};
#endif

View file

@ -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;
}

View file

@ -1,30 +0,0 @@
#include <QDBusArgument>
#ifndef DBUSTYPES_H
#define DBUSTYPES_H
struct IconPixmap {
int width;
int height;
QByteArray bytes;
};
typedef QList<IconPixmap> IconPixmapList;
struct ToolTip {
QString iconName;
QList<IconPixmap> 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

View file

@ -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<QDBusVariant> 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);
}

View file

@ -1,89 +0,0 @@
#if !defined(SNIASYNC_H)
#define SNIASYNC_H
#include <functional>
#include "statusnotifieriteminterface.h"
template<typename>
struct remove_class_type { using type = void; }; // bluff
template<typename C, typename R, typename... ArgTypes>
struct remove_class_type<R (C::*)(ArgTypes...)> { using type = R(ArgTypes...); };
template<typename C, typename R, typename... ArgTypes>
struct remove_class_type<R (C::*)(ArgTypes...) const> { using type = R(ArgTypes...); };
template <typename L>
class call_sig_helper
{
template <typename L1>
static decltype(&L1::operator()) test(int);
template <typename L1>
static void test(...); //bluff
public:
using type = decltype(test<L>(0));
};
template <typename L>
struct call_signature : public remove_class_type<typename call_sig_helper<L>::type> {};
template <typename R, typename... ArgTypes>
struct call_signature<R (ArgTypes...)> { using type = R (ArgTypes...); };
template <typename R, typename... ArgTypes>
struct call_signature<R (*)(ArgTypes...)> { using type = R (ArgTypes...); };
template <typename C, typename R, typename... ArgTypes>
struct call_signature<R (C::*)(ArgTypes...)> { using type = R (ArgTypes...); };
template<typename C, typename R, typename... ArgTypes>
struct call_signature<R (C::*)(ArgTypes...) const> { using type = R(ArgTypes...); };
template <typename> struct is_valid_signature : public std::false_type {};
template <typename Arg>
struct is_valid_signature<void (Arg)> : 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 <typename F>
inline void propertyGetAsync(QString const &name, F finished)
{
static_assert(is_valid_signature<typename call_signature<F>::type>::value, "need callable (lambda, *function, callable obj) (Arg) -> void");
connect(new QDBusPendingCallWatcher{asyncPropGet(name), this},
&QDBusPendingCallWatcher::finished,
[this, finished, name] (QDBusPendingCallWatcher * call)
{
QDBusPendingReply<QVariant> reply = *call;
if (reply.isError())
qDebug().noquote().nospace() << "Error on DBus request(" << m_sni.service() << ',' << m_sni.path() << "): " << reply.error();
finished(qdbus_cast<typename std::function<typename call_signature<F>::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<QDBusVariant> asyncPropGet(QString const & property);
private:
org::kde::StatusNotifierItem m_sni;
};
#endif

View file

@ -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()
{
}

View file

@ -1,136 +0,0 @@
#ifndef STATUSNOTIFIERITEMINTERFACE_H
#define STATUSNOTIFIERITEMINTERFACE_H
#include <QObject>
#include <QByteArray>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <QtDBus>
#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<QVariant> argumentList;
argumentList << QVariant::fromValue(x) << QVariant::fromValue(y);
return asyncCallWithArgumentList(QLatin1String("Activate"), argumentList);
}
inline QDBusPendingReply<> ContextMenu(int x, int y)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(x) << QVariant::fromValue(y);
return asyncCallWithArgumentList(QLatin1String("ContextMenu"), argumentList);
}
inline QDBusPendingReply<> Scroll(int delta, const QString &orientation)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(delta) << QVariant::fromValue(orientation);
return asyncCallWithArgumentList(QLatin1String("Scroll"), argumentList);
}
inline QDBusPendingReply<> SecondaryActivate(int x, int y)
{
QList<QVariant> 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

View file

@ -1,211 +0,0 @@
#include "statusnotifieritemsource.h"
#include <QApplication>
#include <QIcon>
#include <QDebug>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QVariantMap>
#include <QImage>
#include <QMenu>
#include <QPixmap>
#include <dbusmenu-qt5/dbusmenuimporter.h>
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<QVariantMap> 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<QDBusArgument>() >> toolTip;
m_tooltip = toolTip.title;
// Menu
QString menuObjectPath = properties[QStringLiteral("Menu")].value<QDBusObjectPath>().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<QDBusArgument>() >> 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<uchar*>(iconPixmap.bytes.data());
for (const uchar *src = image.constBits(); src < end; src += 4, dest += 4)
qToUnaligned(qToBigEndian<quint32>(qFromUnaligned<quint32>(src)), dest);
icon.addPixmap(QPixmap::fromImage(image));
}
}
return icon.pixmap(QSize(24, 24)).toImage();
}

View file

@ -1,57 +0,0 @@
#ifndef STATUSNOTIFIERITEMSOURCE_H
#define STATUSNOTIFIERITEMSOURCE_H
#include <QObject>
#include <QIcon>
#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

View file

@ -1,162 +0,0 @@
#include "statusnotifiermodel.h"
#include <QFutureWatcher>
#include <QtConcurrent>
#include <QDBusConnectionInterface>
#include <QApplication>
#include <QPixmap>
#include <QIcon>
#include <QImage>
#include <QCursor>
StatusNotifierModel::StatusNotifierModel(QObject *parent)
: QAbstractListModel(parent)
, m_watcher(nullptr)
{
QFutureWatcher<StatusNotifierWatcher *> * futureWatcher = new QFutureWatcher<StatusNotifierWatcher *>;
connect(futureWatcher, &QFutureWatcher<StatusNotifierWatcher *>::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<StatusNotifierWatcher *> 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<int, QByteArray> StatusNotifierModel::roleNames() const
{
QHash<int, QByteArray> 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));
}
}

View file

@ -1,99 +0,0 @@
#include "statusnotifierwatcher.h"
#include <QDebug>
#include <QDBusConnectionInterface>
StatusNotifierWatcher::StatusNotifierWatcher(QObject *parent) : QObject(parent)
{
qRegisterMetaType<IconPixmap>("IconPixmap");
qDBusRegisterMetaType<IconPixmap>();
qRegisterMetaType<IconPixmapList>("IconPixmapList");
qDBusRegisterMetaType<IconPixmapList>();
qRegisterMetaType<ToolTip>("ToolTip");
qDBusRegisterMetaType<ToolTip>();
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;
}
}

View file

@ -1,45 +0,0 @@
#ifndef STATUSNOTIFIERWATCHER_H
#define STATUSNOTIFIERWATCHER_H
#include <QDBusConnection>
#include <QDBusContext>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <QDBusServiceWatcher>
#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

View file

@ -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)

View file

@ -0,0 +1,27 @@
<?xml version="1.0" ?>
<node>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="out"/>
</method>
<method name="GetAll">
<arg type="s" name="interface_name" direction="in"/>
<arg type="a{sv}" name="properties" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<method name="Set">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="in"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
</signal>
</interface>
</node>
<!-- vim:set sw=2 sts=2 et ft=xml: -->

View file

@ -0,0 +1,96 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierItem">
<property name="Category" type="s" access="read"/>
<property name="Id" type="s" access="read"/>
<property name="Title" type="s" access="read"/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<!-- An additional path to add to the theme search path to find the icons specified above. -->
<!-- <property name="IconThemePath" type="s" access="read"/> -->
<!-- <property name="Menu" type="o" access="read"/> -->
<property name="ItemIsMenu" type="b" access="read"/>
<!-- main icon -->
<!-- names are preferred over pixmaps -->
<!-- <property name="IconName" type="s" access="read"/> -->
<!--struct containing width, height and image data-->
<property name="IconPixmap" type="(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/>
</property>
<!-- <property name="OverlayIconName" type="s" access="read"/> -->
<!-- <property name="OverlayIconPixmap" type="(iiay)" access="read"> -->
<!-- <annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/> -->
<!-- </property> -->
<!-- Requesting attention icon -->
<!-- <property name="AttentionIconName" type="s" access="read"/> -->
<!--same definition as image-->
<!-- <property name="AttentionIconPixmap" type="(iiay)" access="read"> -->
<!-- <annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/> -->
<!-- </property> -->
<!-- <property name="AttentionMovieName" type="s" access="read"/> -->
<!-- tooltip data -->
<!--(iiay) is an image-->
<!-- <property name="ToolTip" type="(s(iiay)ss)" access="read"> -->
<!-- <annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusToolTipStruct"/> -->
<!-- </property> -->
<!-- interaction: the systemtray wants the application to do something -->
<method name="ContextMenu">
<!-- we're passing the coordinates of the icon, so the app knows where to put the popup window -->
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="SecondaryActivate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Scroll">
<arg name="delta" type="i" direction="in"/>
<arg name="orientation" type="s" direction="in"/>
</method>
<!-- Signals: the client wants to change something in the status-->
<signal name="NewTitle">
</signal>
<signal name="NewIcon">
</signal>
<signal name="NewAttentionIcon">
</signal>
<signal name="NewOverlayIcon">
</signal>
<signal name="NewToolTip">
</signal>
<signal name="NewStatus">
<arg name="status" type="s"/>
</signal>
</interface>
</node>

View file

@ -0,0 +1,42 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierWatcher">
<!-- methods -->
<method name="RegisterStatusNotifierItem">
<arg name="service" type="s" direction="in"/>
</method>
<method name="RegisterStatusNotifierHost">
<arg name="service" type="s" direction="in"/>
</method>
<!-- properties -->
<property name="RegisteredStatusNotifierItems" type="as" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
</property>
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
<property name="ProtocolVersion" type="i" access="read"/>
<!-- signals -->
<signal name="StatusNotifierItemRegistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierItemUnregistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierHostRegistered">
</signal>
<signal name="StatusNotifierHostUnregistered">
</signal>
</interface>
</node>

View file

@ -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)
{
}

View file

@ -0,0 +1,27 @@
#ifndef STATUSNOTIFIERITEMJOB_H
#define STATUSNOTIFIERITEMJOB_H
#include <QThread>
#include <QMenu>
#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

View file

@ -0,0 +1,355 @@
#include "statusnotifieritemsource.h"
#include "systemtraytypes.h"
#include <QDebug>
#include <dbusmenuimporter.h>
#include <netinet/in.h>
class MenuImporter : public DBusMenuImporter
{
public:
using DBusMenuImporter::DBusMenuImporter;
protected:
QIcon iconForName(const QString & name) override {
return QIcon::fromTheme(name);
}
};
StatusNotifierItemSource::StatusNotifierItemSource(const QString &notifierItemId, 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<KDbusImageStruct>();
qDBusRegisterMetaType<KDbusImageVector>();
qDBusRegisterMetaType<KDbusToolTipStruct>();
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<QVariantMap> 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<QDBusArgument>() >> toolTip;
m_tooltip = toolTip.title;
// Icon
KDbusImageVector image;
properties[QStringLiteral("IconPixmap")].value<QDBusArgument>() >> 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<QDBusArgument>() >> 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<QDBusArgument>() >> image;
// if (!image.isEmpty()) {
// m_icon = imageVectorToPixmap(image);
// changed = true;
// }
// properties[QStringLiteral("IconPixmap")].value<QDBusArgument>() >> image;
// if (!image.isEmpty()) {
// m_icon = imageVectorToPixmap(image);
// changed = true;
// }
// Menu
if (!m_menuImporter) {
QString menuObjectPath = properties[QStringLiteral("Menu")].value<QDBusObjectPath>().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<void> 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<const uchar *>(dataRef->data()),
image.width,
image.height,
QImage::Format_ARGB32,
[](void *ptr) {
delete static_cast<QByteArray *>(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;
}

View file

@ -0,0 +1,72 @@
#ifndef STATUSNOTIFIERITEMSOURCE_H
#define STATUSNOTIFIERITEMSOURCE_H
#include <QObject>
#include <QMenu>
#include <QDBusPendingCallWatcher>
#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

View file

@ -0,0 +1,104 @@
#include "statusnotifierwatcher.h"
#include "statusnotifieritem_interface.h"
#include "statusnotifierwatcheradaptor.h"
#include <QDBusConnection>
#include <QDBusServiceWatcher>
#include <QDebug>
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();
}
}

View file

@ -0,0 +1,45 @@
#ifndef STATUSNOTIFIERWATCHER_H
#define STATUSNOTIFIERWATCHER_H
#include <QObject>
#include <QDBusContext>
#include <QStringList>
#include <QSet>
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<QString> m_statusNotifierHostServices;
};
#endif // STATUSNOTIFIERWATCHER_H

View file

@ -0,0 +1,144 @@
#include "systemtraymodel.h"
#include <QApplication>
#include <QDebug>
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<int, QByteArray> SystemTrayModel::roleNames() const
{
QHash<int, QByteArray> 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));
}
}

View file

@ -1,17 +1,12 @@
#ifndef STATUSNOTIFIERMODEL_H #ifndef SYSTEMTRAYMODEL_H
#define STATUSNOTIFIERMODEL_H #define SYSTEMTRAYMODEL_H
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QIcon>
#include <QMenu>
#include <dbusmenu-qt5/dbusmenuimporter.h>
#include "statusnotifieritemsource.h"
#include "statusnotifierwatcher.h" #include "statusnotifierwatcher.h"
#include "sniasync.h" #include "statusnotifieritemsource.h"
class StatusNotifierModel : public QAbstractListModel class SystemTrayModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -19,16 +14,14 @@ public:
enum Roles { enum Roles {
IdRole = Qt::UserRole + 1, IdRole = Qt::UserRole + 1,
IconNameRole, IconNameRole,
IconBytesRole,
IconRole, IconRole,
TitleRole, TitleRole,
ToolTipRole ToolTipRole
}; };
explicit StatusNotifierModel(QObject *parent = nullptr); explicit SystemTrayModel(QObject *parent = nullptr);
~StatusNotifierModel(); ~SystemTrayModel();
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@ -40,14 +33,15 @@ public:
Q_INVOKABLE void leftButtonClick(const QString &id); Q_INVOKABLE void leftButtonClick(const QString &id);
Q_INVOKABLE void rightButtonClick(const QString &id); Q_INVOKABLE void rightButtonClick(const QString &id);
public slots: private slots:
void itemAdded(QString serviceAndPath); void onItemAdded(const QString &service);
void itemRemoved(const QString &serviceAndPath); void onItemRemoved(const QString &service);
void updated(StatusNotifierItemSource *item); void updated(StatusNotifierItemSource *item);
private: private:
StatusNotifierWatcher *m_watcher; StatusNotifierWatcher *m_watcher;
QList<StatusNotifierItemSource *> m_items; QList<StatusNotifierItemSource *> m_items;
QString m_hostName;
}; };
#endif // STATUSNOTIFIERMODEL_H #endif // SYSTEMTRAYMODEL_H

View file

@ -0,0 +1,48 @@
/***************************************************************************
* *
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
* *
* 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 <QByteArray>
#include <QMetaType>
#include <QString>
#include <QVector>
struct KDbusImageStruct {
int width;
int height;
QByteArray data;
};
typedef QVector<KDbusImageStruct> KDbusImageVector;
struct KDbusToolTipStruct {
QString icon;
KDbusImageVector image;
QString title;
QString subTitle;
};
Q_DECLARE_METATYPE(KDbusImageStruct)
Q_DECLARE_METATYPE(KDbusImageVector)
Q_DECLARE_METATYPE(KDbusToolTipStruct)
#endif

View file

@ -0,0 +1,128 @@
/***************************************************************************
* *
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
* *
* 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 <QDebug>
#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<KDbusImageStruct>());
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;
}

View file

@ -0,0 +1,37 @@
/***************************************************************************
* *
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
* *
* 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 <QDBusArgument>
#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

View file

@ -1,16 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <svg
<metadata> xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
version="1.1"
id="svg9"
sodipodi:docname="bluetooth-symbolic.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2160"
inkscape:window-height="1304"
id="namedview11"
showgrid="false"
inkscape:zoom="45.9375"
inkscape:cx="8"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg9"
inkscape:document-rotation="0" />
<metadata
id="metadata2">
<rdf:RDF> <rdf:RDF>
<cc:Work rdf:about=""> <cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:title />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<defs> <defs
<style id="current-color-scheme" type="text/css">.ColorScheme-Text { color:#363636; } .ColorScheme-Highlight { color:#5294e2; }</style> id="defs5">
<style
id="current-color-scheme"
type="text/css">.ColorScheme-Text { color:#363636; } .ColorScheme-Highlight { color:#5294e2; }</style>
</defs> </defs>
<path class="ColorScheme-Text" d="m7.9981 2e-5v7.041l-3.293-3.293-2e-3 2e-3c-0.35575-0.35595-0.99605-0.99609-0.99605-0.99609l-0.70703 0.70703 4.5449 4.541-4.5449 4.541 0.70703 0.70703s0.64035-0.64009 0.99609-0.99609h2e-3l3.2949-3.293v7.0391h1l4-4-4-4 4-4-4-4zm1.002 1.416 2.5859 2.5859-2.5859 2.5859zm0 8 2.5859 2.5859-2.5859 2.5859z" color="#363636" fill="#363636"/> <path
class="ColorScheme-Text"
d="m7.9981 2e-5v7.041l-3.293-3.293-2e-3 2e-3c-0.35575-0.35595-0.99605-0.99609-0.99605-0.99609l-0.70703 0.70703 4.5449 4.541-4.5449 4.541 0.70703 0.70703s0.64035-0.64009 0.99609-0.99609h2e-3l3.2949-3.293v7.0391h1l4-4-4-4 4-4-4-4zm1.002 1.416 2.5859 2.5859-2.5859 2.5859zm0 8 2.5859 2.5859-2.5859 2.5859z"
color="#363636"
fill="#363636"
id="path7"
style="fill-opacity:1;fill:#363636" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1,006 B

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -23,7 +23,7 @@
inkscape:pageopacity="0" inkscape:pageopacity="0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:window-width="2160" inkscape:window-width="2160"
inkscape:window-height="1307" inkscape:window-height="1304"
id="namedview11" id="namedview11"
showgrid="false" showgrid="false"
inkscape:zoom="45.9375" inkscape:zoom="45.9375"
@ -32,7 +32,8 @@
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="0" inkscape:window-y="0"
inkscape:window-maximized="1" inkscape:window-maximized="1"
inkscape:current-layer="svg9" /> inkscape:current-layer="svg9"
inkscape:document-rotation="0" />
<metadata <metadata
id="metadata2"> id="metadata2">
<rdf:RDF> <rdf:RDF>
@ -54,5 +55,5 @@
<path <path
d="M 8.00028,1 C 4.140704,1 1,4.13971 1,7.9993 1,11.859394 4.140732,15 8.00028,15 11.859814,15 15,11.859394 15,7.9993 15,4.13971 11.859842,1 8.00028,1 Z m 0,12.593196 c -0.007,0 -0.014,-9.62e-4 -0.02058,-9.62e-4 V 2.407812 c 0.007,0 0.014,-0.00101 0.02058,-0.00101 3.083822,0 5.592902,2.508702 5.592902,5.592496 0,3.084326 -2.50908,5.593896 -5.592902,5.593896 z" d="M 8.00028,1 C 4.140704,1 1,4.13971 1,7.9993 1,11.859394 4.140732,15 8.00028,15 11.859814,15 15,11.859394 15,7.9993 15,4.13971 11.859842,1 8.00028,1 Z m 0,12.593196 c -0.007,0 -0.014,-9.62e-4 -0.02058,-9.62e-4 V 2.407812 c 0.007,0 0.014,-0.00101 0.02058,-0.00101 3.083822,0 5.592902,2.508702 5.592902,5.592496 0,3.084326 -2.50908,5.593896 -5.592902,5.593896 z"
id="path2" id="path2"
style="stroke-width:0.0287544" /> style="stroke-width:0.0287544;fill:#363636;fill-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB