From d2393563985956a6994421f233b25075d9e63949 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 29 Aug 2025 20:59:23 +0100 Subject: [PATCH 1/2] Add Settings class --- CMakeLists.txt | 2 ++ src/main.cpp | 41 ++++++++++++++++++++++++ src/maindialog.cpp | 29 ----------------- src/maindialog.h | 1 - src/settings.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 55 ++++++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 30 deletions(-) create mode 100644 src/settings.cpp create mode 100644 src/settings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b55c9f5..83a611f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ set(PROJECT_SOURCES src/layoutmodel.h src/environment.cpp src/environment.h + src/settings.cpp + src/settings.h src/theme.c src/theme.h src/xml.c diff --git a/src/main.cpp b/src/main.cpp index f8f47cd..83e1c72 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,9 +2,16 @@ #include #include +#include #include #include +#include "settings.h" + +extern "C" { +#include "xml.h" +} + static void initLocale(QTranslator *qtTranslator, QTranslator *translator) { QApplication *app = qApp; @@ -37,6 +44,30 @@ static void initLocale(QTranslator *qtTranslator, QTranslator *translator) app->installTranslator(translator); } +void initConfig(std::string &config_file) +{ + bool success = xml_init(config_file.data()); + + if (!success) { + QMessageBox msgBox; + msgBox.setText(QObject::tr("Error loading ") + QString(config_file.data())); + msgBox.setInformativeText( + QObject::tr("Run labwc-tweaks from a terminal to view error messages")); + msgBox.exec(); + exit(EXIT_FAILURE); + } + + /* Ensure all relevant nodes exist before we start getting/setting */ + xpath_add_node("/labwc_config/theme/cornerRadius"); + xpath_add_node("/labwc_config/theme/name"); + xpath_add_node("/labwc_config/theme/dropShadows"); + xpath_add_node("/labwc_config/theme/icon"); + xpath_add_node("/labwc_config/placement/policy"); + xpath_add_node("/labwc_config/libinput/device/naturalScroll"); + + xml_save(); +} + int main(int argc, char *argv[]) { QApplication app(argc, argv); @@ -45,6 +76,16 @@ int main(int argc, char *argv[]) QTranslator qtTranslator, translator; initLocale(&qtTranslator, &translator); + std::string config_dir = + std::getenv("LABWC_CONFIG_DIR") ?: std::getenv("HOME") + std::string("/.config/labwc"); + std::string config_file = config_dir + "/rc.xml"; + initConfig(config_file); + + // The 'settings' vector contains the master state of all settings that can + // be changed by labwc-tweaks. + std::vector> settings; + initSettings(settings); + MainDialog w; w.show(); diff --git a/src/maindialog.cpp b/src/maindialog.cpp index 2342bb3..150569c 100644 --- a/src/maindialog.cpp +++ b/src/maindialog.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -27,11 +26,6 @@ MainDialog::MainDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MainDialog m_model = new LayoutModel(this); ui->layoutView->setModel(m_model); - std::string config_dir = - std::getenv("LABWC_CONFIG_DIR") ?: std::getenv("HOME") + std::string("/.config/labwc"); - std::string config_file = config_dir + "/rc.xml"; - initConfig(config_file); - QObject::connect(ui->buttonBox, &QDialogButtonBox::clicked, [&](QAbstractButton *button) { if (ui->buttonBox->standardButton(button) == QDialogButtonBox::Apply) { onApply(); @@ -153,29 +147,6 @@ void MainDialog::activate() } } -void MainDialog::initConfig(std::string &config_file) -{ - bool success = xml_init(config_file.data()); - - if (!success) { - QMessageBox msgBox; - msgBox.setText(tr("Error loading ") + QString(config_file.data())); - msgBox.setInformativeText(tr("Run labwc-tweaks from a terminal to view error messages")); - msgBox.exec(); - exit(EXIT_FAILURE); - } - - /* Ensure all relevant nodes exist before we start getting/setting */ - xpath_add_node("/labwc_config/theme/cornerRadius"); - xpath_add_node("/labwc_config/theme/name"); - xpath_add_node("/labwc_config/theme/dropShadows"); - xpath_add_node("/labwc_config/theme/name"); - xpath_add_node("/labwc_config/placement/policy"); - xpath_add_node("/labwc_config/libinput/device/naturalScroll"); - - xml_save(); -} - void MainDialog::onApply() { /* ~/.config/labwc/rc.xml */ diff --git a/src/maindialog.h b/src/maindialog.h index a78d22a..149189b 100644 --- a/src/maindialog.h +++ b/src/maindialog.h @@ -26,7 +26,6 @@ private slots: private: LayoutModel *m_model; - void initConfig(std::string &config_file); void onApply(); Ui::MainDialog *ui; diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..e0e8c40 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,78 @@ +#include +#include +#include "settings.h" + +extern "C" { +#include "xml.h" +} + +void initSettings(std::vector> &settings) +{ + // Appearance + settings.push_back(std::make_shared("/labwc_config/theme/name", LAB_FILE_TYPE_RCXML, + LAB_VALUE_TYPE_STRING, "")); + settings.push_back(std::make_shared("/labwc_config/theme/cornerRadius", + LAB_FILE_TYPE_RCXML, LAB_VALUE_TYPE_INT, 8)); + settings.push_back(std::make_shared("/labwc_config/theme/dropShadows", + LAB_FILE_TYPE_RCXML, LAB_VALUE_TYPE_BOOL, 1)); + settings.push_back(std::make_shared("/labwc_config/theme/icon", LAB_FILE_TYPE_RCXML, + LAB_VALUE_TYPE_STRING, "")); + + // Behaviour + settings.push_back(std::make_shared("/labwc_config/placement/policy", + LAB_FILE_TYPE_RCXML, LAB_VALUE_TYPE_STRING, + "Cascade")); + + // Mouse & Touchpad + settings.push_back(std::make_shared("XCURSOR_THEME", LAB_FILE_TYPE_ENVIRONMENT, + LAB_VALUE_TYPE_STRING, "Adwaita")); + settings.push_back(std::make_shared("XCURSOR_SIZE", LAB_FILE_TYPE_ENVIRONMENT, + LAB_VALUE_TYPE_INT, 24)); + settings.push_back(std::make_shared("/labwc_config/libinput/device/naturalScroll", + LAB_FILE_TYPE_RCXML, LAB_VALUE_TYPE_BOOL, 0)); + + // Language + settings.push_back(std::make_shared("XKB_DEFAULT_LAYOUT", LAB_FILE_TYPE_ENVIRONMENT, + LAB_VALUE_TYPE_STRING, "us")); +} + +Setting::Setting(QString name, enum settingFileType fileType, enum settingValueType valueType, + std::variant defaultValue) + : m_name(name), m_fileType(fileType), m_valueType(valueType), m_value(defaultValue) +{ + m_valueOrigin = LAB_VALUE_ORIGIN_DEFAULT; + + if (m_fileType == LAB_FILE_TYPE_RCXML) { + switch (m_valueType) { + case LAB_VALUE_TYPE_STRING: { + QString value = QString(xml_get(m_name.toStdString().c_str())); + if (value != std::get(m_value)) { + m_valueOrigin = LAB_VALUE_ORIGIN_USER_OVERRIDE; + m_value = value; + qDebug() << "USER OVERRIDE: " << m_name << "=" << value; + } + break; + } + case LAB_VALUE_TYPE_INT: { + int value = xml_get_int(m_name.toStdString().c_str()); + if (value != std::get(m_value)) { + m_valueOrigin = LAB_VALUE_ORIGIN_USER_OVERRIDE; + m_value = value; + qDebug() << "USER OVERRIDE: " << m_name << "=" << value; + } + break; + } + case LAB_VALUE_TYPE_BOOL: { + int value = xml_get_bool_text(m_name.toStdString().c_str()); + if (value != std::get(m_value)) { + m_valueOrigin = LAB_VALUE_ORIGIN_USER_OVERRIDE; + m_value = value; + qDebug() << "USER OVERRIDE: " << m_name << "=" << value; + } + break; + } + default: + break; + } + } +}; diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..ebd6310 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +enum settingFileType { + LAB_FILE_TYPE_UNKNOWN = 0, + LAB_FILE_TYPE_RCXML, + LAB_FILE_TYPE_ENVIRONMENT, +}; + +enum settingValueOrigin { + LAB_VALUE_ORIGIN_UNKNOWN = 0, + LAB_VALUE_ORIGIN_DEFAULT, + LAB_VALUE_ORIGIN_USER_OVERRIDE, +}; + +enum settingValueType { + LAB_VALUE_TYPE_UNKNOWN = 0, + LAB_VALUE_TYPE_INT, + LAB_VALUE_TYPE_BOOL, + LAB_VALUE_TYPE_STRING, +}; + +static inline QString settingFileTypeName(enum settingFileType type) +{ + if (type == LAB_FILE_TYPE_RCXML) + return "rc.xml"; + else if (type == LAB_FILE_TYPE_ENVIRONMENT) + return "environment"; + return "unknown"; +} + +class Setting +{ +public: + Setting(QString name, enum settingFileType fileType, enum settingValueType valueType, + std::variant defaultValue); + +private: + QString m_name; + enum settingFileType m_fileType; + enum settingValueOrigin m_valueOrigin; + enum settingValueType m_valueType; + std::variant m_value; + +public: + // Getters + QString name() const { return m_name; } + enum settingFileType fileType() const { return m_fileType; } + enum settingValueOrigin valueOrigin() const { return m_valueOrigin; } + enum settingValueType valueType() const { return m_valueType; } +}; + +void initSettings(std::vector> &settings); From faa6216bc6acdaa26ff8b3450754d3b9c8480dc3 Mon Sep 17 00:00:00 2001 From: Johan Malm Date: Fri, 29 Aug 2025 21:05:19 +0100 Subject: [PATCH 2/2] Only update rc.xml on change from previous state ...which could be either the default or earlier rc.xml state --- src/main.cpp | 2 +- src/maindialog.cpp | 104 +++++++++++++++++++++++++++++++++++++++++---- src/maindialog.h | 4 +- src/settings.cpp | 10 +++++ src/settings.h | 5 +++ 5 files changed, 115 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 83e1c72..8d1a51c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) std::vector> settings; initSettings(settings); - MainDialog w; + MainDialog w(settings); w.show(); // Make work the window icon also when the application is not (yet) installed diff --git a/src/maindialog.cpp b/src/maindialog.cpp index 150569c..d1764ba 100644 --- a/src/maindialog.cpp +++ b/src/maindialog.cpp @@ -10,6 +10,7 @@ #include "evdev-lst-layouts.h" #include "layoutmodel.h" #include "maindialog.h" +#include "settings.h" #include "./ui_maindialog.h" extern "C" { @@ -17,7 +18,8 @@ extern "C" { #include "xml.h" } -MainDialog::MainDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MainDialog) +MainDialog::MainDialog(std::vector> &settings, QWidget *parent) + : QDialog(parent), ui(new Ui::MainDialog), m_settings(settings) { ui->setupUi(this); @@ -81,7 +83,7 @@ void MainDialog::activate() theme_free_vector(&openbox_themes); /* Corner Radius */ - ui->cornerRadius->setValue(xml_get_int("/labwc_config/theme/cornerradius")); + ui->cornerRadius->setValue(xml_get_int("/labwc_config/theme/cornerRadius")); /* Drop Shadows */ ui->dropShadows->addItem("no"); @@ -147,16 +149,102 @@ void MainDialog::activate() } } +void setInt(std::vector> &settings, QString name, int value) +{ + std::shared_ptr setting = retrieve(settings, name); + if (setting == nullptr) { + qDebug() << "warning: no settings with name" << name; + return; + } + if (setting->valueType() != LAB_VALUE_TYPE_INT) { + qDebug() << "setInt(): not valid int setting" << name << value; + } + if (value != std::get(setting->value())) { + qDebug() << name << "has changed to" << value; + xml_set_num(name.toStdString().c_str(), value); + } +} + +void setStr(std::vector> &settings, QString name, QString value) +{ + std::shared_ptr setting = retrieve(settings, name); + if (setting == nullptr) { + qDebug() << "warning: no settings with name" << name; + return; + } + if (setting->valueType() != LAB_VALUE_TYPE_STRING) { + qDebug() << "setStr(): not valid string setting" << name << value; + } + if (value != std::get(setting->value())) { + qDebug() << name << "has changed to" << value; + xml_set(name.toStdString().c_str(), value.toStdString().c_str()); + } +} + +/** + * parse_bool() - Parse boolean value of string. + * @string: String to interpret. This check is case-insensitive. + * @default_value: Default value to use if string is not a recognised boolean. + * Use -1 to avoid setting a default value. + * + * Return: 0 for false; 1 for true; -1 for non-boolean + */ +int parseBool(const char *str, int defaultValue) +{ + if (!str) + goto error_not_a_boolean; + else if (!strcasecmp(str, "yes")) + return 1; + else if (!strcasecmp(str, "true")) + return 1; + else if (!strcasecmp(str, "on")) + return 1; + else if (!strcmp(str, "1")) + return 1; + else if (!strcasecmp(str, "no")) + return 0; + else if (!strcasecmp(str, "false")) + return 0; + else if (!strcasecmp(str, "off")) + return 0; + else if (!strcmp(str, "0")) + return 0; +error_not_a_boolean: + qDebug() << str << "is not a boolean value"; + return defaultValue; +} + +// TODO: make this more bool-ish +void setBool(std::vector> &settings, QString name, QString value) +{ + std::shared_ptr setting = retrieve(settings, name); + if (setting == nullptr) { + qDebug() << "warning: no settings with name" << name; + return; + } + if (setting->valueType() != LAB_VALUE_TYPE_BOOL) { + qDebug() << "setBool(): not valid bool setting" << name << value; + } + int boolValue = parseBool(value.toStdString().c_str(), -1); + if (boolValue != std::get(setting->value())) { + qDebug() << name << "has changed to" << value; + xml_set(name.toStdString().c_str(), value.toStdString().c_str()); + } +} + void MainDialog::onApply() { /* ~/.config/labwc/rc.xml */ - xml_set_num("/labwc_config/theme/cornerradius", ui->cornerRadius->value()); - xml_set("/labwc_config/theme/name", ui->openboxTheme->currentText().toLatin1().data()); - xml_set("/labwc_config/theme/dropShadows", ui->dropShadows->currentText().toLatin1().data()); - xml_set("/labwc_config/theme/icon", ui->iconTheme->currentText().toLatin1().data()); - xml_set("/labwc_config/libinput/device/naturalscroll", + setInt(m_settings, "/labwc_config/theme/cornerRadius", ui->cornerRadius->value()); + setStr(m_settings, "/labwc_config/theme/name", + ui->openboxTheme->currentText().toLatin1().data()); + setBool(m_settings, "/labwc_config/theme/dropShadows", + ui->dropShadows->currentText().toLatin1().data()); + setStr(m_settings, "/labwc_config/theme/icon", ui->iconTheme->currentText().toLatin1().data()); + setBool(m_settings, "/labwc_config/libinput/device/naturalScroll", ui->naturalScroll->currentText().toLatin1().data()); - xml_set("/labwc_config/placement/policy", ui->placementPolicy->currentText().toLatin1().data()); + setStr(m_settings, "/labwc_config/placement/policy", + ui->placementPolicy->currentText().toLatin1().data()); xml_save(); /* ~/.config/labwc/environment */ diff --git a/src/maindialog.h b/src/maindialog.h index 149189b..ffd7061 100644 --- a/src/maindialog.h +++ b/src/maindialog.h @@ -2,6 +2,7 @@ #define MAINDIALOG_H #include #include "layoutmodel.h" +#include "settings.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -14,7 +15,7 @@ class MainDialog : public QDialog Q_OBJECT public: - MainDialog(QWidget *parent = nullptr); + MainDialog(std::vector> &settings, QWidget *parent = nullptr); ~MainDialog(); void activate(); QStringList findIconThemes(); @@ -25,6 +26,7 @@ private slots: private: LayoutModel *m_model; + std::vector> &m_settings; void onApply(); diff --git a/src/settings.cpp b/src/settings.cpp index e0e8c40..a3ae5a6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -76,3 +76,13 @@ Setting::Setting(QString name, enum settingFileType fileType, enum settingValueT } } }; + +std::shared_ptr retrieve(std::vector> &settings, QString name) +{ + for (auto &setting : settings) { + if (name == setting->name()) { + return setting; + } + } + return nullptr; +} diff --git a/src/settings.h b/src/settings.h index ebd6310..b81a0af 100644 --- a/src/settings.h +++ b/src/settings.h @@ -2,6 +2,7 @@ #include #include #include +#include "settings.h" enum settingFileType { LAB_FILE_TYPE_UNKNOWN = 0, @@ -50,6 +51,10 @@ class Setting enum settingFileType fileType() const { return m_fileType; } enum settingValueOrigin valueOrigin() const { return m_valueOrigin; } enum settingValueType valueType() const { return m_valueType; } + std::variant value() const { return m_value; } }; void initSettings(std::vector> &settings); + +std::shared_ptr retrieve(std::vector> &settings, QString name); +