Commit c1bb32d5 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

Make it work!

* Cleanup code and use categorized logging
* Monitor action changes (e.g. toggled state of checkbox)
* Monitor menu changes (e.g. menu label changed)
* Handle when app has no menu on startup but gets one later
* Disable GTK menu bar when we're running
parent 30e387dd
......@@ -31,6 +31,7 @@ target_link_libraries(gmenudbusmenuproxy
Qt5::Core
Qt5::X11Extras
Qt5::DBus
KF5::ConfigCore
KF5::WindowSystem
XCB::XCB
)
......
......@@ -107,7 +107,6 @@ const QDBusArgument &operator>>(const QDBusArgument &argument, GMenuActionsChang
return argument;
}
void GDBusMenuTypes_register()
{
static bool registered = false;
......@@ -127,6 +126,7 @@ void GDBusMenuTypes_register()
qDBusRegisterMetaType<GMenuActionMap>();
qDBusRegisterMetaType<GMenuActionsChange>();
qDBusRegisterMetaType<StringBoolMap>();
registered = true;
}
......@@ -30,6 +30,9 @@ class QDBusArgument;
using VariantMapList = QList<QVariantMap>;
Q_DECLARE_METATYPE(VariantMapList);
using StringBoolMap = QMap<QString, bool>;
Q_DECLARE_METATYPE(StringBoolMap);
// Menu item itself (Start method)
struct GMenuItem
{
......
......@@ -26,14 +26,14 @@
int main(int argc, char ** argv)
{
//qputenv("QT_QPA_PLATFORM", "xcb");
qputenv("QT_QPA_PLATFORM", "xcb");
QGuiApplication::setDesktopSettingsAware(false);
QGuiApplication app(argc, argv);
if (!KWindowSystem::isPlatformX11()) {
//qFatal("qdbusmenuproxy is only useful XCB. Aborting");
qFatal("qdbusmenuproxy is only useful XCB. Aborting");
}
auto disableSessionManagement = [](QSessionManager &sm) {
......
This diff is collapsed.
......@@ -81,8 +81,8 @@ signals:
private slots:
void onMenuChanged(const GMenuChangeList &changes);
void onApplicationActionsChanged(const GMenuActionsChange &changes);
void onWindowActionsChanged(const GMenuActionsChange &changes);
void onApplicationActionsChanged(const QStringList &removed, const StringBoolMap &enabledChanges, const QVariantMap &stateChanges, const GMenuActionMap &added);
void onWindowActionsChanged(const QStringList &removed, const StringBoolMap &enabledChanges, const QVariantMap &stateChanges, const GMenuActionMap &added);
private:
void init();
......@@ -95,9 +95,14 @@ private:
bool getAction(const QString &name, GMenuAction &action) const;
void triggerAction(const QString &name, uint timestamp = 0);
void actionsChanged(const QStringList &removed, const StringBoolMap &enabledChanges, const QVariantMap &stateChanges, const GMenuActionMap &added,
GMenuActionMap &actions, const QString &prefix);
static int treeStructureToInt(int subscription, int section, int index);
static void intToTreeStructure(int source, int &subscription, int &section, int &index);
static QString actionNameOfItem(const QVariantMap &item);
static GMenuItem findSection(const QList<GMenuItem> &list, int section);
QVariantMap gMenuToDBusMenuProperties(const QVariantMap &source) const;
......
......@@ -24,13 +24,17 @@
#include "debug.h"
#include <QByteArray>
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusServiceWatcher>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QHash>
#include <QTimer>
#include <QStandardPaths>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KWindowSystem>
#include <QX11Info>
......@@ -56,6 +60,7 @@ static const QByteArray s_kdeNetWmAppMenuObjectPath = QByteArrayLiteral("_KDE_NE
MenuProxy::MenuProxy()
: QObject()
, m_xConnection(QX11Info::connection())
, m_serviceWatcher(new QDBusServiceWatcher(this))
{
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
......@@ -65,31 +70,41 @@ MenuProxy::MenuProxy()
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString &service) {
Q_UNUSED(service);
qDebug() << "Global menu service became available, starting";
qCDebug(DBUSMENUPROXY) << "Global menu service became available, starting";
init();
});
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &service) {
Q_UNUSED(service);
qDebug() << "Global menu service disappeared, cleaning up";
qCDebug(DBUSMENUPROXY) << "Global menu service disappeared, cleaning up";
teardown();
});
// It's fine to do a blocking call here as we're a separate binary with no UI
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(s_dbusMenuRegistrar)) {
qDebug() << "Global menu service is there, starting up right away";
qCDebug(DBUSMENUPROXY) << "Global menu service is running, starting right away";
init();
} else {
qDebug() << "No global menu service available, waiting for it before we do anything";
qCDebug(DBUSMENUPROXY) << "No global menu service available, waiting for it to start before doing anything";
// be sure when started to restore gtk menus when there's no dbus menu around in case we crashed
setGtkShellShowsMenuBar(false);
}
}
MenuProxy::~MenuProxy()
{
teardown();
}
bool MenuProxy::init()
{
if (!QDBusConnection::sessionBus().registerService(s_ourServiceName)) {
qWarning() << "Failed to register DBus service" << s_ourServiceName;
qCWarning(DBUSMENUPROXY) << "Failed to register DBus service" << s_ourServiceName;
return false;
}
setGtkShellShowsMenuBar(true);
connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &MenuProxy::onWindowAdded);
connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &MenuProxy::onWindowRemoved);
......@@ -99,7 +114,7 @@ bool MenuProxy::init()
}
if (m_menus.isEmpty()) {
qDebug() << "Up and running but no menus in sight";
qCDebug(DBUSMENUPROXY) << "Up and running but no menus in sight";
}
return true;
......@@ -107,6 +122,8 @@ bool MenuProxy::init()
void MenuProxy::teardown()
{
setGtkShellShowsMenuBar(false);
QDBusConnection::sessionBus().unregisterService(s_ourServiceName);
disconnect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &MenuProxy::onWindowAdded);
......@@ -116,50 +133,69 @@ void MenuProxy::teardown()
m_menus.clear();
}
void MenuProxy::setGtkShellShowsMenuBar(bool show)
{
qCDebug(DBUSMENUPROXY) << "Setting gtk-shell-shows-menu-bar to" << show << "which will" << (show ? "hide" : "show") << "menu bars in applications";
// mostly taken from kde-gtk-config
QString root = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
if (root.isEmpty()) {
root = QFileInfo(QDir::home(), QStringLiteral(".config")).absoluteFilePath();
}
const QString settingsFilePath = root + QStringLiteral("/gtk-3.0/settings.ini");
auto cfg = KSharedConfig::openConfig(settingsFilePath, KConfig::NoGlobals);
KConfigGroup group(cfg, "Settings");
if (show) {
group.writeEntry("gtk-shell-shows-menubar", true);
} else {
group.deleteEntry("gtk-shell-shows-menubar");
}
group.sync();
// TODO use gconf/dconf directly or at least signal a change somehow?
}
void MenuProxy::onWindowAdded(WId id)
{
if (m_menus.contains(id)) {
qDebug() << "Already know window" << id;
return;
}
//#if HAVE_X11
if (KWindowSystem::isPlatformX11()) {
// TODO split that stuff out so we can do early returns and not a kilometer of indentation
const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_gtkUniqueBusName));
const QString applicationObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkApplicationObjectPath));
const QString windowObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkWindowObjectPath));
const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_gtkUniqueBusName));
const QString applicationObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkApplicationObjectPath));
const QString windowObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkWindowObjectPath));
if (serviceName.isEmpty() || applicationObjectPath.isEmpty() || windowObjectPath.isEmpty()) {
return;
}
if (serviceName.isEmpty() || applicationObjectPath.isEmpty() || windowObjectPath.isEmpty()) {
return;
}
QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkMenuBarObjectPath));
if (menuObjectPath.isEmpty()) {
// try generic app menu
menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkAppMenuObjectPath));
}
QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkMenuBarObjectPath));
if (menuObjectPath.isEmpty()) {
// try generic app menu
menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkAppMenuObjectPath));
}
if (menuObjectPath.isEmpty()) {
return;
}
if (menuObjectPath.isEmpty()) {
return;
}
Menu *menu = new Menu(id, serviceName, applicationObjectPath, windowObjectPath, menuObjectPath);
m_menus.insert(id, menu);
Menu *menu = new Menu(id, serviceName, applicationObjectPath, windowObjectPath, menuObjectPath);
m_menus.insert(id, menu);
connect(menu, &Menu::requestWriteWindowProperties, this, [this, menu] {
Q_ASSERT(!menu->proxyObjectPath().isEmpty());
connect(menu, &Menu::requestWriteWindowProperties, this, [this, menu] {
Q_ASSERT(!menu->proxyObjectPath().isEmpty());
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuServiceName, s_ourServiceName.toUtf8());
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuObjectPath, menu->proxyObjectPath().toUtf8());
});
connect(menu, &Menu::requestRemoveWindowProperties, this, [this, menu] {
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuServiceName, QByteArray());
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuObjectPath, QByteArray());
});
}
//#endif // HAVE_X11
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuServiceName, s_ourServiceName.toUtf8());
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuObjectPath, menu->proxyObjectPath().toUtf8());
});
connect(menu, &Menu::requestRemoveWindowProperties, this, [this, menu] {
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuServiceName, QByteArray());
writeWindowProperty(menu->winId(), s_kdeNetWmAppMenuObjectPath, QByteArray());
});
}
void MenuProxy::onWindowRemoved(WId id)
......@@ -184,7 +220,7 @@ QByteArray MenuProxy::getWindowPropertyString(WId id, const QByteArray &name)
auto propertyCookie = xcb_get_property(c, false, id, atom, XCB_ATOM_ANY, 0, MAX_PROP_SIZE);
QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, NULL));
if (propertyReply.isNull()) {
qDebug() << "property reply was null";
qCWarning(DBUSMENUPROXY) << "XCB property reply for atom" << name << "on" << id << "was null";
return value;
}
......@@ -202,31 +238,27 @@ QByteArray MenuProxy::getWindowPropertyString(WId id, const QByteArray &name)
void MenuProxy::writeWindowProperty(WId id, const QByteArray &name, const QByteArray &value)
{
auto *c = QX11Info::connection(); // FIXME cache
auto atom = getAtom(name);
if (atom == XCB_ATOM_NONE) {
return;
}
if (value.isEmpty()) {
xcb_delete_property(c,id, atom);
xcb_delete_property(m_xConnection, id, atom);
} else {
xcb_change_property(c, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING,
xcb_change_property(m_xConnection, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING,
8, value.length(), value.constData());
}
}
xcb_atom_t MenuProxy::getAtom(const QByteArray &name)
{
auto *c = QX11Info::connection(); // FIXME cache
static QHash<QByteArray, xcb_atom_t> s_atoms;
auto atom = s_atoms.value(name, XCB_ATOM_NONE);
if (atom == XCB_ATOM_NONE) {
const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData());
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, Q_NULLPTR));
const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(m_xConnection, false, name.length(), name.constData());
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(m_xConnection, atomCookie, Q_NULLPTR));
if (!atomReply.isNull()) {
atom = atomReply->atom;
if (atom != XCB_ATOM_NONE) {
......@@ -237,5 +269,3 @@ xcb_atom_t MenuProxy::getAtom(const QByteArray &name)
return atom;
}
MenuProxy::~MenuProxy() = default;
......@@ -30,7 +30,7 @@ class QDBusServiceWatcher;
class Menu;
class MenuProxy : public QObject//, public QAbstractNativeEventFilter
class MenuProxy : public QObject
{
Q_OBJECT
......@@ -38,9 +38,6 @@ public:
MenuProxy();
~MenuProxy() override;
protected:
//bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) override;
private Q_SLOTS:
void onWindowAdded(WId id);
void onWindowRemoved(WId id);
......@@ -49,6 +46,10 @@ private:
bool init();
void teardown();
void setGtkShellShowsMenuBar(bool show);
xcb_connection_t *m_xConnection;
// FIXME the get one reads "UTF8String" (reads gnome) the write thing writes "String" (writes kde)
QByteArray getWindowPropertyString(WId id, const QByteArray &name);
void writeWindowProperty(WId id, const QByteArray &name, const QByteArray &value);
......
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