diff --git a/CMakeLists.txt b/CMakeLists.txt
index efd2362dfa..affa160954 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/abstract_client.cpp b/abstract_client.cpp
index a33821a91c..6183fd316b 100644
--- a/abstract_client.cpp
+++ b/abstract_client.cpp
@@ -18,6 +18,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*********************************************************************/
#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);
+ }
+}
+
}
diff --git a/abstract_client.h b/abstract_client.h
index 25feb7f821..e509fac939 100644
--- a/abstract_client.h
+++ b/abstract_client.h
@@ -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 m_tabBoxClient;
@@ -1010,9 +1044,11 @@ private:
QPointer client;
QElapsedTimer doubleClickTimer;
} m_decoration;
-
QByteArray m_desktopFileName;
+ bool m_applicationMenuActive = false;
+ QString m_applicationMenuServiceName;
+ QString m_applicationMenuObjectPath;
static bool s_haveResizeEffect;
};
diff --git a/appmenu.cpp b/appmenu.cpp
new file mode 100644
index 0000000000..4ce96f511a
--- /dev/null
+++ b/appmenu.cpp
@@ -0,0 +1,111 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (c) 2011 Lionel Chauvin
+Copyright (c) 2011,2012 Cédric Bellegarde
+Copyright (C) 2013 Martin Gräßlin
+
+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 .
+*********************************************************************/
+#include "appmenu.h"
+#include "client.h"
+#include "workspace.h"
+#include
+
+#include
+
+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();
+ });
+}
diff --git a/appmenu.h b/appmenu.h
new file mode 100644
index 0000000000..1a6af23e2b
--- /dev/null
+++ b/appmenu.h
@@ -0,0 +1,74 @@
+/********************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (c) 2011 Lionel Chauvin
+Copyright (c) 2011,2012 Cédric Bellegarde
+Copyright (C) 2013 Martin Gräßlin
+
+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 .
+*********************************************************************/
+#ifndef KWIN_APPLICATIONMENU_H
+#define KWIN_APPLICATIONMENU_H
+// KWin
+#include
+// Qt
+#include
+// xcb
+#include
+
+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
diff --git a/atoms.cpp b/atoms.cpp
index 55a8aa368f..ee9e8e29e7 100644
--- a/atoms.cpp
+++ b/atoms.cpp
@@ -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)
diff --git a/atoms.h b/atoms.h
index 4b1e97c69b..2c7b2d9f13 100644
--- a/atoms.h
+++ b/atoms.h
@@ -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
diff --git a/client.cpp b/client.cpp
index 99934d2c33..1413951ac4 100644
--- a/client.cpp
+++ b/client.cpp
@@ -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
diff --git a/client.h b/client.h
index 8be241dd2c..25deaf0461 100644
--- a/client.h
+++ b/client.h
@@ -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:
diff --git a/decorations/decoratedclient.cpp b/decorations/decoratedclient.cpp
index 0bb012c50a..907b80d08c 100644
--- a/decorations/decoratedclient.cpp
+++ b/decorations/decoratedclient.cpp
@@ -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()) {
diff --git a/decorations/decoratedclient.h b/decorations/decoratedclient.h
index d31b1dfdd4..e01a2e6a05 100644
--- a/decorations/decoratedclient.h
+++ b/decorations/decoratedclient.h
@@ -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;
}
diff --git a/decorations/settings.cpp b/decorations/settings.cpp
index c1eb22c021..92deb2519c 100644
--- a/decorations/settings.cpp
+++ b/decorations/settings.cpp
@@ -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::Menu,
+ KDecoration2::DecorationButtonType::ApplicationMenu,
KDecoration2::DecorationButtonType::OnAllDesktops
}));
if (left != m_leftButtons) {
diff --git a/events.cpp b/events.cpp
index 9740e493cb..d100747a1f 100644
--- a/events.cpp
+++ b/events.cpp
@@ -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;
}
}
diff --git a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp
index b2ac124ed3..26d7790156 100644
--- a/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp
+++ b/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp
@@ -38,6 +38,7 @@ ButtonsModel::ButtonsModel(const QVector< DecorationButtonType > &buttons, QObje
ButtonsModel::ButtonsModel(QObject* parent)
: ButtonsModel(QVector({
DecorationButtonType::Menu,
+ DecorationButtonType::ApplicationMenu,
DecorationButtonType::OnAllDesktops,
DecorationButtonType::Minimize,
DecorationButtonType::Maximize,
diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp
index 146915f533..7d7c413c2f 100644
--- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp
+++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp
@@ -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);
diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h
index 806a4fbd3f..47ff2c415d 100644
--- a/kcmkwin/kwindecoration/declarative-plugin/previewclient.h
+++ b/kcmkwin/kwindecoration/declarative-plugin/previewclient.h
@@ -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();
diff --git a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp
index c5c767a652..dd996dab3c 100644
--- a/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp
+++ b/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp
@@ -72,6 +72,7 @@ PreviewSettings::PreviewSettings(DecorationSettings *parent)
, m_closeOnDoubleClick(false)
, m_leftButtons(new ButtonsModel(QVector({
DecorationButtonType::Menu,
+ DecorationButtonType::ApplicationMenu,
DecorationButtonType::OnAllDesktops
}), this))
, m_rightButtons(new ButtonsModel(QVector({
diff --git a/manage.cpp b/manage.cpp
index bf372a4d75..a1f927b928 100644
--- a/manage.cpp
+++ b/manage.cpp
@@ -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
// TODO: Is CentralGravity right here, when resizing is done after gravitating?
plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped));
diff --git a/org.kde.kappmenu.xml b/org.kde.kappmenu.xml
new file mode 100644
index 0000000000..d29d3ee86b
--- /dev/null
+++ b/org.kde.kappmenu.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shell_client.cpp b/shell_client.cpp
index 172f10e2a4..db96bafe74 100644
--- a/shell_client.cpp
+++ b/shell_client.cpp
@@ -56,6 +56,8 @@ along with this program. If not, see .
using namespace KWayland::Server;
static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH");
+static const QByteArray s_appMenuServiceNamePropertyName = QByteArrayLiteral("KDE_APPMENU_SERVICE_NAME");
+static const QByteArray s_appMenuObjectPathPropertyName = QByteArrayLiteral("KDE_APPMENU_OBJECT_PATH");
namespace KWin
{
@@ -252,6 +254,7 @@ void ShellClient::init()
}
updateColorScheme(QString());
+ updateApplicationMenu();
}
void ShellClient::destroyClient()
@@ -1234,6 +1237,10 @@ bool ShellClient::eventFilter(QObject *watched, QEvent *event)
QDynamicPropertyChangeEvent *pe = static_cast(event);
if (pe->propertyName() == s_schemePropertyName) {
updateColorScheme(rules()->checkDecoColor(m_qtExtendedSurface->property(pe->propertyName().constData()).toString()));
+ } else if (pe->propertyName() == s_appMenuServiceNamePropertyName) {
+ updateApplicationMenuServiceName(m_qtExtendedSurface->property(pe->propertyName().constData()).toString());
+ } else if (pe->propertyName() == s_appMenuObjectPathPropertyName) {
+ updateApplicationMenuObjectPath(m_qtExtendedSurface->property(pe->propertyName().constData()).toString());
}
}
return false;
@@ -1466,4 +1473,12 @@ void ShellClient::killWindow()
QTimer::singleShot(5000, c, &ClientConnection::destroy);
}
+void ShellClient::updateApplicationMenu()
+{
+ if (m_qtExtendedSurface) {
+ updateApplicationMenuServiceName(m_qtExtendedSurface->property(s_appMenuObjectPathPropertyName).toString());
+ updateApplicationMenuObjectPath(m_qtExtendedSurface->property(s_appMenuServiceNamePropertyName).toString());
+ }
+}
+
}
diff --git a/shell_client.h b/shell_client.h
index 6c3f38eb86..54fca4d187 100644
--- a/shell_client.h
+++ b/shell_client.h
@@ -137,6 +137,8 @@ public:
// TODO: const-ref
void placeIn(QRect &area);
+ void updateApplicationMenu();
+
protected:
void addDamage(const QRegion &damage) override;
bool belongsToSameApplication(const AbstractClient *other, bool active_hack) const override;
diff --git a/useractions.cpp b/useractions.cpp
index 983ad8795f..502be96b3c 100755
--- a/useractions.cpp
+++ b/useractions.cpp
@@ -45,6 +45,7 @@ along with this program. If not, see .
#include "activities.h"
#include
#endif
+#include "appmenu.h"
#include
@@ -1702,6 +1703,11 @@ void Workspace::showWindowMenu(const QRect &pos, AbstractClient* cl)
m_userActionsMenu->show(pos, cl);
}
+void Workspace::showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId)
+{
+ ApplicationMenu::self()->showApplicationMenu(c->geometry().topLeft() + pos.bottomLeft(), c, actionId);
+}
+
/*!
Closes the popup client
*/
diff --git a/workspace.cpp b/workspace.cpp
index acbbcf4879..360f9db6ef 100644
--- a/workspace.cpp
+++ b/workspace.cpp
@@ -27,6 +27,7 @@ along with this program. If not, see .
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
+#include "appmenu.h"
#include "atoms.h"
#include "client.h"
#include "composite.h"
@@ -128,6 +129,8 @@ Workspace::Workspace(const QString &sessionKey)
// If KWin was already running it saved its configuration after loosing the selection -> Reread
QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration);
+ ApplicationMenu::create(this);
+
_self = this;
// first initialize the extensions
diff --git a/workspace.h b/workspace.h
index d2e49bd72a..b390f9614c 100644
--- a/workspace.h
+++ b/workspace.h
@@ -285,6 +285,8 @@ public:
return m_userActionsMenu;
}
+ void showApplicationMenu(const QRect &pos, AbstractClient *c, int actionId);
+
void updateMinimizedOfTransients(AbstractClient*);
void updateOnAllDesktopsOfTransients(AbstractClient*);
void checkTransients(xcb_window_t w);