File indexing completed on 2024-10-13 13:11:20

0001 /*
0002  * SPDX-FileCopyrightText: 2019 Mikhail Zolotukhin <zomial@protonmail.com>
0003  * SPDX-FileCopyrightText: 2019 Nicolas Fella <nicolas.fella@gmx.de>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "gtkconfig.h"
0009 
0010 #include <KColorScheme>
0011 #include <KPluginFactory>
0012 #include <KWindowSystem>
0013 
0014 #include <QDBusConnection>
0015 #include <QDBusMessage>
0016 #include <QFont>
0017 #include <QGuiApplication>
0018 #include <QTimer>
0019 
0020 #include <algorithm>
0021 #include <cmath>
0022 
0023 #include "config_editor/custom_css.h"
0024 #include "config_editor/gsettings.h"
0025 #include "config_editor/gtk2.h"
0026 #include "config_editor/settings_ini.h"
0027 #include "config_editor/xsettings.h"
0028 #include "gsd-xsettings-manager/gsd-xsettings-manager.h"
0029 
0030 K_PLUGIN_CLASS_WITH_JSON(GtkConfig, "gtkconfig.json")
0031 
0032 GtkConfig::GtkConfig(QObject *parent, const QVariantList &)
0033     : KDEDModule(parent)
0034     , configValueProvider(new ConfigValueProvider())
0035     , themePreviewer(new ThemePreviewer(this))
0036     , kdeglobalsConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig()))
0037     , kwinConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kwinrc"))))
0038     , kcmfontsConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kcmfonts"))))
0039     , kcminputConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kcminputrc"))))
0040     , breezeConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("breezerc"))))
0041 {
0042     QDBusConnection dbus = QDBusConnection::sessionBus();
0043     dbus.registerService(QStringLiteral("org.kde.GtkConfig"));
0044     dbus.registerObject(QStringLiteral("/GtkConfig"), this, QDBusConnection::ExportScriptableSlots);
0045 
0046     if (qgetenv("GTK_USE_PORTAL") != "1" && KWindowSystem::isPlatformWayland()) {
0047         m_gsdXsettingsManager = new GSDXSettingsManager(this);
0048     }
0049 
0050     connect(kdeglobalsConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onKdeglobalsSettingsChange);
0051     connect(kwinConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onKWinSettingsChange);
0052     connect(kcmfontsConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onKCMFontsSettingsChange);
0053     connect(kcminputConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onKCMInputSettingsChange);
0054     connect(breezeConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onBreezeSettingsChange);
0055 
0056     Gtk2ConfigEditor::removeLegacyStrings();
0057     applyAllSettings();
0058 }
0059 
0060 GtkConfig::~GtkConfig()
0061 {
0062     QDBusConnection dbus = QDBusConnection::sessionBus();
0063     dbus.unregisterService(QStringLiteral("org.kde.GtkConfig"));
0064     dbus.unregisterObject(QStringLiteral("/GtkConfig"));
0065 }
0066 
0067 void GtkConfig::setGtkTheme(const QString &themeName) const
0068 {
0069     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-theme-name"), themeName);
0070     GSettingsEditor::setValue("gtk-theme", themeName);
0071     SettingsIniEditor::setValue(QStringLiteral("gtk-theme-name"), themeName);
0072     XSettingsEditor::setValue(QStringLiteral("Net/ThemeName"), themeName);
0073 
0074     // Window decorations are part of the theme, in case of Breeze we inject custom ones from KWin
0075     setWindowDecorationsAppearance();
0076 }
0077 
0078 QString GtkConfig::gtkTheme() const
0079 {
0080     return SettingsIniEditor::value(QStringLiteral("gtk-theme-name"));
0081 }
0082 
0083 void GtkConfig::showGtkThemePreview(const QString &themeName) const
0084 {
0085     themePreviewer->showGtk3App(themeName);
0086 }
0087 
0088 void GtkConfig::setFont() const
0089 {
0090     const QString configFontName = configValueProvider->fontName();
0091     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-font-name"), configFontName);
0092     GSettingsEditor::setValue("font-name", configFontName);
0093     SettingsIniEditor::setValue(QStringLiteral("gtk-font-name"), configFontName);
0094     XSettingsEditor::setValue(QStringLiteral("Gtk/FontName"), configFontName);
0095 }
0096 
0097 void GtkConfig::setIconTheme() const
0098 {
0099     const QString iconThemeName = configValueProvider->iconThemeName();
0100     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-icon-theme-name"), iconThemeName);
0101     GSettingsEditor::setValue("icon-theme", iconThemeName);
0102     SettingsIniEditor::setValue(QStringLiteral("gtk-icon-theme-name"), iconThemeName);
0103     XSettingsEditor::setValue(QStringLiteral("Net/IconThemeName"), iconThemeName);
0104 }
0105 
0106 void GtkConfig::setCursorTheme() const
0107 {
0108     const QString cursorThemeName = configValueProvider->cursorThemeName();
0109     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName);
0110     GSettingsEditor::setValue("cursor-theme", cursorThemeName);
0111     SettingsIniEditor::setValue(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName);
0112     XSettingsEditor::setValue(QStringLiteral("Gtk/CursorThemeName"), cursorThemeName);
0113 }
0114 
0115 void GtkConfig::setCursorSize() const
0116 {
0117     const int cursorSize = configValueProvider->cursorSize();
0118     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-cursor-theme-size"), cursorSize);
0119     GSettingsEditor::setValue("cursor-size", cursorSize);
0120     SettingsIniEditor::setValue(QStringLiteral("gtk-cursor-theme-size"), cursorSize);
0121     XSettingsEditor::setValue(QStringLiteral("Gtk/CursorThemeSize"), cursorSize);
0122 }
0123 
0124 void GtkConfig::setIconsOnButtons() const
0125 {
0126     const bool iconsOnButtonsConfigValue = configValueProvider->iconsOnButtons();
0127     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue);
0128     // Deprecated in GTK 4
0129     SettingsIniEditor::setValue(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue, 3);
0130     XSettingsEditor::setValue(QStringLiteral("Gtk/ButtonImages"), iconsOnButtonsConfigValue);
0131 }
0132 
0133 void GtkConfig::setIconsInMenus() const
0134 {
0135     const bool iconsInMenusConfigValue = configValueProvider->iconsInMenus();
0136     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue);
0137     // Deprecated in GTK 4
0138     SettingsIniEditor::setValue(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue, 3);
0139     XSettingsEditor::setValue(QStringLiteral("Gtk/MenuImages"), iconsInMenusConfigValue);
0140 }
0141 
0142 void GtkConfig::setToolbarStyle() const
0143 {
0144     const int toolbarStyle = configValueProvider->toolbarStyle();
0145     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-toolbar-style"), toolbarStyle);
0146     GSettingsEditor::setValueAsEnum("toolbar-style", toolbarStyle);
0147     // Deprecated in GTK 4
0148     SettingsIniEditor::setValue(QStringLiteral("gtk-toolbar-style"), toolbarStyle, 3);
0149     XSettingsEditor::setValue(QStringLiteral("Gtk/ToolbarStyle"), toolbarStyle);
0150 }
0151 
0152 void GtkConfig::setScrollbarBehavior() const
0153 {
0154     const bool scrollbarBehavior = configValueProvider->scrollbarBehavior();
0155     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-primary-button-warps-slider"), scrollbarBehavior);
0156     SettingsIniEditor::setValue(QStringLiteral("gtk-primary-button-warps-slider"), scrollbarBehavior);
0157     XSettingsEditor::setValue(QStringLiteral("Gtk/PrimaryButtonWarpsSlider"), scrollbarBehavior);
0158 }
0159 
0160 void GtkConfig::setDarkThemePreference() const
0161 {
0162     const bool preferDarkTheme = configValueProvider->preferDarkTheme();
0163     SettingsIniEditor::setValue(QStringLiteral("gtk-application-prefer-dark-theme"), preferDarkTheme);
0164     // https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/blob/master/headers/gdesktop-enums.h
0165     GSettingsEditor::setValueAsEnum("color-scheme",
0166                                     preferDarkTheme ? 1 /*G_DESKTOP_COLOR_SCHEME_PREFER_DARK*/ : 0 /*G_DESKTOP_COLOR_SCHEME_DEFAULT*/,
0167                                     "org.gnome.desktop.interface");
0168 }
0169 
0170 void GtkConfig::setWindowDecorationsAppearance() const
0171 {
0172     if (gtkTheme() == QStringLiteral("Breeze")) { // Only Breeze GTK supports custom decoration buttons
0173         const auto windowDecorationsButtonsImages = configValueProvider->windowDecorationsButtonsImages();
0174         CustomCssEditor::setCustomClientSideDecorations(windowDecorationsButtonsImages);
0175     } else {
0176         CustomCssEditor::disableCustomClientSideDecorations();
0177     }
0178 }
0179 
0180 void GtkConfig::setWindowDecorationsButtonsOrder() const
0181 {
0182     const QString windowDecorationsButtonOrder = configValueProvider->windowDecorationsButtonsOrder();
0183     GSettingsEditor::setValue("button-layout", windowDecorationsButtonOrder, "org.gnome.desktop.wm.preferences");
0184     SettingsIniEditor::setValue(QStringLiteral("gtk-decoration-layout"), windowDecorationsButtonOrder);
0185     XSettingsEditor::setValue(QStringLiteral("Gtk/DecorationLayout"), windowDecorationsButtonOrder);
0186 }
0187 
0188 void GtkConfig::setEnableAnimations() const
0189 {
0190     const bool enableAnimations = configValueProvider->enableAnimations();
0191     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-enable-animations"), enableAnimations);
0192     GSettingsEditor::setValue("enable-animations", enableAnimations);
0193     SettingsIniEditor::setValue(QStringLiteral("gtk-enable-animations"), enableAnimations);
0194     XSettingsEditor::setValue(QStringLiteral("Gtk/EnableAnimations"), enableAnimations);
0195     if (m_gsdXsettingsManager) {
0196         m_gsdXsettingsManager->enableAnimationsChanged();
0197     }
0198 }
0199 
0200 void GtkConfig::setGlobalScale() const
0201 {
0202     const unsigned scaleFactor = configValueProvider->x11GlobalScaleFactor();
0203     XSettingsEditor::setValue(QStringLiteral("Gdk/WindowScalingFactor"), scaleFactor);
0204     GSettingsEditor::setValue("scaling-factor", scaleFactor); // For IntelliJ IDEA
0205 }
0206 
0207 void GtkConfig::setTextScale() const
0208 {
0209     const double x11Scale = configValueProvider->x11GlobalScaleFactor();
0210     const int x11ScaleIntegerPart = int(x11Scale);
0211 
0212     const int forceFontDpi = configValueProvider->fontDpi();
0213 
0214     int x11TextDpiAbsolute = 96 * 1024;
0215     double waylandTextScaleFactor = 1.0;
0216 
0217     if (forceFontDpi == 0) {
0218         x11TextDpiAbsolute = (96 * 1024) * x11Scale;
0219     } else {
0220         x11TextDpiAbsolute = (forceFontDpi * 1024);
0221 
0222         if (!KWindowSystem::isPlatformX11()) {
0223             x11TextDpiAbsolute *= x11Scale;
0224         }
0225 
0226         waylandTextScaleFactor = double(forceFontDpi) / 96.0;
0227         waylandTextScaleFactor = std::clamp(waylandTextScaleFactor, 0.5, 3.0);
0228     }
0229 
0230     XSettingsEditor::unsetValue(QStringLiteral("Xft/DPI"));
0231     SettingsIniEditor::setValue(QStringLiteral("gtk-xft-dpi"), x11TextDpiAbsolute);
0232     XSettingsEditor::setValue(QStringLiteral("Gdk/UnscaledDPI"), x11TextDpiAbsolute / x11ScaleIntegerPart);
0233     GSettingsEditor::setValue("text-scaling-factor", waylandTextScaleFactor);
0234 }
0235 
0236 void GtkConfig::setColors() const
0237 {
0238     CustomCssEditor::addGtkModule(QStringLiteral("colorreload-gtk-module"));
0239     if (m_gsdXsettingsManager) {
0240         m_gsdXsettingsManager->modulesChanged();
0241     }
0242     // modulesChanged signal will take some time to reach a GTK app, so explicitly wait a moment
0243     QTimer::singleShot(200, this, [this] {
0244         const QMap<QString, QColor> colors = configValueProvider->colors();
0245         CustomCssEditor::setColors(colors);
0246     });
0247 }
0248 
0249 void GtkConfig::applyAllSettings() const
0250 {
0251     setFont();
0252     setIconTheme();
0253     setCursorTheme();
0254     setCursorSize();
0255     setIconsOnButtons();
0256     setIconsInMenus();
0257     setToolbarStyle();
0258     setScrollbarBehavior();
0259     setDarkThemePreference();
0260     setWindowDecorationsAppearance();
0261     setWindowDecorationsButtonsOrder();
0262     setEnableAnimations();
0263     setGlobalScale();
0264     setTextScale();
0265     setColors();
0266 }
0267 
0268 void GtkConfig::onKdeglobalsSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0269 {
0270     if (group.name() == QStringLiteral("KDE")) {
0271         if (names.contains(QByteArrayLiteral("AnimationDurationFactor"))) {
0272             setEnableAnimations();
0273         }
0274         if (names.contains(QByteArrayLiteral("ShowIconsInMenuItems"))) {
0275             setIconsInMenus();
0276         }
0277         if (names.contains(QByteArrayLiteral("ShowIconsOnPushButtons"))) {
0278             setIconsOnButtons();
0279         }
0280         if (names.contains(QByteArrayLiteral("ScrollbarLeftClickNavigatesByPage"))) {
0281             setScrollbarBehavior();
0282         }
0283     } else if (group.name() == QStringLiteral("Icons")) {
0284         if (names.contains(QByteArrayLiteral("Theme"))) {
0285             setIconTheme();
0286         }
0287     } else if (group.name() == QStringLiteral("General")) {
0288         if (names.contains(QByteArrayLiteral("font"))) {
0289             setFont();
0290         }
0291         if (names.contains(QByteArrayLiteral("ColorScheme")) || names.contains(QByteArrayLiteral("AccentColor"))) {
0292             setColors();
0293             setDarkThemePreference();
0294             setWindowDecorationsAppearance(); // Decorations' color can depend on the current color scheme
0295         }
0296     } else if (group.name() == QStringLiteral("KScreen")) {
0297         if (names.contains(QByteArrayLiteral("ScaleFactor"))) {
0298             setGlobalScale();
0299             // setTextScale() will be called in onKCMFontsSettingsChange
0300         }
0301     } else if (group.name() == QStringLiteral("Toolbar style")) {
0302         if (names.contains(QByteArrayLiteral("ToolButtonStyle"))) {
0303             setToolbarStyle();
0304         }
0305     }
0306 }
0307 
0308 void GtkConfig::onKWinSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0309 {
0310     if (group.name() == QStringLiteral("org.kde.kdecoration2")) {
0311         if (names.contains(QByteArrayLiteral("ButtonsOnRight")) //
0312             || names.contains(QByteArrayLiteral("ButtonsOnLeft"))) {
0313             setWindowDecorationsButtonsOrder();
0314         }
0315         if (names.contains(QByteArrayLiteral("theme"))) {
0316             setWindowDecorationsAppearance();
0317         }
0318     } else if (group.name() == QStringLiteral("Xwayland")) {
0319         if (names.contains(QByteArrayLiteral("Scale"))) {
0320             setGlobalScale();
0321             setTextScale();
0322         }
0323     }
0324 }
0325 
0326 void GtkConfig::onKCMFontsSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0327 {
0328     if (group.name() == QStringLiteral("General")) {
0329         if (names.contains("forceFontDPI") || names.contains("forceFontDPIWayland")) {
0330             setTextScale();
0331         }
0332     }
0333 }
0334 
0335 void GtkConfig::onKCMInputSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0336 {
0337     if (group.name() == QStringLiteral("Mouse")) {
0338         if (names.contains("cursorTheme")) {
0339             setCursorTheme();
0340         }
0341         if (names.contains("cursorSize")) {
0342             setCursorSize();
0343         }
0344     }
0345 }
0346 
0347 void GtkConfig::onBreezeSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0348 {
0349     if (group.name() == QStringLiteral("Common") //
0350         && names.contains("OutlineCloseButton")) {
0351         setWindowDecorationsAppearance();
0352     }
0353 }
0354 
0355 #include "gtkconfig.moc"