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
				
			
		| 
						 | 
				
			
			@ -30,6 +30,8 @@ qt_add_library(quickshell-core STATIC
 | 
			
		|||
	elapsedtimer.cpp
 | 
			
		||||
	desktopentry.cpp
 | 
			
		||||
	objectrepeater.cpp
 | 
			
		||||
	platformmenu.cpp
 | 
			
		||||
	qsmenu.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,12 +8,17 @@
 | 
			
		|||
#include <qfileinfo.h>
 | 
			
		||||
#include <qfilesystemwatcher.h>
 | 
			
		||||
#include <qhash.h>
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qiconengine.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qloggingcategory.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpixmap.h>
 | 
			
		||||
#include <qqmlcontext.h>
 | 
			
		||||
#include <qqmlengine.h>
 | 
			
		||||
#include <qqmlincubator.h>
 | 
			
		||||
#include <qquickimageprovider.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "iconimageprovider.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -308,6 +313,90 @@ EngineGeneration* EngineGeneration::currentGeneration() {
 | 
			
		|||
	} else return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
 | 
			
		||||
class PixmapCacheIconEngine: public QIconEngine {
 | 
			
		||||
	void paint(
 | 
			
		||||
	    QPainter* /*unused*/,
 | 
			
		||||
	    const QRect& /*unused*/,
 | 
			
		||||
	    QIcon::Mode /*unused*/,
 | 
			
		||||
	    QIcon::State /*unused*/
 | 
			
		||||
	) override {
 | 
			
		||||
		qFatal(
 | 
			
		||||
		) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
 | 
			
		||||
		if (this->lastPixmap.isNull() || size != this->lastSize) {
 | 
			
		||||
			this->lastPixmap = this->createPixmap(size);
 | 
			
		||||
			this->lastSize = size;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this->lastPixmap;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	virtual QPixmap createPixmap(const QSize& size) = 0;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QSize lastSize;
 | 
			
		||||
	QPixmap lastPixmap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ImageProviderIconEngine: public PixmapCacheIconEngine {
 | 
			
		||||
public:
 | 
			
		||||
	explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id)
 | 
			
		||||
	    : provider(provider)
 | 
			
		||||
	    , id(std::move(id)) {}
 | 
			
		||||
 | 
			
		||||
	QPixmap createPixmap(const QSize& size) override {
 | 
			
		||||
		if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) {
 | 
			
		||||
			return this->provider->requestPixmap(this->id, nullptr, size);
 | 
			
		||||
		} else if (this->provider->imageType() == QQmlImageProviderBase::Image) {
 | 
			
		||||
			auto image = this->provider->requestImage(this->id, nullptr, size);
 | 
			
		||||
			return QPixmap::fromImage(image);
 | 
			
		||||
		} else {
 | 
			
		||||
			qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType();
 | 
			
		||||
			return QPixmap(); // never reached, satisfies lint
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QIconEngine* clone() const override {
 | 
			
		||||
		return new ImageProviderIconEngine(this->provider, this->id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QQuickImageProvider* provider;
 | 
			
		||||
	QString id;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
QIcon EngineGeneration::iconByUrl(const QUrl& url) const {
 | 
			
		||||
	if (url.isEmpty()) return QIcon();
 | 
			
		||||
 | 
			
		||||
	auto scheme = url.scheme();
 | 
			
		||||
	if (scheme == "image") {
 | 
			
		||||
		auto providerName = url.authority();
 | 
			
		||||
		auto path = url.path();
 | 
			
		||||
		if (!path.isEmpty()) path = path.sliced(1);
 | 
			
		||||
 | 
			
		||||
		auto* provider = qobject_cast<QQuickImageProvider*>(this->engine->imageProvider(providerName));
 | 
			
		||||
 | 
			
		||||
		if (provider == nullptr) {
 | 
			
		||||
			qWarning() << "iconByUrl failed: no provider found for" << url;
 | 
			
		||||
			return QIcon();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (provider->imageType() == QQmlImageProviderBase::Pixmap
 | 
			
		||||
		    || provider->imageType() == QQmlImageProviderBase::Image)
 | 
			
		||||
		{
 | 
			
		||||
			return QIcon(new ImageProviderIconEngine(provider, path));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return QIcon();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EngineGeneration* EngineGeneration::findEngineGeneration(QQmlEngine* engine) {
 | 
			
		||||
	return g_generations.value(engine);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,11 +3,13 @@
 | 
			
		|||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qdir.h>
 | 
			
		||||
#include <qfilesystemwatcher.h>
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpair.h>
 | 
			
		||||
#include <qqmlengine.h>
 | 
			
		||||
#include <qqmlincubator.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qurl.h>
 | 
			
		||||
 | 
			
		||||
#include "incubator.hpp"
 | 
			
		||||
#include "qsintercept.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +42,8 @@ public:
 | 
			
		|||
	// otherwise null.
 | 
			
		||||
	static EngineGeneration* currentGeneration();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QIcon iconByUrl(const QUrl& url) const;
 | 
			
		||||
 | 
			
		||||
	RootWrapper* wrapper = nullptr;
 | 
			
		||||
	QDir rootPath;
 | 
			
		||||
	QmlScanner scanner;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,5 +22,6 @@ headers = [
 | 
			
		|||
	"elapsedtimer.hpp",
 | 
			
		||||
	"desktopentry.hpp",
 | 
			
		||||
	"objectrepeater.hpp",
 | 
			
		||||
	"qsmenu.hpp"
 | 
			
		||||
]
 | 
			
		||||
-----
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										276
									
								
								src/core/platformmenu.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								src/core/platformmenu.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,276 @@
 | 
			
		|||
#include "platformmenu.hpp"
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include <qaction.h>
 | 
			
		||||
#include <qactiongroup.h>
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qicon.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qmenu.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpoint.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "generation.hpp"
 | 
			
		||||
#include "proxywindow.hpp"
 | 
			
		||||
#include "qsmenu.hpp"
 | 
			
		||||
#include "windowinterface.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::menu::platform {
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
QVector<std::function<void(PlatformMenuQMenu*)>> CREATION_HOOKS; // NOLINT
 | 
			
		||||
PlatformMenuQMenu* ACTIVE_MENU = nullptr;                        // NOLINT
 | 
			
		||||
} // namespace
 | 
			
		||||
 | 
			
		||||
PlatformMenuQMenu::~PlatformMenuQMenu() {
 | 
			
		||||
	if (this == ACTIVE_MENU) {
 | 
			
		||||
		ACTIVE_MENU = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuQMenu::setVisible(bool visible) {
 | 
			
		||||
	if (visible) {
 | 
			
		||||
		for (auto& hook: CREATION_HOOKS) {
 | 
			
		||||
			hook(this);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if (this == ACTIVE_MENU) {
 | 
			
		||||
			ACTIVE_MENU = nullptr;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->QMenu::setVisible(visible);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlatformMenuEntry::PlatformMenuEntry(QsMenuEntry* menu): QObject(menu), menu(menu) {
 | 
			
		||||
	this->relayout();
 | 
			
		||||
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::enabledChanged, this, &PlatformMenuEntry::onEnabledChanged);
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::textChanged, this, &PlatformMenuEntry::onTextChanged);
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::iconChanged, this, &PlatformMenuEntry::onIconChanged);
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::buttonTypeChanged, this, &PlatformMenuEntry::onButtonTypeChanged);
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::checkStateChanged, this, &PlatformMenuEntry::onCheckStateChanged);
 | 
			
		||||
	QObject::connect(menu, &QsMenuEntry::hasChildrenChanged, this, &PlatformMenuEntry::relayoutParent);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PlatformMenuEntry::~PlatformMenuEntry() {
 | 
			
		||||
	this->clearChildren();
 | 
			
		||||
	delete this->qaction;
 | 
			
		||||
	delete this->qmenu;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook) {
 | 
			
		||||
	CREATION_HOOKS.push_back(std::move(hook));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
 | 
			
		||||
	QWindow* window = nullptr;
 | 
			
		||||
 | 
			
		||||
	if (this->qmenu == nullptr) {
 | 
			
		||||
		qCritical() << "Cannot display PlatformMenuEntry as it is not a menu.";
 | 
			
		||||
		return false;
 | 
			
		||||
	} else if (parentWindow == nullptr) {
 | 
			
		||||
		qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
 | 
			
		||||
		return false;
 | 
			
		||||
	} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
 | 
			
		||||
		window = proxy->backingWindow();
 | 
			
		||||
	} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
 | 
			
		||||
		window = interface->proxyWindow()->backingWindow();
 | 
			
		||||
	} else {
 | 
			
		||||
		qCritical() << "PlatformMenuEntry.display() must be called with a window.";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (window == nullptr) {
 | 
			
		||||
		qCritical() << "Cannot display PlatformMenuEntry from a parent window that is not visible.";
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ACTIVE_MENU && this->qmenu != ACTIVE_MENU) {
 | 
			
		||||
		ACTIVE_MENU->close();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ACTIVE_MENU = this->qmenu;
 | 
			
		||||
 | 
			
		||||
	auto point = window->mapToGlobal(QPoint(relativeX, relativeY));
 | 
			
		||||
 | 
			
		||||
	this->qmenu->createWinId();
 | 
			
		||||
	this->qmenu->windowHandle()->setTransientParent(window);
 | 
			
		||||
 | 
			
		||||
	this->qmenu->popup(point);
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::relayout() {
 | 
			
		||||
	if (this->menu->hasChildren()) {
 | 
			
		||||
		delete this->qaction;
 | 
			
		||||
		this->qaction = nullptr;
 | 
			
		||||
 | 
			
		||||
		if (this->qmenu == nullptr) {
 | 
			
		||||
			this->qmenu = new PlatformMenuQMenu();
 | 
			
		||||
			QObject::connect(this->qmenu, &QMenu::aboutToShow, this, &PlatformMenuEntry::onAboutToShow);
 | 
			
		||||
			QObject::connect(this->qmenu, &QMenu::aboutToHide, this, &PlatformMenuEntry::onAboutToHide);
 | 
			
		||||
		} else {
 | 
			
		||||
			this->clearChildren();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->qmenu->setTitle(this->menu->text());
 | 
			
		||||
 | 
			
		||||
		auto icon = this->menu->icon();
 | 
			
		||||
		if (!icon.isEmpty()) {
 | 
			
		||||
			auto* generation = EngineGeneration::currentGeneration();
 | 
			
		||||
			this->qmenu->setIcon(generation->iconByUrl(this->menu->icon()));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto children = this->menu->children();
 | 
			
		||||
		auto len = children.count(&children);
 | 
			
		||||
		for (auto i = 0; i < len; i++) {
 | 
			
		||||
			auto* child = children.at(&children, i);
 | 
			
		||||
 | 
			
		||||
			auto* instance = new PlatformMenuEntry(child);
 | 
			
		||||
			QObject::connect(instance, &QObject::destroyed, this, &PlatformMenuEntry::onChildDestroyed);
 | 
			
		||||
 | 
			
		||||
			QObject::connect(
 | 
			
		||||
			    instance,
 | 
			
		||||
			    &PlatformMenuEntry::relayoutParent,
 | 
			
		||||
			    this,
 | 
			
		||||
			    &PlatformMenuEntry::relayout
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			this->childEntries.push_back(instance);
 | 
			
		||||
			instance->addToQMenu(this->qmenu);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (!this->menu->isSeparator()) {
 | 
			
		||||
		this->clearChildren();
 | 
			
		||||
		delete this->qmenu;
 | 
			
		||||
		this->qmenu = nullptr;
 | 
			
		||||
 | 
			
		||||
		if (this->qaction == nullptr) {
 | 
			
		||||
			this->qaction = new QAction(this);
 | 
			
		||||
 | 
			
		||||
			QObject::connect(
 | 
			
		||||
			    this->qaction,
 | 
			
		||||
			    &QAction::triggered,
 | 
			
		||||
			    this,
 | 
			
		||||
			    &PlatformMenuEntry::onActionTriggered
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->qaction->setText(this->menu->text());
 | 
			
		||||
 | 
			
		||||
		auto icon = this->menu->icon();
 | 
			
		||||
		if (!icon.isEmpty()) {
 | 
			
		||||
			auto* generation = EngineGeneration::currentGeneration();
 | 
			
		||||
			this->qaction->setIcon(generation->iconByUrl(this->menu->icon()));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->qaction->setEnabled(this->menu->enabled());
 | 
			
		||||
		this->qaction->setCheckable(this->menu->buttonType() != QsMenuButtonType::None);
 | 
			
		||||
 | 
			
		||||
		if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
 | 
			
		||||
			if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
 | 
			
		||||
			this->qaction->setActionGroup(this->qactiongroup);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
 | 
			
		||||
	} else {
 | 
			
		||||
		delete this->qmenu;
 | 
			
		||||
		delete this->qaction;
 | 
			
		||||
		this->qmenu = nullptr;
 | 
			
		||||
		this->qaction = nullptr;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onAboutToShow() { this->menu->ref(); }
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onAboutToHide() {
 | 
			
		||||
	this->menu->unref();
 | 
			
		||||
	emit this->closed();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onActionTriggered() {
 | 
			
		||||
	auto* action = qobject_cast<PlatformMenuEntry*>(this->sender()->parent());
 | 
			
		||||
	emit action->menu->triggered();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onChildDestroyed() { this->childEntries.removeOne(this->sender()); }
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onEnabledChanged() {
 | 
			
		||||
	if (this->qaction != nullptr) {
 | 
			
		||||
		this->qaction->setEnabled(this->menu->enabled());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onTextChanged() {
 | 
			
		||||
	if (this->qmenu != nullptr) {
 | 
			
		||||
		this->qmenu->setTitle(this->menu->text());
 | 
			
		||||
	} else if (this->qaction != nullptr) {
 | 
			
		||||
		this->qaction->setText(this->menu->text());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onIconChanged() {
 | 
			
		||||
	if (this->qmenu == nullptr && this->qaction == nullptr) return;
 | 
			
		||||
 | 
			
		||||
	auto iconName = this->menu->icon();
 | 
			
		||||
	QIcon icon;
 | 
			
		||||
 | 
			
		||||
	if (!iconName.isEmpty()) {
 | 
			
		||||
		auto* generation = EngineGeneration::currentGeneration();
 | 
			
		||||
		icon = generation->iconByUrl(iconName);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->qmenu != nullptr) {
 | 
			
		||||
		this->qmenu->setIcon(icon);
 | 
			
		||||
	} else if (this->qaction != nullptr) {
 | 
			
		||||
		this->qaction->setIcon(icon);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onButtonTypeChanged() {
 | 
			
		||||
	if (this->qaction != nullptr) {
 | 
			
		||||
		QActionGroup* group = nullptr;
 | 
			
		||||
 | 
			
		||||
		if (this->menu->buttonType() == QsMenuButtonType::RadioButton) {
 | 
			
		||||
			if (!this->qactiongroup) this->qactiongroup = new QActionGroup(this);
 | 
			
		||||
			group = this->qactiongroup;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->qaction->setActionGroup(group);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::onCheckStateChanged() {
 | 
			
		||||
	if (this->qaction != nullptr) {
 | 
			
		||||
		this->qaction->setChecked(this->menu->checkState() != Qt::Unchecked);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::clearChildren() {
 | 
			
		||||
	for (auto* child: this->childEntries) {
 | 
			
		||||
		delete child;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->childEntries.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PlatformMenuEntry::addToQMenu(PlatformMenuQMenu* menu) {
 | 
			
		||||
	if (this->qmenu != nullptr) {
 | 
			
		||||
		menu->addMenu(this->qmenu);
 | 
			
		||||
		this->qmenu->containingMenu = menu;
 | 
			
		||||
	} else if (this->qaction != nullptr) {
 | 
			
		||||
		menu->addAction(this->qaction);
 | 
			
		||||
	} else {
 | 
			
		||||
		menu->addSeparator();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::menu::platform
 | 
			
		||||
							
								
								
									
										72
									
								
								src/core/platformmenu.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/core/platformmenu.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <functional>
 | 
			
		||||
 | 
			
		||||
#include <qaction.h>
 | 
			
		||||
#include <qactiongroup.h>
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qmenu.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpoint.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
#include "qsmenu.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::menu::platform {
 | 
			
		||||
 | 
			
		||||
class PlatformMenuQMenu: public QMenu {
 | 
			
		||||
public:
 | 
			
		||||
	explicit PlatformMenuQMenu() = default;
 | 
			
		||||
	~PlatformMenuQMenu() override;
 | 
			
		||||
	Q_DISABLE_COPY_MOVE(PlatformMenuQMenu);
 | 
			
		||||
 | 
			
		||||
	void setVisible(bool visible) override;
 | 
			
		||||
 | 
			
		||||
	PlatformMenuQMenu* containingMenu = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PlatformMenuEntry: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit PlatformMenuEntry(QsMenuEntry* menu);
 | 
			
		||||
	~PlatformMenuEntry() override;
 | 
			
		||||
	Q_DISABLE_COPY_MOVE(PlatformMenuEntry);
 | 
			
		||||
 | 
			
		||||
	bool display(QObject* parentWindow, int relativeX, int relativeY);
 | 
			
		||||
 | 
			
		||||
	static void registerCreationHook(std::function<void(PlatformMenuQMenu*)> hook);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void closed();
 | 
			
		||||
	void relayoutParent();
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
	void relayout();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onAboutToShow();
 | 
			
		||||
	void onAboutToHide();
 | 
			
		||||
	void onActionTriggered();
 | 
			
		||||
	void onChildDestroyed();
 | 
			
		||||
	void onEnabledChanged();
 | 
			
		||||
	void onTextChanged();
 | 
			
		||||
	void onIconChanged();
 | 
			
		||||
	void onButtonTypeChanged();
 | 
			
		||||
	void onCheckStateChanged();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void clearChildren();
 | 
			
		||||
	void addToQMenu(PlatformMenuQMenu* menu);
 | 
			
		||||
 | 
			
		||||
	QsMenuEntry* menu;
 | 
			
		||||
	PlatformMenuQMenu* qmenu = nullptr;
 | 
			
		||||
	QAction* qaction = nullptr;
 | 
			
		||||
	QActionGroup* qactiongroup = nullptr;
 | 
			
		||||
	QVector<PlatformMenuEntry*> childEntries;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::menu::platform
 | 
			
		||||
							
								
								
									
										95
									
								
								src/core/qsmenu.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/core/qsmenu.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
#include "qsmenu.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "platformmenu.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::menu::platform;
 | 
			
		||||
 | 
			
		||||
namespace qs::menu {
 | 
			
		||||
 | 
			
		||||
QString QsMenuButtonType::toString(QsMenuButtonType::Enum value) {
 | 
			
		||||
	switch (value) {
 | 
			
		||||
	case QsMenuButtonType::None: return "None";
 | 
			
		||||
	case QsMenuButtonType::CheckBox: return "CheckBox";
 | 
			
		||||
	case QsMenuButtonType::RadioButton: return "RadioButton";
 | 
			
		||||
	default: return "Invalid button type";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QsMenuEntry::display(QObject* parentWindow, int relativeX, int relativeY) {
 | 
			
		||||
	auto* platform = new PlatformMenuEntry(this);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(platform, &PlatformMenuEntry::closed, platform, [=]() {
 | 
			
		||||
		platform->deleteLater();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	auto success = platform->display(parentWindow, relativeX, relativeY);
 | 
			
		||||
	if (!success) delete platform;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<QsMenuEntry> QsMenuEntry::emptyChildren(QObject* parent) {
 | 
			
		||||
	return QQmlListProperty<QsMenuEntry>(
 | 
			
		||||
	    parent,
 | 
			
		||||
	    nullptr,
 | 
			
		||||
	    &QsMenuEntry::childCount,
 | 
			
		||||
	    &QsMenuEntry::childAt
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QsMenuEntry::ref() {
 | 
			
		||||
	this->refcount++;
 | 
			
		||||
	if (this->refcount == 1) emit this->opened();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QsMenuEntry::unref() {
 | 
			
		||||
	this->refcount--;
 | 
			
		||||
	if (this->refcount == 0) emit this->closed();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<QsMenuEntry> QsMenuEntry::children() { return QsMenuEntry::emptyChildren(this); }
 | 
			
		||||
 | 
			
		||||
QsMenuEntry* QsMenuOpener::menu() const { return this->mMenu; }
 | 
			
		||||
 | 
			
		||||
void QsMenuOpener::setMenu(QsMenuEntry* menu) {
 | 
			
		||||
	if (menu == this->mMenu) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mMenu != nullptr) {
 | 
			
		||||
		this->mMenu->unref();
 | 
			
		||||
		QObject::disconnect(this->mMenu, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mMenu = menu;
 | 
			
		||||
 | 
			
		||||
	if (menu != nullptr) {
 | 
			
		||||
		QObject::connect(menu, &QObject::destroyed, this, &QsMenuOpener::onMenuDestroyed);
 | 
			
		||||
		QObject::connect(menu, &QsMenuEntry::childrenChanged, this, &QsMenuOpener::childrenChanged);
 | 
			
		||||
		menu->ref();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit this->menuChanged();
 | 
			
		||||
	emit this->childrenChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void QsMenuOpener::onMenuDestroyed() {
 | 
			
		||||
	this->mMenu = nullptr;
 | 
			
		||||
	emit this->menuChanged();
 | 
			
		||||
	emit this->childrenChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<QsMenuEntry> QsMenuOpener::children() {
 | 
			
		||||
	return this->mMenu ? this->mMenu->children() : QsMenuEntry::emptyChildren(this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qsizetype QsMenuEntry::childCount(QQmlListProperty<QsMenuEntry>* /*property*/) { return 0; }
 | 
			
		||||
 | 
			
		||||
QsMenuEntry*
 | 
			
		||||
QsMenuEntry::childAt(QQmlListProperty<QsMenuEntry>* /*property*/, qsizetype /*index*/) {
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::menu
 | 
			
		||||
							
								
								
									
										140
									
								
								src/core/qsmenu.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/core/qsmenu.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "doc.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::menu {
 | 
			
		||||
 | 
			
		||||
class QsMenuButtonType: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_SINGLETON;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	enum Enum {
 | 
			
		||||
		/// This menu item does not have a checkbox or a radiobutton associated with it.
 | 
			
		||||
		None = 0,
 | 
			
		||||
		/// This menu item should draw a checkbox.
 | 
			
		||||
		CheckBox = 1,
 | 
			
		||||
		/// This menu item should draw a radiobutton.
 | 
			
		||||
		RadioButton = 2,
 | 
			
		||||
	};
 | 
			
		||||
	Q_ENUM(Enum);
 | 
			
		||||
 | 
			
		||||
	Q_INVOKABLE static QString toString(QsMenuButtonType::Enum value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class QsMenuEntry: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// If this menu item should be rendered as a separator between other items.
 | 
			
		||||
	///
 | 
			
		||||
	/// No other properties have a meaningful value when `isSeparator` is true.
 | 
			
		||||
	Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY isSeparatorChanged);
 | 
			
		||||
	Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
 | 
			
		||||
	/// Text of the menu item.
 | 
			
		||||
	Q_PROPERTY(QString text READ text NOTIFY textChanged);
 | 
			
		||||
	/// Url of the menu item's icon or `""` if it doesn't have one.
 | 
			
		||||
	///
 | 
			
		||||
	/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
 | 
			
		||||
	/// as shown below.
 | 
			
		||||
	///
 | 
			
		||||
	/// ```qml
 | 
			
		||||
	/// Image {
 | 
			
		||||
	///   source: menuItem.icon
 | 
			
		||||
	///   // To get the best image quality, set the image source size to the same size
 | 
			
		||||
	///   // as the rendered image.
 | 
			
		||||
	///   sourceSize.width: width
 | 
			
		||||
	///   sourceSize.height: height
 | 
			
		||||
	/// }
 | 
			
		||||
	/// ```
 | 
			
		||||
	Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
 | 
			
		||||
	/// If this menu item has an associated checkbox or radiobutton.
 | 
			
		||||
	Q_PROPERTY(QsMenuButtonType::Enum buttonType READ buttonType NOTIFY buttonTypeChanged);
 | 
			
		||||
	/// The check state of the checkbox or radiobutton if applicable, as a
 | 
			
		||||
	/// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
 | 
			
		||||
	Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
 | 
			
		||||
	/// If this menu item has children that can be accessed through a [QsMenuOpener](../qsmenuopener).
 | 
			
		||||
	Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_UNCREATABLE("QsMenuEntry cannot be directly created");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit QsMenuEntry(QObject* parent = nullptr): QObject(parent) {}
 | 
			
		||||
 | 
			
		||||
	/// Display a platform menu at the given location relative to the parent window.
 | 
			
		||||
	Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] virtual bool isSeparator() const { return false; }
 | 
			
		||||
	[[nodiscard]] virtual bool enabled() const { return true; }
 | 
			
		||||
	[[nodiscard]] virtual QString text() const { return ""; }
 | 
			
		||||
	[[nodiscard]] virtual QString icon() const { return ""; }
 | 
			
		||||
	[[nodiscard]] virtual QsMenuButtonType::Enum buttonType() const { return QsMenuButtonType::None; }
 | 
			
		||||
	[[nodiscard]] virtual Qt::CheckState checkState() const { return Qt::Unchecked; }
 | 
			
		||||
	[[nodiscard]] virtual bool hasChildren() const { return false; }
 | 
			
		||||
 | 
			
		||||
	void ref();
 | 
			
		||||
	void unref();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] virtual QQmlListProperty<QsMenuEntry> children();
 | 
			
		||||
 | 
			
		||||
	static QQmlListProperty<QsMenuEntry> emptyChildren(QObject* parent);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	/// Send a trigger/click signal to the menu entry.
 | 
			
		||||
	void triggered();
 | 
			
		||||
 | 
			
		||||
	QSDOC_HIDE void opened();
 | 
			
		||||
	QSDOC_HIDE void closed();
 | 
			
		||||
 | 
			
		||||
	void isSeparatorChanged();
 | 
			
		||||
	void enabledChanged();
 | 
			
		||||
	void textChanged();
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void buttonTypeChanged();
 | 
			
		||||
	void checkStateChanged();
 | 
			
		||||
	void hasChildrenChanged();
 | 
			
		||||
	QSDOC_HIDE void childrenChanged();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static qsizetype childCount(QQmlListProperty<QsMenuEntry>* property);
 | 
			
		||||
	static QsMenuEntry* childAt(QQmlListProperty<QsMenuEntry>* property, qsizetype index);
 | 
			
		||||
 | 
			
		||||
	qsizetype refcount = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///! Provides access to children of a QsMenuEntry
 | 
			
		||||
class QsMenuOpener: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	/// The menu to retrieve children from.
 | 
			
		||||
	Q_PROPERTY(QsMenuEntry* menu READ menu WRITE setMenu NOTIFY menuChanged);
 | 
			
		||||
	/// The children of the given menu.
 | 
			
		||||
	Q_PROPERTY(QQmlListProperty<QsMenuEntry> children READ children NOTIFY childrenChanged);
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit QsMenuOpener(QObject* parent = nullptr): QObject(parent) {}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QsMenuEntry* menu() const;
 | 
			
		||||
	void setMenu(QsMenuEntry* menu);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QQmlListProperty<QsMenuEntry> children();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void menuChanged();
 | 
			
		||||
	void childrenChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onMenuDestroyed();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QsMenuEntry* mMenu = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::menu
 | 
			
		||||
| 
						 | 
				
			
			@ -21,19 +21,26 @@
 | 
			
		|||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
#include "../../core/iconimageprovider.hpp"
 | 
			
		||||
#include "../../core/qsmenu.hpp"
 | 
			
		||||
#include "../../dbus/properties.hpp"
 | 
			
		||||
#include "dbus_menu.h"
 | 
			
		||||
#include "dbus_menu_types.hpp"
 | 
			
		||||
 | 
			
		||||
Q_LOGGING_CATEGORY(logDbusMenu, "quickshell.dbus.dbusmenu", QtWarningMsg);
 | 
			
		||||
 | 
			
		||||
using namespace qs::menu;
 | 
			
		||||
 | 
			
		||||
namespace qs::dbus::dbusmenu {
 | 
			
		||||
 | 
			
		||||
DBusMenuItem::DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu)
 | 
			
		||||
    : QObject(menu)
 | 
			
		||||
    : QsMenuEntry(menu)
 | 
			
		||||
    , id(id)
 | 
			
		||||
    , menu(menu)
 | 
			
		||||
    , parentMenu(parentMenu) {
 | 
			
		||||
	QObject::connect(this, &QsMenuEntry::opened, this, &DBusMenuItem::sendOpened);
 | 
			
		||||
	QObject::connect(this, &QsMenuEntry::closed, this, &DBusMenuItem::sendClosed);
 | 
			
		||||
	QObject::connect(this, &QsMenuEntry::triggered, this, &DBusMenuItem::sendTriggered);
 | 
			
		||||
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    &this->menu->iconThemePath,
 | 
			
		||||
	    &AbstractDBusProperty::changed,
 | 
			
		||||
| 
						 | 
				
			
			@ -42,20 +49,13 @@ DBusMenuItem::DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu)
 | 
			
		|||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DBusMenuItem::click() {
 | 
			
		||||
	if (this->displayChildren) {
 | 
			
		||||
		this->setShowChildren(!this->mShowChildren);
 | 
			
		||||
	} else {
 | 
			
		||||
		this->menu->sendEvent(this->id, "clicked");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DBusMenuItem::hover() const { this->menu->sendEvent(this->id, "hovered"); }
 | 
			
		||||
void DBusMenuItem::sendOpened() const { this->menu->sendEvent(this->id, "opened"); }
 | 
			
		||||
void DBusMenuItem::sendClosed() const { this->menu->sendEvent(this->id, "closed"); }
 | 
			
		||||
void DBusMenuItem::sendTriggered() const { this->menu->sendEvent(this->id, "clicked"); }
 | 
			
		||||
 | 
			
		||||
DBusMenu* DBusMenuItem::menuHandle() const { return this->menu; }
 | 
			
		||||
QString DBusMenuItem::label() const { return this->mLabel; }
 | 
			
		||||
QString DBusMenuItem::cleanLabel() const { return this->mCleanLabel; }
 | 
			
		||||
bool DBusMenuItem::enabled() const { return this->mEnabled; }
 | 
			
		||||
QString DBusMenuItem::text() const { return this->mCleanLabel; }
 | 
			
		||||
 | 
			
		||||
QString DBusMenuItem::icon() const {
 | 
			
		||||
	if (!this->iconName.isEmpty()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,23 +68,20 @@ QString DBusMenuItem::icon() const {
 | 
			
		|||
	} else return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ToggleButtonType::Enum DBusMenuItem::toggleType() const { return this->mToggleType; };
 | 
			
		||||
QsMenuButtonType::Enum DBusMenuItem::buttonType() const { return this->mButtonType; };
 | 
			
		||||
Qt::CheckState DBusMenuItem::checkState() const { return this->mCheckState; }
 | 
			
		||||
bool DBusMenuItem::isSeparator() const { return this->mSeparator; }
 | 
			
		||||
 | 
			
		||||
bool DBusMenuItem::isShowingChildren() const { return this->mShowChildren && this->childrenLoaded; }
 | 
			
		||||
 | 
			
		||||
void DBusMenuItem::setShowChildren(bool showChildren) {
 | 
			
		||||
void DBusMenuItem::setShowChildrenRecursive(bool showChildren) {
 | 
			
		||||
	if (showChildren == this->mShowChildren) return;
 | 
			
		||||
	this->mShowChildren = showChildren;
 | 
			
		||||
	this->childrenLoaded = false;
 | 
			
		||||
 | 
			
		||||
	if (showChildren) {
 | 
			
		||||
		this->menu->prepareToShow(this->id, true);
 | 
			
		||||
		this->menu->prepareToShow(this->id, -1);
 | 
			
		||||
	} else {
 | 
			
		||||
		this->menu->sendEvent(this->id, "closed");
 | 
			
		||||
		emit this->showingChildrenChanged();
 | 
			
		||||
 | 
			
		||||
		if (!this->mChildren.isEmpty()) {
 | 
			
		||||
			for (auto child: this->mChildren) {
 | 
			
		||||
				this->menu->removeRecursive(child);
 | 
			
		||||
| 
						 | 
				
			
			@ -96,10 +93,15 @@ void DBusMenuItem::setShowChildren(bool showChildren) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DBusMenuItem::updateLayout() const {
 | 
			
		||||
	if (!this->isShowingChildren()) return;
 | 
			
		||||
	this->menu->updateLayout(this->id, -1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool DBusMenuItem::hasChildren() const { return this->displayChildren; }
 | 
			
		||||
 | 
			
		||||
QQmlListProperty<DBusMenuItem> DBusMenuItem::children() {
 | 
			
		||||
	return QQmlListProperty<DBusMenuItem>(
 | 
			
		||||
QQmlListProperty<QsMenuEntry> DBusMenuItem::children() {
 | 
			
		||||
	return QQmlListProperty<QsMenuEntry>(
 | 
			
		||||
	    this,
 | 
			
		||||
	    nullptr,
 | 
			
		||||
	    &DBusMenuItem::childrenCount,
 | 
			
		||||
| 
						 | 
				
			
			@ -107,11 +109,11 @@ QQmlListProperty<DBusMenuItem> DBusMenuItem::children() {
 | 
			
		|||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qsizetype DBusMenuItem::childrenCount(QQmlListProperty<DBusMenuItem>* property) {
 | 
			
		||||
qsizetype DBusMenuItem::childrenCount(QQmlListProperty<QsMenuEntry>* property) {
 | 
			
		||||
	return reinterpret_cast<DBusMenuItem*>(property->object)->enabledChildren.count(); // NOLINT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DBusMenuItem* DBusMenuItem::childAt(QQmlListProperty<DBusMenuItem>* property, qsizetype index) {
 | 
			
		||||
QsMenuEntry* DBusMenuItem::childAt(QQmlListProperty<QsMenuEntry>* property, qsizetype index) {
 | 
			
		||||
	auto* item = reinterpret_cast<DBusMenuItem*>(property->object); // NOLINT
 | 
			
		||||
	return item->menu->items.value(item->enabledChildren.at(index));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -124,30 +126,30 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto originalLabel = this->mLabel;
 | 
			
		||||
	auto originalText = this->mText;
 | 
			
		||||
	//auto originalMnemonic = this->mnemonic;
 | 
			
		||||
	auto originalEnabled = this->mEnabled;
 | 
			
		||||
	auto originalVisible = this->visible;
 | 
			
		||||
	auto originalIconName = this->iconName;
 | 
			
		||||
	auto* originalImage = this->image;
 | 
			
		||||
	auto originalIsSeparator = this->mSeparator;
 | 
			
		||||
	auto originalToggleType = this->mToggleType;
 | 
			
		||||
	auto originalButtonType = this->mButtonType;
 | 
			
		||||
	auto originalToggleState = this->mCheckState;
 | 
			
		||||
	auto originalDisplayChildren = this->displayChildren;
 | 
			
		||||
 | 
			
		||||
	auto label = properties.value("label");
 | 
			
		||||
	if (label.canConvert<QString>()) {
 | 
			
		||||
		auto text = label.value<QString>();
 | 
			
		||||
		this->mLabel = text;
 | 
			
		||||
		this->mText = text;
 | 
			
		||||
		this->mCleanLabel = text;
 | 
			
		||||
		//this->mnemonic = QChar();
 | 
			
		||||
 | 
			
		||||
		for (auto i = 0; i < this->mLabel.length() - 1;) {
 | 
			
		||||
			if (this->mLabel.at(i) == '_') {
 | 
			
		||||
		for (auto i = 0; i < this->mText.length() - 1;) {
 | 
			
		||||
			if (this->mText.at(i) == '_') {
 | 
			
		||||
				//if (this->mnemonic == QChar()) this->mnemonic = this->mLabel.at(i + 1);
 | 
			
		||||
				this->mLabel.remove(i, 1);
 | 
			
		||||
				this->mLabel.insert(i + 1, "</u>");
 | 
			
		||||
				this->mLabel.insert(i, "<u>");
 | 
			
		||||
				this->mText.remove(i, 1);
 | 
			
		||||
				this->mText.insert(i + 1, "</u>");
 | 
			
		||||
				this->mText.insert(i, "<u>");
 | 
			
		||||
				i += 8;
 | 
			
		||||
			} else {
 | 
			
		||||
				i++;
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +162,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if (removed.isEmpty() || removed.contains("label")) {
 | 
			
		||||
		this->mLabel = "";
 | 
			
		||||
		this->mText = "";
 | 
			
		||||
		//this->mnemonic = QChar();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -208,15 +210,15 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
	if (toggleType.canConvert<QString>()) {
 | 
			
		||||
		auto toggleTypeStr = toggleType.value<QString>();
 | 
			
		||||
 | 
			
		||||
		if (toggleTypeStr == "") this->mToggleType = ToggleButtonType::None;
 | 
			
		||||
		else if (toggleTypeStr == "checkmark") this->mToggleType = ToggleButtonType::CheckBox;
 | 
			
		||||
		else if (toggleTypeStr == "radio") this->mToggleType = ToggleButtonType::RadioButton;
 | 
			
		||||
		if (toggleTypeStr == "") this->mButtonType = QsMenuButtonType::None;
 | 
			
		||||
		else if (toggleTypeStr == "checkmark") this->mButtonType = QsMenuButtonType::CheckBox;
 | 
			
		||||
		else if (toggleTypeStr == "radio") this->mButtonType = QsMenuButtonType::RadioButton;
 | 
			
		||||
		else {
 | 
			
		||||
			qCWarning(logDbusMenu) << "Unrecognized toggle type" << toggleTypeStr << "for" << this;
 | 
			
		||||
			this->mToggleType = ToggleButtonType::None;
 | 
			
		||||
			this->mButtonType = QsMenuButtonType::None;
 | 
			
		||||
		}
 | 
			
		||||
	} else if (removed.isEmpty() || removed.contains("toggle-type")) {
 | 
			
		||||
		this->mToggleType = ToggleButtonType::None;
 | 
			
		||||
		this->mButtonType = QsMenuButtonType::None;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto toggleState = properties.value("toggle-state");
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +229,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
		else if (toggleStateInt == 1) this->mCheckState = Qt::Checked;
 | 
			
		||||
		else this->mCheckState = Qt::PartiallyChecked;
 | 
			
		||||
	} else if (removed.isEmpty() || removed.contains("toggle-state")) {
 | 
			
		||||
		this->mCheckState = Qt::PartiallyChecked;
 | 
			
		||||
		this->mCheckState = Qt::Unchecked;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto childrenDisplay = properties.value("children-display");
 | 
			
		||||
| 
						 | 
				
			
			@ -245,14 +247,14 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
		this->displayChildren = false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (this->mLabel != originalLabel) emit this->labelChanged();
 | 
			
		||||
	if (this->mText != originalText) emit this->textChanged();
 | 
			
		||||
	//if (this->mnemonic != originalMnemonic) emit this->labelChanged();
 | 
			
		||||
	if (this->mEnabled != originalEnabled) emit this->enabledChanged();
 | 
			
		||||
	if (this->visible != originalVisible && this->parentMenu != nullptr)
 | 
			
		||||
		this->parentMenu->onChildrenUpdated();
 | 
			
		||||
	if (this->mToggleType != originalToggleType) emit this->toggleTypeChanged();
 | 
			
		||||
	if (this->mButtonType != originalButtonType) emit this->buttonTypeChanged();
 | 
			
		||||
	if (this->mCheckState != originalToggleState) emit this->checkStateChanged();
 | 
			
		||||
	if (this->mSeparator != originalIsSeparator) emit this->separatorChanged();
 | 
			
		||||
	if (this->mSeparator != originalIsSeparator) emit this->isSeparatorChanged();
 | 
			
		||||
	if (this->displayChildren != originalDisplayChildren) emit this->hasChildrenChanged();
 | 
			
		||||
 | 
			
		||||
	if (this->iconName != originalIconName || this->image != originalImage) {
 | 
			
		||||
| 
						 | 
				
			
			@ -263,11 +265,11 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString
 | 
			
		|||
		emit this->iconChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qCDebug(logDbusMenu).nospace() << "Updated properties of " << this << " { label=" << this->mLabel
 | 
			
		||||
	qCDebug(logDbusMenu).nospace() << "Updated properties of " << this << " { label=" << this->mText
 | 
			
		||||
	                               << ", enabled=" << this->mEnabled << ", visible=" << this->visible
 | 
			
		||||
	                               << ", iconName=" << this->iconName << ", iconData=" << this->image
 | 
			
		||||
	                               << ", separator=" << this->mSeparator
 | 
			
		||||
	                               << ", toggleType=" << this->mToggleType
 | 
			
		||||
	                               << ", toggleType=" << this->mButtonType
 | 
			
		||||
	                               << ", toggleState=" << this->mCheckState
 | 
			
		||||
	                               << ", displayChildren=" << this->displayChildren << " }";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -291,20 +293,7 @@ QDebug operator<<(QDebug debug, DBusMenuItem* item) {
 | 
			
		|||
 | 
			
		||||
	auto saver = QDebugStateSaver(debug);
 | 
			
		||||
	debug.nospace() << "DBusMenuItem(" << static_cast<void*>(item) << ", id=" << item->id
 | 
			
		||||
	                << ", label=" << item->mLabel << ", menu=" << item->menu << ")";
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const ToggleButtonType::Enum& toggleType) {
 | 
			
		||||
	auto saver = QDebugStateSaver(debug);
 | 
			
		||||
	debug.nospace() << "ToggleType::";
 | 
			
		||||
 | 
			
		||||
	switch (toggleType) {
 | 
			
		||||
	case ToggleButtonType::None: debug << "None"; break;
 | 
			
		||||
	case ToggleButtonType::CheckBox: debug << "Checkbox"; break;
 | 
			
		||||
	case ToggleButtonType::RadioButton: debug << "Radiobutton"; break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	                << ", label=" << item->mText << ", menu=" << item->menu << ")";
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -334,19 +323,18 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
 | 
			
		|||
	this->properties.updateAllViaGetAll();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DBusMenu::prepareToShow(qint32 item, bool sendOpened) {
 | 
			
		||||
void DBusMenu::prepareToShow(qint32 item, qint32 depth) {
 | 
			
		||||
	auto pending = this->interface->AboutToShow(item);
 | 
			
		||||
	auto* call = new QDBusPendingCallWatcher(pending, this);
 | 
			
		||||
 | 
			
		||||
	auto responseCallback = [this, item, sendOpened](QDBusPendingCallWatcher* call) {
 | 
			
		||||
	auto responseCallback = [this, item, depth](QDBusPendingCallWatcher* call) {
 | 
			
		||||
		const QDBusPendingReply<bool> reply = *call;
 | 
			
		||||
		if (reply.isError()) {
 | 
			
		||||
			qCWarning(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of"
 | 
			
		||||
			                       << this << reply.error();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->updateLayout(item, 1);
 | 
			
		||||
		if (sendOpened) this->sendEvent(item, "opened");
 | 
			
		||||
		this->updateLayout(item, depth);
 | 
			
		||||
 | 
			
		||||
		delete call;
 | 
			
		||||
	};
 | 
			
		||||
| 
						 | 
				
			
			@ -385,6 +373,7 @@ void DBusMenu::updateLayoutRecursive(
 | 
			
		|||
		// there is an actual nullptr in the map and not no entry
 | 
			
		||||
		if (this->items.contains(layout.id)) {
 | 
			
		||||
			item = new DBusMenuItem(layout.id, this, parent);
 | 
			
		||||
			item->mShowChildren = parent != nullptr && parent->mShowChildren;
 | 
			
		||||
			this->items.insert(layout.id, item);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -431,8 +420,9 @@ void DBusMenu::updateLayoutRecursive(
 | 
			
		|||
 | 
			
		||||
	if (item->mShowChildren && !item->childrenLoaded) {
 | 
			
		||||
		item->childrenLoaded = true;
 | 
			
		||||
		emit item->showingChildrenChanged();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit item->layoutUpdated();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DBusMenu::removeRecursive(qint32 id) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,32 +14,18 @@
 | 
			
		|||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../../core/imageprovider.hpp"
 | 
			
		||||
#include "../../core/qsmenu.hpp"
 | 
			
		||||
#include "../properties.hpp"
 | 
			
		||||
#include "dbus_menu_types.hpp"
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_LOGGING_CATEGORY(logDbusMenu);
 | 
			
		||||
 | 
			
		||||
namespace ToggleButtonType { // NOLINT
 | 
			
		||||
Q_NAMESPACE;
 | 
			
		||||
QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
enum Enum {
 | 
			
		||||
	/// This menu item does not have a checkbox or a radiobutton associated with it.
 | 
			
		||||
	None = 0,
 | 
			
		||||
	/// This menu item should draw a checkbox.
 | 
			
		||||
	CheckBox = 1,
 | 
			
		||||
	/// This menu item should draw a radiobutton.
 | 
			
		||||
	RadioButton = 2,
 | 
			
		||||
};
 | 
			
		||||
Q_ENUM_NS(Enum);
 | 
			
		||||
 | 
			
		||||
} // namespace ToggleButtonType
 | 
			
		||||
 | 
			
		||||
class DBusMenuInterface;
 | 
			
		||||
 | 
			
		||||
namespace qs::dbus::dbusmenu {
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const ToggleButtonType::Enum& toggleType);
 | 
			
		||||
// hack because docgen can't take namespaces in superclasses
 | 
			
		||||
using menu::QsMenuEntry;
 | 
			
		||||
 | 
			
		||||
class DBusMenu;
 | 
			
		||||
class DBusMenuPngImage;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,113 +33,56 @@ class DBusMenuPngImage;
 | 
			
		|||
///! Menu item shared by an external program.
 | 
			
		||||
/// Menu item shared by an external program via the
 | 
			
		||||
/// [DBusMenu specification](https://github.com/AyatanaIndicators/libdbusmenu/blob/master/libdbusmenu-glib/dbus-menu.xml).
 | 
			
		||||
class DBusMenuItem: public QObject {
 | 
			
		||||
class DBusMenuItem: public QsMenuEntry {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// Handle to the root of this menu.
 | 
			
		||||
	Q_PROPERTY(DBusMenu* menuHandle READ menuHandle CONSTANT);
 | 
			
		||||
	/// Text of the menu item, including hotkey markup.
 | 
			
		||||
	Q_PROPERTY(QString label READ label NOTIFY labelChanged);
 | 
			
		||||
	/// Text of the menu item without hotkey markup.
 | 
			
		||||
	Q_PROPERTY(QString cleanLabel READ cleanLabel NOTIFY labelChanged);
 | 
			
		||||
  /// If the menu item should be shown as enabled.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] Disabled menu items are often used as headers in addition
 | 
			
		||||
	/// > to actual disabled entries.
 | 
			
		||||
	Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged);
 | 
			
		||||
	/// Url of the menu item's icon or `""` if it doesn't have one.
 | 
			
		||||
	///
 | 
			
		||||
	/// This can be passed to [Image.source](https://doc.qt.io/qt-6/qml-qtquick-image.html#source-prop)
 | 
			
		||||
	/// as shown below.
 | 
			
		||||
	///
 | 
			
		||||
	/// ```qml
 | 
			
		||||
	/// Image {
 | 
			
		||||
	///   source: menuItem.icon
 | 
			
		||||
	///   // To get the best image quality, set the image source size to the same size
 | 
			
		||||
	///   // as the rendered image.
 | 
			
		||||
	///   sourceSize.width: width
 | 
			
		||||
	///   sourceSize.height: height
 | 
			
		||||
	/// }
 | 
			
		||||
	/// ```
 | 
			
		||||
	Q_PROPERTY(QString icon READ icon NOTIFY iconChanged);
 | 
			
		||||
	/// If this menu item has an associated checkbox or radiobutton.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] It is the responsibility of the remote application to update the state of
 | 
			
		||||
	/// > checkboxes and radiobuttons via [checkState](#prop.checkState).
 | 
			
		||||
	Q_PROPERTY(ToggleButtonType::Enum toggleType READ toggleType NOTIFY toggleTypeChanged);
 | 
			
		||||
	/// The check state of the checkbox or radiobutton if applicable, as a
 | 
			
		||||
  /// [Qt.CheckState](https://doc.qt.io/qt-6/qt.html#CheckState-enum).
 | 
			
		||||
	Q_PROPERTY(Qt::CheckState checkState READ checkState NOTIFY checkStateChanged);
 | 
			
		||||
	/// If this menu item should be rendered as a separator between other items.
 | 
			
		||||
	///
 | 
			
		||||
	/// No other properties have a meaningful value when `isSeparator` is true.
 | 
			
		||||
	Q_PROPERTY(bool isSeparator READ isSeparator NOTIFY separatorChanged);
 | 
			
		||||
	/// If this menu item reveals a submenu containing more items.
 | 
			
		||||
	///
 | 
			
		||||
	/// Any submenu items must be requested by setting [showChildren](#prop.showChildren).
 | 
			
		||||
	Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY hasChildrenChanged);
 | 
			
		||||
	/// If submenu entries of this item should be shown.
 | 
			
		||||
	///
 | 
			
		||||
	/// When true, children of this menu item will be exposed via [children](#prop.children).
 | 
			
		||||
	/// Setting this property will additionally send the `opened` and `closed` events to the
 | 
			
		||||
	/// process that provided the menu.
 | 
			
		||||
	Q_PROPERTY(bool showChildren READ isShowingChildren WRITE setShowChildren NOTIFY showingChildrenChanged);
 | 
			
		||||
	/// Children of this menu item. Only populated when [showChildren](#prop.showChildren) is true.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] Use [hasChildren](#prop.hasChildren) to check if this item should reveal a submenu
 | 
			
		||||
	/// > instead of checking if `children` is empty.
 | 
			
		||||
	Q_PROPERTY(QQmlListProperty<DBusMenuItem> children READ children NOTIFY childrenChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_UNCREATABLE("DBusMenus can only be acquired from a DBusMenuHandle");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit DBusMenuItem(qint32 id, DBusMenu* menu, DBusMenuItem* parentMenu);
 | 
			
		||||
 | 
			
		||||
	/// Send a `clicked` event to the remote application for this menu item.
 | 
			
		||||
	Q_INVOKABLE void click();
 | 
			
		||||
 | 
			
		||||
	/// Send a `hovered` event to the remote application for this menu item.
 | 
			
		||||
	/// Refreshes the menu contents.
 | 
			
		||||
	///
 | 
			
		||||
	/// Note: we are not aware of any programs that use this in any meaningful way.
 | 
			
		||||
	Q_INVOKABLE void hover() const;
 | 
			
		||||
	/// Usually you shouldn't need to call this manually but some applications providing
 | 
			
		||||
	/// menus do not update them correctly. Call this if menus don't update their state.
 | 
			
		||||
	///
 | 
			
		||||
	/// The `layoutUpdated` signal will be sent when a response is received.
 | 
			
		||||
	Q_INVOKABLE void updateLayout() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] DBusMenu* menuHandle() const;
 | 
			
		||||
	[[nodiscard]] QString label() const;
 | 
			
		||||
	[[nodiscard]] QString cleanLabel() const;
 | 
			
		||||
	[[nodiscard]] bool enabled() const;
 | 
			
		||||
	[[nodiscard]] QString icon() const;
 | 
			
		||||
	[[nodiscard]] ToggleButtonType::Enum toggleType() const;
 | 
			
		||||
	[[nodiscard]] Qt::CheckState checkState() const;
 | 
			
		||||
	[[nodiscard]] bool isSeparator() const;
 | 
			
		||||
	[[nodiscard]] bool hasChildren() const;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isSeparator() const override;
 | 
			
		||||
	[[nodiscard]] bool enabled() const override;
 | 
			
		||||
	[[nodiscard]] QString text() const override;
 | 
			
		||||
	[[nodiscard]] QString icon() const override;
 | 
			
		||||
	[[nodiscard]] menu::QsMenuButtonType::Enum buttonType() const override;
 | 
			
		||||
	[[nodiscard]] Qt::CheckState checkState() const override;
 | 
			
		||||
	[[nodiscard]] bool hasChildren() const override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isShowingChildren() const;
 | 
			
		||||
	void setShowChildren(bool showChildren);
 | 
			
		||||
	void setShowChildrenRecursive(bool showChildren);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QQmlListProperty<DBusMenuItem> children();
 | 
			
		||||
	[[nodiscard]] QQmlListProperty<menu::QsMenuEntry> children() override;
 | 
			
		||||
 | 
			
		||||
	void updateProperties(const QVariantMap& properties, const QStringList& removed = {});
 | 
			
		||||
	void onChildrenUpdated();
 | 
			
		||||
 | 
			
		||||
	qint32 id = 0;
 | 
			
		||||
	QString mLabel;
 | 
			
		||||
	QString mText;
 | 
			
		||||
	QVector<qint32> mChildren;
 | 
			
		||||
	bool mShowChildren = false;
 | 
			
		||||
	bool childrenLoaded = false;
 | 
			
		||||
	DBusMenu* menu = nullptr;
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void labelChanged();
 | 
			
		||||
	//void mnemonicChanged();
 | 
			
		||||
	void enabledChanged();
 | 
			
		||||
	void iconChanged();
 | 
			
		||||
	void separatorChanged();
 | 
			
		||||
	void toggleTypeChanged();
 | 
			
		||||
	void checkStateChanged();
 | 
			
		||||
	void hasChildrenChanged();
 | 
			
		||||
	void showingChildrenChanged();
 | 
			
		||||
	void childrenChanged();
 | 
			
		||||
	void layoutUpdated();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void sendOpened() const;
 | 
			
		||||
	void sendClosed() const;
 | 
			
		||||
	void sendTriggered() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QString mCleanLabel;
 | 
			
		||||
| 
						 | 
				
			
			@ -163,14 +92,14 @@ private:
 | 
			
		|||
	bool mSeparator = false;
 | 
			
		||||
	QString iconName;
 | 
			
		||||
	DBusMenuPngImage* image = nullptr;
 | 
			
		||||
	ToggleButtonType::Enum mToggleType = ToggleButtonType::None;
 | 
			
		||||
	Qt::CheckState mCheckState = Qt::Checked;
 | 
			
		||||
	menu::QsMenuButtonType::Enum mButtonType = menu::QsMenuButtonType::None;
 | 
			
		||||
	Qt::CheckState mCheckState = Qt::Unchecked;
 | 
			
		||||
	bool displayChildren = false;
 | 
			
		||||
	QVector<qint32> enabledChildren;
 | 
			
		||||
	DBusMenuItem* parentMenu = nullptr;
 | 
			
		||||
 | 
			
		||||
	static qsizetype childrenCount(QQmlListProperty<DBusMenuItem>* property);
 | 
			
		||||
	static DBusMenuItem* childAt(QQmlListProperty<DBusMenuItem>* property, qsizetype index);
 | 
			
		||||
	static qsizetype childrenCount(QQmlListProperty<menu::QsMenuEntry>* property);
 | 
			
		||||
	static menu::QsMenuEntry* childAt(QQmlListProperty<menu::QsMenuEntry>* property, qsizetype index);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, DBusMenuItem* item);
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +121,7 @@ public:
 | 
			
		|||
	dbus::DBusProperty<QString> status {this->properties, "Status"};
 | 
			
		||||
	dbus::DBusProperty<QStringList> iconThemePath {this->properties, "IconThemePath", {}, false};
 | 
			
		||||
 | 
			
		||||
	void prepareToShow(qint32 item, bool sendOpened);
 | 
			
		||||
	void prepareToShow(qint32 item, qint32 depth);
 | 
			
		||||
	void updateLayout(qint32 parent, qint32 depth);
 | 
			
		||||
	void removeRecursive(qint32 id);
 | 
			
		||||
	void sendEvent(qint32 item, const QString& event);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,9 @@ endfunction()
 | 
			
		|||
 | 
			
		||||
# -----
 | 
			
		||||
 | 
			
		||||
qt_add_library(quickshell-wayland STATIC)
 | 
			
		||||
qt_add_library(quickshell-wayland STATIC
 | 
			
		||||
	platformmenu.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# required to make sure the constructor is linked
 | 
			
		||||
add_library(quickshell-wayland-init OBJECT init.cpp)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include <qtenvironmentvariables.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/plugin.hpp"
 | 
			
		||||
#include "platformmenu.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
 | 
			
		||||
#include "wlr_layershell.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +27,8 @@ class WaylandPlugin: public QuickshellPlugin {
 | 
			
		|||
		return isWayland;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void init() override { installPlatformMenuHook(); }
 | 
			
		||||
 | 
			
		||||
	void registerTypes() override {
 | 
			
		||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
 | 
			
		||||
		qmlRegisterType<WaylandPanelInterface>("Quickshell._WaylandOverlay", 1, 0, "PanelWindow");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										32
									
								
								src/wayland/platformmenu.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/wayland/platformmenu.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
#include "platformmenu.hpp"
 | 
			
		||||
 | 
			
		||||
#include <private/qwayland-xdg-shell.h>
 | 
			
		||||
#include <qmargins.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/platformmenu.hpp"
 | 
			
		||||
 | 
			
		||||
using namespace qs::menu::platform;
 | 
			
		||||
using namespace QtWayland;
 | 
			
		||||
 | 
			
		||||
// fixes positioning of submenus when hitting screen edges
 | 
			
		||||
void platformMenuHook(PlatformMenuQMenu* menu) {
 | 
			
		||||
	auto* window = menu->windowHandle();
 | 
			
		||||
 | 
			
		||||
	auto constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x
 | 
			
		||||
	                          | QtWayland::xdg_positioner::constraint_adjustment_flip_y;
 | 
			
		||||
 | 
			
		||||
	window->setProperty("_q_waylandPopupConstraintAdjustment", constraintAdjustment);
 | 
			
		||||
 | 
			
		||||
	if (auto* containingMenu = menu->containingMenu) {
 | 
			
		||||
		auto geom = containingMenu->actionGeometry(menu->menuAction());
 | 
			
		||||
 | 
			
		||||
		// use the first action to find the offsets relative to the containing window
 | 
			
		||||
		auto baseGeom = containingMenu->actionGeometry(containingMenu->actions().first());
 | 
			
		||||
		geom += QMargins(0, baseGeom.top(), 0, baseGeom.top());
 | 
			
		||||
 | 
			
		||||
		window->setProperty("_q_waylandPopupAnchorRect", geom);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void installPlatformMenuHook() { PlatformMenuEntry::registerCreationHook(&platformMenuHook); }
 | 
			
		||||
							
								
								
									
										3
									
								
								src/wayland/platformmenu.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/wayland/platformmenu.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
void installPlatformMenuHook();
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue