forked from quickshell/quickshell
		
	service/tray!: refactor qml bindings to StatusNotifierItem
Breaking: Dropped SystemTrayMenuWatcher.
This commit is contained in:
		
							parent
							
								
									f53e6fb515
								
							
						
					
					
						commit
						ac50767873
					
				
					 4 changed files with 222 additions and 415 deletions
				
			
		| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
 | 
			
		||||
#include "../../core/iconimageprovider.hpp"
 | 
			
		||||
#include "../../core/imageprovider.hpp"
 | 
			
		||||
#include "../../core/platformmenu.hpp"
 | 
			
		||||
#include "../../dbus/dbusmenu/dbusmenu.hpp"
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "dbus_item.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +31,7 @@
 | 
			
		|||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
using namespace qs::dbus::dbusmenu;
 | 
			
		||||
using namespace qs::menu::platform;
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logStatusNotifierItem, "quickshell.service.sni.item", QtWarningMsg);
 | 
			
		||||
Q_LOGGING_CATEGORY(logSniMenu, "quickshell.service.sni.item.menu", QtWarningMsg);
 | 
			
		||||
| 
						 | 
				
			
			@ -56,34 +58,34 @@ StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent)
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// 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->item, &DBusStatusNotifierItem::NewTitle, &this->pTitle, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->pIconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->pIconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewIcon, &this->pIconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->pOverlayIconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->pOverlayIconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewOverlayIcon, &this->pIconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->pAttentionIconName, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->pAttentionIconPixmaps, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewAttentionIcon, &this->pIconThemePath, &AbstractDBusProperty::update);
 | 
			
		||||
	QObject::connect(this->item, &DBusStatusNotifierItem::NewToolTip, &this->pTooltip, &AbstractDBusProperty::update);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->iconThemePath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	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->pIconThemePath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pIconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pAttentionIconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pOverlayIconName, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pAttentionIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
	QObject::connect(&this->pOverlayIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &StatusNotifierItem::onGetAllFinished);
 | 
			
		||||
	QObject::connect(&this->properties, &DBusPropertyGroup::getAllFailed, this, &StatusNotifierItem::onGetAllFailed);
 | 
			
		||||
	QObject::connect(&this->menuPath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::onMenuPathChanged);
 | 
			
		||||
	QObject::connect(&this->pMenuPath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::onMenuPathChanged);
 | 
			
		||||
	// 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));
 | 
			
		||||
		qCDebug(logStatusNotifierItem) << "Received update for" << this->pStatus.toString() << value;
 | 
			
		||||
		this->pStatus.set(std::move(value));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	this->properties.setInterface(this->item);
 | 
			
		||||
| 
						 | 
				
			
			@ -94,21 +96,21 @@ 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 IconImageProvider::requestString(name, this->iconThemePath.get());
 | 
			
		||||
	if (this->pStatus.get() == "NeedsAttention") {
 | 
			
		||||
		auto name = this->pAttentionIconName.get();
 | 
			
		||||
		if (!name.isEmpty()) return IconImageProvider::requestString(name, this->pIconThemePath.get());
 | 
			
		||||
	} else {
 | 
			
		||||
		auto name = this->iconName.get();
 | 
			
		||||
		auto overlayName = this->overlayIconName.get();
 | 
			
		||||
		auto name = this->pIconName.get();
 | 
			
		||||
		auto overlayName = this->pOverlayIconName.get();
 | 
			
		||||
		if (!name.isEmpty() && overlayName.isEmpty())
 | 
			
		||||
			return IconImageProvider::requestString(name, this->iconThemePath.get());
 | 
			
		||||
			return IconImageProvider::requestString(name, this->pIconThemePath.get());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return this->imageHandle.url() + "/" + QString::number(this->iconIndex);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
 | 
			
		||||
	auto needsAttention = this->status.get() == "NeedsAttention";
 | 
			
		||||
	auto needsAttention = this->pStatus.get() == "NeedsAttention";
 | 
			
		||||
 | 
			
		||||
	auto closestPixmap = [](const QSize& size, const DBusSniIconPixmapList& pixmaps) {
 | 
			
		||||
		const DBusSniIconPixmap* ret = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -133,11 +135,11 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
 | 
			
		|||
 | 
			
		||||
	QPixmap pixmap;
 | 
			
		||||
	if (needsAttention) {
 | 
			
		||||
		if (!this->attentionIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->attentionIconName.get());
 | 
			
		||||
		if (!this->pAttentionIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->pAttentionIconName.get());
 | 
			
		||||
			pixmap = icon.pixmap(size.width(), size.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->attentionIconPixmaps.get());
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->pAttentionIconPixmaps.get());
 | 
			
		||||
 | 
			
		||||
			if (icon != nullptr) {
 | 
			
		||||
				const auto image =
 | 
			
		||||
| 
						 | 
				
			
			@ -147,11 +149,11 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (!this->iconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->iconName.get());
 | 
			
		||||
		if (!this->pIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->pIconName.get());
 | 
			
		||||
			pixmap = icon.pixmap(size.width(), size.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->iconPixmaps.get());
 | 
			
		||||
			const auto* icon = closestPixmap(size, this->pIconPixmaps.get());
 | 
			
		||||
 | 
			
		||||
			if (icon != nullptr) {
 | 
			
		||||
				const auto image =
 | 
			
		||||
| 
						 | 
				
			
			@ -162,11 +164,11 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		QPixmap overlay;
 | 
			
		||||
		if (!this->overlayIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->overlayIconName.get());
 | 
			
		||||
		if (!this->pOverlayIconName.get().isEmpty()) {
 | 
			
		||||
			auto icon = QIcon::fromTheme(this->pOverlayIconName.get());
 | 
			
		||||
			overlay = icon.pixmap(pixmap.width(), pixmap.height());
 | 
			
		||||
		} else {
 | 
			
		||||
			const auto* icon = closestPixmap(pixmap.size(), this->overlayIconPixmaps.get());
 | 
			
		||||
			const auto* icon = closestPixmap(pixmap.size(), this->pOverlayIconPixmaps.get());
 | 
			
		||||
 | 
			
		||||
			if (icon != nullptr) {
 | 
			
		||||
				const auto image =
 | 
			
		||||
| 
						 | 
				
			
			@ -225,7 +227,7 @@ void StatusNotifierItem::secondaryActivate() {
 | 
			
		|||
	QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::scroll(qint32 delta, bool horizontal) {
 | 
			
		||||
void StatusNotifierItem::scroll(qint32 delta, bool horizontal) const {
 | 
			
		||||
	this->item->Scroll(delta, horizontal ? "horizontal" : "vertical");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -235,11 +237,11 @@ void StatusNotifierItem::updateIcon() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
DBusMenuHandle* StatusNotifierItem::menuHandle() {
 | 
			
		||||
	return this->menuPath.get().path().isEmpty() ? nullptr : &this->mMenuHandle;
 | 
			
		||||
	return this->pMenuPath.get().path().isEmpty() ? nullptr : &this->mMenuHandle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::onMenuPathChanged() {
 | 
			
		||||
	this->mMenuHandle.setAddress(this->item->service(), this->menuPath.get().path());
 | 
			
		||||
	this->mMenuHandle.setAddress(this->item->service(), this->pMenuPath.get().path());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::onGetAllFinished() {
 | 
			
		||||
| 
						 | 
				
			
			@ -280,4 +282,73 @@ TrayImageHandle::requestPixmap(const QString& /*unused*/, QSize* size, const QSi
 | 
			
		|||
	return pixmap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString StatusNotifierItem::id() const { return this->pId.get(); }
 | 
			
		||||
QString StatusNotifierItem::title() const { return this->pTitle.get(); }
 | 
			
		||||
 | 
			
		||||
Status::Enum StatusNotifierItem::status() const {
 | 
			
		||||
	auto status = this->pStatus.get();
 | 
			
		||||
 | 
			
		||||
	if (status == "Passive") return Status::Passive;
 | 
			
		||||
	if (status == "Active") return Status::Active;
 | 
			
		||||
	if (status == "NeedsAttention") return Status::NeedsAttention;
 | 
			
		||||
 | 
			
		||||
	qCWarning(logStatusNotifierItem) << "Nonconformant StatusNotifierItem status" << status
 | 
			
		||||
	                                 << "returned for" << this->properties.toString();
 | 
			
		||||
 | 
			
		||||
	return Status::Passive;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Category::Enum StatusNotifierItem::category() const {
 | 
			
		||||
	auto category = this->pCategory.get();
 | 
			
		||||
 | 
			
		||||
	if (category == "ApplicationStatus") return Category::ApplicationStatus;
 | 
			
		||||
	if (category == "SystemServices") return Category::SystemServices;
 | 
			
		||||
	if (category == "Hardware") return Category::Hardware;
 | 
			
		||||
 | 
			
		||||
	qCWarning(logStatusNotifierItem) << "Nonconformant StatusNotifierItem category" << category
 | 
			
		||||
	                                 << "returned for" << this->properties.toString();
 | 
			
		||||
 | 
			
		||||
	return Category::ApplicationStatus;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString StatusNotifierItem::tooltipTitle() const { return this->pTooltip.get().title; }
 | 
			
		||||
QString StatusNotifierItem::tooltipDescription() const { return this->pTooltip.get().description; }
 | 
			
		||||
 | 
			
		||||
bool StatusNotifierItem::hasMenu() const { return !this->pMenuPath.get().path().isEmpty(); }
 | 
			
		||||
bool StatusNotifierItem::onlyMenu() const { return this->pIsMenu.get(); }
 | 
			
		||||
 | 
			
		||||
void StatusNotifierItem::display(QObject* parentWindow, qint32 relativeX, qint32 relativeY) {
 | 
			
		||||
	if (!this->menuHandle()) {
 | 
			
		||||
		qCritical() << "No menu present for" << this;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto* handle = this->menuHandle();
 | 
			
		||||
 | 
			
		||||
	auto onMenuChanged = [this, parentWindow, relativeX, relativeY, handle]() {
 | 
			
		||||
		QObject::disconnect(handle, nullptr, this, nullptr);
 | 
			
		||||
 | 
			
		||||
		if (!handle->menu()) {
 | 
			
		||||
			handle->unrefHandle();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto* platform = new PlatformMenuEntry(handle->menu());
 | 
			
		||||
 | 
			
		||||
		// clang-format off
 | 
			
		||||
		QObject::connect(platform, &PlatformMenuEntry::closed, this, [=]() { platform->deleteLater(); });
 | 
			
		||||
		QObject::connect(platform, &QObject::destroyed, this, [=]() { handle->unrefHandle(); });
 | 
			
		||||
		// clang-format on
 | 
			
		||||
 | 
			
		||||
		auto success = platform->display(parentWindow, relativeX, relativeY);
 | 
			
		||||
 | 
			
		||||
		// calls destroy which also unrefs
 | 
			
		||||
		if (!success) delete platform;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (handle->menu()) onMenuChanged();
 | 
			
		||||
	QObject::connect(handle, &DBusMenuHandle::menuChanged, this, onMenuChanged);
 | 
			
		||||
	handle->refHandle();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,41 @@ Q_DECLARE_LOGGING_CATEGORY(logStatusNotifierItem);
 | 
			
		|||
 | 
			
		||||
namespace qs::service::sni {
 | 
			
		||||
 | 
			
		||||
///! Status of a StatusNotifierItem.
 | 
			
		||||
/// See @@StatusNotifierItem.status.
 | 
			
		||||
namespace Status { // 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 Status
 | 
			
		||||
 | 
			
		||||
///! Category of a StatusNotifierItem.
 | 
			
		||||
/// See @@StatusNotifierItem.category.
 | 
			
		||||
namespace Category { // 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 Category
 | 
			
		||||
 | 
			
		||||
class StatusNotifierItem;
 | 
			
		||||
 | 
			
		||||
class TrayImageHandle: public QsImageHandle {
 | 
			
		||||
| 
						 | 
				
			
			@ -31,9 +66,35 @@ public:
 | 
			
		|||
	StatusNotifierItem* item;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///! 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 StatusNotifierItem: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
	/// A name unique to the application, such as its name.
 | 
			
		||||
	Q_PROPERTY(QString id READ id NOTIFY idChanged);
 | 
			
		||||
	/// Text that describes the application.
 | 
			
		||||
	Q_PROPERTY(QString title READ title NOTIFY titleChanged);
 | 
			
		||||
	Q_PROPERTY(qs::service::sni::Status::Enum status READ status NOTIFY statusChanged);
 | 
			
		||||
	Q_PROPERTY(qs::service::sni::Category::Enum category READ category NOTIFY categoryChanged);
 | 
			
		||||
	/// Icon source string, usable as an Image source.
 | 
			
		||||
	Q_PROPERTY(QString icon READ iconId NOTIFY iconChanged);
 | 
			
		||||
	Q_PROPERTY(QString tooltipTitle READ tooltipTitle NOTIFY tooltipTitleChanged);
 | 
			
		||||
	Q_PROPERTY(QString tooltipDescription READ tooltipDescription NOTIFY tooltipDescriptionChanged);
 | 
			
		||||
	/// If this tray item has an associated menu accessible via @@display() or @@menu.
 | 
			
		||||
	Q_PROPERTY(bool hasMenu READ hasMenu NOTIFY hasMenuChanged);
 | 
			
		||||
	/// A handle to the menu associated with this tray item, if any.
 | 
			
		||||
	///
 | 
			
		||||
	/// Can be displayed with @@Quickshell.QsMenuAnchor or @@Quickshell.QsMenuOpener.
 | 
			
		||||
	Q_PROPERTY(qs::dbus::dbusmenu::DBusMenuHandle* menu READ menuHandle NOTIFY hasMenuChanged);
 | 
			
		||||
	/// If this tray item only offers a menu and activation will do nothing.
 | 
			
		||||
	Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged);
 | 
			
		||||
	QML_NAMED_ELEMENT(SystemTrayItem);
 | 
			
		||||
	QML_UNCREATABLE("SystemTrayItems can only be acquired from SystemTray");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit StatusNotifierItem(const QString& address, QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,34 +105,37 @@ public:
 | 
			
		|||
 | 
			
		||||
	[[nodiscard]] dbus::dbusmenu::DBusMenuHandle* menuHandle();
 | 
			
		||||
 | 
			
		||||
	/// Primary activation action, generally triggered via a left click.
 | 
			
		||||
	void activate();
 | 
			
		||||
	/// Secondary activation action, generally triggered via a middle click.
 | 
			
		||||
	void secondaryActivate();
 | 
			
		||||
	void scroll(qint32 delta, bool horizontal);
 | 
			
		||||
	/// Scroll action, such as changing volume on a mixer.
 | 
			
		||||
	void scroll(qint32 delta, bool horizontal) const;
 | 
			
		||||
	/// Display a platform menu at the given location relative to the parent window.
 | 
			
		||||
	void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
 | 
			
		||||
 | 
			
		||||
	// 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", "", false};
 | 
			
		||||
	dbus::DBusProperty<QString> iconName {this->properties, "IconName", "", false}; // IconPixmap may be set
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> iconPixmaps {this->properties, "IconPixmap", {}, false}; // IconName may be set
 | 
			
		||||
	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", "", false};
 | 
			
		||||
	dbus::DBusProperty<DBusSniTooltip> tooltip {this->properties, "ToolTip"};
 | 
			
		||||
	dbus::DBusProperty<bool> isMenu {this->properties, "ItemIsMenu"};
 | 
			
		||||
	dbus::DBusProperty<QDBusObjectPath> menuPath {this->properties, "Menu"};
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	[[nodiscard]] QString id() const;
 | 
			
		||||
	[[nodiscard]] QString title() const;
 | 
			
		||||
	[[nodiscard]] Status::Enum status() const;
 | 
			
		||||
	[[nodiscard]] Category::Enum category() const;
 | 
			
		||||
	[[nodiscard]] QString tooltipTitle() const;
 | 
			
		||||
	[[nodiscard]] QString tooltipDescription() const;
 | 
			
		||||
	[[nodiscard]] bool hasMenu() const;
 | 
			
		||||
	[[nodiscard]] bool onlyMenu() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void ready();
 | 
			
		||||
 | 
			
		||||
	void idChanged();
 | 
			
		||||
	void titleChanged();
 | 
			
		||||
	void statusChanged();
 | 
			
		||||
	void categoryChanged();
 | 
			
		||||
	void tooltipTitleChanged();
 | 
			
		||||
	void tooltipDescriptionChanged();
 | 
			
		||||
	void hasMenuChanged();
 | 
			
		||||
	void onlyMenuChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void updateIcon();
 | 
			
		||||
	void onGetAllFinished();
 | 
			
		||||
| 
						 | 
				
			
			@ -90,6 +154,26 @@ private:
 | 
			
		|||
	// bumped to inhibit caching
 | 
			
		||||
	quint32 iconIndex = 0;
 | 
			
		||||
	QString watcherId;
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	dbus::DBusPropertyGroup properties;
 | 
			
		||||
	dbus::DBusProperty<QString> pId {this->properties, "Id"};
 | 
			
		||||
	dbus::DBusProperty<QString> pTitle {this->properties, "Title"};
 | 
			
		||||
	dbus::DBusProperty<QString> pStatus {this->properties, "Status"};
 | 
			
		||||
	dbus::DBusProperty<QString> pCategory {this->properties, "Category"};
 | 
			
		||||
	dbus::DBusProperty<quint32> pWindowId {this->properties, "WindowId"};
 | 
			
		||||
	dbus::DBusProperty<QString> pIconThemePath {this->properties, "IconThemePath", "", false};
 | 
			
		||||
	dbus::DBusProperty<QString> pIconName {this->properties, "IconName", "", false}; // IconPixmap may be set
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> pIconPixmaps {this->properties, "IconPixmap", {}, false}; // IconName may be set
 | 
			
		||||
	dbus::DBusProperty<QString> pOverlayIconName {this->properties, "OverlayIconName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> pOverlayIconPixmaps {this->properties, "OverlayIconPixmap"};
 | 
			
		||||
	dbus::DBusProperty<QString> pAttentionIconName {this->properties, "AttentionIconName"};
 | 
			
		||||
	dbus::DBusProperty<DBusSniIconPixmapList> pAttentionIconPixmaps {this->properties, "AttentionIconPixmap"};
 | 
			
		||||
	dbus::DBusProperty<QString> pAttentionMovieName {this->properties, "AttentionMovieName", "", false};
 | 
			
		||||
	dbus::DBusProperty<DBusSniTooltip> pTooltip {this->properties, "ToolTip"};
 | 
			
		||||
	dbus::DBusProperty<bool> pIsMenu {this->properties, "ItemIsMenu"};
 | 
			
		||||
	dbus::DBusProperty<QDBusObjectPath> pMenuPath {this->properties, "Menu"};
 | 
			
		||||
	// clang-format on
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::service::sni
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,150 +1,12 @@
 | 
			
		|||
#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 "../../core/model.hpp"
 | 
			
		||||
#include "../../core/platformmenu.hpp"
 | 
			
		||||
#include "../../dbus/dbusmenu/dbusmenu.hpp"
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "host.hpp"
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::dbus;
 | 
			
		||||
using namespace qs::dbus::dbusmenu;
 | 
			
		||||
using namespace qs::service::sni;
 | 
			
		||||
using namespace qs::menu::platform;
 | 
			
		||||
 | 
			
		||||
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->menuPath, &AbstractDBusProperty::changed, this, &SystemTrayItem::hasMenuChanged);
 | 
			
		||||
	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::hasMenu() const {
 | 
			
		||||
	if (this->item == nullptr) return false;
 | 
			
		||||
	return !this->item->menuPath.get().path().isEmpty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DBusMenuHandle* SystemTrayItem::menu() const {
 | 
			
		||||
	if (this->item == nullptr) return nullptr;
 | 
			
		||||
	return this->item->menuHandle();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SystemTrayItem::onlyMenu() const {
 | 
			
		||||
	if (this->item == nullptr) return false;
 | 
			
		||||
	return this->item->isMenu.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTrayItem::activate() const { this->item->activate(); }
 | 
			
		||||
void SystemTrayItem::secondaryActivate() const { this->item->secondaryActivate(); }
 | 
			
		||||
 | 
			
		||||
void SystemTrayItem::scroll(qint32 delta, bool horizontal) const {
 | 
			
		||||
	this->item->scroll(delta, horizontal);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTrayItem::display(QObject* parentWindow, qint32 relativeX, qint32 relativeY) {
 | 
			
		||||
	if (!this->item->menuHandle()) {
 | 
			
		||||
		qCritical() << "No menu present for" << this;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto* handle = this->item->menuHandle();
 | 
			
		||||
 | 
			
		||||
	auto onMenuChanged = [this, parentWindow, relativeX, relativeY, handle]() {
 | 
			
		||||
		QObject::disconnect(handle, nullptr, this, nullptr);
 | 
			
		||||
 | 
			
		||||
		if (!handle->menu()) {
 | 
			
		||||
			handle->unrefHandle();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto* platform = new PlatformMenuEntry(handle->menu());
 | 
			
		||||
 | 
			
		||||
		// clang-format off
 | 
			
		||||
		QObject::connect(platform, &PlatformMenuEntry::closed, this, [=]() { platform->deleteLater(); });
 | 
			
		||||
		QObject::connect(platform, &QObject::destroyed, this, [=]() { handle->unrefHandle(); });
 | 
			
		||||
		// clang-format on
 | 
			
		||||
 | 
			
		||||
		auto success = platform->display(parentWindow, relativeX, relativeY);
 | 
			
		||||
 | 
			
		||||
		// calls destroy which also unrefs
 | 
			
		||||
		if (!success) delete platform;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (handle->menu()) onMenuChanged();
 | 
			
		||||
	QObject::connect(handle, &DBusMenuHandle::menuChanged, this, onMenuChanged);
 | 
			
		||||
	handle->refHandle();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SystemTray::SystemTray(QObject* parent): QObject(parent) {
 | 
			
		||||
	auto* host = StatusNotifierHost::instance();
 | 
			
		||||
| 
						 | 
				
			
			@ -155,71 +17,10 @@ SystemTray::SystemTray(QObject* parent): QObject(parent) {
 | 
			
		|||
	// clang-format on
 | 
			
		||||
 | 
			
		||||
	for (auto* item: host->items()) {
 | 
			
		||||
		this->mItems.insertObject(new SystemTrayItem(item, this));
 | 
			
		||||
		this->mItems.insertObject(item);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTray::onItemRegistered(StatusNotifierItem* item) {
 | 
			
		||||
	this->mItems.insertObject(new SystemTrayItem(item, this));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTray::onItemUnregistered(StatusNotifierItem* item) {
 | 
			
		||||
	for (const auto* storedItem: this->mItems.valueList()) {
 | 
			
		||||
		if (storedItem->item == item) {
 | 
			
		||||
			this->mItems.removeObject(storedItem);
 | 
			
		||||
			delete storedItem;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ObjectModel<SystemTrayItem>* SystemTray::items() { return &this->mItems; }
 | 
			
		||||
 | 
			
		||||
SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; }
 | 
			
		||||
 | 
			
		||||
SystemTrayMenuWatcher::~SystemTrayMenuWatcher() {
 | 
			
		||||
	if (this->item != nullptr) {
 | 
			
		||||
		this->item->item->menuHandle()->unrefHandle();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTrayMenuWatcher::setTrayItem(SystemTrayItem* item) {
 | 
			
		||||
	if (item == this->item) return;
 | 
			
		||||
 | 
			
		||||
	if (this->item != nullptr) {
 | 
			
		||||
		this->item->item->menuHandle()->unrefHandle();
 | 
			
		||||
		QObject::disconnect(this->item, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->item = item;
 | 
			
		||||
 | 
			
		||||
	if (item != nullptr) {
 | 
			
		||||
		this->item->item->menuHandle()->refHandle();
 | 
			
		||||
 | 
			
		||||
		QObject::connect(item, &QObject::destroyed, this, &SystemTrayMenuWatcher::onItemDestroyed);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    item->item->menuHandle(),
 | 
			
		||||
		    &DBusMenuHandle::menuChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &SystemTrayMenuWatcher::menuChanged
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit this->trayItemChanged();
 | 
			
		||||
	emit this->menuChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DBusMenuItem* SystemTrayMenuWatcher::menu() const {
 | 
			
		||||
	if (this->item) {
 | 
			
		||||
		return static_cast<DBusMenuItem*>(this->item->item->menuHandle()->menu()); // NOLINT
 | 
			
		||||
	} else {
 | 
			
		||||
		return nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SystemTrayMenuWatcher::onItemDestroyed() {
 | 
			
		||||
	this->item = nullptr;
 | 
			
		||||
	emit this->trayItemChanged();
 | 
			
		||||
	emit this->menuChanged();
 | 
			
		||||
}
 | 
			
		||||
void SystemTray::onItemRegistered(StatusNotifierItem* item) { this->mItems.insertObject(item); }
 | 
			
		||||
void SystemTray::onItemUnregistered(StatusNotifierItem* item) { this->mItems.removeObject(item); }
 | 
			
		||||
ObjectModel<StatusNotifierItem>* SystemTray::items() { return &this->mItems; }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,116 +9,6 @@
 | 
			
		|||
#include "../../core/model.hpp"
 | 
			
		||||
#include "item.hpp"
 | 
			
		||||
 | 
			
		||||
///! Statis of a SystemTrayItem.
 | 
			
		||||
/// See @@SystemTrayItem.status.
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
///! Category of a SystemTrayItem.
 | 
			
		||||
/// See @@SystemTrayItem.category.
 | 
			
		||||
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 {
 | 
			
		||||
	using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem;
 | 
			
		||||
 | 
			
		||||
	// intentionally wrongly aliased to temporarily hack around a docgen issue
 | 
			
		||||
	using QsMenuHandle = qs::dbus::dbusmenu::DBusMenuHandle;
 | 
			
		||||
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// A name unique to the application, such as its name.
 | 
			
		||||
	Q_PROPERTY(QString id READ id NOTIFY idChanged);
 | 
			
		||||
	/// Text 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 has an associated menu accessible via @@display() or @@menu.
 | 
			
		||||
	Q_PROPERTY(bool hasMenu READ hasMenu NOTIFY hasMenuChanged);
 | 
			
		||||
	/// A handle to the menu associated with this tray item, if any.
 | 
			
		||||
	///
 | 
			
		||||
	/// Can be displayed with @@Quickshell.QsMenuAnchor or @@Quickshell.QsMenuOpener.
 | 
			
		||||
	Q_PROPERTY(qs::menu::QsMenuHandle* menu READ menu NOTIFY hasMenuChanged);
 | 
			
		||||
	/// If this tray item only offers a menu and activation will do nothing.
 | 
			
		||||
	Q_PROPERTY(bool onlyMenu READ onlyMenu NOTIFY onlyMenuChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_UNCREATABLE("SystemTrayItems can only be acquired from SystemTray");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	/// Primary activation action, generally triggered via a left click.
 | 
			
		||||
	Q_INVOKABLE void activate() const;
 | 
			
		||||
 | 
			
		||||
	/// Secondary activation action, generally triggered via a middle click.
 | 
			
		||||
	Q_INVOKABLE void secondaryActivate() const;
 | 
			
		||||
 | 
			
		||||
	/// Scroll action, such as changing volume on a mixer.
 | 
			
		||||
	Q_INVOKABLE void scroll(qint32 delta, bool horizontal) const;
 | 
			
		||||
 | 
			
		||||
	/// Display a platform menu at the given location relative to the parent window.
 | 
			
		||||
	Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
 | 
			
		||||
 | 
			
		||||
	[[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 hasMenu() const;
 | 
			
		||||
	[[nodiscard]] QsMenuHandle* menu() const;
 | 
			
		||||
	[[nodiscard]] bool onlyMenu() const;
 | 
			
		||||
 | 
			
		||||
	qs::service::sni::StatusNotifierItem* item = nullptr;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void idChanged();
 | 
			
		||||
	void titleChanged();
 | 
			
		||||
	void statusChanged();
 | 
			
		||||
	void categoryChanged();
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void tooltipTitleChanged();
 | 
			
		||||
	void tooltipDescriptionChanged();
 | 
			
		||||
	void hasMenuChanged();
 | 
			
		||||
	void onlyMenuChanged();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///! System tray
 | 
			
		||||
/// Referencing the SystemTray singleton will make quickshell start tracking
 | 
			
		||||
/// system tray contents, which are updated as the tray changes, and can be
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +16,7 @@ signals:
 | 
			
		|||
class SystemTray: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// List of all system tray icons.
 | 
			
		||||
	QSDOC_TYPE_OVERRIDE(ObjectModel<SystemTrayItem>*);
 | 
			
		||||
	QSDOC_TYPE_OVERRIDE(ObjectModel<qs::service::sni::StatusNotifierItem>*);
 | 
			
		||||
	Q_PROPERTY(UntypedObjectModel* items READ items CONSTANT);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_SINGLETON;
 | 
			
		||||
| 
						 | 
				
			
			@ -134,51 +24,12 @@ class SystemTray: public QObject {
 | 
			
		|||
public:
 | 
			
		||||
	explicit SystemTray(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] ObjectModel<SystemTrayItem>* items();
 | 
			
		||||
	[[nodiscard]] ObjectModel<qs::service::sni::StatusNotifierItem>* items();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onItemRegistered(qs::service::sni::StatusNotifierItem* item);
 | 
			
		||||
	void onItemUnregistered(qs::service::sni::StatusNotifierItem* item);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	ObjectModel<SystemTrayItem> mItems {this};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///! Accessor for SystemTrayItem menus.
 | 
			
		||||
/// > [!ERROR] Deprecated in favor of @@Quickshell.QsMenuOpener.menu,
 | 
			
		||||
/// > which now supports directly accessing a tray menu via @@SystemTrayItem.menu.
 | 
			
		||||
///
 | 
			
		||||
/// SystemTrayMenuWatcher provides access to the associated
 | 
			
		||||
/// @@Quickshell.DBusMenu.DBusMenuItem for a tray item.
 | 
			
		||||
class SystemTrayMenuWatcher: public QObject {
 | 
			
		||||
	using DBusMenu = qs::dbus::dbusmenu::DBusMenu;
 | 
			
		||||
	using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem;
 | 
			
		||||
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// The tray item to watch.
 | 
			
		||||
	Q_PROPERTY(SystemTrayItem* trayItem READ trayItem WRITE setTrayItem NOTIFY trayItemChanged);
 | 
			
		||||
	/// The menu associated with the tray item. Will be null if @@trayItem is null
 | 
			
		||||
	/// or has no associated menu.
 | 
			
		||||
	Q_PROPERTY(DBusMenuItem* menu READ menu NOTIFY menuChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit SystemTrayMenuWatcher(QObject* parent = nullptr): QObject(parent) {}
 | 
			
		||||
	~SystemTrayMenuWatcher() override;
 | 
			
		||||
	Q_DISABLE_COPY_MOVE(SystemTrayMenuWatcher);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] SystemTrayItem* trayItem() const;
 | 
			
		||||
	void setTrayItem(SystemTrayItem* item);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] DBusMenuItem* menu() const;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void trayItemChanged();
 | 
			
		||||
	void menuChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onItemDestroyed();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	SystemTrayItem* item = nullptr;
 | 
			
		||||
	ObjectModel<qs::service::sni::StatusNotifierItem> mItems {this};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue