Commit bc6f94f0 authored by Jan Blackquill's avatar Jan Blackquill 🌈 Committed by Jan Blackquill
Browse files

QDbusMenuBar: properly handle app-wide menubars

When a QPlatformMenuBar has a nullptr parent; it's supposed to be
appwide (this is documented in public QMenuBar docs), so we now
make sure to register app-wide menubars for all windows that
don't already have a menubar of their own.
parent 18f88bb5
Pipeline #183081 passed with stage
in 1 minute and 8 seconds
......@@ -63,12 +63,12 @@ KdePlatformTheme::KdePlatformTheme()
// explicitly not KWindowSystem::isPlatformWayland to not include the kwin process
if (QGuiApplication::platformName() == QLatin1String("wayland")) {
m_kwaylandIntegration.reset(new KWaylandIntegration());
m_kwaylandIntegration.reset(new KWaylandIntegration(this));
}
#if HAVE_X11
if (KWindowSystem::isPlatformX11()) {
m_x11Integration.reset(new X11Integration());
m_x11Integration.reset(new X11Integration(this));
m_x11Integration->init();
}
#endif
......@@ -346,33 +346,14 @@ QPlatformSystemTrayIcon *KdePlatformTheme::createPlatformSystemTrayIcon() const
QPlatformMenuBar *KdePlatformTheme::createPlatformMenuBar() const
{
if (isDBusGlobalMenuAvailable()) {
auto *menu = new QDBusMenuBar();
auto *menu = new QDBusMenuBar(const_cast<KdePlatformTheme *>(this));
QObject::connect(menu, &QDBusMenuBar::windowChanged, menu, [this, menu](QWindow *newWindow, QWindow *oldWindow) {
const QString &serviceName = QDBusConnection::sessionBus().baseService();
const QString &objectPath = menu->objectPath();
if (m_x11Integration) {
if (oldWindow) {
m_x11Integration->setWindowProperty(oldWindow, s_x11AppMenuServiceNamePropertyName, {});
m_x11Integration->setWindowProperty(oldWindow, s_x11AppMenuObjectPathPropertyName, {});
}
if (newWindow) {
m_x11Integration->setWindowProperty(newWindow, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8());
m_x11Integration->setWindowProperty(newWindow, s_x11AppMenuObjectPathPropertyName, objectPath.toUtf8());
}
}
if (m_kwaylandIntegration) {
if (oldWindow) {
m_kwaylandIntegration->setAppMenu(oldWindow, QString(), QString());
}
if (newWindow) {
m_kwaylandIntegration->setAppMenu(newWindow, serviceName, objectPath);
}
}
setMenuBarForWindow(oldWindow, {}, {});
setMenuBarForWindow(newWindow, serviceName, objectPath);
});
return menu;
......@@ -404,3 +385,64 @@ bool KdePlatformTheme::useXdgDesktopPortal()
static bool usePortal = qEnvironmentVariableIntValue("PLASMA_INTEGRATION_USE_PORTAL") == 1;
return usePortal;
}
inline bool windowRelevantForGlobalMenu(QWindow* window)
{
return !(window->type() & Qt::WindowType::Popup);
}
void KdePlatformTheme::globalMenuBarExistsNow()
{
const QString &serviceName = QDBusConnection::sessionBus().baseService();
const QString &objectPath = QDBusMenuBar::globalMenuBar()->objectPath();
for (auto *window : qApp->topLevelWindows()) {
if (QDBusMenuBar::menuBarForWindow(window))
continue;
if (!windowRelevantForGlobalMenu(window))
return;
setMenuBarForWindow(window, serviceName, objectPath);
}
}
void KdePlatformTheme::windowCreated(QWindow *window)
{
if (!QDBusMenuBar::globalMenuBar())
return;
if (QDBusMenuBar::menuBarForWindow(window))
return;
const QString &serviceName = QDBusConnection::sessionBus().baseService();
const QString &objectPath = QDBusMenuBar::globalMenuBar()->objectPath();
setMenuBarForWindow(window, serviceName, objectPath);
}
void KdePlatformTheme::globalMenuBarNoLongerExists()
{
for (auto *window : qApp->topLevelWindows()) {
if (QDBusMenuBar::menuBarForWindow(window))
continue;
if (!windowRelevantForGlobalMenu(window))
return;
setMenuBarForWindow(window, {}, {});
}
}
void KdePlatformTheme::setMenuBarForWindow(QWindow* window, const QString& serviceName, const QString& objectPath) const
{
if (!window)
return;
if (m_x11Integration) {
m_x11Integration->setWindowProperty(window, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8());
m_x11Integration->setWindowProperty(window, s_x11AppMenuObjectPathPropertyName, objectPath.toUtf8());
}
if (m_kwaylandIntegration) {
m_kwaylandIntegration->setAppMenu(window, serviceName, objectPath);
}
}
......@@ -42,7 +42,12 @@ public:
QPlatformMenuBar *createPlatformMenuBar() const override;
void globalMenuBarExistsNow();
void globalMenuBarNoLongerExists();
void windowCreated(QWindow *window);
private:
void setMenuBarForWindow(QWindow* window, const QString& serviceName, const QString& objectPath) const;
void loadSettings();
void setQtQuickControlsTheme();
......
......@@ -11,6 +11,7 @@
#include <qpa/qplatformnativeinterface.h>
#include <qtwaylandclientversion.h>
#include "qdbusmenubar_p.h"
#include "qwayland-appmenu.h"
#include "qwayland-server-decoration-palette.h"
......@@ -58,10 +59,11 @@ using ServerSideDecorationPalette = QtWayland::org_kde_kwin_server_decoration_pa
Q_DECLARE_METATYPE(AppMenu *);
Q_DECLARE_METATYPE(ServerSideDecorationPalette *);
KWaylandIntegration::KWaylandIntegration()
KWaylandIntegration::KWaylandIntegration(KdePlatformTheme *platformTheme)
: QObject()
, m_appMenuManager(new AppMenuManager)
, m_paletteManager(new ServerSideDecorationPaletteManager)
, m_platformTheme(platformTheme)
{
QCoreApplication::instance()->installEventFilter(this);
}
......@@ -112,6 +114,15 @@ bool KWaylandIntegration::eventFilter(QObject *watched, QEvent *event)
installColorScheme(w);
}
}
} else if (event->type() == QEvent::PlatformSurface) {
if (QWindow *w = qobject_cast<QWindow *>(watched)) {
QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
if (!w->flags().testFlag(Qt::ForeignWindow)) {
if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
m_platformTheme->windowCreated(w);
}
}
}
}
return false;
......@@ -138,7 +149,13 @@ void KWaylandIntegration::shellSurfaceCreated(QWindow *w)
if (m_appMenuManager->isActive()) {
auto menu = new AppMenu(m_appMenuManager->create(s));
w->setProperty("org.kde.plasma.integration.appmenu", QVariant::fromValue(menu));
menu->set_address(m_windowInfo[w].appMenuServiceName, m_windowInfo[w].appMenuObjectPath);
auto menuBar = QDBusMenuBar::menuBarForWindow(w);
if (!menuBar) {
menuBar = QDBusMenuBar::globalMenuBar();
}
if (menuBar) {
menu->set_address(QDBusConnection::sessionBus().baseService(), menuBar->objectPath());
}
}
}
......@@ -182,13 +199,6 @@ void KWaylandIntegration::installColorScheme(QWindow *w)
void KWaylandIntegration::setAppMenu(QWindow *window, const QString &serviceName, const QString &objectPath)
{
if (!m_windowInfo.contains(window)) { // effectively makes this connect unique
connect(window, &QObject::destroyed, this, [=]() {
m_windowInfo.remove(window);
});
}
m_windowInfo[window].appMenuServiceName = serviceName;
m_windowInfo[window].appMenuObjectPath = objectPath;
auto menu = window->property("org.kde.plasma.integration.appmenu").value<AppMenu *>();
if (menu) {
menu->set_address(serviceName, objectPath);
......
......@@ -6,6 +6,7 @@
#ifndef KWAYLANDINTEGRATION_H
#define KWAYLANDINTEGRATION_H
#include "kdeplatformtheme.h"
#include <QHash>
#include <QObject>
#include <QtWaylandClient/QWaylandClientExtensionTemplate>
......@@ -19,7 +20,7 @@ class KWaylandIntegration : public QObject
{
Q_OBJECT
public:
explicit KWaylandIntegration();
explicit KWaylandIntegration(KdePlatformTheme *platformTheme);
~KWaylandIntegration() override;
void setAppMenu(QWindow *window, const QString &serviceName, const QString &objectPath);
......@@ -37,11 +38,7 @@ private:
QScopedPointer<AppMenuManager> m_appMenuManager;
QScopedPointer<ServerSideDecorationPaletteManager> m_paletteManager;
struct WindowInfo {
QString appMenuServiceName;
QString appMenuObjectPath;
};
QHash<QWindow *, WindowInfo> m_windowInfo;
KdePlatformTheme *m_platformTheme;
};
#endif
......@@ -17,20 +17,29 @@ QT_BEGIN_NAMESPACE
#define REGISTRAR_SERVICE QLatin1String("com.canonical.AppMenu.Registrar")
#define REGISTRAR_PATH QLatin1String("/com/canonical/AppMenu/Registrar")
QDBusMenuBar::QDBusMenuBar()
QDBusMenuBar::QDBusMenuBar(KdePlatformTheme *platformTheme)
: QPlatformMenuBar()
, m_menu(new QDBusPlatformMenu())
, m_menuAdaptor(new QDBusMenuAdaptor(m_menu))
, m_platformTheme(platformTheme)
{
QDBusMenuItem::registerDBusTypes();
connect(m_menu, &QDBusPlatformMenu::propertiesUpdated, m_menuAdaptor, &QDBusMenuAdaptor::ItemsPropertiesUpdated);
connect(m_menu, &QDBusPlatformMenu::updated, m_menuAdaptor, &QDBusMenuAdaptor::LayoutUpdated);
connect(m_menu, SIGNAL(popupRequested(int,uint)), m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint)));
connect(m_menu, SIGNAL(popupRequested(int, uint)), m_menuAdaptor, SIGNAL(ItemActivationRequested(int, uint)));
}
QDBusMenuBar::~QDBusMenuBar()
{
unregisterMenuBar();
if (s_globalMenuBar == this) {
s_globalMenuBar = nullptr;
m_platformTheme->globalMenuBarNoLongerExists();
}
if (this == s_menuBars.value(m_window))
s_menuBars.remove(m_window);
unregisterMenuBarX11(m_window);
delete m_menuAdaptor;
delete m_menu;
qDeleteAll(m_menuItems);
......@@ -85,22 +94,47 @@ void QDBusMenuBar::syncMenu(QPlatformMenu *menu)
void QDBusMenuBar::handleReparent(QWindow *newParentWindow)
{
if (newParentWindow == m_window) {
// if the parent is set to nullptr on our first time around,
// this is supposed to be an app-wide menu bar
// so we only care if nullptr == nullptr after our first round
if (m_initted && newParentWindow == m_window) {
return;
}
m_initted = true;
QWindow *oldWindow = m_window;
unregisterMenuBar();
if (this == s_menuBars.value(oldWindow))
s_menuBars.remove(oldWindow);
unregisterMenuBarX11(m_window);
m_window = newParentWindow;
s_menuBars[newParentWindow] = this;
if (newParentWindow) {
registerMenuBar();
if (s_globalMenuBar == this) {
s_globalMenuBar = nullptr;
m_platformTheme->globalMenuBarNoLongerExists();
}
if (createDBusMenuBar()) {
registerMenuBarX11(m_window, m_objectPath);
}
} else if (!s_globalMenuBar) {
s_globalMenuBar = this;
createDBusMenuBar();
m_platformTheme->globalMenuBarExistsNow();
} else {
qWarning() << "There's already a global menu bar...";
}
Q_EMIT windowChanged(newParentWindow, oldWindow);
}
QDBusMenuBar *QDBusMenuBar::globalMenuBar()
{
return s_globalMenuBar;
}
QPlatformMenu *QDBusMenuBar::menuForTag(quintptr tag) const
{
QDBusPlatformMenuItem *menuItem = m_menuItems.value(tag);
......@@ -114,43 +148,62 @@ QPlatformMenu *QDBusMenuBar::createMenu() const
return new QDBusPlatformMenu;
}
void QDBusMenuBar::registerMenuBar()
bool QDBusMenuBar::createDBusMenuBar()
{
static uint menuBarId = 0;
if (!m_window) {
qWarning("Cannot register window menu without window");
return;
}
QDBusConnection connection = QDBusConnection::sessionBus();
m_objectPath = QStringLiteral("/MenuBar/%1").arg(++menuBarId);
if (!connection.registerObject(m_objectPath, m_menu))
return false;
return true;
}
void QDBusMenuBar::uncreateDBusMenuBar()
{
QDBusConnection connection = QDBusConnection::sessionBus();
if (!m_objectPath.isEmpty())
connection.unregisterObject(m_objectPath);
}
QDBusMenuBar *QDBusMenuBar::menuBarForWindow(QWindow *window)
{
return s_menuBars.value(window);
}
void QDBusMenuBar::registerMenuBarX11(QWindow *window, const QString &objectPath)
{
if (!window) {
qWarning("Cannot register window menu without window");
return;
}
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this);
QDBusPendingReply<> r = registrar.RegisterWindow(static_cast<uint>(window()->winId()), QDBusObjectPath(m_objectPath));
QDBusConnection connection = QDBusConnection::sessionBus();
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, window);
QDBusPendingReply<> r = registrar.RegisterWindow(static_cast<uint>(window->winId()), QDBusObjectPath(objectPath));
r.waitForFinished();
if (r.isError()) {
qWarning("Failed to register window menu, reason: %s (\"%s\")", qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message()));
connection.unregisterObject(m_objectPath);
connection.unregisterObject(objectPath);
}
}
void QDBusMenuBar::unregisterMenuBar()
{
QDBusConnection connection = QDBusConnection::sessionBus();
QDBusMenuBar *QDBusMenuBar::s_globalMenuBar = nullptr;
QMap<QWindow *, QDBusMenuBar *> QDBusMenuBar::s_menuBars;
if (m_window) {
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this);
QDBusPendingReply<> r = registrar.UnregisterWindow(static_cast<uint>(window()->winId()));
r.waitForFinished();
if (r.isError())
qWarning("Failed to unregister window menu, reason: %s (\"%s\")", qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message()));
void QDBusMenuBar::unregisterMenuBarX11(QWindow *window)
{
if (!window) {
return;
}
if (!m_objectPath.isEmpty())
connection.unregisterObject(m_objectPath);
QDBusConnection connection = QDBusConnection::sessionBus();
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, window);
QDBusPendingReply<> r = registrar.UnregisterWindow(static_cast<uint>(window->winId()));
r.waitForFinished();
if (r.isError())
qWarning("Failed to unregister window menu, reason: %s (\"%s\")", qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message()));
}
QT_END_NAMESPACE
......@@ -23,6 +23,8 @@
// We mean it.
//
#include "kdeplatformtheme.h"
#include <QHash>
#include <QString>
#include <QWindow>
......@@ -46,7 +48,7 @@ class QDBusMenuBar : public QPlatformMenuBar
Q_OBJECT
public:
QDBusMenuBar();
QDBusMenuBar(KdePlatformTheme *platformTheme);
~QDBusMenuBar() override;
void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override;
......@@ -55,6 +57,8 @@ public:
void handleReparent(QWindow *newParentWindow) override;
QPlatformMenu *menuForTag(quintptr tag) const override;
QPlatformMenu *createMenu() const override;
static QDBusMenuBar *globalMenuBar();
static QDBusMenuBar *menuBarForWindow(QWindow *window);
QWindow *window() const
{
......@@ -74,11 +78,18 @@ private:
QHash<quintptr, QDBusPlatformMenuItem *> m_menuItems;
QPointer<QWindow> m_window;
QString m_objectPath;
bool m_initted = false;
KdePlatformTheme *m_platformTheme;
static QDBusMenuBar *s_globalMenuBar;
static QMap<QWindow *, QDBusMenuBar *> s_menuBars;
QDBusPlatformMenuItem *menuItemForMenu(QPlatformMenu *menu);
static void updateMenuItem(QDBusPlatformMenuItem *item, QPlatformMenu *menu);
void registerMenuBar();
void unregisterMenuBar();
bool createDBusMenuBar();
void uncreateDBusMenuBar();
static void registerMenuBarX11(QWindow *window, const QString &objectPath);
static void unregisterMenuBarX11(QWindow *window);
};
QT_END_NAMESPACE
......
......@@ -22,8 +22,9 @@
static const char s_schemePropertyName[] = "KDE_COLOR_SCHEME_PATH";
static const QByteArray s_blurBehindPropertyName = QByteArrayLiteral("ENABLE_BLUR_BEHIND_HINT");
X11Integration::X11Integration()
X11Integration::X11Integration(KdePlatformTheme *platformTheme)
: QObject()
, m_platformTheme(platformTheme)
{
}
......@@ -50,6 +51,7 @@ bool X11Integration::eventFilter(QObject *watched, QEvent *event)
QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent *>(event);
if (!w->flags().testFlag(Qt::ForeignWindow)) {
if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
m_platformTheme->windowCreated(w);
auto flags = w->flags();
// A recent KWin change means it now follows WindowButtonHints on X11
// Some KDE applications use QDialogs for their main window,
......
......@@ -7,6 +7,7 @@
#ifndef X11INTEGRATION_H
#define X11INTEGRATION_H
#include "kdeplatformtheme.h"
#include <QHash>
#include <QObject>
#include <xcb/xcb.h>
......@@ -17,7 +18,7 @@ class X11Integration : public QObject
{
Q_OBJECT
public:
explicit X11Integration();
explicit X11Integration(KdePlatformTheme *platformTheme);
~X11Integration() override;
void init();
......@@ -29,6 +30,7 @@ private:
void installColorScheme(QWindow *w);
void installDesktopFileName(QWindow *w);
QHash<QByteArray, xcb_atom_t> m_atoms;
KdePlatformTheme *m_platformTheme;
};
#endif
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment