File indexing completed on 2024-04-21 05:47:46

0001 /*******************************************************************************
0002  * Copyright (C) 2016 Ragnar Thomsen <rthomsen6@gmail.com>                     *
0003  *                                                                             *
0004  * This program is free software: you can redistribute it and/or modify it     *
0005  * under the terms of the GNU General Public License as published by the Free  *
0006  * Software Foundation, either version 2 of the License, or (at your option)   *
0007  * any later version.                                                          *
0008  *                                                                             *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT *
0010  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       *
0011  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    *
0012  * more details.                                                               *
0013  *                                                                             *
0014  * You should have received a copy of the GNU General Public License along     *
0015  * with this program. If not, see <http://www.gnu.org/licenses/>.              *
0016  *******************************************************************************/
0017 
0018 #include "mainwindow.h"
0019 
0020 #include <KActionCollection>
0021 #include <KAuth/Action>
0022 #include <KAuth/ExecuteJob>
0023 #include <KColorScheme>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 #include <KXMLGUIFactory>
0027 
0028 #include <QDebug>
0029 #include <QPlainTextEdit>
0030 #include <QProcess>
0031 #include <QPushButton>
0032 #include <QRegularExpression>
0033 #include <QtDBus>
0034 
0035 #include <unistd.h>
0036 
0037 MainWindow::MainWindow(QWidget *parent)
0038     : KXmlGuiWindow(parent)
0039 {
0040     ui.setupUi(this);
0041 
0042     ui.leSearchUnit->setFocus();
0043 
0044     // See if systemd is reachable via dbus
0045     if (getDbusProperty(QStringLiteral("Version"), sysdMgr) != QLatin1String("invalidIface")) {
0046         systemdVersion = getDbusProperty(QStringLiteral("Version"), sysdMgr).toString().remove(QStringLiteral("systemd ")).toInt();
0047         qDebug() << "Detected systemd" << systemdVersion;
0048     } else {
0049         qDebug() << "Unable to contact systemd daemon!";
0050         KMessageBox::error(this, i18n("Unable to contact the systemd daemon. Quitting..."), i18n("SystemdGenie"));
0051         close();
0052     }
0053 
0054     // Search for user dbus.
0055     if (QFileInfo::exists(QStringLiteral("/run/user/%1/bus").arg(QString::number(getuid())))) {
0056         m_userBusPath = QStringLiteral("unix:path=/run/user/%1/bus").arg(QString::number(getuid()));
0057     } else if (QFileInfo::exists(QStringLiteral("/run/user/%1/dbus/user_bus_socket").arg(QString::number(getuid())))) {
0058         m_userBusPath = QStringLiteral("unix:path=/run/user/%1/dbus/user_bus_socket").arg(QString::number(getuid()));
0059     } else {
0060         qDebug() << "User dbus not found. Support for user units disabled.";
0061         ui.tabWidget->setTabEnabled(1, false);
0062         ui.tabWidget->setTabToolTip(1, i18n("User dbus not found. Support for user units disabled."));
0063         enableUserUnits = false;
0064     }
0065 
0066     QStringList allowUnitTypes = QStringList{i18n("All"), i18n("Services"), i18n("Automounts"), i18n("Devices"),
0067                                              i18n("Mounts"), i18n("Paths"), i18n("Scopes"), i18n("Slices"),
0068                                              i18n("Sockets"), i18n("Swaps"), i18n("Targets"), i18n("Timers")};
0069     ui.cmbUnitTypes->addItems(allowUnitTypes);
0070     ui.cmbUserUnitTypes->addItems(allowUnitTypes);
0071 
0072     // Get list of units
0073     slotRefreshUnitsList(true, sys);
0074     slotRefreshUnitsList(true, user);
0075 
0076     setupUnitslist();
0077     setupSessionlist();
0078     setupTimerlist();
0079     setupConfFilelist();
0080     setupActions();
0081 
0082     setupSignalSlots();
0083 
0084     m_lblLog = new QLabel(this);
0085     m_lblLog->setText(QString());
0086     statusBar()->addPermanentWidget(m_lblLog, 20);
0087 
0088     setupGUI(Default, QStringLiteral("systemdgenieui.rc"));
0089 }
0090 
0091 MainWindow::~MainWindow()
0092 {
0093 }
0094 
0095 void MainWindow::quit()
0096 {
0097     close();
0098 }
0099 
0100 
0101 QDBusArgument &operator<<(QDBusArgument &argument, const SystemdUnit &unit)
0102 {
0103     argument.beginStructure();
0104     argument << unit.id
0105              << unit.description
0106              << unit.load_state
0107              << unit.active_state
0108              << unit.sub_state
0109              << unit.following
0110              << unit.unit_path
0111              << unit.job_id
0112              << unit.job_type
0113              << unit.job_path;
0114     argument.endStructure();
0115     return argument;
0116 }
0117 
0118 const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdUnit &unit)
0119 {
0120     argument.beginStructure();
0121     argument >> unit.id
0122             >> unit.description
0123             >> unit.load_state
0124             >> unit.active_state
0125             >> unit.sub_state
0126             >> unit.following
0127             >> unit.unit_path
0128             >> unit.job_id
0129             >> unit.job_type
0130             >> unit.job_path;
0131     argument.endStructure();
0132     return argument;
0133 }
0134 
0135 QDBusArgument &operator<<(QDBusArgument &argument, const SystemdSession &session)
0136 {
0137     argument.beginStructure();
0138     argument << session.session_id
0139              << session.user_id
0140              << session.user_name
0141              << session.seat_id
0142              << session.session_path;
0143     argument.endStructure();
0144     return argument;
0145 }
0146 
0147 const QDBusArgument &operator>>(const QDBusArgument &argument, SystemdSession &session)
0148 {
0149     argument.beginStructure();
0150     argument >> session.session_id
0151             >> session.user_id
0152             >> session.user_name
0153             >> session.seat_id
0154             >> session.session_path;
0155     argument.endStructure();
0156     return argument;
0157 }
0158 
0159 void MainWindow::setupSignalSlots()
0160 {
0161     // Connect signals for unit tabs.
0162     connect(ui.chkInactiveUnits, &QCheckBox::stateChanged, this, &MainWindow::slotChkShowUnits);
0163     connect(ui.chkUnloadedUnits, &QCheckBox::stateChanged, this, &MainWindow::slotChkShowUnits);
0164     connect(ui.chkInactiveUserUnits, &QCheckBox::stateChanged, this, &MainWindow::slotChkShowUnits);
0165     connect(ui.chkUnloadedUserUnits, &QCheckBox::stateChanged, this, &MainWindow::slotChkShowUnits);
0166     connect(ui.cmbUnitTypes, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::slotCmbUnitTypes);
0167     connect(ui.cmbUserUnitTypes,static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::slotCmbUnitTypes);
0168     connect(ui.tblUnits, &QTableView::customContextMenuRequested, this, &MainWindow::slotUnitContextMenu);
0169     connect(ui.tblUserUnits, &QTableView::customContextMenuRequested, this, &MainWindow::slotUnitContextMenu);
0170     connect(ui.tblConfFiles, &QTableView::customContextMenuRequested, this, &MainWindow::slotConfFileContextMenu);
0171     connect(ui.leSearchUnit, &QLineEdit::textChanged, this, &MainWindow::slotLeSearchUnitChanged);
0172     connect(ui.leSearchUserUnit, &QLineEdit::textChanged, this, &MainWindow::slotLeSearchUnitChanged);
0173     connect(ui.tblUnits->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions);
0174     connect(ui.tblUserUnits->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions);
0175     connect(ui.tblConfFiles->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions);
0176     connect(ui.tblSessions->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions);
0177     connect(ui.tabWidget, &QTabWidget::currentChanged, this, &MainWindow::updateActions);
0178 
0179     connect(ui.tabWidget, &QTabWidget::currentChanged, [=]{
0180         if (ui.tabWidget->currentIndex() == 0)
0181             ui.leSearchUnit->setFocus();
0182         else if (ui.tabWidget->currentIndex() == 1)
0183             ui.leSearchUserUnit->setFocus();
0184     });
0185 
0186     // Connect signals for sessions tab.
0187     connect(ui.tblSessions, &QTableView::customContextMenuRequested, this, &MainWindow::slotSessionContextMenu);
0188 
0189     // Subscribe to dbus signals from systemd system daemon and connect them to slots.
0190     callDbusMethod(QStringLiteral("Subscribe"), sysdMgr);
0191     m_systemBus.connect(connSystemd,
0192                         pathSysdMgr,
0193                         ifaceMgr,
0194                         QStringLiteral("Reloading"),
0195                         this,
0196                         SLOT(slotSystemSystemdReloading(bool)));
0197     /*m_systemBus.connect(connSystemd,
0198                         pathSysdMgr,
0199                         ifaceMgr,
0200                         QStringLiteral("UnitNew"),
0201                         this,
0202                         SLOT(slotSystemUnitNew(const QString&, const QDBusObjectPath&)));
0203     m_systemBus.connect(connSystemd,
0204                         pathSysdMgr,
0205                         ifaceMgr,
0206                         QStringLiteral("UnitRemoved"),
0207                         this,
0208                         SLOT(slotSystemUnitRemoved(const QString&, const QDBusObjectPath&)));*/
0209 
0210     m_systemBus.connect(connSystemd,
0211                         pathSysdMgr,
0212                         ifaceMgr,
0213                         QStringLiteral("JobNew"),
0214                         this,
0215                         SLOT(slotSystemJobNew(uint, const QDBusObjectPath&, const QString&)));
0216     // We need to use the JobRemoved signal, because stopping units does not emit PropertiesChanged signal
0217     m_systemBus.connect(connSystemd,
0218                         pathSysdMgr,
0219                         ifaceMgr,
0220                         QStringLiteral("JobRemoved"),
0221                         this,
0222                         SLOT(slotSystemJobRemoved(uint, const QDBusObjectPath&, const QString&, const QString&)));
0223 
0224     m_systemBus.connect(connSystemd,
0225                         pathSysdMgr,
0226                         ifaceMgr,
0227                         QStringLiteral("UnitFilesChanged"),
0228                         this,
0229                         SLOT(slotSystemUnitFilesChanged()));
0230     m_systemBus.connect(connSystemd,
0231                         QString(),
0232                         ifaceDbusProp,
0233                         QStringLiteral("PropertiesChanged"),
0234                         this,
0235                         SLOT(slotSystemPropertiesChanged(const QString&, const QVariantMap&, const QStringList&)));
0236 
0237 
0238     // Subscribe to dbus signals from systemd user daemon and connect them to slots
0239     callDbusMethod(QStringLiteral("Subscribe"), sysdMgr, user);
0240     QDBusConnection userbus = QDBusConnection::connectToBus(m_userBusPath, connSystemd);
0241     userbus.connect(connSystemd,
0242                     pathSysdMgr,
0243                     ifaceMgr,
0244                     QStringLiteral("Reloading"),
0245                     this,
0246                     SLOT(slotUserSystemdReloading(bool)));
0247     /*userbus.connect(connSystemd,
0248                     pathSysdMgr,
0249                     ifaceMgr,
0250                     QStringLiteral("UnitNew"),
0251                     this,
0252                     SLOT(slotUnitNew(const QString&, const QDBusObjectPath&)));
0253     userbus.connect(connSystemd,
0254                     pathSysdMgr,
0255                     ifaceMgr,
0256                     QStringLiteral("UnitRemoved"),
0257                     this,
0258                     SLOT(slotUnitRemoved(const QString&, const QDBusObjectPath&)));*/
0259 
0260     userbus.connect(connSystemd,
0261                     pathSysdMgr,
0262                     ifaceMgr,
0263                     QStringLiteral("JobNew"),
0264                     this,
0265                     SLOT(slotUserJobNew(uint, const QDBusObjectPath&, const QString&)));
0266     // We need to use the JobRemoved signal, because stopping units does not emit PropertiesChanged signal
0267     userbus.connect(connSystemd,
0268                     pathSysdMgr,
0269                     ifaceMgr,
0270                     QStringLiteral("JobRemoved"),
0271                     this,
0272                     SLOT(slotUserJobRemoved(uint, const QDBusObjectPath&, const QString&, const QString&)));
0273 
0274     userbus.connect(connSystemd,
0275                     pathSysdMgr,
0276                     ifaceMgr,
0277                     QStringLiteral("UnitFilesChanged"),
0278                     this,
0279                     SLOT(slotUserUnitFilesChanged()));
0280     userbus.connect(connSystemd,
0281                     QString(),
0282                     ifaceDbusProp,
0283                     QStringLiteral("PropertiesChanged"),
0284                     this,
0285                     SLOT(slotUserPropertiesChanged(const QString&, const QVariantMap&, const QStringList&)));
0286 
0287     /*
0288     userbus.connect(connSystemd,
0289                     pathSysdMgr,
0290                     ifaceMgr,
0291                     QStringLiteral("Reloading"),
0292                     this,
0293                     SLOT(slotUserSystemdReloading(bool)));
0294     userbus.connect(connSystemd,
0295                     pathSysdMgr,
0296                     ifaceMgr,
0297                     QStringLiteral("UnitFilesChanged"),
0298                     this,
0299                     SLOT(slotUserUnitsChanged()));
0300     userbus.connect(connSystemd, QString(), ifaceDbusProp,
0301                     QStringLiteral("PropertiesChanged"), this, SLOT(slotUserUnitsChanged()));
0302     userbus.connect(connSystemd, pathSysdMgr, ifaceMgr,
0303                     QStringLiteral("JobRemoved"), this, SLOT(slotUserUnitsChanged())); */
0304 
0305     // logind.
0306     m_systemBus.connect(connLogind,
0307                         QString(),
0308                         ifaceDbusProp,
0309                         QStringLiteral("PropertiesChanged"),
0310                         this,
0311                         SLOT(slotLogindPropertiesChanged(QString,QVariantMap,QStringList)));
0312 }
0313 
0314 
0315 void MainWindow::setupUnitslist()
0316 {
0317     // Sets up the units list initially
0318 
0319     // Register the meta type for storing units
0320     qDBusRegisterMetaType<SystemdUnit>();
0321 
0322     QMap<filterType, QString> filters;
0323     filters[activeState] = QString();
0324     filters[unitType] = QString();
0325     filters[unitName] = QString();
0326 
0327     // QList<SystemdUnit> *ptrUnits;
0328     // ptrUnits = &m_systemUnitsList;
0329 
0330     // Setup the system unit model
0331     ui.tblUnits->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0332     m_systemUnitModel = new UnitModel(this, &m_systemUnitsList);
0333     m_systemUnitFilterModel = new SortFilterUnitModel(this);
0334     m_systemUnitFilterModel->setDynamicSortFilter(false);
0335     m_systemUnitFilterModel->initFilterMap(filters);
0336     m_systemUnitFilterModel->setSourceModel(m_systemUnitModel);
0337     ui.tblUnits->setModel(m_systemUnitFilterModel);
0338     ui.tblUnits->sortByColumn(0, Qt::AscendingOrder);
0339     ui.tblUnits->resizeColumnsToContents();
0340     ui.tblUnits->setColumnWidth(0, 400);
0341     ui.tblUnits->resizeRowsToContents();
0342 
0343     // Setup the user unit model
0344     ui.tblUserUnits->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0345     m_userUnitModel = new UnitModel(this, &m_userUnitsList, m_userBusPath);
0346     m_userUnitFilterModel = new SortFilterUnitModel(this);
0347     m_userUnitFilterModel->setDynamicSortFilter(false);
0348     m_userUnitFilterModel->initFilterMap(filters);
0349     m_userUnitFilterModel->setSourceModel(m_userUnitModel);
0350     ui.tblUserUnits->setModel(m_userUnitFilterModel);
0351     ui.tblUserUnits->sortByColumn(0, Qt::AscendingOrder);
0352     ui.tblUserUnits->resizeColumnsToContents();
0353     ui.tblUserUnits->setColumnWidth(0, 400);
0354     ui.tblUserUnits->resizeRowsToContents();
0355 
0356     slotChkShowUnits(-1);
0357 }
0358 
0359 void MainWindow::setupSessionlist()
0360 {
0361     // Sets up the session list initially
0362 
0363     // Register the meta type for storing units
0364     qDBusRegisterMetaType<SystemdSession>();
0365 
0366     // Setup model for session list
0367     m_sessionModel = new QStandardItemModel(this);
0368 
0369     // Install eventfilter to capture mouse move events
0370     ui.tblSessions->viewport()->installEventFilter(this);
0371 
0372     // Set header row
0373     m_sessionModel->setHorizontalHeaderItem(0, new QStandardItem(i18n("Session ID")));
0374     m_sessionModel->setHorizontalHeaderItem(1, new QStandardItem(i18n("Session Object Path"))); // This column is hidden
0375     m_sessionModel->setHorizontalHeaderItem(2, new QStandardItem(i18n("State")));
0376     m_sessionModel->setHorizontalHeaderItem(3, new QStandardItem(i18n("User ID")));
0377     m_sessionModel->setHorizontalHeaderItem(4, new QStandardItem(i18n("User Name")));
0378     m_sessionModel->setHorizontalHeaderItem(5, new QStandardItem(i18n("Seat ID")));
0379     ui.tblSessions->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0380 
0381     // Set model for QTableView (should be called after headers are set)
0382     ui.tblSessions->setModel(m_sessionModel);
0383     ui.tblSessions->setColumnHidden(1, true);
0384 
0385     // Add all the sessions
0386     slotRefreshSessionList();
0387 }
0388 
0389 void MainWindow::setupTimerlist()
0390 {
0391     // Sets up the timer list initially
0392 
0393     // Setup model for timer list
0394     m_timerModel = new QStandardItemModel(this);
0395 
0396     // Install eventfilter to capture mouse move events
0397     // ui.tblTimers->viewport()->installEventFilter(this);
0398 
0399     // Set header row
0400     m_timerModel->setHorizontalHeaderItem(0, new QStandardItem(i18n("Timer")));
0401     m_timerModel->setHorizontalHeaderItem(1, new QStandardItem(i18n("Next")));
0402     m_timerModel->setHorizontalHeaderItem(2, new QStandardItem(i18n("Left")));
0403     m_timerModel->setHorizontalHeaderItem(3, new QStandardItem(i18n("Last")));
0404     m_timerModel->setHorizontalHeaderItem(4, new QStandardItem(i18n("Passed")));
0405     m_timerModel->setHorizontalHeaderItem(5, new QStandardItem(i18n("Activates")));
0406     ui.tblTimers->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0407 
0408     // Set model for QTableView (should be called after headers are set)
0409     ui.tblTimers->setModel(m_timerModel);
0410     ui.tblTimers->sortByColumn(1, Qt::AscendingOrder);
0411 
0412     // Setup a timer that updates the left and passed columns every 5secs
0413     timer = new QTimer(this);
0414     connect(timer, SIGNAL(timeout()), this, SLOT(slotUpdateTimers()));
0415     timer->start(1000);
0416 
0417     slotRefreshTimerList();
0418 }
0419 
0420 void MainWindow::setupConfFilelist()
0421 {
0422     m_confFileModel = new QStandardItemModel(this);
0423     m_confFileModel->setHorizontalHeaderItem(0, new QStandardItem(i18n("File")));
0424     m_confFileModel->setHorizontalHeaderItem(1, new QStandardItem(i18n("Modified")));
0425     m_confFileModel->setHorizontalHeaderItem(2, new QStandardItem(i18n("Description")));
0426 
0427     ui.tblConfFiles->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0428     ui.tblConfFiles->setModel(m_confFileModel);
0429     ui.tblConfFiles->sortByColumn(1, Qt::AscendingOrder);
0430 
0431     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/coredump.conf"),
0432                                    QStringLiteral("coredump.conf"),
0433                                    i18n("Coredump generation and storage")));
0434     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/journal-upload.conf"),
0435                                    QStringLiteral("journal-upload.conf"),
0436                                    i18n("Send journal messages over network")));
0437     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/journald.conf"),
0438                                    QStringLiteral("journald.conf"),
0439                                    i18n("Journal manager settings")));
0440     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/logind.conf"),
0441                                    QStringLiteral("logind.conf"),
0442                                    i18n("Login manager configuration")));
0443     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/resolved.conf"),
0444                                    QStringLiteral("resolved.conf"),
0445                                    i18n("Network name resolution configuration")));
0446     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/system.conf"),
0447                                    QStringLiteral("systemd-system.conf"),
0448                                    i18n("Systemd daemon configuration")));
0449     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/timesyncd.conf"),
0450                                    QStringLiteral("timesyncd.conf"),
0451                                    i18n("Time synchronization settings")));
0452     m_confFileList.append(conffile(QStringLiteral("/etc/systemd/user.conf"),
0453                                    QStringLiteral("systemd-system.conf"),
0454                                    i18n("Systemd user daemon configuration")));
0455 
0456     slotRefreshConfFileList();
0457 
0458     QFileSystemWatcher *m_fileWatcher = new QFileSystemWatcher;
0459     connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::slotRefreshConfFileList);
0460     foreach (const conffile &f, m_confFileList) {
0461         m_fileWatcher->addPath(f.filePath);
0462     }
0463 }
0464 
0465 void MainWindow::slotRefreshConfFileList()
0466 {
0467     qDebug() << "Refreshing config files list...";
0468 
0469     foreach (const conffile &f, m_confFileList) {
0470         if (!QFileInfo::exists(f.filePath)) {
0471             continue;
0472         }
0473         QStandardItem *item = new QStandardItem(QIcon::fromTheme(QStringLiteral("text-plain")), QStringLiteral("%1").arg(f.filePath));
0474         QStandardItem *item2 = new QStandardItem(QFileInfo(f.filePath).lastModified().toString(QStringLiteral("yyyy-MM-dd HH:mm:ss")));
0475         QStandardItem *item3 = new QStandardItem(f.description);
0476         m_confFileModel->setItem(m_confFileList.indexOf(f), 0, item);
0477         m_confFileModel->setItem(m_confFileList.indexOf(f), 1, item2);
0478         m_confFileModel->setItem(m_confFileList.indexOf(f), 2, item3);
0479     }
0480     ui.tblConfFiles->resizeColumnsToContents();
0481     ui.tblConfFiles->resizeRowsToContents();
0482 }
0483 
0484 void MainWindow::slotChkShowUnits(int state)
0485 {
0486     if (state == -1 ||
0487         QObject::sender()->objectName() == QLatin1String("chkInactiveUnits") ||
0488         QObject::sender()->objectName() == QLatin1String("chkUnloadedUnits"))
0489     {
0490         // System units
0491         if (ui.chkInactiveUnits->isChecked())
0492         {
0493             ui.chkUnloadedUnits->setEnabled(true);
0494             if (ui.chkUnloadedUnits->isChecked())
0495                 m_systemUnitFilterModel->addFilterRegExp(activeState, QString());
0496             else
0497                 m_systemUnitFilterModel->addFilterRegExp(activeState, QStringLiteral("active"));
0498         }
0499         else
0500         {
0501             ui.chkUnloadedUnits->setEnabled(false);
0502             m_systemUnitFilterModel->addFilterRegExp(activeState, QStringLiteral("^(active)"));
0503         }
0504         m_systemUnitFilterModel->invalidate();
0505         ui.tblUnits->sortByColumn(ui.tblUnits->horizontalHeader()->sortIndicatorSection(),
0506                                   ui.tblUnits->horizontalHeader()->sortIndicatorOrder());
0507     }
0508     if (state == -1 ||
0509         QObject::sender()->objectName() == QLatin1String("chkInactiveUserUnits") ||
0510         QObject::sender()->objectName() == QLatin1String("chkUnloadedUserUnits"))
0511     {
0512         // User units
0513         if (ui.chkInactiveUserUnits->isChecked())
0514         {
0515             ui.chkUnloadedUserUnits->setEnabled(true);
0516             if (ui.chkUnloadedUserUnits->isChecked())
0517                 m_userUnitFilterModel->addFilterRegExp(activeState, QString());
0518             else
0519                 m_userUnitFilterModel->addFilterRegExp(activeState, QStringLiteral("active"));
0520         }
0521         else
0522         {
0523             ui.chkUnloadedUserUnits->setEnabled(false);
0524             m_userUnitFilterModel->addFilterRegExp(activeState, QStringLiteral("^(active)"));
0525         }
0526         m_userUnitFilterModel->invalidate();
0527         ui.tblUserUnits->sortByColumn(ui.tblUserUnits->horizontalHeader()->sortIndicatorSection(),
0528                                       ui.tblUserUnits->horizontalHeader()->sortIndicatorOrder());
0529     }
0530     updateUnitCount();
0531 }
0532 
0533 void MainWindow::slotCmbUnitTypes(int index)
0534 {
0535     // Filter unit list for a selected unit type
0536 
0537     if (QObject::sender()->objectName() == QLatin1String("cmbUnitTypes"))
0538     {
0539         m_systemUnitFilterModel->addFilterRegExp(unitType,  QStringLiteral("(%1)$").arg(unitTypeSufx.at(index)));
0540         m_systemUnitFilterModel->invalidate();
0541         ui.tblUnits->sortByColumn(ui.tblUnits->horizontalHeader()->sortIndicatorSection(),
0542                                   ui.tblUnits->horizontalHeader()->sortIndicatorOrder());
0543     }
0544     else if (QObject::sender()->objectName() == QLatin1String("cmbUserUnitTypes"))
0545     {
0546         m_userUnitFilterModel->addFilterRegExp(unitType, QStringLiteral("(%1)$").arg(unitTypeSufx.at(index)));
0547         m_userUnitFilterModel->invalidate();
0548         ui.tblUserUnits->sortByColumn(ui.tblUserUnits->horizontalHeader()->sortIndicatorSection(),
0549                                       ui.tblUserUnits->horizontalHeader()->sortIndicatorOrder());
0550     }
0551     updateUnitCount();
0552 }
0553 
0554 void MainWindow::slotRefreshUnitsList(bool initial, dbusBus bus)
0555 {
0556     // Updates the unit lists
0557 
0558     if (bus == sys) {
0559 
0560         qDebug() << "Refreshing system units...";
0561 
0562         // get an updated list of system units via dbus
0563         m_systemUnitsList.clear();
0564         m_systemUnitsList = getUnitsFromDbus(sys);
0565         m_noActSystemUnits = 0;
0566         foreach (const SystemdUnit &unit, m_systemUnitsList)
0567         {
0568             if (unit.active_state == QLatin1String("active"))
0569                 m_noActSystemUnits++;
0570         }
0571         if (!initial) {
0572             m_systemUnitModel->dataChanged(m_systemUnitModel->index(0, 0), m_systemUnitModel->index(m_systemUnitModel->rowCount(), 0));
0573             m_systemUnitFilterModel->invalidate();
0574             updateUnitCount();
0575             slotRefreshTimerList();
0576             updateActions();
0577         }
0578 
0579     } else if (enableUserUnits && bus == user) {
0580 
0581         qDebug() << "Refreshing user units...";
0582 
0583         // get an updated list of user units via dbus
0584         m_userUnitsList.clear();
0585         m_userUnitsList = getUnitsFromDbus(user);
0586         m_noActUserUnits = 0;
0587         foreach (const SystemdUnit &unit, m_userUnitsList)
0588         {
0589             if (unit.active_state == QLatin1String("active"))
0590                 m_noActUserUnits++;
0591         }
0592         if (!initial) {
0593             m_userUnitModel->dataChanged(m_userUnitModel->index(0, 0), m_userUnitModel->index(m_userUnitModel->rowCount(), 0));
0594             m_userUnitFilterModel->invalidate();
0595             updateUnitCount();
0596             slotRefreshTimerList();
0597             updateActions();
0598         }
0599     }
0600 }
0601 
0602 void MainWindow::slotRefreshSessionList()
0603 {
0604     // Updates the session list
0605     qDebug() << "Refreshing session list...";
0606 
0607     // clear list
0608     m_sessionList.clear();
0609 
0610     // get an updated list of sessions via dbus
0611     QDBusMessage dbusreply = callDbusMethod(QStringLiteral("ListSessions"), logdMgr);
0612 
0613     // extract the list of sessions from the reply
0614     const QDBusArgument arg = dbusreply.arguments().at(0).value<QDBusArgument>();
0615     if (arg.currentType() == QDBusArgument::ArrayType)
0616     {
0617         arg.beginArray();
0618         while (!arg.atEnd())
0619         {
0620             SystemdSession session;
0621             arg >> session;
0622             m_sessionList.append(session);
0623         }
0624         arg.endArray();
0625     }
0626 
0627     // Iterate through the new list and compare to model
0628     foreach (const SystemdSession &s, m_sessionList) {
0629         // This is needed to get the "State" property
0630 
0631         QList<QStandardItem *> items = m_sessionModel->findItems(s.session_id, Qt::MatchExactly, 0);
0632 
0633         if (items.isEmpty())
0634         {
0635             // New session discovered so add it to the model
0636             QList<QStandardItem *> row;
0637             row <<
0638                    new QStandardItem(s.session_id) <<
0639                    new QStandardItem(s.session_path.path()) <<
0640                    new QStandardItem(getDbusProperty(QStringLiteral("State"), logdSession, s.session_path).toString()) <<
0641                    new QStandardItem(QString::number(s.user_id)) <<
0642                    new QStandardItem(s.user_name) <<
0643                    new QStandardItem(s.seat_id);
0644             m_sessionModel->appendRow(row);
0645         } else {
0646             m_sessionModel->item(items.at(0)->row(), 2)->setData(getDbusProperty(QStringLiteral("State"), logdSession, s.session_path).toString(), Qt::DisplayRole);
0647         }
0648     }
0649 
0650     // Check to see if any sessions were removed
0651     if (m_sessionModel->rowCount() != m_sessionList.size())
0652     {
0653         QList<QPersistentModelIndex> indexes;
0654         // Loop through model and compare to retrieved m_sessionList
0655         for (int row = 0; row < m_sessionModel->rowCount(); ++row)
0656         {
0657             SystemdSession session;
0658             session.session_id = m_sessionModel->index(row,0).data().toString();
0659             if (!m_sessionList.contains(session))
0660             {
0661                 // Add removed units to list for deletion
0662                 // qDebug() << "Unit removed: " << systemUnitModel->index(row,0).data().toString();
0663                 indexes << m_sessionModel->index(row,0);
0664             }
0665         }
0666         // Delete the identified units from model
0667         foreach (const QPersistentModelIndex &i, indexes)
0668             m_sessionModel->removeRow(i.row());
0669     }
0670 
0671     // Update the text color in model
0672     QColor newcolor;
0673     for (int row = 0; row < m_sessionModel->rowCount(); ++row)
0674     {
0675         QBrush newcolor;
0676         const KColorScheme scheme(QPalette::Normal);
0677         if (m_sessionModel->data(m_sessionModel->index(row,2), Qt::DisplayRole) == QLatin1String("active"))
0678             newcolor = scheme.foreground(KColorScheme::PositiveText);
0679         else if (m_sessionModel->data(m_sessionModel->index(row,2), Qt::DisplayRole) == QLatin1String("closing"))
0680             newcolor = scheme.foreground(KColorScheme::InactiveText);
0681         else
0682             newcolor = scheme.foreground(KColorScheme::NormalText);
0683 
0684         for (int col = 0; col < m_sessionModel->columnCount(); ++col)
0685             m_sessionModel->setData(m_sessionModel->index(row,col), QVariant(newcolor), Qt::ForegroundRole);
0686     }
0687     ui.tblSessions->resizeColumnsToContents();
0688     ui.tblSessions->resizeRowsToContents();
0689 }
0690 
0691 void MainWindow::slotRefreshTimerList()
0692 {
0693     // Updates the timer list
0694     qDebug() << "Refreshing timer list...";
0695 
0696     m_timerModel->removeRows(0, m_timerModel->rowCount());
0697 
0698     // Iterate through system unitlist and add timers to the model
0699     foreach (const SystemdUnit &unit, m_systemUnitsList)
0700     {
0701         if (unit.id.endsWith(QLatin1String(".timer")) &&
0702                 unit.load_state != QLatin1String("unloaded")) {
0703             m_timerModel->appendRow(buildTimerListRow(unit, m_systemUnitsList, sys));
0704         }
0705     }
0706 
0707     // Iterate through user unitlist and add timers to the model
0708     foreach (const SystemdUnit &unit, m_userUnitsList)
0709     {
0710         if (unit.id.endsWith(QLatin1String(".timer")) &&
0711                 unit.load_state != QLatin1String("unloaded")) {
0712             m_timerModel->appendRow(buildTimerListRow(unit, m_userUnitsList, user));
0713         }
0714     }
0715 
0716     // Update the left and passed columns
0717     slotUpdateTimers();
0718 
0719     ui.tblTimers->resizeColumnsToContents();
0720     ui.tblTimers->resizeRowsToContents();
0721     ui.tblTimers->sortByColumn(ui.tblTimers->horizontalHeader()->sortIndicatorSection(),
0722                                ui.tblTimers->horizontalHeader()->sortIndicatorOrder());
0723 }
0724 
0725 QList<QStandardItem *> MainWindow::buildTimerListRow(const SystemdUnit &unit, const QVector<SystemdUnit> &list, dbusBus bus)
0726 {
0727     // Builds a row for the timers list
0728 
0729     QDBusObjectPath path = unit.unit_path;
0730     QString unitToActivate = getDbusProperty(QStringLiteral("Unit"), sysdTimer, path, bus).toString();
0731 
0732     QDateTime time;
0733     QIcon icon;
0734     if (bus == sys)
0735         icon = QIcon::fromTheme(QStringLiteral("applications-system"));
0736     else
0737         icon = QIcon::fromTheme(QStringLiteral("user-identity"));
0738 
0739     // Add the next elapsation point
0740     qlonglong nextElapseMonotonicMsec = getDbusProperty(QStringLiteral("NextElapseUSecMonotonic"), sysdTimer, path, bus).toULongLong() / 1000;
0741     qlonglong nextElapseRealtimeMsec = getDbusProperty(QStringLiteral("NextElapseUSecRealtime"), sysdTimer, path, bus).toULongLong() / 1000;
0742     qlonglong lastTriggerMSec = getDbusProperty(QStringLiteral("LastTriggerUSec"), sysdTimer, path, bus).toULongLong() / 1000;
0743 
0744     if (nextElapseMonotonicMsec == 0)
0745     {
0746         // Timer is calendar-based
0747         time.setMSecsSinceEpoch(nextElapseRealtimeMsec);
0748     }
0749     else
0750     {
0751         // Timer is monotonic
0752         time = QDateTime().currentDateTime();
0753         time = time.addMSecs(nextElapseMonotonicMsec);
0754 
0755         // Get the monotonic system clock
0756         struct timespec ts;
0757         if (clock_gettime( CLOCK_MONOTONIC, &ts ) != 0)
0758             qDebug() << "Failed to get the monotonic system clock!";
0759 
0760         // Convert the monotonic system clock to microseconds
0761         qlonglong now_mono_usec = ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
0762 
0763         // And subtract it.
0764         time = time.addMSecs(-now_mono_usec/1000);
0765     }
0766 
0767     QString next = time.toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"));
0768 
0769     QString last;
0770 
0771     // use unit object to get last time for activated service
0772     int index = list.indexOf(SystemdUnit(unitToActivate));
0773     if (index != -1)
0774     {
0775         qlonglong inactivateExitTimestampMsec =
0776                 getDbusProperty(QStringLiteral("InactiveExitTimestamp"), sysdUnit, list.at(index).unit_path, bus).toULongLong() / 1000;
0777 
0778         if (inactivateExitTimestampMsec == 0)
0779         {
0780             // The unit has not run in this boot
0781             // Use LastTrigger to see if the timer is persistent
0782             if (lastTriggerMSec == 0)
0783                 last = QStringLiteral("n/a");
0784             else
0785             {
0786                 time.setMSecsSinceEpoch(lastTriggerMSec);
0787                 last = time.toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"));
0788             }
0789         }
0790         else
0791         {
0792             QDateTime time;
0793             time.setMSecsSinceEpoch(inactivateExitTimestampMsec);
0794             last = time.toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"));
0795         }
0796     }
0797 
0798     // Set icon for id column
0799     QStandardItem *id = new QStandardItem(unit.id);
0800     id->setData(icon, Qt::DecorationRole);
0801 
0802     // Build a row from QStandardItems
0803     QList<QStandardItem *> row;
0804     row << id <<
0805            new QStandardItem(next) <<
0806            new QStandardItem() <<
0807            new QStandardItem(last) <<
0808            new QStandardItem() <<
0809            new QStandardItem(unitToActivate);
0810 
0811     return row;
0812 }
0813 
0814 void MainWindow::updateUnitCount()
0815 {
0816     QString systemUnits = i18ncp("First part of 'Total: %1, %2, %3'",
0817                                  "1 unit", "%1 units", m_systemUnitModel->rowCount());
0818     QString systemActive = i18ncp("Second part of 'Total: %1, %2, %3'",
0819                                   "1 active", "%1 active", m_noActSystemUnits);
0820     QString systemDisplayed = i18ncp("Third part of 'Total: %1, %2, %3'",
0821                                      "1 displayed", "%1 displayed", m_systemUnitFilterModel->rowCount());
0822     ui.lblUnitCount->setText(i18nc("%1 is '%1 units' and %2 is '%2 active' and %3 is '%3 displayed'",
0823                                    "Total: %1, %2, %3", systemUnits, systemActive, systemDisplayed));
0824 
0825     QString userUnits = i18ncp("First part of 'Total: %1, %2, %3'",
0826                                "1 unit", "%1 units", m_userUnitModel->rowCount());
0827     QString userActive = i18ncp("Second part of 'Total: %1, %2, %3'",
0828                                 "1 active", "%1 active", m_noActUserUnits);
0829     QString userDisplayed = i18ncp("Third part of 'Total: %1, %2, %3'",
0830                                    "1 displayed", "%1 displayed", m_userUnitFilterModel->rowCount());
0831     ui.lblUserUnitCount->setText(i18nc("%1 is '%1 units' and %2 is '%2 active' and %3 is '%3 displayed'",
0832                                        "Total: %1, %2, %3", userUnits, userActive, userDisplayed));
0833 }
0834 
0835 void MainWindow::authServiceAction(const QString &service, const QString &path, const QString &iface, const QString &method, const QList<QVariant> &args)
0836 {
0837     // Function to call the helper to authenticate a call to systemd over the system DBus
0838 
0839     // Declare a QVariantMap with arguments for the helper
0840     QVariantMap helperArgs;
0841     helperArgs[QStringLiteral("service")] = service;
0842     helperArgs[QStringLiteral("path")] = path;
0843     helperArgs[QStringLiteral("interface")] = iface;
0844     helperArgs[QStringLiteral("method")] = method;
0845     helperArgs[QStringLiteral("argsForCall")] = args;
0846 
0847     // Call the helper. This call causes the debug output: "QDBusArgument: read from a write-only object"
0848     KAuth::Action serviceAction(QStringLiteral("org.kde.kcontrol.systemdgenie.dbusaction"));
0849     serviceAction.setHelperId(QStringLiteral("org.kde.kcontrol.systemdgenie"));
0850     serviceAction.setArguments(helperArgs);
0851 
0852     KAuth::ExecuteJob *job = serviceAction.execute();
0853     job->exec();
0854 
0855     if (!job->exec())
0856         displayMsgWidget(KMessageWidget::Error,
0857                          i18n("Unable to authenticate/execute the action: %1", job->error()));
0858     else
0859     {
0860         qDebug() << "DBus action successful.";
0861         // KMessageBox::information(this, i18n("DBus action successful."));
0862     }
0863 }
0864 
0865 void MainWindow::slotConfFileContextMenu(const QPoint &pos)
0866 {
0867     Q_UNUSED(pos)
0868 
0869     // Slot for creating the right-click menu in unitlists
0870     if (!factory()) {
0871         return;
0872     }
0873 
0874     QMenu *popup = static_cast<QMenu *>(factory()->container(QStringLiteral("context_menu_conf"), this));
0875     popup->popup(QCursor::pos());
0876 }
0877 
0878 void MainWindow::slotUnitContextMenu(const QPoint &pos)
0879 {
0880     Q_UNUSED(pos)
0881 
0882     // Slot for creating the right-click menu in unitlists
0883     if (!factory()) {
0884         return;
0885     }
0886 
0887     QMenu *popup = static_cast<QMenu *>(factory()->container(QStringLiteral("context_menu_units"), this));
0888     popup->popup(QCursor::pos());
0889 }
0890 
0891 void MainWindow::updateActions()
0892 {
0893     //qDebug() << "Updating actions...";
0894 
0895     if ((ui.tabWidget->currentIndex() == 0 && !ui.tblUnits->selectionModel()->selectedRows(0).isEmpty()) ||
0896         (ui.tabWidget->currentIndex() == 1 && !ui.tblUserUnits->selectionModel()->selectedRows(0).isEmpty()))
0897     {
0898         QTableView *tblView;
0899         QVector<SystemdUnit> *list;
0900         dbusBus bus;
0901         if (ui.tabWidget->currentIndex() == 0) {
0902             list = &m_systemUnitsList;
0903             tblView = ui.tblUnits;
0904             bus = sys;
0905         } else if (ui.tabWidget->currentIndex() == 1) {
0906             list = &m_userUnitsList;
0907             tblView = ui.tblUserUnits;
0908             bus = user;
0909         } else {
0910             m_startUnitAction->setEnabled(false);
0911             m_stopUnitAction->setEnabled(false);
0912             m_restartUnitAction->setEnabled(false);
0913             m_reloadUnitAction->setEnabled(false);
0914             m_enableUnitAction->setEnabled(false);
0915             m_disableUnitAction->setEnabled(false);
0916             m_maskUnitAction->setEnabled(false);
0917             m_unmaskUnitAction->setEnabled(false);
0918             m_editUnitFileAction->setEnabled(false);
0919             return;
0920         }
0921 
0922 
0923         // Find name and object path of unit
0924         const QString unit = tblView->selectionModel()->selectedRows(0).at(0).data().toString();
0925         Q_ASSERT(!unit.isEmpty());
0926         Q_ASSERT(list->contains(SystemdUnit(unit)));
0927         const QDBusObjectPath pathUnit = list->at(list->indexOf(SystemdUnit(unit))).unit_path;
0928 
0929         // Check capabilities of unit
0930         bool CanStart = false;
0931         bool CanStop = false;
0932         bool CanReload = false;
0933         bool hasUnitObject = true;
0934         //bool CanIsolate = false;
0935         QString LoadState;
0936         QString ActiveState;
0937         QString unitFileState;
0938         if (!pathUnit.path().isEmpty() &&
0939             getDbusProperty(QStringLiteral("Test"), sysdUnit, pathUnit, bus).toString() != QLatin1String("invalidIface"))
0940         {
0941             // Unit has a Unit DBus object, fetch properties
0942             LoadState = getDbusProperty(QStringLiteral("LoadState"), sysdUnit, pathUnit, bus).toString();
0943             ActiveState = getDbusProperty(QStringLiteral("ActiveState"), sysdUnit, pathUnit, bus).toString();
0944             CanStart = getDbusProperty(QStringLiteral("CanStart"), sysdUnit, pathUnit, bus).toBool();
0945             CanStop = getDbusProperty(QStringLiteral("CanStop"), sysdUnit, pathUnit, bus).toBool();
0946             CanReload = getDbusProperty(QStringLiteral("CanReload"), sysdUnit, pathUnit, bus).toBool();
0947             //CanIsolate = getDbusProperty(QStringLiteral("CanIsolate"), sysdUnit, pathUnit, bus).toBool();
0948 
0949             unitFileState = getDbusProperty(QStringLiteral("UnitFileState"), sysdUnit, pathUnit, bus).toString();
0950         } else {
0951             hasUnitObject = false;
0952             // Get UnitFileState from Manager object.
0953             unitFileState = callDbusMethod(QStringLiteral("GetUnitFileState"), sysdMgr, bus, QVariantList{unit}).arguments().at(0).toString();
0954         }
0955 
0956         // Check if unit has a unit file, if not disable editing.
0957         QString frpath;
0958         int index = list->indexOf(SystemdUnit(unit));
0959         if (index != -1) {
0960             frpath = list->at(index).unit_file;
0961         }
0962 
0963         bool isUnitSelected = !unit.isEmpty();
0964 
0965         m_startUnitAction->setEnabled(isUnitSelected &&
0966                                       (CanStart || !hasUnitObject) &&
0967                                       ActiveState != QLatin1String("active"));
0968 
0969         m_stopUnitAction->setEnabled(isUnitSelected &&
0970                                      CanStop &&
0971                                      ActiveState != QLatin1String("inactive") &&
0972                                      ActiveState != QLatin1String("failed"));
0973 
0974         m_restartUnitAction->setEnabled(isUnitSelected &&
0975                                         CanStart &&
0976                                         ActiveState != QLatin1String("inactive") &&
0977                                         ActiveState != QLatin1String("failed") &&
0978                                         !LoadState.isEmpty());
0979 
0980         m_reloadUnitAction->setEnabled(isUnitSelected &&
0981                                        CanReload &&
0982                                        ActiveState != QLatin1String("inactive") &&
0983                                        ActiveState != QLatin1String("failed"));
0984 
0985         m_enableUnitAction->setEnabled(isUnitSelected &&
0986                                        unitFileState == QLatin1String("disabled"));
0987 
0988         m_disableUnitAction->setEnabled(isUnitSelected &&
0989                                         unitFileState == QLatin1String("enabled"));
0990 
0991         m_maskUnitAction->setEnabled(isUnitSelected &&
0992                                      LoadState != QLatin1String("masked"));
0993 
0994         m_unmaskUnitAction->setEnabled(isUnitSelected &&
0995                                        LoadState == QLatin1String("masked"));
0996 
0997         m_editUnitFileAction->setEnabled(isUnitSelected &&
0998                                          !frpath.isEmpty());
0999     } else {
1000         m_startUnitAction->setEnabled(false);
1001         m_stopUnitAction->setEnabled(false);
1002         m_restartUnitAction->setEnabled(false);
1003         m_reloadUnitAction->setEnabled(false);
1004         m_enableUnitAction->setEnabled(false);
1005         m_disableUnitAction->setEnabled(false);
1006         m_maskUnitAction->setEnabled(false);
1007         m_unmaskUnitAction->setEnabled(false);
1008         m_editUnitFileAction->setEnabled(false);
1009     }
1010 
1011     m_editConfFileAction->setEnabled(ui.tabWidget->currentIndex() == 2 &&
1012                                      !ui.tblConfFiles->selectionModel()->selectedRows(0).isEmpty());
1013     m_openManPageAction->setEnabled(ui.tabWidget->currentIndex() == 2 &&
1014                                    !ui.tblConfFiles->selectionModel()->selectedRows(0).isEmpty());
1015 
1016     m_activateSessionAction->setEnabled(ui.tabWidget->currentIndex() == 3 &&
1017                                         !ui.tblSessions->selectionModel()->selectedRows(0).isEmpty() &&
1018                                         ui.tblSessions->selectionModel()->selectedRows(2).at(0).data().toString() != QLatin1String("active"));
1019 
1020     m_terminateSessionAction->setEnabled(ui.tabWidget->currentIndex() == 3 &&
1021                                          !ui.tblSessions->selectionModel()->selectedRows(0).isEmpty());
1022 
1023     QDBusObjectPath pathSession;
1024     if (!ui.tblSessions->selectionModel()->selectedRows(0).isEmpty()) {
1025         pathSession = QDBusObjectPath(ui.tblSessions->selectionModel()->selectedRows(1).at(0).data().toString());
1026     }
1027     m_lockSessionAction->setEnabled(ui.tabWidget->currentIndex() == 3 &&
1028                                    !ui.tblSessions->selectionModel()->selectedRows(0).isEmpty() &&
1029                                    getDbusProperty(QStringLiteral("Type"), logdSession, pathSession).toString() != QLatin1String("tty"));
1030 }
1031 
1032 void MainWindow::setupActions()
1033 {
1034     KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
1035 
1036     m_refreshAction = new QAction(this);
1037     m_refreshAction->setText(i18n("&Refresh"));
1038     m_refreshAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
1039     m_refreshAction->setToolTip(i18nc("@info:tooltip", "Refresh"));
1040     actionCollection()->setDefaultShortcut(m_refreshAction, Qt::Key_F5);
1041     actionCollection()->addAction(QStringLiteral("refresh"), m_refreshAction);
1042     connect(m_refreshAction, &QAction::triggered, this, &MainWindow::slotRefreshAll);
1043 
1044     m_startUnitAction = new QAction(this);
1045     m_startUnitAction->setText(i18n("Start Unit"));
1046     m_startUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("kt-start")));
1047     actionCollection()->addAction(QStringLiteral("start-unit"), m_startUnitAction);
1048     connect(m_startUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("StartUnit")); });
1049 
1050     m_stopUnitAction = new QAction(this);
1051     m_stopUnitAction->setText(i18n("Stop Unit"));
1052     m_stopUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("kt-stop")));
1053     actionCollection()->addAction(QStringLiteral("stop-unit"), m_stopUnitAction);
1054     connect(m_stopUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("StopUnit")); });
1055 
1056     m_reloadUnitAction = new QAction(this);
1057     m_reloadUnitAction->setText(i18n("Reload Unit"));
1058     m_reloadUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
1059     actionCollection()->addAction(QStringLiteral("reload-unit"), m_reloadUnitAction);
1060     connect(m_reloadUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("ReloadUnit")); });
1061 
1062     m_restartUnitAction = new QAction(this);
1063     m_restartUnitAction->setText(i18n("Restart Unit"));
1064     m_restartUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("start-over")));
1065     actionCollection()->addAction(QStringLiteral("restart-unit"), m_restartUnitAction);
1066     connect(m_restartUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("RestartUnit")); });
1067 
1068     m_enableUnitAction = new QAction(this);
1069     m_enableUnitAction->setText(i18n("Enable Unit"));
1070     m_enableUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("archive-insert")));
1071     actionCollection()->addAction(QStringLiteral("enable-unit"), m_enableUnitAction);
1072     connect(m_enableUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("EnableUnitFiles")); });
1073 
1074     m_disableUnitAction = new QAction(this);
1075     m_disableUnitAction->setText(i18n("Disable Unit"));
1076     m_disableUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
1077     actionCollection()->addAction(QStringLiteral("disable-unit"), m_disableUnitAction);
1078     connect(m_disableUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("DisableUnitFiles")); });
1079 
1080     m_maskUnitAction = new QAction(this);
1081     m_maskUnitAction->setText(i18n("Mask Unit"));
1082     m_maskUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("password-show-off")));
1083     actionCollection()->addAction(QStringLiteral("mask-unit"), m_maskUnitAction);
1084     connect(m_maskUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("MaskUnitFiles")); });
1085 
1086     m_unmaskUnitAction = new QAction(this);
1087     m_unmaskUnitAction->setText(i18n("Unmask Unit"));
1088     m_unmaskUnitAction->setIcon(QIcon::fromTheme(QStringLiteral("password-show-on")));
1089     actionCollection()->addAction(QStringLiteral("unmask-unit"), m_unmaskUnitAction);
1090     connect(m_unmaskUnitAction, &QAction::triggered, this, [this]() { executeUnitAction(QStringLiteral("UnmaskUnitFiles")); });
1091 
1092     m_reloadSystemDaemonAction = new QAction(this);
1093     m_reloadSystemDaemonAction->setText(i18n("Re&load systemd"));
1094     m_reloadSystemDaemonAction->setToolTip(i18nc("@info:tooltip", "Click to reload all unit files"));
1095     m_reloadSystemDaemonAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts")));
1096     actionCollection()->addAction(QStringLiteral("reload-daemon-system"), m_reloadSystemDaemonAction);
1097     connect(m_reloadSystemDaemonAction, &QAction::triggered, this, [this]() { executeSystemDaemonAction(QStringLiteral("Reload")); });
1098 
1099     m_reexecSystemDaemonAction = new QAction(this);
1100     m_reexecSystemDaemonAction->setText(i18n("Re-e&xecute systemd"));
1101     m_reexecSystemDaemonAction->setToolTip(i18nc("@info:tooltip", "Click to re-execute the systemd daemon"));
1102     m_reexecSystemDaemonAction->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts")));
1103     actionCollection()->addAction(QStringLiteral("reexec-daemon-system"), m_reexecSystemDaemonAction);
1104     connect(m_reexecSystemDaemonAction, &QAction::triggered, this, [this]() { executeSystemDaemonAction(QStringLiteral("Reexecute")); });
1105 
1106     m_reloadUserDaemonAction = new QAction(this);
1107     m_reloadUserDaemonAction->setText(i18n("Re&load user systemd"));
1108     m_reloadUserDaemonAction->setToolTip(i18nc("@info:tooltip", "Click to reload all user unit files"));
1109     m_reloadUserDaemonAction->setIcon(QIcon::fromTheme(QStringLiteral("user")));
1110     actionCollection()->addAction(QStringLiteral("reload-daemon-user"), m_reloadUserDaemonAction);
1111     connect(m_reloadUserDaemonAction, &QAction::triggered, this, [this]() { executeUserDaemonAction(QStringLiteral("Reload")); });
1112 
1113     m_reexecUserDaemonAction = new QAction(this);
1114     m_reexecUserDaemonAction->setText(i18n("Re-e&xecute user systemd"));
1115     m_reexecUserDaemonAction->setToolTip(i18nc("@info:tooltip", "Click to re-execute the user systemd daemon"));
1116     m_reexecUserDaemonAction->setIcon(QIcon::fromTheme(QStringLiteral("user")));
1117     actionCollection()->addAction(QStringLiteral("reexec-daemon-user"), m_reexecUserDaemonAction);
1118     connect(m_reexecUserDaemonAction, &QAction::triggered, this, [this]() { executeUserDaemonAction(QStringLiteral("Reexecute")); });
1119 
1120     m_editUnitFileAction = new QAction(this);
1121     m_editUnitFileAction->setText(i18n("Edit Unit File"));
1122     m_editUnitFileAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
1123     actionCollection()->addAction(QStringLiteral("edit-unitfile"), m_editUnitFileAction);
1124     connect(m_editUnitFileAction, &QAction::triggered, this, &MainWindow::slotEditUnitFile);
1125 
1126     m_editConfFileAction = new QAction(this);
1127     m_editConfFileAction->setText(i18n("Edit Configuration File"));
1128     m_editConfFileAction->setIcon(QIcon::fromTheme(QStringLiteral("editor")));
1129     actionCollection()->addAction(QStringLiteral("edit-conffile"), m_editConfFileAction);
1130     connect(m_editConfFileAction, &QAction::triggered, this, &MainWindow::slotEditConfFile);
1131 
1132     m_openManPageAction = new QAction(this);
1133     m_openManPageAction->setText(i18n("Open Man Page"));
1134     m_openManPageAction->setIcon(QIcon::fromTheme(QStringLiteral("help-contents")));
1135     actionCollection()->addAction(QStringLiteral("open-manpage"), m_openManPageAction);
1136     connect(m_openManPageAction, &QAction::triggered, this, &MainWindow::slotOpenManPage);
1137 
1138     m_activateSessionAction = new QAction(this);
1139     m_activateSessionAction->setText(i18n("Activate Session"));
1140     m_activateSessionAction->setIcon(QIcon::fromTheme(QStringLiteral("kt-start")));
1141     actionCollection()->addAction(QStringLiteral("activate-session"), m_activateSessionAction);
1142     connect(m_activateSessionAction, &QAction::triggered, this, [this]() { executeSessionAction(QStringLiteral("Activate")); });
1143 
1144     m_terminateSessionAction = new QAction(this);
1145     m_terminateSessionAction->setText(i18n("Terminate Session"));
1146     m_terminateSessionAction->setIcon(QIcon::fromTheme(QStringLiteral("kt-remove")));
1147     actionCollection()->addAction(QStringLiteral("terminate-session"), m_terminateSessionAction);
1148     connect(m_terminateSessionAction, &QAction::triggered, this, [this]() { executeSessionAction(QStringLiteral("Terminate")); });
1149 
1150     m_lockSessionAction = new QAction(this);
1151     m_lockSessionAction->setText(i18n("Lock Session"));
1152     m_lockSessionAction->setIcon(QIcon::fromTheme(QStringLiteral("lock")));
1153     actionCollection()->addAction(QStringLiteral("lock-session"), m_lockSessionAction);
1154     connect(m_lockSessionAction, &QAction::triggered, this, [this]() { executeSessionAction(QStringLiteral("Lock")); });
1155 
1156     updateActions();
1157 }
1158 
1159 void MainWindow::slotOpenManPage()
1160 {
1161     if (ui.tblConfFiles->selectionModel()->selectedRows(0).isEmpty()) {
1162         return;
1163     }
1164 
1165     QModelIndex index = ui.tblConfFiles->selectionModel()->selectedRows(0).at(0);
1166     Q_ASSERT(index.isValid());
1167 
1168     const QString manPage = m_confFileList.at(m_confFileList.indexOf(conffile(index.data().toString()))).manPage;
1169 
1170     const QString KHelpCenterExec = QStandardPaths::findExecutable(QStringLiteral("khelpcenter"));
1171     if (KHelpCenterExec.isEmpty()) {
1172         KMessageBox::error(this, i18n("KHelpCenter executable not found in path. Please install KHelpCenter to view man pages."));
1173         return;
1174     }
1175 
1176     QProcess *helpcenter = new QProcess(this);
1177     helpcenter->start(KHelpCenterExec, QStringList{QStringLiteral("man:/%1").arg(manPage)});
1178 }
1179 
1180 void MainWindow::executeUnitAction(const QString &method)
1181 {
1182     QTableView *tblView;
1183     if (ui.tabWidget->currentIndex() == 0) {
1184          tblView = ui.tblUnits;
1185     } else {
1186         tblView = ui.tblUserUnits;
1187     }
1188 
1189     if (tblView->selectionModel()->selectedRows(0).isEmpty()) {
1190         return;
1191     }
1192 
1193     QModelIndex index = tblView->selectionModel()->selectedRows(0).at(0);
1194     Q_ASSERT(index.isValid());
1195 
1196     QString unit = index.data().toString();
1197     //qDebug() << "unit:" << unit;
1198 
1199     QVariantList args;
1200     if (method == QLatin1String("EnableUnitFiles") ||
1201         method == QLatin1String("MaskUnitFiles")) {
1202         args << QVariant(QStringList{unit}) << false << true;
1203     } else if (method == QLatin1String("DisableUnitFiles") ||
1204                method == QLatin1String("UnmaskUnitFiles")) {
1205         args << QVariant(QStringList{unit}) << false;
1206     } else {
1207         args = QVariantList{unit, QStringLiteral("replace")};
1208     }
1209 
1210     if (ui.tabWidget->currentIndex() == 0) {
1211         authServiceAction(connSystemd, pathSysdMgr, ifaceMgr, method, args);
1212     } else if (ui.tabWidget->currentIndex() == 1) {
1213         // user unit
1214         callDbusMethod(method, sysdMgr, user, args);
1215     }
1216 }
1217 
1218 void MainWindow::executeSessionAction(const QString &method)
1219 {
1220     if (ui.tblSessions->selectionModel()->selectedRows(0).isEmpty()) {
1221         return;
1222     }
1223 
1224     QModelIndex index = ui.tblSessions->selectionModel()->selectedRows(0).at(0);
1225     Q_ASSERT(index.isValid());
1226 
1227     QDBusObjectPath pathSession = QDBusObjectPath(ui.tblSessions->selectionModel()->selectedRows(1).at(0).data().toString());
1228 
1229     authServiceAction(connLogind, pathSession.path(), ifaceSession, method, QVariantList());
1230 }
1231 
1232 void MainWindow::executeSystemDaemonAction(const QString &method)
1233 {
1234     // Execute the DBus actions
1235     if (!method.isEmpty()) {
1236         authServiceAction(connSystemd, pathSysdMgr, ifaceMgr, method, QVariantList());
1237     }
1238 }
1239 
1240 void MainWindow::executeUserDaemonAction(const QString &method)
1241 {
1242     if (!method.isEmpty())
1243     {
1244         callDbusMethod(method, sysdMgr, user, QVariantList());
1245         if (QRegularExpression(QStringLiteral("EnableUnitFiles|DisableUnitFiles|MaskUnitFiles|UnmaskUnitFiles")).match(method).hasMatch()) {
1246             callDbusMethod(QStringLiteral("Reload"), sysdMgr, user);
1247         }
1248     }
1249 }
1250 
1251 void MainWindow::slotRefreshAll()
1252 {
1253     slotRefreshUnitsList(false, sys);
1254     slotRefreshUnitsList(false, user);
1255     slotRefreshConfFileList();
1256     slotRefreshSessionList();
1257 }
1258 
1259 void MainWindow::slotSessionContextMenu(const QPoint &pos)
1260 {
1261     // Slot for creating the right-click menu in the session list
1262 
1263     Q_UNUSED(pos)
1264 
1265     if (!factory()) {
1266         return;
1267     }
1268 
1269     QMenu *popup = static_cast<QMenu *>(factory()->container(QStringLiteral("context_menu_session"), this));
1270     popup->popup(QCursor::pos());
1271 }
1272 
1273 
1274 bool MainWindow::eventFilter(QObject *obj, QEvent* event)
1275 {
1276     // Eventfilter for catching mouse move events over session list
1277     // Used for dynamically generating tooltips
1278 
1279     if (event->type() == QEvent::MouseMove && obj->parent()->objectName() == QLatin1String("tblSessions"))
1280     {
1281         // Session list
1282         QMouseEvent *me = static_cast<QMouseEvent*>(event);
1283         QModelIndex inSessionModel = ui.tblSessions->indexAt(me->pos());
1284         if (!inSessionModel.isValid())
1285             return false;
1286 
1287         if (m_sessionModel->itemFromIndex(inSessionModel)->row() != lastSessionRowChecked)
1288         {
1289             // Cursor moved to a different row. Only build tooltips when moving
1290             // cursor to a new row to avoid excessive DBus calls.
1291 
1292             QString selSession = ui.tblSessions->model()->index(ui.tblSessions->indexAt(me->pos()).row(),0).data().toString();
1293             QDBusObjectPath spath = QDBusObjectPath(ui.tblSessions->model()->index(ui.tblSessions->indexAt(me->pos()).row(),1).data().toString());
1294 
1295             QString toolTipText;
1296             toolTipText.append(QStringLiteral("<FONT COLOR=white>"));
1297             toolTipText.append(QStringLiteral("<b>%1</b><hr>").arg(selSession));
1298 
1299             // Use the DBus interface to get session properties
1300             if (getDbusProperty(QStringLiteral("test"), logdSession, spath) != QLatin1String("invalidIface"))
1301             {
1302                 // Session has a valid session DBus object
1303                 toolTipText.append(i18n("<b>VT:</b> %1", getDbusProperty(QStringLiteral("VTNr"), logdSession, spath).toString()));
1304 
1305                 QString remoteHost = getDbusProperty(QStringLiteral("RemoteHost"), logdSession, spath).toString();
1306                 if (getDbusProperty(QStringLiteral("Remote"), logdSession, spath).toBool())
1307                 {
1308                     toolTipText.append(i18n("<br><b>Remote host:</b> %1", remoteHost));
1309                     toolTipText.append(i18n("<br><b>Remote user:</b> %1", getDbusProperty(QStringLiteral("RemoteUser"), logdSession, spath).toString()));
1310                 }
1311                 toolTipText.append(i18n("<br><b>Service:</b> %1", getDbusProperty(QStringLiteral("Service"), logdSession, spath).toString()));
1312                 toolTipText.append(i18n("<br><b>Leader (PID):</b> %1", getDbusProperty(QStringLiteral("Leader"), logdSession, spath).toString()));
1313 
1314                 QString type = getDbusProperty(QStringLiteral("Type"), logdSession, spath).toString();
1315                 toolTipText.append(i18n("<br><b>Type:</b> %1", type));
1316                 if (type == QLatin1String("x11"))
1317                     toolTipText.append(i18n(" (display %1)", getDbusProperty(QStringLiteral("Display"), logdSession, spath).toString()));
1318                 else if (type == QLatin1String("tty"))
1319                 {
1320                     QString path, tty = getDbusProperty(QStringLiteral("TTY"), logdSession, spath).toString();
1321                     if (!tty.isEmpty())
1322                         path = tty;
1323                     else if (!remoteHost.isEmpty())
1324                         path = QStringLiteral("%1@%2").arg(getDbusProperty(QStringLiteral("Name"), logdSession, spath).toString()).arg(remoteHost);
1325                     toolTipText.append(QStringLiteral(" (%1)").arg(path));
1326                 }
1327                 toolTipText.append(i18n("<br><b>Class:</b> %1", getDbusProperty(QStringLiteral("Class"), logdSession, spath).toString()));
1328                 toolTipText.append(i18n("<br><b>State:</b> %1", getDbusProperty(QStringLiteral("State"), logdSession, spath).toString()));
1329                 toolTipText.append(i18n("<br><b>Scope:</b> %1", getDbusProperty(QStringLiteral("Scope"), logdSession, spath).toString()));
1330 
1331 
1332                 toolTipText.append(i18n("<br><b>Created: </b>"));
1333                 if (getDbusProperty(QStringLiteral("Timestamp"), logdSession, spath).toULongLong() == 0)
1334                     toolTipText.append(QStringLiteral("n/a"));
1335                 else
1336                 {
1337                     QDateTime time;
1338                     time.setMSecsSinceEpoch(getDbusProperty(QStringLiteral("Timestamp"), logdSession, spath).toULongLong()/1000);
1339                     toolTipText.append(time.toString());
1340                 }
1341             }
1342 
1343             toolTipText.append(QStringLiteral("</FONT"));
1344             m_sessionModel->itemFromIndex(inSessionModel)->setToolTip(toolTipText);
1345 
1346             lastSessionRowChecked = m_sessionModel->itemFromIndex(inSessionModel)->row();
1347             return true;
1348 
1349         } // Row was different
1350     }
1351     return false;
1352     // return true;
1353 }
1354 
1355 void MainWindow::slotSystemSystemdReloading(bool status)
1356 {
1357     if (status) {
1358         qDebug() << "System daemon reloading...";
1359     } else {
1360         qDebug() << "System daemon reloaded";
1361         m_lblLog->setText(i18n("%1: Systemd daemon reloaded...", QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss"))));
1362         slotRefreshUnitsList(false, sys);
1363     }
1364 }
1365 
1366 void MainWindow::slotUserSystemdReloading(bool status)
1367 {
1368     if (status) {
1369         qDebug() << "User daemon reloading...";
1370     } else {
1371         qDebug() << "User daemon reloaded";
1372         m_lblLog->setText(i18n("%1: User daemon reloaded...", QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss"))));
1373         slotRefreshUnitsList(false, user);
1374     }
1375 }
1376 
1377 /*
1378 void MainWindow::slotSystemUnitNew(const QString &id, const QDBusObjectPath &path)
1379 {
1380     //qDebug() << "SystemUnitNew: " << id << path.path();
1381 }
1382 
1383 void MainWindow::slotSystemUnitRemoved(const QString &id, const QDBusObjectPath &path)
1384 {
1385     //qDebug() << "SystemUnitRemoved: " << id << path.path();
1386 }
1387 */
1388 
1389 void MainWindow::slotSystemJobNew(uint id, const QDBusObjectPath &path, const QString &unit)
1390 {
1391     qDebug() << "SystemJobNew: " << id << path.path() << unit;
1392 }
1393 
1394 void MainWindow::slotSystemJobRemoved(uint id, const QDBusObjectPath &path, const QString &unit, const QString &result)
1395 {
1396     qDebug() << "SystmJobRemoved: " << id << path.path() << unit << result;
1397     slotRefreshUnitsList(false, sys);
1398 }
1399 
1400 void MainWindow::slotUserJobNew(uint id, const QDBusObjectPath &path, const QString &unit)
1401 {
1402     qDebug() << "UserJobNew: " << id << path.path() << unit;
1403 }
1404 
1405 void MainWindow::slotUserJobRemoved(uint id, const QDBusObjectPath &path, const QString &unit, const QString &result)
1406 {
1407     qDebug() << "UserJobRemoved: " << id << path.path() << unit << result;
1408     slotRefreshUnitsList(false, user);
1409 }
1410 
1411 void MainWindow::slotSystemUnitFilesChanged()
1412 {
1413     //qDebug() << "System unitFilesChanged";
1414     slotRefreshUnitsList(false, sys);
1415     m_lblLog->setText(i18n("%1: System units changed...", QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss"))));
1416 }
1417 
1418 void MainWindow::slotUserUnitFilesChanged()
1419 {
1420     //qDebug() << "User unitFilesChanged";
1421     slotRefreshUnitsList(false, user);
1422 }
1423 
1424 void MainWindow::slotSystemPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
1425 {
1426     Q_UNUSED(changedProps)
1427     Q_UNUSED(invalidatedProps)
1428 
1429     qDebug() << "systemPropertiesChanged:" << iface; // << changedProps << invalidatedProps;
1430     slotRefreshUnitsList(false, sys);
1431 }
1432 
1433 void MainWindow::slotUserPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
1434 {
1435     Q_UNUSED(changedProps)
1436     Q_UNUSED(invalidatedProps)
1437 
1438     qDebug() << "userPropertiesChanged:" << iface; // << changedProps << invalidatedProps;
1439     slotRefreshUnitsList(false, user);
1440 }
1441 
1442 void MainWindow::slotLogindPropertiesChanged(const QString &iface, const QVariantMap &changedProps, const QStringList &invalidatedProps)
1443 {
1444     Q_UNUSED(iface)
1445     Q_UNUSED(changedProps)
1446     Q_UNUSED(invalidatedProps)
1447 
1448     // qDebug() << "Logind properties changed on iface " << iface_name;
1449     slotRefreshSessionList();
1450 }
1451 
1452 void MainWindow::slotLeSearchUnitChanged(QString term)
1453 {
1454     if (QObject::sender()->objectName() == QLatin1String("leSearchUnit"))
1455     {
1456         m_systemUnitFilterModel->addFilterRegExp(unitName, term);
1457         m_systemUnitFilterModel->invalidate();
1458         ui.tblUnits->sortByColumn(ui.tblUnits->horizontalHeader()->sortIndicatorSection(),
1459                                   ui.tblUnits->horizontalHeader()->sortIndicatorOrder());
1460     }
1461     else if (QObject::sender()->objectName() == QLatin1String("leSearchUserUnit"))
1462     {
1463         m_userUnitFilterModel->addFilterRegExp(unitName, term);
1464         m_userUnitFilterModel->invalidate();
1465         ui.tblUserUnits->sortByColumn(ui.tblUserUnits->horizontalHeader()->sortIndicatorSection(),
1466                                       ui.tblUserUnits->horizontalHeader()->sortIndicatorOrder());
1467     }
1468     updateUnitCount();
1469 }
1470 
1471 void MainWindow::slotUpdateTimers()
1472 {
1473     // Updates the left and passed columns in the timers list
1474     for (int row = 0; row < m_timerModel->rowCount(); ++row)
1475     {
1476         QDateTime next = m_timerModel->index(row, 1).data().toDateTime();
1477         QDateTime last = m_timerModel->index(row, 3).data().toDateTime();
1478         QDateTime current = QDateTime().currentDateTime();
1479         qlonglong leftSecs = current.secsTo(next);
1480         qlonglong passedSecs = last.secsTo(current);
1481 
1482         QString left;
1483         if (leftSecs >= 31536000)
1484             left = QStringLiteral("%1 years").arg(leftSecs / 31536000);
1485         else if (leftSecs >= 604800)
1486             left = QStringLiteral("%1 weeks").arg(leftSecs / 604800);
1487         else if (leftSecs >= 86400)
1488             left = QStringLiteral("%1 days").arg(leftSecs / 86400);
1489         else if (leftSecs >= 3600)
1490             left = QStringLiteral("%1 hr").arg(leftSecs / 3600);
1491         else if (leftSecs >= 60)
1492             left = QStringLiteral("%1 min").arg(leftSecs / 60);
1493         else if (leftSecs < 0)
1494             left = QStringLiteral("0 s");
1495         else
1496             left = QStringLiteral("%1 s").arg(leftSecs);
1497         m_timerModel->setData(m_timerModel->index(row, 2), left);
1498 
1499         QString passed;
1500         if (m_timerModel->index(row, 3).data() == QStringLiteral("n/a"))
1501             passed = QStringLiteral("n/a");
1502         else if (passedSecs >= 31536000)
1503             passed = QStringLiteral("%1 years").arg(passedSecs / 31536000);
1504         else if (passedSecs >= 604800)
1505             passed = QStringLiteral("%1 weeks").arg(passedSecs / 604800);
1506         else if (passedSecs >= 86400)
1507             passed = QStringLiteral("%1 days").arg(passedSecs / 86400);
1508         else if (passedSecs >= 3600)
1509             passed = QStringLiteral("%1 hr").arg(passedSecs / 3600);
1510         else if (passedSecs >= 60)
1511             passed = QStringLiteral("%1 min").arg(passedSecs / 60);
1512         else if (passedSecs < 0)
1513             passed = QStringLiteral("0 s");
1514         else
1515             passed = QStringLiteral("%1 s").arg(passedSecs);
1516         m_timerModel->setData(m_timerModel->index(row, 4), passed);
1517     }
1518 }
1519 
1520 void MainWindow::slotEditUnitFile()
1521 {
1522     QTableView *tblView;
1523     QVector<SystemdUnit> *list;
1524     if (ui.tabWidget->currentIndex() == 0) {
1525          tblView = ui.tblUnits;
1526          list = &m_systemUnitsList;
1527     } else {
1528         tblView = ui.tblUserUnits;
1529         list = &m_userUnitsList;
1530     }
1531 
1532     if (tblView->selectionModel()->selectedRows(0).isEmpty()) {
1533         return;
1534     }
1535 
1536     QModelIndex index = tblView->selectionModel()->selectedRows(0).at(0);
1537     Q_ASSERT(index.isValid());
1538     QString file = list->at(list->indexOf(SystemdUnit(index.data().toString()))).unit_file;
1539 
1540     openEditor(file);
1541 }
1542 
1543 void MainWindow::slotEditConfFile()
1544 {
1545     if (ui.tblConfFiles->selectionModel()->selectedRows(0).isEmpty()) {
1546         return;
1547     }
1548 
1549     QModelIndex index = ui.tblConfFiles->selectionModel()->selectedRows(0).at(0);
1550     Q_ASSERT(index.isValid());
1551 
1552     QString file = index.data().toString();
1553 
1554     openEditor(file);
1555 }
1556 
1557 void MainWindow::openEditor(const QString &file)
1558 {
1559     // Using a QPointer is safer for modal dialogs.
1560     // See: https://blogs.kde.org/node/3919
1561     QPointer<QDialog> dlgEditor = new QDialog(this);
1562     dlgEditor->setWindowTitle(i18n("Editing %1", file.section(QLatin1Char('/'), -1)));
1563 
1564     QPlainTextEdit *textEdit = new QPlainTextEdit(dlgEditor);
1565     textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
1566 
1567     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Save |
1568                                                        QDialogButtonBox::Cancel,
1569                                                        dlgEditor);
1570     connect(buttonBox, SIGNAL(accepted()), dlgEditor, SLOT(accept()));
1571     connect(buttonBox, SIGNAL(rejected()), dlgEditor, SLOT(reject()));
1572     buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
1573 
1574     QLabel *lblFilepath = new QLabel(i18n("Editing file: %1", file));
1575     QVBoxLayout *vlayout = new QVBoxLayout(dlgEditor);
1576     vlayout->addWidget(lblFilepath);
1577     vlayout->addWidget(textEdit);
1578     vlayout->addWidget(buttonBox);
1579 
1580     // Read contents of unit file.
1581     QFile f(file);
1582     if (!f.open(QFile::ReadOnly | QFile::Text)) {
1583         displayMsgWidget(KMessageWidget::Error,
1584                          i18n("Failed to open the unit file:\n%1", file));
1585         return;
1586     }
1587     QTextStream in(&f);
1588     textEdit->setPlainText(in.readAll());
1589     textEdit->setMinimumSize(500,300);
1590 
1591     connect(textEdit, &QPlainTextEdit::textChanged, [=]{
1592         buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
1593     });
1594 
1595     if (dlgEditor->exec() == QDialog::Accepted) {
1596 
1597         // Declare a QVariantMap with arguments for the helper.
1598         QVariantMap helperArgs;
1599         helperArgs[QStringLiteral("file")] = file;
1600         helperArgs[QStringLiteral("contents")] = textEdit->toPlainText();
1601 
1602         // Create the action.
1603         KAuth::Action action(QStringLiteral("org.kde.kcontrol.systemdgenie.saveunitfile"));
1604         action.setHelperId(QStringLiteral("org.kde.kcontrol.systemdgenie"));
1605         action.setArguments(helperArgs);
1606 
1607         KAuth::ExecuteJob *job = action.execute();
1608         if (!job->exec()) {
1609             displayMsgWidget(KMessageWidget::Error,
1610                              i18n("Unable to authenticate/execute the action: %1", job->error()));
1611         } else {
1612             //displayMsgWidget(KMessageWidget::Positive,
1613             //                 i18n("Unit file successfully written."));
1614             m_lblLog->setText(i18n("%1: %2 successfully written...", QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss")), file.section(QLatin1Char('/'), -1)));
1615         }
1616     }
1617     delete dlgEditor;
1618 }
1619 
1620 QVector<SystemdUnit> MainWindow::getUnitsFromDbus(dbusBus bus)
1621 {
1622     // get an updated list of units via dbus
1623 
1624     QVector<SystemdUnit> list;
1625     QVector<unitfile> unitfileslist;
1626     QDBusMessage dbusreply;
1627 
1628     dbusreply = callDbusMethod(QStringLiteral("ListUnits"), sysdMgr, bus);
1629 
1630     if (dbusreply.type() == QDBusMessage::ReplyMessage)
1631     {
1632 
1633         const QDBusArgument argUnits = dbusreply.arguments().at(0).value<QDBusArgument>();
1634         if (argUnits.currentType() == QDBusArgument::ArrayType)
1635         {
1636             argUnits.beginArray();
1637             while (!argUnits.atEnd())
1638             {
1639                 SystemdUnit unit;
1640                 argUnits >> unit;
1641                 if (unit.following.isEmpty()) {
1642                     list.append(unit);
1643                 }
1644             }
1645             argUnits.endArray();
1646         }
1647         //qDebug() << "Added " << list.size() << " units on bus " << bus;
1648 
1649         // Get a list of unit files
1650         dbusreply = callDbusMethod(QStringLiteral("ListUnitFiles"), sysdMgr, bus);
1651         const QDBusArgument argUnitFiles = dbusreply.arguments().at(0).value<QDBusArgument>();
1652         argUnitFiles.beginArray();
1653         while (!argUnitFiles.atEnd())
1654         {
1655             unitfile u;
1656             argUnitFiles.beginStructure();
1657             argUnitFiles >> u.name >> u.status;
1658             argUnitFiles.endStructure();
1659             unitfileslist.append(u);
1660         }
1661         argUnitFiles.endArray();
1662 
1663         // Add unloaded units to the list
1664         foreach (const unitfile &f, unitfileslist)
1665         {
1666             int index = list.indexOf(SystemdUnit(f.name.section(QLatin1Char('/'), -1)));
1667             if (index > -1)
1668             {
1669                 // The unit was already in the list, add unit file and its status
1670                 list[index].unit_file = f.name;
1671                 list[index].unit_file_status = f.status;
1672             }
1673             else
1674             {
1675                 // Unit not in the list, add it.
1676                 QFile unitfile(f.name);
1677                 if (unitfile.symLinkTarget().isEmpty())
1678                 {
1679                     SystemdUnit unit;
1680                     unit.id = f.name.section(QLatin1Char('/'), -1);
1681                     unit.load_state = QStringLiteral("unloaded");
1682                     unit.active_state = QLatin1Char('-');
1683                     unit.sub_state = QLatin1Char('-');
1684                     unit.unit_file = f.name;
1685                     unit.unit_file_status= f.status;
1686                     list.append(unit);
1687                 }
1688             }
1689         }
1690         // qDebug() << "Added " << tal << " units from files on bus " << bus;
1691 
1692     }
1693 
1694     return list;
1695 }
1696 
1697 QVariant MainWindow::getDbusProperty(QString prop, dbusIface ifaceName, QDBusObjectPath path, dbusBus bus)
1698 {
1699     // qDebug() << "Fetching property" << prop << ifaceName << path.path() << "on bus" << bus;
1700     QString conn, ifc;
1701     QDBusConnection abus(m_systemBus);
1702     if (bus == user)
1703         abus = QDBusConnection::connectToBus(m_userBusPath, connSystemd);
1704 
1705     if (ifaceName == sysdMgr)
1706     {
1707         conn = connSystemd;
1708         ifc = ifaceMgr;
1709     }
1710     else if (ifaceName == sysdUnit)
1711     {
1712         conn = connSystemd;
1713         ifc = ifaceUnit;
1714     }
1715     else if (ifaceName == sysdTimer)
1716     {
1717         conn = connSystemd;
1718         ifc = ifaceTimer;
1719     }
1720     else if (ifaceName == logdSession)
1721     {
1722         conn = connLogind;
1723         ifc = ifaceSession;
1724     }
1725     QVariant r;
1726     QDBusInterface *iface = new QDBusInterface (conn, path.path(), ifc, abus, this);
1727     if (iface->isValid())
1728     {
1729         r = iface->property(prop.toLatin1());
1730         delete iface;
1731         return r;
1732     }
1733     qDebug() << "Interface" << ifc << "invalid for" << path.path();
1734     return QVariant(QStringLiteral("invalidIface"));
1735 }
1736 
1737 QDBusMessage MainWindow::callDbusMethod(QString method, dbusIface ifaceName, dbusBus bus, const QList<QVariant> &args)
1738 {
1739     //qDebug() << "Calling method" << method << "with iface" << ifaceName << "on bus" << bus;
1740 
1741     QDBusConnection abus(m_systemBus);
1742     if (bus == user)
1743         abus = QDBusConnection::connectToBus(m_userBusPath, connSystemd);
1744 
1745     QDBusInterface *iface;
1746     if (ifaceName == sysdMgr)
1747         iface = new QDBusInterface(connSystemd, pathSysdMgr, ifaceMgr, abus, this);
1748     else if (ifaceName == logdMgr)
1749         iface = new QDBusInterface(connLogind, pathLogdMgr, ifaceLogdMgr, abus, this);
1750 
1751     QDBusMessage msg;
1752     if (iface->isValid())
1753     {
1754         if (args.isEmpty())
1755             msg = iface->call(QDBus::AutoDetect, method);
1756         else
1757             msg = iface->callWithArgumentList(QDBus::AutoDetect, method, args);
1758         delete iface;
1759         if (msg.type() == QDBusMessage::ErrorMessage)
1760             qDebug() << "DBus method call failed: " << msg.errorMessage();
1761     }
1762     else
1763     {
1764         qDebug() << "Invalid DBus interface on bus" << bus;
1765         delete iface;
1766     }
1767     return msg;
1768 }
1769 
1770 void MainWindow::displayMsgWidget(KMessageWidget::MessageType type, QString msg)
1771 {
1772     KMessageWidget *msgWidget = new KMessageWidget;
1773     msgWidget->setText(msg);
1774     msgWidget->setMessageType(type);
1775     ui.verticalLayoutMsg->insertWidget(0, msgWidget);
1776     msgWidget->animatedShow();
1777 }
1778