summaryrefslogtreecommitdiffstats
path: root/ksysguard/gui/SensorDisplayLib/ProcessController.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ksysguard/gui/SensorDisplayLib/ProcessController.cc')
-rw-r--r--ksysguard/gui/SensorDisplayLib/ProcessController.cc472
1 files changed, 472 insertions, 0 deletions
diff --git a/ksysguard/gui/SensorDisplayLib/ProcessController.cc b/ksysguard/gui/SensorDisplayLib/ProcessController.cc
new file mode 100644
index 000000000..b9fcd4f06
--- /dev/null
+++ b/ksysguard/gui/SensorDisplayLib/ProcessController.cc
@@ -0,0 +1,472 @@
+/*
+ KSysGuard, the KDE System Guard
+
+ Copyright (c) 1999 - 2001 Chris Schlaeger <[email protected]>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms version 2 of of the GNU General Public
+ License as published by the Free Software Foundation.
+
+ 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.
+
+ KSysGuard is currently maintained by Chris Schlaeger <[email protected]>.
+ Please do not commit any changes without consulting me first. Thanks!
+
+*/
+
+#include <assert.h>
+
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdialogbase.h>
+#include <klistviewsearchline.h>
+
+#include <ksgrd/SensorManager.h>
+
+#include "ProcessController.moc"
+#include "SignalIDs.h"
+
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+
+#include <kapplication.h>
+#include <kpushbutton.h>
+
+
+
+ProcessController::ProcessController(QWidget* parent, const char* name, const QString &title, bool nf)
+ : KSGRD::SensorDisplay(parent, name, title, nf)
+{
+ dict.setAutoDelete(true);
+ dict.insert("Name", new QString(i18n("Name")));
+ dict.insert("PID", new QString(i18n("PID")));
+ dict.insert("PPID", new QString(i18n("PPID")));
+ dict.insert("UID", new QString(i18n("UID")));
+ dict.insert("GID", new QString(i18n("GID")));
+ dict.insert("Status", new QString(i18n("Status")));
+ dict.insert("User%", new QString(i18n("User%")));
+ dict.insert("System%", new QString(i18n("System%")));
+ dict.insert("Nice", new QString(i18n("Nice")));
+ dict.insert("VmSize", new QString(i18n("VmSize")));
+ dict.insert("VmRss", new QString(i18n("VmRss")));
+ dict.insert("Login", new QString(i18n("Login")));
+ dict.insert("Command", new QString(i18n("Command")));
+
+ // Setup the geometry management.
+ gm = new QVBoxLayout(this, 10);
+ Q_CHECK_PTR(gm);
+ gm->addSpacing(15);
+
+ gmSearch = new QHBoxLayout();
+ Q_CHECK_PTR(gmSearch);
+ gm->addLayout(gmSearch, 0);
+
+ // Create the table that lists the processes.
+ pList = new ProcessList(this, "pList");
+ Q_CHECK_PTR(pList);
+ pList->setShowSortIndicator(true);
+ pListSearchLine = new KListViewSearchLineWidget(pList, this, "process_list_search_line");
+ gmSearch->addWidget(pListSearchLine, 1);
+
+ connect(pList, SIGNAL(killProcess(int, int)),
+ this, SLOT(killProcess(int, int)));
+ connect(pList, SIGNAL(reniceProcess(const QValueList<int> &, int)),
+ this, SLOT(reniceProcess(const QValueList<int> &, int)));
+ connect(pList, SIGNAL(listModified(bool)),
+ this, SLOT(setModified(bool)));
+
+ /* Create the combo box to configure the process filter. The
+ * cbFilter must be created prior to constructing pList as the
+ * pList constructor sets cbFilter to its start value. */
+ cbFilter = new QComboBox(this, "pList_cbFilter");
+ Q_CHECK_PTR(cbFilter);
+ gmSearch->addWidget(cbFilter,0);
+ cbFilter->insertItem(i18n("All Processes"), 0);
+ cbFilter->insertItem(i18n("System Processes"), 1);
+ cbFilter->insertItem(i18n("User Processes"), 2);
+ cbFilter->insertItem(i18n("Own Processes"), 3);
+ cbFilter->setMinimumSize(cbFilter->sizeHint());
+ // Create the check box to switch between tree view and list view.
+ xbTreeView = new QCheckBox(i18n("&Tree"), this, "xbTreeView");
+ Q_CHECK_PTR(xbTreeView);
+ xbTreeView->setMinimumSize(xbTreeView->sizeHint());
+ connect(xbTreeView, SIGNAL(toggled(bool)),
+ this, SLOT(setTreeView(bool)));
+
+
+ /* When the both cbFilter and pList are constructed we can connect the
+ * missing link. */
+ connect(cbFilter, SIGNAL(activated(int)),
+ this, SLOT(filterModeChanged(int)));
+
+ // Create the 'Refresh' button.
+ bRefresh = new KPushButton( KGuiItem( i18n( "&Refresh" ), "reload" ),
+ this, "bRefresh" );
+ Q_CHECK_PTR(bRefresh);
+ bRefresh->setMinimumSize(bRefresh->sizeHint());
+ connect(bRefresh, SIGNAL(clicked()), this, SLOT(updateList()));
+
+ // Create the 'Kill' button.
+ bKill = new KPushButton(i18n("&Kill"), this, "bKill");
+ Q_CHECK_PTR(bKill);
+ bKill->setMinimumSize(bKill->sizeHint());
+ connect(bKill, SIGNAL(clicked()), this, SLOT(killProcess()));
+ /* Disable the kill button until we know that the daemon supports the
+ * kill command. */
+ bKill->setEnabled(false);
+ killSupported = false;
+
+ gm->addWidget(pList, 1);
+
+ gm1 = new QHBoxLayout();
+ Q_CHECK_PTR(gm1);
+ gm->addLayout(gm1, 0);
+ gm1->addStretch();
+ gm1->addWidget(xbTreeView);
+ gm1->addStretch();
+ gm1->addWidget(bRefresh);
+ gm1->addStretch();
+ gm1->addWidget(bKill);
+ gm1->addStretch();
+ gm->addSpacing(5);
+
+ gm->activate();
+
+ setPlotterWidget(pList);
+
+ setMinimumSize(sizeHint());
+ fixTabOrder();
+}
+
+void ProcessController::setSearchFocus() {
+ //stupid search line widget. See rant in fixTabOrder
+ if(!pListSearchLine->searchLine())
+ QTimer::singleShot(100, this, SLOT(setSearchFocus()));
+ else {
+ pListSearchLine->searchLine()->setFocus();
+ }
+}
+void ProcessController::fixTabOrder() {
+
+ //Wow, I hate this search line widget so much.
+ //It creates the searchline in a singleshot timer. This makes it totally unpredictable when searchLine is actually valid.
+ //So we set up singleshot timer and call ourselves over and over until it's ready.
+ //
+ //Did i mention I hate this?
+ if(!pListSearchLine->searchLine())
+ QTimer::singleShot(100, this, SLOT(fixTabOrder()));
+ else {
+ setTabOrder(pListSearchLine->searchLine(), cbFilter);
+ setTabOrder(cbFilter, pList);
+ setTabOrder(pList, xbTreeView);
+ setTabOrder(xbTreeView, bRefresh);
+ setTabOrder(bRefresh, bKill);
+ }
+}
+
+void
+ProcessController::resizeEvent(QResizeEvent* ev)
+{
+ if(frame())
+ frame()->setGeometry(0, 0, width(), height());
+
+ QWidget::resizeEvent(ev);
+}
+
+bool
+ProcessController::addSensor(const QString& hostName,
+ const QString& sensorName,
+ const QString& sensorType,
+ const QString& title)
+{
+ if (sensorType != "table")
+ return (false);
+
+ registerSensor(new KSGRD::SensorProperties(hostName, sensorName, sensorType, title));
+ /* This just triggers the first communication. The full set of
+ * requests are send whenever the sensor reconnects (detected in
+ * sensorError(). */
+
+ sendRequest(hostName, "test kill", 4);
+
+ if (title.isEmpty())
+ setTitle(i18n("%1: Running Processes").arg(hostName));
+ else
+ setTitle(title);
+
+ return (true);
+}
+
+void
+ProcessController::updateList()
+{
+ sendRequest(sensors().at(0)->hostName(), "ps", 2);
+}
+
+void
+ProcessController::killProcess(int pid, int sig)
+{
+ sendRequest(sensors().at(0)->hostName(),
+ QString("kill %1 %2" ).arg(pid).arg(sig), 3);
+
+ if ( !timerOn() )
+ // give ksysguardd time to update its proccess list
+ QTimer::singleShot(3000, this, SLOT(updateList()));
+ else
+ updateList();
+}
+
+void
+ProcessController::killProcess()
+{
+ const QStringList& selectedAsStrings = pList->getSelectedAsStrings();
+ if (selectedAsStrings.isEmpty())
+ {
+ KMessageBox::sorry(this,
+ i18n("You need to select a process first."));
+ return;
+ }
+ else
+ {
+ QString msg = i18n("Do you want to kill the selected process?",
+ "Do you want to kill the %n selected processes?",
+ selectedAsStrings.count());
+
+ KDialogBase *dlg = new KDialogBase ( i18n ("Kill Process"),
+ KDialogBase::Yes | KDialogBase::Cancel,
+ KDialogBase::Yes, KDialogBase::Cancel, this->parentWidget(),
+ "killconfirmation",
+ true, true, KGuiItem(i18n("Kill")));
+
+ bool dontAgain = false;
+
+ int res = KMessageBox::createKMessageBox(dlg, QMessageBox::Question,
+ msg, selectedAsStrings,
+ i18n("Do not ask again"), &dontAgain,
+ KMessageBox::Notify);
+
+ if (res != KDialogBase::Yes)
+ {
+ return;
+ }
+ }
+
+ const QValueList<int>& selectedPIds = pList->getSelectedPIds();
+
+ // send kill signal to all seleted processes
+ QValueListConstIterator<int> it;
+ for (it = selectedPIds.begin(); it != selectedPIds.end(); ++it)
+ sendRequest(sensors().at(0)->hostName(), QString("kill %1 %2" ).arg(*it)
+ .arg(MENU_ID_SIGKILL), 3);
+
+ if ( !timerOn())
+ // give ksysguardd time to update its proccess list
+ QTimer::singleShot(3000, this, SLOT(updateList()));
+ else
+ updateList();
+}
+
+void
+ProcessController::reniceProcess(const QValueList<int> &pids, int niceValue)
+{
+ for( QValueList<int>::ConstIterator it = pids.constBegin(), end = pids.constEnd(); it != end; ++it )
+ sendRequest(sensors().at(0)->hostName(),
+ QString("setpriority %1 %2" ).arg(*it).arg(niceValue), 5);
+ sendRequest(sensors().at(0)->hostName(), "ps", 2); //update the display afterwards
+}
+
+void
+ProcessController::answerReceived(int id, const QString& answer)
+{
+ /* We received something, so the sensor is probably ok. */
+ sensorError(id, false);
+
+ switch (id)
+ {
+ case 1:
+ {
+ /* We have received the answer to a ps? command that contains
+ * the information about the table headers. */
+ KSGRD::SensorTokenizer lines(answer, '\n');
+ if (lines.count() != 2)
+ {
+ kdDebug (1215) << "ProcessController::answerReceived(1)"
+ "wrong number of lines [" << answer << "]" << endl;
+ sensorError(id, true);
+ return;
+ }
+ KSGRD::SensorTokenizer headers(lines[0], '\t');
+ KSGRD::SensorTokenizer colTypes(lines[1], '\t');
+
+ pList->removeColumns();
+ for (unsigned int i = 0; i < headers.count(); i++)
+ {
+ QString header;
+ if (dict[headers[i]])
+ header = *dict[headers[i]];
+ else
+ header = headers[i];
+ pList->addColumn(header, colTypes[i]);
+ }
+
+ break;
+ }
+ case 2:
+ /* We have received the answer to a ps command that contains a
+ * list of processes with various additional information. */
+ pList->update(answer);
+ pListSearchLine->searchLine()->updateSearch(); //re-apply the filter
+ break;
+ case 3:
+ {
+ // result of kill operation
+ kdDebug(1215) << answer << endl;
+ KSGRD::SensorTokenizer vals(answer, '\t');
+ switch (vals[0].toInt())
+ {
+ case 0: // successful kill operation
+ break;
+ case 1: // unknown error
+ KSGRD::SensorMgr->notify(
+ i18n("Error while attempting to kill process %1.")
+ .arg(vals[1]));
+ break;
+ case 2:
+ KSGRD::SensorMgr->notify(
+ i18n("Insufficient permissions to kill "
+ "process %1.").arg(vals[1]));
+ break;
+ case 3:
+ KSGRD::SensorMgr->notify(
+ i18n("Process %1 has already disappeared.")
+ .arg(vals[1]));
+ break;
+ case 4:
+ KSGRD::SensorMgr->notify(i18n("Invalid Signal."));
+ break;
+ }
+ break;
+ }
+ case 4:
+ killSupported = (answer.toInt() == 1);
+ pList->setKillSupported(killSupported);
+ bKill->setEnabled(killSupported);
+ break;
+ case 5:
+ {
+ // result of renice operation
+ kdDebug(1215) << answer << endl;
+ KSGRD::SensorTokenizer vals(answer, '\t');
+ switch (vals[0].toInt())
+ {
+ case 0: // successful renice operation
+ break;
+ case 1: // unknown error
+ KSGRD::SensorMgr->notify(
+ i18n("Error while attempting to renice process %1.")
+ .arg(vals[1]));
+ break;
+ case 2:
+ KSGRD::SensorMgr->notify(
+ i18n("Insufficient permissions to renice "
+ "process %1.").arg(vals[1]));
+ break;
+ case 3:
+ KSGRD::SensorMgr->notify(
+ i18n("Process %1 has already disappeared.")
+ .arg(vals[1]));
+ break;
+ case 4:
+ KSGRD::SensorMgr->notify(i18n("Invalid argument."));
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void
+ProcessController::sensorError(int, bool err)
+{
+ if (err == sensors().at(0)->isOk())
+ {
+ if (!err)
+ {
+ /* Whenever the communication with the sensor has been
+ * (re-)established we need to requests the full set of
+ * properties again, since the back-end might be a new
+ * one. */
+ sendRequest(sensors().at(0)->hostName(), "test kill", 4);
+ sendRequest(sensors().at(0)->hostName(), "ps?", 1);
+ sendRequest(sensors().at(0)->hostName(), "ps", 2);
+ }
+
+ /* This happens only when the sensorOk status needs to be changed. */
+ sensors().at(0)->setIsOk( !err );
+ }
+ setSensorOk(sensors().at(0)->isOk());
+}
+
+bool
+ProcessController::restoreSettings(QDomElement& element)
+{
+ bool result = addSensor(element.attribute("hostName"),
+ element.attribute("sensorName"), (element.attribute("sensorType").isEmpty() ? "table" : element.attribute("sensorType")),
+ QString::null);
+
+ xbTreeView->setChecked(element.attribute("tree").toInt());
+ setTreeView(element.attribute("tree").toInt());
+
+ uint filter = element.attribute("filter").toUInt();
+ cbFilter->setCurrentItem(filter);
+ filterModeChanged(filter);
+
+ uint col = element.attribute("sortColumn").toUInt();
+ bool inc = element.attribute("incrOrder").toUInt();
+
+ if (!pList->load(element))
+ return (false);
+
+ pList->setSortColumn(col, inc);
+
+ SensorDisplay::restoreSettings(element);
+
+ setModified(false);
+
+ return (result);
+}
+
+bool
+ProcessController::saveSettings(QDomDocument& doc, QDomElement& element, bool save)
+{
+ element.setAttribute("hostName", sensors().at(0)->hostName());
+ element.setAttribute("sensorName", sensors().at(0)->name());
+ element.setAttribute("sensorType", sensors().at(0)->type());
+ element.setAttribute("tree", (uint) xbTreeView->isChecked());
+ element.setAttribute("filter", cbFilter->currentItem());
+ element.setAttribute("sortColumn", pList->getSortColumn());
+ element.setAttribute("incrOrder", pList->getIncreasing());
+
+ if (!pList->save(doc, element))
+ return (false);
+
+ SensorDisplay::saveSettings(doc, element);
+
+ if (save)
+ setModified(false);
+
+ return (true);
+}