/* SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include "AboutDialog.h" #include "Configuration.h" #include "MainWindow.h" #include "NvramToolCli.h" #include "ToggleSwitch.h" #include "ui_MainWindow.h" static auto s_errorWindowTitle = MainWindow::tr("Error Occurred"); static auto s_nvramErrorMessage = MainWindow::tr("Nvramtool was not able to access cmos settings. Look at documentation for possible causes of errors."); QString makeNvramErrorMessage(const QString& error){ if(!error.trimmed().isEmpty()){ return QString(MainWindow::tr("%1

Error message:
%2")).arg(s_nvramErrorMessage, Qt::convertFromPlainText(error)); } return s_nvramErrorMessage; } namespace YAML { template <> struct convert{ static Node encode(const QString& rhs) { return Node(rhs.toUtf8().data()); } static bool decode(const Node& node, QString& rhs) { if (!node.IsScalar()) return false; rhs = QString::fromStdString(node.Scalar()); return true; } }; } static auto s_metadataErrorMessage = MainWindow::tr("Can't load categories metadata file. Check your installation."); static constexpr char s_sudoProg[] = "/usr/bin/pkexec"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); connect(ui->actionAbout, &QAction::triggered, this, [](){ AboutDialog().exec(); }); #if MOCK this->setWindowTitle("coreboot configurator "+tr("[MOCKED DATA]")); #else this->setWindowTitle("coreboot configurator"); #endif this->setWindowIcon(QIcon::fromTheme("coreboot_configurator")); QFile catFile(":/config/categories.yaml"); if(!catFile.open(QFile::ReadOnly)){ QMessageBox::critical(this, s_errorWindowTitle, s_metadataErrorMessage); this->close(); return; } m_categories = YAML::Load(catFile.readAll()); if(m_categories.IsNull() || !m_categories.IsDefined()){ QMessageBox::critical(this, s_errorWindowTitle, s_metadataErrorMessage); this->close(); return; } QShortcut* returnAction = new QShortcut(QKeySequence("Ctrl+Return"), this); connect(returnAction, &QShortcut::activated, this, &MainWindow::on_saveButton_clicked); generateUi(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::pullSettings() { QString error; m_parameters = NvramToolCli::readParameters(&error); if(m_parameters.isEmpty()){ QMessageBox::critical(this, s_errorWindowTitle, makeNvramErrorMessage(error)); /* we need delayed close as initialization error happened before event loop start so we can't stop application properly */ QTimer::singleShot(0, this, &MainWindow::close); } } void MainWindow::pushSettings() { QString error; if(!NvramToolCli::writeParameters(m_parameters, &error)){ QMessageBox::critical(this, s_errorWindowTitle, makeNvramErrorMessage(error)); } } QComboBox* MainWindow::createComboBox(const QString& key) { auto box = new QComboBox(this); auto opts = NvramToolCli::readOptions(key); box->addItems(opts); box->setCurrentText(m_parameters[key]); connect(ui->advancedModeCheckBox, &QCheckBox::clicked, this, [box](bool clicked){ box->setEditable(clicked); }); connect(this, &MainWindow::updateValue, this, [box, this, key](const QString& name){ if(key!=name || m_parameters[name]==box->currentText()){ return; } box->setCurrentText(m_parameters[name]); }); connect(box, &QComboBox::currentTextChanged, this, [key, this](const QString& value){ if(value==m_parameters[key]){ return; } m_parameters[key] = value; emit updateValue(key); }); return box; } QString boolToString(bool value){ return value?QStringLiteral("Enable"):QStringLiteral("Disable"); } bool stringToBool(const QString& str){ return str==QStringLiteral("Enable"); } QCheckBox* MainWindow::createCheckBox(const QString& key) { auto box = new ToggleSwitch(this); box->setChecked(stringToBool(m_parameters[key])); connect(this, &MainWindow::updateValue, this, [box, this, key](const QString& name){ if(key!=name || m_parameters[name]==boolToString(box->isChecked())){ return; } auto newValue = stringToBool(m_parameters[name]); box->setChecked(newValue); }); connect(box, &QCheckBox::clicked, this, [key, this](bool checked){ auto value = boolToString(checked); if(value==m_parameters[key]){ return; } m_parameters[key] = value; emit updateValue(key); }); return box; } QTableWidget *MainWindow::createRawTable() { /* Create Raw values table */ auto table = new QTableWidget(m_parameters.size(), 2); table->setHorizontalHeaderLabels({tr("Key"), tr("Value")}); table->horizontalHeader()->setSectionResizeMode(0,QHeaderView::Stretch); table->verticalHeader()->hide(); table->setSelectionBehavior(QTableWidget::SelectRows); connect(table, &QTableWidget::cellChanged, this, [table, this](int row, int column){ if(column != 1 || row >= table->rowCount() || row < 0 ){ /* Weird state when changed cell is not a value cell */ return; } auto keyItem = table->item(row, 0); auto valueItem = table->item(row, 1); if(keyItem == nullptr || valueItem == nullptr){ /* Invalid cells */ return; } if(valueItem->text()==m_parameters[keyItem->text()]){ return; } m_parameters[keyItem->text()] = valueItem->text(); emit updateValue(keyItem->text()); }); auto it = m_parameters.begin(); for(int i = 0; isetFlags(item->flags() ^ Qt::ItemIsEditable); table->setItem(i,0,item); item = new QTableWidgetItem(it.value()); connect(this, &MainWindow::updateValue, this, [item, it, this](const QString& name){ if(it.key()!=name || m_parameters[name]==item->text()){ return; } item->setText(m_parameters[name]); }); table->setItem(i,1,item); } return table; } void MainWindow::generateUi() { pullSettings(); if(!m_categories.IsMap()){ return; } for(const auto& category : m_categories){ if(!category.second.IsMap()){ continue; } auto name = category.second["displayName"].as(); auto layout = new QVBoxLayout; auto tabPage = new QWidget(this); tabPage->setLayout(layout); ui->centralTabWidget->addTab(tabPage, name); for(const auto& value : category.second){ if(!value.second.IsMap() || !m_parameters.contains(value.first.as())){ continue; } auto displayName = value.second["displayName"]; if(!displayName.IsDefined()){ continue; } auto type = value.second["type"]; if(!type.IsDefined()){ continue; } auto controlLayout = new QHBoxLayout(); auto help = value.second["help"]; if(help.IsDefined()){ auto labelWithTooltip = new QWidget; labelWithTooltip->setToolTip(help.as()); labelWithTooltip->setCursor({Qt::WhatsThisCursor}); labelWithTooltip->setLayout(new QHBoxLayout); auto helpButton = new QLabel(); helpButton->setPixmap(QIcon::fromTheme("help-hint").pixmap(16,16)); { auto layout = qobject_cast(labelWithTooltip->layout()); layout->addWidget(new QLabel(displayName.as())); layout->addWidget(helpButton,1); } controlLayout->addWidget(labelWithTooltip, 0); } else { controlLayout->addWidget(new QLabel(displayName.as()), 0); } controlLayout->addStretch(1); QWidget* res = nullptr; if(type.as() == QStringLiteral("bool")){ res = createCheckBox(value.first.as()); } else if (type.as() == QStringLiteral("enum")){ res = createComboBox(value.first.as()); } else { controlLayout->deleteLater(); continue; } res->setObjectName(value.first.as()); controlLayout->addWidget(res, 0); layout->addLayout(controlLayout); } } auto table = createRawTable(); connect(ui->advancedModeCheckBox, &QCheckBox::clicked, this, [table,this](bool clicked){ if(clicked && ui->centralTabWidget->widget(ui->centralTabWidget->count()-1) != table){ ui->centralTabWidget->addTab(table, tr("Raw")); } else if(!clicked && ui->centralTabWidget->widget(ui->centralTabWidget->count()-1) == table) { ui->centralTabWidget->removeTab(ui->centralTabWidget->count()-1); } }); } void MainWindow::askForReboot() { QMessageBox rebootDialog(QMessageBox::Question, tr("Reboot"), tr("Changes are saved. Do you want to reboot to apply changes?")); auto nowButton = rebootDialog.addButton(tr("Reboot now"), QMessageBox::AcceptRole); rebootDialog.addButton(tr("Reboot later"), QMessageBox::RejectRole); rebootDialog.exec(); if(rebootDialog.clickedButton()==nowButton){ QProcess::startDetached(s_sudoProg, {"/usr/bin/systemctl", "reboot"}); this->close(); } } void MainWindow::readSettings(const QString &fileName) { if(fileName.isEmpty()){ return; } auto configValues = Configuration::fromFile(fileName); for(auto it = configValues.begin(); it != configValues.end(); ++it){ if(!m_parameters.contains(it.key())){ continue; } m_parameters[it.key()]=it.value(); emit updateValue(it.key()); } } void MainWindow::writeSettings(const QString &fileName) { if(fileName.isEmpty()){ return; } if(!Configuration::toFile(fileName, m_parameters)){ QMessageBox::critical(this, tr("Error Occurred"), tr("Can't open file to write")); this->close(); } } void MainWindow::on_actionSave_triggered() { auto filename = QFileDialog::getSaveFileName(this, tr("Select File To Save"), QDir::homePath(), tr("Coreboot Configuration Files")+"(*.cfg)"); writeSettings(filename); } void MainWindow::on_actionLoad_triggered() { auto filename = QFileDialog::getOpenFileName(this, tr("Select File To Load"), QDir::homePath(), tr("Coreboot Configuration Files")+"(*.cfg)"); readSettings(filename); } void MainWindow::on_saveButton_clicked() { ui->centralwidget->setEnabled(false); ui->menubar->setEnabled(false); pushSettings(); askForReboot(); ui->centralwidget->setEnabled(true); ui->menubar->setEnabled(true); }