diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ PURPOSE "Used by the HTML-based GUI ksysguard library" ) -find_package(KF5 REQUIRED COMPONENTS CoreAddons Config I18n WindowSystem Completion Auth WidgetsAddons IconThemes ConfigWidgets Service) +find_package(KF5 REQUIRED COMPONENTS CoreAddons Config I18n WindowSystem Completion Auth WidgetsAddons IconThemes ConfigWidgets Service Plasma GlobalAccel KIO) find_package(KF5 OPTIONAL_COMPONENTS Plasma) set_package_properties(KF5Plasma PROPERTIES URL "https://cgit.kde.org/plasma-framework.git/" diff --git a/processui/CMakeLists.txt b/processui/CMakeLists.txt --- a/processui/CMakeLists.txt +++ b/processui/CMakeLists.txt @@ -44,6 +44,9 @@ KF5::ConfigWidgets KF5::WidgetsAddons KF5::IconThemes + KF5::GlobalAccel + KF5::Service + KF5::KIOWidgets ) target_include_directories(processui PUBLIC diff --git a/processui/ProcessWidgetUI.ui b/processui/ProcessWidgetUI.ui --- a/processui/ProcessWidgetUI.ui +++ b/processui/ProcessWidgetUI.ui @@ -6,20 +6,29 @@ 0 0 - 490 + 498 472 - + + 0 + + + 0 + + + 0 + + 0 - + - false + true @@ -41,13 +50,16 @@ &End Process... - + + Qt::ToolButtonTextBesideIcon + + false - + false - + false @@ -68,7 +80,7 @@ Quick search - + true @@ -135,6 +147,13 @@ + + + + Tools + + + diff --git a/processui/ksysguardprocesslist.cpp b/processui/ksysguardprocesslist.cpp --- a/processui/ksysguardprocesslist.cpp +++ b/processui/ksysguardprocesslist.cpp @@ -56,6 +56,9 @@ #include #include #include +#include +#include +#include #include "ReniceDlg.h" #include "ui_ProcessWidgetUI.h" @@ -68,6 +71,7 @@ //#define DO_MODELCHECK #ifdef DO_MODELCHECK #include "modeltest.h" +class KGlobalAccel; #endif class ProgressBarItemDelegate : public QStyledItemDelegate { @@ -192,7 +196,7 @@ struct KSysGuardProcessListPrivate { KSysGuardProcessListPrivate(KSysGuardProcessList* q, const QString &hostName) - : mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(nullptr), mUpdateTimer(nullptr) + : mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(nullptr), mUpdateTimer(nullptr), mToolsMenu(new QMenu()) { mScripting = nullptr; mNeedToExpandInit = false; @@ -285,6 +289,8 @@ QAction *sigKill; QAction *sigUsr1; QAction *sigUsr2; + + QMenu* mToolsMenu; }; KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostName) @@ -357,7 +363,6 @@ d->mUi->treeView->setDragEnabled(true); d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly); - //Sort by username by default d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder); @@ -376,8 +381,79 @@ retranslateUi(); + d->mUi->btnKillProcess->setEnabled(false); d->mUi->btnKillProcess->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); - d->mUi->btnKillProcess->setToolTip(i18n("End the selected process. Warning - you may lose unsaved work.
Right click on a process to send other signals.
See What's This for technical information.
To target a specific window to kill, press Ctrl+Alt+Esc at any time.")); + d->mUi->btnKillProcess->setToolTip(i18n("End the selected process. Warning - you may lose unsaved work.
Right click on a process to send other signals.
See What's This for technical information.")); + + auto d1 = d; + auto addByDesktopName = [this, d1](const QString& desktopName) + { + auto d = d1; + auto kService = KService::serviceByDesktopName(desktopName); + if (kService != nullptr) { + auto action = new QAction(QIcon::fromTheme(kService->icon()), + kService->name(), nullptr); + + connect(action, &QAction::triggered, this, + [kService](bool) { + KRun::runService(*kService, { }, nullptr); + }); + d->mToolsMenu->addAction(action); + } + }; + + addByDesktopName(QStringLiteral("org.kde.konsole")); + + // QCoreApplication::applicationFilePath() returns something like "/usr/bin/ksysguard" + // when this view is embedded in KSysGuard. + // And in this case we do not add the KSysGuard item to the menu. + if (!QCoreApplication::applicationFilePath().contains(QStringLiteral("ksysguard"))) { + addByDesktopName(QStringLiteral("org.kde.ksysguard")); + } + + addByDesktopName(QStringLiteral("org.kde.ksystemlog")); + addByDesktopName(QStringLiteral("org.kde.kinfocenter")); + addByDesktopName(QStringLiteral("org.kde.filelight")); + addByDesktopName(QStringLiteral("sweeper")); + addByDesktopName(QStringLiteral("kmag")); + addByDesktopName(QStringLiteral("htop")); + + { + auto action = new QAction(i18n("Run Command"), this); + action->setIcon(QIcon::fromTheme(QStringLiteral("system-run"))); + connect(action, &QAction::triggered, this, [](){ + KRun::runCommand(QStringLiteral("krunner"), nullptr); + }); + d->mToolsMenu->addAction(action); + } + + { + // find shortcut of xkill functionality which is defined in KWin + auto killWindowKeys = KGlobalAccel::self()->globalShortcut(QStringLiteral("kwin"), QStringLiteral("Kill Window")); + QString killWindowShortcut = i18nc("the keyboard shortcut of the Kill Window function", "not set"); + if (killWindowKeys.size() > 0) { + killWindowShortcut = killWindowKeys[0].toString(); + } + auto killWindowAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), + i18nc("%1 is a keyboard shortcut", "Kill a window (%1)", killWindowShortcut), this); + + // Alternative to using xkill directly. The KWin method also allows to press Esc to abort. + auto killWindowKwinMethod = new QDBusInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin")); + // If KWin is not the window manager, then we disable the entry: + if (!killWindowKwinMethod->isValid()) { + killWindowAction->setEnabled(false); + } + + connect(killWindowAction, &QAction::triggered, this, [this, killWindowKwinMethod](bool) { + // with DBus call, always use the async method. + // Otherwise it could wait up to 30 seconds in certain situations. + killWindowKwinMethod->asyncCall(QStringLiteral("killWindow")); + }); + + d->mToolsMenu->addAction(killWindowAction); + } + + d->mUi->btnTools->setMenu(d->mToolsMenu); } KSysGuardProcessList::~KSysGuardProcessList() @@ -413,8 +489,8 @@ d->mFilterModel.setFilterRegExp(newText.trimmed()); if(isVisible()) expandInit(); - d->mUi->btnKillProcess->setEnabled( d->mUi->treeView->selectionModel()->hasSelection() ); - d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex()); + d->mUi->btnKillProcess->setEnabled(d->mUi->treeView->selectionModel()->hasSelection()); + d->mUi->treeView->scrollTo(d->mUi->treeView->currentIndex()); } int KSysGuardProcessList::visibleProcessesCount() const { @@ -453,7 +529,7 @@ if(numSelected == d->mNumItemsSelected) return; d->mNumItemsSelected = numSelected; - d->mUi->btnKillProcess->setEnabled( numSelected != 0 ); + d->mUi->btnKillProcess->setEnabled(numSelected != 0); d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected)); d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected));