File indexing completed on 2025-04-20 04:33:21
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"