diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 05a9d8a..55b56d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,59 +1,61 @@ -set(SDDM_CONFIG_FILE "/etc/sddm.conf" CACHE PATH "Path of the sddm config file") +set(SDDM_CONFIG_FILE "${CMAKE_INSTALL_FULL_SYSCONFDIR}/sddm.conf" CACHE PATH "Path of the sddm config file") +set(SDDM_CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/sddm.conf.d" CACHE PATH "Path of the sddm config directory") +set(SDDM_SYSTEM_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/lib/sddm/sddm.conf.d" CACHE PATH "Path of the system sddm config directory") 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 advanceconfig.cpp usersmodel.cpp sessionmodel.cpp cursortheme/thememodel.cpp cursortheme/xcursortheme.cpp cursortheme/cursortheme.cpp cursortheme/sortproxymodel.cpp cursortheme/dummytheme.cpp configwidgets/selectimagebutton.cpp ) set(SDDM_KCM_UI ui/themeconfig.ui ui/advanceconfig.ui) ki18n_wrap_ui(SDDM_KCM_SRCS ${SDDM_KCM_UI}) add_library(kcm_sddm MODULE ${SDDM_KCM_SRCS}) target_compile_definitions(kcm_sddm PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(kcm_sddm Qt5::Widgets Qt5::X11Extras Qt5::Quick Qt5::QuickWidgets KF5::I18n KF5::ConfigWidgets KF5::Auth KF5::KIOWidgets KF5::NewStuff ${X11_LIBRARIES} XCB::XCB # For mouse cursor themes ) if (X11_Xcursor_FOUND) target_link_libraries(kcm_sddm ${X11_Xcursor_LIB}) endif (X11_Xcursor_FOUND) if (X11_Xfixes_FOUND) target_link_libraries(kcm_sddm ${X11_Xfixes_LIB}) endif (X11_Xfixes_FOUND) install(TARGETS kcm_sddm DESTINATION ${CMAKE_INSTALL_PLUGINDIR}) install(FILES qml/main.qml DESTINATION ${CMAKE_INSTALL_DATADIR}/sddm-kcm) diff --git a/src/advanceconfig.cpp b/src/advanceconfig.cpp index e7c16fd..437094e 100644 --- a/src/advanceconfig.cpp +++ b/src/advanceconfig.cpp @@ -1,162 +1,161 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #include "advanceconfig.h" #include "ui_advanceconfig.h" #include #include #include #include #include "usersmodel.h" #include "sessionmodel.h" #include "config.h" #include "cursortheme/thememodel.h" #include "cursortheme/sortproxymodel.h" #include "cursortheme/cursortheme.h" const int MIN_UID = 1000; const int MAX_UID = 65000; -AdvanceConfig::AdvanceConfig(QWidget *parent) : - QWidget(parent) +AdvanceConfig::AdvanceConfig(const KSharedConfigPtr &config, QWidget *parent) : + QWidget(parent), + mConfig(config) { - mConfig = KSharedConfig::openConfig(SDDM_CONFIG_FILE, KConfig::SimpleConfig); - configUi = new Ui::AdvanceConfig(); configUi->setupUi(this); 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->cursorList, SIGNAL(activated(int)), SIGNAL(changed())); connect(configUi->minimumUid, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect(configUi->minimumUid, SIGNAL(textChanged(QString)), SLOT(slotUidRangeChanged())); connect(configUi->maximumUid, SIGNAL(textChanged(QString)), SIGNAL(changed())); connect(configUi->maximumUid, SIGNAL(textChanged(QString)), SLOT(slotUidRangeChanged())); connect(configUi->autoLogin, SIGNAL(clicked()), SIGNAL(changed())); connect(configUi->reloginAfterQuit, SIGNAL(clicked()), SIGNAL(changed())); } AdvanceConfig::~AdvanceConfig() { delete configUi; } void AdvanceConfig::load() { //Cursor themes CursorThemeModel *cursorModel = new CursorThemeModel(this); proxyCursorModel = new SortProxyModel(this); proxyCursorModel->setSourceModel(cursorModel); proxyCursorModel->setFilterCaseSensitivity(Qt::CaseSensitive); proxyCursorModel->sort(Qt::DisplayRole, Qt::AscendingOrder); configUi->cursorList->setModel(proxyCursorModel); QString currentCursor = mConfig->group("Theme").readEntry("CursorTheme", ""); QModelIndex cursorIndex = proxyCursorModel->findIndex(currentCursor); configUi->cursorList->setCurrentIndex(cursorIndex.row() < 0 ? 0 : cursorIndex.row()); //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"))); } QVariantMap AdvanceConfig::save() { QVariantMap args; qDebug() << "idx:" << configUi->cursorList->currentIndex(); QModelIndex cursorIndex = configUi->cursorList->model()->index(configUi->cursorList->currentIndex(),0); if (cursorIndex.isValid()) { const CursorTheme *cursorTheme = proxyCursorModel->theme(cursorIndex); if (cursorTheme) args["sddm.conf/Theme/CursorTheme"] = cursorTheme->name(); } args["sddm.conf/Autologin/User"] = ( configUi->autoLogin->isChecked() ) ? configUi->userList->currentText() : ""; args["sddm.conf/Autologin/Session"] = ( configUi->autoLogin->isChecked() ) ? configUi->sessionList->currentData() : ""; args["sddm.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["sddm.conf/Users/MinimumUid"] = configUi->minimumUid->text(); args["sddm.conf/Users/MaximumUid"] = configUi->maximumUid->text(); } args["sddm.conf/General/HaltCommand"] = configUi->haltCommand->url().toLocalFile(); args["sddm.conf/General/RebootCommand"] = configUi->rebootCommand->url().toLocalFile(); return args; } void AdvanceConfig::slotUidRangeChanged() { int minUid = configUi->minimumUid->text().toInt(); int maxUid = configUi->maximumUid->text().toInt(); if (!isUidRangeValid(minUid, maxUid)) { return; } userModel->populate(minUid, maxUid); } bool AdvanceConfig::isUidRangeValid(int minUid, int maxUid) const { if (minUid < 0 || minUid > maxUid) return false; return true; } diff --git a/src/advanceconfig.h b/src/advanceconfig.h index e79271f..8130136 100644 --- a/src/advanceconfig.h +++ b/src/advanceconfig.h @@ -1,60 +1,60 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #ifndef ADVANCECONFIG_H #define ADVANCECONFIG_H #include #include namespace Ui { class AdvanceConfig; } class SortProxyModel; class CursorTheme; class UsersModel; class SessionModel; class AdvanceConfig : public QWidget { Q_OBJECT public: - explicit AdvanceConfig(QWidget *parent = 0); + explicit AdvanceConfig(const KSharedConfigPtr &config, QWidget *parent = 0); ~AdvanceConfig(); QVariantMap save(); signals: void changed(bool changed=true); private slots: void slotUidRangeChanged(); private: void load(); bool isUidRangeValid(int minUid, int maxUid) const; private: Ui::AdvanceConfig *configUi; KSharedConfigPtr mConfig; SortProxyModel *proxyCursorModel; UsersModel *userModel; SessionModel *sessionModel; }; #endif // ADVANCECONFIG_H diff --git a/src/config.h.in b/src/config.h.in index 77a5e22..80309ce 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1,6 +1,8 @@ #ifndef CONFIG_H #define CONFIG_H -#define SDDM_CONFIG_FILE "@SDDM_CONFIG_FILE@" +#define SDDM_CONFIG_FILE "@SDDM_CONFIG_FILE@" +#define SDDM_CONFIG_DIR "@SDDM_CONFIG_DIR@" +#define SDDM_SYSTEM_CONFIG_DIR "@SDDM_SYSTEM_CONFIG_DIR@" -#endif //CONFIG_H \ No newline at end of file +#endif //CONFIG_H diff --git a/src/cursortheme/thememodel.cpp b/src/cursortheme/thememodel.cpp index 0dc04cf..65a558c 100644 --- a/src/cursortheme/thememodel.cpp +++ b/src/cursortheme/thememodel.cpp @@ -1,419 +1,414 @@ /* * Copyright © 2005-2007 Fredrik Höglund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 as published by the Free Software Foundation. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "thememodel.h" #include "thememodel.moc" #include "xcursortheme.h" #include #include // Check for older version #if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR) # define XCURSOR_LIB_MAJOR XCURSOR_MAJOR # define XCURSOR_LIB_MINOR XCURSOR_MINOR #endif CursorThemeModel::CursorThemeModel(QObject *parent) : QAbstractTableModel(parent) { insertThemes(); } CursorThemeModel::~CursorThemeModel() { qDeleteAll(list); list.clear(); } void CursorThemeModel::refreshList() { beginResetModel(); qDeleteAll(list); list.clear(); endResetModel(); insertThemes(); } QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const { // Only provide text for the headers if (role != Qt::DisplayRole) return QVariant(); // Horizontal header labels if (orientation == Qt::Horizontal) { switch (section) { case NameColumn: return i18n("Name"); case DescColumn: return i18n("Description"); default: return QVariant(); } } // Numbered vertical header lables return QString(section); } QVariant CursorThemeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= list.count()) return QVariant(); const CursorTheme *theme = list.at(index.row()); // Text label if (role == Qt::DisplayRole) { switch (index.column()) { case NameColumn: return theme->title(); case DescColumn: return theme->description(); default: return QVariant(); } } // Description for the first name column if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn) return theme->description(); // Icon for the name column if (role == Qt::DecorationRole && index.column() == NameColumn) return theme->icon(); return QVariant(); } void CursorThemeModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); Q_UNUSED(order); // Sorting of the model isn't implemented, as the KCM currently uses // a sorting proxy model. } const CursorTheme *CursorThemeModel::theme(const QModelIndex &index) { if (!index.isValid()) return NULL; if (index.row() < 0 || index.row() >= list.count()) return NULL; return list.at(index.row()); } QModelIndex CursorThemeModel::findIndex(const QString &name) { uint hash = qHash(name); for (int i = 0; i < list.count(); i++) { const CursorTheme *theme = list.at(i); if (theme->hash() == hash) return index(i, 0); } return QModelIndex(); } QModelIndex CursorThemeModel::defaultIndex() { return findIndex(defaultName); } const QStringList CursorThemeModel::searchPaths() { if (!baseDirs.isEmpty()) return baseDirs; #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1 // These are the default paths Xcursor will scan for cursor themes QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"); // If XCURSOR_PATH is set, use that instead of the default path char *xcursorPath = std::getenv("XCURSOR_PATH"); if (xcursorPath) path = xcursorPath; #else // Get the search path from Xcursor QString path = XcursorLibraryPath(); #endif // Separate the paths baseDirs = path.split(':', QString::SkipEmptyParts); // Remove duplicates QMutableStringListIterator i(baseDirs); while (i.hasNext()) { const QString path = i.next(); QMutableStringListIterator j(i); while (j.hasNext()) if (j.next() == path) j.remove(); } // Expand all occurrences of ~/ to the home dir baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/'); return baseDirs; } bool CursorThemeModel::hasTheme(const QString &name) const { const uint hash = qHash(name); foreach (const CursorTheme *theme, list) if (theme->hash() == hash) return true; return false; } bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth) { // Prevent infinite recursion if (depth > 10) return false; // Search each icon theme directory for 'theme' foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists() || !dir.cd(theme)) continue; // If there's a cursors subdir, we'll assume this is a cursor theme if (dir.exists("cursors")) return true; // If the theme doesn't have an index.theme file, it can't inherit any themes. if (!dir.exists("index.theme")) continue; // Open the index.theme file, so we can get the list of inherited themes KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); // Recurse through the list of inherited themes, to check if one of them // is a cursor theme. QStringList inherits = cg.readEntry("Inherits", QStringList()); foreach (const QString &inherit, inherits) { // Avoid possible DoS if (inherit == theme) continue; if (isCursorTheme(inherit, depth + 1)) return true; } } return false; } bool CursorThemeModel::handleDefault(const QDir &themeDir) { QFileInfo info(themeDir.path()); // If "default" is a symlink if (info.isSymLink()) { QFileInfo target(info.symLinkTarget()); if (target.exists() && (target.isDir() || target.isSymLink())) defaultName = target.fileName(); return true; } // If there's no cursors subdir, or if it's empty if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors") .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty()) { if (themeDir.exists("index.theme")) { XCursorTheme theme(themeDir); if (!theme.inherits().isEmpty()) defaultName = theme.inherits().at(0); } return true; } defaultName = QLatin1String("default"); return false; } void CursorThemeModel::processThemeDir(const QDir &themeDir) { bool haveCursors = themeDir.exists("cursors"); // Special case handling of "default", since it's usually either a // symlink to another theme, or an empty theme that inherits another // theme. if (defaultName.isNull() && themeDir.dirName() == "default") { if (handleDefault(themeDir)) return; } // If the directory doesn't have a cursors subdir and lacks an // index.theme file it can't be a cursor theme. if (!themeDir.exists("index.theme") && !haveCursors) return; - static bool isX11 = QX11Info::isPlatformX11(); - if (!isX11) { - // TODO: implement Wayland Cursor Theme support - return; - } // Create a cursor theme object for the theme dir XCursorTheme *theme = new XCursorTheme(themeDir); // Skip this theme if it's hidden. if (theme->isHidden()) { delete theme; return; } // If there's no cursors subdirectory we'll do a recursive scan // to check if the theme inherits a theme with one. if (!haveCursors) { bool foundCursorTheme = false; foreach (const QString &name, theme->inherits()) if ((foundCursorTheme = isCursorTheme(name))) break; if (!foundCursorTheme) { delete theme; return; } } // Append the theme to the list beginInsertRows(QModelIndex(), list.size(), list.size()); list.append(theme); endInsertRows(); } void CursorThemeModel::insertThemes() { // Scan each base dir for Xcursor themes and add them to the list. foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists()) continue; // Process each subdir in the directory foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { // Don't process the theme if a theme with the same name already exists // in the list. Xcursor will pick the first one it finds in that case, // and since we use the same search order, the one Xcursor picks should // be the one already in the list. if (hasTheme(name) || !dir.cd(name)) continue; processThemeDir(dir); dir.cdUp(); // Return to the base dir } } // The theme Xcursor will end up using if no theme is configured if (defaultName.isNull() || !hasTheme(defaultName)) defaultName = QLatin1String("KDE_Classic"); } bool CursorThemeModel::addTheme(const QDir &dir) { XCursorTheme *theme = new XCursorTheme(dir); // Don't add the theme to the list if it's hidden if (theme->isHidden()) { delete theme; return false; } // ### If the theme is hidden, the user will probably find it strange that it // doesn't appear in the list view. There also won't be a way for the user // to delete the theme using the KCM. Perhaps a warning about this should // be issued, and the user be given a chance to undo the installation. // If an item with the same name already exists in the list, // we'll remove it before inserting the new one. for (int i = 0; i < list.count(); i++) { if (list.at(i)->hash() == theme->hash()) { removeTheme(index(i, 0)); break; } } // Append the theme to the list beginInsertRows(QModelIndex(), rowCount(), rowCount()); list.append(theme); endInsertRows(); return true; } void CursorThemeModel::removeTheme(const QModelIndex &index) { if (!index.isValid()) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); delete list.takeAt(index.row()); endRemoveRows(); } diff --git a/src/cursortheme/xcursortheme.cpp b/src/cursortheme/xcursortheme.cpp index 44be89e..1e3517e 100644 --- a/src/cursortheme/xcursortheme.cpp +++ b/src/cursortheme/xcursortheme.cpp @@ -1,229 +1,232 @@ /* * Copyright © 2006-2007 Fredrik Höglund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 or at your option version 3 as published * by the Free Software Foundation. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include "xcursortheme.h" // Static variable holding alternative names for some cursors QHash XCursorTheme::alternatives; XCursorTheme::XCursorTheme(const QDir &themeDir) : CursorTheme(themeDir.dirName()) { // Directory information setName(themeDir.dirName()); setPath(themeDir.path()); setIsWritable(QFileInfo(themeDir.path()).isWritable()); // ### perhaps this shouldn't be cached if (themeDir.exists("index.theme")) parseIndexFile(); QString cursorFile = path() + "/cursors/left_ptr"; QList sizeList; XcursorImages *images = XcursorFilenameLoadAllImages(qPrintable(cursorFile)); if (images) { for (int i = 0; i < images->nimage; ++i) { if (!sizeList.contains(images->images[i]->size)) sizeList.append(images->images[i]->size); }; XcursorImagesDestroy(images); qSort(sizeList.begin(), sizeList.end()); m_availableSizes = sizeList; }; if (!sizeList.isEmpty()) { QString sizeListString = QString::number(sizeList.takeFirst()); while (!sizeList.isEmpty()) { sizeListString.append(", "); sizeListString.append(QString::number(sizeList.takeFirst())); }; QString tempString = i18nc( "@info The argument is the list of available sizes (in pixel). Example: " "'Available sizes: 24' or 'Available sizes: 24, 36, 48'", "(Available sizes: %1)", sizeListString); if (m_description.isEmpty()) m_description = tempString; else m_description = m_description + ' ' + tempString; }; } void XCursorTheme::parseIndexFile() { KConfig config(path() + "/index.theme", KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); m_title = cg.readEntry("Name", m_title); m_description = cg.readEntry("Comment", m_description); m_sample = cg.readEntry("Example", m_sample); m_hidden = cg.readEntry("Hidden", false); m_inherits = cg.readEntry("Inherits", QStringList()); } QString XCursorTheme::findAlternative(const QString &name) const { if (alternatives.isEmpty()) { alternatives.reserve(18); // Qt uses non-standard names for some core cursors. // If Xcursor fails to load the cursor, Qt creates it with the correct name using the // core protcol instead (which in turn calls Xcursor). We emulate that process here. // Note that there's a core cursor called cross, but it's not the one Qt expects. alternatives.insert("cross", "crosshair"); alternatives.insert("up_arrow", "center_ptr"); alternatives.insert("wait", "watch"); alternatives.insert("ibeam", "xterm"); alternatives.insert("size_all", "fleur"); alternatives.insert("pointing_hand", "hand2"); // Precomputed MD5 hashes for the hardcoded bitmap cursors in Qt and KDE. // Note that the MD5 hash for left_ptr_watch is for the KDE version of that cursor. alternatives.insert("size_ver", "00008160000006810000408080010102"); alternatives.insert("size_hor", "028006030e0e7ebffc7f7070c0600140"); alternatives.insert("size_bdiag", "fcf1c3c7cd4491d801f1e1c78f100000"); alternatives.insert("size_fdiag", "c7088f0f3e6c8088236ef8e1e3e70000"); alternatives.insert("whats_this", "d9ce0ab605698f320427677b458ad60b"); alternatives.insert("split_h", "14fef782d02440884392942c11205230"); alternatives.insert("split_v", "2870a09082c103050810ffdffffe0204"); alternatives.insert("forbidden", "03b6e0fcb3499374a867c041f52298f0"); alternatives.insert("left_ptr_watch", "3ecb610c1bf2410f44200f48c40d3599"); alternatives.insert("hand2", "e29285e634086352946a0e7090d73106"); alternatives.insert("openhand", "9141b49c8149039304290b508d208c40"); alternatives.insert("closedhand", "05e88622050804100c20044008402080"); } return alternatives.value(name, QString()); } XcursorImage *XCursorTheme::xcLoadImage(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImage(cursorName, themeName, size); } XcursorImages *XCursorTheme::xcLoadImages(const QString &image, int size) const { QByteArray cursorName = QFile::encodeName(image); QByteArray themeName = QFile::encodeName(name()); return XcursorLibraryLoadImages(cursorName, themeName, size); } int XCursorTheme::autodetectCursorSize() const { /* This code is basically borrowed from display.c of the XCursor library We can't use "int XcursorGetDefaultSize(Display *dpy)" because if previously the cursor size was set to a custom value, it would return this custom value. */ int size = 0; int dpi = 0; Display *dpy = QX11Info::display(); + // Fallback on wayland + if (!dpy) + return 24; // The string "v" is owned and will be destroyed by Xlib char *v = XGetDefault(dpy, "Xft", "dpi"); if (v) dpi = atoi(v); if (dpi) size = dpi * 16 / 72; if (size == 0) { int dim; if (DisplayHeight(dpy, DefaultScreen(dpy)) < DisplayWidth(dpy, DefaultScreen(dpy))) { dim = DisplayHeight(dpy, DefaultScreen(dpy)); } else { dim = DisplayWidth(dpy, DefaultScreen(dpy)); }; size = dim / 48; } return size; } qulonglong XCursorTheme::loadCursor(const QString &name, int size) const { if (size <= 0) size = autodetectCursorSize(); // Load the cursor images XcursorImages *images = xcLoadImages(name, size); if (!images) images = xcLoadImages(findAlternative(name), size); if (!images) return None; // Create the cursor Cursor handle = XcursorImagesLoadCursor(QX11Info::display(), images); XcursorImagesDestroy(images); setCursorName(handle, name); return handle; } QImage XCursorTheme::loadImage(const QString &name, int size) const { if (size <= 0) size = autodetectCursorSize(); // Load the image XcursorImage *xcimage = xcLoadImage(name, size); if (!xcimage) xcimage = xcLoadImage(findAlternative(name), size); if (!xcimage) { return QImage(); } // Convert the XcursorImage to a QImage, and auto-crop it QImage image((uchar *)xcimage->pixels, xcimage->width, xcimage->height, QImage::Format_ARGB32_Premultiplied ); image = autoCropImage(image); XcursorImageDestroy(xcimage); return image; } diff --git a/src/sddmkcm.cpp b/src/sddmkcm.cpp index 3dd8d07..6580e8c 100644 --- a/src/sddmkcm.cpp +++ b/src/sddmkcm.cpp @@ -1,115 +1,130 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #include "sddmkcm.h" +#include + +#include #include #include #include #include #include #include #include #include "config.h" #include "themeconfig.h" #include "advanceconfig.h" #include #include #include K_PLUGIN_FACTORY(SddmKcmFactory, registerPlugin();) K_EXPORT_PLUGIN(SddmKcmFactory("kcm_sddm")) SddmKcm::SddmKcm(QWidget *parent, const QVariantList &args) : KCModule(parent, args) { KAboutData* aboutData = new KAboutData("kcmsddm", i18n("SDDM KDE Config"), PROJECT_VERSION); aboutData->setShortDescription(i18n("Login screen using the SDDM")); aboutData->setLicense(KAboutLicense::GPL_V2); aboutData->setHomepage("https://projects.kde.org/projects/kde/workspace/sddm-kcm"); aboutData->addAuthor("Reza Fatahilah Shah", i18n("Author"), "rshah0385@kireihana.com"); aboutData->addAuthor("David Edmundson", i18n("Author"), "davidedmundson@kde.org"); setAboutData(aboutData); setNeedsAuthorization(true); - + + mSddmConfig = KSharedConfig::openConfig(SDDM_CONFIG_FILE, KConfig::CascadeConfig); + + // This does not listen for new config files in the directory. + QStringList configFiles = QDir(SDDM_CONFIG_DIR).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware), + systemConfigFiles = QDir(SDDM_SYSTEM_CONFIG_DIR).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware); + std::transform(systemConfigFiles.begin(), systemConfigFiles.end(), systemConfigFiles.begin(), + [](const QString &filename) { return QStringLiteral(SDDM_SYSTEM_CONFIG_DIR "/") + filename; }); + std::transform(configFiles.begin(), configFiles.end(), configFiles.begin(), + [](const QString &filename) { return QStringLiteral(SDDM_CONFIG_DIR "/") + filename; }); + + mSddmConfig->addConfigSources(systemConfigFiles + configFiles); + prepareUi(); } SddmKcm::~SddmKcm() { } void SddmKcm::save() { QVariantMap args; args["sddm.conf"] = SDDM_CONFIG_FILE; if (!mThemeConfig->themeConfigPath().isEmpty()) { args["theme.conf.user"] = mThemeConfig->themeConfigPath() + ".user"; } qDebug() << "Ovr:" << args["theme.conf.user"].toString(); args.unite(mThemeConfig->save()); args.unite(mAdvanceConfig->save()); KAuth::Action saveAction = authAction(); saveAction.setHelperId("org.kde.kcontrol.kcmsddm"); saveAction.setArguments(args); auto job = saveAction.execute(); job->exec(); if (job->error()){ qDebug() << "Save Failed"; qDebug() << job->errorString(); qDebug() << job->errorText(); } else { changed(false); qDebug() << "Option saved"; } } void SddmKcm::prepareUi() { QHBoxLayout* layout = new QHBoxLayout(this); QTabWidget* tabHolder = new QTabWidget(this); layout->addWidget(tabHolder); - mThemeConfig = new ThemeConfig(this); + mThemeConfig = new ThemeConfig(mSddmConfig, this); connect(mThemeConfig, SIGNAL(changed(bool)), SIGNAL(changed(bool))); tabHolder->addTab(mThemeConfig, i18n("Theme")); - mAdvanceConfig = new AdvanceConfig(this); + mAdvanceConfig = new AdvanceConfig(mSddmConfig, this); connect(mAdvanceConfig, SIGNAL(changed(bool)), SIGNAL(changed(bool))); tabHolder->addTab(mAdvanceConfig, i18n("Advanced")); } #include "sddmkcm.moc" diff --git a/src/sddmkcm.h b/src/sddmkcm.h index cef3c33..679d57a 100644 --- a/src/sddmkcm.h +++ b/src/sddmkcm.h @@ -1,43 +1,45 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #ifndef SDDMKCM_H #define SDDMKCM_H #include +#include class ThemeConfig; class AdvanceConfig; class SddmKcm : public KCModule { Q_OBJECT public: explicit SddmKcm(QWidget *parent, const QVariantList &args); ~SddmKcm(); public slots: void save() Q_DECL_OVERRIDE; private: void prepareUi(); private: + KSharedConfigPtr mSddmConfig; ThemeConfig *mThemeConfig; AdvanceConfig *mAdvanceConfig; }; #endif // SDDMKCM_H diff --git a/src/themeconfig.cpp b/src/themeconfig.cpp index 9722216..31801f8 100644 --- a/src/themeconfig.cpp +++ b/src/themeconfig.cpp @@ -1,240 +1,239 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #include "themeconfig.h" #include "ui_themeconfig.h" #include "themesmodel.h" #include "themesdelegate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" -ThemeConfig::ThemeConfig(QWidget *parent) : - QWidget(parent) +ThemeConfig::ThemeConfig(const KSharedConfigPtr &config, QWidget *parent) : + QWidget(parent), + mConfig(config) { - mConfig = KSharedConfig::openConfig(SDDM_CONFIG_FILE, KConfig::SimpleConfig); - configUi = new Ui::ThemeConfig(); configUi->setupUi(this); configUi->messageWidget->setVisible(false); ThemesModel *model = new ThemesModel(this); configUi->themesListView->setModel(model); ThemesDelegate *delegate = new ThemesDelegate(configUi->themesListView); delegate->setPreviewSize(QSize(128,128)); configUi->themesListView->setItemDelegate(delegate); model->populate(); connect(this, &ThemeConfig::themesChanged, model, &ThemesModel::populate); connect(configUi->themesListView, SIGNAL(activated(QModelIndex)), SLOT(themeSelected(QModelIndex))); connect(configUi->themesListView, SIGNAL(clicked(QModelIndex)), SLOT(themeSelected(QModelIndex))); connect(configUi->selectBackgroundButton, SIGNAL(imagePathChanged(QString)), SLOT(backgroundChanged(QString))); connect(configUi->getNewButton, &QPushButton::clicked, this, &ThemeConfig::getNewStuffClicked); connect(configUi->installFromFileButton, &QPushButton::clicked, this, &ThemeConfig::installFromFileClicked); connect(configUi->removeThemeButton, &QPushButton::clicked, this, &ThemeConfig::removeThemeClicked); prepareInitialTheme(); } ThemeConfig::~ThemeConfig() { delete configUi; } QVariantMap ThemeConfig::save() { QModelIndex index = configUi->themesListView->currentIndex(); if (!index.isValid()) { return QVariantMap(); } QVariantMap args; args["sddm.conf/Theme/Current"] = index.data(ThemesModel::IdRole); if (!mThemeConfigPath.isEmpty()) { if (!mBackgroundPath.isEmpty()) { args["theme.conf.user/General/background"] = mBackgroundPath; args["theme.conf.user/General/type"] = QStringLiteral("image"); } else { args["theme.conf.user/General/type"] = QStringLiteral("color"); } } return args; } QString ThemeConfig::themeConfigPath() const { return mThemeConfigPath; } void ThemeConfig::prepareInitialTheme() { const QString initialTheme = mConfig->group("Theme").readEntry("Current"); QModelIndex index = findThemeIndex(initialTheme); if (!index.isValid() && configUi->themesListView->model()->rowCount() > 0) { //if we can't find the currently configured theme from the config, just select the first theme in the UI index = configUi->themesListView->model()->index(0,0); } configUi->themesListView->setCurrentIndex(index); themeSelected(index); } QModelIndex ThemeConfig::findThemeIndex(const QString &id) const { QAbstractItemModel* model = configUi->themesListView->model(); for (int i=0; i < model->rowCount(); i++) { QModelIndex index = model->index(i, 0); if (index.data(ThemesModel::IdRole).toString() == id) { return index; } } return QModelIndex(); } void ThemeConfig::themeSelected(const QModelIndex &index) { if (!configUi->quickWidget->source().isValid()) { const QString mainQmlPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "sddm-kcm/main.qml"); configUi->quickWidget->setSource(QUrl::fromLocalFile(mainQmlPath)); } QString themePath = index.data(ThemesModel::PathRole).toString(); QString previewFilename = themePath + index.data(ThemesModel::PreviewRole).toString(); configUi->quickWidget->rootObject()->setProperty("themeName", index.data().toString()); configUi->quickWidget->rootObject()->setProperty("previewPath", previewFilename); configUi->quickWidget->rootObject()->setProperty("authorName", index.data(ThemesModel::AuthorRole).toString()); configUi->quickWidget->rootObject()->setProperty("description", index.data(ThemesModel::DescriptionRole).toString()); configUi->quickWidget->rootObject()->setProperty("license", index.data(ThemesModel::LicenseRole).toString()); configUi->quickWidget->rootObject()->setProperty("email", index.data(ThemesModel::EmailRole).toString()); configUi->quickWidget->rootObject()->setProperty("website", index.data(ThemesModel::WebsiteRole).toString()); configUi->quickWidget->rootObject()->setProperty("copyright", index.data(ThemesModel::CopyrightRole).toString()); configUi->quickWidget->rootObject()->setProperty("version", index.data(ThemesModel::VersionRole).toString()); //Check if we need to display configuration group QString configPath = themePath + index.data(ThemesModel::ConfigFileRole).toString(); prepareConfigurationUi(configPath); emit changed(true); } void ThemeConfig::backgroundChanged(const QString &imagePath) { mBackgroundPath = imagePath; emit changed(true); } void ThemeConfig::prepareConfigurationUi(const QString &configPath) { mThemeConfigPath = configPath; QFile configFile(configPath); if (configFile.exists()) { KSharedConfigPtr themeConfig = KSharedConfig::openConfig(configFile.fileName() + ".user", KConfig::CascadeConfig); themeConfig->addConfigSources({configFile.fileName()}); configUi->customizeBox->setVisible(true); configUi->selectBackgroundButton->setImagePath(themeConfig->group("General").readEntry("background")); } else { configUi->customizeBox->setVisible(false); } } void ThemeConfig::dump() { //dump sddm conf KConfigGroup config = mConfig->group("General"); qDebug() << "Current theme:" << config.readEntry("CurrentTheme"); } void ThemeConfig::getNewStuffClicked() { QPointer dialog(new KNS3::DownloadDialog(QStringLiteral("sddmtheme.knsrc"), this)); dialog->setWindowTitle(i18n("Download New SDDM Themes")); if (dialog->exec()) { emit themesChanged(); } delete dialog.data(); } void ThemeConfig::installFromFileClicked() { QPointer dialog(new QFileDialog(this)); dialog->exec(); QStringList files = dialog->selectedFiles(); if (files.count() == 1) { QString file = files.first(); KAuth::Action saveAction(QStringLiteral("org.kde.kcontrol.kcmsddm.installtheme")); saveAction.setHelperId("org.kde.kcontrol.kcmsddm"); saveAction.addArgument(QStringLiteral("filePath"), file); auto job = saveAction.execute(); if (!job->exec()) { configUi->messageWidget->setMessageType(KMessageWidget::Warning); configUi->messageWidget->setText(job->errorString()); configUi->messageWidget->animatedShow(); } else { emit themesChanged(); } } delete dialog.data(); } void ThemeConfig::removeThemeClicked() { if (!configUi->themesListView->currentIndex().isValid()) { return; } const QString path = configUi->themesListView->currentIndex().data(ThemesModel::PathRole).toString(); KAuth::Action saveAction(QStringLiteral("org.kde.kcontrol.kcmsddm.uninstalltheme")); saveAction.setHelperId("org.kde.kcontrol.kcmsddm"); saveAction.addArgument(QStringLiteral("filePath"), path); auto job = saveAction.execute(); if (!job->exec()) { configUi->messageWidget->setMessageType(KMessageWidget::Warning); configUi->messageWidget->setText(job->errorString()); configUi->messageWidget->animatedShow(); } else { emit themesChanged(); } } diff --git a/src/themeconfig.h b/src/themeconfig.h index 22c2172..bf47ec6 100644 --- a/src/themeconfig.h +++ b/src/themeconfig.h @@ -1,63 +1,63 @@ /* Copyright 2013 by Reza Fatahilah Shah 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 . */ #ifndef THEMECONFIG_H #define THEMECONFIG_H #include #include namespace Ui { class ThemeConfig; } class QModelIndex; class ThemeConfig : public QWidget { Q_OBJECT public: - explicit ThemeConfig(QWidget *parent = 0); + explicit ThemeConfig(const KSharedConfigPtr &config, QWidget *parent = 0); ~ThemeConfig(); QVariantMap save(); QString themeConfigPath() const; signals: void changed(bool); void themesChanged(); private slots: void themeSelected(const QModelIndex &index); void backgroundChanged(const QString &imagePath); void getNewStuffClicked(); void installFromFileClicked(); void removeThemeClicked(); private: Ui::ThemeConfig *configUi; KSharedConfigPtr mConfig; QString mBackgroundPath; QString mThemeConfigPath; void prepareInitialTheme(); QModelIndex findThemeIndex(const QString &theme) const; void prepareConfigurationUi(const QString &configPath); void dump(); }; #endif // THEMECONFIG_H