diff --git a/libtaskmanager/CMakeLists.txt b/libtaskmanager/CMakeLists.txt --- a/libtaskmanager/CMakeLists.txt +++ b/libtaskmanager/CMakeLists.txt @@ -57,6 +57,7 @@ target_link_libraries(taskmanager PRIVATE Qt5::X11Extras + XCB::XCB KF5::IconThemes) endif() diff --git a/libtaskmanager/abstracttasksmodel.h b/libtaskmanager/abstracttasksmodel.h --- a/libtaskmanager/abstracttasksmodel.h +++ b/libtaskmanager/abstracttasksmodel.h @@ -96,6 +96,10 @@ model contents due to the asynchronous nature of the windowing system. */ LastActivated, /**< The timestamp of the last time a task was the active task. */ + ApplicationMenuServiceName, /**< The DBus service name for the application's menu. + May be empty. */ + ApplicationMenuObjectPath,/**< The DBus object path for the application's menu. + May be empty. */ }; Q_ENUM(AdditionalRoles) diff --git a/libtaskmanager/waylandtasksmodel.cpp b/libtaskmanager/waylandtasksmodel.cpp --- a/libtaskmanager/waylandtasksmodel.cpp +++ b/libtaskmanager/waylandtasksmodel.cpp @@ -312,6 +312,12 @@ QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q, [window, this] { this->dataChanged(window, SkipTaskbar); } ); + + QObject::connect(window, &KWayland::Client::PlasmaWindow::applicationMenuChanged, q, + [window, this] { + this->dataChanged(window, QVector{ApplicationMenuServiceName, ApplicationMenuObjectPath}); + } + ); } AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window) @@ -461,6 +467,10 @@ // FIXME Implement. } else if (role == AppPid) { return window->pid(); + } else if (role == ApplicationMenuObjectPath) { + return window->applicationMenuObjectPath(); + } else if (role == ApplicationMenuServiceName) { + return window->applicationMenuServiceName(); } return QVariant(); diff --git a/libtaskmanager/xwindowtasksmodel.cpp b/libtaskmanager/xwindowtasksmodel.cpp --- a/libtaskmanager/xwindowtasksmodel.cpp +++ b/libtaskmanager/xwindowtasksmodel.cpp @@ -22,6 +22,7 @@ #include "xwindowtasksmodel.h" #include "tasktools.h" #include "xwindowsystemeventbatcher.h" +#include #include #include @@ -44,6 +45,8 @@ #include #include +#include "xcb/xcb.h" + namespace TaskManager { @@ -81,6 +84,7 @@ KWindowInfo* windowInfo(WId window); AppData appData(WId window); + QPair appMenu(WId window); QIcon icon(WId window); static QString mimeType(); @@ -465,6 +469,60 @@ return data; } +static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); +static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); + +#if HAVE_X11 +static QHash s_atoms; +#endif + +// returns QPair for WId window +QPair XWindowTasksModel::Private::appMenu(WId window) +{ + auto *c = QX11Info::connection(); + + auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray { + QByteArray value; + if (!s_atoms.contains(name)) { + const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); + QScopedPointer atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr)); + if (atomReply.isNull()) { + return value; + } + + s_atoms[name] = atomReply->atom; + if (s_atoms[name] == XCB_ATOM_NONE) { + return value; + } + } + + static const long MAX_PROP_SIZE = 10000; + auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); + QScopedPointer propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr)); + if (propertyReply.isNull()) { + return value; + } + + if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) { + const char *data = (const char *) xcb_get_property_value(propertyReply.data()); + int len = propertyReply->value_len; + if (data) { + value = QByteArray(data, data[len - 1] ? len : len - 1); + } + } + + return value; + }; + + const QString serviceName = QString::fromUtf8(getWindowPropertyString(window, s_x11AppMenuServiceNamePropertyName)); + const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(window, s_x11AppMenuObjectPathPropertyName)); + + if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { + return qMakePair(serviceName, menuObjectPath); + } + return qMakePair(QStringLiteral(""), QStringLiteral("")); +} + QIcon XWindowTasksModel::Private::icon(WId window) { const AppData &app = appData(window); @@ -690,6 +748,10 @@ if (d->lastActivated.contains(window)) { return d->lastActivated.value(window); } + } else if (role == ApplicationMenuObjectPath) { + return d->appMenu(window).second; + } else if (role == ApplicationMenuServiceName) { + return d->appMenu(window).first; } return QVariant();