From ac50767873457b511ba66fbef316bb2a42201f1b Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Thu, 21 Nov 2024 04:59:48 -0800 Subject: [PATCH] service/tray!: refactor qml bindings to StatusNotifierItem Breaking: Dropped SystemTrayMenuWatcher. --- src/services/status_notifier/item.cpp | 151 ++++++++++++++----- src/services/status_notifier/item.hpp | 124 ++++++++++++--- src/services/status_notifier/qml.cpp | 207 +------------------------- src/services/status_notifier/qml.hpp | 155 +------------------ 4 files changed, 222 insertions(+), 415 deletions(-) diff --git a/src/services/status_notifier/item.cpp b/src/services/status_notifier/item.cpp index e3bb0120..df898312 100644 --- a/src/services/status_notifier/item.cpp +++ b/src/services/status_notifier/item.cpp @@ -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 diff --git a/src/services/status_notifier/item.hpp b/src/services/status_notifier/item.hpp index eccf79b0..4126424f 100644 --- a/src/services/status_notifier/item.hpp +++ b/src/services/status_notifier/item.hpp @@ -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 id {this->properties, "Id"}; - dbus::DBusProperty title {this->properties, "Title"}; - dbus::DBusProperty status {this->properties, "Status"}; - dbus::DBusProperty category {this->properties, "Category"}; - dbus::DBusProperty windowId {this->properties, "WindowId"}; - dbus::DBusProperty iconThemePath {this->properties, "IconThemePath", "", false}; - dbus::DBusProperty iconName {this->properties, "IconName", "", false}; // IconPixmap may be set - dbus::DBusProperty iconPixmaps {this->properties, "IconPixmap", {}, false}; // IconName may be set - dbus::DBusProperty overlayIconName {this->properties, "OverlayIconName"}; - dbus::DBusProperty overlayIconPixmaps {this->properties, "OverlayIconPixmap"}; - dbus::DBusProperty attentionIconName {this->properties, "AttentionIconName"}; - dbus::DBusProperty attentionIconPixmaps {this->properties, "AttentionIconPixmap"}; - dbus::DBusProperty attentionMovieName {this->properties, "AttentionMovieName", "", false}; - dbus::DBusProperty tooltip {this->properties, "ToolTip"}; - dbus::DBusProperty isMenu {this->properties, "ItemIsMenu"}; - dbus::DBusProperty 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 pId {this->properties, "Id"}; + dbus::DBusProperty pTitle {this->properties, "Title"}; + dbus::DBusProperty pStatus {this->properties, "Status"}; + dbus::DBusProperty pCategory {this->properties, "Category"}; + dbus::DBusProperty pWindowId {this->properties, "WindowId"}; + dbus::DBusProperty pIconThemePath {this->properties, "IconThemePath", "", false}; + dbus::DBusProperty pIconName {this->properties, "IconName", "", false}; // IconPixmap may be set + dbus::DBusProperty pIconPixmaps {this->properties, "IconPixmap", {}, false}; // IconName may be set + dbus::DBusProperty pOverlayIconName {this->properties, "OverlayIconName"}; + dbus::DBusProperty pOverlayIconPixmaps {this->properties, "OverlayIconPixmap"}; + dbus::DBusProperty pAttentionIconName {this->properties, "AttentionIconName"}; + dbus::DBusProperty pAttentionIconPixmaps {this->properties, "AttentionIconPixmap"}; + dbus::DBusProperty pAttentionMovieName {this->properties, "AttentionMovieName", "", false}; + dbus::DBusProperty pTooltip {this->properties, "ToolTip"}; + dbus::DBusProperty pIsMenu {this->properties, "ItemIsMenu"}; + dbus::DBusProperty pMenuPath {this->properties, "Menu"}; + // clang-format on }; } // namespace qs::service::sni diff --git a/src/services/status_notifier/qml.cpp b/src/services/status_notifier/qml.cpp index d39963f4..0c980f16 100644 --- a/src/services/status_notifier/qml.cpp +++ b/src/services/status_notifier/qml.cpp @@ -1,150 +1,12 @@ #include "qml.hpp" -#include -#include -#include -#include #include -#include -#include -#include #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* 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(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* SystemTray::items() { return &this->mItems; } diff --git a/src/services/status_notifier/qml.hpp b/src/services/status_notifier/qml.hpp index 98a647fa..d77279dc 100644 --- a/src/services/status_notifier/qml.hpp +++ b/src/services/status_notifier/qml.hpp @@ -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*); + QSDOC_TYPE_OVERRIDE(ObjectModel*); 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* items(); + [[nodiscard]] ObjectModel* items(); private slots: void onItemRegistered(qs::service::sni::StatusNotifierItem* item); void onItemUnregistered(qs::service::sni::StatusNotifierItem* item); private: - ObjectModel 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 mItems {this}; };