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,73 @@ return data; } +// returns QPair for WId window +QPair XWindowTasksModel::Private::appMenu(WId window) +{ + static const auto s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); + static const auto s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); + + static QHash> s_cachedAppmenus; + static QHash s_atoms; + + auto *c = QX11Info::connection(); + + auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray { + QByteArray value; + xcb_atom_t atom = XCB_ATOM_NONE; + auto it = s_atoms.constFind(name); + if (it == s_atoms.constEnd()) { + 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; + } + atom = atomReply->atom; + + s_atoms[name] = atom; + if (atom == XCB_ATOM_NONE) { + return value; + } + } else { + atom = *it; + } + + if (atom == 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; + }; + + if (s_cachedAppmenus.contains(window)) { + return s_cachedAppmenus[window]; + } else { + const auto serviceName = QString::fromUtf8(getWindowPropertyString(window, s_x11AppMenuServiceNamePropertyName)); + const auto menuObjectPath = QString::fromUtf8(getWindowPropertyString(window, s_x11AppMenuObjectPathPropertyName)); + if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { + auto pair = qMakePair(serviceName, menuObjectPath); + s_cachedAppmenus.insert(window, pair); + return pair; + } + return qMakePair(QString(), QString()); + } +} + QIcon XWindowTasksModel::Private::icon(WId window) { const AppData &app = appData(window); @@ -690,6 +761,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();