File indexing completed on 2024-04-28 09:21: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::setGtk2Theme(const QString &themeName, const bool preferDarkTheme) const
0068 {
0069     // GTK2 does not support using dark variant automatically, so we have to get dark preference and switch based on that
0070     QString possiblydarkthemeName = themeName;
0071     if (themeName == QLatin1String("Breeze") && preferDarkTheme) {
0072         possiblydarkthemeName = QStringLiteral("Breeze-Dark");
0073     }
0074 
0075     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-theme-name"), possiblydarkthemeName);
0076     XSettingsEditor::setValue(QStringLiteral("Net/ThemeName"), possiblydarkthemeName);
0077 }
0078 
0079 void GtkConfig::setGtkTheme(const QString &themeName) const
0080 {
0081     setGtk2Theme(themeName, configValueProvider->preferDarkTheme());
0082     GSettingsEditor::setValue("gtk-theme", themeName);
0083     SettingsIniEditor::setValue(QStringLiteral("gtk-theme-name"), themeName);
0084 
0085     // Window decorations are part of the theme, in case of Breeze we inject custom ones from KWin
0086     setWindowDecorationsAppearance();
0087 }
0088 
0089 QString GtkConfig::gtkTheme() const
0090 {
0091     return SettingsIniEditor::value(QStringLiteral("gtk-theme-name"));
0092 }
0093 
0094 void GtkConfig::showGtkThemePreview(const QString &themeName) const
0095 {
0096     themePreviewer->showGtk3App(themeName);
0097 }
0098 
0099 void GtkConfig::setFont() const
0100 {
0101     const QString configFontName = configValueProvider->fontName();
0102     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-font-name"), configFontName);
0103     GSettingsEditor::setValue("font-name", configFontName);
0104     SettingsIniEditor::setValue(QStringLiteral("gtk-font-name"), configFontName);
0105     XSettingsEditor::setValue(QStringLiteral("Gtk/FontName"), configFontName);
0106 }
0107 
0108 void GtkConfig::setIconTheme() const
0109 {
0110     const QString iconThemeName = configValueProvider->iconThemeName();
0111     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-icon-theme-name"), iconThemeName);
0112     GSettingsEditor::setValue("icon-theme", iconThemeName);
0113     SettingsIniEditor::setValue(QStringLiteral("gtk-icon-theme-name"), iconThemeName);
0114     XSettingsEditor::setValue(QStringLiteral("Net/IconThemeName"), iconThemeName);
0115 }
0116 
0117 void GtkConfig::setSoundTheme() const
0118 {
0119     const QString soundThemeName = configValueProvider->soundThemeName();
0120     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-sound-theme-name"), soundThemeName);
0121     GSettingsEditor::setValue("theme-name", soundThemeName, "org.gnome.desktop.sound");
0122     SettingsIniEditor::setValue(QStringLiteral("gtk-sound-theme-name"), soundThemeName);
0123     XSettingsEditor::setValue(QStringLiteral("Net/SoundThemeName"), soundThemeName);
0124 }
0125 
0126 void GtkConfig::setEventSoundsEnabled() const
0127 {
0128     const bool soundsEnabled = configValueProvider->eventSoundsEnabled();
0129     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-enable-event-sounds"), soundsEnabled);
0130     GSettingsEditor::setValue("event-sounds", soundsEnabled, "org.gnome.desktop.sound");
0131     SettingsIniEditor::setValue(QStringLiteral("gtk-enable-event-sounds"), soundsEnabled);
0132     XSettingsEditor::setValue(QStringLiteral("Net/EnableEventSounds"), soundsEnabled);
0133 }
0134 
0135 void GtkConfig::setCursorTheme() const
0136 {
0137     const QString cursorThemeName = configValueProvider->cursorThemeName();
0138     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName);
0139     GSettingsEditor::setValue("cursor-theme", cursorThemeName);
0140     SettingsIniEditor::setValue(QStringLiteral("gtk-cursor-theme-name"), cursorThemeName);
0141     XSettingsEditor::setValue(QStringLiteral("Gtk/CursorThemeName"), cursorThemeName);
0142 }
0143 
0144 void GtkConfig::setCursorSize() const
0145 {
0146     qreal xwaylandScale = 1.0;
0147     if (KWindowSystem::isPlatformWayland()) {
0148         xwaylandScale = configValueProvider->x11GlobalScaleFactor();
0149     }
0150 
0151     const int cursorSize = configValueProvider->cursorSize();
0152     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-cursor-theme-size"), cursorSize);
0153     GSettingsEditor::setValue("cursor-size", cursorSize);
0154     SettingsIniEditor::setValue(QStringLiteral("gtk-cursor-theme-size"), cursorSize);
0155     XSettingsEditor::setValue(QStringLiteral("Gtk/CursorThemeSize"), int(cursorSize * xwaylandScale));
0156 }
0157 
0158 void GtkConfig::setIconsOnButtons() const
0159 {
0160     const bool iconsOnButtonsConfigValue = configValueProvider->iconsOnButtons();
0161     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue);
0162     // Deprecated in GTK 4
0163     SettingsIniEditor::setValue(QStringLiteral("gtk-button-images"), iconsOnButtonsConfigValue, 3);
0164     XSettingsEditor::setValue(QStringLiteral("Gtk/ButtonImages"), iconsOnButtonsConfigValue);
0165 }
0166 
0167 void GtkConfig::setIconsInMenus() const
0168 {
0169     const bool iconsInMenusConfigValue = configValueProvider->iconsInMenus();
0170     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue);
0171     // Deprecated in GTK 4
0172     SettingsIniEditor::setValue(QStringLiteral("gtk-menu-images"), iconsInMenusConfigValue, 3);
0173     XSettingsEditor::setValue(QStringLiteral("Gtk/MenuImages"), iconsInMenusConfigValue);
0174 }
0175 
0176 void GtkConfig::setToolbarStyle() const
0177 {
0178     const int toolbarStyle = configValueProvider->toolbarStyle();
0179     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-toolbar-style"), toolbarStyle);
0180     GSettingsEditor::setValueAsEnum("toolbar-style", toolbarStyle);
0181     // Deprecated in GTK 4
0182     SettingsIniEditor::setValue(QStringLiteral("gtk-toolbar-style"), toolbarStyle, 3);
0183     XSettingsEditor::setValue(QStringLiteral("Gtk/ToolbarStyle"), toolbarStyle);
0184 }
0185 
0186 void GtkConfig::setScrollbarBehavior() const
0187 {
0188     const bool scrollbarBehavior = configValueProvider->scrollbarBehavior();
0189     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-primary-button-warps-slider"), scrollbarBehavior);
0190     SettingsIniEditor::setValue(QStringLiteral("gtk-primary-button-warps-slider"), scrollbarBehavior);
0191     XSettingsEditor::setValue(QStringLiteral("Gtk/PrimaryButtonWarpsSlider"), scrollbarBehavior);
0192 }
0193 
0194 void GtkConfig::setDoubleClickInterval() const
0195 {
0196     const int doubleClickInterval = configValueProvider->doubleClickInterval();
0197     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-double-click-time"), doubleClickInterval);
0198     GSettingsEditor::setValue("double-click", doubleClickInterval, "org.gnome.desktop.peripherals.mouse");
0199     SettingsIniEditor::setValue(QStringLiteral("gtk-double-click-time"), doubleClickInterval);
0200     XSettingsEditor::setValue(QStringLiteral("Net/DoubleClickTime"), doubleClickInterval);
0201 }
0202 
0203 void GtkConfig::setDarkThemePreference() const
0204 {
0205     const bool preferDarkTheme = configValueProvider->preferDarkTheme();
0206     SettingsIniEditor::setValue(QStringLiteral("gtk-application-prefer-dark-theme"), preferDarkTheme);
0207     // https://gitlab.gnome.org/GNOME/gsettings-desktop-schemas/-/blob/master/headers/gdesktop-enums.h
0208     GSettingsEditor::setValueAsEnum("color-scheme",
0209                                     preferDarkTheme ? 1 /*G_DESKTOP_COLOR_SCHEME_PREFER_DARK*/ : 0 /*G_DESKTOP_COLOR_SCHEME_DEFAULT*/,
0210                                     "org.gnome.desktop.interface");
0211     setGtk2Theme(gtkTheme(), preferDarkTheme);
0212 }
0213 
0214 void GtkConfig::setWindowDecorationsAppearance() const
0215 {
0216     if (gtkTheme() == QStringLiteral("Breeze")) { // Only Breeze GTK supports custom decoration buttons
0217         const auto windowDecorationsButtonsImages = configValueProvider->windowDecorationsButtonsImages();
0218         CustomCssEditor::setCustomClientSideDecorations(windowDecorationsButtonsImages);
0219     } else {
0220         CustomCssEditor::disableCustomClientSideDecorations();
0221     }
0222 }
0223 
0224 void GtkConfig::setWindowDecorationsButtonsOrder() const
0225 {
0226     const QString windowDecorationsButtonOrder = configValueProvider->windowDecorationsButtonsOrder();
0227     GSettingsEditor::setValue("button-layout", windowDecorationsButtonOrder, "org.gnome.desktop.wm.preferences");
0228     SettingsIniEditor::setValue(QStringLiteral("gtk-decoration-layout"), windowDecorationsButtonOrder);
0229     XSettingsEditor::setValue(QStringLiteral("Gtk/DecorationLayout"), windowDecorationsButtonOrder);
0230 }
0231 
0232 void GtkConfig::setEnableAnimations() const
0233 {
0234     const bool enableAnimations = configValueProvider->enableAnimations();
0235     Gtk2ConfigEditor::setValue(QStringLiteral("gtk-enable-animations"), enableAnimations);
0236     GSettingsEditor::setValue("enable-animations", enableAnimations);
0237     SettingsIniEditor::setValue(QStringLiteral("gtk-enable-animations"), enableAnimations);
0238     XSettingsEditor::setValue(QStringLiteral("Gtk/EnableAnimations"), enableAnimations);
0239     if (m_gsdXsettingsManager) {
0240         m_gsdXsettingsManager->enableAnimationsChanged();
0241     }
0242 }
0243 
0244 void GtkConfig::setGlobalScale() const
0245 {
0246     const unsigned scaleFactor = configValueProvider->x11GlobalScaleFactor();
0247     XSettingsEditor::setValue(QStringLiteral("Gdk/WindowScalingFactor"), scaleFactor);
0248     GSettingsEditor::setValue("scaling-factor", scaleFactor); // For IntelliJ IDEA
0249 }
0250 
0251 void GtkConfig::setTextScale() const
0252 {
0253     const double x11Scale = configValueProvider->x11GlobalScaleFactor();
0254     const int x11ScaleIntegerPart = int(x11Scale);
0255 
0256     const int forceFontDpi = configValueProvider->fontDpi();
0257 
0258     int x11TextDpiAbsolute = 96 * 1024;
0259     double waylandTextScaleFactor = 1.0;
0260 
0261     if (forceFontDpi == 0) {
0262         x11TextDpiAbsolute = (96 * 1024) * x11Scale;
0263     } else {
0264         x11TextDpiAbsolute = (forceFontDpi * 1024);
0265 
0266         if (!KWindowSystem::isPlatformX11()) {
0267             x11TextDpiAbsolute *= x11Scale;
0268         }
0269 
0270         waylandTextScaleFactor = double(forceFontDpi) / 96.0;
0271         waylandTextScaleFactor = std::clamp(waylandTextScaleFactor, 0.5, 3.0);
0272     }
0273 
0274     XSettingsEditor::unsetValue(QStringLiteral("Xft/DPI"));
0275     SettingsIniEditor::setValue(QStringLiteral("gtk-xft-dpi"), x11TextDpiAbsolute);
0276     XSettingsEditor::setValue(QStringLiteral("Gdk/UnscaledDPI"), x11TextDpiAbsolute / x11ScaleIntegerPart);
0277     GSettingsEditor::setValue("text-scaling-factor", waylandTextScaleFactor);
0278 }
0279 
0280 void GtkConfig::setColors() const
0281 {
0282     CustomCssEditor::addGtkModule(QStringLiteral("colorreload-gtk-module"));
0283     if (m_gsdXsettingsManager) {
0284         m_gsdXsettingsManager->modulesChanged();
0285     }
0286     // modulesChanged signal will take some time to reach a GTK app, so explicitly wait a moment
0287     QTimer::singleShot(200, this, [this] {
0288         const QMap<QString, QColor> colors = configValueProvider->colors();
0289         CustomCssEditor::setColors(colors);
0290     });
0291 }
0292 
0293 void GtkConfig::applyAllSettings() const
0294 {
0295     setFont();
0296     setIconTheme();
0297     setSoundTheme();
0298     setCursorTheme();
0299     setCursorSize();
0300     setIconsOnButtons();
0301     setIconsInMenus();
0302     setToolbarStyle();
0303     setScrollbarBehavior();
0304     setDarkThemePreference();
0305     setWindowDecorationsAppearance();
0306     setWindowDecorationsButtonsOrder();
0307     setEnableAnimations();
0308     setGlobalScale();
0309     setTextScale();
0310     setColors();
0311 }
0312 
0313 void GtkConfig::onKdeglobalsSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0314 {
0315     if (group.name() == QStringLiteral("KDE")) {
0316         if (names.contains(QByteArrayLiteral("AnimationDurationFactor"))) {
0317             setEnableAnimations();
0318         }
0319         if (names.contains(QByteArrayLiteral("ShowIconsInMenuItems"))) {
0320             setIconsInMenus();
0321         }
0322         if (names.contains(QByteArrayLiteral("ShowIconsOnPushButtons"))) {
0323             setIconsOnButtons();
0324         }
0325         // ScrollbarLeftClickNavigatesByPage is now the default setting, so when it's
0326         // true, it won't be present, so we need to check for its absence
0327         if (!names.contains(QByteArrayLiteral("ScrollbarLeftClickNavigatesByPage"))) {
0328             setScrollbarBehavior();
0329         }
0330         if (names.contains(QByteArrayLiteral("DoubleClickInterval"))) {
0331             setDoubleClickInterval();
0332         }
0333     } else if (group.name() == QStringLiteral("Icons")) {
0334         if (names.contains(QByteArrayLiteral("Theme"))) {
0335             setIconTheme();
0336         }
0337     } else if (group.name() == QLatin1String("Sounds")) {
0338         if (names.contains(QByteArrayLiteral("Theme"))) {
0339             setSoundTheme();
0340         }
0341         if (names.contains(QByteArrayLiteral("Enable"))) {
0342             setEventSoundsEnabled();
0343         }
0344     } else if (group.name() == QStringLiteral("General")) {
0345         if (names.contains(QByteArrayLiteral("font"))) {
0346             setFont();
0347         }
0348         if (names.contains(QByteArrayLiteral("ColorScheme")) || names.contains(QByteArrayLiteral("AccentColor"))) {
0349             setColors();
0350             setDarkThemePreference();
0351             setWindowDecorationsAppearance(); // Decorations' color can depend on the current color scheme
0352         }
0353     } else if (group.name() == QStringLiteral("KScreen")) {
0354         if (names.contains(QByteArrayLiteral("ScaleFactor"))) {
0355             setGlobalScale();
0356             // setTextScale() will be called in onKCMFontsSettingsChange
0357         }
0358     } else if (group.name() == QStringLiteral("Toolbar style")) {
0359         if (names.contains(QByteArrayLiteral("ToolButtonStyle"))) {
0360             setToolbarStyle();
0361         }
0362     }
0363 }
0364 
0365 void GtkConfig::onKWinSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0366 {
0367     if (group.name() == QStringLiteral("org.kde.kdecoration2")) {
0368         if (names.contains(QByteArrayLiteral("ButtonsOnRight")) //
0369             || names.contains(QByteArrayLiteral("ButtonsOnLeft"))) {
0370             setWindowDecorationsButtonsOrder();
0371         }
0372         if (names.contains(QByteArrayLiteral("theme"))) {
0373             setWindowDecorationsAppearance();
0374         }
0375     } else if (group.name() == QStringLiteral("Xwayland")) {
0376         if (names.contains(QByteArrayLiteral("Scale"))) {
0377             setGlobalScale();
0378             setTextScale();
0379             setCursorSize();
0380         }
0381     }
0382 }
0383 
0384 void GtkConfig::onKCMFontsSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0385 {
0386     if (group.name() == QStringLiteral("General")) {
0387         if (names.contains("forceFontDPI")) {
0388             setTextScale();
0389         }
0390     }
0391 }
0392 
0393 void GtkConfig::onKCMInputSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0394 {
0395     if (group.name() == QStringLiteral("Mouse")) {
0396         if (names.contains("cursorTheme")) {
0397             setCursorTheme();
0398         }
0399         if (names.contains("cursorSize")) {
0400             setCursorSize();
0401         }
0402     }
0403 }
0404 
0405 void GtkConfig::onBreezeSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const
0406 {
0407     if (group.name() == QStringLiteral("Common") //
0408         && names.contains("OutlineCloseButton")) {
0409         setWindowDecorationsAppearance();
0410     }
0411 }
0412 
0413 #include "gtkconfig.moc"
0414 
0415 #include "moc_gtkconfig.cpp"