diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ${ECM_MODULE_PATH}) find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Widgets Svg Test) -find_package(KF5 REQUIRED COMPONENTS I18n KIO ConfigWidgets NewStuff Archive KCMUtils IconThemes) +find_package(KF5 REQUIRED COMPONENTS I18n KIO ConfigWidgets NewStuff Archive KCMUtils IconThemes KDELibs4Support) find_package(X11 REQUIRED) find_package(GTK3 REQUIRED) find_package(GSettingSchemas REQUIRED) @@ -66,5 +66,6 @@ add_subdirectory(gtk3proxies) add_subdirectory(icons) add_subdirectory(tests) +add_subdirectory(kded-module) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kded-module/CMakeLists.txt b/kded-module/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/kded-module/CMakeLists.txt @@ -0,0 +1,29 @@ +set(kscreen_daemon_SRCS + gtkconfig.cpp + configeditor.cpp + configvalueprovider.cpp +) + +add_library(gtkconfig MODULE ${kscreen_daemon_SRCS}) + +target_include_directories(gtkconfig + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${GTK3_INCLUDE_DIRS} +) + +target_link_libraries(gtkconfig + Qt5::Gui + Qt5::DBus + KF5::CoreAddons + KF5::ConfigCore + KF5::DBusAddons + KF5::IconThemes + KF5::KDELibs4Support + ${GIO2_LIBRARY} + ${GLIB2_LIBRARY} + ${GTK3_LIBRARY} + ${GOBJECT2_LIBRARY} +) + +install(TARGETS gtkconfig DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded) diff --git a/kded-module/configeditor.h b/kded-module/configeditor.h new file mode 100644 --- /dev/null +++ b/kded-module/configeditor.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class QString; +class QFile; + +class ConfigEditor +{ +public: + ConfigEditor() = default; + + void setGtk2ConfigValue(const QString ¶mName, const QString ¶mValue); + void setGtk3ConfigValueDconf(const QString ¶mName, const QString ¶mValue); + void setGtk3ConfigValueSettingsIni(const QString ¶mName, const QString ¶mValue); + void setGtk3ConfigValueXSettingsd(const QString ¶mName, const QString ¶mValue); + +private: + void replaceValueInGtkrcContents(QString >krcContents, const QString ¶mName, const QString ¶mValue); + void replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString ¶mName, const QString ¶mValue); + + void reloadGtk2Apps(); + void reloadXSettingsd(); + + QString readFileContents(QFile >krc); + pid_t getPidOfXSettingsd(); +}; diff --git a/kded-module/configeditor.cpp b/kded-module/configeditor.cpp new file mode 100644 --- /dev/null +++ b/kded-module/configeditor.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2019 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#undef signals +#include +#include +#define signals Q_SIGNALS + +#include "configeditor.h" + +void ConfigEditor::setGtk3ConfigValueDconf(const QString ¶mName, const QString ¶mValue) +{ + gtk_init(nullptr, nullptr); + g_autoptr(GSettings) gsettings = g_settings_new("org.gnome.desktop.interface"); + g_settings_set_string(gsettings, paramName.toUtf8().constData(), paramValue.toUtf8().constData()); +} + +void ConfigEditor::setGtk3ConfigValueSettingsIni(const QString ¶mName, const QString ¶mValue) +{ + using qsp = QStandardPaths; + QString configLocation(qsp::writableLocation(qsp::GenericConfigLocation)); + QString gtk3ConfigPath(configLocation + "/gtk-3.0/settings.ini"); + + KSharedConfig::Ptr gtk3Config = KSharedConfig::openConfig(gtk3ConfigPath, KConfig::NoGlobals); + KConfigGroup group(gtk3Config, QStringLiteral("Settings")); + + group.writeEntry(paramName, paramValue); + group.sync(); +} + +void ConfigEditor::setGtk3ConfigValueXSettingsd(const QString ¶mName, const QString ¶mValue) +{ + using qsp = QStandardPaths; + QString configLocation(qsp::writableLocation(qsp::GenericConfigLocation)); + + QDir xsettingsdPath(configLocation + "/xsettingsd"); + if (!xsettingsdPath.exists()) { + xsettingsdPath.mkpath("."); + } + + QString xSettingsdConfigPath(xsettingsdPath.path() + "/xsettingsd.conf"); + + QFile xSettingsdConfig(xSettingsdConfigPath); + QString xSettingsdConfigContents(readFileContents(xSettingsdConfig)); + replaceValueInXSettingsdContents(xSettingsdConfigContents, paramName, paramValue); + xSettingsdConfig.remove(); + xSettingsdConfig.open(QIODevice::WriteOnly | QIODevice::Text); + xSettingsdConfig.write(xSettingsdConfigContents.toUtf8()); + reloadXSettingsd(); +} + +void ConfigEditor::setGtk2ConfigValue(const QString ¶mName, const QString ¶mValue) +{ + QString gtkrcPath(QDir::homePath() + "/.gtkrc-2.0"); + QFile gtkrc(gtkrcPath); + QString gtkrcContents(readFileContents(gtkrc)); + replaceValueInGtkrcContents(gtkrcContents, paramName, paramValue); + gtkrc.remove(); + gtkrc.open(QIODevice::WriteOnly | QIODevice::Text); + gtkrc.write(gtkrcContents.toUtf8()); + reloadGtk2Apps(); +} + +QString ConfigEditor::readFileContents(QFile &file) +{ + if (file.open(QIODevice::ReadWrite | QIODevice::Text)) { + return file.readAll(); + } else { + return ""; + } +} + +void ConfigEditor::replaceValueInGtkrcContents(QString >krcContents, const QString ¶mName, const QString ¶mValue) +{ + QRegularExpression regExp(paramName + "=[^\n]*($|\n)"); + + static const QStringList nonStringProperties{ + QStringLiteral("gtk-toolbar-style"), + QStringLiteral("gtk-menu-images"), + QStringLiteral("gtk-button-images"), + QStringLiteral("gtk-primary-button-warps-slider"), + }; + + QString newConfigString; + if (nonStringProperties.contains(paramName)) { + newConfigString = paramName + "=" + paramValue + "\n"; + } else { + newConfigString = paramName + "=\"" + paramValue + "\"\n"; + } + + if (gtkrcContents.contains(regExp)) { + gtkrcContents.replace(regExp, newConfigString); + } else { + gtkrcContents = newConfigString + "\n" + gtkrcContents; + } +} + +void ConfigEditor::replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString ¶mName, const QString ¶mValue) +{ + QRegularExpression regExp(paramName + " [^\n]*($|\n)"); + + static const QStringList nonStringProperties{ + QStringLiteral("Gtk/ButtonImages"), + QStringLiteral("Gtk/MenuImages"), + QStringLiteral("Gtk/ToolbarStyle"), + }; + + QString newConfigString; + if (nonStringProperties.contains(paramName)) { + newConfigString = paramName + " " + paramValue + "\n"; + } else { + newConfigString = paramName + " \"" + paramValue + "\"\n"; + } + + if (xSettingsdContents.contains(regExp)) { + xSettingsdContents.replace(regExp, newConfigString); + } else { + xSettingsdContents = newConfigString + "\n" + xSettingsdContents; + } +} + +void ConfigEditor::reloadGtk2Apps() +{ + QProcess::startDetached(QStandardPaths::findExecutable(QStringLiteral("reload_gtk_apps"))); +} + +void ConfigEditor::reloadXSettingsd() +{ + pid_t pidOfXSettingsd(getPidOfXSettingsd()); + if (pidOfXSettingsd == 0) { + QProcess::startDetached(QStandardPaths::findExecutable(QStringLiteral("xsettingsd"))); + } else { + kill(pidOfXSettingsd, SIGHUP); + } +} + +pid_t ConfigEditor::getPidOfXSettingsd() +{ + char line[512]; + FILE *cmd(popen("pidof -s xsettingsd", "r")); + fgets(line, 512, cmd); + pclose(cmd); + return std::atoi(line); +} diff --git a/kded-module/configvalueprovider.h b/kded-module/configvalueprovider.h new file mode 100644 --- /dev/null +++ b/kded-module/configvalueprovider.h @@ -0,0 +1,27 @@ +#pragma once + +class QString; +class QFont; + +class ConfigValueProvider +{ +public: + enum class ToolbarStyleNotation { + XSETTINGSD = 0, + SETTINGS_INI, + DCONF + }; + + ConfigValueProvider() = default; + + QString getConfigFontName(const QFont &font); + QString getConfigIconThemeName(); + QString getConfigCursorThemeName(); + QString getIconsOnButtonsConfigValue(); + QString getIconsInMenusConfigValue(); + QString getToolbarStyle(ToolbarStyleNotation notation); + +private: + QString getToolbarStyleInDesiredNotation(const QString &kdeConfigValue, ToolbarStyleNotation notation); + +}; diff --git a/kded-module/configvalueprovider.cpp b/kded-module/configvalueprovider.cpp new file mode 100644 --- /dev/null +++ b/kded-module/configvalueprovider.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2019 Mikhail Zolotukhin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include +#include +#include + +#include "configvalueprovider.h" + +QString ConfigValueProvider::getConfigFontName(const QFont &font) +{ + return font.family() + ' ' + font.styleName() + ' ' + QString::number(font.pointSize()); +} + +QString ConfigValueProvider::getConfigIconThemeName() +{ + KIconTheme *newIconTheme(KIconLoader::global()->theme()); + return newIconTheme->internalName(); +} + +QString ConfigValueProvider::getConfigCursorThemeName() +{ + KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kcminputrc"))); + KConfigGroup configGroup(config->group(QStringLiteral("Mouse"))); + return configGroup.readEntry(QStringLiteral("cursorTheme")); +} + +QString ConfigValueProvider::getIconsOnButtonsConfigValue() +{ + KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals)); + KConfigGroup configGroup(config->group(QStringLiteral("KDE"))); + QString kdeConfigValue(configGroup.readEntry(QStringLiteral("ShowIconsOnPushButtons"))); + + if (kdeConfigValue == QStringLiteral("true")) { + return QStringLiteral("1"); + } else { + return QStringLiteral("0"); + } +} + +QString ConfigValueProvider::getIconsInMenusConfigValue() +{ + KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals)); + KConfigGroup configGroup(config->group(QStringLiteral("KDE"))); + QString kdeConfigValue(configGroup.readEntry(QStringLiteral("ShowIconsInMenuItems"))); + + if (kdeConfigValue == QStringLiteral("true")) { + return QStringLiteral("1"); + } else { + return QStringLiteral("0"); + } +} + +QString ConfigValueProvider::getToolbarStyle(ConfigValueProvider::ToolbarStyleNotation notation) +{ + KSharedConfigPtr config(KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals)); + KConfigGroup configGroup(config->group(QStringLiteral("Toolbar style"))); + QString kdeConfigValue(configGroup.readEntry(QStringLiteral("ToolButtonStyle"))); + return getToolbarStyleInDesiredNotation(kdeConfigValue, notation); +} + +QString ConfigValueProvider::getToolbarStyleInDesiredNotation(const QString &kdeConfigValue, ConfigValueProvider::ToolbarStyleNotation notation) +{ + QStringList toolbarStyles; + if (notation == ToolbarStyleNotation::SETTINGS_INI) { + toolbarStyles.append({"GTK_TOOLBAR_ICONS", "GTK_TOOLBAR_TEXT", "GTK_TOOLBAR_BOTH_HORIZ", "GTK_TOOLBAR_BOTH"}); + } else if (notation == ToolbarStyleNotation::XSETTINGSD) { + toolbarStyles.append({"0", "1", "3", "2"}); + } else { + toolbarStyles.append({"icons", "text", "both-horiz", "both"}); + } + + if (kdeConfigValue == QStringLiteral("NoText")) { + return toolbarStyles[0]; + } else if (kdeConfigValue == QStringLiteral("TextOnly")) { + return toolbarStyles[1]; + } else if (kdeConfigValue == QStringLiteral("TextBesideIcon")) { + return toolbarStyles[2]; + } else { + return toolbarStyles[3]; + } +} diff --git a/kded-module/gtkconfig.h b/kded-module/gtkconfig.h new file mode 100644 --- /dev/null +++ b/kded-module/gtkconfig.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019 Mikhail Zolotukhin + * Copyright (C) 2019 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#include + +#include "configeditor.h" +#include "configvalueprovider.h" + +class Q_DECL_EXPORT GtkConfig : public KDEDModule +{ + Q_CLASSINFO("D-Bus Interface", "org.kde.gtkconfig") + Q_OBJECT + +public: + GtkConfig(QObject *parent, const QVariantList& args); + + void setFont(const QFont &font); + void setIconTheme(int iconGroup); + void setCursorTheme(); + void setIconsOnButtons(); + void setIconsInMenus(); + void setToolbarStyle(); + +public slots: + void onGlobalSettingsChange(int changeType, int arg); + +private: + QScopedPointer configValueProvider; + QScopedPointer configEditor; +}; diff --git a/kded-module/gtkconfig.cpp b/kded-module/gtkconfig.cpp new file mode 100644 --- /dev/null +++ b/kded-module/gtkconfig.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 Mikhail Zolotukhin + * Copyright (C) 2019 Nicolas Fella + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "gtkconfig.h" +#include "configvalueprovider.h" + +K_PLUGIN_CLASS_WITH_JSON(GtkConfig, "gtkconfig.json") + +GtkConfig::GtkConfig(QObject *parent, const QVariantList&) : + KDEDModule(parent), configValueProvider(new ConfigValueProvider()), configEditor(new ConfigEditor()) +{ + connect(qGuiApp, &QGuiApplication::fontChanged, this, &GtkConfig::setFont); + connect(KIconLoader::global(), &KIconLoader::iconChanged, this, &GtkConfig::setIconTheme); + QDBusConnection::sessionBus().connect(QString(), + QStringLiteral("/KGlobalSettings"), + QStringLiteral("org.kde.KGlobalSettings"), + QStringLiteral("notifyChange"), + this, + SLOT(onGlobalSettingsChange(int,int))); +} + +void GtkConfig::setFont(const QFont &font) +{ + const QString configFontName(configValueProvider->getConfigFontName(font)); + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-font-name"), configFontName); + configEditor->setGtk3ConfigValueDconf(QStringLiteral("font-name"), configFontName); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-font-name"), configFontName); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Gtk/FontName"), configFontName); +} + +void GtkConfig::setIconTheme(int iconGroup) +{ + if (iconGroup == KIconLoader::Group::Desktop) { // This is needed to update icons only once + QString iconThemeName(configValueProvider->getConfigIconThemeName()); + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-icon-theme-name"), iconThemeName); + configEditor->setGtk3ConfigValueDconf(QStringLiteral("icon-theme"), iconThemeName); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-icon-theme-name"), iconThemeName); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Net/IconThemeName"), iconThemeName); + } +} + +void GtkConfig::setCursorTheme() +{ + QString cursorThemeName(configValueProvider->getConfigCursorThemeName()); + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName); + configEditor->setGtk3ConfigValueDconf(QStringLiteral("cursor-theme"), cursorThemeName); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Gtk/CursorThemeName"), cursorThemeName); +} + +void GtkConfig::setIconsOnButtons() +{ + QString iconsOnButtonsConfigValue(configValueProvider->getIconsOnButtonsConfigValue()); + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Gtk/ButtonImages"), iconsOnButtonsConfigValue); +} + +void GtkConfig::setIconsInMenus() +{ + QString iconsInMenusConfigValue(configValueProvider->getIconsInMenusConfigValue()); + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Gtk/MenuImages"), iconsInMenusConfigValue); +} + +void GtkConfig::setToolbarStyle() +{ + using ToolbarStyleNotation = ConfigValueProvider::ToolbarStyleNotation; + + QString toolbarStyleSettingsFile(configValueProvider->getToolbarStyle(ToolbarStyleNotation::SETTINGS_INI)); + QString toolbarStyleDConf(configValueProvider->getToolbarStyle(ToolbarStyleNotation::DCONF)); + QString toolbarStyleXSettingsd(configValueProvider->getToolbarStyle(ToolbarStyleNotation::XSETTINGSD)); + + configEditor->setGtk2ConfigValue(QStringLiteral("gtk-toolbar-style"), toolbarStyleSettingsFile); + configEditor->setGtk3ConfigValueDconf(QStringLiteral("toolbar-style"), toolbarStyleDConf); + configEditor->setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-toolbar-style"), toolbarStyleSettingsFile); + configEditor->setGtk3ConfigValueXSettingsd(QStringLiteral("Gtk/ToolbarStyle"), toolbarStyleXSettingsd); +} + +void GtkConfig::onGlobalSettingsChange(int changeType, int arg) +{ + using ChangeType = KGlobalSettings::ChangeType; + using SettingsCategory = KGlobalSettings::SettingsCategory; + + if (changeType == ChangeType::CursorChanged) { + setCursorTheme(); + } else if (changeType == ChangeType::SettingsChanged && arg == SettingsCategory::SETTINGS_STYLE) { + // Since KGlobalSettings::ChangeType::ToolbarStyleChanged is not working, + // we use the style settings category as a whole to change the respective settings + setIconsOnButtons(); + setIconsInMenus(); + setToolbarStyle(); + } +} +#include "gtkconfig.moc" + diff --git a/kded-module/gtkconfig.json b/kded-module/gtkconfig.json new file mode 100644 --- /dev/null +++ b/kded-module/gtkconfig.json @@ -0,0 +1,15 @@ +{ + "KPlugin": { + "Description": "GTK config management", + "Icon": "preferences-system-power-management", + "Name": "Plasma GTKd", + "ServiceTypes": [ + "KDEDModule" + ], + "Version": "0.1" + }, + "X-KDE-Kded-autoload": true, + "X-KDE-Kded-load-on-demand": false, + "X-KDE-Kded-phase": 1 +} +