File indexing completed on 2024-04-21 04:56:53

0001 /**
0002  * SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "runcommand_config.h"
0008 
0009 #include <QDebug>
0010 #include <QFileDialog>
0011 #include <QHBoxLayout>
0012 #include <QHeaderView>
0013 #include <QJsonArray>
0014 #include <QJsonDocument>
0015 #include <QMenu>
0016 #include <QPushButton>
0017 #include <QStandardItemModel>
0018 #include <QStandardPaths>
0019 #include <QTableView>
0020 #include <QUuid>
0021 
0022 #include <KLocalizedString>
0023 #include <KPluginFactory>
0024 
0025 #include <dbushelper.h>
0026 
0027 K_PLUGIN_CLASS(RunCommandConfig)
0028 
0029 RunCommandConfig::RunCommandConfig(QObject *parent, const KPluginMetaData &data, const QVariantList &args)
0030     : KdeConnectPluginKcm(parent, data, args)
0031 {
0032     // The qdbus executable name is different on some systems
0033     QString qdbusExe = QStringLiteral("qdbus-qt5");
0034     if (QStandardPaths::findExecutable(qdbusExe).isEmpty()) {
0035         qdbusExe = QStringLiteral("qdbus");
0036     }
0037 
0038     QMenu *defaultMenu = new QMenu(widget());
0039 
0040 #ifdef Q_OS_WIN
0041     addSuggestedCommand(defaultMenu, i18n("Schedule a shutdown"), QStringLiteral("shutdown /s /t 60"));
0042     addSuggestedCommand(defaultMenu, i18n("Shutdown now"), QStringLiteral("shutdown /s /t 0"));
0043     addSuggestedCommand(defaultMenu, i18n("Cancel last shutdown"), QStringLiteral("shutdown /a"));
0044     addSuggestedCommand(defaultMenu, i18n("Schedule a reboot"), QStringLiteral("shutdown /r /t 60"));
0045     addSuggestedCommand(defaultMenu, i18n("Suspend"), QStringLiteral("rundll32.exe powrprof.dll,SetSuspendState 0,1,0"));
0046     addSuggestedCommand(defaultMenu, i18n("Lock Screen"), QStringLiteral("rundll32.exe user32.dll,LockWorkStation"));
0047     addSuggestedCommand(
0048         defaultMenu,
0049         i18n("Say Hello"),
0050         QStringLiteral("PowerShell -Command \"Add-Type –AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('hello');\""));
0051 #else
0052     addSuggestedCommand(defaultMenu, i18n("Shutdown"), QStringLiteral("systemctl poweroff"));
0053     addSuggestedCommand(defaultMenu, i18n("Reboot"), QStringLiteral("systemctl reboot"));
0054     addSuggestedCommand(defaultMenu, i18n("Suspend"), QStringLiteral("systemctl suspend"));
0055     addSuggestedCommand(
0056         defaultMenu,
0057         i18n("Maximum Brightness"),
0058         QStringLiteral("%0 org.kde.Solid.PowerManagement /org/kde/Solid/PowerManagement/Actions/BrightnessControl "
0059                        "org.kde.Solid.PowerManagement.Actions.BrightnessControl.setBrightness `%0 org.kde.Solid.PowerManagement "
0060                        "/org/kde/Solid/PowerManagement/Actions/BrightnessControl org.kde.Solid.PowerManagement.Actions.BrightnessControl.brightnessMax`")
0061             .arg(qdbusExe));
0062     addSuggestedCommand(defaultMenu, i18n("Lock Screen"), QStringLiteral("loginctl lock-session"));
0063     addSuggestedCommand(defaultMenu, i18n("Unlock Screen"), QStringLiteral("loginctl unlock-session"));
0064     addSuggestedCommand(defaultMenu, i18n("Close All Vaults"), QStringLiteral("%0 org.kde.kded5 /modules/plasmavault closeAllVaults").arg(qdbusExe));
0065     addSuggestedCommand(defaultMenu,
0066                         i18n("Forcefully Close All Vaults"),
0067                         QStringLiteral("%0 org.kde.kded5 /modules/plasmavault forceCloseAllVaults").arg(qdbusExe));
0068 #endif
0069 
0070     QTableView *table = new QTableView(widget());
0071     table->horizontalHeader()->setStretchLastSection(true);
0072     table->verticalHeader()->setVisible(false);
0073     QPushButton *button = new QPushButton(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Sample commands"), widget());
0074     button->setMenu(defaultMenu);
0075 
0076     QHBoxLayout *importExportLayout = new QHBoxLayout();
0077     QPushButton *exportButton = new QPushButton(i18n("Export"), widget());
0078     importExportLayout->addWidget(exportButton);
0079     connect(exportButton, &QPushButton::clicked, this, &RunCommandConfig::exportCommands);
0080     QPushButton *importButton = new QPushButton(i18n("Import"), widget());
0081     importExportLayout->addWidget(importButton);
0082     connect(importButton, &QPushButton::clicked, this, &RunCommandConfig::importCommands);
0083 
0084     QVBoxLayout *layout = new QVBoxLayout();
0085     layout->addWidget(table);
0086     layout->addLayout(importExportLayout);
0087     layout->addWidget(button);
0088     widget()->setLayout(layout);
0089 
0090     m_entriesModel = new QStandardItemModel(this);
0091     table->setModel(m_entriesModel);
0092 
0093     m_entriesModel->setHorizontalHeaderLabels(QStringList{i18n("Name"), i18n("Command")});
0094 }
0095 
0096 void RunCommandConfig::exportCommands()
0097 {
0098     QString filePath = QFileDialog::getSaveFileName(widget(), i18n("Export Commands"), QDir::homePath(), QStringLiteral("JSON (*.json)"));
0099     if (filePath.isEmpty())
0100         return;
0101 
0102     QFile file(filePath);
0103     if (!file.open(QFile::WriteOnly | QFile::Text)) {
0104         qWarning() << "Could not write to file:" << filePath;
0105         return;
0106     }
0107 
0108     QJsonArray jsonArray;
0109     for (int i = 0; i < m_entriesModel->rowCount(); i++) {
0110         QJsonObject jsonObj;
0111         jsonObj[QStringLiteral("name")] = m_entriesModel->index(i, 0).data().toString();
0112         jsonObj[QStringLiteral("command")] = m_entriesModel->index(i, 1).data().toString();
0113         jsonArray.append(jsonObj);
0114     }
0115 
0116     QJsonDocument jsonDocument(jsonArray);
0117     file.write(jsonDocument.toJson());
0118     file.close();
0119 }
0120 
0121 void RunCommandConfig::importCommands()
0122 {
0123     QString filePath = QFileDialog::getOpenFileName(widget(), i18n("Import Commands"), QDir::homePath(), QStringLiteral("JSON (*.json)"));
0124     if (filePath.isEmpty())
0125         return;
0126 
0127     QFile file(filePath);
0128     if (!file.open(QFile::ReadOnly | QFile::Text)) {
0129         qWarning() << "Could not read file:" << filePath;
0130         return;
0131     }
0132 
0133     QByteArray jsonData = file.readAll();
0134     file.close();
0135 
0136     QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
0137     if (jsonDoc.isNull() || !jsonDoc.isArray()) {
0138         qWarning() << "Invalid JSON format.";
0139         return;
0140     }
0141 
0142     // Clear the current command list
0143     m_entriesModel->removeRows(0, m_entriesModel->rowCount());
0144 
0145     // Populate the model with the imported commands
0146     QJsonArray jsonArray = jsonDoc.array();
0147     for (const QJsonValue &jsonValue : jsonArray) {
0148         QJsonObject jsonObj = jsonValue.toObject();
0149         QString name = jsonObj.value(QStringLiteral("name")).toString();
0150         QString command = jsonObj.value(QStringLiteral("command")).toString();
0151         insertRow(m_entriesModel->rowCount(), name, command);
0152     }
0153 
0154     markAsChanged();
0155 }
0156 
0157 void RunCommandConfig::addSuggestedCommand(QMenu *menu, const QString &name, const QString &command)
0158 {
0159     auto action = new QAction(name);
0160     connect(action, &QAction::triggered, action, [this, name, command]() {
0161         insertRow(0, name, command);
0162         markAsChanged();
0163     });
0164     menu->addAction(action);
0165 }
0166 
0167 void RunCommandConfig::defaults()
0168 {
0169     KCModule::defaults();
0170     m_entriesModel->removeRows(0, m_entriesModel->rowCount());
0171 
0172     markAsChanged();
0173 }
0174 
0175 void RunCommandConfig::load()
0176 {
0177     KCModule::load();
0178 
0179     QJsonDocument jsonDocument = QJsonDocument::fromJson(config()->getByteArray(QStringLiteral("commands"), "{}"));
0180     QJsonObject jsonConfig = jsonDocument.object();
0181     const QStringList keys = jsonConfig.keys();
0182     for (const QString &key : keys) {
0183         const QJsonObject entry = jsonConfig[key].toObject();
0184         const QString name = entry[QStringLiteral("name")].toString();
0185         const QString command = entry[QStringLiteral("command")].toString();
0186 
0187         QStandardItem *newName = new QStandardItem(name);
0188         newName->setEditable(true);
0189         newName->setData(key);
0190         QStandardItem *newCommand = new QStandardItem(command);
0191         newName->setEditable(true);
0192 
0193         m_entriesModel->appendRow(QList<QStandardItem *>() << newName << newCommand);
0194     }
0195 
0196     m_entriesModel->sort(0);
0197 
0198     insertEmptyRow();
0199     connect(m_entriesModel, &QAbstractItemModel::dataChanged, this, &RunCommandConfig::onDataChanged);
0200 }
0201 
0202 void RunCommandConfig::save()
0203 {
0204     KCModule::save();
0205     QJsonObject jsonConfig;
0206     for (int i = 0; i < m_entriesModel->rowCount(); i++) {
0207         QString key = m_entriesModel->item(i, 0)->data().toString();
0208         const QString name = m_entriesModel->item(i, 0)->text();
0209         const QString command = m_entriesModel->item(i, 1)->text();
0210 
0211         if (name.isEmpty() || command.isEmpty()) {
0212             continue;
0213         }
0214 
0215         if (key.isEmpty()) {
0216             key = QUuid::createUuid().toString();
0217             DBusHelper::filterNonExportableCharacters(key);
0218         }
0219         QJsonObject entry;
0220         entry[QStringLiteral("name")] = name;
0221         entry[QStringLiteral("command")] = command;
0222         jsonConfig[key] = entry;
0223     }
0224     QJsonDocument document;
0225     document.setObject(jsonConfig);
0226     config()->set(QStringLiteral("commands"), document.toJson(QJsonDocument::Compact));
0227 }
0228 
0229 void RunCommandConfig::insertEmptyRow()
0230 {
0231     insertRow(m_entriesModel->rowCount(), {}, {});
0232 }
0233 
0234 void RunCommandConfig::insertRow(int i, const QString &name, const QString &command)
0235 {
0236     QStandardItem *newName = new QStandardItem(name);
0237     newName->setEditable(true);
0238     QStandardItem *newCommand = new QStandardItem(command);
0239     newName->setEditable(true);
0240 
0241     m_entriesModel->insertRow(i, QList<QStandardItem *>() << newName << newCommand);
0242 }
0243 
0244 void RunCommandConfig::onDataChanged(const QModelIndex & /*topLeft*/, const QModelIndex &bottomRight)
0245 {
0246     markAsChanged();
0247     if (bottomRight.row() == m_entriesModel->rowCount() - 1) {
0248         // TODO check both entries are still empty
0249         insertEmptyRow();
0250     }
0251 }
0252 
0253 #include "moc_runcommand_config.cpp"
0254 #include "runcommand_config.moc"