forked from quickshell/quickshell
		
	service/tray: mostly complete StatusNotifierItem implementation
Notably missing dbusmenu which makes it actually useful.
This commit is contained in:
		
							parent
							
								
									d47a7f2cff
								
							
						
					
					
						commit
						6214ac1002
					
				
					 25 changed files with 1321 additions and 4 deletions
				
			
		| 
						 | 
				
			
			@ -14,6 +14,7 @@ option(SOCKETS "Enable unix socket support" ON)
 | 
			
		|||
option(WAYLAND "Enable wayland support" ON)
 | 
			
		||||
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
 | 
			
		||||
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
 | 
			
		||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
			
		||||
 | 
			
		||||
message(STATUS "Quickshell configuration")
 | 
			
		||||
message(STATUS "  NVIDIA workarounds: ${NVIDIA_COMPAT}")
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,8 @@ if (WAYLAND)
 | 
			
		|||
	message(STATUS "    Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}")
 | 
			
		||||
	message(STATUS "    Session Lock: ${WAYLAND_SESSION_LOCK}")
 | 
			
		||||
endif ()
 | 
			
		||||
message(STATUS "  Services")
 | 
			
		||||
message(STATUS "    StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
 | 
			
		||||
 | 
			
		||||
if (NOT DEFINED GIT_REVISION)
 | 
			
		||||
	execute_process(
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +76,10 @@ if (WAYLAND)
 | 
			
		|||
	list(APPEND QT_FPDEPS WaylandClient)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (SERVICE_STATUS_NOTIFIER)
 | 
			
		||||
	set(DBUS ON)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (DBUS)
 | 
			
		||||
	list(APPEND QT_DEPS Qt6::DBus)
 | 
			
		||||
	list(APPEND QT_FPDEPS DBus)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,3 +12,5 @@ endif()
 | 
			
		|||
if (WAYLAND)
 | 
			
		||||
	add_subdirectory(wayland)
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
add_subdirectory(services)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,6 +33,8 @@ EngineGeneration::EngineGeneration(QmlScanner scanner)
 | 
			
		|||
	this->engine.setIncubationController(&this->delayedIncubationController);
 | 
			
		||||
 | 
			
		||||
	this->engine.addImageProvider("icon", new IconImageProvider());
 | 
			
		||||
 | 
			
		||||
	QuickshellPlugin::runConstructGeneration(*this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EngineGeneration::~EngineGeneration() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
#include "iconimageprovider.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qstring.h>
 | 
			
		||||
 | 
			
		||||
QPixmap
 | 
			
		||||
IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
 | 
			
		||||
#include <qvector.h> // NOLINT (what??)
 | 
			
		||||
 | 
			
		||||
#include "generation.hpp"
 | 
			
		||||
 | 
			
		||||
static QVector<QuickshellPlugin*> plugins; // NOLINT
 | 
			
		||||
 | 
			
		||||
void QuickshellPlugin::registerPlugin(QuickshellPlugin& plugin) { plugins.push_back(&plugin); }
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +28,12 @@ void QuickshellPlugin::initPlugins() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QuickshellPlugin::runConstructGeneration(EngineGeneration& generation) {
 | 
			
		||||
	for (QuickshellPlugin* plugin: plugins) {
 | 
			
		||||
		plugin->constructGeneration(generation);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QuickshellPlugin::runOnReload() {
 | 
			
		||||
	for (QuickshellPlugin* plugin: plugins) {
 | 
			
		||||
		plugin->onReload();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qfunctionpointer.h>
 | 
			
		||||
 | 
			
		||||
class EngineGeneration;
 | 
			
		||||
 | 
			
		||||
class QuickshellPlugin {
 | 
			
		||||
public:
 | 
			
		||||
	QuickshellPlugin() = default;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,10 +17,12 @@ public:
 | 
			
		|||
	virtual bool applies() { return true; }
 | 
			
		||||
	virtual void init() {}
 | 
			
		||||
	virtual void registerTypes() {}
 | 
			
		||||
	virtual void constructGeneration(EngineGeneration& generation) {} // NOLINT
 | 
			
		||||
	virtual void onReload() {}
 | 
			
		||||
 | 
			
		||||
	static void registerPlugin(QuickshellPlugin& plugin);
 | 
			
		||||
	static void initPlugins();
 | 
			
		||||
	static void runConstructGeneration(EngineGeneration& generation);
 | 
			
		||||
	static void runOnReload();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@
 | 
			
		|||
#include <qmetatype.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpolygon.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
#include "dbus_properties.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -52,11 +53,15 @@ QDBusError demarshallVariant(const QVariant& variant, const QMetaType& type, voi
 | 
			
		|||
				QDebug(&error) << "failed to deserialize dbus value" << variant << "into" << type;
 | 
			
		||||
				return QDBusError(QDBusError::InvalidArgs, error);
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			QString error;
 | 
			
		||||
			QDebug(&error) << "mismatched signature while trying to demarshall" << variant << "into"
 | 
			
		||||
			               << type << "expected" << expectedSignature << "got" << signature;
 | 
			
		||||
			return QDBusError(QDBusError::InvalidArgs, error);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		QString error;
 | 
			
		||||
		QDebug(&error) << "failed to deserialize variant" << variant
 | 
			
		||||
		               << "which is not a primitive type or a dbus argument (what?)";
 | 
			
		||||
		QDebug(&error) << "failed to deserialize variant" << variant << "into" << type;
 | 
			
		||||
		return QDBusError(QDBusError::InvalidArgs, error);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +231,8 @@ void DBusPropertyGroup::updateAllViaGetAll() {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		delete call;
 | 
			
		||||
 | 
			
		||||
		emit this->getAllFinished();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
 | 
			
		||||
| 
						 | 
				
			
			@ -262,7 +269,8 @@ void DBusPropertyGroup::onPropertiesChanged(
 | 
			
		|||
    const QStringList& invalidatedProperties
 | 
			
		||||
) {
 | 
			
		||||
	if (interfaceName != this->interface->interface()) return;
 | 
			
		||||
	qCDebug(logDbus) << "Received property change set and invalidations for" << this->toString();
 | 
			
		||||
	qCDebug(logDbus).noquote() << "Received property change set and invalidations for"
 | 
			
		||||
	                           << this->toString();
 | 
			
		||||
 | 
			
		||||
	for (const auto& name: invalidatedProperties) {
 | 
			
		||||
		auto prop = std::find_if(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -122,6 +122,9 @@ public:
 | 
			
		|||
	void updateAllViaGetAll();
 | 
			
		||||
	[[nodiscard]] QString toString() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void getAllFinished();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onPropertiesChanged(
 | 
			
		||||
	    const QString& interfaceName,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								src/services/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/services/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
add_subdirectory(status_notifier)
 | 
			
		||||
							
								
								
									
										55
									
								
								src/services/status_notifier/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/services/status_notifier/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
qt_add_dbus_adaptor(DBUS_INTERFACES
 | 
			
		||||
	org.kde.StatusNotifierWatcher.xml
 | 
			
		||||
	watcher.hpp
 | 
			
		||||
	qs::service::sni::StatusNotifierWatcher
 | 
			
		||||
	dbus_watcher
 | 
			
		||||
	StatusNotifierWatcherAdaptor
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(org.kde.StatusNotifierItem.xml PROPERTIES
 | 
			
		||||
	CLASSNAME DBusStatusNotifierItem
 | 
			
		||||
	INCLUDE dbus_item_types.hpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
			
		||||
	org.kde.StatusNotifierItem.xml
 | 
			
		||||
	dbus_item
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(org.kde.StatusNotifierWatcher.xml PROPERTIES
 | 
			
		||||
	CLASSNAME DBusStatusNotifierWatcher
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
			
		||||
	org.kde.StatusNotifierWatcher.xml
 | 
			
		||||
	dbus_watcher_interface
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_library(quickshell-service-statusnotifier STATIC
 | 
			
		||||
	qml.cpp
 | 
			
		||||
	trayimageprovider.cpp
 | 
			
		||||
 | 
			
		||||
	watcher.cpp
 | 
			
		||||
	host.cpp
 | 
			
		||||
	item.cpp
 | 
			
		||||
	dbus_item_types.cpp
 | 
			
		||||
	${DBUS_INTERFACES}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
add_library(quickshell-service-statusnotifier-init OBJECT init.cpp)
 | 
			
		||||
 | 
			
		||||
# dbus headers
 | 
			
		||||
target_include_directories(quickshell-service-statusnotifier PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
 | 
			
		||||
 | 
			
		||||
qt_add_qml_module(quickshell-service-statusnotifier
 | 
			
		||||
  URI Quickshell.Services.SystemTray
 | 
			
		||||
  VERSION 0.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(quickshell-service-statusnotifier PRIVATE ${QT_DEPS} quickshell-dbus)
 | 
			
		||||
target_link_libraries(quickshell-service-statusnotifier-init PRIVATE ${QT_DEPS})
 | 
			
		||||
target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin quickshell-service-statusnotifier-init)
 | 
			
		||||
 | 
			
		||||
qs_pch(quickshell-service-statusnotifier)
 | 
			
		||||
qs_pch(quickshell-service-statusnotifierplugin)
 | 
			
		||||
qs_pch(quickshell-service-statusnotifier-init)
 | 
			
		||||
							
								
								
									
										121
									
								
								src/services/status_notifier/dbus_item_types.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/services/status_notifier/dbus_item_types.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,121 @@
 | 
			
		|||
#include "dbus_item_types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qdbusargument.h>
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qendian.h>
 | 
			
		||||
#include <qimage.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qmetatype.h>
 | 
			
		||||
#include <qsysinfo.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
QImage DBusSniIconPixmap::createImage() const {
 | 
			
		||||
	// fix byte order if on a little endian machine
 | 
			
		||||
	if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
 | 
			
		||||
		auto* newbuf = new quint32[this->data.size()];
 | 
			
		||||
		const auto* oldbuf = reinterpret_cast<const quint32*>(this->data.data()); // NOLINT
 | 
			
		||||
 | 
			
		||||
		for (uint i = 0; i < this->data.size() / sizeof(quint32); ++i) {
 | 
			
		||||
			newbuf[i] = qFromBigEndian(oldbuf[i]); // NOLINT
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return QImage(
 | 
			
		||||
		    reinterpret_cast<const uchar*>(newbuf), // NOLINT
 | 
			
		||||
		    this->width,
 | 
			
		||||
		    this->height,
 | 
			
		||||
		    QImage::Format_ARGB32,
 | 
			
		||||
		    [](void* ptr) { delete reinterpret_cast<quint32*>(ptr); }, // NOLINT
 | 
			
		||||
		    newbuf
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		return QImage(
 | 
			
		||||
		    reinterpret_cast<const uchar*>(this->data.data()), // NOLINT
 | 
			
		||||
		    this->width,
 | 
			
		||||
		    this->height,
 | 
			
		||||
		    QImage::Format_ARGB32
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmap& pixmap) {
 | 
			
		||||
	argument.beginStructure();
 | 
			
		||||
	argument >> pixmap.width;
 | 
			
		||||
	argument >> pixmap.height;
 | 
			
		||||
	argument >> pixmap.data;
 | 
			
		||||
	argument.endStructure();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmap& pixmap) {
 | 
			
		||||
	argument.beginStructure();
 | 
			
		||||
	argument << pixmap.width;
 | 
			
		||||
	argument << pixmap.height;
 | 
			
		||||
	argument << pixmap.data;
 | 
			
		||||
	argument.endStructure();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmapList& pixmaps) {
 | 
			
		||||
	argument.beginArray();
 | 
			
		||||
	pixmaps.clear();
 | 
			
		||||
 | 
			
		||||
	while (!argument.atEnd()) {
 | 
			
		||||
		pixmaps.append(qdbus_cast<DBusSniIconPixmap>(argument));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	argument.endArray();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmapList& pixmaps) {
 | 
			
		||||
	argument.beginArray(qMetaTypeId<DBusSniIconPixmap>());
 | 
			
		||||
 | 
			
		||||
	for (const auto& pixmap: pixmaps) {
 | 
			
		||||
		argument << pixmap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	argument.endArray();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniTooltip& tooltip) {
 | 
			
		||||
	argument.beginStructure();
 | 
			
		||||
	argument >> tooltip.icon;
 | 
			
		||||
	argument >> tooltip.iconPixmaps;
 | 
			
		||||
	argument >> tooltip.title;
 | 
			
		||||
	argument >> tooltip.description;
 | 
			
		||||
	argument.endStructure();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniTooltip& tooltip) {
 | 
			
		||||
	argument.beginStructure();
 | 
			
		||||
	argument << tooltip.icon;
 | 
			
		||||
	argument << tooltip.iconPixmaps;
 | 
			
		||||
	argument << tooltip.title;
 | 
			
		||||
	argument << tooltip.description;
 | 
			
		||||
	argument.endStructure();
 | 
			
		||||
	return argument;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const DBusSniIconPixmap& pixmap) {
 | 
			
		||||
	debug.nospace() << "DBusSniIconPixmap(width=" << pixmap.width << ", height=" << pixmap.height
 | 
			
		||||
	                << ")";
 | 
			
		||||
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip) {
 | 
			
		||||
	debug.nospace() << "DBusSniTooltip(title=" << tooltip.title
 | 
			
		||||
	                << ", description=" << tooltip.description << ", icon=" << tooltip.icon
 | 
			
		||||
	                << ", iconPixmaps=" << tooltip.iconPixmaps << ")";
 | 
			
		||||
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const QDBusObjectPath& path) {
 | 
			
		||||
	debug.nospace() << "QDBusObjectPath(" << path.path() << ")";
 | 
			
		||||
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/services/status_notifier/dbus_item_types.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/services/status_notifier/dbus_item_types.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbusargument.h>
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
 | 
			
		||||
struct DBusSniIconPixmap {
 | 
			
		||||
	qint32 width = 0;
 | 
			
		||||
	qint32 height = 0;
 | 
			
		||||
	QByteArray data;
 | 
			
		||||
 | 
			
		||||
	// valid only for the lifetime of the pixmap
 | 
			
		||||
	[[nodiscard]] QImage createImage() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using DBusSniIconPixmapList = QList<DBusSniIconPixmap>;
 | 
			
		||||
 | 
			
		||||
struct DBusSniTooltip {
 | 
			
		||||
	QString icon;
 | 
			
		||||
	DBusSniIconPixmapList iconPixmaps;
 | 
			
		||||
	QString title;
 | 
			
		||||
	QString description;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmap& pixmap);
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmap& pixmap);
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniIconPixmapList& pixmaps);
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniIconPixmapList& pixmaps);
 | 
			
		||||
const QDBusArgument& operator>>(const QDBusArgument& argument, DBusSniTooltip& tooltip);
 | 
			
		||||
const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniTooltip& tooltip);
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const DBusSniIconPixmap& pixmap);
 | 
			
		||||
QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip);
 | 
			
		||||
QDebug operator<<(QDebug debug, const QDBusObjectPath& path);
 | 
			
		||||
							
								
								
									
										188
									
								
								src/services/status_notifier/host.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/services/status_notifier/host.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
#include "host.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qdbusconnection.h>
 | 
			
		||||
#include <qdbuserror.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/dbusutil.hpp"
 | 
			
		||||
#include "dbus_watcher_interface.h"
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
#include "watcher.hpp"
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logStatusNotifierHost, "quickshell.service.sni.host", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
StatusNotifierHost::StatusNotifierHost(QObject* parent): QObject(parent) {
 | 
			
		||||
	StatusNotifierWatcher::instance(); // ensure at least one watcher exists
 | 
			
		||||
 | 
			
		||||
	auto bus = QDBusConnection::sessionBus();
 | 
			
		||||
 | 
			
		||||
	if (!bus.isConnected()) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost)
 | 
			
		||||
		    << "Could not connect to DBus. StatusNotifier service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->hostId = QString("org.kde.StatusNotifierHost-") + QString::number(getpid());
 | 
			
		||||
	auto success = bus.registerService(this->hostId);
 | 
			
		||||
 | 
			
		||||
	if (!success) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost) << "Could not register StatusNotifierHost object with DBus. "
 | 
			
		||||
		                                    "StatusNotifer service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &this->serviceWatcher,
 | 
			
		||||
	    &QDBusServiceWatcher::serviceRegistered,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &StatusNotifierHost::onWatcherRegistered
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &this->serviceWatcher,
 | 
			
		||||
	    &QDBusServiceWatcher::serviceUnregistered,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &StatusNotifierHost::onWatcherUnregistered
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.addWatchedService("org.kde.StatusNotifierWatcher");
 | 
			
		||||
	this->serviceWatcher.setConnection(bus);
 | 
			
		||||
 | 
			
		||||
	this->watcher = new DBusStatusNotifierWatcher(
 | 
			
		||||
	    "org.kde.StatusNotifierWatcher",
 | 
			
		||||
	    "/StatusNotifierWatcher",
 | 
			
		||||
	    bus,
 | 
			
		||||
	    this
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    this->watcher,
 | 
			
		||||
	    &DBusStatusNotifierWatcher::StatusNotifierItemRegistered,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &StatusNotifierHost::onItemRegistered
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    this->watcher,
 | 
			
		||||
	    &DBusStatusNotifierWatcher::StatusNotifierItemUnregistered,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &StatusNotifierHost::onItemUnregistered
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	if (!this->watcher->isValid()) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost)
 | 
			
		||||
		    << "Could not find active StatusNotifierWatcher. StatusNotifier service will not work "
 | 
			
		||||
		       "until one is present.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->connectToWatcher();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::connectToWatcher() {
 | 
			
		||||
	qCDebug(logStatusNotifierHost) << "Registering host with active StatusNotifierWatcher";
 | 
			
		||||
	this->watcher->RegisterStatusNotifierHost(this->hostId);
 | 
			
		||||
 | 
			
		||||
	qs::dbus::asyncReadProperty<QStringList>(
 | 
			
		||||
	    *this->watcher,
 | 
			
		||||
	    "RegisteredStatusNotifierItems",
 | 
			
		||||
	    [this](QStringList value, QDBusError error) { // NOLINT
 | 
			
		||||
		    if (error.isValid()) {
 | 
			
		||||
			    qCWarning(logStatusNotifierHost).noquote()
 | 
			
		||||
			        << "Error reading \"RegisteredStatusNotifierITems\" property of watcher"
 | 
			
		||||
			        << this->watcher->service();
 | 
			
		||||
 | 
			
		||||
			    qCWarning(logStatusNotifierHost) << error;
 | 
			
		||||
		    } else {
 | 
			
		||||
			    qCDebug(logStatusNotifierHost)
 | 
			
		||||
			        << "Registering preexisting status notifier items from watcher:" << value;
 | 
			
		||||
 | 
			
		||||
			    for (auto& item: value) {
 | 
			
		||||
				    this->onItemRegistered(item);
 | 
			
		||||
			    }
 | 
			
		||||
		    }
 | 
			
		||||
	    }
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QList<StatusNotifierItem*> StatusNotifierHost::items() const {
 | 
			
		||||
	auto items = this->mItems.values();
 | 
			
		||||
	items.removeIf([](StatusNotifierItem* item) { return !item->isReady(); });
 | 
			
		||||
	return items;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
StatusNotifierItem* StatusNotifierHost::itemByService(const QString& service) const {
 | 
			
		||||
	return this->mItems.value(service);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::onWatcherRegistered() { this->connectToWatcher(); }
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::onWatcherUnregistered() {
 | 
			
		||||
	qCDebug(logStatusNotifierHost) << "Unregistering StatusNotifierItems from old watcher";
 | 
			
		||||
 | 
			
		||||
	for (auto [service, item]: this->mItems.asKeyValueRange()) {
 | 
			
		||||
		emit this->itemUnregistered(item);
 | 
			
		||||
		delete item;
 | 
			
		||||
		qCDebug(logStatusNotifierHost).noquote()
 | 
			
		||||
		    << "Unregistered StatusNotifierItem" << service << "from host";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mItems.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::onItemRegistered(const QString& item) {
 | 
			
		||||
	if (this->mItems.contains(item)) {
 | 
			
		||||
		qCDebug(logStatusNotifierHost).noquote()
 | 
			
		||||
		    << "Ignoring duplicate registration of StatusNotifierItem" << item;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qCDebug(logStatusNotifierHost).noquote() << "Registering StatusNotifierItem" << item << "to host";
 | 
			
		||||
	auto* dItem = new StatusNotifierItem(item, this);
 | 
			
		||||
	if (!dItem->isValid()) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost).noquote()
 | 
			
		||||
		    << "Unable to connect to StatusNotifierItem at" << item;
 | 
			
		||||
		delete dItem;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mItems.insert(item, dItem);
 | 
			
		||||
	QObject::connect(dItem, &StatusNotifierItem::ready, this, &StatusNotifierHost::onItemReady);
 | 
			
		||||
	emit this->itemRegistered(dItem);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::onItemUnregistered(const QString& item) {
 | 
			
		||||
	if (auto* dItem = this->mItems.value(item)) {
 | 
			
		||||
		this->mItems.remove(item);
 | 
			
		||||
		emit this->itemUnregistered(dItem);
 | 
			
		||||
		delete dItem;
 | 
			
		||||
		qCDebug(logStatusNotifierHost).noquote()
 | 
			
		||||
		    << "Unregistered StatusNotifierItem" << item << "from host";
 | 
			
		||||
	} else {
 | 
			
		||||
		qCWarning(logStatusNotifierHost).noquote()
 | 
			
		||||
		    << "Ignoring unregistration for missing StatusNotifierItem at" << item;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierHost::onItemReady() {
 | 
			
		||||
	if (auto* item = qobject_cast<StatusNotifierItem*>(this->sender())) {
 | 
			
		||||
		emit this->itemReady(item);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
StatusNotifierHost* StatusNotifierHost::instance() {
 | 
			
		||||
	static StatusNotifierHost* instance = nullptr; // NOLINT
 | 
			
		||||
	if (instance == nullptr) instance = new StatusNotifierHost();
 | 
			
		||||
	return instance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										49
									
								
								src/services/status_notifier/host.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/services/status_notifier/host.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qhash.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "dbus_watcher_interface.h"
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierHost);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
class StatusNotifierHost: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit StatusNotifierHost(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	void connectToWatcher();
 | 
			
		||||
	[[nodiscard]] QList<StatusNotifierItem*> items() const;
 | 
			
		||||
	[[nodiscard]] StatusNotifierItem* itemByService(const QString& service) const;
 | 
			
		||||
 | 
			
		||||
	static StatusNotifierHost* instance();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void itemRegistered(StatusNotifierItem* item);
 | 
			
		||||
	void itemReady(StatusNotifierItem* item);
 | 
			
		||||
	void itemUnregistered(StatusNotifierItem* item);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onWatcherRegistered();
 | 
			
		||||
	void onWatcherUnregistered();
 | 
			
		||||
	void onItemRegistered(const QString& item);
 | 
			
		||||
	void onItemUnregistered(const QString& item);
 | 
			
		||||
	void onItemReady();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QString hostId;
 | 
			
		||||
	QDBusServiceWatcher serviceWatcher;
 | 
			
		||||
	DBusStatusNotifierWatcher* watcher = nullptr;
 | 
			
		||||
	QHash<QString, StatusNotifierItem*> mItems;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										15
									
								
								src/services/status_notifier/init.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/services/status_notifier/init.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
#include "../../core/generation.hpp"
 | 
			
		||||
#include "../../core/plugin.hpp"
 | 
			
		||||
#include "trayimageprovider.hpp"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class SniPlugin: public QuickshellPlugin {
 | 
			
		||||
	void constructGeneration(EngineGeneration& generation) override {
 | 
			
		||||
		generation.engine.addImageProvider("service.sni", new qs::service::sni::TrayImageProvider());
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
QS_REGISTER_PLUGIN(SniPlugin);
 | 
			
		||||
 | 
			
		||||
} // namespace
 | 
			
		||||
							
								
								
									
										164
									
								
								src/services/status_notifier/item.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								src/services/status_notifier/item.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,164 @@
 | 
			
		|||
#include "item.hpp"
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <qdbusmetatype.h>
 | 
			
		||||
#include <qdbuspendingcall.h>
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpainter.h>
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qrect.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qstring.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/dbusutil.hpp"
 | 
			
		||||
#include "dbus_item.h"
 | 
			
		||||
#include "dbus_item_types.hpp"
 | 
			
		||||
#include "host.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logStatusNotifierItem, "quickshell.service.sni.item", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent): QObject(parent) {
 | 
			
		||||
	// spec is unclear about what exactly an item address is, so split off anything but the connection path
 | 
			
		||||
	auto conn = address.split("/").value(0);
 | 
			
		||||
	this->item =
 | 
			
		||||
	    new DBusStatusNotifierItem(conn, "/StatusNotifierItem", QDBusConnection::sessionBus(), this);
 | 
			
		||||
 | 
			
		||||
	if (!this->item->isValid()) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost).noquote() << "Cannot create StatusNotifierItem for" << conn;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qDBusRegisterMetaType<DBusSniIconPixmap>();
 | 
			
		||||
	qDBusRegisterMetaType<DBusSniIconPixmapList>();
 | 
			
		||||
	qDBusRegisterMetaType<DBusSniTooltip>();
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewTitle, &this->title, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->iconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->iconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	//QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->iconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->overlayIconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->overlayIconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	//QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->iconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->attentionIconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->attentionIconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	//QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->iconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewToolTip, &this->tooltip, &AbstractDBusProperty::update);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->iconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->attentionIconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->overlayIconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->iconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->attentionIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->overlayIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &StatusNotifierItem::onGetAllFinished);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewStatus, this, [this](QString value) {
 | 
			
		||||
		qCDebug(logStatusNotifierItem) << "Received update for" << this->status.toString() << value;
 | 
			
		||||
		this->status.set(std::move(value));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	this->properties.setInterface(this->item);
 | 
			
		||||
	this->properties.updateAllViaGetAll();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StatusNotifierItem::isValid() const { return this->item->isValid(); }
 | 
			
		||||
bool StatusNotifierItem::isReady() const { return this->mReady; }
 | 
			
		||||
 | 
			
		||||
QString StatusNotifierItem::iconId() const {
 | 
			
		||||
	if (this->status.get() == "NeedsAttention") {
 | 
			
		||||
		auto name = this->attentionIconName.get();
 | 
			
		||||
		if (!name.isEmpty()) return QString("image://icon/") + name;
 | 
			
		||||
	} else {
 | 
			
		||||
		auto name = this->iconName.get();
 | 
			
		||||
		auto overlayName = this->overlayIconName.get();
 | 
			
		||||
		if (!name.isEmpty() && overlayName.isEmpty()) return QString("image://icon/") + name;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return QString("image://service.sni/") + this->item->service() + "/"
 | 
			
		||||
	     + QString::number(this->iconIndex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
 | 
			
		||||
	auto needsAttention = this->status.get() == "NeedsAttention";
 | 
			
		||||
 | 
			
		||||
	auto closestPixmap = [](const QSize& size, const DBusSniIconPixmapList& pixmaps) {
 | 
			
		||||
		const DBusSniIconPixmap* ret = nullptr;
 | 
			
		||||
 | 
			
		||||
		for (const auto& pixmap: pixmaps) {
 | 
			
		||||
			if (ret == nullptr) {
 | 
			
		||||
				ret = &pixmap;
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto existingAdequate = ret->width >= size.width() && ret->height >= size.height();
 | 
			
		||||
			auto newAdequite = pixmap.width >= size.width() && pixmap.height >= size.height();
 | 
			
		||||
			auto newSmaller = pixmap.width < ret->width || pixmap.height < ret->height;
 | 
			
		||||
 | 
			
		||||
			if ((existingAdequate && newAdequite && newSmaller) || (!existingAdequate && !newSmaller)) {
 | 
			
		||||
				ret = &pixmap;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return ret;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	QPixmap pixmap;
 | 
			
		||||
	if (needsAttention) {
 | 
			
		||||
		if (!this->attentionIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->attentionIconName.get());
 | 
			
		||||
			pixmap = icon.pixmap(size.width(), size.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->attentionIconPixmaps.get());
 | 
			
		||||
			if (icon != nullptr) pixmap = QPixmap::fromImage(icon->createImage());
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (!this->iconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->iconName.get());
 | 
			
		||||
			pixmap = icon.pixmap(size.width(), size.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->iconPixmaps.get());
 | 
			
		||||
			if (icon != nullptr) pixmap = QPixmap::fromImage(icon->createImage());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		QPixmap overlay;
 | 
			
		||||
		if (!this->overlayIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->overlayIconName.get());
 | 
			
		||||
			overlay = icon.pixmap(pixmap.width(), pixmap.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(pixmap.size(), this->overlayIconPixmaps.get());
 | 
			
		||||
			if (icon != nullptr) overlay = QPixmap::fromImage(icon->createImage());
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!overlay.isNull()) {
 | 
			
		||||
			auto painter = QPainter(&pixmap);
 | 
			
		||||
			painter.drawPixmap(QRect(0, 0, pixmap.width(), pixmap.height()), overlay);
 | 
			
		||||
			painter.end();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return pixmap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::updateIcon() {
 | 
			
		||||
	this->iconIndex++;
 | 
			
		||||
	emit this->iconChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::onGetAllFinished() {
 | 
			
		||||
	if (this->mReady) return;
 | 
			
		||||
	this->mReady = true;
 | 
			
		||||
	emit this->ready();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										66
									
								
								src/services/status_notifier/item.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/services/status_notifier/item.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbusextratypes.h>
 | 
			
		||||
#include <qdbuspendingcall.h>
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/dbusutil.hpp"
 | 
			
		||||
#include "dbus_item.h"
 | 
			
		||||
#include "dbus_item_types.hpp"
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierItem);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
class StatusNotifierItem: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit StatusNotifierItem(const QString& address, QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isValid() const;
 | 
			
		||||
	[[nodiscard]] bool isReady() const;
 | 
			
		||||
	[[nodiscard]] QString iconId() const;
 | 
			
		||||
	[[nodiscard]] QPixmap createPixmap(const QSize& size) const;
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	dbus::DBusPropertyGroup properties;
 | 
			
		||||
	dbus::DBusProperty<QString> id {this->properties, "Id"};
 | 
			
		||||
	dbus::DBusProperty<QString> title {this->properties, "Title"};
 | 
			
		||||
	dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
			
		||||
	dbus::DBusProperty<QString> category {this->properties, "Category"};
 | 
			
		||||
	dbus::DBusProperty<quint32> windowId {this->properties, "WindowId"};
 | 
			
		||||
	//dbus::DBusProperty<QString> iconThemePath {this->properties, "IconThemePath"};
 | 
			
		||||
	dbus::DBusProperty<QString> iconName {this->properties, "IconName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> iconPixmaps {this->properties, "IconPixmap"};
 | 
			
		||||
	dbus::DBusProperty<QString> overlayIconName {this->properties, "OverlayIconName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> overlayIconPixmaps {this->properties, "OverlayIconPixmap"};
 | 
			
		||||
	dbus::DBusProperty<QString> attentionIconName {this->properties, "AttentionIconName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> attentionIconPixmaps {this->properties, "AttentionIconPixmap"};
 | 
			
		||||
	dbus::DBusProperty<QString> attentionMovieName {this->properties, "AttentionMovieName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniTooltip> tooltip {this->properties, "ToolTip"};
 | 
			
		||||
	dbus::DBusProperty<bool> isMenu {this->properties, "ItemIsMenu"};
 | 
			
		||||
	dbus::DBusProperty<QDBusObjectPath> menuPath {this->properties, "Menu"};
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void ready();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void updateIcon();
 | 
			
		||||
	void onGetAllFinished();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	DBusStatusNotifierItem* item = nullptr;
 | 
			
		||||
	bool mReady = false;
 | 
			
		||||
 | 
			
		||||
	// bumped to inhibit caching
 | 
			
		||||
	quint32 iconIndex = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										54
									
								
								src/services/status_notifier/org.kde.StatusNotifierItem.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/services/status_notifier/org.kde.StatusNotifierItem.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<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="u" access="read"/>
 | 
			
		||||
    <property name="IconThemePath" type="s" access="read"/>
 | 
			
		||||
    <property name="IconName" type="s" access="read"/>
 | 
			
		||||
    <property name="IconPixmap" type="a(iiay)" access="read">
 | 
			
		||||
			<annotation name="org.qtproject.QtDBus.QtTypeName" value="DBusSniIconPixmapList"/>
 | 
			
		||||
		</property>
 | 
			
		||||
    <property name="OverlayIconName" type="s" access="read"/>
 | 
			
		||||
    <property name="OverlayIconPixmap" type="a(iiay)" access="read">
 | 
			
		||||
			<annotation name="org.qtproject.QtDBus.QtTypeName" value="DBusSniIconPixmapList"/>
 | 
			
		||||
		</property>
 | 
			
		||||
    <property name="AttentionIconName" type="s" access="read"/>
 | 
			
		||||
    <property name="AttentionIconPixmap" type="a(iiay)" access="read">
 | 
			
		||||
			<annotation name="org.qtproject.QtDBus.QtTypeName" value="DBusSniIconPixmapList"/>
 | 
			
		||||
		</property>
 | 
			
		||||
    <property name="AttentionMovieName" type="s" access="read"/>
 | 
			
		||||
    <property name="ToolTip" type="(sa(iiay)ss)" access="read">
 | 
			
		||||
			<annotation name="org.qtproject.QtDBus.QtTypeName" value="DBusSniTooltip"/>
 | 
			
		||||
		</property>
 | 
			
		||||
    <property name="Menu" type="o" access="read"/>
 | 
			
		||||
    <property name="ItemIsMenu" type="b" access="read"/>
 | 
			
		||||
 | 
			
		||||
    <method name="ContextMenu">
 | 
			
		||||
      <arg type="i" direction="in" name="x"/>
 | 
			
		||||
      <arg type="i" direction="in" name="y"/>
 | 
			
		||||
    </method>
 | 
			
		||||
    <method name="Activate">
 | 
			
		||||
      <arg type="i" direction="in" name="x"/>
 | 
			
		||||
      <arg type="i" direction="in" name="y"/>
 | 
			
		||||
    </method>
 | 
			
		||||
    <method name="SecondaryActivate">
 | 
			
		||||
      <arg type="i" direction="in" name="x"/>
 | 
			
		||||
      <arg type="i" direction="in" name="y"/>
 | 
			
		||||
    </method>
 | 
			
		||||
    <method name="Scroll">
 | 
			
		||||
      <arg type="i" direction="in" name="delta"/>
 | 
			
		||||
      <arg type="s" direction="in" name="orientation"/>
 | 
			
		||||
    </method>
 | 
			
		||||
 | 
			
		||||
    <signal name="NewTitle"/>
 | 
			
		||||
    <signal name="NewIcon"/>
 | 
			
		||||
    <signal name="NewAttentionIcon"/>
 | 
			
		||||
    <signal name="NewOverlayIcon"/>
 | 
			
		||||
    <signal name="NewToolTip"/>
 | 
			
		||||
    <signal name="NewStatus">
 | 
			
		||||
      <arg type="s" name="status"/>
 | 
			
		||||
    </signal>
 | 
			
		||||
  </interface>
 | 
			
		||||
</node>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
<node>
 | 
			
		||||
  <interface name="org.kde.StatusNotifierWatcher">
 | 
			
		||||
    <property name="ProtocolVersion" type="i" access="read"/>
 | 
			
		||||
    <property name="RegisteredStatusNotifierItems" type="as" access="read"/>
 | 
			
		||||
    <property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
 | 
			
		||||
 | 
			
		||||
    <method name="RegisterStatusNotifierItem">
 | 
			
		||||
      <arg name="service" type="s" direction="in"/>
 | 
			
		||||
    </method>
 | 
			
		||||
    <method name="RegisterStatusNotifierHost">
 | 
			
		||||
			<arg name="service" type="s" direction="in"/>
 | 
			
		||||
    </method>
 | 
			
		||||
 | 
			
		||||
    <signal name="StatusNotifierItemRegistered">
 | 
			
		||||
      <arg type="s" direction="out" name="service"/>
 | 
			
		||||
    </signal>
 | 
			
		||||
    <signal name="StatusNotifierItemUnregistered">
 | 
			
		||||
      <arg type="s" direction="out" name="service"/>
 | 
			
		||||
    </signal>
 | 
			
		||||
    <signal name="StatusNotifierHostRegistered"/>
 | 
			
		||||
    <signal name="StatusNotifierHostUnregistered"/>
 | 
			
		||||
  </interface>
 | 
			
		||||
</node>
 | 
			
		||||
							
								
								
									
										140
									
								
								src/services/status_notifier/qml.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/services/status_notifier/qml.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
#include "qml.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../../dbus/dbusutil.hpp"
 | 
			
		||||
#include "host.hpp"
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
using namespace qs::service::sni;
 | 
			
		||||
 | 
			
		||||
SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObject* parent)
 | 
			
		||||
    : QObject(parent)
 | 
			
		||||
    , item(item) {
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&this->item->id, &AbstractDBusProperty::changed, this, &SystemTrayItem::idChanged);
 | 
			
		||||
	QObject::connect(&this->item->title, &AbstractDBusProperty::changed, this, &SystemTrayItem::titleChanged);
 | 
			
		||||
	QObject::connect(&this->item->status, &AbstractDBusProperty::changed, this, &SystemTrayItem::statusChanged);
 | 
			
		||||
	QObject::connect(&this->item->category, &AbstractDBusProperty::changed, this, &SystemTrayItem::categoryChanged);
 | 
			
		||||
	QObject::connect(this->item, &StatusNotifierItem::iconChanged, this, &SystemTrayItem::iconChanged);
 | 
			
		||||
	QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipTitleChanged);
 | 
			
		||||
	QObject::connect(&this->item->tooltip, &AbstractDBusProperty::changed, this, &SystemTrayItem::tooltipDescriptionChanged);
 | 
			
		||||
	QObject::connect(&this->item->isMenu, &AbstractDBusProperty::changed, this, &SystemTrayItem::onlyMenuChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString SystemTrayItem::id() const {
 | 
			
		||||
	if (this->item == nullptr) return "";
 | 
			
		||||
	return this->item->id.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString SystemTrayItem::title() const {
 | 
			
		||||
	if (this->item == nullptr) return "";
 | 
			
		||||
	return this->item->title.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SystemTrayStatus::Enum SystemTrayItem::status() const {
 | 
			
		||||
	if (this->item == nullptr) return SystemTrayStatus::Passive;
 | 
			
		||||
	auto status = this->item->status.get();
 | 
			
		||||
 | 
			
		||||
	if (status == "Passive") return SystemTrayStatus::Passive;
 | 
			
		||||
	if (status == "Active") return SystemTrayStatus::Active;
 | 
			
		||||
	if (status == "NeedsAttention") return SystemTrayStatus::NeedsAttention;
 | 
			
		||||
 | 
			
		||||
	qCWarning(logStatusNotifierItem) << "Nonconformant StatusNotifierItem status" << status
 | 
			
		||||
	                                 << "returned for" << this->item->properties.toString();
 | 
			
		||||
 | 
			
		||||
	return SystemTrayStatus::Passive;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SystemTrayCategory::Enum SystemTrayItem::category() const {
 | 
			
		||||
	if (this->item == nullptr) return SystemTrayCategory::ApplicationStatus;
 | 
			
		||||
	auto category = this->item->category.get();
 | 
			
		||||
 | 
			
		||||
	if (category == "ApplicationStatus") return SystemTrayCategory::ApplicationStatus;
 | 
			
		||||
	if (category == "SystemServices") return SystemTrayCategory::SystemServices;
 | 
			
		||||
	if (category == "Hardware") return SystemTrayCategory::Hardware;
 | 
			
		||||
 | 
			
		||||
	qCWarning(logStatusNotifierItem) << "Nonconformant StatusNotifierItem category" << category
 | 
			
		||||
	                                 << "returned for" << this->item->properties.toString();
 | 
			
		||||
 | 
			
		||||
	return SystemTrayCategory::ApplicationStatus;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString SystemTrayItem::icon() const {
 | 
			
		||||
	if (this->item == nullptr) return "";
 | 
			
		||||
	return this->item->iconId();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString SystemTrayItem::tooltipTitle() const {
 | 
			
		||||
	if (this->item == nullptr) return "";
 | 
			
		||||
	return this->item->tooltip.get().title;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString SystemTrayItem::tooltipDescription() const {
 | 
			
		||||
	if (this->item == nullptr) return "";
 | 
			
		||||
	return this->item->tooltip.get().description;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SystemTrayItem::onlyMenu() const {
 | 
			
		||||
	if (this->item == nullptr) return false;
 | 
			
		||||
	return this->item->isMenu.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SystemTray::SystemTray(QObject* parent): QObject(parent) {
 | 
			
		||||
	auto* host = StatusNotifierHost::instance();
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(host, &StatusNotifierHost::itemReady, this, &SystemTray::onItemRegistered);
 | 
			
		||||
	QObject::connect(host, &StatusNotifierHost::itemUnregistered, this, &SystemTray::onItemUnregistered);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	for (auto* item: host->items()) {
 | 
			
		||||
		this->mItems.push_back(new SystemTrayItem(item, this));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTray::onItemRegistered(StatusNotifierItem* item) {
 | 
			
		||||
	this->mItems.push_back(new SystemTrayItem(item, this));
 | 
			
		||||
	emit this->itemsChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTray::onItemUnregistered(StatusNotifierItem* item) {
 | 
			
		||||
	SystemTrayItem* trayItem = nullptr;
 | 
			
		||||
 | 
			
		||||
	this->mItems.removeIf([item, &trayItem](SystemTrayItem* testItem) {
 | 
			
		||||
		if (testItem->item == item) {
 | 
			
		||||
			trayItem = testItem;
 | 
			
		||||
			return true;
 | 
			
		||||
		} else return false;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	emit this->itemsChanged();
 | 
			
		||||
 | 
			
		||||
	delete trayItem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<SystemTrayItem> SystemTray::items() {
 | 
			
		||||
	return QQmlListProperty<SystemTrayItem>(
 | 
			
		||||
	    this,
 | 
			
		||||
	    nullptr,
 | 
			
		||||
	    &SystemTray::itemsCount,
 | 
			
		||||
	    &SystemTray::itemAt
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qsizetype SystemTray::itemsCount(QQmlListProperty<SystemTrayItem>* property) {
 | 
			
		||||
	return reinterpret_cast<SystemTray*>(property->object)->mItems.count(); // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SystemTrayItem* SystemTray::itemAt(QQmlListProperty<SystemTrayItem>* property, qsizetype index) {
 | 
			
		||||
	return reinterpret_cast<SystemTray*>(property->object)->mItems.at(index); // NOLINT
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								src/services/status_notifier/qml.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/services/status_notifier/qml.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,128 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
 | 
			
		||||
namespace SystemTrayStatus { // NOLINT
 | 
			
		||||
Q_NAMESPACE;
 | 
			
		||||
QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
enum Enum {
 | 
			
		||||
	// A passive item does not convey important information and can be considered idle. You may want to hide these.
 | 
			
		||||
	Passive = 0,
 | 
			
		||||
	// An active item may have information more important than a passive one and you probably do not want to hide it.
 | 
			
		||||
	Active = 1,
 | 
			
		||||
	// An item that needs attention conveys very important information such as low battery.
 | 
			
		||||
	NeedsAttention = 2,
 | 
			
		||||
};
 | 
			
		||||
Q_ENUM_NS(Enum);
 | 
			
		||||
 | 
			
		||||
} // namespace SystemTrayStatus
 | 
			
		||||
 | 
			
		||||
namespace SystemTrayCategory { // NOLINT
 | 
			
		||||
Q_NAMESPACE;
 | 
			
		||||
QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
enum Enum {
 | 
			
		||||
	// The fallback category for general applications or anything that does
 | 
			
		||||
	// not fit into a different category.
 | 
			
		||||
	ApplicationStatus = 0,
 | 
			
		||||
	// System services such as IMEs or disk indexing.
 | 
			
		||||
	SystemServices = 1,
 | 
			
		||||
	// Hardware controls like battery indicators or volume control.
 | 
			
		||||
	Hardware = 2,
 | 
			
		||||
};
 | 
			
		||||
Q_ENUM_NS(Enum);
 | 
			
		||||
 | 
			
		||||
} // namespace SystemTrayCategory
 | 
			
		||||
 | 
			
		||||
///! An item in the system tray.
 | 
			
		||||
/// A system tray item, roughly conforming to the [kde/freedesktop spec]
 | 
			
		||||
/// (there is no real spec, we just implemented whatever seemed to actually be used).
 | 
			
		||||
///
 | 
			
		||||
/// [kde/freedesktop spec]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/
 | 
			
		||||
class SystemTrayItem: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// A name unique to the application, such as its name.
 | 
			
		||||
	Q_PROPERTY(QString id READ id NOTIFY idChanged);
 | 
			
		||||
	// A name that describes the application
 | 
			
		||||
	Q_PROPERTY(QString title READ title NOTIFY titleChanged);
 | 
			
		||||
	Q_PROPERTY(SystemTrayStatus::Enum status READ status NOTIFY statusChanged);
 | 
			
		||||
	Q_PROPERTY(SystemTrayCategory::Enum category READ category NOTIFY categoryChanged);
 | 
			
		||||
	// Icon source string, usable as an Image source.
 | 
			
		||||
	Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
 | 
			
		||||
	Q_PROPERTY(QString tooltipTitle READ tooltipTitle NOTIFY tooltipTitleChanged);
 | 
			
		||||
	Q_PROPERTY(QString tooltipDescription READ tooltipDescription NOTIFY tooltipDescriptionChanged);
 | 
			
		||||
	// If this tray item only offers a menu and no activation action.
 | 
			
		||||
	Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit SystemTrayItem(
 | 
			
		||||
	    qs::service::sni::StatusNotifierItem* item = nullptr,
 | 
			
		||||
	    QObject* parent = nullptr
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	// Primary activation action, generally triggered via a left click.
 | 
			
		||||
	//Q_INVOKABLE void activate();
 | 
			
		||||
 | 
			
		||||
	// Secondary activation action, generally triggered via a middle click.
 | 
			
		||||
	//Q_INVOKABLE void secondaryActivate();
 | 
			
		||||
 | 
			
		||||
	// Scroll action, such as changing volume on a mixer.
 | 
			
		||||
	//Q_INVOKABLE void scroll(qint32 delta, bool horizontal);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QString id() const;
 | 
			
		||||
	[[nodiscard]] QString title() const;
 | 
			
		||||
	[[nodiscard]] SystemTrayStatus::Enum status() const;
 | 
			
		||||
	[[nodiscard]] SystemTrayCategory::Enum category() const;
 | 
			
		||||
	[[nodiscard]] QString icon() const;
 | 
			
		||||
	[[nodiscard]] QString tooltipTitle() const;
 | 
			
		||||
	[[nodiscard]] QString tooltipDescription() const;
 | 
			
		||||
	[[nodiscard]] bool onlyMenu() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void idChanged();
 | 
			
		||||
	void titleChanged();
 | 
			
		||||
	void statusChanged();
 | 
			
		||||
	void categoryChanged();
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void tooltipTitleChanged();
 | 
			
		||||
	void tooltipDescriptionChanged();
 | 
			
		||||
	void onlyMenuChanged();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	qs::service::sni::StatusNotifierItem* item = nullptr;
 | 
			
		||||
 | 
			
		||||
	friend class SystemTray;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SystemTray: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	Q_PROPERTY(QQmlListProperty<SystemTrayItem> items READ items NOTIFY itemsChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_SINGLETON;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit SystemTray(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QQmlListProperty<SystemTrayItem> items();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void itemsChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onItemRegistered(qs::service::sni::StatusNotifierItem* item);
 | 
			
		||||
	void onItemUnregistered(qs::service::sni::StatusNotifierItem* item);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static qsizetype itemsCount(QQmlListProperty<SystemTrayItem>* property);
 | 
			
		||||
	static SystemTrayItem* itemAt(QQmlListProperty<SystemTrayItem>* property, qsizetype index);
 | 
			
		||||
 | 
			
		||||
	QList<SystemTrayItem*> mItems;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								src/services/status_notifier/trayimageprovider.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/services/status_notifier/trayimageprovider.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
#include "trayimageprovider.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qstring.h>
 | 
			
		||||
 | 
			
		||||
#include "host.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
QPixmap
 | 
			
		||||
TrayImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) {
 | 
			
		||||
	auto split = id.split('/');
 | 
			
		||||
	if (split.size() != 2) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost) << "Invalid image request:" << id;
 | 
			
		||||
		return QPixmap();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto* item = StatusNotifierHost::instance()->itemByService(split[0]);
 | 
			
		||||
 | 
			
		||||
	if (item == nullptr) {
 | 
			
		||||
		qCWarning(logStatusNotifierHost) << "Image requested for nonexistant service" << split[0];
 | 
			
		||||
		return QPixmap();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto pixmap = item->createPixmap(requestedSize);
 | 
			
		||||
	if (size != nullptr) *size = pixmap.size();
 | 
			
		||||
	return pixmap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										16
									
								
								src/services/status_notifier/trayimageprovider.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/services/status_notifier/trayimageprovider.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qquickimageprovider.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
class TrayImageProvider: public QQuickImageProvider {
 | 
			
		||||
public:
 | 
			
		||||
	explicit TrayImageProvider(): QQuickImageProvider(QQuickImageProvider::Pixmap) {}
 | 
			
		||||
 | 
			
		||||
	QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										140
									
								
								src/services/status_notifier/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/services/status_notifier/watcher.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
#include "watcher.hpp"
 | 
			
		||||
 | 
			
		||||
#include <dbus_watcher.h>
 | 
			
		||||
#include <qdbusconnection.h>
 | 
			
		||||
#include <qdbusconnectioninterface.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logStatusNotifierWatcher, "quickshell.service.sni.watcher", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
StatusNotifierWatcher::StatusNotifierWatcher(QObject* parent): QObject(parent) {
 | 
			
		||||
	new StatusNotifierWatcherAdaptor(this);
 | 
			
		||||
 | 
			
		||||
	qCDebug(logStatusNotifierWatcher) << "Starting StatusNotifierWatcher";
 | 
			
		||||
 | 
			
		||||
	auto bus = QDBusConnection::sessionBus();
 | 
			
		||||
 | 
			
		||||
	if (!bus.isConnected()) {
 | 
			
		||||
		qCWarning(logStatusNotifierWatcher)
 | 
			
		||||
		    << "Could not connect to DBus. StatusNotifier service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!bus.registerObject("/StatusNotifierWatcher", this)) {
 | 
			
		||||
		qCWarning(logStatusNotifierWatcher) << "Could not register StatusNotifierWatcher object with "
 | 
			
		||||
		                                       "DBus. StatusNotifer service will not work.";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &this->serviceWatcher,
 | 
			
		||||
	    &QDBusServiceWatcher::serviceUnregistered,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &StatusNotifierWatcher::onServiceUnregistered
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
 | 
			
		||||
	this->serviceWatcher.addWatchedService("org.kde.StatusNotifierWatcher");
 | 
			
		||||
	this->serviceWatcher.setConnection(bus);
 | 
			
		||||
 | 
			
		||||
	this->tryRegister();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierWatcher::tryRegister() { // NOLINT
 | 
			
		||||
	auto bus = QDBusConnection::sessionBus();
 | 
			
		||||
	auto success = bus.registerService("org.kde.StatusNotifierWatcher");
 | 
			
		||||
 | 
			
		||||
	if (success) {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher) << "Registered watcher at org.kde.StatusNotifierWatcher";
 | 
			
		||||
	} else {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher)
 | 
			
		||||
		    << "Could not register watcher at org.kde.StatusNotifierWatcher, presumably because one is "
 | 
			
		||||
		       "already registered.";
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher)
 | 
			
		||||
		    << "Registration will be attempted again if the active service is unregistered.";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierWatcher::onServiceUnregistered(const QString& service) {
 | 
			
		||||
	if (service == "org.kde.StatusNotifierWatcher") {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher)
 | 
			
		||||
		    << "Active StatusNotifierWatcher unregistered, attempting registration";
 | 
			
		||||
		this->tryRegister();
 | 
			
		||||
		return;
 | 
			
		||||
	} else if (this->items.removeAll(service) != 0) {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Unregistered StatusNotifierItem" << service << "from watcher";
 | 
			
		||||
		emit this->StatusNotifierItemUnregistered(service);
 | 
			
		||||
	} else if (this->hosts.removeAll(service) != 0) {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Unregistered StatusNotifierHost" << service << "from watcher";
 | 
			
		||||
		emit this->StatusNotifierHostUnregistered();
 | 
			
		||||
	} else {
 | 
			
		||||
		qCWarning(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Got service unregister event for untracked service" << service;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.removeWatchedService(service);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool StatusNotifierWatcher::isHostRegistered() const { // NOLINT
 | 
			
		||||
	// no point ever returning false
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QList<QString> StatusNotifierWatcher::registeredItems() const { return this->items; }
 | 
			
		||||
 | 
			
		||||
void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString& host) {
 | 
			
		||||
	if (this->hosts.contains(host)) {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Skipping duplicate registration of StatusNotifierHost" << host << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!QDBusConnection::sessionBus().interface()->serviceOwner(host).isValid()) {
 | 
			
		||||
		qCWarning(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Ignoring invalid StatusNotifierHost registration of" << host << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.addWatchedService(host);
 | 
			
		||||
	this->hosts.push_back(host);
 | 
			
		||||
	qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
	    << "Registered StatusNotifierHost" << host << "to watcher";
 | 
			
		||||
	emit this->StatusNotifierHostRegistered();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierWatcher::RegisterStatusNotifierItem(const QString& item) {
 | 
			
		||||
	if (this->items.contains(item)) {
 | 
			
		||||
		qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Skipping duplicate registration of StatusNotifierItem" << item << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!QDBusConnection::sessionBus().interface()->serviceOwner(item).isValid()) {
 | 
			
		||||
		qCWarning(logStatusNotifierWatcher).noquote()
 | 
			
		||||
		    << "Ignoring invalid StatusNotifierItem registration of" << item << "to watcher";
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->serviceWatcher.addWatchedService(item);
 | 
			
		||||
	this->items.push_back(item);
 | 
			
		||||
	qCDebug(logStatusNotifierWatcher).noquote()
 | 
			
		||||
	    << "Registered StatusNotifierItem" << item << "to watcher";
 | 
			
		||||
	emit this->StatusNotifierItemRegistered(item);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
StatusNotifierWatcher* StatusNotifierWatcher::instance() {
 | 
			
		||||
	static StatusNotifierWatcher* instance = nullptr; // NOLINT
 | 
			
		||||
	if (instance == nullptr) instance = new StatusNotifierWatcher();
 | 
			
		||||
	return instance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
							
								
								
									
										54
									
								
								src/services/status_notifier/watcher.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/services/status_notifier/watcher.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdbusinterface.h>
 | 
			
		||||
#include <qdbusservicewatcher.h>
 | 
			
		||||
#include <qlist.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierWatcher);
 | 
			
		||||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
class StatusNotifierWatcher: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	Q_PROPERTY(qint32 ProtocolVersion READ protocolVersion);
 | 
			
		||||
	Q_PROPERTY(bool IsStatusNotifierHostRegistered READ isHostRegistered);
 | 
			
		||||
	Q_PROPERTY(QList<QString> RegisteredStatusNotifierItems READ registeredItems);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit StatusNotifierWatcher(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	void tryRegister();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] qint32 protocolVersion() const { return 0; } // NOLINT
 | 
			
		||||
	[[nodiscard]] bool isHostRegistered() const;
 | 
			
		||||
	[[nodiscard]] QList<QString> registeredItems() const;
 | 
			
		||||
 | 
			
		||||
	// NOLINTBEGIN
 | 
			
		||||
	void RegisterStatusNotifierHost(const QString& host);
 | 
			
		||||
	void RegisterStatusNotifierItem(const QString& item);
 | 
			
		||||
	// NOLINTEND
 | 
			
		||||
 | 
			
		||||
	static StatusNotifierWatcher* instance();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	// NOLINTBEGIN
 | 
			
		||||
	void StatusNotifierHostRegistered();
 | 
			
		||||
	void StatusNotifierHostUnregistered();
 | 
			
		||||
	void StatusNotifierItemRegistered(const QString& service);
 | 
			
		||||
	void StatusNotifierItemUnregistered(const QString& service);
 | 
			
		||||
	// NOLINTEND
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onServiceUnregistered(const QString& service);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QDBusServiceWatcher serviceWatcher;
 | 
			
		||||
	QList<QString> hosts;
 | 
			
		||||
	QList<QString> items;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue