kded: Add startup settings manager

This adds a new component that manages Plasma Mobile specific configuration on every session startup. It also restores configuration when logged into a desktop session. 

This allows us to remove https://invent.kde.org/plasma-mobile/plasma-phone-settings, as well as configuration that was set in the look and feel. This also gives us an easy way to control configuration upgrade paths, and in the future, add ways for the configuration to easily be reset for debugging purposes.
This commit is contained in:
Devin Lin 2023-02-23 16:43:38 +00:00
parent e6f7bc67eb
commit d2b5416513
13 changed files with 375 additions and 10 deletions

View file

@ -42,6 +42,11 @@ include(FeatureSummary)
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX PLASMA_MOBILE
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h
)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED
Core
Qml
@ -56,6 +61,7 @@ endif()
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
I18n
KIO
DBusAddons
Plasma
PlasmaQuick
Service
@ -90,6 +96,7 @@ add_subdirectory(containments)
add_subdirectory(components)
add_subdirectory(quicksettings)
add_subdirectory(kcms)
add_subdirectory(kded)
find_program(PlasmaOpenSettings plasma-open-settings)
set_package_properties(PlasmaOpenSettings PROPERTIES

View file

@ -11,6 +11,8 @@ target_link_libraries(ppc-mmqmlplugin
Qt::Qml
KF5::ModemManagerQt
KF5::NetworkManagerQt
KF5::CoreAddons
KF5::I18n
)
set_property(TARGET ppc-mmqmlplugin PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/org/kde/plasma/mm)

View file

@ -1,13 +1,15 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-FileCopyrightText: 2022-2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "signalindicator.h"
#include <NetworkManagerQt/GsmSetting>
#include <NetworkManagerQt/Manager>
#include <NetworkManagerQt/Settings>
#include <NetworkManagerQt/Utils>
#include "signalindicator.h"
#include <KUser>
SignalIndicator::SignalIndicator(QObject *parent)
: QObject{parent}

View file

@ -22,8 +22,10 @@ class SignalIndicator : public QObject
Q_PROPERTY(bool modemAvailable READ modemAvailable NOTIFY modemAvailableChanged)
Q_PROPERTY(bool simLocked READ simLocked NOTIFY simLockedChanged)
Q_PROPERTY(bool simEmpty READ simEmpty NOTIFY simEmptyChanged)
Q_PROPERTY(bool mobileDataSupported READ mobileDataSupported NOTIFY mobileDataSupportedChanged)
Q_PROPERTY(bool mobileDataEnabled READ mobileDataEnabled WRITE setMobileDataEnabled NOTIFY mobileDataEnabledChanged)
Q_PROPERTY(bool needsAPNAdded READ needsAPNAdded NOTIFY mobileDataEnabledChanged)
public:

21
kded/CMakeLists.txt Normal file
View file

@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
# SPDX-License-Identifier: GPL-2.0-or-later
kcoreaddons_add_plugin(kded_plasma-mobile-start INSTALL_NAMESPACE "kf${QT_MAJOR_VERSION}/kded")
target_sources(kded_plasma-mobile-start PRIVATE
startdaemon.cpp
settings.cpp
config.h
utils.h
)
target_link_libraries(kded_plasma-mobile-start PRIVATE
Qt::Core
KF5::DBusAddons
KF5::ConfigWidgets
KF5::KIOGui
KF5::Notifications
KF5::Package
)

34
kded/config.h Normal file
View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <utility>
#include <QMap>
#include <QString>
#include <QVariant>
// kwinrc
const QMap<QString, QMap<QString, QVariant>> KWINRC_SETTINGS = {
{"Plugins", {{"blurEnabled", false}}},
{"Wayland", {{"InputMethod", "/usr/share/applications/com.github.maliit.keyboard.desktop"}, {"VirtualKeyboardEnabled", true}}},
{"Windows",
{
{"Placement", "Maximizing"},
}},
{"org.kde.kdecoration2", {{"NoPlugin", true}}},
};
// applications-blacklistrc
// NOTE: we only write these entries if they are not already defined in the config
const QMap<QString, QMap<QString, QVariant>> APPLICATIONS_BLACKLIST_SETTINGS = {
{"Applications",
{{"blacklist",
"cuttlefish,org.kde.plasma.themeexplorer,org.kde.klipper,ciborium,syncmonitorhelper,org.kde.okular,wordview,assistant,assistant-qt5,designer,designer-"
"qt5,linguist,linguist-qt5,org.kde.perusecreator,UserFeedbackConsole,org.kde.kuserfeedback-console,avahi-discover,bssh,bvnc,ktelnetservice5,qv4l2,"
"qvidcap"}}}};
// kdeglobals
// NOTE: we only write these entries if they are not already defined in the config
const QMap<QString, QMap<QString, QVariant>> KDEGLOBALS_SETTINGS = {{"General", {{"BrowserApplication", "angelfish"}}}};

View file

@ -0,0 +1,9 @@
{
"KPlugin": {
"Description": "Run initial tasks for Plasma Mobile during session startup",
"Name": "Plasma Mobile Start"
},
"X-KDE-Kded-autoload": true,
"X-KDE-Kded-phase": 1,
"X-KDE-Library": "plasma-mobile"
}

198
kded/settings.cpp Normal file
View file

@ -0,0 +1,198 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "settings.h"
#include "config.h"
#include "utils.h"
#include <KPackage/PackageLoader>
#include <KRuntimePlatform>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QProcess>
const QString CONFIG_FILE = QStringLiteral("plasmamobilerc");
const QString INITIAL_START_CONFIG_GROUP = QStringLiteral("InitialStart");
const QString SAVED_CONFIG_GROUP = QStringLiteral("SavedConfig");
const QString MOBILE_LOOK_AND_FEEL = QStringLiteral("org.kde.plasma.phone");
const QString LOOK_AND_FEEL_KEY = QStringLiteral("LookAndFeelPackage");
Settings::Settings(QObject *parent)
: QObject{parent}
, m_isMobilePlatform{KRuntimePlatform::runtimePlatform().contains(QStringLiteral("phone"))}
, m_initialStartConfig{KSharedConfig::openConfig(CONFIG_FILE, KConfig::SimpleConfig)}
, m_kwinrcConfig{KSharedConfig::openConfig(QStringLiteral("kwinrc"), KConfig::SimpleConfig)}
, m_appBlacklistConfig{KSharedConfig::openConfig(QStringLiteral("applications-blacklistrc"), KConfig::SimpleConfig)}
, m_kdeglobalsConfig{KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::SimpleConfig)}
{
}
Settings *Settings::self()
{
static Settings *settings = new Settings;
return settings;
}
bool Settings::shouldStartWizard()
{
if (!m_isMobilePlatform) {
return false;
}
const auto group = KConfigGroup{m_initialStartConfig, INITIAL_START_CONFIG_GROUP};
return !group.readEntry("wizardRun", false);
}
void Settings::setWizardFinished()
{
auto group = KConfigGroup{m_initialStartConfig, INITIAL_START_CONFIG_GROUP};
group.writeEntry("wizardRun", true, KConfigGroup::Notify);
m_initialStartConfig->sync();
}
void Settings::applyConfiguration()
{
if (!m_isMobilePlatform) {
qCDebug(LOGGING_CATEGORY) << "Configuration will not be applied, as the session is not Plasma Mobile.";
qCDebug(LOGGING_CATEGORY) << "Restoring any previously saved configuration...";
loadSavedConfiguration();
return;
}
qCDebug(LOGGING_CATEGORY) << "Checking and applying mobile configuration...";
applyMobileConfiguration();
}
void Settings::loadSavedConfiguration()
{
// check look and feel
loadSavedConfigSetting(m_kdeglobalsConfig, QStringLiteral("kdeglobals"), QStringLiteral("KDE"), LOOK_AND_FEEL_KEY);
// kwinrc
loadKeys(QStringLiteral("kwinrc"), m_kwinrcConfig, KWINRC_SETTINGS);
m_kwinrcConfig->sync();
reloadKWinConfig();
// applications-blacklistrc
loadKeys(QStringLiteral("applications-blacklistrc"), m_appBlacklistConfig, APPLICATIONS_BLACKLIST_SETTINGS);
m_appBlacklistConfig->sync();
// kdeglobals
loadKeys(QStringLiteral("kdeglobals"), m_kdeglobalsConfig, KDEGLOBALS_SETTINGS);
m_kdeglobalsConfig->sync();
// save our changes
m_initialStartConfig->sync();
}
void Settings::applyMobileConfiguration()
{
// check look and feel
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
if (package.path() != MOBILE_LOOK_AND_FEEL) {
// save it to be loaded when back on desktop
saveConfigSetting(QStringLiteral("kdeglobals"), QStringLiteral("KDE"), LOOK_AND_FEEL_KEY, package.path());
// ensure correct look and feel is applied
QProcess::execute("plasma-apply-lookandfeel", {"-a", MOBILE_LOOK_AND_FEEL});
}
// kwinrc
writeKeys(QStringLiteral("kwinrc"), m_kwinrcConfig, KWINRC_SETTINGS, false);
m_kwinrcConfig->sync();
reloadKWinConfig();
// applications-blacklistrc
// NOTE: we only write entries if they are not already defined in the config
writeKeys(QStringLiteral("applications-blacklistrc"), m_appBlacklistConfig, APPLICATIONS_BLACKLIST_SETTINGS, true);
m_appBlacklistConfig->sync();
// kdeglobals
// NOTE: we only write entries if they are not already defined in the config
writeKeys(QStringLiteral("kdeglobals"), m_kdeglobalsConfig, KDEGLOBALS_SETTINGS, true);
m_kdeglobalsConfig->sync();
// save our changes
m_initialStartConfig->sync();
}
void Settings::writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings, bool overwriteOnlyIfEmpty)
{
for (auto groupName : settings.keys()) {
auto group = KConfigGroup{config, groupName};
for (auto key : settings[groupName].keys()) {
if (!group.hasKey(key) || !overwriteOnlyIfEmpty) {
// save key
saveConfigSetting(fileName, groupName, key, group.readEntry(key));
// overwrite with mobile setting
group.writeEntry(key, settings[groupName][key], KConfigGroup::Notify);
}
}
}
}
void Settings::loadKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings)
{
for (auto groupName : settings.keys()) {
auto group = KConfigGroup{config, groupName};
for (auto key : settings[groupName].keys()) {
loadSavedConfigSetting(config, fileName, groupName, key);
}
}
}
// NOTE: this only saves a value if it hasn't already been saved
void Settings::saveConfigSetting(const QString &fileName, const QString &group, const QString &key, const QVariant value)
{
auto savedGroup = KConfigGroup{m_initialStartConfig, SAVED_CONFIG_GROUP};
auto fileGroup = KConfigGroup{&savedGroup, fileName};
auto keyGroup = KConfigGroup{&fileGroup, group};
if (!keyGroup.hasKey(key)) {
qCDebug(LOGGING_CATEGORY) << "In" << fileName << "set" << key << "to" << value;
keyGroup.writeEntry(key, value);
}
}
// NOTE: this deletes the stored value from the config after loading
void Settings::loadSavedConfigSetting(KSharedConfig::Ptr &config, const QString &fileName, const QString &group, const QString &key)
{
const auto savedGroup = KConfigGroup{m_initialStartConfig, SAVED_CONFIG_GROUP};
const auto fileGroup = KConfigGroup{&savedGroup, fileName};
auto keyGroup = KConfigGroup{&fileGroup, group};
if (!keyGroup.hasKey(key)) {
return;
}
const auto value = keyGroup.readEntry(key);
// write to real config
auto configGroup = KConfigGroup{config, group};
if (!configGroup.hasKey(key) || configGroup.readEntry(key) != value) {
qCDebug(LOGGING_CATEGORY) << "In" << fileName << "loading saved value of" << key << "which is" << value;
if (value.isEmpty()) { // delete blank entries!
configGroup.deleteEntry(key);
} else {
configGroup.writeEntry(key, value, KConfigGroup::Notify);
}
}
// remove saved config option
keyGroup.deleteEntry(key);
}
void Settings::reloadKWinConfig()
{
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
}

