diff --git a/CMakeLists.txt b/CMakeLists.txt index f4ec9e8ae..f1d78d6b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,190 +1,191 @@ cmake_minimum_required(VERSION 3.0) project(plasma-desktop) set(PROJECT_VERSION "5.17.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.11.0") set(KF5_MIN_VERSION "5.62.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMOptionalAddSubdirectory) include(ECMQtDeclareLoggingCategory) include(FeatureSummary) include(CheckIncludeFiles) include(KDEClangFormat) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Quick QuickWidgets DBus Widgets X11Extras Svg Concurrent ) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Auth Plasma PlasmaQuick DocTools I18n KCMUtils NewStuff NewStuffQuick KDELibs4Support Notifications NotifyConfig Attica Wallet Runner GlobalAccel Declarative DBusAddons Activities ActivitiesStats Config ) find_package(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5Kirigami2 PROPERTIES DESCRIPTION "A QtQuick based components set" PURPOSE "Required at runtime by many KCMs" TYPE RUNTIME ) find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5QQC2DesktopStyle PROPERTIES DESCRIPTION "QtQuickControls 2 style that uses QWidget's QStyle for painting" PURPOSE "Required at runtime by many KCMs" TYPE RUNTIME ) find_package(LibKWorkspace ${PROJECT_VERSION} CONFIG REQUIRED) find_package(LibNotificationManager ${PROJECT_VERSION} CONFIG REQUIRED) find_package(LibTaskManager ${PROJECT_VERSION} CONFIG REQUIRED) find_package(LibColorCorrect ${PROJECT_VERSION} CONFIG REQUIRED) find_package(KWinDBusInterface CONFIG REQUIRED) find_package(ScreenSaverDBusInterface CONFIG REQUIRED) find_package(KRunnerAppDBusInterface CONFIG REQUIRED) find_package(KSMServerDBusInterface CONFIG REQUIRED) find_package(KF5ItemModels CONFIG REQUIRED) find_package(KF5Emoticons CONFIG REQUIRED) +find_package(KF5 REQUIRED COMPONENTS SysGuard) find_package(KF5Baloo ${KF5_MIN_VERSION}) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "File Searching" TYPE RECOMMENDED PURPOSE "Needed to build the File Search KCM" ) find_package(Fontconfig) set_package_properties(Fontconfig PROPERTIES DESCRIPTION "Font access configuration library" URL "https://www.freedesktop.org/wiki/Software/fontconfig" TYPE OPTIONAL PURPOSE "Needed to build font configuration and installation tools" ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "https://www.x.org" TYPE REQUIRED PURPOSE "Required for building the X11 based workspace" ) if(X11_FOUND) set(HAVE_X11 1) endif() find_package(UDev) set_package_properties(UDev PROPERTIES DESCRIPTION "UDev library" URL "https://www.freedesktop.org/wiki/Software/systemd/" TYPE OPTIONAL PURPOSE "Required for device discovery in keyboard daemon" ) find_package(XCB REQUIRED COMPONENTS XCB SHM IMAGE OPTIONAL_COMPONENTS XKB XINPUT ) set_package_properties(XCB PROPERTIES TYPE REQUIRED) add_feature_info("XCB-XKB" XCB_XKB_FOUND "Required for building kcm/keyboard") add_feature_info("libxft" X11_Xft_FOUND "X FreeType interface library required for font installation") find_package(Evdev) set_package_properties(Evdev PROPERTIES TYPE OPTIONAL) add_feature_info("Evdev" EVDEV_FOUND "Evdev driver headers needed for mouse KCM") find_package(Synaptics) set_package_properties(Synaptics PROPERTIES TYPE OPTIONAL) add_feature_info("Synaptics" SYNAPTICS_FOUND "Synaptics libraries needed for touchpad KCM") find_package(XorgLibinput) set_package_properties(XorgLibinput PROPERTIES TYPE OPTIONAL) add_feature_info("XorgLibinput" XORGLIBINPUT_FOUND "Libinput driver headers needed for mouse and touchpad KCM") if(XORGLIBINPUT_FOUND) set(HAVE_XORGLIBINPUT 1) endif() include(ConfigureChecks.cmake) find_package(Breeze ${PROJECT_VERSION} CONFIG) set_package_properties(Breeze PROPERTIES TYPE OPTIONAL PURPOSE "For setting the default window decoration plugin") if(${Breeze_FOUND}) if(${BREEZE_WITH_KDECORATION}) set(HAVE_BREEZE_DECO true) else() set(HAVE_BREEZE_DECO FALSE) endif() else() set(HAVE_BREEZE_DECO FALSE) endif() include_directories("${CMAKE_CURRENT_BINARY_DIR}") configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h) configure_file(config-unix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-unix.h ) configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) configure_file(config-runtime.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-runtime.h) plasma_install_package(desktoppackage org.kde.plasma.desktop shells shell) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_subdirectory(layout-templates) add_subdirectory(doc) add_subdirectory(runners) add_subdirectory(containments) add_subdirectory(toolboxes) add_subdirectory(applets) add_subdirectory(dataengines) add_subdirectory(kcms) add_subdirectory(knetattach) add_subdirectory(attica-kde) add_subdirectory(imports/activitymanager/) add_subdirectory(solid-device-automounter) if(X11_Xkb_FOUND AND XCB_XKB_FOUND) add_subdirectory(kaccess) endif() install(FILES org.kde.plasmashell.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/applets/taskmanager/CMakeLists.txt b/applets/taskmanager/CMakeLists.txt index 8a4479a99..3430ac5f7 100644 --- a/applets/taskmanager/CMakeLists.txt +++ b/applets/taskmanager/CMakeLists.txt @@ -1,35 +1,36 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.taskmanager\") plasma_install_package(package org.kde.plasma.taskmanager) set(taskmanagerplugin_SRCS plugin/backend.cpp plugin/draghelper.cpp plugin/taskmanagerplugin.cpp plugin/smartlaunchers/smartlauncherbackend.cpp plugin/smartlaunchers/smartlauncheritem.cpp ) install(FILES plugin/qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/taskmanager) add_library(taskmanagerplugin SHARED ${taskmanagerplugin_SRCS}) # FIXME Cleanup no longer used libs. target_link_libraries(taskmanagerplugin Qt5::Core Qt5::DBus Qt5::Qml Qt5::Quick KF5::Activities KF5::ActivitiesStats KF5::I18n KF5::KIOCore KF5::KIOWidgets KF5::KIOFileWidgets # KFilePlacesModel KF5::Plasma + KF5::ProcessCore KF5::Service KF5::WindowSystem PW::LibNotificationManager) install(TARGETS taskmanagerplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/taskmanager) diff --git a/applets/taskmanager/package/contents/ui/PulseAudio.qml b/applets/taskmanager/package/contents/ui/PulseAudio.qml index 84c7a95c4..0e5b58786 100644 --- a/applets/taskmanager/package/contents/ui/PulseAudio.qml +++ b/applets/taskmanager/package/contents/ui/PulseAudio.qml @@ -1,93 +1,111 @@ /*************************************************************************** * Copyright (C) 2017 by Kai Uwe Broulik * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.2 import org.kde.plasma.private.volume 0.1 QtObject { id: pulseAudio signal streamsChanged // It's a JS object so we can do key lookup and don't need to take care of filtering duplicates. property var pidMatches: ({}) // TODO Evict cache at some point, preferably if all instances of an application closed. function registerPidMatch(appName) { if (!hasPidMatch(appName)) { pidMatches[appName] = true; // In case this match is new, notify that streams might have changed. // This way we also catch the case when the non-playing instance // shows up first. // Only notify if we changed to avoid infinite recursion. streamsChanged(); } } function hasPidMatch(appName) { return pidMatches[appName] === true; } function findStreams(key, value) { var streams = [] for (var i = 0, length = instantiator.count; i < length; ++i) { var stream = instantiator.objectAt(i); if (stream[key] === value) { streams.push(stream); } } return streams } function streamsForAppName(appName) { return findStreams("appName", appName); } function streamsForPid(pid) { - return findStreams("pid", pid); + var streams = findStreams("pid", pid); + + if (streams.length === 0) { + for (var i = 0, length = instantiator.count; i < length; ++i) { + var stream = instantiator.objectAt(i); + + if (stream.parentPid === -1) { + stream.parentPid = backend.parentPid(stream.pid); + } + + if (stream.parentPid === pid) { + streams.push(stream); + } + } + } + + return streams; } // QtObject has no default property, hence adding the Instantiator to one explicitly. property var instantiator: Instantiator { model: PulseObjectFilterModel { filters: [ { role: "VirtualStream", value: false } ] sourceModel: SinkInputModel {} } delegate: QtObject { readonly property int pid: Client ? Client.properties["application.process.id"] : 0 + // Determined on demand. + property int parentPid: -1 readonly property string appName: Client ? Client.properties["application.name"] : "" readonly property bool muted: Muted // whether there is nothing actually going on on that stream readonly property bool corked: Corked function mute() { Muted = true } function unmute() { Muted = false } } onObjectAdded: pulseAudio.streamsChanged() onObjectRemoved: pulseAudio.streamsChanged() } } diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp index ea972e9d9..a3b0dda1f 100644 --- a/applets/taskmanager/plugin/backend.cpp +++ b/applets/taskmanager/plugin/backend.cpp @@ -1,588 +1,604 @@ /*************************************************************************** * Copyright (C) 2012-2016 by Eike Hein * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include + namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; Backend::Backend(QObject* parent) : QObject(parent) , m_panelWinId(0) , m_highlightWindows(false) , m_actionGroup(new QActionGroup(this)) { } Backend::~Backend() { } QQuickItem *Backend::taskManagerItem() const { return m_taskManagerItem; } void Backend::setTaskManagerItem(QQuickItem* item) { if (item != m_taskManagerItem) { m_taskManagerItem = item; emit taskManagerItemChanged(); } } QQuickItem *Backend::toolTipItem() const { return m_toolTipItem; } void Backend::setToolTipItem(QQuickItem *item) { if (item != m_toolTipItem) { m_toolTipItem = item; connect(item, &QQuickItem::windowChanged, this, &Backend::toolTipWindowChanged); emit toolTipItemChanged(); } } QQuickWindow *Backend::groupDialog() const { return m_groupDialog; } void Backend::setGroupDialog(QQuickWindow *dialog) { if (dialog != m_groupDialog) { m_groupDialog = dialog; emit groupDialogChanged(); } } bool Backend::highlightWindows() const { return m_highlightWindows; } void Backend::setHighlightWindows(bool highlight) { if (highlight != m_highlightWindows) { m_highlightWindows = highlight; updateWindowHighlight(); emit highlightWindowsChanged(); } } QUrl Backend::tryDecodeApplicationsUrl(const QUrl &launcherUrl) { if (launcherUrl.isValid() && launcherUrl.scheme() == QLatin1String("applications")) { const KService::Ptr service = KService::serviceByMenuId(launcherUrl.path()); if (service) { return QUrl::fromLocalFile(service->entryPath()); } } return launcherUrl; } QVariantList Backend::jumpListActions(const QUrl &launcherUrl, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; KDesktopFile desktopFile(desktopEntryUrl.toLocalFile()); const QStringList &jumpListActions = desktopFile.readActions(); const QLatin1String kde("KDE"); foreach (const QString &actionName, jumpListActions) { const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); if (!actionGroup.isValid() || !actionGroup.exists()) { continue; } const QStringList ¬ShowIn = actionGroup.readXdgListEntry(QStringLiteral("NotShowIn")); if (notShowIn.contains(kde)) { continue; } const QStringList &onlyShowIn = actionGroup.readXdgListEntry(QStringLiteral("OnlyShowIn")); if (!onlyShowIn.isEmpty() && !onlyShowIn.contains(kde)) { continue; } const QString &name = actionGroup.readEntry(QStringLiteral("Name")); const QString &exec = actionGroup.readEntry(QStringLiteral("Exec")); if (name.isEmpty() || exec.isEmpty()) { continue; } QAction *action = new QAction(parent); action->setText(name); action->setIcon(QIcon::fromTheme(actionGroup.readEntry("Icon"))); action->setProperty("exec", exec); // so we can show the proper application name and icon when it launches action->setProperty("applicationName", desktopFile.readName()); action->setProperty("applicationIcon", desktopFile.readIcon()); connect(action, &QAction::triggered, this, &Backend::handleJumpListAction); actions << QVariant::fromValue(action); } return actions; } QVariantList Backend::placesActions(const QUrl &launcherUrl, bool showAllPlaces, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; KDesktopFile desktopFile(desktopEntryUrl.toLocalFile()); // Since we can't have dynamic jump list actions, at least add the user's "Places" for file managers. const QStringList &categories = desktopFile.desktopGroup().readXdgListEntry(QStringLiteral("Categories")); if (!categories.contains(QLatin1String("FileManager"))) { return actions; } QString previousGroup; QMenu *subMenu = nullptr; QScopedPointer placesModel(new KFilePlacesModel()); for (int i = 0; i < placesModel->rowCount(); ++i) { QModelIndex idx = placesModel->index(i, 0); if (placesModel->isHidden(idx)) { continue; } const QString &title = idx.data(Qt::DisplayRole).toString(); const QIcon &icon = idx.data(Qt::DecorationRole).value(); const QUrl &url = idx.data(KFilePlacesModel::UrlRole).toUrl(); QAction *placeAction = new QAction(icon, title, parent); connect(placeAction, &QAction::triggered, this, [url, desktopEntryUrl] { KService::Ptr service = KService::serviceByDesktopPath(desktopEntryUrl.toLocalFile()); if (!service) { return; } KRun::runService(*service, {url}, QApplication::activeWindow()); }); const QString &groupName = idx.data(KFilePlacesModel::GroupRole).toString(); if (previousGroup.isEmpty()) { // Skip first group heading. previousGroup = groupName; } // Put all subsequent categories into a submenu. if (previousGroup != groupName) { QAction *subMenuAction = new QAction(groupName, parent); subMenu = new QMenu(); // Cannot parent a QMenu to a QAction, need to delete it manually. connect(parent, &QObject::destroyed, subMenu, &QObject::deleteLater); subMenuAction->setMenu(subMenu); actions << QVariant::fromValue(subMenuAction); previousGroup = groupName; } if (subMenu) { subMenu->addAction(placeAction); } else { actions << QVariant::fromValue(placeAction); } } // There is nothing more frustrating than having a "More" entry that ends up showing just one or two // additional entries. Therefore we truncate to max. 5 entries only if there are more than 7 in total. if (!showAllPlaces && actions.count() > 7) { const int totalActionCount = actions.count(); while (actions.count() > 5) { actions.removeLast(); } QAction *action = new QAction(parent); action->setText(i18ncp("Show all user Places", "%1 more Place", "%1 more Places", totalActionCount - actions.count())); connect(action, &QAction::triggered, this, &Backend::showAllPlaces); actions << QVariant::fromValue(action); } return actions; } QVariantList Backend::recentDocumentActions(const QUrl &launcherUrl, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; QString desktopName = desktopEntryUrl.fileName(); QString storageId = desktopName; if (storageId.endsWith(QLatin1String(".desktop"))) { storageId = storageId.left(storageId.length() - 8); } auto query = UsedResources | RecentlyUsedFirst | Agent(storageId) | Type::any() | Activity::current() | Url::file(); ResultSet results(query); ResultSet::const_iterator resultIt = results.begin(); int actionCount = 0; while (actionCount < 5 && resultIt != results.end()) { const QString resource = (*resultIt).resource(); ++resultIt; const QUrl url(resource); if (!url.isValid()) { continue; } const KFileItem fileItem(url); if (!fileItem.isFile()) { continue; } QAction *action = new QAction(parent); action->setText(url.fileName()); action->setIcon(QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme(QStringLiteral("unknown")))); action->setProperty("agent", storageId); action->setProperty("entryPath", desktopEntryUrl); action->setData(resource); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); ++actionCount; } if (actionCount > 0) { QAction *action = new QAction(parent); action->setText(i18n("Forget Recent Documents")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action->setProperty("agent", storageId); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); } return actions; } void Backend::toolTipWindowChanged(QQuickWindow *window) { Q_UNUSED(window) updateWindowHighlight(); } void Backend::handleJumpListAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } KRun::run(action->property("exec").toString(), {}, nullptr, action->property("applicationName").toString(), action->property("applicationIcon").toString()); } void Backend::handleRecentDocumentAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } const QString agent = action->property("agent").toString(); if (agent.isEmpty()) { return; } const QString desktopPath = action->property("entryPath").toUrl().toLocalFile(); const QString resource = action->data().toString(); if (desktopPath.isEmpty() || resource.isEmpty()) { auto query = UsedResources | Agent(agent) | Type::any() | Activity::current() | Url::file(); KAStats::forgetResources(query); return; } KService::Ptr service = KService::serviceByDesktopPath(desktopPath); qDebug() << service; if (!service) { return; } KRun::runService(*service, QList() << QUrl(resource), QApplication::activeWindow()); } void Backend::setActionGroup(QAction *action) const { if (action) { action->setActionGroup(m_actionGroup); } } QRect Backend::globalRect(QQuickItem *item) const { if (!item || !item->window()) { return QRect(); } QRect iconRect(item->x(), item->y(), item->width(), item->height()); iconRect.moveTopLeft(item->parentItem()->mapToScene(iconRect.topLeft()).toPoint()); iconRect.moveTopLeft(item->window()->mapToGlobal(iconRect.topLeft())); return iconRect; } void Backend::ungrabMouse(QQuickItem *item) const { //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [item]() { if (item && item->window() && item->window()->mouseGrabberItem()) { item->window()->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(QString::fromLatin1(qVersion())) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, item, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround } bool Backend::canPresentWindows() const { return (KWindowSystem::compositingActive() && KWindowEffects::isEffectAvailable(KWindowEffects::PresentWindowsGroup)); } void Backend::presentWindows(const QVariant &_winIds) { if (!m_taskManagerItem || !m_taskManagerItem->window()) { return; } QList winIds; const QVariantList &_winIdsList = _winIds.toList(); foreach(const QVariant &_winId, _winIdsList) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { winIds.append(winId); } } if (winIds.isEmpty()) { return; } if (m_windowsToHighlight.count()) { m_windowsToHighlight.clear(); updateWindowHighlight(); } KWindowEffects::presentWindows(m_taskManagerItem->window()->winId(), winIds); } bool Backend::isApplication(const QUrl &url) const { if (!url.isValid() || !url.isLocalFile()) { return false; } const QString &localPath = url.toLocalFile(); if (!KDesktopFile::isDesktopFile(localPath)) { return false; } KDesktopFile desktopFile(localPath); return desktopFile.hasApplicationType(); } QList Backend::jsonArrayToUrlList(const QJsonArray &array) const { QList urls; urls.reserve(array.count()); for (auto it = array.constBegin(), end = array.constEnd(); it != end; ++it) { urls << QUrl(it->toString()); } return urls; } void Backend::cancelHighlightWindows() { m_windowsToHighlight.clear(); updateWindowHighlight(); } +qint64 Backend::parentPid(qint64 pid) const +{ + KSysGuard::Processes procs; + procs.updateOrAddProcess(pid); + + KSysGuard::Process *proc = procs.getProcess(pid); + if (!proc) { + return -1; + } + + return proc->parentPid(); +} + void Backend::windowsHovered(const QVariant &_winIds, bool hovered) { m_windowsToHighlight.clear(); if (hovered) { const QVariantList &winIds = _winIds.toList(); foreach(const QVariant &_winId, winIds) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { m_windowsToHighlight.append(winId); } } } updateWindowHighlight(); } void Backend::updateWindowHighlight() { if (!m_highlightWindows) { if (m_panelWinId) { KWindowEffects::highlightWindows(m_panelWinId, QList()); m_panelWinId = 0; } return; } if (m_taskManagerItem && m_taskManagerItem->window()) { m_panelWinId = m_taskManagerItem->window()->winId(); } else { return; } QList windows = m_windowsToHighlight; if (!windows.isEmpty() && m_toolTipItem && m_toolTipItem->window()) { windows.append(m_toolTipItem->window()->winId()); } if (!windows.isEmpty() && m_groupDialog) { windows.append(m_groupDialog->winId()); } KWindowEffects::highlightWindows(m_panelWinId, windows); } diff --git a/applets/taskmanager/plugin/backend.h b/applets/taskmanager/plugin/backend.h index 46e5e47ce..42e93c02f 100644 --- a/applets/taskmanager/plugin/backend.h +++ b/applets/taskmanager/plugin/backend.h @@ -1,125 +1,127 @@ /*************************************************************************** * Copyright (C) 2013-2016 by Eike Hein * * * * 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef BACKEND_H #define BACKEND_H #include #include #include #include class QAction; class QActionGroup; class QQuickItem; class QQuickWindow; class QJsonArray; namespace KActivities { class Consumer; } class Backend : public QObject { Q_OBJECT Q_PROPERTY(QQuickItem* taskManagerItem READ taskManagerItem WRITE setTaskManagerItem NOTIFY taskManagerItemChanged) Q_PROPERTY(QQuickItem* toolTipItem READ toolTipItem WRITE setToolTipItem NOTIFY toolTipItemChanged) Q_PROPERTY(QQuickWindow* groupDialog READ groupDialog WRITE setGroupDialog NOTIFY groupDialogChanged) Q_PROPERTY(bool highlightWindows READ highlightWindows WRITE setHighlightWindows NOTIFY highlightWindowsChanged) public: enum MiddleClickAction { None = 0, Close, NewInstance, ToggleMinimized, ToggleGrouping, BringToCurrentDesktop }; Q_ENUM(MiddleClickAction) explicit Backend(QObject *parent = nullptr); ~Backend() override; QQuickItem *taskManagerItem() const; void setTaskManagerItem(QQuickItem *item); QQuickItem *toolTipItem() const; void setToolTipItem(QQuickItem *item); QQuickWindow *groupDialog() const; void setGroupDialog(QQuickWindow *dialog); bool highlightWindows() const; void setHighlightWindows(bool highlight); Q_INVOKABLE QVariantList jumpListActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE QVariantList placesActions(const QUrl &launcherUrl, bool showAllPlaces, QObject *parent); Q_INVOKABLE QVariantList recentDocumentActions(const QUrl &launcherUrl, QObject *parent); Q_INVOKABLE void setActionGroup(QAction *action) const; Q_INVOKABLE QRect globalRect(QQuickItem *item) const; Q_INVOKABLE void ungrabMouse(QQuickItem *item) const; Q_INVOKABLE bool canPresentWindows() const; Q_INVOKABLE bool isApplication(const QUrl &url) const; Q_INVOKABLE QList jsonArrayToUrlList(const QJsonArray &array) const; Q_INVOKABLE void cancelHighlightWindows(); + Q_INVOKABLE qint64 parentPid(qint64 pid) const; + static QUrl tryDecodeApplicationsUrl(const QUrl &launcherUrl); public Q_SLOTS: void presentWindows(const QVariant &winIds); void windowsHovered(const QVariant &winIds, bool hovered); Q_SIGNALS: void taskManagerItemChanged() const; void toolTipItemChanged() const; void groupDialogChanged() const; void highlightWindowsChanged() const; void addLauncher(const QUrl &url) const; void showAllPlaces(); private Q_SLOTS: void toolTipWindowChanged(QQuickWindow *window); void handleJumpListAction() const; void handleRecentDocumentAction() const; private: void updateWindowHighlight(); QQuickItem *m_taskManagerItem = nullptr; QQuickItem *m_toolTipItem = nullptr; QQuickWindow *m_groupDialog = nullptr; WId m_panelWinId; bool m_highlightWindows; QList m_windowsToHighlight; QActionGroup *m_actionGroup = nullptr; KActivities::Consumer *m_activitiesConsumer = nullptr; }; #endif