File indexing completed on 2024-11-17 05:01:38

0001 /* This file is part of the KDE libraries
0002     SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
0003     SPDX-FileCopyrightText: 2008 Friedrich W. H. Kossebau <kossebau@kde.org>
0004     SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "kfontsettingsdata.h"
0011 #include <QApplication>
0012 #include <QCoreApplication>
0013 #include <QDBusConnection>
0014 #include <QDBusMessage>
0015 #include <QDBusReply>
0016 #include <QString>
0017 #include <QVariant>
0018 #include <qpa/qwindowsysteminterface.h>
0019 
0020 #include <KSandbox>
0021 #include <kconfiggroup.h>
0022 
0023 KFontSettingsData::KFontSettingsData()
0024     : QObject(nullptr)
0025     , mUsePortal(KSandbox::isInside())
0026     , mKdeGlobals(KSharedConfig::openConfig())
0027 {
0028     QMetaObject::invokeMethod(this, "delayedDBusConnects", Qt::QueuedConnection);
0029 
0030     for (int i = 0; i < FontTypesCount; ++i) {
0031         mFonts[i] = nullptr;
0032     }
0033 }
0034 
0035 KFontSettingsData::~KFontSettingsData()
0036 {
0037     for (int i = 0; i < FontTypesCount; ++i) {
0038         delete mFonts[i];
0039     }
0040 }
0041 
0042 // NOTE: keep in sync with plasma-desktop/kcms/fonts/fonts.cpp
0043 static const char GeneralId[] = "General";
0044 static const char DefaultFont[] = "Noto Sans";
0045 
0046 static const KFontData DefaultFontData[KFontSettingsData::FontTypesCount] = {
0047     {GeneralId, "font", DefaultFont, 10, QFont::Normal, QFont::SansSerif, "Regular"},
0048     {GeneralId, "fixed", "Hack", 10, QFont::Normal, QFont::Monospace, "Regular"},
0049     {GeneralId, "toolBarFont", DefaultFont, 10, QFont::Normal, QFont::SansSerif, "Regular"},
0050     {GeneralId, "menuFont", DefaultFont, 10, QFont::Normal, QFont::SansSerif, "Regular"},
0051     {"WM", "activeFont", DefaultFont, 10, QFont::Normal, QFont::SansSerif, "Regular"},
0052     {GeneralId, "taskbarFont", DefaultFont, 10, QFont::Normal, QFont::SansSerif, "Regular"},
0053     {GeneralId, "smallestReadableFont", DefaultFont, 8, QFont::Normal, QFont::SansSerif, "Regular"},
0054 };
0055 
0056 // From https://invent.kde.org/qt/qt/qtbase/blob/6.7/src/gui/text/qfont.cpp#L146
0057 static int convertWeights(int weight, bool inverted)
0058 {
0059     static constexpr int legacyToOpenTypeMap[][2] = {
0060         {0, 100},
0061         {12, 200},
0062         {25, 300},
0063         {50, 400},
0064         {57, 500},
0065         {63, 600},
0066         {75, 700},
0067         {81, 800},
0068         {87, 900},
0069     };
0070 
0071     int closestDist = INT_MAX;
0072     int result = -1;
0073 
0074     // Go through and find the closest mapped value
0075     for (const auto &mapping : legacyToOpenTypeMap) {
0076         const int weightOld = mapping[inverted];
0077         const int weightNew = mapping[!inverted];
0078         const int dist = qAbs(weightOld - weight);
0079         if (dist < closestDist) {
0080             result = weightNew;
0081             closestDist = dist;
0082         } else {
0083             // Break early since following values will be further away
0084             break;
0085         }
0086     }
0087 
0088     return result;
0089 }
0090 
0091 // Qt5: https://invent.kde.org/qt/qt/qtbase/blob/5.15/src/gui/text/qfont.cpp#L2110
0092 // Qt6: https://invent.kde.org/qt/qt/qtbase/blob/6.7/src/gui/text/qfont.cpp#L2135
0093 static QString convertQt6FontStringToQt5(const QString &fontInfo)
0094 {
0095     const auto parts = fontInfo.trimmed().split(QLatin1Char(','));
0096     const int count = parts.count();
0097 
0098     if (count != 16 && count != 17) {
0099         return fontInfo;
0100     }
0101 
0102     auto result = parts.mid(0, 10);
0103     result[4] = QString::number(convertWeights(parts[4].toInt(), true));
0104 
0105     if (count == 17) {
0106         result << parts.last();
0107     }
0108     return result.join(QLatin1Char(','));
0109 }
0110 
0111 QFont *KFontSettingsData::font(FontTypes fontType)
0112 {
0113     QFont *cachedFont = mFonts[fontType];
0114 
0115     if (!cachedFont) {
0116         const KFontData &fontData = DefaultFontData[fontType];
0117         cachedFont = new QFont(QLatin1String(fontData.FontName), fontData.Size, fontData.Weight);
0118         cachedFont->setStyleHint(fontData.StyleHint);
0119 
0120         const QString fontInfo = readConfigValue(QLatin1String(fontData.ConfigGroupKey), QLatin1String(fontData.ConfigKey));
0121 
0122         // If we have serialized information for this font, restore it
0123         // NOTE: We are not using KConfig directly because we can't call QFont::QFont from here
0124         if (!fontInfo.isEmpty()) {
0125             cachedFont->fromString(convertQt6FontStringToQt5(fontInfo));
0126         }
0127         // Don't set default font style names, as it prevents different font weights from being used (the QFont::Normal weight should work)
0128 
0129         mFonts[fontType] = cachedFont;
0130     }
0131 
0132     return cachedFont;
0133 }
0134 
0135 void KFontSettingsData::dropFontSettingsCache()
0136 {
0137     mKdeGlobals->reparseConfiguration();
0138     for (int i = 0; i < FontTypesCount; ++i) {
0139         delete mFonts[i];
0140         mFonts[i] = nullptr;
0141     }
0142 
0143     QWindowSystemInterface::handleThemeChange(nullptr);
0144 
0145     if (qobject_cast<QApplication *>(QCoreApplication::instance())) {
0146         QApplication::setFont(*font(KFontSettingsData::GeneralFont));
0147     } else {
0148         QGuiApplication::setFont(*font(KFontSettingsData::GeneralFont));
0149     }
0150 }
0151 
0152 void KFontSettingsData::delayedDBusConnects()
0153 {
0154     QDBusConnection::sessionBus().connect(QString(),
0155                                           QStringLiteral("/KDEPlatformTheme"),
0156                                           QStringLiteral("org.kde.KDEPlatformTheme"),
0157                                           QStringLiteral("refreshFonts"),
0158                                           this,
0159                                           SLOT(dropFontSettingsCache()));
0160 
0161     if (mUsePortal) {
0162         QDBusConnection::sessionBus().connect(QString(),
0163                                               QStringLiteral("/org/freedesktop/portal/desktop"),
0164                                               QStringLiteral("org.freedesktop.portal.Settings"),
0165                                               QStringLiteral("SettingChanged"),
0166                                               this,
0167                                               SLOT(slotPortalSettingChanged(QString, QString, QDBusVariant)));
0168     }
0169 }
0170 
0171 void KFontSettingsData::slotPortalSettingChanged(const QString &group, const QString &key, const QDBusVariant &value)
0172 {
0173     Q_UNUSED(value);
0174 
0175     if (group == QLatin1String("org.kde.kdeglobals.General") && key == QLatin1String("font")) {
0176         dropFontSettingsCache();
0177     }
0178 }
0179 
0180 QString KFontSettingsData::readConfigValue(const QString &group, const QString &key, const QString &defaultValue) const
0181 {
0182     if (mUsePortal) {
0183         const QString settingName = QStringLiteral("org.kde.kdeglobals.%1").arg(group);
0184         QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"),
0185                                                               QStringLiteral("/org/freedesktop/portal/desktop"),
0186                                                               QStringLiteral("org.freedesktop.portal.Settings"),
0187                                                               QStringLiteral("Read"));
0188         message << settingName << key;
0189 
0190         // FIXME: async?
0191         QDBusReply<QVariant> reply = QDBusConnection::sessionBus().call(message);
0192         if (reply.isValid()) {
0193             QDBusVariant result = qvariant_cast<QDBusVariant>(reply.value());
0194             const QString resultStr = result.variant().toString();
0195 
0196             if (!resultStr.isEmpty()) {
0197                 return resultStr;
0198             }
0199         }
0200     }
0201 
0202     const KConfigGroup configGroup(mKdeGlobals, group);
0203     return configGroup.readEntry(key, defaultValue);
0204 }