diff --git a/autotests/netwininfotestclient.cpp b/autotests/netwininfotestclient.cpp --- a/autotests/netwininfotestclient.cpp +++ b/autotests/netwininfotestclient.cpp @@ -52,6 +52,8 @@ void testUserTime(); void testStartupId(); void testDesktopFileName(); + void testAppMenuObjectPath(); + void testAppMenuServiceName(); void testHandledIcons_data(); void testHandledIcons(); void testPid(); @@ -276,6 +278,36 @@ QCOMPARE(info.startupId(), "foo"); } +void NetWinInfoTestClient::testAppMenuObjectPath() +{ + ATOM(_KDE_NET_WM_APPMENU_OBJECT_PATH) + INFO + + QVERIFY(!info.appMenuObjectPath()); + + xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow, + atom, XCB_ATOM_STRING, 8, 3, "foo"); + xcb_flush(connection()); + + waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2AppMenuObjectPath); + QCOMPARE(info.appMenuObjectPath(), "foo"); +} + +void NetWinInfoTestClient::testAppMenuServiceName() +{ + ATOM(_KDE_NET_WM_APPMENU_SERVICE_NAME) + INFO + + QVERIFY(!info.appMenuServiceName()); + + xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, m_testWindow, + atom, XCB_ATOM_STRING, 8, 3, "foo"); + xcb_flush(connection()); + + waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2AppMenuServiceName); + QCOMPARE(info.appMenuServiceName(), "foo"); +} + void NetWinInfoTestClient::testDesktopFileName() { QVERIFY(connection()); diff --git a/src/kwindowinfo.h b/src/kwindowinfo.h --- a/src/kwindowinfo.h +++ b/src/kwindowinfo.h @@ -573,6 +573,24 @@ */ int pid() const; + /** + * Returns service name of a window's application menu if present. + * + * Requires NET::WMPid passed as properties parameter to the constructor. + * + * @since 5.69 + */ + QByteArray applicationMenuServiceName() const; + + /** + * Returns object path of a window's application menu if present. + * + * Requires NET::WMPid passed as properties parameter to the constructor. + * + * @since 5.69 + */ + QByteArray applicationMenuObjectPath() const; + /** * Copy constructor. */ diff --git a/src/kwindowinfo.cpp b/src/kwindowinfo.cpp --- a/src/kwindowinfo.cpp +++ b/src/kwindowinfo.cpp @@ -28,6 +28,9 @@ KWindowInfoPrivatePidExtension::KWindowInfoPrivatePidExtension() = default; KWindowInfoPrivatePidExtension::~KWindowInfoPrivatePidExtension() = default; +KWindowInfoPrivateAppMenuExtension::KWindowInfoPrivateAppMenuExtension() = default; +KWindowInfoPrivateAppMenuExtension::~KWindowInfoPrivateAppMenuExtension() = default; + class Q_DECL_HIDDEN KWindowInfoPrivate::Private { public: @@ -37,14 +40,16 @@ NET::Properties2 properties2; KWindowInfoPrivateDesktopFileNameExtension *desktopFileNameExtension; KWindowInfoPrivatePidExtension *pidExtension; + KWindowInfoPrivateAppMenuExtension *appMenuExtension; }; KWindowInfoPrivate::Private::Private(WId window, NET::Properties properties, NET::Properties2 properties2) : window(window) , properties(properties) , properties2(properties2) , desktopFileNameExtension(nullptr) , pidExtension(nullptr) + , appMenuExtension(nullptr) { } @@ -82,6 +87,16 @@ d->pidExtension = extension; } +KWindowInfoPrivateAppMenuExtension *KWindowInfoPrivate::appMenuExtension() const +{ + return d->appMenuExtension; +} + +void KWindowInfoPrivate::installAppMenuExtension(KWindowInfoPrivateAppMenuExtension *extension) +{ + d->appMenuExtension = extension; +} + KWindowInfoPrivateDummy::KWindowInfoPrivateDummy(WId window, NET::Properties properties, NET::Properties2 properties2) : KWindowInfoPrivate(window, properties, properties2) { @@ -394,6 +409,22 @@ return QByteArray(); } +QByteArray KWindowInfo::applicationMenuServiceName() const +{ + if (auto extension = d->appMenuExtension()) { + return extension->applicationMenuServiceName(); + } + return QByteArray(); +} + +QByteArray KWindowInfo::applicationMenuObjectPath() const +{ + if (auto extension = d->appMenuExtension()) { + return extension->applicationMenuObjectPath(); + } + return QByteArray(); +} + int KWindowInfo::pid() const { if (auto extension = d->pidExtension()) { diff --git a/src/kwindowinfo_p.h b/src/kwindowinfo_p.h --- a/src/kwindowinfo_p.h +++ b/src/kwindowinfo_p.h @@ -19,6 +19,7 @@ class KWindowInfoPrivateDesktopFileNameExtension; class KWindowInfoPrivatePidExtension; +class KWindowInfoPrivateAppMenuExtension; class KWINDOWSYSTEM_EXPORT KWindowInfoPrivate : public QSharedData { @@ -55,14 +56,16 @@ KWindowInfoPrivateDesktopFileNameExtension *desktopFileNameExtension() const; KWindowInfoPrivatePidExtension *pidExtension() const; + KWindowInfoPrivateAppMenuExtension *appMenuExtension() const; static KWindowInfoPrivate *create(WId window, NET::Properties properties, NET::Properties2 properties2); protected: KWindowInfoPrivate(WId window, NET::Properties properties, NET::Properties2 properties2); void installDesktopFileNameExtension(KWindowInfoPrivateDesktopFileNameExtension *extension); void installPidExtension(KWindowInfoPrivatePidExtension *extension); + void installAppMenuExtension(KWindowInfoPrivateAppMenuExtension *extension); private: class Private; @@ -91,4 +94,16 @@ explicit KWindowInfoPrivatePidExtension(); }; +class KWINDOWSYSTEM_EXPORT KWindowInfoPrivateAppMenuExtension +{ +public: + virtual ~KWindowInfoPrivateAppMenuExtension(); + + virtual QByteArray applicationMenuServiceName() const = 0; + virtual QByteArray applicationMenuObjectPath() const = 0; + +protected: + explicit KWindowInfoPrivateAppMenuExtension(); +}; + #endif diff --git a/src/netwm_def.h b/src/netwm_def.h --- a/src/netwm_def.h +++ b/src/netwm_def.h @@ -724,6 +724,8 @@ WM2OpaqueRegion = 1u << 25, // @since 5.7 WM2DesktopFileName = 1u << 26, // NOT STANDARD @since 5.28 WM2GTKFrameExtents = 1u << 27, // NOT STANDARD @since 5.65 + WM2AppMenuServiceName = 1u << 28, // NOT STANDARD @since 5.69 + WM2AppMenuObjectPath = 1u << 29, // NOT STANDARD @since 5.69 WM2AllProperties = ~0u }; Q_DECLARE_FLAGS(Properties2, Property2) diff --git a/src/platforms/xcb/atoms_p.h b/src/platforms/xcb/atoms_p.h --- a/src/platforms/xcb/atoms_p.h +++ b/src/platforms/xcb/atoms_p.h @@ -143,6 +143,9 @@ ENUM(_KDE_NET_WM_WINDOW_TYPE_CRITICAL_NOTIFICATION), ENUM(_KDE_NET_WM_TEMPORARY_RULES), ENUM(_NET_WM_FRAME_OVERLAP), + ENUM(_KDE_NET_WM_APPMENU_SERVICE_NAME), + ENUM(_KDE_NET_WM_APPMENU_OBJECT_PATH), + // deprecated and naming convention violation ENUM(_NET_WM_STATE_STAYS_ON_TOP), diff --git a/src/platforms/xcb/kwindowinfo.cpp b/src/platforms/xcb/kwindowinfo.cpp --- a/src/platforms/xcb/kwindowinfo.cpp +++ b/src/platforms/xcb/kwindowinfo.cpp @@ -36,9 +36,11 @@ : KWindowInfoPrivate(_win, properties, properties2) , KWindowInfoPrivateDesktopFileNameExtension() , KWindowInfoPrivatePidExtension() + , KWindowInfoPrivateAppMenuExtension() { installDesktopFileNameExtension(this); installPidExtension(this); + installAppMenuExtension(this); KXErrorHandler handler; if (properties & NET::WMVisibleIconName) { @@ -450,6 +452,26 @@ return QByteArray(m_info->desktopFileName()); } +QByteArray KWindowInfoPrivateX11::applicationMenuObjectPath() const +{ +#if !defined(KDE_NO_WARNING_OUTPUT) + if (!(m_info->passedProperties2() & NET::WM2AppMenuObjectPath)) { + qWarning() << "Pass NET::WM2AppMenuObjectPath to KWindowInfo"; + } +#endif + return QByteArray(m_info->appMenuObjectPath()); +} + +QByteArray KWindowInfoPrivateX11::applicationMenuServiceName() const +{ +#if !defined(KDE_NO_WARNING_OUTPUT) + if (!(m_info->passedProperties2() & NET::WM2AppMenuServiceName)) { + qWarning() << "Pass NET::WM2AppMenuServiceName to KWindowInfo"; + } +#endif + return QByteArray(m_info->appMenuServiceName()); +} + int KWindowInfoPrivateX11::pid() const { // If pid is found using the XRes extension use that instead. diff --git a/src/platforms/xcb/kwindowinfo_p_x11.h b/src/platforms/xcb/kwindowinfo_p_x11.h --- a/src/platforms/xcb/kwindowinfo_p_x11.h +++ b/src/platforms/xcb/kwindowinfo_p_x11.h @@ -11,7 +11,7 @@ class NETWinInfo; -class KWindowInfoPrivateX11 : public KWindowInfoPrivate, public KWindowInfoPrivateDesktopFileNameExtension, public KWindowInfoPrivatePidExtension +class KWindowInfoPrivateX11 : public KWindowInfoPrivate, public KWindowInfoPrivateDesktopFileNameExtension, public KWindowInfoPrivatePidExtension, public KWindowInfoPrivateAppMenuExtension { public: KWindowInfoPrivateX11(WId window, NET::Properties properties, NET::Properties2 properties2); @@ -44,6 +44,8 @@ bool actionSupported(NET::Action action) const override; QByteArray desktopFileName() const override; + QByteArray applicationMenuObjectPath() const override; + QByteArray applicationMenuServiceName() const override; int pid() const override; diff --git a/src/platforms/xcb/netwm.h b/src/platforms/xcb/netwm.h --- a/src/platforms/xcb/netwm.h +++ b/src/platforms/xcb/netwm.h @@ -1606,6 +1606,30 @@ **/ const char *desktopFileName() const; + /** + * Sets the @p name as the D-BUS service name for the application menu. + * @since 5.69 + **/ + void setAppMenuServiceName(const char *name); + + /** + * Sets the @p name as the D-BUS object path for the application menu. + * @since 5.69 + **/ + void setAppMenuObjectPath(const char *path); + + /** + * @returns The menu service name of the window's application if present. + * @since 5.69 + **/ + const char *appMenuServiceName() const; + + /** + * @returns The menu object path of the window's application if present. + * @since 5.69 + **/ + const char *appMenuObjectPath() const; + /** Sentinel value to indicate that the client wishes to be visible on all desktops. diff --git a/src/platforms/xcb/netwm.cpp b/src/platforms/xcb/netwm.cpp --- a/src/platforms/xcb/netwm.cpp +++ b/src/platforms/xcb/netwm.cpp @@ -155,6 +155,8 @@ delete [] p->activities; delete [] p->client_machine; delete [] p->desktop_file; + delete [] p->appmenu_object_path; + delete [] p->appmenu_service_name; int i; for (i = 0; i < p->icons.size(); i++) { @@ -1436,6 +1438,14 @@ else if (atom == p->atom(_GTK_FRAME_EXTENTS)) { p->properties2 |= WM2GTKFrameExtents; } + + else if (atom == p->atom(_KDE_NET_WM_APPMENU_OBJECT_PATH)) { + p->properties2 |= WM2AppMenuObjectPath; + } + + else if (atom == p->atom(_KDE_NET_WM_APPMENU_SERVICE_NAME)) { + p->properties2 |= WM2AppMenuServiceName; + } } void NETRootInfo::setActiveWindow(xcb_window_t window) @@ -2592,6 +2602,8 @@ p->icon_sizes = nullptr; p->activities = (char *) nullptr; p->desktop_file = nullptr; + p->appmenu_object_path = nullptr; + p->appmenu_service_name = nullptr; p->blockCompositing = false; p->urgency = false; p->input = true; @@ -2655,6 +2667,8 @@ p->icon_sizes = nullptr; p->activities = (char *) nullptr; p->desktop_file = nullptr; + p->appmenu_object_path = nullptr; + p->appmenu_service_name = nullptr; p->blockCompositing = false; p->urgency = false; p->input = true; @@ -3509,6 +3523,44 @@ return p->gtk_frame_extents; } +void NETWinInfo::setAppMenuObjectPath(const char *name) +{ + if (p->role != Client) { + return; + } + + delete[] p->appmenu_object_path; + p->appmenu_object_path = nstrdup(name); + + xcb_change_property(p->conn, XCB_PROP_MODE_REPLACE, p->window, p->atom(_KDE_NET_WM_APPMENU_OBJECT_PATH), + XCB_ATOM_STRING, 8, strlen(p->appmenu_object_path), + (const void *) p->appmenu_object_path); +} + +void NETWinInfo::setAppMenuServiceName(const char *name) +{ + if (p->role != Client) { + return; + } + + delete[] p->appmenu_service_name; + p->appmenu_service_name = nstrdup(name); + + xcb_change_property(p->conn, XCB_PROP_MODE_REPLACE, p->window, p->atom(_KDE_NET_WM_APPMENU_SERVICE_NAME), + XCB_ATOM_STRING, 8, strlen(p->appmenu_service_name), + (const void *) p->appmenu_service_name); +} + +const char *NETWinInfo::appMenuObjectPath() const +{ + return p->appmenu_object_path; +} + +const char *NETWinInfo::appMenuServiceName() const +{ + return p->appmenu_service_name; +} + void NETWinInfo::kdeGeometry(NETRect &frame, NETRect &window) { if (p->win_geom.size.width == 0 || p->win_geom.size.height == 0) { @@ -3829,6 +3881,10 @@ dirty2 = WM2FullscreenMonitors; } else if (pe->atom == p->atom(_GTK_FRAME_EXTENTS)) { dirty2 |= WM2GTKFrameExtents; + } else if (pe->atom == p->atom(_KDE_NET_WM_APPMENU_SERVICE_NAME)) { + dirty2 |= WM2AppMenuServiceName; + } else if (pe->atom == p->atom(_KDE_NET_WM_APPMENU_OBJECT_PATH)) { + dirty2 |= WM2AppMenuObjectPath; } do_update = true; @@ -4024,6 +4080,14 @@ cookies[c++] = xcb_get_property(p->conn, false, p->window, p->atom(_GTK_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4); } + if (dirty2 & WM2AppMenuObjectPath) { + cookies[c++] = xcb_get_property(p->conn, false, p->window, p->atom(_KDE_NET_WM_APPMENU_OBJECT_PATH), XCB_ATOM_STRING, 0, MAX_PROP_SIZE); + } + + if (dirty2 & WM2AppMenuServiceName) { + cookies[c++] = xcb_get_property(p->conn, false, p->window, p->atom(_KDE_NET_WM_APPMENU_SERVICE_NAME), XCB_ATOM_STRING, 0, MAX_PROP_SIZE); + } + c = 0; if (dirty & XAWMState) { @@ -4621,6 +4685,26 @@ p->gtk_frame_extents.bottom = data[3]; } } + + if (dirty2 & WM2AppMenuObjectPath) { + delete[] p->appmenu_object_path; + p->appmenu_object_path = nullptr; + + const QByteArray id = get_string_reply(p->conn, cookies[c++], XCB_ATOM_STRING); + if (id.length() > 0) { + p->appmenu_object_path = nstrndup(id.constData(), id.length()); + } + } + + if (dirty2 & WM2AppMenuServiceName) { + delete[] p->appmenu_service_name; + p->appmenu_service_name = nullptr; + + const QByteArray id = get_string_reply(p->conn, cookies[c++], XCB_ATOM_STRING); + if (id.length() > 0) { + p->appmenu_service_name = nstrndup(id.constData(), id.length()); + } + } } NETRect NETWinInfo::iconGeometry() const diff --git a/src/platforms/xcb/netwm_p.h b/src/platforms/xcb/netwm_p.h --- a/src/platforms/xcb/netwm_p.h +++ b/src/platforms/xcb/netwm_p.h @@ -165,7 +165,7 @@ xcb_window_t transient_for, window_group; xcb_pixmap_t icon_pixmap, icon_mask; NET::Actions allowed_actions; - char *class_class, *class_name, *window_role, *client_machine, *desktop_file; + char *class_class, *class_name, *window_role, *client_machine, *desktop_file, *appmenu_object_path, *appmenu_service_name; NET::Properties properties; NET::Properties2 properties2;