forked from quickshell/quickshell
service/tray!: redesign menus / dbusmenu and add native menu support
Reworks dbusmenu menus to be displayable with a system context menu. Breaks the entire DBusMenu api.
This commit is contained in:
parent
c31bbea837
commit
ec362637b8
18 changed files with 898 additions and 191 deletions
|
@ -75,6 +75,7 @@ StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent)
|
|||
QObject::connect(&this->overlayIconPixmaps, &AbstractDBusProperty::changed, this, &StatusNotifierItem::updateIcon);
|
||||
|
||||
QObject::connect(&this->properties, &DBusPropertyGroup::getAllFinished, this, &StatusNotifierItem::onGetAllFinished);
|
||||
QObject::connect(&this->menuPath, &AbstractDBusProperty::changed, this, &StatusNotifierItem::onMenuPathChanged);
|
||||
// clang-format on
|
||||
|
||||
QObject::connect(this->item, &DBusStatusNotifierItem::NewStatus, this, [this](QString value) {
|
||||
|
@ -230,13 +231,41 @@ void StatusNotifierItem::updateIcon() {
|
|||
emit this->iconChanged();
|
||||
}
|
||||
|
||||
DBusMenu* StatusNotifierItem::createMenu() const {
|
||||
auto path = this->menuPath.get().path();
|
||||
if (!path.isEmpty()) {
|
||||
return new DBusMenu(this->item->service(), this->menuPath.get().path());
|
||||
DBusMenu* StatusNotifierItem::menu() const { return this->mMenu; }
|
||||
|
||||
void StatusNotifierItem::refMenu() {
|
||||
this->menuRefcount++;
|
||||
|
||||
if (this->menuRefcount == 1) {
|
||||
this->onMenuPathChanged();
|
||||
} else {
|
||||
// Refresh the layout when opening a menu in case a bad client isn't updating it
|
||||
// and another ref is open somewhere.
|
||||
this->mMenu->rootItem.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusNotifierItem::unrefMenu() {
|
||||
this->menuRefcount--;
|
||||
|
||||
if (this->menuRefcount == 0) {
|
||||
this->onMenuPathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusNotifierItem::onMenuPathChanged() {
|
||||
if (this->mMenu) {
|
||||
this->mMenu->deleteLater();
|
||||
this->mMenu = nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (this->menuRefcount > 0 && !this->menuPath.get().path().isEmpty()) {
|
||||
this->mMenu = new DBusMenu(this->item->service(), this->menuPath.get().path());
|
||||
this->mMenu->setParent(this);
|
||||
this->mMenu->rootItem.setShowChildrenRecursive(true);
|
||||
}
|
||||
|
||||
emit this->menuChanged();
|
||||
}
|
||||
|
||||
void StatusNotifierItem::onGetAllFinished() {
|
||||
|
|
|
@ -41,7 +41,10 @@ public:
|
|||
[[nodiscard]] bool isReady() const;
|
||||
[[nodiscard]] QString iconId() const;
|
||||
[[nodiscard]] QPixmap createPixmap(const QSize& size) const;
|
||||
[[nodiscard]] qs::dbus::dbusmenu::DBusMenu* createMenu() const;
|
||||
|
||||
[[nodiscard]] qs::dbus::dbusmenu::DBusMenu* menu() const;
|
||||
void refMenu();
|
||||
void unrefMenu();
|
||||
|
||||
void activate();
|
||||
void secondaryActivate();
|
||||
|
@ -70,16 +73,22 @@ public:
|
|||
signals:
|
||||
void iconChanged();
|
||||
void ready();
|
||||
void menuChanged();
|
||||
|
||||
private slots:
|
||||
void updateIcon();
|
||||
void onGetAllFinished();
|
||||
void onMenuPathChanged();
|
||||
|
||||
private:
|
||||
void updateMenuState();
|
||||
|
||||
DBusStatusNotifierItem* item = nullptr;
|
||||
TrayImageHandle imageHandle {this};
|
||||
bool mReady = false;
|
||||
|
||||
dbus::dbusmenu::DBusMenu* mMenu = nullptr;
|
||||
quint32 menuRefcount = 0;
|
||||
|
||||
// bumped to inhibit caching
|
||||
quint32 iconIndex = 0;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/model.hpp"
|
||||
#include "../../core/platformmenu.hpp"
|
||||
#include "../../dbus/dbusmenu/dbusmenu.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "host.hpp"
|
||||
|
@ -18,6 +19,7 @@
|
|||
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)
|
||||
|
@ -30,6 +32,7 @@ SystemTrayItem::SystemTrayItem(qs::service::sni::StatusNotifierItem* item, QObje
|
|||
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
|
||||
}
|
||||
|
@ -87,6 +90,11 @@ QString SystemTrayItem::tooltipDescription() const {
|
|||
return this->item->tooltip.get().description;
|
||||
}
|
||||
|
||||
bool SystemTrayItem::hasMenu() const {
|
||||
if (this->item == nullptr) return false;
|
||||
return !this->item->menuPath.get().path().isEmpty();
|
||||
}
|
||||
|
||||
bool SystemTrayItem::onlyMenu() const {
|
||||
if (this->item == nullptr) return false;
|
||||
return this->item->isMenu.get();
|
||||
|
@ -94,10 +102,27 @@ bool SystemTrayItem::onlyMenu() const {
|
|||
|
||||
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) {
|
||||
this->item->refMenu();
|
||||
auto* platform = new PlatformMenuEntry(&this->item->menu()->rootItem);
|
||||
|
||||
QObject::connect(&this->item->menu()->rootItem, &DBusMenuItem::layoutUpdated, platform, [=]() {
|
||||
platform->relayout();
|
||||
auto success = platform->display(parentWindow, relativeX, relativeY);
|
||||
|
||||
// calls destroy which also unrefs
|
||||
if (!success) delete platform;
|
||||
});
|
||||
|
||||
QObject::connect(platform, &PlatformMenuEntry::closed, this, [=]() { platform->deleteLater(); });
|
||||
QObject::connect(platform, &QObject::destroyed, this, [this]() { this->item->unrefMenu(); });
|
||||
}
|
||||
|
||||
SystemTray::SystemTray(QObject* parent): QObject(parent) {
|
||||
auto* host = StatusNotifierHost::instance();
|
||||
|
||||
|
@ -129,46 +154,45 @@ ObjectModel<SystemTrayItem>* SystemTray::items() { return &this->mItems; }
|
|||
|
||||
SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; }
|
||||
|
||||
SystemTrayMenuWatcher::~SystemTrayMenuWatcher() {
|
||||
if (this->item != nullptr) {
|
||||
this->item->item->unrefMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void SystemTrayMenuWatcher::setTrayItem(SystemTrayItem* item) {
|
||||
if (item == this->item) return;
|
||||
|
||||
if (this->item != nullptr) {
|
||||
this->item->item->unrefMenu();
|
||||
QObject::disconnect(this->item, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->item = item;
|
||||
|
||||
if (item != nullptr) {
|
||||
this->item->item->refMenu();
|
||||
|
||||
QObject::connect(item, &QObject::destroyed, this, &SystemTrayMenuWatcher::onItemDestroyed);
|
||||
|
||||
QObject::connect(
|
||||
&item->item->menuPath,
|
||||
&AbstractDBusProperty::changed,
|
||||
item->item,
|
||||
&StatusNotifierItem::menuChanged,
|
||||
this,
|
||||
&SystemTrayMenuWatcher::onMenuPathChanged
|
||||
&SystemTrayMenuWatcher::menuChanged
|
||||
);
|
||||
}
|
||||
|
||||
this->onMenuPathChanged();
|
||||
emit this->trayItemChanged();
|
||||
emit this->menuChanged();
|
||||
}
|
||||
|
||||
DBusMenuItem* SystemTrayMenuWatcher::menu() const {
|
||||
if (this->mMenu == nullptr) return nullptr;
|
||||
return &this->mMenu->rootItem;
|
||||
return this->item ? &this->item->item->menu()->rootItem : nullptr;
|
||||
}
|
||||
|
||||
void SystemTrayMenuWatcher::onItemDestroyed() {
|
||||
this->item = nullptr;
|
||||
this->onMenuPathChanged();
|
||||
emit this->trayItemChanged();
|
||||
}
|
||||
|
||||
void SystemTrayMenuWatcher::onMenuPathChanged() {
|
||||
if (this->mMenu != nullptr) {
|
||||
this->mMenu->deleteLater();
|
||||
}
|
||||
|
||||
this->mMenu = this->item == nullptr ? nullptr : this->item->item->createMenu();
|
||||
emit this->menuChanged();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../../core/model.hpp"
|
||||
|
@ -44,8 +45,6 @@ Q_ENUM_NS(Enum);
|
|||
/// 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).
|
||||
///
|
||||
/// The associated context menu can be retrieved using a [SystemTrayMenuWatcher](../systemtraymenuwatcher).
|
||||
///
|
||||
/// [kde/freedesktop spec]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/
|
||||
class SystemTrayItem: public QObject {
|
||||
using DBusMenuItem = qs::dbus::dbusmenu::DBusMenuItem;
|
||||
|
@ -61,6 +60,9 @@ class SystemTrayItem: public QObject {
|
|||
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 a [SystemTrayMenuWatcher](../systemtraymenuwatcher).
|
||||
Q_PROPERTY(bool hasMenu READ hasMenu 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;
|
||||
|
@ -78,6 +80,9 @@ public:
|
|||
/// 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;
|
||||
|
@ -85,6 +90,7 @@ public:
|
|||
[[nodiscard]] QString icon() const;
|
||||
[[nodiscard]] QString tooltipTitle() const;
|
||||
[[nodiscard]] QString tooltipDescription() const;
|
||||
[[nodiscard]] bool hasMenu() const;
|
||||
[[nodiscard]] bool onlyMenu() const;
|
||||
|
||||
qs::service::sni::StatusNotifierItem* item = nullptr;
|
||||
|
@ -97,6 +103,7 @@ signals:
|
|||
void iconChanged();
|
||||
void tooltipTitleChanged();
|
||||
void tooltipDescriptionChanged();
|
||||
void hasMenuChanged();
|
||||
void onlyMenuChanged();
|
||||
};
|
||||
|
||||
|
@ -141,6 +148,8 @@ class SystemTrayMenuWatcher: public QObject {
|
|||
|
||||
public:
|
||||
explicit SystemTrayMenuWatcher(QObject* parent = nullptr): QObject(parent) {}
|
||||
~SystemTrayMenuWatcher() override;
|
||||
Q_DISABLE_COPY_MOVE(SystemTrayMenuWatcher);
|
||||
|
||||
[[nodiscard]] SystemTrayItem* trayItem() const;
|
||||
void setTrayItem(SystemTrayItem* item);
|
||||
|
@ -148,14 +157,12 @@ public:
|
|||
[[nodiscard]] DBusMenuItem* menu() const;
|
||||
|
||||
signals:
|
||||
void menuChanged();
|
||||
void trayItemChanged();
|
||||
void menuChanged();
|
||||
|
||||
private slots:
|
||||
void onItemDestroyed();
|
||||
void onMenuPathChanged();
|
||||
|
||||
private:
|
||||
SystemTrayItem* item = nullptr;
|
||||
DBusMenu* mMenu = nullptr;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue