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

Add gmenu-dbusmenu-proxy

This will mediate between applications using GMenu (like LibreOffice) and expose their menus
using our DBusMenu protocol so that no changes on our global menu side is needed.

WIP: So far all it can do is detect windows with GMenus attached to it, register a DBus service
and then announce the corresponding DBusMenu paths.
parent 56600554
......@@ -166,6 +166,8 @@ add_subdirectory(solidautoeject)
ecm_optional_add_subdirectory(xembed-sni-proxy)
ecm_optional_add_subdirectory(gmenu-dbusmenu-proxy)
add_subdirectory(soliduiserver)
if(KF5Holidays_FOUND)
......
add_definitions(-DQT_NO_CAST_TO_ASCII
-DQT_NO_CAST_FROM_ASCII
-DQT_NO_URL_CAST_FROM_STRING
-DQT_NO_CAST_FROM_BYTEARRAY)
find_package(XCB
REQUIRED COMPONENTS
XCB
)
set(GMENU_DBUSMENU_PROXY_SRCS
main.cpp
menuproxy.cpp
menu.cpp
gdbusmenutypes_p.cpp
../libdbusmenuqt/dbusmenutypes_p.cpp
)
# FIXME use the one in libdbusmenuqt rather than a copy but for testing and meddling with it use a copy for now
#qt5_add_dbus_adaptor(GMENU_DBUSMENU_PROXY_SRCS ../libdbusmenuqt/com.canonical.dbusmenu.xml menu.h Menu)
qt5_add_dbus_adaptor(GMENU_DBUSMENU_PROXY_SRCS com.canonical.dbusmenu.xml menu.h Menu)
ecm_qt_declare_logging_category(GMENU_DBUSMENU_PROXY_SRCS HEADER debug.h
IDENTIFIER DBUSMENUPROXY
CATEGORY_NAME kde.dbusmenuproxy
DEFAULT_SEVERITY Info)
add_executable(gmenudbusmenuproxy ${GMENU_DBUSMENU_PROXY_SRCS})
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
target_link_libraries(gmenudbusmenuproxy
Qt5::Core
Qt5::X11Extras
Qt5::DBus
KF5::WindowSystem
XCB::XCB
)
install(TARGETS gmenudbusmenuproxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES gmenudbusmenuproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
<interface name="com.canonical.dbusmenu">
<property name="Version" type="u" access="read"/>
<property name="Status" type="s" access="read"/>
<signal name="ItemsPropertiesUpdated">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusMenuItemKeysList"/>
<arg type="a(ia{sv})" direction="out"/>
<arg type="a(ias)" direction="out"/>
</signal>
<signal name="LayoutUpdated">
<arg name="revision" type="u" direction="out"/>
<arg name="parentId" type="i" direction="out"/>
</signal>
<signal name="ItemActivationRequested">
<arg name="id" type="i" direction="out"/>
<arg name="timeStamp" type="u" direction="out"/>
</signal>
<method name="Event">
<arg name="id" type="i" direction="in"/>
<arg name="eventId" type="s" direction="in"/>
<arg name="data" type="v" direction="in"/>
<arg name="timestamp" type="u" direction="in"/>
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<method name="GetProperty">
<arg type="v" direction="out"/>
<arg name="id" type="i" direction="in"/>
<arg name="property" type="s" direction="in"/>
</method>
<method name="GetLayout">
<annotation value="DBusMenuLayoutItem" name="org.qtproject.QtDBus.QtTypeName.Out1"/>
<arg direction="in" type="i" name="parentId"/>
<arg direction="in" type="i" name="recursionDepth"/>
<arg direction="in" type="as" name="propertyNames"/>
<arg direction="out" type="u" name="revision"/>
<arg direction="out" type="(ia{sv}av)" name="layout"/>
</method>
<method name="GetGroupProperties">
<arg type="a(ia{sv})" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusMenuItemList"/>
<arg name="ids" type="ai" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QList&lt;int&gt;"/>
<arg name="propertyNames" type="as" direction="in"/>
</method>
<method name="AboutToShow">
<arg type="b" direction="out"/>
<arg name="id" type="i" direction="in"/>
</method>
</interface>
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "gdbusmenutypes_p.h"
#include <QDBusArgument>
#include <QDBusMetaType>
// GMenuItem
QDBusArgument &operator<<(QDBusArgument &argument, const GMenuItem &item)
{
argument.beginStructure();
argument << item.id << item.count << item.items;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, GMenuItem &item)
{
argument.beginStructure();
argument >> item.id >> item.count >> item.items;
argument.endStructure();
return argument;
}
// GMenuChange
QDBusArgument &operator<<(QDBusArgument &argument, const GMenuChange &item)
{
argument.beginStructure();
argument << item.id << item.count << item.changePosition << item.itemsToRemoveCount << item.itemsToInsert;
argument.endStructure();
return argument;
}
const QDBusArgument &operator>>(const QDBusArgument &argument, GMenuChange &item)
{
argument.beginStructure();
argument >> item.id >> item.count >> item.changePosition >> item.itemsToRemoveCount >> item.itemsToInsert;
argument.endStructure();
return argument;
}
void GDBusMenuTypes_register()
{
static bool registered = false;
if (registered) {
return;
}
qDBusRegisterMetaType<GMenuItem>();
qDBusRegisterMetaType<GMenuItemList>();
qDBusRegisterMetaType<GMenuChange>();
qDBusRegisterMetaType<GMenuChangeList>();
registered = true;
}
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#pragma once
#include <QHash>
#include <QList>
#include <QVariant>
class QDBusArgument;
// Various
using VariantHashList = QList<QVariantHash>;
// Menu item itself (Start method)
struct GMenuItem
{
uint id;
uint count;
VariantHashList items;
};
Q_DECLARE_METATYPE(GMenuItem)
QDBusArgument &operator<<(QDBusArgument &argument, const GMenuItem &item);
const QDBusArgument &operator>>(const QDBusArgument &argument, GMenuItem &item);
using GMenuItemList = QList<GMenuItem>;
// Changes of a menu item (Changed signal)
struct GMenuChange
{
uint id;
uint count;
uint changePosition;
uint itemsToRemoveCount;
VariantHashList itemsToInsert;
};
Q_DECLARE_METATYPE(GMenuChange)
QDBusArgument &operator<<(QDBusArgument &argument, const GMenuChange &item);
const QDBusArgument &operator>>(const QDBusArgument &argument, GMenuChange &item);
using GMenuChangeList = QList<GMenuChange>;
void GDBusMenuTypes_register();
[Desktop Entry]
Exec=gmenudbusmenuproxy
Name=GMenuDBusMenuProxy
Type=Application
X-KDE-StartupNotify=false
OnlyShowIn=KDE;
X-KDE-autostart-phase=0
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <QGuiApplication>
#include <QSessionManager>
#include <KWindowSystem>
#include "menuproxy.h"
int main(int argc, char ** argv)
{
//qputenv("QT_QPA_PLATFORM", "xcb");
QGuiApplication::setDesktopSettingsAware(false);
QGuiApplication app(argc, argv);
if (!KWindowSystem::isPlatformX11()) {
//qFatal("qdbusmenuproxy is only useful XCB. Aborting");
}
auto disableSessionManagement = [](QSessionManager &sm) {
sm.setRestartHint(QSessionManager::RestartNever);
};
QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement);
QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement);
app.setQuitOnLastWindowClosed(false);
MenuProxy proxy;
return app.exec();
}
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "menu.h"
#include <QDebug>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QList>
#include <QVariantList>
#include <QWindow>
#include "dbusmenuadaptor.h"
static const QString s_orgGtkMenus = QStringLiteral("org.gtk.Menus");
Menu::Menu(WId winId, const QString &serviceName, const QString &objectPath)
: QObject()
, m_winId(winId)
, m_serviceName(serviceName)
, m_objectPath(objectPath)
{
qDebug() << "Created menu on" << m_serviceName << "at" << m_objectPath;
// FIXME doesn't work work
if (!QDBusConnection::sessionBus().connect(m_serviceName,
m_objectPath,
s_orgGtkMenus,
QStringLiteral("Changed"),
this,
SLOT(onMenuChanged(GMenuChangeList)))) {
qWarning() << "Failed to subscribe to menu changes in" << m_serviceName << "at" << m_objectPath;
}
start({0}); // root menu
}
Menu::~Menu()
{
stop(m_subscriptions);
}
WId Menu::winId() const
{
return m_winId;
}
QString Menu::serviceName() const
{
return m_serviceName;
}
QString Menu::objectPath() const
{
return m_objectPath;
}
QString Menu::proxyObjectPath() const
{
return m_proxyObjectPath;
}
void Menu::start(const QList<uint> &ids)
{
GDBusMenuTypes_register();
// TODO watch service disappearing?
// dbus-send --print-reply --session --dest=:1.103 /org/libreoffice/window/104857641/menus/menubar org.gtk.Menus.Start array:uint32:0
QDBusMessage msg = QDBusMessage::createMethodCall(m_serviceName,
m_objectPath,
s_orgGtkMenus,
QStringLiteral("Start"));
msg.setArguments({QVariant::fromValue(ids)});
QDBusPendingReply<GMenuItemList> reply = QDBusConnection::sessionBus().asyncCall(msg);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, ids](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<GMenuItemList> reply = *watcher;
if (reply.isError()) {
qWarning() << "Failed to start subscription to" << ids << "from" << m_serviceName << "at" << m_objectPath << reply.error();
} else {
const bool wasSubscribed = !m_subscriptions.isEmpty();
const auto menus = reply.value();
for (auto menu : menus) {
m_menus.insert(menu.id, menu.items);
// TODO are we subscribed to all it returns or just to the ones we requested?
m_subscriptions.append(menu.id);
}
// first time we successfully requested a menu, announce that this window supports DBusMenu
if (!m_subscriptions.isEmpty() && !wasSubscribed) {
if (registerDBusObject()) {
emit requestWriteWindowProperties();
}
}
}
watcher->deleteLater();
});
}
void Menu::stop(const QList<uint> &ids)
{
// TODO
Q_UNUSED(ids);
}
void Menu::onMenuChanged(const GMenuChangeList &changes)
{
// TODO
Q_UNUSED(changes);
}
bool Menu::registerDBusObject()
{
Q_ASSERT(m_proxyObjectPath.isEmpty());
Q_ASSERT(!m_menus.isEmpty());
static int menus = 0;
++menus;
const QString objectPath = QStringLiteral("/MenuBar/%1").arg(QString::number(menus));
qDebug() << "register as object path" << objectPath;
if (!QDBusConnection::sessionBus().registerObject(objectPath, this)) {
qWarning() << "Failed to register object" << this << "at" << objectPath;
return false;
}
new DbusmenuAdaptor(this); // do this before registering the object?
m_proxyObjectPath = objectPath;
return true;
}
// DBus
bool Menu::AboutToShow(int id)
{
qDebug() << "about to show" << id;
return false;
}
void Menu::Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp)
{
qDebug() << "event" << id << eventId << data.variant() << timestamp;
}
DBusMenuItemList Menu::GetGroupProperties(const QList<int> &ids, const QStringList &propertyNames)
{
qDebug() << "get group props" << ids << propertyNames;
return DBusMenuItemList();
}
// FIXME doesn't work :(
// No such method 'GetLayout' in interface 'com.canonical.dbusmenu' at object path '/MenuBar/1' (signature 'iias')
uint Menu::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &item)
{
qDebug() << "getlayout" << parentId << "depth" << recursionDepth << propertyNames;// << item.id << item.properties;
item.id = 0;
item.properties = {
{QStringLiteral("title"), QStringLiteral("hello")}
};
return 1; // revision
}
QDBusVariant Menu::GetProperty(int id, const QString &property)
{
qDebug() << "get property" << id << property;
QDBusVariant value;
return value;
}
QString Menu::status() const
{
return QStringLiteral("normal");
}
uint Menu::version() const
{
return 4;
}
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#pragma once
#include <QObject>
#include <QString>
#include <QVector>
#include <QWindow> // for WId
#include "gdbusmenutypes_p.h"
#include "../libdbusmenuqt/dbusmenutypes_p.h"
class QDBusVariant;
class Menu : public QObject
{
Q_OBJECT
// DBus
Q_PROPERTY(QString Status READ status)
Q_PROPERTY(uint Version READ version)
public:
Menu(WId winId, const QString &serviceName, const QString &objectPath);
~Menu();
WId winId() const;
QString serviceName() const;
QString objectPath() const;
QString proxyObjectPath() const;
// DBus
bool AboutToShow(int id);
void Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp);
DBusMenuItemList GetGroupProperties(const QList<int> &ids, const QStringList &propertyNames);
uint GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &item);
QDBusVariant GetProperty(int id, const QString &property);
QString status() const;
uint version() const;
signals:
// don't want to pollute X stuff into Menu, let all of that be in MenuProxy
void requestWriteWindowProperties();
void requestRemoveWindowProperties();
// DBus
void ItemActivationRequested(int id, uint timestamp);
void ItemsPropertiesUpdated(const DBusMenuItemList &updatedProps, const DBusMenuItemKeysList &removedProps);
void LayoutUpdated(uint revision, int parent);
private slots:
void onMenuChanged(const GMenuChangeList &changes);
private:
void start(const QList<uint> &ids);
void stop(const QList<uint> &ids);
bool registerDBusObject();
WId m_winId;
QString m_serviceName; // original GMenu service (the gtk app)
QString m_objectPath; // original GMenu object path (the gtk app)
QString m_proxyObjectPath; // our object path on this proxy app
QList<uint> m_subscriptions; // keeps track of which menu trees we're subscribed to
QHash<uint, VariantHashList> m_menus; // the menu data we gathered in Start + subsequent updates
};
/*
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/