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 <updatelaunchenvjob.h>
0052 
0053 #include "krdb.h"
0054 #if HAVE_X11
0055 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0056 #include <private/qtx11extras_p.h>
0057 #else
0058 #include <QX11Info>
0059 #endif
0060 #include <X11/Xlib.h>
0061 #endif
0062 
0063 #include <filesystem>
0064 
0065 inline const char *gtkEnvVar(int version)
0066 {
0067     return 2 == version ? "GTK2_RC_FILES" : "GTK_RC_FILES";
0068 }
0069 
0070 inline QLatin1String sysGtkrc(int version)
0071 {
0072     std::error_code error;
0073     if (version == 2) {
0074         if (std::filesystem::exists("/etc/opt/gnome/gtk-2.0", error) && !error) {
0075             return QLatin1String("/etc/opt/gnome/gtk-2.0/gtkrc");
0076         }
0077         return QLatin1String("/etc/gtk-2.0/gtkrc");
0078     }
0079     if (std::filesystem::exists("/etc/opt/gnome/gtk", error) && !error) {
0080         return QLatin1String("/etc/opt/gnome/gtk/gtkrc");
0081     }
0082     return QLatin1String("/etc/gtk/gtkrc");
0083 }
0084 
0085 inline QLatin1String userGtkrc(int version)
0086 {
0087     return version == 2 ? QLatin1String("/.gtkrc-2.0") : QLatin1String("/.gtkrc");
0088 }
0089 
0090 // -----------------------------------------------------------------------------
0091 static QString writableGtkrc(int version)
0092 {
0093     QString gtkrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0094     QDir dir;
0095     dir.mkpath(gtkrc);
0096     gtkrc += 2 == version ? "/gtkrc-2.0" : "/gtkrc";
0097     return gtkrc;
0098 }
0099 
0100 // -----------------------------------------------------------------------------
0101 static void applyGtkStyles(int version)
0102 {
0103     const char *varName = gtkEnvVar(version);
0104     const char *envVar = getenv(varName);
0105     if (envVar) { // Already set by user, don't override it
0106         return;
0107     }
0108 
0109     const QByteArray gtkrc(envVar);
0110     const QString userHomeGtkrc = QDir::homePath() + userGtkrc(version);
0111     QStringList list = QFile::decodeName(gtkrc).split(QLatin1Char(':'));
0112     if (!list.contains(userHomeGtkrc)) {
0113         list.prepend(userHomeGtkrc);
0114     }
0115 
0116     const QLatin1String systemGtkrc = sysGtkrc(version);
0117     if (!list.contains(systemGtkrc)) {
0118         list.prepend(systemGtkrc);
0119     }
0120 
0121     list.removeAll(QLatin1String(""));
0122 
0123     const QString gtkkde = writableGtkrc(version);
0124     list.removeAll(gtkkde);
0125     list.append(gtkkde);
0126 
0127     // Pass env. var to kdeinit.
0128     const QString value = list.join(QLatin1Char(':'));
0129     UpdateLaunchEnvJob(varName, value);
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, "WM");
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, "KDE");
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(), QStringLiteral("General"));
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, "General");
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     KConfigGroup fontsCfg(&cfg, "General");
0330 
0331     int defaultDpi = 0;
0332     const bool isWayland = KWindowSystem::isPlatformWayland();
0333 
0334     if (isWayland) {
0335         KConfig cfg(QStringLiteral("kwinrc"));
0336         KConfigGroup xwaylandGroup = cfg.group("Xwayland");
0337         qreal scale = xwaylandGroup.readEntry("Scale", 1.0);
0338         defaultDpi = scale * 96;
0339     }
0340 
0341     QString fontDpiKey = isWayland ? QStringLiteral("forceFontDPIWayland") : QStringLiteral("forceFontDPI");
0342     return fontsCfg.readEntry(fontDpiKey, defaultDpi);
0343 }
0344 
0345 void runRdb(unsigned int flags)
0346 {
0347     // Obtain the application palette that is about to be set.
0348     bool exportQtColors = flags & KRdbExportQtColors;
0349     bool exportQtSettings = flags & KRdbExportQtSettings;
0350     bool exportXftSettings = flags & KRdbExportXftSettings;
0351     bool exportGtkTheme = flags & KRdbExportGtkTheme;
0352 
0353     KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals"));
0354     KConfigGroup kglobals(kglobalcfg, "KDE");
0355     QPalette newPal = KColorScheme::createApplicationPalette(kglobalcfg);
0356 
0357     QTemporaryFile tmpFile;
0358     if (!tmpFile.open()) {
0359         qDebug() << "Couldn't open temp file";
0360         exit(0);
0361     }
0362 
0363     KConfigGroup generalCfgGroup(kglobalcfg, "General");
0364 
0365     QString gtkTheme;
0366     if (kglobals.hasKey("widgetStyle"))
0367         gtkTheme = kglobals.readEntry("widgetStyle");
0368     else
0369         gtkTheme = QStringLiteral("oxygen");
0370 
0371     createGtkrc(newPal, exportGtkTheme, gtkTheme, 1);
0372     createGtkrc(newPal, exportGtkTheme, gtkTheme, 2);
0373 
0374     // Merge ~/.Xresources or fallback to ~/.Xdefaults
0375     QString homeDir = QDir::homePath();
0376     QString xResources = homeDir + "/.Xresources";
0377 
0378     // very primitive support for ~/.Xresources by appending it
0379     if (QFile::exists(xResources))
0380         copyFile(tmpFile, xResources, true);
0381     else
0382         copyFile(tmpFile, homeDir + "/.Xdefaults", true);
0383 
0384     // Export the Xcursor theme & size settings
0385     KConfigGroup mousecfg(KSharedConfig::openConfig(QStringLiteral("kcminputrc")), "Mouse");
0386     QString theme = mousecfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors"));
0387     QString size = mousecfg.readEntry("cursorSize", QStringLiteral("24"));
0388     QString contents;
0389 
0390     if (!theme.isNull())
0391         contents = "Xcursor.theme: " + theme + '\n';
0392 
0393     if (!size.isNull())
0394         contents += "Xcursor.size: " + size + '\n';
0395 
0396     if (exportXftSettings) {
0397         contents += QLatin1String("Xft.antialias: ");
0398         if (generalCfgGroup.readEntry("XftAntialias", true))
0399             contents += QLatin1String("1\n");
0400         else
0401             contents += QLatin1String("0\n");
0402 
0403         QString hintStyle = generalCfgGroup.readEntry("XftHintStyle", "hintslight");
0404         contents += QLatin1String("Xft.hinting: ");
0405         if (hintStyle.isEmpty())
0406             contents += QLatin1String("-1\n");
0407         else {
0408             if (hintStyle != QLatin1String("hintnone"))
0409                 contents += QLatin1String("1\n");
0410             else
0411                 contents += QLatin1String("0\n");
0412             contents += "Xft.hintstyle: " + hintStyle + '\n';
0413         }
0414 
0415         QString subPixel = generalCfgGroup.readEntry("XftSubPixel", "rgb");
0416         if (!subPixel.isEmpty())
0417             contents += "Xft.rgba: " + subPixel + '\n';
0418 
0419         int dpi = xftDpi();
0420         if (dpi > 0)
0421             contents += "Xft.dpi: " + QString::number(dpi) + '\n';
0422         else {
0423             KProcess queryProc;
0424             queryProc << QStringLiteral("xrdb") << QStringLiteral("-query");
0425             queryProc.setOutputChannelMode(KProcess::OnlyStdoutChannel);
0426             queryProc.start();
0427             if (queryProc.waitForFinished()) {
0428                 QByteArray db = queryProc.readAllStandardOutput();
0429                 int idx1 = 0;
0430                 while (idx1 < db.size()) {
0431                     int idx2 = db.indexOf('\n', idx1);
0432                     if (idx2 == -1) {
0433                         idx2 = db.size() - 1;
0434                     }
0435                     const auto entry = QByteArray::fromRawData(db.constData() + idx1, idx2 - idx1 + 1);
0436                     if (entry.startsWith("Xft.dpi:")) {
0437                         db.remove(idx1, entry.size());
0438                     } else {
0439                         idx1 = idx2 + 1;
0440                     }
0441                 }
0442 
0443                 KProcess loadProc;
0444                 loadProc << QStringLiteral("xrdb") << QStringLiteral("-quiet") << QStringLiteral("-load") << QStringLiteral("-nocpp");
0445                 loadProc.start();
0446                 if (loadProc.waitForStarted()) {
0447                     loadProc.write(db);
0448                     loadProc.closeWriteChannel();
0449                     loadProc.waitForFinished();
0450                 }
0451             }
0452         }
0453     }
0454 
0455     if (contents.length() > 0)
0456         tmpFile.write(contents.toLatin1(), contents.length());
0457 
0458     tmpFile.flush();
0459 
0460     KProcess proc;
0461 #ifndef NDEBUG
0462     proc << QStringLiteral("xrdb") << QStringLiteral("-merge") << tmpFile.fileName();
0463 #else
0464     proc << "xrdb"
0465          << "-quiet"
0466          << "-merge" << tmpFile.fileName();
0467 #endif
0468     proc.execute();
0469 
0470     // Needed for applications that don't set their own cursor.
0471     QProcess::execute(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")});
0472 
0473     applyGtkStyles(1);
0474     applyGtkStyles(2);
0475 
0476     /* Qt exports */
0477     if (exportQtColors || exportQtSettings) {
0478         QSettings *settings = new QSettings(QStringLiteral("Trolltech"));
0479 
0480         if (exportQtColors)
0481             applyQtColors(kglobalcfg, *settings, newPal); // For kcmcolors
0482 
0483         if (exportQtSettings)
0484             applyQtSettings(kglobalcfg, *settings); // For kcmstyle
0485 
0486         delete settings;
0487         QCoreApplication::processEvents();
0488 #if HAVE_X11
0489         if (qApp->platformName() == QLatin1String("xcb")) {
0490             // We let KIPC take care of ourselves, as we are in a KDE app with
0491             // QApp::setDesktopSettingsAware(false);
0492             // Instead of calling QApp::x11_apply_settings() directly, we instead
0493             // modify the timestamp which propagates the settings changes onto
0494             // Qt-only apps without adversely affecting ourselves.
0495 
0496             // Cheat and use the current timestamp, since we just saved to qtrc.
0497             QDateTime settingsstamp = QDateTime::currentDateTime();
0498 
0499             static Atom qt_settings_timestamp = 0;
0500             if (!qt_settings_timestamp) {
0501                 QString atomname(QStringLiteral("_QT_SETTINGS_TIMESTAMP_"));
0502                 atomname += XDisplayName(nullptr); // Use the $DISPLAY envvar.
0503                 qt_settings_timestamp = XInternAtom(QX11Info::display(), atomname.toLatin1(), False);
0504             }
0505 
0506             QBuffer stamp;
0507             QDataStream s(&stamp.buffer(), QIODevice::WriteOnly);
0508             s << settingsstamp;
0509             XChangeProperty(QX11Info::display(),
0510                             QX11Info::appRootWindow(),
0511                             qt_settings_timestamp,
0512                             qt_settings_timestamp,
0513                             8,
0514                             PropModeReplace,
0515                             (unsigned char *)stamp.buffer().data(),
0516                             stamp.buffer().size());
0517             qApp->processEvents();
0518         }
0519 #endif
0520     }
0521 }