Warning, file /plasma/plasma-workspace/kcms/krdb/krdb.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002    KRDB - puts current KDE color scheme into preprocessor statements
0003    cats specially written application default files and uses xrdb -merge to
0004    write to RESOURCE_MANAGER. Thus it gives a  simple way to make non-KDE
0005    applications fit in with the desktop
0006 
0007    SPDX-FileCopyrightText: 1998 Mark Donohoe
0008    SPDX-FileCopyrightText: 2001 Waldo Bastian <bastian@kde.org>
0009    SPDX-FileCopyrightText: 2002 Karol Szwed <gallium@kde.org>
0010    SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0011 
0012    reworked for KDE 2.0:
0013    SPDX-FileCopyrightText: 1999 Dirk A. Mueller
0014 
0015    add support for GTK applications:
0016    SPDX-FileCopyrightText: 2001 Matthias Ettrich
0017 
0018    SPDX-License-Identifier: GPL-2.0-or-later
0019 */
0020 
0021 #include <config-X11.h>
0022 #include <config-workspace.h>
0023 #include <limits.h>
0024 #include <stdlib.h>
0025 #include <string.h>
0026 #include <unistd.h>
0027 
0028 #undef Unsorted
0029 #include <QApplication>
0030 #include <QBuffer>
0031 #include <QDir>
0032 #include <QFontDatabase>
0033 #include <QSettings>
0034 
0035 #include <QByteArray>
0036 #include <QDBusConnection>
0037 #include <QDateTime>
0038 #include <QDebug>
0039 #include <QPixmap>
0040 #include <QSaveFile>
0041 #include <QTemporaryFile>
0042 #include <QTextStream>
0043 
0044 #include <KColorScheme>
0045 #include <KConfig>
0046 #include <KConfigGroup>
0047 #include <KLocalizedString>
0048 #include <KProcess>
0049 #include <KWindowSystem>
0050 
0051 #include <KUpdateLaunchEnvironmentJob>
0052 
0053 #include "krdb.h"
0054 #if HAVE_X11
0055 #include <X11/Xlib.h>
0056 #include <private/qtx11extras_p.h>
0057 #endif
0058 
0059 #include <filesystem>
0060 
0061 using namespace Qt::StringLiterals;
0062 
0063 inline const char *gtkEnvVar(int version)
0064 {
0065     return 2 == version ? "GTK2_RC_FILES" : "GTK_RC_FILES";
0066 }
0067 
0068 inline QLatin1String sysGtkrc(int version)
0069 {
0070     std::error_code error;
0071     if (version == 2) {
0072         if (std::filesystem::exists("/etc/opt/gnome/gtk-2.0", error) && !error) {
0073             return QLatin1String("/etc/opt/gnome/gtk-2.0/gtkrc");
0074         }
0075         return QLatin1String("/etc/gtk-2.0/gtkrc");
0076     }
0077     if (std::filesystem::exists("/etc/opt/gnome/gtk", error) && !error) {
0078         return QLatin1String("/etc/opt/gnome/gtk/gtkrc");
0079     }
0080     return QLatin1String("/etc/gtk/gtkrc");
0081 }
0082 
0083 inline QLatin1String userGtkrc(int version)
0084 {
0085     return version == 2 ? QLatin1String("/.gtkrc-2.0") : QLatin1String("/.gtkrc");
0086 }
0087 
0088 // -----------------------------------------------------------------------------
0089 static QString writableGtkrc(int version)
0090 {
0091     QString gtkrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0092     QDir dir;
0093     dir.mkpath(gtkrc);
0094     gtkrc += 2 == version ? "/gtkrc-2.0" : "/gtkrc";
0095     return gtkrc;
0096 }
0097 
0098 // -----------------------------------------------------------------------------
0099 static void applyGtkStyles(int version)
0100 {
0101     const char *varName = gtkEnvVar(version);
0102     const char *envVar = getenv(varName);
0103     if (envVar) { // Already set by user, don't override it
0104         return;
0105     }
0106 
0107     const QByteArray gtkrc(envVar);
0108     const QString userHomeGtkrc = QDir::homePath() + userGtkrc(version);
0109     QStringList list = QFile::decodeName(gtkrc).split(QLatin1Char(':'));
0110     if (!list.contains(userHomeGtkrc)) {
0111         list.prepend(userHomeGtkrc);
0112     }
0113 
0114     const QLatin1String systemGtkrc = sysGtkrc(version);
0115     if (!list.contains(systemGtkrc)) {
0116         list.prepend(systemGtkrc);
0117     }
0118 
0119     list.removeAll(QLatin1String(""));
0120 
0121     const QString gtkkde = writableGtkrc(version);
0122     list.removeAll(gtkkde);
0123     list.append(gtkkde);
0124 
0125     // Pass env. var to kdeinit.
0126     const QString value = list.join(QLatin1Char(':'));
0127     QProcessEnvironment newEnv;
0128     newEnv.insert(varName, value);
0129     new KUpdateLaunchEnvironmentJob(newEnv);
0130 }
0131 
0132 // -----------------------------------------------------------------------------
0133 
0134 static void applyQtColors(KSharedConfigPtr kglobalcfg, QSettings &settings, QPalette &newPal)
0135 {
0136     QStringList actcg, inactcg, discg;
0137     /* export kde color settings */
0138     int i;
0139     for (i = 0; i < QPalette::NColorRoles; i++)
0140         actcg << newPal.color(QPalette::Active, (QPalette::ColorRole)i).name();
0141     for (i = 0; i < QPalette::NColorRoles; i++)
0142         inactcg << newPal.color(QPalette::Inactive, (QPalette::ColorRole)i).name();
0143     for (i = 0; i < QPalette::NColorRoles; i++)
0144         discg << newPal.color(QPalette::Disabled, (QPalette::ColorRole)i).name();
0145 
0146     settings.setValue(QStringLiteral("/qt/Palette/active"), actcg);
0147     settings.setValue(QStringLiteral("/qt/Palette/inactive"), inactcg);
0148     settings.setValue(QStringLiteral("/qt/Palette/disabled"), discg);
0149 
0150     // export kwin's colors to qtrc for kstyle to use
0151     KConfigGroup wmCfgGroup(kglobalcfg, u"WM"_s);
0152 
0153     // active colors
0154     QColor clr = newPal.color(QPalette::Active, QPalette::Window);
0155     clr = wmCfgGroup.readEntry("activeBackground", clr);
0156     settings.setValue(QStringLiteral("/qt/KWinPalette/activeBackground"), clr.name());
0157     if (QPixmap::defaultDepth() > 8)
0158         clr = clr.darker(110);
0159     clr = wmCfgGroup.readEntry("activeBlend", clr);
0160     settings.setValue(QStringLiteral("/qt/KWinPalette/activeBlend"), clr.name());
0161     clr = newPal.color(QPalette::Active, QPalette::HighlightedText);
0162     clr = wmCfgGroup.readEntry("activeForeground", clr);
0163     settings.setValue(QStringLiteral("/qt/KWinPalette/activeForeground"), clr.name());
0164     clr = newPal.color(QPalette::Active, QPalette::Window);
0165     clr = wmCfgGroup.readEntry("frame", clr);
0166     settings.setValue(QStringLiteral("/qt/KWinPalette/frame"), clr.name());
0167     clr = wmCfgGroup.readEntry("activeTitleBtnBg", clr);
0168     settings.setValue(QStringLiteral("/qt/KWinPalette/activeTitleBtnBg"), clr.name());
0169 
0170     // inactive colors
0171     clr = newPal.color(QPalette::Inactive, QPalette::Window);
0172     clr = wmCfgGroup.readEntry("inactiveBackground", clr);
0173     settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBackground"), clr.name());
0174     if (QPixmap::defaultDepth() > 8)
0175         clr = clr.darker(110);
0176     clr = wmCfgGroup.readEntry("inactiveBlend", clr);
0177     settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBlend"), clr.name());
0178     clr = newPal.color(QPalette::Inactive, QPalette::Window).darker();
0179     clr = wmCfgGroup.readEntry("inactiveForeground", clr);
0180     settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveForeground"), clr.name());
0181     clr = newPal.color(QPalette::Inactive, QPalette::Window);
0182     clr = wmCfgGroup.readEntry("inactiveFrame", clr);
0183     settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveFrame"), clr.name());
0184     clr = wmCfgGroup.readEntry("inactiveTitleBtnBg", clr);
0185     settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveTitleBtnBg"), clr.name());
0186 
0187     KConfigGroup kdeCfgGroup(kglobalcfg, u"KDE"_s);
0188     settings.setValue(QStringLiteral("/qt/KDE/contrast"), kdeCfgGroup.readEntry("contrast", 7));
0189 }
0190 
0191 // -----------------------------------------------------------------------------
0192 
0193 static void applyQtSettings(KSharedConfigPtr kglobalcfg, QSettings &settings)
0194 {
0195     /* export font settings */
0196 
0197     // NOTE keep this in sync with kfontsettingsdata in plasma-integration (cf. also Bug 378262)
0198     QFont defaultFont(QStringLiteral("Noto Sans"), 10, -1);
0199     defaultFont.setStyleHint(QFont::SansSerif);
0200 
0201     const KConfigGroup configGroup(KSharedConfig::openConfig(), u"General"_s);
0202     const QString fontInfo = configGroup.readEntry(QStringLiteral("font"), QString());
0203     if (!fontInfo.isEmpty()) {
0204         defaultFont.fromString(fontInfo);
0205     }
0206 
0207     settings.setValue(QStringLiteral("/qt/font"), defaultFont.toString());
0208 
0209     /* export effects settings */
0210     KConfigGroup kdeCfgGroup(kglobalcfg, u"General"_s);
0211     bool effectsEnabled = kdeCfgGroup.readEntry("EffectsEnabled", false);
0212     bool fadeMenus = kdeCfgGroup.readEntry("EffectFadeMenu", false);
0213     bool fadeTooltips = kdeCfgGroup.readEntry("EffectFadeTooltip", false);
0214     bool animateCombobox = kdeCfgGroup.readEntry("EffectAnimateCombo", false);
0215 
0216     QStringList guieffects;
0217     if (effectsEnabled) {
0218         guieffects << QStringLiteral("general");
0219         if (fadeMenus)
0220             guieffects << QStringLiteral("fademenu");
0221         if (animateCombobox)
0222             guieffects << QStringLiteral("animatecombo");
0223         if (fadeTooltips)
0224             guieffects << QStringLiteral("fadetooltip");
0225     } else
0226         guieffects << QStringLiteral("none");
0227 
0228     settings.setValue(QStringLiteral("/qt/GUIEffects"), guieffects);
0229 }
0230 
0231 // -----------------------------------------------------------------------------
0232 
0233 static void copyFile(QFile &tmp, QString const &filename, bool)
0234 {
0235     QFile f(filename);
0236     if (f.open(QIODevice::ReadOnly)) {
0237         QByteArray buf(8192, ' ');
0238         while (!f.atEnd()) {
0239             int read = f.read(buf.data(), buf.size());
0240             if (read > 0)
0241                 tmp.write(buf.data(), read);
0242         }
0243     }
0244 }
0245 
0246 // -----------------------------------------------------------------------------
0247 
0248 static void createGtkrc(const QPalette &cg, bool exportGtkTheme, const QString &gtkTheme, int version)
0249 {
0250     Q_UNUSED(cg);
0251     // lukas: why does it create in ~/.kde/share/config ???
0252     // pfeiffer: so that we don't overwrite the user's gtkrc.
0253     // it is found via the GTK_RC_FILES environment variable.
0254     QSaveFile saveFile(writableGtkrc(version));
0255     if (!saveFile.open(QIODevice::WriteOnly))
0256         return;
0257 
0258     QTextStream t(&saveFile);
0259     t << i18n(
0260         "# created by KDE Plasma, %1\n"
0261         "#\n",
0262         QDateTime::currentDateTime().toString());
0263 
0264     if (2 == version) { // we should maybe check for MacOS settings here
0265         using Qt::endl;
0266         t << endl;
0267         t << "gtk-alternative-button-order = 1" << endl;
0268         t << endl;
0269     }
0270 
0271     if (exportGtkTheme) {
0272         QString gtkStyle;
0273         if (gtkTheme.toLower() == QLatin1String("oxygen"))
0274             gtkStyle = QStringLiteral("oxygen-gtk");
0275         else
0276             gtkStyle = gtkTheme;
0277 
0278         bool exist_gtkrc = false;
0279         QByteArray gtkrc = getenv(gtkEnvVar(version));
0280         QStringList listGtkrc = QFile::decodeName(gtkrc).split(QLatin1Char(':'));
0281         if (listGtkrc.contains(saveFile.fileName()))
0282             listGtkrc.removeAll(saveFile.fileName());
0283         listGtkrc.append(QDir::homePath() + userGtkrc(version));
0284         listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde");
0285         listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde4");
0286         listGtkrc.removeAll(QLatin1String(""));
0287         listGtkrc.removeDuplicates();
0288         for (int i = 0; i < listGtkrc.size(); ++i) {
0289             if ((exist_gtkrc = QFile::exists(listGtkrc.at(i))))
0290                 break;
0291         }
0292 
0293         if (!exist_gtkrc) {
0294             QString gtk2ThemeFilename;
0295             gtk2ThemeFilename = QStringLiteral("%1/.themes/%2/gtk-2.0/gtkrc").arg(QDir::homePath(), gtkStyle);
0296             if (!QFile::exists(gtk2ThemeFilename)) {
0297                 QStringList gtk2ThemePath;
0298                 gtk2ThemeFilename.clear();
0299                 QByteArray xdgDataDirs = getenv("XDG_DATA_DIRS");
0300                 gtk2ThemePath.append(QDir::homePath() + "/.local");
0301                 gtk2ThemePath.append(QFile::decodeName(xdgDataDirs).split(QLatin1Char(':')));
0302                 gtk2ThemePath.removeDuplicates();
0303                 for (int i = 0; i < gtk2ThemePath.size(); ++i) {
0304                     gtk2ThemeFilename = QStringLiteral("%1/themes/%2/gtk-2.0/gtkrc").arg(gtk2ThemePath.at(i), gtkStyle);
0305                     if (QFile::exists(gtk2ThemeFilename))
0306                         break;
0307                     else
0308                         gtk2ThemeFilename.clear();
0309                 }
0310             }
0311 
0312             if (!gtk2ThemeFilename.isEmpty()) {
0313                 t << "include \"" << gtk2ThemeFilename << "\"" << Qt::endl;
0314                 t << Qt::endl;
0315                 t << "gtk-theme-name=\"" << gtkStyle << "\"" << Qt::endl;
0316                 t << Qt::endl;
0317             }
0318         }
0319     }
0320 
0321     saveFile.commit();
0322 }
0323 
0324 // -----------------------------------------------------------------------------
0325 
0326 int xftDpi()
0327 {
0328     KConfig cfg(QStringLiteral("kcmfonts"));
0329     int dpi = 0;
0330 
0331     if (KWindowSystem::isPlatformWayland()) {
0332         KConfig cfg(QStringLiteral("kwinrc"));
0333         KConfigGroup xwaylandGroup = cfg.group(QStringLiteral("Xwayland"));
0334         qreal scale = xwaylandGroup.readEntry("Scale", 1.0);
0335         dpi = scale * 96;
0336     } else {
0337         KConfigGroup fontsCfg(&cfg, u"General"_s);
0338         dpi = fontsCfg.readEntry(QStringLiteral("forceFontDPI"), 96);
0339     }
0340 
0341     return dpi;
0342 }
0343 
0344 void runRdb(unsigned int flags)
0345 {
0346     // Obtain the application palette that is about to be set.
0347     bool exportQtColors = flags & KRdbExportQtColors;
0348     bool exportQtSettings = flags & KRdbExportQtSettings;
0349     bool exportXftSettings = flags & KRdbExportXftSettings;
0350     bool exportGtkTheme = flags & KRdbExportGtkTheme;
0351 
0352     KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals"));
0353     KConfigGroup kglobals(kglobalcfg, u"KDE"_s);
0354     QPalette newPal = KColorScheme::createApplicationPalette(kglobalcfg);
0355 
0356     QTemporaryFile tmpFile;
0357     if (!tmpFile.open()) {
0358         qDebug() << "Couldn't open temp file";
0359         exit(0);
0360     }
0361 
0362     KConfigGroup generalCfgGroup(kglobalcfg, u"General"_s);
0363 
0364     QString gtkTheme;
0365     if (kglobals.hasKey("widgetStyle"))
0366         gtkTheme = kglobals.readEntry("widgetStyle");
0367     else
0368         gtkTheme = QStringLiteral("oxygen");
0369 
0370     createGtkrc(newPal, exportGtkTheme, gtkTheme, 1);
0371     createGtkrc(newPal, exportGtkTheme, gtkTheme, 2);
0372 
0373     // Merge ~/.Xresources or fallback to ~/.Xdefaults
0374     QString homeDir = QDir::homePath();
0375     QString xResources = homeDir + "/.Xresources";
0376 
0377     // very primitive support for ~/.Xresources by appending it
0378     if (QFile::exists(xResources))
0379         copyFile(tmpFile, xResources, true);
0380     else
0381         copyFile(tmpFile, homeDir + "/.Xdefaults", true);
0382 
0383     // Export the Xcursor theme & size settings
0384     KConfigGroup mousecfg(KSharedConfig::openConfig(QStringLiteral("kcminputrc")), u"Mouse"_s);
0385     QString theme = mousecfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors"));
0386     int cursorSize = mousecfg.readEntry("cursorSize", 24);
0387 
0388     if (KWindowSystem::isPlatformWayland()) {
0389         KConfig kwinConfig(QStringLiteral("kwinrc"));
0390         KConfigGroup xwaylandGroup(&kwinConfig, u"Xwayland"_s);
0391         cursorSize *= xwaylandGroup.readEntry("Scale", 1.0);
0392     }
0393 
0394     QString contents;
0395     contents += "Xcursor.theme: " + theme + '\n';
0396     contents += "Xcursor.size: " + QString::number(cursorSize) + '\n';
0397 
0398     if (exportXftSettings) {
0399         contents += QLatin1String("Xft.antialias: ");
0400         if (generalCfgGroup.readEntry("XftAntialias", true))
0401             contents += QLatin1String("1\n");
0402         else
0403             contents += QLatin1String("0\n");
0404 
0405         QString hintStyle = generalCfgGroup.readEntry("XftHintStyle", "hintslight");
0406         contents += QLatin1String("Xft.hinting: ");
0407         if (hintStyle.isEmpty())
0408             contents += QLatin1String("-1\n");
0409         else {
0410             if (hintStyle != QLatin1String("hintnone"))
0411                 contents += QLatin1String("1\n");
0412             else
0413                 contents += QLatin1String("0\n");
0414             contents += "Xft.hintstyle: " + hintStyle + '\n';
0415         }
0416 
0417         QString subPixel = generalCfgGroup.readEntry("XftSubPixel", "rgb");
0418         if (!subPixel.isEmpty())
0419             contents += "Xft.rgba: " + subPixel + '\n';
0420 
0421         int dpi = xftDpi();
0422         if (dpi > 0)
0423             contents += "Xft.dpi: " + QString::number(dpi) + '\n';
0424         else {
0425             KProcess queryProc;
0426             queryProc << QStringLiteral("xrdb") << QStringLiteral("-query");
0427             queryProc.setOutputChannelMode(KProcess::OnlyStdoutChannel);
0428             queryProc.start();
0429             if (queryProc.waitForFinished()) {
0430                 QByteArray db = queryProc.readAllStandardOutput();
0431                 int idx1 = 0;
0432                 while (idx1 < db.size()) {
0433                     int idx2 = db.indexOf('\n', idx1);
0434                     if (idx2 == -1) {
0435                         idx2 = db.size() - 1;
0436                     }
0437                     const auto entry = QByteArray::fromRawData(db.constData() + idx1, idx2 - idx1 + 1);
0438                     if (entry.startsWith("Xft.dpi:")) {
0439                         db.remove(idx1, entry.size());
0440                     } else {
0441                         idx1 = idx2 + 1;
0442                     }
0443                 }
0444 
0445                 KProcess loadProc;
0446                 loadProc << QStringLiteral("xrdb") << QStringLiteral("-quiet") << QStringLiteral("-load") << QStringLiteral("-nocpp");
0447                 loadProc.start();
0448                 if (loadProc.waitForStarted()) {
0449                     loadProc.write(db);
0450                     loadProc.closeWriteChannel();
0451                     loadProc.waitForFinished();
0452                 }
0453             }
0454         }
0455     }
0456 
0457     if (contents.length() > 0)
0458         tmpFile.write(contents.toLatin1(), contents.length());
0459 
0460     tmpFile.flush();
0461 
0462     KProcess proc;
0463 #ifndef NDEBUG
0464     proc << QStringLiteral("xrdb") << QStringLiteral("-merge") << tmpFile.fileName();
0465 #else
0466     proc << "xrdb"
0467          << "-quiet"
0468          << "-merge" << tmpFile.fileName();
0469 #endif
0470     proc.execute();
0471 
0472     // Needed for applications that don't set their own cursor.
0473     QProcess::execute(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")});
0474 
0475     applyGtkStyles(1);
0476     applyGtkStyles(2);
0477 
0478     /* Qt exports */
0479     if (exportQtColors || exportQtSettings) {
0480         QSettings *settings = new QSettings(QStringLiteral("Trolltech"));
0481 
0482         if (exportQtColors)
0483             applyQtColors(kglobalcfg, *settings, newPal); // For kcmcolors
0484 
0485         if (exportQtSettings)
0486             applyQtSettings(kglobalcfg, *settings); // For kcmstyle
0487 
0488         delete settings;
0489         QCoreApplication::processEvents();
0490 #if HAVE_X11
0491         if (qApp->platformName() == QLatin1String("xcb")) {
0492             // We let KIPC take care of ourselves, as we are in a KDE app with
0493             // QApp::setDesktopSettingsAware(false);
0494             // Instead of calling QApp::x11_apply_settings() directly, we instead
0495             // modify the timestamp which propagates the settings changes onto
0496             // Qt-only apps without adversely affecting ourselves.
0497 
0498             // Cheat and use the current timestamp, since we just saved to qtrc.
0499             QDateTime settingsstamp = QDateTime::currentDateTime();
0500 
0501             static Atom qt_settings_timestamp = 0;
0502             if (!qt_settings_timestamp) {
0503                 QString atomname(QStringLiteral("_QT_SETTINGS_TIMESTAMP_"));
0504                 atomname += XDisplayName(nullptr); // Use the $DISPLAY envvar.
0505                 qt_settings_timestamp = XInternAtom(QX11Info::display(), atomname.toLatin1(), False);
0506             }
0507 
0508             QBuffer stamp;
0509             QDataStream s(&stamp.buffer(), QIODevice::WriteOnly);
0510             s << settingsstamp;
0511             XChangeProperty(QX11Info::display(),
0512                             QX11Info::appRootWindow(),
0513                             qt_settings_timestamp,
0514                             qt_settings_timestamp,
0515                             8,
0516                             PropModeReplace,
0517                             (unsigned char *)stamp.buffer().data(),
0518                             stamp.buffer().size());
0519             qApp->processEvents();
0520         }
0521 #endif
0522     }
0523 }