diff --git a/applets/appmenu/lib/CMakeLists.txt b/applets/appmenu/lib/CMakeLists.txt index 4d5a9efbc..0beebf8d4 100644 --- a/applets/appmenu/lib/CMakeLists.txt +++ b/applets/appmenu/lib/CMakeLists.txt @@ -1,16 +1,17 @@ set(appmenuapplet_SRCS appmenuapplet.cpp ) add_library(plasma_applet_appmenu MODULE ${appmenuapplet_SRCS}) kcoreaddons_desktop_to_json(plasma_applet_appmenu ../package/metadata.desktop) target_link_libraries(plasma_applet_appmenu Qt5::Widgets Qt5::Quick Qt5::DBus KF5::Plasma - KF5::WindowSystem) + KF5::WindowSystem + PW::LibTaskManager) install(TARGETS plasma_applet_appmenu DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) diff --git a/applets/appmenu/plugin/CMakeLists.txt b/applets/appmenu/plugin/CMakeLists.txt index a223acbdd..e693d525f 100644 --- a/applets/appmenu/plugin/CMakeLists.txt +++ b/applets/appmenu/plugin/CMakeLists.txt @@ -1,21 +1,18 @@ set(appmenuapplet_SRCS appmenumodel.cpp appmenuplugin.cpp ) add_library(appmenuplugin SHARED ${appmenuapplet_SRCS}) target_link_libraries(appmenuplugin Qt5::Core Qt5::Widgets Qt5::Quick KF5::Plasma KF5::WindowSystem + PW::LibTaskManager dbusmenuqt) -if(HAVE_X11) - target_link_libraries(appmenuplugin Qt5::X11Extras XCB::XCB) -endif() - install(TARGETS appmenuplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/appmenu) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/appmenu) diff --git a/applets/appmenu/plugin/appmenumodel.cpp b/applets/appmenu/plugin/appmenumodel.cpp index ffeb9a342..08461c860 100644 --- a/applets/appmenu/plugin/appmenumodel.cpp +++ b/applets/appmenu/plugin/appmenumodel.cpp @@ -1,405 +1,249 @@ /****************************************************************** * Copyright 2016 Kai Uwe Broulik * Copyright 2016 Chinmoy Ranjan Pradhan * * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 "appmenumodel.h" -#include - -#if HAVE_X11 -#include -#include -#endif - #include #include #include #include #include #include #include - -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 +#include class KDBusMenuImporter : public DBusMenuImporter { public: KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) : DBusMenuImporter(service, path, parent) { } protected: QIcon iconForName(const QString &name) override { return QIcon::fromTheme(name); } }; AppMenuModel::AppMenuModel(QObject *parent) : QAbstractListModel(parent), - m_serviceWatcher(new QDBusServiceWatcher(this)) + m_serviceWatcher(new QDBusServiceWatcher(this)), + m_tasksModel(new TaskManager::TasksModel(this)) { - connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); - connect(KWindowSystem::self() - , static_cast(&KWindowSystem::windowChanged) - , this - , &AppMenuModel::onWindowChanged); + m_tasksModel->setFilterByScreen(true); + connect(m_tasksModel, &TaskManager::TasksModel::activeTaskChanged, this, &AppMenuModel::onActiveWindowChanged); + connect(m_tasksModel, &TaskManager::TasksModel::screenGeometryChanged, this, &AppMenuModel::screenGeometryChanged); connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { if (!m_updatePending) { m_updatePending = true; QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); } }); - connect(this, &AppMenuModel::screenGeometryChanged, this, [this] { - onWindowChanged(m_currentWindowId); - }); - - onActiveWindowChanged(KWindowSystem::activeWindow()); + onActiveWindowChanged(); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); //if our current DBus connection gets lost, close the menu //we'll select the new menu when the focus changes connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) { if (serviceName == m_serviceName) { setMenuAvailable(false); emit modelNeedsUpdate(); } }); } AppMenuModel::~AppMenuModel() = default; bool AppMenuModel::menuAvailable() const { return m_menuAvailable; } void AppMenuModel::setMenuAvailable(bool set) { if (m_menuAvailable != set) { m_menuAvailable = set; setVisible(true); emit menuAvailableChanged(); } } bool AppMenuModel::visible() const { return m_visible; } void AppMenuModel::setVisible(bool visible) { if (m_visible != visible) { m_visible = visible; emit visibleChanged(); } } QRect AppMenuModel::screenGeometry() const { - return m_screenGeometry; + return m_tasksModel->screenGeometry(); } void AppMenuModel::setScreenGeometry(QRect geometry) { - if (m_screenGeometry == geometry) { - return; - } - - m_screenGeometry = geometry; - emit screenGeometryChanged(); + m_tasksModel->setScreenGeometry(geometry); } int AppMenuModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (!m_menuAvailable || !m_menu) { return 0; } return m_menu->actions().count(); } void AppMenuModel::update() { beginResetModel(); endResetModel(); m_updatePending = false; } - -void AppMenuModel::onActiveWindowChanged(WId id) +void AppMenuModel::onActiveWindowChanged() { - qApp->removeNativeEventFilter(this); + const QModelIndex activeTaskIndex = m_tasksModel->activeTask(); + const QString objectPath = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuObjectPath).toString(); + const QString serviceName = m_tasksModel->data(activeTaskIndex, TaskManager::AbstractTasksModel::ApplicationMenuServiceName).toString(); - if (!id) { - setMenuAvailable(false); + if (!objectPath.isEmpty() && !serviceName.isEmpty()) { + setMenuAvailable(true); + updateApplicationMenu(serviceName, objectPath); + setVisible(true); emit modelNeedsUpdate(); - return; - } - -#if HAVE_X11 - if (KWindowSystem::isPlatformX11()) { - 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; - }; - - auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { - const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); - const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); - - if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { - updateApplicationMenu(serviceName, menuObjectPath); - return true; - } - return false; - }; - - KWindowInfo info(id, NET::WMState | NET::WMWindowType, NET::WM2TransientFor); - if (info.hasState(NET::SkipTaskbar) || - info.windowType(NET::UtilityMask) == NET::Utility || - info.windowType(NET::DesktopMask) == NET::Desktop) { - return; - } - - m_currentWindowId = id; - - WId transientId = info.transientFor(); - // lok at transient windows first - while (transientId) { - if (updateMenuFromWindowIfHasMenu(transientId)) { - setVisible(true); - return; - } - transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor(); - } - - if (updateMenuFromWindowIfHasMenu(id)) { - setVisible(true); - return; - } - - // monitor whether an app menu becomes available later - // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) - qApp->installNativeEventFilter(this); - m_delayedMenuWindowId = id; - - //no menu found, set it to unavailable + } else { setMenuAvailable(false); - emit modelNeedsUpdate(); - } -#endif - -} - -void AppMenuModel::onWindowChanged(WId id) -{ - if (m_currentWindowId == id) { - KWindowInfo info(id, NET::WMState | NET::WMGeometry); - - //! HACK: if the user has enabled screen scaling under X11 environment - //! then the window and screen geometries can not be trusted for comparison - //! before windows coordinates be adjusted properly. - //! BUG: 404500 - QPoint windowCenter = info.geometry().center(); - if (KWindowSystem::isPlatformX11()) { - windowCenter /= qApp->devicePixelRatio(); - } - - const bool contained = m_screenGeometry.isNull() || m_screenGeometry.contains(windowCenter); - - setVisible(contained && !info.isMinimized()); + setVisible(false); } } QHash AppMenuModel::roleNames() const { QHash roleNames; roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); roleNames[ActionRole] = QByteArrayLiteral("activeActions"); return roleNames; } QVariant AppMenuModel::data(const QModelIndex &index, int role) const { const int row = index.row(); if (row < 0 || !m_menuAvailable || !m_menu) { return QVariant(); } const auto actions = m_menu->actions(); if (row >= actions.count()) { return QVariant(); } if (role == MenuRole) { // TODO this should be Qt::DisplayRole return actions.at(row)->text(); } else if (role == ActionRole) { return QVariant::fromValue((void *) actions.at(row)); } return QVariant(); } void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) { if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { if (m_importer) { QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); } return; } m_serviceName = serviceName; m_serviceWatcher->setWatchedServices(QStringList({m_serviceName})); m_menuObjectPath = menuObjectPath; if (m_importer) { m_importer->deleteLater(); } m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { m_menu = m_importer->menu(); if (m_menu.isNull() || menu != m_menu) { return; } //cache first layer of sub menus, which we'll be popping up for(QAction *a: m_menu->actions()) { // signal dataChanged when the action changes connect(a, &QAction::changed, this, [this, a] { if (m_menuAvailable && m_menu) { const int actionIdx = m_menu->actions().indexOf(a); if (actionIdx > -1) { const QModelIndex modelIdx = index(actionIdx, 0); emit dataChanged(modelIdx, modelIdx); } } }); connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); if (a->menu()) { m_importer->updateMenu(a->menu()); } } setMenuAvailable(true); emit modelNeedsUpdate(); }); connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { // TODO submenus if (!m_menuAvailable || !m_menu) { return; } const auto actions = m_menu->actions(); auto it = std::find(actions.begin(), actions.end(), action); if (it != actions.end()) { requestActivateIndex(it - actions.begin()); } }); } - -bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result) -{ - Q_UNUSED(result); - - if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") { - return false; - } - -#if HAVE_X11 - auto e = static_cast(message); - const uint8_t type = e->response_type & ~0x80; - if (type == XCB_PROPERTY_NOTIFY) { - auto *event = reinterpret_cast(e); - if (event->window == m_delayedMenuWindowId) { - - auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); - auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); - - if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen - if (event->atom == serviceNameAtom || event->atom == objectPathAtom) { - // see if we now have a menu - onActiveWindowChanged(KWindowSystem::activeWindow()); - } - } - } - } -#else - Q_UNUSED(message); -#endif - - return false; -} - diff --git a/applets/appmenu/plugin/appmenumodel.h b/applets/appmenu/plugin/appmenumodel.h index 067824eca..e29752149 100644 --- a/applets/appmenu/plugin/appmenumodel.h +++ b/applets/appmenu/plugin/appmenumodel.h @@ -1,108 +1,104 @@ /****************************************************************** * Copyright 2016 Chinmoy Ranjan Pradhan * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 APPMENUMODEL_H #define APPMENUMODEL_H #include -#include #include #include #include #include +#include class QMenu; class QModelIndex; class QDBusServiceWatcher; class KDBusMenuImporter; -class AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter +class AppMenuModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged) Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) public: explicit AppMenuModel(QObject *parent = nullptr); ~AppMenuModel() override; enum AppMenuRole { MenuRole = Qt::UserRole+1, // TODO this should be Qt::DisplayRole ActionRole }; QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QHash roleNames() const override; void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath); bool menuAvailable() const; void setMenuAvailable(bool set); bool visible() const; QRect screenGeometry() const; void setScreenGeometry(QRect geometry); signals: void requestActivateIndex(int index); -protected: - bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override; - private Q_SLOTS: - void onActiveWindowChanged(WId id); - void onWindowChanged(WId id); + void onActiveWindowChanged(); void setVisible(bool visible); void update(); signals: void menuAvailableChanged(); void modelNeedsUpdate(); void screenGeometryChanged(); void visibleChanged(); private: bool m_menuAvailable; bool m_updatePending = false; bool m_visible = true; - QRect m_screenGeometry; + TaskManager::TasksModel* m_tasksModel; //! current active window used WId m_currentWindowId = 0; //! window that its menu initialization may be delayed WId m_delayedMenuWindowId = 0; QPointer m_menu; QDBusServiceWatcher *m_serviceWatcher; QString m_serviceName; QString m_menuObjectPath; QPointer m_importer; }; #endif