49
kded/settings.h Normal file
View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QObject>
#include <KConfigGroup>
#include <KSharedConfig>
class Settings : public QObject
{
Q_OBJECT
public:
Settings(QObject *parent = nullptr);
static Settings *self();
// whether the initial start wizard should be started
bool shouldStartWizard();
// set that the wizard has finished
void setWizardFinished();
// apply the configuration
void applyConfiguration();
private:
// loads the saved configuration, so it can be restored on desktop
void loadSavedConfiguration();
// applies our mobile configuration
void applyMobileConfiguration();
void writeKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings, bool overwriteOnlyIfEmpty);
void loadKeys(const QString &fileName, KSharedConfig::Ptr &config, const QMap<QString, QMap<QString, QVariant>> &settings);
void saveConfigSetting(const QString &fileName, const QString &group, const QString &key, const QVariant value);
void loadSavedConfigSetting(KSharedConfig::Ptr &config, const QString &fileName, const QString &group, const QString &key);
void reloadKWinConfig();
// whether this is Plasma Mobile
bool m_isMobilePlatform;
KSharedConfig::Ptr m_initialStartConfig;
KSharedConfig::Ptr m_kwinrcConfig;
KSharedConfig::Ptr m_appBlacklistConfig;
KSharedConfig::Ptr m_kdeglobalsConfig;
};

