/* KRDB - puts current KDE color scheme into preprocessor statements cats specially written application default files and uses xrdb -merge to write to RESOURCE_MANAGER. Thus it gives a simple way to make non-KDE applications fit in with the desktop SPDX-FileCopyrightText: 1998 Mark Donohoe SPDX-FileCopyrightText: 2001 Waldo Bastian SPDX-FileCopyrightText: 2002 Karol Szwed SPDX-FileCopyrightText: 2022 Harald Sitter reworked for KDE 2.0: SPDX-FileCopyrightText: 1999 Dirk A. Mueller add support for GTK applications: SPDX-FileCopyrightText: 2001 Matthias Ettrich SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #undef Unsorted #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "krdb.h" #if HAVE_X11 #include #include #endif #include using namespace Qt::StringLiterals; inline const char *gtkEnvVar(int version) { return 2 == version ? "GTK2_RC_FILES" : "GTK_RC_FILES"; } inline QLatin1String sysGtkrc(int version) { std::error_code error; if (version == 2) { if (std::filesystem::exists("/etc/opt/gnome/gtk-2.0", error) && !error) { return QLatin1String("/etc/opt/gnome/gtk-2.0/gtkrc"); } return QLatin1String("/etc/gtk-2.0/gtkrc"); } if (std::filesystem::exists("/etc/opt/gnome/gtk", error) && !error) { return QLatin1String("/etc/opt/gnome/gtk/gtkrc"); } return QLatin1String("/etc/gtk/gtkrc"); } inline QLatin1String userGtkrc(int version) { return version == 2 ? QLatin1String("/.gtkrc-2.0") : QLatin1String("/.gtkrc"); } // ----------------------------------------------------------------------------- static QString writableGtkrc(int version) { QString gtkrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QDir dir; dir.mkpath(gtkrc); gtkrc += 2 == version ? u"/gtkrc-2.0"_s : u"/gtkrc"_s; return gtkrc; } // ----------------------------------------------------------------------------- static void applyGtkStyles(int version) { const char *varName = gtkEnvVar(version); const char *envVar = getenv(varName); if (envVar) { // Already set by user, don't override it return; } const QByteArray gtkrc(envVar); const QString userHomeGtkrc = QDir::homePath() + userGtkrc(version); QStringList list = QFile::decodeName(gtkrc).split(QLatin1Char(':')); if (!list.contains(userHomeGtkrc)) { list.prepend(userHomeGtkrc); } const QLatin1String systemGtkrc = sysGtkrc(version); if (!list.contains(systemGtkrc)) { list.prepend(systemGtkrc); } list.removeAll(QString()); const QString gtkkde = writableGtkrc(version); list.removeAll(gtkkde); list.append(gtkkde); // Pass env. var to kdeinit. const QString value = list.join(QLatin1Char(':')); QProcessEnvironment newEnv; newEnv.insert(QString::fromLatin1(varName), value); new KUpdateLaunchEnvironmentJob(newEnv); } // ----------------------------------------------------------------------------- static void applyQtColors(KSharedConfigPtr kglobalcfg, QSettings &settings, QPalette &newPal) { QStringList actcg, inactcg, discg; /* export kde color settings */ int i; for (i = 0; i < QPalette::NColorRoles; i++) actcg << newPal.color(QPalette::Active, (QPalette::ColorRole)i).name(); for (i = 0; i < QPalette::NColorRoles; i++) inactcg << newPal.color(QPalette::Inactive, (QPalette::ColorRole)i).name(); for (i = 0; i < QPalette::NColorRoles; i++) discg << newPal.color(QPalette::Disabled, (QPalette::ColorRole)i).name(); settings.setValue(QStringLiteral("/qt/Palette/active"), actcg); settings.setValue(QStringLiteral("/qt/Palette/inactive"), inactcg); settings.setValue(QStringLiteral("/qt/Palette/disabled"), discg); // export kwin's colors to qtrc for kstyle to use KConfigGroup wmCfgGroup(kglobalcfg, u"WM"_s); // active colors QColor clr = newPal.color(QPalette::Active, QPalette::Window); clr = wmCfgGroup.readEntry("activeBackground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeBackground"), clr.name()); if (QPixmap::defaultDepth() > 8) clr = clr.darker(110); clr = wmCfgGroup.readEntry("activeBlend", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeBlend"), clr.name()); clr = newPal.color(QPalette::Active, QPalette::HighlightedText); clr = wmCfgGroup.readEntry("activeForeground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeForeground"), clr.name()); clr = newPal.color(QPalette::Active, QPalette::Window); clr = wmCfgGroup.readEntry("frame", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/frame"), clr.name()); clr = wmCfgGroup.readEntry("activeTitleBtnBg", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeTitleBtnBg"), clr.name()); // inactive colors clr = newPal.color(QPalette::Inactive, QPalette::Window); clr = wmCfgGroup.readEntry("inactiveBackground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBackground"), clr.name()); if (QPixmap::defaultDepth() > 8) clr = clr.darker(110); clr = wmCfgGroup.readEntry("inactiveBlend", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBlend"), clr.name()); clr = newPal.color(QPalette::Inactive, QPalette::Window).darker(); clr = wmCfgGroup.readEntry("inactiveForeground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveForeground"), clr.name()); clr = newPal.color(QPalette::Inactive, QPalette::Window); clr = wmCfgGroup.readEntry("inactiveFrame", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveFrame"), clr.name()); clr = wmCfgGroup.readEntry("inactiveTitleBtnBg", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveTitleBtnBg"), clr.name()); KConfigGroup kdeCfgGroup(kglobalcfg, u"KDE"_s); settings.setValue(QStringLiteral("/qt/KDE/contrast"), kdeCfgGroup.readEntry("contrast", 7)); } // ----------------------------------------------------------------------------- static void applyQtSettings(KSharedConfigPtr kglobalcfg, QSettings &settings) { /* export font settings */ // NOTE keep this in sync with kfontsettingsdata in plasma-integration (cf. also Bug 378262) QFont defaultFont(QStringLiteral("Noto Sans"), 10, -1); defaultFont.setStyleHint(QFont::SansSerif); const KConfigGroup configGroup(KSharedConfig::openConfig(), u"General"_s); const QString fontInfo = configGroup.readEntry(QStringLiteral("font"), QString()); if (!fontInfo.isEmpty()) { defaultFont.fromString(fontInfo); } settings.setValue(QStringLiteral("/qt/font"), defaultFont.toString()); /* export effects settings */ KConfigGroup kdeCfgGroup(kglobalcfg, u"General"_s); bool effectsEnabled = kdeCfgGroup.readEntry("EffectsEnabled", false); bool fadeMenus = kdeCfgGroup.readEntry("EffectFadeMenu", false); bool fadeTooltips = kdeCfgGroup.readEntry("EffectFadeTooltip", false); bool animateCombobox = kdeCfgGroup.readEntry("EffectAnimateCombo", false); QStringList guieffects; if (effectsEnabled) { guieffects << QStringLiteral("general"); if (fadeMenus) guieffects << QStringLiteral("fademenu"); if (animateCombobox) guieffects << QStringLiteral("animatecombo"); if (fadeTooltips) guieffects << QStringLiteral("fadetooltip"); } else guieffects << QStringLiteral("none"); settings.setValue(QStringLiteral("/qt/GUIEffects"), guieffects); } // ----------------------------------------------------------------------------- static void copyFile(QFile &tmp, QString const &filename, bool) { QFile f(filename); if (f.open(QIODevice::ReadOnly)) { QByteArray buf(8192, ' '); while (!f.atEnd()) { int read = f.read(buf.data(), buf.size()); if (read > 0) tmp.write(buf.data(), read); } } } // ----------------------------------------------------------------------------- static void createGtkrc(const QPalette &cg, bool exportGtkTheme, const QString >kTheme, int version) { Q_UNUSED(cg); // lukas: why does it create in ~/.kde/share/config ??? // pfeiffer: so that we don't overwrite the user's gtkrc. // it is found via the GTK_RC_FILES environment variable. QSaveFile saveFile(writableGtkrc(version)); if (!saveFile.open(QIODevice::WriteOnly)) return; QTextStream t(&saveFile); t << i18n( "# created by KDE Plasma, %1\n" "#\n", QDateTime::currentDateTime().toString()); if (2 == version) { // we should maybe check for MacOS settings here using Qt::endl; t << endl; t << "gtk-alternative-button-order = 1" << endl; t << endl; } if (exportGtkTheme) { QString gtkStyle; if (QString::compare(gtkTheme, QLatin1String("oxygen"), Qt::CaseInsensitive) == 0) gtkStyle = QStringLiteral("oxygen-gtk"); else gtkStyle = gtkTheme; bool exist_gtkrc = false; QByteArray gtkrc = getenv(gtkEnvVar(version)); QStringList listGtkrc = QFile::decodeName(gtkrc).split(QLatin1Char(':')); if (listGtkrc.contains(saveFile.fileName())) listGtkrc.removeAll(saveFile.fileName()); listGtkrc.append(QDir::homePath() + userGtkrc(version)); listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde"_L1); listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde4"_L1); listGtkrc.removeAll(QString()); listGtkrc.removeDuplicates(); for (int i = 0; i < listGtkrc.size(); ++i) { if ((exist_gtkrc = QFile::exists(listGtkrc.at(i)))) break; } if (!exist_gtkrc) { QString gtk2ThemeFilename; gtk2ThemeFilename = QStringLiteral("%1/.themes/%2/gtk-2.0/gtkrc").arg(QDir::homePath(), gtkStyle); if (!QFile::exists(gtk2ThemeFilename)) { QStringList gtk2ThemePath; gtk2ThemeFilename.clear(); QByteArray xdgDataDirs = getenv("XDG_DATA_DIRS"); gtk2ThemePath.append(QDir::homePath() + "/.local"_L1); gtk2ThemePath.append(QFile::decodeName(xdgDataDirs).split(QLatin1Char(':'))); gtk2ThemePath.removeDuplicates(); for (int i = 0; i < gtk2ThemePath.size(); ++i) { gtk2ThemeFilename = QStringLiteral("%1/themes/%2/gtk-2.0/gtkrc").arg(gtk2ThemePath.at(i), gtkStyle); if (QFile::exists(gtk2ThemeFilename)) break; else gtk2ThemeFilename.clear(); } } if (!gtk2ThemeFilename.isEmpty()) { t << "include \"" << gtk2ThemeFilename << "\"" << Qt::endl; t << Qt::endl; t << "gtk-theme-name=\"" << gtkStyle << "\"" << Qt::endl; t << Qt::endl; } } } saveFile.commit(); } // ----------------------------------------------------------------------------- int xftDpi() { KConfig cfg(QStringLiteral("kcmfonts")); int dpi = 0; if (KWindowSystem::isPlatformWayland()) { KConfig cfg(QStringLiteral("kwinrc")); KConfigGroup xwaylandGroup = cfg.group(QStringLiteral("Xwayland")); qreal scale = xwaylandGroup.readEntry("Scale", 1.0); dpi = scale * 96; } else { KConfigGroup fontsCfg(&cfg, u"General"_s); dpi = fontsCfg.readEntry(QStringLiteral("forceFontDPI"), 96); } return dpi; } void runRdb(unsigned int flags) { // Obtain the application palette that is about to be set. bool exportQtColors = flags & KRdbExportQtColors; bool exportQtSettings = flags & KRdbExportQtSettings; bool exportXftSettings = flags & KRdbExportXftSettings; bool exportGtkTheme = flags & KRdbExportGtkTheme; KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); KConfigGroup kglobals(kglobalcfg, u"KDE"_s); QPalette newPal = KColorScheme::createApplicationPalette(kglobalcfg); QTemporaryFile tmpFile; if (!tmpFile.open()) { qDebug() << "Couldn't open temp file"; exit(0); } KConfigGroup generalCfgGroup(kglobalcfg, u"General"_s); QString gtkTheme; if (kglobals.hasKey("widgetStyle")) gtkTheme = kglobals.readEntry("widgetStyle"); else gtkTheme = QStringLiteral("oxygen"); createGtkrc(newPal, exportGtkTheme, gtkTheme, 1); createGtkrc(newPal, exportGtkTheme, gtkTheme, 2); // Merge ~/.Xresources or fallback to ~/.Xdefaults QString homeDir = QDir::homePath(); QString xResources = homeDir + "/.Xresources"_L1; // very primitive support for ~/.Xresources by appending it if (QFile::exists(xResources)) copyFile(tmpFile, xResources, true); else copyFile(tmpFile, homeDir + "/.Xdefaults"_L1, true); // Export the Xcursor theme & size settings KConfigGroup mousecfg(KSharedConfig::openConfig(QStringLiteral("kcminputrc")), u"Mouse"_s); QString theme = mousecfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors")); int cursorSize = mousecfg.readEntry("cursorSize", 24); if (KWindowSystem::isPlatformWayland()) { KConfig kwinConfig(QStringLiteral("kwinrc")); KConfigGroup xwaylandGroup(&kwinConfig, u"Xwayland"_s); cursorSize *= xwaylandGroup.readEntry("Scale", 1.0); } QString contents; contents += "Xcursor.theme: "_L1 + theme + u'\n'; contents += "Xcursor.size: "_L1 + QString::number(cursorSize) + u'\n'; if (exportXftSettings) { contents += QLatin1String("Xft.antialias: "); if (generalCfgGroup.readEntry("XftAntialias", true)) contents += QLatin1String("1\n"); else contents += QLatin1String("0\n"); QString hintStyle = generalCfgGroup.readEntry("XftHintStyle", "hintslight"); contents += QLatin1String("Xft.hinting: "); if (hintStyle.isEmpty()) contents += QLatin1String("-1\n"); else { if (hintStyle != QLatin1String("hintnone")) contents += QLatin1String("1\n"); else contents += QLatin1String("0\n"); contents += "Xft.hintstyle: "_L1 + hintStyle + u'\n'; } QString subPixel = generalCfgGroup.readEntry("XftSubPixel", "rgb"); if (!subPixel.isEmpty()) contents += "Xft.rgba: "_L1 + subPixel + u'\n'; int dpi = xftDpi(); if (dpi > 0) contents += "Xft.dpi: "_L1 + QString::number(dpi) + u'\n'; else { KProcess queryProc; queryProc << QStringLiteral("xrdb") << QStringLiteral("-query"); queryProc.setOutputChannelMode(KProcess::OnlyStdoutChannel); queryProc.start(); if (queryProc.waitForFinished()) { QByteArray db = queryProc.readAllStandardOutput(); int idx1 = 0; while (idx1 < db.size()) { int idx2 = db.indexOf('\n', idx1); if (idx2 == -1) { idx2 = db.size() - 1; } const auto entry = QByteArray::fromRawData(db.constData() + idx1, idx2 - idx1 + 1); if (entry.startsWith("Xft.dpi:")) { db.remove(idx1, entry.size()); } else { idx1 = idx2 + 1; } } KProcess loadProc; loadProc << QStringLiteral("xrdb") << QStringLiteral("-quiet") << QStringLiteral("-load") << QStringLiteral("-nocpp"); loadProc.start(); if (loadProc.waitForStarted()) { loadProc.write(db); loadProc.closeWriteChannel(); loadProc.waitForFinished(); } } } } if (contents.length() > 0) tmpFile.write(contents.toLatin1()); tmpFile.flush(); KProcess proc; #ifndef NDEBUG proc << QStringLiteral("xrdb") << QStringLiteral("-merge") << tmpFile.fileName(); #else proc << u"xrdb"_s << u"-quiet"_s << u"-merge"_s << tmpFile.fileName(); #endif proc.execute(); // Needed for applications that don't set their own cursor. QProcess::execute(QStringLiteral("xsetroot"), {QStringLiteral("-cursor_name"), QStringLiteral("left_ptr")}); applyGtkStyles(1); applyGtkStyles(2); /* Qt exports */ if (exportQtColors || exportQtSettings) { QSettings *settings = new QSettings(QStringLiteral("Trolltech")); if (exportQtColors) applyQtColors(kglobalcfg, *settings, newPal); // For kcmcolors if (exportQtSettings) applyQtSettings(kglobalcfg, *settings); // For kcmstyle delete settings; #if HAVE_X11 if (qApp->platformName() == QLatin1String("xcb")) { // We let KIPC take care of ourselves, as we are in a KDE app with // QApp::setDesktopSettingsAware(false); // Instead of calling QApp::x11_apply_settings() directly, we instead // modify the timestamp which propagates the settings changes onto // Qt-only apps without adversely affecting ourselves. // Cheat and use the current timestamp, since we just saved to qtrc. QDateTime settingsstamp = QDateTime::currentDateTime(); static Atom qt_settings_timestamp = 0; if (!qt_settings_timestamp) { QString atomname(QStringLiteral("_QT_SETTINGS_TIMESTAMP_")); atomname += QString::fromLocal8Bit(XDisplayName(nullptr)); // Use the $DISPLAY envvar. qt_settings_timestamp = XInternAtom(QX11Info::display(), atomname.toLatin1().constData(), False); } QBuffer stamp; QDataStream s(&stamp.buffer(), QIODevice::WriteOnly); s << settingsstamp; XChangeProperty(QX11Info::display(), QX11Info::appRootWindow(), qt_settings_timestamp, qt_settings_timestamp, 8, PropModeReplace, (unsigned char *)stamp.buffer().data(), stamp.buffer().size()); } #endif } }