File indexing completed on 2024-04-28 05:36:53

0001 /*
0002  * SPDX-FileCopyrightText: 2018-2019 Red Hat Inc
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  *
0006  * SPDX-FileCopyrightText: 2018-2019 Jan Grulich <jgrulich@redhat.com>
0007  * SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0008  */
0009 
0010 #include "settings.h"
0011 #include "settings_debug.h"
0012 
0013 #include <QApplication>
0014 #include <QDBusConnection>
0015 #include <QDBusContext>
0016 #include <QDBusMessage>
0017 #include <QDBusMetaType>
0018 #include <QPalette>
0019 
0020 #include <KConfigGroup>
0021 
0022 #include "desktopportal.h"
0023 #include "tabletmodemanager_interface.h"
0024 #include "virtualkeyboard_interface.h"
0025 
0026 using namespace Qt::Literals::StringLiterals;
0027 
0028 static bool groupMatches(const QString &group, const QStringList &patterns)
0029 {
0030     return std::any_of(patterns.cbegin(), patterns.cend(), [&group](const auto &pattern) {
0031         if (pattern.isEmpty()) {
0032             return true;
0033         }
0034 
0035         if (pattern == group) {
0036             return true;
0037         }
0038 
0039         if (pattern.startsWith(group)) {
0040             return true;
0041         }
0042 
0043         if (pattern.endsWith(QLatin1Char('*')) && group.startsWith(pattern.left(pattern.length() - 1))) {
0044             return true;
0045         }
0046 
0047         return false;
0048     });
0049 }
0050 
0051 class VirtualKeyboardSettings : public SettingsModule
0052 {
0053     Q_OBJECT
0054     static constexpr auto KEY_ACTIVE = "active"_L1;
0055     static constexpr auto KEY_ACTIVE_CLIENT_SUPPORTS_TEXT_INPUT = "activeClientSupportsTextInput"_L1;
0056     static constexpr auto KEY_AVAILABLE = "available"_L1;
0057     static constexpr auto KEY_ENABLED = "enabled"_L1;
0058     static constexpr auto KEY_VISIBLE = "visible"_L1;
0059     static constexpr auto KEY_WILL_SHOW_ON_ACTIVE = "willShowOnActive"_L1;
0060     static constexpr auto KEYS = {KEY_ACTIVE, KEY_ACTIVE_CLIENT_SUPPORTS_TEXT_INPUT, KEY_AVAILABLE, KEY_ENABLED, KEY_VISIBLE, KEY_WILL_SHOW_ON_ACTIVE};
0061 
0062 public:
0063     explicit VirtualKeyboardSettings(QObject *parent = nullptr)
0064         : SettingsModule(parent)
0065         , m_interface(u"org.kde.KWin"_s, u"/VirtualKeyboard"_s, QDBusConnection::sessionBus())
0066     {
0067         connect(&m_interface, &OrgKdeKwinVirtualKeyboardInterface::activeChanged, this, [this]() {
0068             Q_EMIT settingChanged(group(), KEY_ACTIVE, QDBusVariant(readInternal(KEY_ACTIVE)));
0069         });
0070         connect(&m_interface, &OrgKdeKwinVirtualKeyboardInterface::activeClientSupportsTextInputChanged, this, [this]() {
0071             Q_EMIT settingChanged(group(), KEY_ACTIVE_CLIENT_SUPPORTS_TEXT_INPUT, QDBusVariant(readInternal(KEY_ACTIVE_CLIENT_SUPPORTS_TEXT_INPUT)));
0072         });
0073         connect(&m_interface, &OrgKdeKwinVirtualKeyboardInterface::availableChanged, this, [this]() {
0074             Q_EMIT settingChanged(group(), KEY_AVAILABLE, QDBusVariant(readInternal(KEY_AVAILABLE)));
0075         });
0076         connect(&m_interface, &OrgKdeKwinVirtualKeyboardInterface::enabledChanged, this, [this]() {
0077             Q_EMIT settingChanged(group(), KEY_ENABLED, QDBusVariant(readInternal(KEY_ENABLED)));
0078         });
0079         connect(&m_interface, &OrgKdeKwinVirtualKeyboardInterface::visibleChanged, this, [this]() {
0080             Q_EMIT settingChanged(group(), KEY_VISIBLE, QDBusVariant(readInternal(KEY_VISIBLE)));
0081         });
0082     }
0083 
0084     inline QString group() final
0085     {
0086         return u"org.kde.VirtualKeyboard"_s;
0087     }
0088 
0089     VariantMapMap readAll(const QStringList &groups) final
0090     {
0091         Q_UNUSED(groups);
0092         VariantMapMap result;
0093         QVariantMap map;
0094         for (const auto &key : KEYS) {
0095             if (const auto value = readInternal(key); value.isValid()) {
0096                 map.insert(key, value);
0097             }
0098         }
0099         result.insert(group(), map);
0100         return result;
0101     }
0102 
0103     QVariant read(const QString &group, const QString &key) final
0104     {
0105         Q_UNUSED(group);
0106         for (const auto &keyIt : KEYS) {
0107             if (key == keyIt) {
0108                 return readInternal(key);
0109             }
0110         }
0111         return {};
0112     }
0113 
0114 private:
0115     inline QVariant readInternal(const QString &key)
0116     {
0117         if (key == KEY_WILL_SHOW_ON_ACTIVE) {
0118             return m_interface.willShowOnActive().value();
0119         }
0120         return m_interface.property(qUtf8Printable(key));
0121     }
0122 
0123     OrgKdeKwinVirtualKeyboardInterface m_interface;
0124 };
0125 
0126 // For consistency reasons TabletSettings have their property names changed.
0127 // org.kde.TabletModel.enabled on our end is called tabletMode on the KWin side.
0128 // As a consequence of that we do not meta program a mapping but instead manually write out the logic per key.
0129 class TabletModeSettings : public SettingsModule
0130 {
0131     Q_OBJECT
0132     static constexpr auto KEY_ENABLED = "enabled"_L1;
0133     static constexpr auto KEY_AVAILABLE = "available"_L1;
0134 
0135 public:
0136     explicit TabletModeSettings(QObject *parent = nullptr)
0137         : SettingsModule(parent)
0138         , m_interface(u"org.kde.KWin"_s, u"/org/kde/KWin"_s, QDBusConnection::sessionBus(), this)
0139     {
0140         connect(&m_interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeAvailableChanged, this, [this](bool available) {
0141             Q_EMIT settingChanged(group(), KEY_AVAILABLE, QDBusVariant(available));
0142         });
0143         connect(&m_interface, &OrgKdeKWinTabletModeManagerInterface::tabletModeChanged, this, [this](bool enabled) {
0144             Q_EMIT settingChanged(group(), KEY_ENABLED, QDBusVariant(enabled));
0145             qputenv("BREEZE_IS_TABLET_MODE", enabled ? QByteArrayLiteral("1") : QByteArrayLiteral("0"));
0146         });
0147         qputenv("BREEZE_IS_TABLET_MODE", m_interface.tabletMode() ? QByteArrayLiteral("1") : QByteArrayLiteral("0"));
0148     }
0149 
0150     inline QString group() final
0151     {
0152         return u"org.kde.TabletMode"_s;
0153     }
0154 
0155     VariantMapMap readAll(const QStringList &groups) final
0156     {
0157         Q_UNUSED(groups);
0158         VariantMapMap result;
0159         result.insert(group(), {{KEY_AVAILABLE, read(group(), KEY_AVAILABLE)}, {KEY_ENABLED, read(group(), KEY_ENABLED)}});
0160         return result;
0161     }
0162 
0163     QVariant read(const QString &group, const QString &key) final
0164     {
0165         Q_UNUSED(group);
0166         if (key == KEY_AVAILABLE) {
0167             return m_interface.tabletModeAvailable();
0168         }
0169         if (key == KEY_ENABLED) {
0170             return m_interface.tabletMode();
0171         }
0172         return {};
0173     }
0174 
0175 private:
0176     OrgKdeKWinTabletModeManagerInterface m_interface;
0177 };
0178 
0179 /* accent-color */
0180 struct AccentColorArray {
0181     double r = 0.0; // 0-1
0182     double g = 0.0; // 0-1
0183     double b = 0.0; // 0-1
0184 
0185     operator QVariant() const
0186     {
0187         return QVariant::fromValue(*this);
0188     }
0189 };
0190 Q_DECLARE_METATYPE(AccentColorArray)
0191 
0192 QDBusArgument &operator<<(QDBusArgument &argument, const AccentColorArray &item)
0193 {
0194     argument.beginStructure();
0195     argument << item.r << item.g << item.b;
0196     argument.endStructure();
0197     return argument;
0198 }
0199 
0200 const QDBusArgument &operator>>(const QDBusArgument &argument, AccentColorArray &item)
0201 {
0202     argument.beginStructure();
0203     argument >> item.r >> item.g >> item.b;
0204     argument.endStructure();
0205     return argument;
0206 }
0207 
0208 class FdoAppearanceSettings : public SettingsModule
0209 {
0210     Q_OBJECT
0211     static constexpr auto colorScheme = "color-scheme"_L1;
0212     static constexpr auto accentColor = "accent-color"_L1;
0213 
0214 public:
0215     explicit FdoAppearanceSettings(QObject *parent = nullptr)
0216         : SettingsModule(parent)
0217     {
0218         qDBusRegisterMetaType<AccentColorArray>();
0219         connect(qGuiApp, &QGuiApplication::paletteChanged, this, &FdoAppearanceSettings::onPaletteChanged);
0220     }
0221 
0222     inline QString group() final
0223     {
0224         return u"org.freedesktop.appearance"_s;
0225     }
0226 
0227     VariantMapMap readAll(const QStringList &groups) final
0228     {
0229         Q_UNUSED(groups);
0230         VariantMapMap result;
0231         QVariantMap appearanceSettings;
0232         appearanceSettings.insert(colorScheme, readFdoColorScheme().variant());
0233         appearanceSettings.insert(accentColor, readAccentColor().variant());
0234         result.insert(group(), appearanceSettings);
0235         return result;
0236     }
0237 
0238     QVariant read(const QString &group, const QString &key) final
0239     {
0240         if (group != this->group()) {
0241             return {};
0242         }
0243         if (key == colorScheme) {
0244             return readFdoColorScheme().variant();
0245         } else if (key == accentColor) {
0246             return readAccentColor().variant();
0247         }
0248         return {};
0249     }
0250 
0251 private:
0252     QDBusVariant readFdoColorScheme()
0253     {
0254         const QPalette palette = QApplication::palette();
0255         const int windowBackgroundGray = qGray(palette.window().color().rgb());
0256 
0257         uint result = 0; // no preference
0258 
0259         if (windowBackgroundGray < 192) {
0260             result = 1; // prefer dark
0261         } else {
0262             result = 2; // prefer light
0263         }
0264 
0265         return QDBusVariant(result);
0266     }
0267 
0268     /**
0269      * Returns a list that contains redF, blueF and greenF and represents
0270      * the current accent color.
0271      * Format: (ddd)
0272      */
0273     QDBusVariant readAccentColor() const
0274     {
0275         const QColor accentColor = qGuiApp->palette().highlight().color();
0276         return QDBusVariant(AccentColorArray{accentColor.redF(), accentColor.greenF(), accentColor.blueF()});
0277     }
0278 
0279 private Q_SLOTS:
0280     void onPaletteChanged()
0281     {
0282         Q_EMIT settingChanged(group(), colorScheme, readFdoColorScheme());
0283         Q_EMIT settingChanged(group(), accentColor, readAccentColor());
0284     }
0285 };
0286 
0287 class KDEGlobalsSettings : public SettingsModule
0288 {
0289     Q_OBJECT
0290 
0291     /**
0292      * An identifier for change signals.
0293      * @note Copied from KGlobalSettings
0294      */
0295     enum ChangeType {
0296         PaletteChanged = 0,
0297         FontChanged,
0298         StyleChanged,
0299         SettingsChanged,
0300         IconChanged,
0301         CursorChanged,
0302         ToolbarStyleChanged,
0303         ClipboardConfigChanged,
0304         BlockShortcuts,
0305         NaturalSortingChanged,
0306     };
0307 
0308     /**
0309      * Valid values for the settingsChanged signal
0310      * @note Copied from KGlobalSettings
0311      */
0312     enum SettingsCategory {
0313         SETTINGS_MOUSE,
0314         SETTINGS_COMPLETION,
0315         SETTINGS_PATHS,
0316         SETTINGS_POPUPMENU,
0317         SETTINGS_QT,
0318         SETTINGS_SHORTCUTS,
0319         SETTINGS_LOCALE,
0320         SETTINGS_STYLE,
0321     };
0322 
0323 public:
0324     explicit KDEGlobalsSettings(QObject *parent = nullptr)
0325         : SettingsModule(parent)
0326     {
0327         m_kdeglobals = KSharedConfig::openConfig();
0328 
0329         QDBusConnection::sessionBus().connect(QString(),
0330                                               QStringLiteral("/KDEPlatformTheme"),
0331                                               QStringLiteral("org.kde.KDEPlatformTheme"),
0332                                               QStringLiteral("refreshFonts"),
0333                                               this,
0334                                               SLOT(fontChanged()));
0335         QDBusConnection::sessionBus().connect(QString(),
0336                                               QStringLiteral("/KGlobalSettings"),
0337                                               QStringLiteral("org.kde.KGlobalSettings"),
0338                                               QStringLiteral("notifyChange"),
0339                                               this,
0340                                               // clang-format off
0341                                             SLOT(globalSettingChanged(int,int)));
0342         // clang-format on
0343         QDBusConnection::sessionBus().connect(QString(),
0344                                               QStringLiteral("/KToolBar"),
0345                                               QStringLiteral("org.kde.KToolBar"),
0346                                               QStringLiteral("styleChanged"),
0347                                               this,
0348                                               SLOT(toolbarStyleChanged()));
0349     }
0350 
0351     inline QString group() final
0352     {
0353         return u"org.kde.kdeglobals"_s;
0354     }
0355 
0356     VariantMapMap readAll(const QStringList &groups) final
0357     {
0358         VariantMapMap result;
0359 
0360         const auto groupList = m_kdeglobals->groupList();
0361         for (const QString &settingGroupName : groupList) {
0362             // NOTE: use org.kde.kdeglobals prefix
0363 
0364             QString uniqueGroupName = QStringLiteral("org.kde.kdeglobals.") + settingGroupName;
0365 
0366             if (!groupMatches(uniqueGroupName, groups)) {
0367                 continue;
0368             }
0369 
0370             QVariantMap map;
0371             KConfigGroup configGroup(m_kdeglobals, settingGroupName);
0372 
0373             const auto keyList = configGroup.keyList();
0374             for (const QString &key : keyList) {
0375                 map.insert(key, configGroup.readEntry(key));
0376             }
0377 
0378             result.insert(uniqueGroupName, map);
0379         }
0380 
0381         return result;
0382     }
0383 
0384     QVariant read(const QString &group, const QString &key) final
0385     {
0386         return readProperty(group, key).variant();
0387     }
0388 
0389 private Q_SLOTS:
0390     void fontChanged()
0391     {
0392         Q_EMIT settingChanged(u"org.kde.kdeglobals.General"_s, u"font"_s, readProperty(u"org.kde.kdeglobals.General"_s, u"font"_s));
0393     }
0394 
0395     void globalSettingChanged(int type, int arg)
0396     {
0397         m_kdeglobals->reparseConfiguration();
0398 
0399         // Mostly based on plasma-integration needs
0400         switch (type) {
0401         case PaletteChanged:
0402             // Plasma-integration will be loading whole palette again, there is no reason to try to identify
0403             // particular categories or colors
0404             Q_EMIT settingChanged(u"org.kde.kdeglobals.General"_s, u"ColorScheme"_s, readProperty(u"org.kde.kdeglobals.General"_s, u"ColorScheme"_s));
0405             break;
0406         case FontChanged:
0407             fontChanged();
0408             break;
0409         case StyleChanged:
0410             Q_EMIT settingChanged(u"org.kde.kdeglobals.KDE"_s, u"widgetStyle"_s, readProperty(u"org.kde.kdeglobals.KDE"_s, u"widgetStyle"_s));
0411             break;
0412         case SettingsChanged: {
0413             auto category = SettingsCategory(arg);
0414             if (category == SETTINGS_QT || category == SETTINGS_MOUSE) {
0415                 // TODO
0416             } else if (category == SETTINGS_STYLE) {
0417                 // TODO
0418             }
0419             break;
0420         }
0421         case IconChanged:
0422             // we will get notified about each category, but it probably makes sense to send this signal just once
0423             if (arg == 0) { // KIconLoader::Desktop
0424                 Q_EMIT settingChanged(u"org.kde.kdeglobals.Icons"_s, u"Theme"_s, readProperty(u"org.kde.kdeglobals.Icons"_s, u"Theme"_s));
0425             }
0426             break;
0427         case CursorChanged:
0428             // TODO
0429             break;
0430         case ToolbarStyleChanged:
0431             toolbarStyleChanged();
0432             break;
0433         default:
0434             break;
0435         }
0436     }
0437 
0438     void toolbarStyleChanged()
0439     {
0440         Q_EMIT settingChanged(u"org.kde.kdeglobals.Toolbar style"_s,
0441                               u"ToolButtonStyle"_s,
0442                               readProperty(u"org.kde.kdeglobals.Toolbar style"_s, u"ToolButtonStyle"_s));
0443     }
0444 
0445 private:
0446     QDBusVariant readProperty(const QString &group, const QString &key)
0447     {
0448         static constexpr auto prefixLength = "org.kde.kdeglobals."_L1.length();
0449         QString groupName = group.right(group.length() - prefixLength);
0450 
0451         if (!m_kdeglobals->hasGroup(groupName)) {
0452             qCWarning(XdgDesktopPortalKdeSettings) << "Group " << group << " doesn't exist";
0453             return {};
0454         }
0455 
0456         KConfigGroup configGroup(m_kdeglobals, groupName);
0457 
0458         if (!configGroup.hasKey(key)) {
0459             qCWarning(XdgDesktopPortalKdeSettings) << "Key " << key << " doesn't exist";
0460             return {};
0461         }
0462 
0463         return QDBusVariant(configGroup.readEntry(key));
0464     }
0465 
0466     KSharedConfigPtr m_kdeglobals = KSharedConfig::openConfig();
0467 };
0468 
0469 SettingsPortal::SettingsPortal(DesktopPortal *parent)
0470     : QDBusAbstractAdaptor(parent)
0471     , m_parent(parent)
0472 {
0473     m_settings.push_back(std::make_unique<FdoAppearanceSettings>(this));
0474     m_settings.push_back(std::make_unique<VirtualKeyboardSettings>(this));
0475     m_settings.push_back(std::make_unique<TabletModeSettings>(this));
0476     m_settings.push_back(std::make_unique<KDEGlobalsSettings>(this));
0477     for (const auto &setting : std::as_const(m_settings)) {
0478         connect(setting.get(), &SettingsModule::settingChanged, this, &SettingsPortal::SettingChanged);
0479     }
0480     qDBusRegisterMetaType<VariantMapMap>();
0481 }
0482 
0483 void SettingsPortal::ReadAll(const QStringList &groups)
0484 {
0485     qCDebug(XdgDesktopPortalKdeSettings) << "ReadAll called with parameters:";
0486     qCDebug(XdgDesktopPortalKdeSettings) << "    groups: " << groups;
0487 
0488     VariantMapMap result;
0489 
0490     for (const auto &setting : m_settings) {
0491         if (groupMatches(setting->group(), groups)) {
0492             result.insert(setting->readAll(groups));
0493         }
0494     }
0495 
0496     QDBusMessage message = m_parent->message();
0497     QDBusMessage reply = message.createReply(QVariant::fromValue(result));
0498     QDBusConnection::sessionBus().send(reply);
0499 }
0500 
0501 void SettingsPortal::Read(const QString &group, const QString &key)
0502 {
0503     qCDebug(XdgDesktopPortalKdeSettings) << "Read called with parameters:";
0504     qCDebug(XdgDesktopPortalKdeSettings) << "    group: " << group;
0505     qCDebug(XdgDesktopPortalKdeSettings) << "    key: " << key;
0506 
0507     QDBusMessage message = m_parent->message();
0508 
0509     const auto sentMesssage = std::any_of(m_settings.cbegin(), m_settings.cend(), [&message, &group, &key](const auto &setting) {
0510         if (group.startsWith(setting->group())) {
0511             const QVariant result = setting->read(group, key);
0512             QDBusMessage reply;
0513             if (result.isNull()) {
0514                 reply = message.createErrorReply(QDBusError::UnknownProperty, QStringLiteral("Property doesn't exist"));
0515             } else {
0516                 reply = message.createReply(QVariant::fromValue(QDBusVariant(result)));
0517             }
0518             QDBusConnection::sessionBus().send(reply);
0519             return true;
0520         }
0521         return false;
0522     });
0523     if (sentMesssage) {
0524         return;
0525     }
0526 
0527     qCWarning(XdgDesktopPortalKdeSettings) << "Namespace " << group << " is not supported";
0528     QDBusMessage reply = message.createErrorReply(QDBusError::UnknownProperty, QStringLiteral("Namespace is not supported"));
0529     QDBusConnection::sessionBus().send(reply);
0530 }
0531 
0532 #include "settings.moc"