blob: 974f2adc90e3feafbc60d60e7902f4f5f5aff0b1 [file] [log] [blame]
Sean Rhodes9c89e3a2021-11-12 08:54:50 +00001/* SPDX-License-Identifier: GPL-2.0-only */
2
3#include <QFileDialog>
4#include <QHeaderView>
5#include <QLabel>
6#include <QMessageBox>
7#include <QShortcut>
8#include <QtGui>
9
10#include "AboutDialog.h"
11#include "Configuration.h"
12#include "MainWindow.h"
13#include "NvramToolCli.h"
14#include "ToggleSwitch.h"
15#include "ui_MainWindow.h"
16
Martin Rothe44a3d22022-05-28 12:33:44 -060017static auto s_errorWindowTitle = MainWindow::tr("Error Occurred");
Sean Rhodes9c89e3a2021-11-12 08:54:50 +000018static auto s_nvramErrorMessage = MainWindow::tr("Nvramtool was not able to access cmos settings. Look at documentation for possible causes of errors.");
19
20QString makeNvramErrorMessage(const QString& error){
21 if(!error.trimmed().isEmpty()){
22 return QString(MainWindow::tr("%1<br><br>Error message:<br><tt>%2</tt>")).arg(s_nvramErrorMessage,
23 Qt::convertFromPlainText(error));
24 }
25 return s_nvramErrorMessage;
26}
27
28namespace YAML {
29template <>
30struct convert<QString>{
31 static Node encode(const QString& rhs) { return Node(rhs.toUtf8().data()); }
32
33 static bool decode(const Node& node, QString& rhs) {
34 if (!node.IsScalar())
35 return false;
36 rhs = QString::fromStdString(node.Scalar());
37 return true;
38 }
39};
40}
41
42static auto s_metadataErrorMessage = MainWindow::tr("Can't load categories metadata file. Check your installation.");
43static constexpr char s_sudoProg[] = "/usr/bin/pkexec";
44
45MainWindow::MainWindow(QWidget *parent)
46 : QMainWindow(parent)
47 , ui(new Ui::MainWindow)
48{
49 ui->setupUi(this);
50
51 connect(ui->actionAbout, &QAction::triggered, this, [](){
52 AboutDialog().exec();
53 });
54
55#if MOCK
56 this->setWindowTitle("coreboot configurator "+tr("[MOCKED DATA]"));
57#else
58 this->setWindowTitle("coreboot configurator");
59#endif
60 this->setWindowIcon(QIcon::fromTheme("coreboot_configurator"));
61
62 QFile catFile(":/config/categories.yaml");
63
64 if(!catFile.open(QFile::ReadOnly)){
65 QMessageBox::critical(this, s_errorWindowTitle, s_metadataErrorMessage);
66 this->close();
67 return;
68 }
69
70 m_categories = YAML::Load(catFile.readAll());
71
72 if(m_categories.IsNull() || !m_categories.IsDefined()){
73 QMessageBox::critical(this, s_errorWindowTitle, s_metadataErrorMessage);
74 this->close();
75 return;
76 }
77
78 QShortcut* returnAction = new QShortcut(QKeySequence("Ctrl+Return"), this);
79 connect(returnAction, &QShortcut::activated, this, &MainWindow::on_saveButton_clicked);
80
81 generateUi();
82}
83
84MainWindow::~MainWindow()
85{
86 delete ui;
87}
88
89void MainWindow::pullSettings()
90{
91 QString error;
92 m_parameters = NvramToolCli::readParameters(&error);
93
94 if(m_parameters.isEmpty()){
95 QMessageBox::critical(this, s_errorWindowTitle, makeNvramErrorMessage(error));
96
97 /* we need delayed close as initialization error happened before event loop start so we can't stop application properly */
98 QTimer::singleShot(0, this, &MainWindow::close);
99 }
100}
101
102void MainWindow::pushSettings()
103{
104 QString error;
105 if(!NvramToolCli::writeParameters(m_parameters, &error)){
106 QMessageBox::critical(this, s_errorWindowTitle, makeNvramErrorMessage(error));
107 }
108}
109
110
111QComboBox* MainWindow::createComboBox(const QString& key) {
112 auto box = new QComboBox(this);
113
114 auto opts = NvramToolCli::readOptions(key);
115
116 box->addItems(opts);
117 box->setCurrentText(m_parameters[key]);
118
119 connect(ui->advancedModeCheckBox, &QCheckBox::clicked, this, [box](bool clicked){
120 box->setEditable(clicked);
121 });
122
123 connect(this, &MainWindow::updateValue, this, [box, this, key](const QString& name){
124 if(key!=name || m_parameters[name]==box->currentText()){
125 return;
126 }
127 box->setCurrentText(m_parameters[name]);
128 });
129
130 connect(box, &QComboBox::currentTextChanged, this, [key, this](const QString& value){
131 if(value==m_parameters[key]){
132 return;
133 }
134 m_parameters[key] = value;
135 emit updateValue(key);
136 });
137
138 return box;
139}
140QString boolToString(bool value){
141 return value?QStringLiteral("Enable"):QStringLiteral("Disable");
142}
143bool stringToBool(const QString& str){
144 return str==QStringLiteral("Enable");
145}
146QCheckBox* MainWindow::createCheckBox(const QString& key) {
147 auto box = new ToggleSwitch(this);
148
149 box->setChecked(stringToBool(m_parameters[key]));
150
151 connect(this, &MainWindow::updateValue, this, [box, this, key](const QString& name){
152
153 if(key!=name || m_parameters[name]==boolToString(box->isChecked())){
154 return;
155 }
156 auto newValue = stringToBool(m_parameters[name]);
157
158 box->setChecked(newValue);
159 });
160
161 connect(box, &QCheckBox::clicked, this, [key, this](bool checked){
162 auto value = boolToString(checked);
163 if(value==m_parameters[key]){
164 return;
165 }
166 m_parameters[key] = value;
167 emit updateValue(key);
168 });
169
170 return box;
171}
172
173
174QTableWidget *MainWindow::createRawTable()
175{
176 /* Create Raw values table */
177 auto table = new QTableWidget(m_parameters.size(), 2);
178 table->setHorizontalHeaderLabels({tr("Key"), tr("Value")});
179 table->horizontalHeader()->setSectionResizeMode(0,QHeaderView::Stretch);
180 table->verticalHeader()->hide();
181 table->setSelectionBehavior(QTableWidget::SelectRows);
182
183 connect(table, &QTableWidget::cellChanged, this, [table, this](int row, int column){
184 if(column != 1 || row >= table->rowCount() || row < 0 ){
185 /* Weird state when changed cell is not a value cell */
186 return;
187 }
188 auto keyItem = table->item(row, 0);
189 auto valueItem = table->item(row, 1);
190
191 if(keyItem == nullptr || valueItem == nullptr){
192 /* Invalid cells */
193 return;
194 }
195
196 if(valueItem->text()==m_parameters[keyItem->text()]){
197 return;
198 }
199
200 m_parameters[keyItem->text()] = valueItem->text();
201 emit updateValue(keyItem->text());
202 });
203
204 auto it = m_parameters.begin();
205 for(int i = 0; i<m_parameters.size(); i++, ++it){
206
207 auto item = new QTableWidgetItem(it.key());
208 item->setFlags(item->flags() ^ Qt::ItemIsEditable);
209 table->setItem(i,0,item);
210
211 item = new QTableWidgetItem(it.value());
212 connect(this, &MainWindow::updateValue, this, [item, it, this](const QString& name){
213 if(it.key()!=name || m_parameters[name]==item->text()){
214 return;
215 }
216 item->setText(m_parameters[name]);
217 });
218
219 table->setItem(i,1,item);
220 }
221 return table;
222}
223
224void MainWindow::generateUi()
225{
226 pullSettings();
227
228 if(!m_categories.IsMap()){
229 return;
230 }
231 for(const auto& category : m_categories){
232 if(!category.second.IsMap()){
233 continue;
234 }
235 auto name = category.second["displayName"].as<QString>();
236
237 auto layout = new QVBoxLayout;
238
239 auto tabPage = new QWidget(this);
240 tabPage->setLayout(layout);
241
242 ui->centralTabWidget->addTab(tabPage, name);
243
244 for(const auto& value : category.second){
245 if(!value.second.IsMap() || !m_parameters.contains(value.first.as<QString>())){
246 continue;
247 }
248 auto displayName = value.second["displayName"];
249 if(!displayName.IsDefined()){
250 continue;
251 }
252 auto type = value.second["type"];
253 if(!type.IsDefined()){
254 continue;
255 }
256
257 auto controlLayout = new QHBoxLayout();
258
259 auto help = value.second["help"];
260
261 if(help.IsDefined()){
262 auto labelWithTooltip = new QWidget;
263 labelWithTooltip->setToolTip(help.as<QString>());
264 labelWithTooltip->setCursor({Qt::WhatsThisCursor});
265 labelWithTooltip->setLayout(new QHBoxLayout);
266
267 auto helpButton = new QLabel();
268 helpButton->setPixmap(QIcon::fromTheme("help-hint").pixmap(16,16));
269
270 {
271 auto layout = qobject_cast<QHBoxLayout*>(labelWithTooltip->layout());
272 layout->addWidget(new QLabel(displayName.as<QString>()));
273 layout->addWidget(helpButton,1);
274 }
275 controlLayout->addWidget(labelWithTooltip, 0);
276 } else {
277 controlLayout->addWidget(new QLabel(displayName.as<QString>()), 0);
278 }
279
280 controlLayout->addStretch(1);
281
282 QWidget* res = nullptr;
283
284 if(type.as<QString>() == QStringLiteral("bool")){
285 res = createCheckBox(value.first.as<QString>());
286 } else if (type.as<QString>() == QStringLiteral("enum")){
287 res = createComboBox(value.first.as<QString>());
288 } else {
289 controlLayout->deleteLater();
290 continue;
291 }
292 res->setObjectName(value.first.as<QString>());
293
294 controlLayout->addWidget(res, 0);
295
296 layout->addLayout(controlLayout);
297 }
298 }
299
300 auto table = createRawTable();
301
302 connect(ui->advancedModeCheckBox, &QCheckBox::clicked, this, [table,this](bool clicked){
303 if(clicked && ui->centralTabWidget->widget(ui->centralTabWidget->count()-1) != table){
304 ui->centralTabWidget->addTab(table, tr("Raw"));
305 } else if(!clicked && ui->centralTabWidget->widget(ui->centralTabWidget->count()-1) == table) {
306 ui->centralTabWidget->removeTab(ui->centralTabWidget->count()-1);
307 }
308 });
309}
310
311void MainWindow::askForReboot()
312{
313 QMessageBox rebootDialog(QMessageBox::Question,
314 tr("Reboot"),
315 tr("Changes are saved. Do you want to reboot to apply changes?"));
316
317 auto nowButton = rebootDialog.addButton(tr("Reboot now"), QMessageBox::AcceptRole);
318 rebootDialog.addButton(tr("Reboot later"), QMessageBox::RejectRole);
319
320 rebootDialog.exec();
321 if(rebootDialog.clickedButton()==nowButton){
322 QProcess::startDetached(s_sudoProg, {"/usr/bin/systemctl", "reboot"});
323 this->close();
324 }
325}
326
327void MainWindow::readSettings(const QString &fileName)
328{
329 if(fileName.isEmpty()){
330 return;
331 }
332
333 auto configValues = Configuration::fromFile(fileName);
334
335 for(auto it = configValues.begin(); it != configValues.end(); ++it){
336 if(!m_parameters.contains(it.key())){
337 continue;
338 }
339 m_parameters[it.key()]=it.value();
340 emit updateValue(it.key());
341 }
342}
343
344void MainWindow::writeSettings(const QString &fileName)
345{
346 if(fileName.isEmpty()){
347 return;
348 }
349 if(!Configuration::toFile(fileName, m_parameters)){
Martin Rothe44a3d22022-05-28 12:33:44 -0600350 QMessageBox::critical(this, tr("Error Occurred"), tr("Can't open file to write"));
Sean Rhodes9c89e3a2021-11-12 08:54:50 +0000351 this->close();
352 }
353}
354
355
356void MainWindow::on_actionSave_triggered()
357{
358 auto filename = QFileDialog::getSaveFileName(this,
359 tr("Select File To Save"),
360 QDir::homePath(),
361 tr("Coreboot Configuration Files")+"(*.cfg)");
362 writeSettings(filename);
363}
364
365
366void MainWindow::on_actionLoad_triggered()
367{
368 auto filename = QFileDialog::getOpenFileName(this,
369 tr("Select File To Load"),
370 QDir::homePath(),
371 tr("Coreboot Configuration Files")+"(*.cfg)");
372
373 readSettings(filename);
374}
375
376
377void MainWindow::on_saveButton_clicked()
378{
379 ui->centralwidget->setEnabled(false);
380 ui->menubar->setEnabled(false);
381
382 pushSettings();
383
384 askForReboot();
385
386 ui->centralwidget->setEnabled(true);
387 ui->menubar->setEnabled(true);
388}