File indexing completed on 2024-03-24 05:29:55
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"