Commit 93938d60 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇

Restore global menu support

This brings back global menu support in KWin.
The DBusMenu infrastructure is different that we just read the DBus service name and
menu object path from the windows rather than passing around window IDs on DBus which
won't work on Wayland.

Differential Revision: https://phabricator.kde.org/D3089
parent e616925f
......@@ -435,6 +435,7 @@ set(kwin_KDEINIT_SRCS
wayland_server.cpp
wayland_cursor_theme.cpp
virtualkeyboard.cpp
appmenu.cpp
)
if(KWIN_BUILD_TABBOX)
......@@ -485,6 +486,8 @@ qt5_add_dbus_adaptor( kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin:
qt5_add_dbus_interface( kwin_KDEINIT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/org.freedesktop.ScreenSaver.xml screenlocker_interface)
qt5_add_dbus_interface( kwin_KDEINIT_SRCS org.kde.kappmenu.xml appmenu_interface )
qt5_add_resources( kwin_KDEINIT_SRCS resources.qrc )
ki18n_wrap_ui(kwin_KDEINIT_SRCS
......
......@@ -18,6 +18,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "abstract_client.h"
#include "appmenu.h"
#include "decorations/decoratedclient.h"
#include "decorations/decorationpalette.h"
#include "decorations/decorationbridge.h"
......@@ -82,6 +84,10 @@ AbstractClient::AbstractClient()
}
}
);
connect(ApplicationMenu::self(), &ApplicationMenu::applicationMenuEnabledChanged, this, [this] {
emit hasApplicationMenuChanged(hasApplicationMenu());
});
}
AbstractClient::~AbstractClient()
......@@ -1676,4 +1682,53 @@ QString AbstractClient::iconFromDesktopFile() const
return df.readIcon();
}
bool AbstractClient::hasApplicationMenu() const
{
return ApplicationMenu::self()->applicationMenuEnabled() && !m_applicationMenuServiceName.isEmpty() && !m_applicationMenuObjectPath.isEmpty();
}
void AbstractClient::updateApplicationMenuServiceName(const QString &serviceName)
{
const bool old_hasApplicationMenu = hasApplicationMenu();
m_applicationMenuServiceName = serviceName;
const bool new_hasApplicationMenu = hasApplicationMenu();
if (old_hasApplicationMenu != new_hasApplicationMenu) {
emit hasApplicationMenuChanged(new_hasApplicationMenu);
}
}
void AbstractClient::updateApplicationMenuObjectPath(const QString &objectPath)
{
const bool old_hasApplicationMenu = hasApplicationMenu();
m_applicationMenuObjectPath = objectPath;
const bool new_hasApplicationMenu = hasApplicationMenu();
if (old_hasApplicationMenu != new_hasApplicationMenu) {
emit hasApplicationMenuChanged(new_hasApplicationMenu);
}
}
void AbstractClient::setApplicationMenuActive(bool applicationMenuActive)
{
if (m_applicationMenuActive != applicationMenuActive) {
m_applicationMenuActive = applicationMenuActive;
emit applicationMenuActiveChanged(applicationMenuActive);
}
}
void AbstractClient::showApplicationMenu(int actionId)
{
if (isDecorated()) {
decoration()->showApplicationMenu(actionId);
} else {
// we don't know where the application menu button will be, show it in the top left corner instead
Workspace::self()->showApplicationMenu(QRect(), this, actionId);
}
}
}
......@@ -255,6 +255,16 @@ class KWIN_EXPORT AbstractClient : public Toplevel
* (e.g. "/opt/kde/share/org.kde.foo.desktop") in case it's not in a standard location.
**/
Q_PROPERTY(QByteArray desktopFileName READ desktopFileName NOTIFY desktopFileNameChanged)
/**
* Whether an application menu is available for this Client
*/
Q_PROPERTY(bool hasApplicationMenu READ hasApplicationMenu NOTIFY hasApplicationMenuChanged)
/**
* Whether the application menu for this Client is currently opened
*/
Q_PROPERTY(bool applicationMenuActive READ applicationMenuActive NOTIFY applicationMenuActiveChanged)
public:
virtual ~AbstractClient();
......@@ -624,6 +634,25 @@ public:
// TODO: remove boolean trap
static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false);
bool hasApplicationMenu() const;
bool applicationMenuActive() const {
return m_applicationMenuActive;
}
void setApplicationMenuActive(bool applicationMenuActive);
QString applicationMenuServiceName() const {
return m_applicationMenuServiceName;
}
QString applicationMenuObjectPath() const {
return m_applicationMenuObjectPath;
}
/**
* Request showing the application menu bar
* @param actionId The DBus menu ID of the action that should be highlighted, 0 for the root menu
*/
void showApplicationMenu(int actionId);
public Q_SLOTS:
virtual void closeWindow() = 0;
......@@ -663,6 +692,8 @@ Q_SIGNALS:
void shadeableChanged(bool);
void maximizeableChanged(bool);
void desktopFileNameChanged();
void hasApplicationMenuChanged(bool);
void applicationMenuActiveChanged(bool);
protected:
AbstractClient();
......@@ -944,6 +975,9 @@ protected:
void setDesktopFileName(const QByteArray &name);
QString iconFromDesktopFile() const;
void updateApplicationMenuServiceName(const QString &serviceName);
void updateApplicationMenuObjectPath(const QString &objectPath);
private:
void handlePaletteChange();
QSharedPointer<TabBox::TabBoxClientImpl> m_tabBoxClient;
......@@ -1010,9 +1044,11 @@ private:
QPointer<Decoration::DecoratedClientImpl> client;
QElapsedTimer doubleClickTimer;
} m_decoration;
QByteArray m_desktopFileName;
bool m_applicationMenuActive = false;
QString m_applicationMenuServiceName;
QString m_applicationMenuObjectPath;
static bool s_haveResizeEffect;
};
......
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "appmenu.h"
#include "client.h"
#include "workspace.h"
#include <appmenu_interface.h>
#include <QDBusObjectPath>
using namespace KWin;
KWIN_SINGLETON_FACTORY(ApplicationMenu)
ApplicationMenu::ApplicationMenu(QObject *parent)
: QObject(parent)
, m_appmenuInterface(new OrgKdeKappmenuInterface(QStringLiteral("org.kde.kappmenu"), QStringLiteral("/KAppMenu"), QDBusConnection::sessionBus(), this))
{
connect(m_appmenuInterface, &OrgKdeKappmenuInterface::showRequest, this, &ApplicationMenu::slotShowRequest);
connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuShown, this, &ApplicationMenu::slotMenuShown);
connect(m_appmenuInterface, &OrgKdeKappmenuInterface::menuHidden, this, &ApplicationMenu::slotMenuHidden);
connect(m_appmenuInterface, &OrgKdeKappmenuInterface::reconfigured, this, &ApplicationMenu::slotReconfigured);
updateApplicationMenuEnabled();
}
ApplicationMenu::~ApplicationMenu()
{
s_self = nullptr;
}
void ApplicationMenu::slotReconfigured()
{
updateApplicationMenuEnabled();
}
bool ApplicationMenu::applicationMenuEnabled() const
{
return m_applicationMenuEnabled;
}
void ApplicationMenu::updateApplicationMenuEnabled()
{
const bool old_enabled = m_applicationMenuEnabled;
KConfigGroup config(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), QStringLiteral("Appmenu Style"));
const QString &menuStyle = config.readEntry(QStringLiteral("Style"));
const bool enabled = (menuStyle == QLatin1String("Decoration"));
if (old_enabled != enabled) {
m_applicationMenuEnabled = enabled;
emit applicationMenuEnabledChanged(enabled);
}
}
void ApplicationMenu::slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId)
{
if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) {
c->showApplicationMenu(actionId);
}
}
void ApplicationMenu::slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath)
{
if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) {
c->setApplicationMenuActive(true);
}
}
void ApplicationMenu::slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath)
{
if (AbstractClient *c = findAbstractClientWithApplicationMenu(serviceName, menuObjectPath)) {
c->setApplicationMenuActive(false);
}
}
void ApplicationMenu::showApplicationMenu(const QPoint &p, AbstractClient *c, int actionId)
{
m_appmenuInterface->showMenu(p.x(), p.y(), c->applicationMenuServiceName(), QDBusObjectPath(c->applicationMenuObjectPath()), actionId);
}
AbstractClient *ApplicationMenu::findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath)
{
if (serviceName.isEmpty() || menuObjectPath.path().isEmpty()) {
return nullptr;
}
return Workspace::self()->findAbstractClient([&](const AbstractClient *c) {
return c->applicationMenuServiceName() == serviceName
&& c->applicationMenuObjectPath() == menuObjectPath.path();
});
}
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef KWIN_APPLICATIONMENU_H
#define KWIN_APPLICATIONMENU_H
// KWin
#include <kwinglobals.h>
// Qt
#include <QObject>
// xcb
#include <xcb/xcb.h>
class QPoint;
class OrgKdeKappmenuInterface;
class QDBusObjectPath;
namespace KWin
{
class AbstractClient;
class ApplicationMenu : public QObject
{
Q_OBJECT
public:
~ApplicationMenu() override;
void showApplicationMenu(const QPoint &pos, AbstractClient *c, int actionId);
bool applicationMenuEnabled() const;
signals:
void applicationMenuEnabledChanged(bool enabled);
private Q_SLOTS:
void slotReconfigured();
void slotShowRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
void slotMenuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath);
void slotMenuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath);
private:
void updateApplicationMenuEnabled();
OrgKdeKappmenuInterface *m_appmenuInterface;
AbstractClient *findAbstractClientWithApplicationMenu(const QString &serviceName, const QDBusObjectPath &menuObjectPath);
bool m_applicationMenuEnabled = false;
KWIN_SINGLETON(ApplicationMenu)
};
}
#endif // KWIN_APPLICATIONMENU_H
......@@ -59,6 +59,8 @@ Atoms::Atoms()
, kwin_dbus_service(QByteArrayLiteral("_ORG_KDE_KWIN_DBUS_SERVICE"))
, utf8_string(QByteArrayLiteral("UTF8_STRING"))
, wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID"))
, kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"))
, kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"))
, m_dtSmWindowInfo(QByteArrayLiteral("_DT_SM_WINDOW_INFO"))
, m_motifSupport(QByteArrayLiteral("_MOTIF_WM_INFO"))
, m_helpersRetrieved(false)
......
......@@ -68,6 +68,8 @@ public:
Xcb::Atom kwin_dbus_service;
Xcb::Atom utf8_string;
Xcb::Atom wl_surface_id;
Xcb::Atom kde_net_wm_appmenu_service_name;
Xcb::Atom kde_net_wm_appmenu_object_path;
/**
* @internal
......
......@@ -2152,5 +2152,37 @@ QSize Client::resizeIncrements() const
return m_geometryHints.resizeIncrements();
}
Xcb::StringProperty Client::fetchApplicationMenuServiceName() const
{
return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_service_name);
}
void Client::readApplicationMenuServiceName(Xcb::StringProperty &property)
{
updateApplicationMenuServiceName(QString::fromUtf8(property));
}
void Client::checkApplicationMenuServiceName()
{
Xcb::StringProperty property = fetchApplicationMenuServiceName();
readApplicationMenuServiceName(property);
}
Xcb::StringProperty Client::fetchApplicationMenuObjectPath() const
{
return Xcb::StringProperty(m_client, atoms->kde_net_wm_appmenu_object_path);
}
void Client::readApplicationMenuObjectPath(Xcb::StringProperty &property)
{
updateApplicationMenuObjectPath(QString::fromUtf8(property));
}
void Client::checkApplicationMenuObjectPath()
{
Xcb::StringProperty property = fetchApplicationMenuObjectPath();
readApplicationMenuObjectPath(property);
}
} // namespace
......@@ -331,6 +331,14 @@ public:
**/
void showOnScreenEdge() override;
Xcb::StringProperty fetchApplicationMenuServiceName() const;
void readApplicationMenuServiceName(Xcb::StringProperty &property);
void checkApplicationMenuServiceName();
Xcb::StringProperty fetchApplicationMenuObjectPath() const;
void readApplicationMenuObjectPath(Xcb::StringProperty &property);
void checkApplicationMenuObjectPath();
static void cleanupX11();
public Q_SLOTS:
......
......@@ -39,7 +39,7 @@ namespace Decoration
DecoratedClientImpl::DecoratedClientImpl(AbstractClient *client, KDecoration2::DecoratedClient *decoratedClient, KDecoration2::Decoration *decoration)
: QObject()
, DecoratedClientPrivate(decoratedClient, decoration)
, ApplicationMenuEnabledDecoratedClientPrivate(decoratedClient, decoration)
, m_client(client)
, m_clientSize(client->clientSize())
, m_renderer(nullptr)
......@@ -110,6 +110,9 @@ DecoratedClientImpl::DecoratedClientImpl(AbstractClient *client, KDecoration2::D
connect(client, &AbstractClient::maximizeableChanged, decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged);
connect(client, &AbstractClient::paletteChanged, decoratedClient, &KDecoration2::DecoratedClient::paletteChanged);
connect(client, &AbstractClient::hasApplicationMenuChanged, decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged);
connect(client, &AbstractClient::applicationMenuActiveChanged, decoratedClient, &KDecoration2::DecoratedClient::applicationMenuActiveChanged);
}
DecoratedClientImpl::~DecoratedClientImpl() = default;
......@@ -203,6 +206,16 @@ void DecoratedClientImpl::requestShowWindowMenu()
Workspace::self()->showWindowMenu(QRect(Cursor::pos(), Cursor::pos()), m_client);
}
void DecoratedClientImpl::requestShowApplicationMenu(const QRect &rect, int actionId)
{
Workspace::self()->showApplicationMenu(rect, m_client, actionId);
}
void DecoratedClientImpl::showApplicationMenu(int actionId)
{
decoration()->showApplicationMenu(actionId);
}
void DecoratedClientImpl::requestToggleMaximization(Qt::MouseButtons buttons)
{
QMetaObject::invokeMethod(this, "delayedRequestToggleMaximization", Qt::QueuedConnection, Q_ARG(Options::WindowOperation, options->operationMaxButtonClick(buttons)));
......@@ -265,6 +278,16 @@ Qt::Edges DecoratedClientImpl::adjacentScreenEdges() const
return edges;
}
bool DecoratedClientImpl::hasApplicationMenu() const
{
return m_client->hasApplicationMenu();
}
bool DecoratedClientImpl::isApplicationMenuActive() const
{
return m_client->applicationMenuActive();
}
void DecoratedClientImpl::createRenderer()
{
if (Compositor::self()->hasScene()) {
......
......@@ -35,7 +35,7 @@ namespace Decoration
class Renderer;
class DecoratedClientImpl : public QObject, public KDecoration2::DecoratedClientPrivate
class DecoratedClientImpl : public QObject, public KDecoration2::ApplicationMenuEnabledDecoratedClientPrivate
{
Q_OBJECT
public:
......@@ -69,16 +69,22 @@ public:
Qt::Edges adjacentScreenEdges() const override;
bool hasApplicationMenu() const override;
bool isApplicationMenuActive() const override;
void requestClose() override;
void requestContextHelp() override;
void requestToggleMaximization(Qt::MouseButtons buttons) override;
void requestMinimize() override;
void requestShowWindowMenu() override;
void requestShowApplicationMenu(const QRect &rect, int actionId) override;
void requestToggleKeepAbove() override;
void requestToggleKeepBelow() override;
void requestToggleOnAllDesktops() override;
void requestToggleShade() override;
void showApplicationMenu(int actionId);
AbstractClient *client() {
return m_client;
}
......
......@@ -155,6 +155,7 @@ void SettingsImpl::readSettings()
KConfigGroup config = kwinApp()->config()->group(QStringLiteral("org.kde.kdecoration2"));
const auto &left = readDecorationButtons(config, "ButtonsOnLeft", QVector<KDecoration2::DecorationButtonType >({
KDecoration2::DecorationButtonType::Menu,
KDecoration2::DecorationButtonType::ApplicationMenu,
KDecoration2::DecorationButtonType::OnAllDesktops
}));
if (left != m_leftButtons) {
......
......@@ -923,6 +923,10 @@ void Client::propertyNotifyEvent(xcb_property_notify_event_t *e)
updateShowOnScreenEdge();
else if (e->atom == atoms->gtk_frame_extents)
detectGtkFrameExtents();
else if (e->atom == atoms->kde_net_wm_appmenu_service_name)
checkApplicationMenuServiceName();
else if (e->atom == atoms->kde_net_wm_appmenu_object_path)
checkApplicationMenuObjectPath();
break;
}
}
......
......@@ -38,6 +38,7 @@ ButtonsModel::ButtonsModel(const QVector< DecorationButtonType > &buttons, QObje
ButtonsModel::ButtonsModel(QObject* parent)
: ButtonsModel(QVector<DecorationButtonType>({
DecorationButtonType::Menu,
DecorationButtonType::ApplicationMenu,
DecorationButtonType::OnAllDesktops,
DecorationButtonType::Minimize,
DecorationButtonType::Maximize,
......
......@@ -36,7 +36,7 @@ namespace Preview
PreviewClient::PreviewClient(DecoratedClient *c, Decoration *decoration)
: QObject(decoration)
, DecoratedClientPrivate(c, decoration)
, ApplicationMenuEnabledDecoratedClientPrivate(c, decoration)
, m_colorSchemeManager(new KColorSchemeManager(this))
, m_colorSchemeIndex(0)
, m_icon(QIcon::fromTheme(QStringLiteral("start-here-kde")))
......@@ -309,6 +309,16 @@ Qt::Edges PreviewClient::adjacentScreenEdges() const
return edges;
}
bool PreviewClient::hasApplicationMenu() const
{
return true;
}
bool PreviewClient::isApplicationMenuActive() const
{
return false;
}
bool PreviewClient::bordersBottomEdge() const
{
return m_bordersBottomEdge;
......@@ -408,6 +418,17 @@ void PreviewClient::requestShowWindowMenu()
emit showWindowMenuRequested();
}
void PreviewClient::requestShowApplicationMenu(const QRect &rect, int actionId)
{
Q_UNUSED(rect);
Q_UNUSED(actionId);
}
void PreviewClient::showApplicationMenu(int actionId)
{
Q_UNUSED(actionId)
}
void PreviewClient::requestToggleOnAllDesktops()
{
setDesktop(isOnAllDesktops() ? 1 : -1);
......
......@@ -33,7 +33,7 @@ namespace KDecoration2
{
namespace Preview
{
class PreviewClient : public QObject, public DecoratedClientPrivate
class PreviewClient : public QObject, public ApplicationMenuEnabledDecoratedClientPrivate
{
Q_OBJECT
Q_PROPERTY(KDecoration2::Decoration *decoration READ decoration CONSTANT)
......@@ -97,6 +97,9 @@ public:
QColor color(ColorGroup group, ColorRole role) const override;
Qt::Edges adjacentScreenEdges() const override;
bool hasApplicationMenu() const override;
bool isApplicationMenuActive() const override;
void requestClose() override;
void requestContextHelp() override;
void requestToggleMaximization(Qt::MouseButtons buttons) override;
......@@ -105,8 +108,11 @@ public:
void requestToggleKeepBelow() override;
void requestToggleShade() override;
void requestShowWindowMenu() override;
void requestShowApplicationMenu(const QRect &rect, int actionId) override;
void requestToggleOnAllDesktops() override;
void showApplicationMenu(int actionId);
void setCaption(const QString &caption);
void setActive(bool active);
void setCloseable(bool closeable);
......@@ -176,6 +182,7 @@ Q_SIGNALS:
void bordersBottomEdgeChanged(bool);
void showWindowMenuRequested();
void showApplicationMenuRequested();
void minimizeRequested();
void closeRequested();
......
......@@ -72,6 +72,7 @@ PreviewSettings::PreviewSettings(DecorationSettings *parent)
, m_closeOnDoubleClick(false)
, m_leftButtons(new ButtonsModel(QVector<DecorationButtonType>({
DecorationButtonType::Menu,
DecorationButtonType::ApplicationMenu,
DecorationButtonType::OnAllDesktops
}), this))
, m_rightButtons(new ButtonsModel(QVector<DecorationButtonType>({
......
......@@ -104,6 +104,9 @@ bool Client::manage(xcb_window_t w, bool isMapped)
auto firstInTabBoxCookie = fetchFirstInTabBox();
auto transientCookie = fetchTransient();
auto activitiesCookie = fetchActivities();
auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName();
auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath();
m_geometryHints.init(window());
m_motif.init(window());
info = new WinInfo(this, m_client, rootWindow(), properties, properties2);
......@@ -391,6 +394,9 @@ bool Client::manage(xcb_window_t w, bool isMapped)
readColorScheme(colorSchemeCookie);
readApplicationMenuServiceName(applicationMenuServiceNameCookie);
readApplicationMenuObjectPath(applicationMenuObjectPathCookie);
updateDecoration(false); // Also gravitates