diff --git a/CMakeLists.txt b/CMakeLists.txt index 492dd16..22fca1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,40 +1,40 @@ project(user-manager) cmake_minimum_required(VERSION 2.8.12) set(PROJECT_VERSION "5.17.80") set(QT_MIN_VERSION "5.12.0") set(KF5_MIN_VERSION "5.62.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) SET(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ${CMAKE_MODULE_PATH}) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets DBus) -find_package(KF5 ${KF5_MIN_VERSION} REQUIRED WidgetsAddons CoreAddons I18n Config ConfigWidgets KCMUtils KIO IconThemes Auth) +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED WidgetsAddons CoreAddons I18n Config ConfigWidgets KCMUtils KIO Auth) find_package(PWQuality REQUIRED) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEClangFormat) include(ECMQtDeclareLoggingCategory) include_directories(${PWQUALITY_INCLUDE_DIR}) # Set KI18n translation domain add_definitions(-DTRANSLATION_DOMAIN=\"user_manager\") if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x060000) endif() add_subdirectory(src) install(FILES user-manager.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3312f7e..edeb158 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,65 +1,64 @@ set(user_manager_SRCS lib/accountmodel.cpp lib/modeltest.cpp lib/usersessions.cpp usermanager.cpp accountinfo.cpp createavatarjob.cpp passworddialog.cpp avatargallery.cpp ) set_source_files_properties(lib/org.freedesktop.Accounts.xml PROPERTIES NO_NAMESPACE TRUE) set_source_files_properties(lib/org.freedesktop.Accounts.User.xml PROPERTIES NO_NAMESPACE TRUE) qt5_add_dbus_interface(user_manager_SRCS lib/org.freedesktop.Accounts.xml accounts_interface ) qt5_add_dbus_interface(user_manager_SRCS lib/org.freedesktop.Accounts.User.xml user_interface ) set(login1_manager_xml lib/org.freedesktop.login1.Manager.xml) set_source_files_properties(${login1_manager_xml} PROPERTIES INCLUDE "lib/usersessions.h") qt5_add_dbus_interface(user_manager_SRCS ${login1_manager_xml} login1_interface ) ki18n_wrap_ui(user_manager_SRCS kcm.ui account.ui password.ui avatargallery.ui) ecm_qt_declare_logging_category(user_manager_SRCS HEADER user_manager_debug.h IDENTIFIER USER_MANAGER_LOG CATEGORY_NAME log_user_manager) add_library(user_manager MODULE ${user_manager_SRCS}) target_link_libraries(user_manager Qt5::Core Qt5::Widgets Qt5::DBus KF5::AuthCore KF5::WidgetsAddons KF5::CoreAddons KF5::I18n KF5::ConfigCore KF5::ConfigWidgets KF5::KCMUtils KF5::KIOCore - KF5::IconThemes ${PWQUALITY_LIBRARY} ) if (NOT APPLE) target_link_libraries(user_manager crypt) endif() install(TARGETS user_manager DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES user_manager.desktop DESTINATION ${SERVICES_INSTALL_DIR}) install(DIRECTORY pics/ DESTINATION ${DATA_INSTALL_DIR}/user-manager/avatars) diff --git a/src/accountinfo.cpp b/src/accountinfo.cpp index 90e43be..ac65f54 100644 --- a/src/accountinfo.cpp +++ b/src/accountinfo.cpp @@ -1,478 +1,476 @@ /************************************************************************************* * Copyright (C) 2013 by Alejandro Fiestas Olivares * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "accountinfo.h" #include "ui_account.h" #include "createavatarjob.h" #include "passworddialog.h" #include "lib/accountmodel.h" #include "avatargallery.h" #include #include #include #include #include #include #include #include #include "user_manager_debug.h" #include #include -#include #include #include AccountInfo::AccountInfo(AccountModel* model, QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) , m_info(new Ui::AccountInfo()) , m_model(model) { m_info->setupUi(this); // ensure avatar user icon use correct device pixel ratio const qreal dpr = qApp->devicePixelRatio(); m_model->setDpr(dpr); connect(m_info->username, &QLineEdit::textEdited, this, &AccountInfo::hasChanged); connect(m_info->realName, &QLineEdit::textEdited, this, &AccountInfo::hasChanged); connect(m_info->email, &QLineEdit::textEdited, this, &AccountInfo::hasChanged); connect(m_info->administrator, &QAbstractButton::clicked, this, &AccountInfo::hasChanged); connect(m_info->automaticLogin, &QAbstractButton::clicked, this, &AccountInfo::hasChanged); connect(m_info->changePasswordButton, &QPushButton::clicked, this, &AccountInfo::changePassword); connect(m_model, &QAbstractItemModel::dataChanged, this, &AccountInfo::dataChanged); m_info->face->setPopupMode(QToolButton::InstantPopup); QMenu* menu = new QMenu(this); QAction *gallery = new QAction(i18n("Choose from Gallery..."), this); gallery->setIcon(QIcon::fromTheme(QStringLiteral("shape-choose"))); // TODO proper icon connect(gallery, &QAction::triggered, this, &AccountInfo::openGallery); QAction *openAvatar = new QAction(i18n("Load from file..."), this); openAvatar->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder"))); connect(openAvatar, &QAction::triggered, this, &AccountInfo::openAvatarSlot); QAction *editClear = new QAction(i18n("Clear Avatar"), this); editClear->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear"))); connect(editClear, &QAction::triggered, this, &AccountInfo::clearAvatar); menu->addAction(gallery); menu->addAction(openAvatar); menu->addAction(editClear); - int iconSizeX = IconSize(KIconLoader::Dialog); + int iconSizeX = style()->pixelMetric(QStyle::PM_LargeIconSize); QSize iconSize(iconSizeX, iconSizeX); m_info->face->setIconSize(iconSize); m_info->face->setMinimumSize(iconSize); m_info->face->setMenu(menu); int size = QFontMetrics(QFontDatabase::systemFont(QFontDatabase::FixedFont)).xHeight() * 29; m_info->username->setMinimumWidth(size); m_info->realName->setMinimumWidth(size); m_info->email->setMinimumWidth(size); int pixmapSize = m_info->username->sizeHint().height(); m_negative = QIcon::fromTheme(QStringLiteral("dialog-cancel")).pixmap(pixmapSize, pixmapSize); } AccountInfo::~AccountInfo() { delete m_info; } void AccountInfo::setModelIndex(const QModelIndex& index) { if (!index.isValid() || m_index == index) { return; } m_index = index; m_infoToSave.clear(); loadFromModel(); } QModelIndex AccountInfo::modelIndex() const { return m_index; } void AccountInfo::loadFromModel() { QString username = m_model->data(m_index, AccountModel::Username).toString(); if (!username.isEmpty()) { m_info->username->setDisabled(true);//Do not allow to change the username m_info->changePasswordButton->setText(i18nc("@label:button", "Change Password")); } else { m_info->username->setDisabled(false); m_info->changePasswordButton->setText(i18nc("@label:button", "Set Password")); } m_info->username->setText(username); m_info->face->setIcon(QIcon(m_model->data(m_index, AccountModel::Face).value())); m_info->realName->setText(m_model->data(m_index, AccountModel::RealName).toString()); m_info->email->setText(m_model->data(m_index, AccountModel::Email).toString()); m_info->administrator->setChecked(m_model->data(m_index, AccountModel::Administrator).toBool()); m_info->automaticLogin->setChecked(m_model->data(m_index, AccountModel::AutomaticLogin).toBool()); } bool AccountInfo::save() { if (m_infoToSave.isEmpty()) { return false; } qCDebug(USER_MANAGER_LOG) << "Saving on Index: " << m_index.row(); QList failed; if (m_infoToSave.contains(AccountModel::Username) && !m_model->setData(m_index, m_infoToSave[AccountModel::Username], AccountModel::Username)) { failed.append(AccountModel::Username); } if (m_infoToSave.contains(AccountModel::RealName) && !m_model->setData(m_index, m_infoToSave[AccountModel::RealName], AccountModel::RealName)) { failed.append(AccountModel::RealName); } if (m_infoToSave.contains(AccountModel::Email) && !m_model->setData(m_index, m_infoToSave[AccountModel::Email], AccountModel::Email)) { failed.append(AccountModel::Email); } if (m_infoToSave.contains(AccountModel::Administrator) && !m_model->setData(m_index, m_info->administrator->isChecked(), AccountModel::Administrator)) { failed.append(AccountModel::Administrator); } if (m_infoToSave.contains(AccountModel::AutomaticLogin) && !m_model->setData(m_index, m_info->automaticLogin->isChecked(), AccountModel::AutomaticLogin)) { failed.append(AccountModel::AutomaticLogin); } if (m_infoToSave.contains(AccountModel::Password)) { if (!m_model->setData(m_index, m_infoToSave[AccountModel::Password], AccountModel::Password)) { failed.append(AccountModel::Password); } } if (m_infoToSave.contains(AccountModel::Face)) { const QString path = m_infoToSave[AccountModel::Face].toString(); //we want to save the face using AccountsService, but for backwards compatibility we also //save the icon into ~/.face for old apps/DisplayManagers that still expect that //This works when setting a face as the current user, but doesn't make sense when setting the icon //of another user. const QString username = m_model->data(m_index, AccountModel::Username).toString(); if (username != KUser().loginName()) { m_model->setData(m_index, QVariant(path), AccountModel::Face); } else { QString faceFile = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); faceFile.append(QLatin1String("/.face")); QFile::remove(faceFile); KIO::CopyJob* copyJob = KIO::copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(faceFile), KIO::HideProgressInfo); connect(copyJob, &KJob::finished, this, &AccountInfo::avatarModelChanged); copyJob->setUiDelegate(nullptr); copyJob->setUiDelegateExtension(nullptr); copyJob->start(); QString faceFile2 = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); faceFile2.append(QLatin1String("/.face.icon")); QFile::remove(faceFile2); QFile::link(faceFile, faceFile2); } } if (!failed.isEmpty()) { qCDebug(USER_MANAGER_LOG) << "Failed Roles: " << failed; } m_info->username->setEnabled(false); m_infoToSave.clear(); return true; } void AccountInfo::hasChanged() { m_info->nameValidation->setPixmap(m_positive); m_info->usernameValidation->setPixmap(m_positive); m_info->emailValidation->setPixmap(m_positive); QMap infoToSave; const QString name = cleanName(m_info->realName->text()); if (name != m_model->data(m_index, AccountModel::RealName).toString()) { if (validateName(name)) { infoToSave.insert(AccountModel::RealName, name); } } const QString username = cleanUsername(m_info->username->text()); if (username != m_model->data(m_index, AccountModel::Username).toString()) { if (validateUsername(username)) { infoToSave.insert(AccountModel::Username, username); } } const QString email = cleanEmail(m_info->email->text()); if (email != m_model->data(m_index, AccountModel::Email).toString()) { if (validateEmail(email)) { infoToSave.insert(AccountModel::Email, email); } } if (m_info->administrator->isChecked() != m_model->data(m_index, AccountModel::Administrator).toBool()) { infoToSave.insert(AccountModel::Administrator, m_info->administrator->isChecked()); } if (m_info->automaticLogin->isChecked() != m_model->data(m_index, AccountModel::AutomaticLogin).toBool()) { infoToSave.insert(AccountModel::AutomaticLogin, m_info->automaticLogin->isChecked()); } if (m_infoToSave.contains(AccountModel::Face)) { infoToSave[AccountModel::Face] = m_infoToSave[AccountModel::Face]; } if (m_infoToSave.contains(AccountModel::Password)) { infoToSave[AccountModel::Password] = m_infoToSave[AccountModel::Password]; } m_infoToSave = infoToSave; Q_EMIT changed(!m_infoToSave.isEmpty()); } QString AccountInfo::cleanName(QString name) const { return name; } bool AccountInfo::validateName(const QString& name) const { if (!name.isEmpty() && name.trimmed().isEmpty()) { m_info->realName->clear(); return false; } return true; } QString AccountInfo::cleanUsername(QString username) { if (username.isEmpty()) { return username; } if (username[0].isUpper()) { username[0] = username[0].toLower(); } username.remove(QLatin1Char(' ')); m_info->username->setText(username); return username; } bool AccountInfo::validateUsername(const QString &username) const { if (username.isEmpty()) { return false; } const QByteArray userchar = username.toUtf8(); if (getpwnam(userchar.constData()) != nullptr) { m_info->usernameValidation->setPixmap(m_negative); m_info->usernameValidation->setToolTip(i18n("This username is already used")); return false; } QString errorTooltip; char first = userchar.at(0); bool valid = (first >= 'a' && first <= 'z'); if (!valid) { errorTooltip.append(i18n("The username must start with a letter")); errorTooltip.append(QLatin1Char('\n')); } for (const char c : userchar) { valid = ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_') || (c == '.') || (c == '-') ); if (!valid) { break; } } if (!valid) { errorTooltip.append(i18n("The username can contain only letters, numbers, score, underscore and dot")); errorTooltip.append(QLatin1Char('\n')); } static const long MAX_USER_NAME_LENGTH = []() { long result = sysconf(_SC_LOGIN_NAME_MAX); if (result < 0) { qWarning("Could not query LOGIN_NAME_MAX, defaulting to 32"); result = 32; } return result; }(); if (username.size() > MAX_USER_NAME_LENGTH) { errorTooltip.append(i18n("The username is too long")); valid = false; } if (!errorTooltip.isEmpty()) { m_info->usernameValidation->setPixmap(m_negative); m_info->usernameValidation->setToolTip(errorTooltip); return false; } return true; } QString AccountInfo::cleanEmail(QString email) { if (email.isEmpty()) { return email; } email = email.toLower().remove(QLatin1Char(' ')); int pos = m_info->email->cursorPosition(); m_info->email->setText(email); m_info->email->setCursorPosition(pos); return email; } bool AccountInfo::validateEmail(const QString& email) const { if (email.isEmpty()) { return false; } QString strPatt = QStringLiteral("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,63}\\b"); QRegExp rx(strPatt); rx.setCaseSensitivity(Qt::CaseInsensitive); rx.setPatternSyntax(QRegExp::RegExp); if (!rx.exactMatch(email)) { m_info->emailValidation->setPixmap(m_negative); m_info->emailValidation->setToolTip(i18n("This e-mail address is incorrect")); } return true; } void AccountInfo::dataChanged(const QModelIndex& index) { if (m_index != index) { return; } //If we have no username, we assume this was user-new if (m_info->username->text().isEmpty()) { loadFromModel(); return; } hasChanged(); } void AccountInfo::openGallery() { QScopedPointer gallery(new AvatarGallery()); if (gallery->exec() != QDialog::Accepted) { return; } QString path = gallery->url().toLocalFile(); m_info->face->setIcon(QIcon(path)); m_infoToSave.insert(AccountModel::Face, path); Q_EMIT changed(true); } QStringList AccountInfo::imageFormats() const { QStringList result; const QList supportedMimes = QImageReader::supportedMimeTypes(); for (const QByteArray &b: supportedMimes) { if (! b.isEmpty()) result.append(QString::fromLatin1(b)); } return result; } void AccountInfo::openAvatarSlot() { QFileDialog dlg(this, i18nc("@title:window", "Choose Image"), QDir::homePath()); dlg.setMimeTypeFilters(imageFormats()); dlg.setAcceptMode(QFileDialog::AcceptOpen); dlg.setFileMode(QFileDialog::ExistingFile); if (dlg.exec() != QDialog::Accepted) { return; } QUrl url = QUrl::fromLocalFile(dlg.selectedFiles().first()); CreateAvatarJob *job = new CreateAvatarJob(this); connect(job, &KJob::finished, this, &AccountInfo::avatarCreated); job->setUrl(url); job->start(); } void AccountInfo::avatarCreated(KJob* job) { if (! job->error()) { qCDebug(USER_MANAGER_LOG) << "Avatar created"; CreateAvatarJob *aJob = qobject_cast(job); m_info->face->setIcon(QIcon(aJob->avatarPath())); m_infoToSave.insert(AccountModel::Face, aJob->avatarPath()); Q_EMIT changed(true); } } void AccountInfo::avatarModelChanged(KJob* job) { KIO::CopyJob* cJob = qobject_cast(job); m_model->setData(m_index, QVariant(cJob->destUrl().path()), AccountModel::Face); m_info->face->setIcon(QIcon(m_model->data(m_index, AccountModel::Face).value())); // If there is a leftover temp file, remove it if (cJob->srcUrls().constFirst().path().startsWith(QLatin1String("/tmp/"))) { QFile::remove(cJob->srcUrls().constFirst().path()); } } void AccountInfo::clearAvatar() { - QSize icon(IconSize(KIconLoader::Dialog), IconSize(KIconLoader::Dialog)); m_info->face->setIcon(QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(48, 48)); m_infoToSave.insert(AccountModel::Face, QString()); Q_EMIT changed(true); } void AccountInfo::changePassword() { QScopedPointer dialog(new PasswordDialog(this)); dialog->setUsername(m_model->data(m_index, AccountModel::Username).toByteArray()); dialog->setModal(true); if (!dialog->exec()) { return; } m_infoToSave[AccountModel::Password] = dialog->password(); Q_EMIT changed(true); } diff --git a/src/lib/accountmodel.cpp b/src/lib/accountmodel.cpp index 155fedb..d39ad2a 100644 --- a/src/lib/accountmodel.cpp +++ b/src/lib/accountmodel.cpp @@ -1,549 +1,550 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "accountmodel.h" #include "usersessions.h" #include "accounts_interface.h" #include "user_interface.h" +#include #include +#include #include "user_manager_debug.h" #include -#include #include #include #include #include #include #include #define SDDM_CONFIG_FILE "/etc/sddm.conf" AutomaticLoginSettings::AutomaticLoginSettings() { KConfig config(QStringLiteral(SDDM_CONFIG_FILE)); m_autoLoginUser = config.group("Autologin").readEntry("User", QString()); } QString AutomaticLoginSettings::autoLoginUser() const { return m_autoLoginUser; } bool AutomaticLoginSettings::setAutoLoginUser(const QString& username) { KAuth::Action saveAction(QStringLiteral("org.kde.kcontrol.kcmsddm.save")); saveAction.setHelperId(QStringLiteral("org.kde.kcontrol.kcmsddm")); QVariantMap args; args[QStringLiteral("sddm.conf")] = QStringLiteral(SDDM_CONFIG_FILE); args[QStringLiteral("sddm.conf/Autologin/User")] = username; saveAction.setHelperId(QStringLiteral("org.kde.kcontrol.kcmsddm")); saveAction.setArguments(args); auto job = saveAction.execute(); if (!job->exec()) { qCWarning(USER_MANAGER_LOG) << "fail" << job->errorText(); return false; } m_autoLoginUser = username; return true; } typedef OrgFreedesktopAccountsInterface AccountsManager; typedef OrgFreedesktopAccountsUserInterface Account; AccountModel::AccountModel(QObject* parent) : QAbstractListModel(parent) , m_sessions(new UserSession(this)) { m_dbus = new AccountsManager(QStringLiteral("org.freedesktop.Accounts"), QStringLiteral("/org/freedesktop/Accounts"), QDBusConnection::systemBus(), this); QDBusPendingReply > reply = m_dbus->ListCachedUsers(); reply.waitForFinished(); if (reply.isError()) { qCDebug(USER_MANAGER_LOG) << reply.error().message(); return; } const QList users = reply.value(); for (const QDBusObjectPath& path : users) { addAccount(path.path()); } // Adding fake "new user" directly into cache addAccountToCache(QStringLiteral("new-user"), nullptr); m_kEmailSettings.setProfile(m_kEmailSettings.defaultProfileName()); connect(m_dbus, &OrgFreedesktopAccountsInterface::UserAdded, this, &AccountModel::UserAdded); connect(m_dbus, &OrgFreedesktopAccountsInterface::UserDeleted, this, &AccountModel::UserDeleted); connect(m_sessions, &UserSession::userLogged, this, &AccountModel::userLogged); } AccountModel::~AccountModel() { delete m_dbus; qDeleteAll(m_users); } int AccountModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } return m_userPath.count(); } QVariant AccountModel::data(const QModelIndex& index, int role) const { if(!index.isValid()) { return QVariant(); } if (index.row() >= m_users.count()) { return QVariant(); } QString path = m_userPath.at(index.row()); Account* acc = m_users.value(path); if (!acc) { //new user return newUserData(role); } switch(role) { case Qt::DisplayRole || AccountModel::FriendlyName: if (!acc->realName().isEmpty()) { return acc->realName(); } return acc->userName(); case Qt::DecorationRole || AccountModel::Face: { QFile file(acc->iconFile()); - int size = IconSize(KIconLoader::Dialog); + int size = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize); if (!file.exists()) { return QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(size, size); } auto pixMap = QPixmap(file.fileName()).scaled(static_cast(size * m_dpr), static_cast(size * m_dpr), Qt::KeepAspectRatio, Qt::SmoothTransformation); pixMap.setDevicePixelRatio(m_dpr); return pixMap; } case AccountModel::RealName: return acc->realName(); case AccountModel::Username: return acc->userName(); case AccountModel::Email: return acc->email(); case AccountModel::Administrator: return acc->accountType() == 1; case AccountModel::AutomaticLogin: { const QString username = index.data(AccountModel::Username).toString(); return m_autoLoginSettings.autoLoginUser() == username; } case AccountModel::Logged: if (m_loggedAccounts.contains(path)) { return m_loggedAccounts[path]; } return QVariant(); case AccountModel::Created: return true; } return QVariant(); } bool AccountModel::setData(const QModelIndex& index, const QVariant& value, int role) { if(!index.isValid()) { return false; } if (index.row() >= m_users.count()) { return false; } QString path = m_userPath.at(index.row()); Account* acc = m_users.value(path); if (!acc) { return newUserSetData(index, value, role); } switch(role) { //The modification of the face file should be done outside case AccountModel::Face: if (checkForErrors(acc->SetIconFile(value.toString()))) { return false; } emit dataChanged(index, index); return true; case AccountModel::RealName: if (checkForErrors(acc->SetRealName(value.toString()))) { return false; } m_kEmailSettings.setSetting(KEMailSettings::RealName, value.toString()); emit dataChanged(index, index); return true; case AccountModel::Username: if (checkForErrors(acc->SetUserName(value.toString()))) { return false; } emit dataChanged(index, index); return true; case AccountModel::Password: if (checkForErrors(acc->SetPassword(cryptPassword(value.toString()), QString()))) { return false; } emit dataChanged(index, index); return true; case AccountModel::Email: if (checkForErrors(acc->SetEmail(value.toString()))) { return false; } m_kEmailSettings.setSetting(KEMailSettings::EmailAddress, value.toString()); emit dataChanged(index, index); return true; case AccountModel::Administrator: if (checkForErrors(acc->SetAccountType(value.toBool() ? 1 : 0))) { return false; } emit dataChanged(index, index); return true; case AccountModel::AutomaticLogin: { const bool autoLoginSet = value.toBool(); const QString username = index.data(AccountModel::Username).toString(); //if the checkbox is set and the SDDM config is not already us, set it to us //all rows need updating as we may have unset it from someone else. if (autoLoginSet && m_autoLoginSettings.autoLoginUser() != username) { if (m_autoLoginSettings.setAutoLoginUser(username)) { emit dataChanged(createIndex(0, 0), createIndex(rowCount(), 0)); return true; } return false; } //if the checkbox is not set and the SDDM config is set to us, then clear it else if (!autoLoginSet && m_autoLoginSettings.autoLoginUser() == username) { if (m_autoLoginSettings.setAutoLoginUser(QString())) { emit dataChanged(index, index); return true; } return false; } return true; } case AccountModel::Logged: m_loggedAccounts[path] = value.toBool(); emit dataChanged(index, index); return true; case AccountModel::Created: qFatal("AccountModel NewAccount should never be set"); return false; } return QAbstractItemModel::setData(index, value, role); } bool AccountModel::removeRows(int row, int count, const QModelIndex& parent) { Q_UNUSED(count); Q_UNUSED(parent); return removeAccountKeepingFiles(row, true); } bool AccountModel::removeAccountKeepingFiles(int row, bool keepFile) { Account* acc = m_users.value(m_userPath.at(row)); QDBusPendingReply rep = m_dbus->DeleteUser(acc->uid(), keepFile); rep.waitForFinished(); return !rep.isError(); } QVariant AccountModel::newUserData(int role) const { switch(role) { case Qt::DisplayRole || AccountModel::FriendlyName: return i18n("New User"); case Qt::DecorationRole || AccountModel::Face: - return QIcon::fromTheme(QStringLiteral("list-add-user")).pixmap(IconSize(KIconLoader::Dialog), IconSize(KIconLoader::Dialog)); + return QIcon::fromTheme(QStringLiteral("list-add-user")); case AccountModel::Created: return false; } return QVariant(); } bool AccountModel::newUserSetData(const QModelIndex &index, const QVariant& value, int roleInt) { AccountModel::Role role = static_cast(roleInt); m_newUserData[role] = value; QList roles = m_newUserData.keys(); if (!roles.contains(Username) || !roles.contains(RealName)) { return true; } // defaults to non-administrator int userType = 0; if (m_newUserData.contains(Administrator)) { userType = m_newUserData[Administrator].toBool(); } QDBusPendingReply reply = m_dbus->CreateUser(m_newUserData[Username].toString(), m_newUserData[RealName].toString(), userType); reply.waitForFinished(); if (reply.isError()) { qCDebug(USER_MANAGER_LOG) << reply.error().name(); qCDebug(USER_MANAGER_LOG) << reply.error().message(); m_newUserData.clear(); return false; } m_newUserData.remove(Username); m_newUserData.remove(RealName); //If we don't have anything else to set just return if (m_newUserData.isEmpty()) { return true; } UserAdded(reply.value()); QHash::const_iterator i = m_newUserData.constBegin(); while (i != m_newUserData.constEnd()) { qCDebug(USER_MANAGER_LOG) << "Setting extra:" << i.key() << "with value:" << i.value(); setData(index, i.value(), i.key()); ++i; } m_newUserData.clear(); return true; } void AccountModel::addAccount(const QString& path) { Account *acc = new Account(QStringLiteral("org.freedesktop.Accounts"), path, QDBusConnection::systemBus(), this); qulonglong uid = acc->uid(); if (!acc->isValid() || acc->lastError().isValid() || acc->systemAccount()) { return; } connect(acc, &OrgFreedesktopAccountsUserInterface::Changed, this, &AccountModel::Changed); if (uid == getuid()) { addAccountToCache(path, acc, 0); return; } addAccountToCache(path, acc); } void AccountModel::addAccountToCache(const QString& path, Account* acc, int pos) { if (pos > -1) { m_userPath.insert(pos, path); } else { m_userPath.append(path); } m_users.insert(path, acc); m_loggedAccounts[path] = false; } void AccountModel::replaceAccount(const QString &path, OrgFreedesktopAccountsUserInterface *acc, int pos) { if (pos >= m_userPath.size() || pos < 0) { return; } m_userPath.replace(pos, path); m_users.insert(path, acc); m_loggedAccounts[path] = false; } void AccountModel::removeAccount(const QString& path) { m_userPath.removeAll(path); delete m_users.take(path); m_loggedAccounts.remove(path); } bool AccountModel::checkForErrors(QDBusPendingReply reply) const { reply.waitForFinished(); if (reply.isError()) { qCDebug(USER_MANAGER_LOG) << reply.error().name(); qCDebug(USER_MANAGER_LOG) << reply.error().message(); return true; } return false; } QVariant AccountModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(); } return i18n("Users"); } void AccountModel::UserAdded(const QDBusObjectPath& dbusPath) { QString path = dbusPath.path(); if (m_userPath.contains(path)) { qCDebug(USER_MANAGER_LOG) << "We already have:" << path; return; } Account* acc = new Account(QStringLiteral("org.freedesktop.Accounts"), path, QDBusConnection::systemBus(), this); if (acc->systemAccount()) { return; } connect(acc, &OrgFreedesktopAccountsUserInterface::Changed, this, &AccountModel::Changed); // First, we modify "new-user" to become the new created user int row = rowCount(); replaceAccount(path, acc, row - 1); QModelIndex changedIndex = index(row - 1, 0); emit dataChanged(changedIndex, changedIndex); // Then we add new-user again. beginInsertRows(QModelIndex(), row, row); addAccountToCache(QStringLiteral("new-user"), nullptr); endInsertRows(); } void AccountModel::UserDeleted(const QDBusObjectPath& path) { if (!m_userPath.contains(path.path())) { qCDebug(USER_MANAGER_LOG) << "User Deleted but not found: " << path.path(); return; } beginRemoveRows(QModelIndex(), m_userPath.indexOf(path.path()), m_userPath.indexOf(path.path())); removeAccount(path.path()); endRemoveRows(); } void AccountModel::Changed() { Account* acc = qobject_cast(sender()); acc->path(); QModelIndex accountIndex = index(m_userPath.indexOf(acc->path()), 0); Q_EMIT dataChanged(accountIndex, accountIndex); } void AccountModel::userLogged(uint uid, bool logged) { QString path = accountPathForUid(uid); int row = m_userPath.indexOf(path); setData(index(row), logged, Logged); } const QString AccountModel::accountPathForUid(uint uid) const { QHash::ConstIterator i; for (i = m_users.constBegin(); i != m_users.constEnd(); ++i) { if (i.value() && i.value()->uid() == uid) { return i.key(); } } return QString(); } QString AccountModel::cryptPassword(const QString& password) const { QString cryptedPassword; QByteArray alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZ" "abcdefghijklmnopqrstuvxyz./"; QByteArray salt("$6$");//sha512 int len = alpha.count(); for(int i = 0; i < 16; i++){ salt.append(alpha.at((qrand() % len))); } return QString::fromUtf8(crypt(password.toUtf8().constData(), salt.constData())); } QDebug operator<<(QDebug debug, AccountModel::Role role) { switch(role) { case AccountModel::FriendlyName: debug << "AccountModel::FriendlyName"; break; case AccountModel::Face: debug << "AccountModel::Face"; break; case AccountModel::RealName: debug << "AccountModel::RealName"; break; case AccountModel::Username: debug << "AccountModel::Username"; break; case AccountModel::Password: debug << "AccountModel::Password"; break; case AccountModel::Email: debug << "AccountModel::Email"; break; case AccountModel::Administrator: debug << "AccountModel::Administrator"; break; case AccountModel::AutomaticLogin: debug << "AccountModel::AutomaticLogin"; break; case AccountModel::Logged: debug << "AccountModel::Logged"; break; case AccountModel::Created: debug << "AccountModel::Created"; break; } return debug; } void AccountModel::setDpr(qreal dpr) { m_dpr = dpr; } diff --git a/src/usermanager.cpp b/src/usermanager.cpp index 2811812..7dccf9c 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -1,139 +1,139 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "usermanager.h" #include "ui_kcm.h" #include "ui_account.h" #include "accountinfo.h" #include "lib/accountmodel.h" #include "lib/modeltest.h" #include #include #include #include #include -#include K_PLUGIN_FACTORY(UserManagerFactory, registerPlugin();) UserManager::UserManager(QWidget* parent, const QVariantList& args) : KCModule(parent, args) , m_saveNeeded(false) , m_model(new AccountModel(this)) , m_widget(new AccountInfo(m_model, this)) , m_ui(new Ui::KCMUserManager) { Q_UNUSED(args); QVBoxLayout *layout = new QVBoxLayout(); m_ui->setupUi(this); m_ui->accountInfo->setLayout(layout); layout->addWidget(m_widget); m_selectionModel = new QItemSelectionModel(m_model); connect(m_selectionModel, &QItemSelectionModel::currentChanged, this, &UserManager::currentChanged); m_selectionModel->setCurrentIndex(m_model->index(0), QItemSelectionModel::SelectCurrent); m_ui->userList->setModel(m_model); m_ui->userList->setSelectionModel(m_selectionModel); - m_ui->userList->setIconSize(QSize(IconSize(KIconLoader::Dialog), IconSize(KIconLoader::Dialog))); + const auto iconSize = style()->pixelMetric(QStyle::PM_LargeIconSize); + m_ui->userList->setIconSize(QSize(iconSize, iconSize)); ModelTest* test = new ModelTest(m_model, nullptr); Q_UNUSED(test) connect(m_ui->addBtn, &QAbstractButton::clicked, this, &UserManager::addNewUser); connect(m_ui->removeBtn, &QAbstractButton::clicked, this, &UserManager::removeUser); connect(m_widget, SIGNAL(changed(bool)), SIGNAL(changed(bool))); connect(m_model, &QAbstractItemModel::dataChanged, this, &UserManager::dataChanged); } UserManager::~UserManager() { delete m_model; } void UserManager::load() { m_widget->loadFromModel(); } void UserManager::save() { m_widget->save(); } void UserManager::currentChanged(const QModelIndex& selected, const QModelIndex& previous) { Q_UNUSED(previous); m_widget->setModelIndex(selected); bool enabled = false; //If it is not last and not first if (selected.row() < m_model->rowCount() - 1 && selected.row() > 0) { enabled = true; } m_ui->removeBtn->setEnabled(enabled); m_selectionModel->setCurrentIndex(selected, QItemSelectionModel::SelectCurrent); } void UserManager::dataChanged(const QModelIndex& topLeft, const QModelIndex& topRight) { Q_UNUSED(topRight); if (m_selectionModel->currentIndex() != topLeft) { return; } currentChanged(topLeft, topLeft); } void UserManager::addNewUser() { m_selectionModel->setCurrentIndex(m_model->index(m_model->rowCount()-1), QItemSelectionModel::SelectCurrent); } void UserManager::removeUser() { QModelIndex index = m_selectionModel->currentIndex(); KGuiItem keep; keep.setText(i18n("Keep files")); KGuiItem deletefiles; deletefiles.setText(i18n("Delete files")); QString warning = i18n("What do you want to do after deleting %1 ?", m_model->data(index, AccountModel::FriendlyName).toString()); if (!m_model->data(index, AccountModel::Logged).toBool()) { warning.append(QStringLiteral("\n\n")); warning.append(i18n("This user is using the system right now, removing it will cause problems")); } int result = KMessageBox::questionYesNoCancel(this, warning, i18n("Delete User"), keep, deletefiles); if (result == KMessageBox::Cancel) { return; } bool deleteFiles = result == KMessageBox::Yes ? false : true; m_model->removeAccountKeepingFiles(index.row(), deleteFiles); Q_EMIT changed(false); } #include "usermanager.moc"