23
kded/startdaemon.cpp Normal file
View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include <KIO/CommandLauncherJob>
#include <KNotificationJobUiDelegate>
#include <KPluginFactory>
#include <KPluginLoader>
#include <QProcess>
#include "settings.h"
#include "startdaemon.h"
K_PLUGIN_CLASS_WITH_JSON(PlasmaMobileStartDaemon, "kded_plasma-mobile-start.json")
PlasmaMobileStartDaemon::PlasmaMobileStartDaemon(QObject *parent, const QList<QVariant> &)
: KDEDModule{parent}
{
// apply configuration
Settings::self()->applyConfiguration();
}
#include "startdaemon.moc"

14
kded/startdaemon.h Normal file
View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <kdedmodule.h>
class PlasmaMobileStartDaemon : public KDEDModule
{
Q_OBJECT
public:
PlasmaMobileStartDaemon(QObject *parent, const QList<QVariant> &);
};

12
kded/utils.h Normal file
View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QLoggingCategory>
static const QLoggingCategory &LOGGING_CATEGORY()
{
static const QLoggingCategory category("plasma-mobile-initial-start");
return category;
}

View file

@ -15,11 +15,3 @@ Theme=breeze
[kdeglobals][General]
ColorScheme=BreezeLight
Name=Breeze
## kwinrc
[kwinrc][Windows]
Placement=Maximizing
[kwinrc][org.kde.kdecoration2]
NoPlugin=true