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);