xref: /aosp_15_r20/external/coreboot/util/coreboot-configurator/src/application/MainWindow.cpp (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* 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 
17 static auto s_errorWindowTitle = MainWindow::tr("Error Occurred");
18 static auto s_nvramErrorMessage = MainWindow::tr("Nvramtool was not able to access cmos settings. Look at documentation for possible causes of errors.");
19 
makeNvramErrorMessage(const QString & error)20 QString 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 
28 namespace YAML {
29 template <>
30 struct convert<QString>{
encodeYAML::convert31 	static Node encode(const QString& rhs) { return Node(rhs.toUtf8().data()); }
32 
decodeYAML::convert33 	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 
42 static auto s_metadataErrorMessage =  MainWindow::tr("Can't load categories metadata file. Check your installation.");
43 static constexpr char s_sudoProg[] = "/usr/bin/pkexec";
44 
MainWindow(QWidget * parent)45 MainWindow::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 
~MainWindow()84 MainWindow::~MainWindow()
85 {
86 	delete ui;
87 }
88 
pullSettings()89 void 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 
pushSettings()102 void 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 
createComboBox(const QString & key)111 QComboBox* 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 }
boolToString(bool value)140 QString boolToString(bool value){
141 	return value?QStringLiteral("Enable"):QStringLiteral("Disable");
142 }
stringToBool(const QString & str)143 bool stringToBool(const QString& str){
144 	return str==QStringLiteral("Enable");
145 }
createCheckBox(const QString & key)146 QCheckBox* 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 
createRawTable()174 QTableWidget *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 
generateUi()224 void 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 
askForReboot()311 void 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 
readSettings(const QString & fileName)327 void 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 
writeSettings(const QString & fileName)344 void MainWindow::writeSettings(const QString &fileName)
345 {
346 	if(fileName.isEmpty()){
347 		return;
348 	}
349 	if(!Configuration::toFile(fileName, m_parameters)){
350 		QMessageBox::critical(this, tr("Error Occurred"), tr("Can't open file to write"));
351 		this->close();
352 	}
353 }
354 
355 
on_actionSave_triggered()356 void 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 
on_actionLoad_triggered()366 void 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 
on_saveButton_clicked()377 void 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 }
389