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..8d1a51c 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,7 +76,17 @@ int main(int argc, char *argv[]) QTranslator qtTranslator, translator; initLocale(&qtTranslator, &translator); - MainDialog w; + 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(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 2342bb3..d1764ba 100644 --- a/src/maindialog.cpp +++ b/src/maindialog.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -11,6 +10,7 @@ #include "evdev-lst-layouts.h" #include "layoutmodel.h" #include "maindialog.h" +#include "settings.h" #include "./ui_maindialog.h" extern "C" { @@ -18,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); @@ -27,11 +28,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(); @@ -87,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"); @@ -153,39 +149,102 @@ void MainDialog::activate() } } -void MainDialog::initConfig(std::string &config_file) +void setInt(std::vector> &settings, QString name, int value) { - bool success = xml_init(config_file.data()); + 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); + } +} - 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); +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()); } +} - /* 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"); +/** + * 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; +} - xml_save(); +// 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 a78d22a..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,8 +26,8 @@ private slots: private: LayoutModel *m_model; + std::vector> &m_settings; - 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..a3ae5a6 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,88 @@ +#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; + } + } +}; + +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 new file mode 100644 index 0000000..b81a0af --- /dev/null +++ b/src/settings.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include "settings.h" + +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; } + std::variant value() const { return m_value; } +}; + +void initSettings(std::vector> &settings); + +std::shared_ptr retrieve(std::vector> &settings, QString name); +