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"