Commit 5bbbe087 authored by David Redondo's avatar David Redondo 🏎

Port to ManagedConfigModule

The settings are now read via KConfigXT, with a trick to follow the config
scheme of SDDM. One KConfig object is used to read the defaults and another one
to read the actual config values, see SddmSettingsBase.h for details.
The UI is now entirely in Qml and follows the GridviewKCM style. The preview pane
has been moved into a Dialog. The "Advanced" has been moved into a page called
"Behavior" and the sync options live now in a OverlaySheet accessible from the main
page as does the background configuration.
BUG:403530
BUG:407689
BUG:411004
BUG:411504
BUG:419327
BUG:424639
parent b616f247
......@@ -25,6 +25,8 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS
KIO
Archive
NewStuff
Declarative
KCMUtils
)
if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
......
......@@ -5,43 +5,37 @@ set(XSESSIONS_DIR "${CMAKE_INSTALL_PREFIX}/share/xsessions"
set(WAYLAND_SESSIONS_DIR "${CMAKE_INSTALL_PREFIX}/share/wayland-sessions" CACHE PATH "Path of the wayland sessions")
configure_file(config.h.in config.h IMMEDIATE @ONLY)
# add_subdirectory(configwidgets)
include_directories(configwidgets)
set(SDDM_KCM_SRCS
sddmkcm.cpp
themeconfig.cpp
themesmodel.cpp
thememetadata.cpp
themesdelegate.cpp
advancedconfig.cpp
usersmodel.cpp
sessionmodel.cpp
configwidgets/selectimagebutton.cpp
sddmdata.cpp
sddmsettingsbase.cpp
)
set(SDDM_KCM_UI
ui/themeconfig.ui
ui/advancedconfig.ui)
ki18n_wrap_ui(SDDM_KCM_SRCS ${SDDM_KCM_UI})
add_library(kcm_sddm MODULE ${SDDM_KCM_SRCS})
kconfig_add_kcfg_files(kcm_sddm sddmsettings.kcfgc GENERATE_MOC)
target_compile_definitions(kcm_sddm PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}")
target_link_libraries(kcm_sddm
Qt5::Widgets
Qt5::Quick
Qt5::QuickWidgets
KF5::I18n
KF5::ConfigWidgets
KF5::AuthCore
KF5::KIOWidgets
KF5::KCMUtils
KF5::NewStuff
KF5::QuickAddons
)
install(TARGETS kcm_sddm DESTINATION ${CMAKE_INSTALL_PLUGINDIR})
kcoreaddons_desktop_to_json(kcm_sddm "../kcm_sddm.desktop")
install(FILES qml/main.qml DESTINATION ${CMAKE_INSTALL_DATADIR}/sddm-kcm)
kpackage_install_package(package kcm_sddm kcms)
install(TARGETS kcm_sddm DESTINATION ${CMAKE_INSTALL_PLUGINDIR}/kcms)
/*
Copyright 2019 Filip Fila <filipfila.kde@gmail.com>
Copyright 2013 by Reza Fatahilah Shah <rshah0385@kireihana.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "advancedconfig.h"
#include "ui_advancedconfig.h"
#include "config.h"
#include "sessionmodel.h"
#include "usersmodel.h"
#include <QDebug>
#include <QFontDatabase>
#include <QIntValidator>
#include <QStandardPaths>
#include <KAuth/KAuthActionReply>
#include <KAuthExecuteJob>
#include <KConfigGroup>
#include <KMessageBox>
#include <KUser>
const int MIN_UID = 1000;
const int MAX_UID = 60000;
AdvancedConfig::AdvancedConfig(const KSharedConfigPtr &config, QWidget *parent) :
QWidget(parent),
mConfig(config)
{
configUi = new Ui::AdvancedConfig();
configUi->setupUi(this);
configUi->syncExplanation->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
configUi->syncWarning->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
load();
connect(configUi->userList, SIGNAL(activated(int)), SIGNAL(changed()));
connect(configUi->sessionList, SIGNAL(activated(int)), SIGNAL(changed()));
connect(configUi->haltCommand, SIGNAL(textChanged(QString)), SIGNAL(changed()));
connect(configUi->rebootCommand, SIGNAL(textChanged(QString)), SIGNAL(changed()));
connect(configUi->minimumUid, SIGNAL(textChanged(QString)), SIGNAL(changed()));
connect(configUi->minimumUid, &QLineEdit::textChanged, this, &AdvancedConfig::slotUidRangeChanged);
connect(configUi->maximumUid, SIGNAL(textChanged(QString)), SIGNAL(changed()));
connect(configUi->maximumUid, &QLineEdit::textChanged, this, &AdvancedConfig::slotUidRangeChanged);
connect(configUi->autoLogin, &QCheckBox::toggled, this, [this] { emit changed(); });
connect(configUi->reloginAfterQuit, &QAbstractButton::toggled, this, [this] { emit changed(); });
connect(configUi->syncSettings, &QPushButton::clicked, this, &AdvancedConfig::syncSettingsChanged);
connect(configUi->resetSettings, &QPushButton::clicked, this, &AdvancedConfig::resetSettingsChanged);
}
AdvancedConfig::~AdvancedConfig()
{
delete configUi;
}
void AdvancedConfig::load()
{
//User list
int minUid, maxUid;
minUid = mConfig->group("Users").readEntry("MinimumUid", MIN_UID);
maxUid = mConfig->group("Users").readEntry("MaximumUid", MAX_UID);
userModel = new UsersModel(this);
configUi->userList->setModel(userModel);
userModel->populate( minUid, maxUid );
sessionModel = new SessionModel(this);
configUi->sessionList->setModel(sessionModel);
const QString currentUser = mConfig->group("Autologin").readEntry("User", "");
configUi->userList->setCurrentIndex(userModel->indexOf(currentUser));
const QString autologinSession = mConfig->group("Autologin").readEntry("Session", "");
configUi->sessionList->setCurrentIndex(sessionModel->indexOf(autologinSession));
configUi->autoLogin->setChecked(!currentUser.isEmpty());
configUi->reloginAfterQuit->setChecked(mConfig->group("Autologin").readEntry("Relogin", false));
QValidator *uidValidator = new QIntValidator(MIN_UID, MAX_UID, configUi->minimumUid);
configUi->minimumUid->setValidator(uidValidator);
configUi->minimumUid->setText(QString::number(minUid));
configUi->maximumUid->setValidator(uidValidator);
configUi->maximumUid->setText(QString::number(maxUid));
//Commands
configUi->haltCommand->setUrl(QUrl::fromLocalFile(mConfig->group("General").readEntry("HaltCommand")));
configUi->rebootCommand->setUrl(QUrl::fromLocalFile(mConfig->group("General").readEntry("RebootCommand")));
}
void AdvancedConfig::save(QVariantMap &args)
{
args[QStringLiteral("kde_settings.conf/Autologin/User")] = ( configUi->autoLogin->isChecked() ) ? configUi->userList->currentText() : QString();
args[QStringLiteral("kde_settings.conf/Autologin/Session")] = ( configUi->autoLogin->isChecked() ) ? configUi->sessionList->currentData() : QString();
args[QStringLiteral("kde_settings.conf/Autologin/Relogin")] = configUi->reloginAfterQuit->isChecked();
//TODO session
int minUid = configUi->minimumUid->text().toInt();
int maxUid = configUi->maximumUid->text().toInt();
if (isUidRangeValid(minUid, maxUid)) {
args[QStringLiteral("kde_settings.conf/Users/MinimumUid")] = configUi->minimumUid->text();
args[QStringLiteral("kde_settings.conf/Users/MaximumUid")] = configUi->maximumUid->text();
}
args[QStringLiteral("kde_settings.conf/General/HaltCommand")] = configUi->haltCommand->url().toLocalFile();
args[QStringLiteral("kde_settings.conf/General/RebootCommand")] = configUi->rebootCommand->url().toLocalFile();
}
void AdvancedConfig::slotUidRangeChanged()
{
int minUid = configUi->minimumUid->text().toInt();
int maxUid = configUi->maximumUid->text().toInt();
if (!isUidRangeValid(minUid, maxUid)) {
return;
}
userModel->populate(minUid, maxUid);
}
bool AdvancedConfig::isUidRangeValid(int minUid, int maxUid) const
{
if (minUid < 0 || minUid > maxUid)
return false;
return true;
}
void AdvancedConfig::syncSettingsChanged()
{
// initial check for sddm user; abort if user not present
// we have to check with QString and isEmpty() instead of QDir and exists() because
// QDir returns "." and true for exists() in the case of a non-existent user;
QString sddmHomeDirPath = KUser("sddm").homeDir();
if (sddmHomeDirPath.isEmpty()) {
KMessageBox::error(this, QStringLiteral("Cannot proceed, user 'sddm' does not exist. Please check your SDDM install."));
return;
}
// read Plasma values
KConfig cursorConfig(QStringLiteral("kcminputrc"));
KConfigGroup cursorConfigGroup(&cursorConfig, "Mouse");
QVariant cursorTheme = cursorConfigGroup.readEntry("cursorTheme", QString());
KConfig dpiConfig(QStringLiteral("kcmfonts"));
KConfigGroup dpiConfigGroup(&dpiConfig, "General");
QString dpiValue = dpiConfigGroup.readEntry("forceFontDPI");
QString dpiArgument = QStringLiteral("-dpi ") + dpiValue;
KConfig numLockConfig(QStringLiteral("kcminputrc"));
KConfigGroup numLockConfigGroup(&numLockConfig, "Keyboard");
QString numLock = numLockConfigGroup.readEntry("NumLock");
// define paths
const QString fontconfigPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("fontconfig"), QStandardPaths::LocateDirectory);
const QString kdeglobalsPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
const QString plasmarcPath = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("plasmarc"));
// send values and paths to helper, debug if it fails
QVariantMap args;
args[QStringLiteral("kde_settings.conf")] = QString {QLatin1String(SDDM_CONFIG_DIR "/") + QStringLiteral("kde_settings.conf")};
args[QStringLiteral("sddm.conf")] = QLatin1String(SDDM_CONFIG_FILE);
if (!cursorTheme.isNull()) {
args[QStringLiteral("kde_settings.conf/Theme/CursorTheme")] = cursorTheme;
}
else {
qDebug() << "Cannot find cursor theme value.";
}
if (!dpiValue.isEmpty()) {
args[QStringLiteral("kde_settings.conf/X11/ServerArguments")] = dpiArgument;
}
else {
qDebug() << "Cannot find scaling DPI value.";
}
if (!numLock.isEmpty()) {
if (numLock == QStringLiteral("0")) {
args[QStringLiteral("kde_settings.conf/General/Numlock")] = QStringLiteral("on");
}
else if (numLock == QStringLiteral("1")) {
args[QStringLiteral("kde_settings.conf/General/Numlock")] = QStringLiteral("off");
}
else if (numLock == QStringLiteral("2")) {
args[QStringLiteral("kde_settings.conf/General/Numlock")] = QStringLiteral("none");
}
}
else {
qDebug() << "Cannot find NumLock value.";
}
if (!fontconfigPath.isEmpty()) {
args[QStringLiteral("fontconfig")] = fontconfigPath;
}
else {
qDebug() << "Cannot find fontconfig folder.";
}
if (!kdeglobalsPath.isEmpty()) {
args[QStringLiteral("kdeglobals")] = kdeglobalsPath;
}
else {
qDebug() << "Cannot find kdeglobals file.";
}
if (!plasmarcPath.isEmpty()) {
args[QStringLiteral("plasmarc")] = plasmarcPath;
}
else {
qDebug() << "Cannot find plasmarc file.";
}
KAuth::Action syncAction(QStringLiteral("org.kde.kcontrol.kcmsddm.sync"));
syncAction.setHelperId(QStringLiteral("org.kde.kcontrol.kcmsddm"));
syncAction.setArguments(args);
auto job = syncAction.execute();
job->exec();
if (job->error()){
qDebug() << "Synchronization failed";
qDebug() << job->errorString();
qDebug() << job->errorText();
if (!job->errorText().isEmpty()) {
KMessageBox::error(this, job->errorText());
}
} else {
Q_EMIT changed(false);
qDebug() << "Synchronization successful";
}
}
void AdvancedConfig::resetSettingsChanged()
{
// initial check for sddm user; abort if user not present
// we have to check with QString and isEmpty() instead of QDir and exists() because
// QDir returns "." and true for exists() in the case of a non-existent user
QString sddmHomeDirPath = KUser("sddm").homeDir();
if (sddmHomeDirPath.isEmpty()) {
KMessageBox::error(this, QStringLiteral("Cannot proceed, user 'sddm' does not exist. Please check your SDDM install."));
return;
}
// send paths to helper
QVariantMap args;
args[QStringLiteral("kde_settings.conf")] = QString {QLatin1String(SDDM_CONFIG_DIR "/") + QStringLiteral("kde_settings.conf")};
args[QStringLiteral("sddm.conf")] = QLatin1String(SDDM_CONFIG_FILE);
args[QStringLiteral("kde_settings.conf/Theme/CursorTheme")] = QVariant();
args[QStringLiteral("kde_settings.conf/X11/ServerArguments")] = QVariant();
args[QStringLiteral("kde_settings.conf/General/Numlock")] = QVariant();
KAuth::Action resetAction(QStringLiteral("org.kde.kcontrol.kcmsddm.reset"));
resetAction.setHelperId(QStringLiteral("org.kde.kcontrol.kcmsddm"));
resetAction.setArguments(args);
auto job = resetAction.execute();
job->exec();
if (job->error()){
qDebug() << "Reset failed";
qDebug() << job->errorString();
qDebug() << job->errorText();
if (!job->errorText().isEmpty()) {
KMessageBox::error(this, job->errorText());
}
} else {
Q_EMIT changed(false);
qDebug() << "Reset successful";
}
}
/*
Copyright 2019 Filip Fila <filipfila.kde@gmail.com>
Copyright 2013 by Reza Fatahilah Shah <rshah0385@kireihana.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ADVANCEDCONFIG_H
#define ADVANCEDCONFIG_H
#include <QWidget>
#include <KSharedConfig>
namespace Ui {
class AdvancedConfig;
}
class SortProxyModel;
class UsersModel;
class SessionModel;
class AdvancedConfig : public QWidget
{
Q_OBJECT
public:
explicit AdvancedConfig(const KSharedConfigPtr &config, QWidget *parent = nullptr);
~AdvancedConfig();
void save(QVariantMap &args);
Q_SIGNALS:
void changed(bool changed=true);
public Q_SLOTS:
void syncSettingsChanged();
void resetSettingsChanged();
private Q_SLOTS:
void slotUidRangeChanged();
private:
void load();
bool isUidRangeValid(int minUid, int maxUid) const;
private:
Ui::AdvancedConfig *configUi;
KSharedConfigPtr mConfig;
UsersModel *userModel;
SessionModel *sessionModel;
};
#endif // ADVANCEDCONFIG_H
/*
* Button for selecting an image.
*
* Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com>
* Copyright (C) 2011, 2012, 2014 David Edmundson <kde@davidedmundson.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "selectimagebutton.h"
#include <QFileDialog>
#include <QImageReader>
#include <QMenu>
#include <QUrl>
#include <QWidgetAction>
#include <KLocalizedString>
#include <KMessageBox>
SelectImageButton::SelectImageButton(QWidget *parent)
: QToolButton(parent)
{
QMenu *menu = new QMenu(this);
setPopupMode(QToolButton::InstantPopup);
setIconSize(QSize(64,64));
menu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Load from file..."), this, &SelectImageButton::onLoadImageFromFile);
menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), i18n("Clear Image"), this, &SelectImageButton::onClearImage);
setMenu(menu);
onClearImage();
}
SelectImageButton::~SelectImageButton()
{
}
void SelectImageButton::setImagePath(const QString &imagePath) {
m_imagePath = imagePath;
QPixmap image(imagePath);
if (! image.isNull()) {
QIcon imageIcon;
//scale oversized avatars to fit, but don't stretch smaller images
imageIcon.addPixmap(image.scaled(iconSize().boundedTo(image.size()), Qt::KeepAspectRatio));
setIcon(imageIcon);
} else {
setIcon(QIcon::fromTheme(QStringLiteral("image-x-generic")));
}
Q_EMIT imagePathChanged(m_imagePath);
}
QString SelectImageButton::imagePath() const {
return m_imagePath;
}
void SelectImageButton::onLoadImageFromFile()
{
QPointer<QFileDialog> dialog = new QFileDialog(this, i18nc("@title:window", "Select Image"));
dialog->setAcceptMode(QFileDialog::AcceptOpen);
dialog->setFileMode(QFileDialog::ExistingFile);
const QList<QByteArray> supportedMimeTypes = QImageReader::supportedMimeTypes();
QStringList mimeTypeFilter;
mimeTypeFilter.reserve(supportedMimeTypes.count());
for(const QByteArray &b: supportedMimeTypes) {
mimeTypeFilter.append(QString::fromLatin1(b));
}
dialog->setMimeTypeFilters(mimeTypeFilter);
int rc = dialog->exec();
if (rc == QDialog::Accepted && dialog->selectedFiles().count() > 0) {
setImagePath(dialog->selectedFiles().first());
}
delete dialog;
}
void SelectImageButton::onClearImage()
{
setImagePath(QString());
}
/*
* Button representing user's Avatar
*
* Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com>
* Copyright (C) 2011, 2012 David Edmundson <kde@davidedmundson.co.uk>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef SELECTIMAGEBUTTON_H
#define SELECTIMAGEBUTTON_H
#include <QToolButton>
class SelectImageButton : public QToolButton
{
Q_OBJECT
Q_PROPERTY(QString imagePath READ imagePath WRITE setImagePath NOTIFY imagePathChanged USER true)
public:
explicit SelectImageButton(QWidget* parent = nullptr);
virtual ~SelectImageButton();
//we use QString rather that KUrl because it seems to work better with KConfigXT
void setImagePath(const QString &imagePath);
QString imagePath() const;
Q_SIGNALS:
void imagePathChanged(const QString&);
private Q_SLOTS:
void onLoadImageFromFile();
void onClearImage();
private:
QString m_imagePath;
};
#endif //AVATAR_BUTTON_H
/*
Copyright 2020 David Redondo <kde@david-redondo.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 3 of
the license or (at your option) at any later version that is
accepted by the membership of KDE e.V. (or its successor
approved by the membership of KDE e.V.), which shall act as a
proxy as defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.15
import org.kde.kcm 1.5 as KCM
import org.kde.kirigami 2.14 as Kirigami
import org.kde.kitemmodels 1.0 as ItemModels
import org.kde.private.kcms.sddm 1.0
Kirigami.Page {
title: i18nc("@title", "Behavior")
Kirigami.FormLayout {
width: parent.width
RowLayout {
Kirigami.FormData.label: i18nc("option:check", "Automatically log in:")
QQC2.CheckBox {
id: autologinBox