diff --git a/.github/workflows/ddm-archlinux-build.yml b/.github/workflows/ddm-archlinux-build.yml index f9143ed..b45233c 100644 --- a/.github/workflows/ddm-archlinux-build.yml +++ b/.github/workflows/ddm-archlinux-build.yml @@ -21,7 +21,7 @@ jobs: - name: Install base dependencies run: | - pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools treeland-protocols ninja + pacman -S --noconfirm --noprogressbar base-devel bash gcc-libs glibc libxau pam qt6-base qt6-declarative dtk6core systemd-libs ttf-font xorg-server xorg-xauth git extra-cmake-modules python-docutils qt6-tools treeland-protocols ninja - name: Build and Install treeland-protocols run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 21ddaff..3b0549f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25.0) project(DDM - VERSION 0.3.8 + VERSION 0.3.9 LANGUAGES CXX C) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -83,6 +83,9 @@ find_package(TreelandProtocols 0.5.3 REQUIRED) find_package(Qt6 CONFIG REQUIRED Core DBus Network) qt_standard_project_setup(REQUIRES 6.6) +# DTK (DConfig) +find_package(Dtk6 REQUIRED COMPONENTS Core) + # Uninstall target if ("${ECM_VERSION}" VERSION_LESS "1.7.0") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" @@ -117,10 +120,7 @@ set(RUNTIME_DIR "${RUNTIME_DIR_DEFAULT}" set(SESSION_COMMAND "${DATA_INSTALL_DIR}/scripts/Xsession" CACHE PATH "Script to execute when starting the X11 desktop session") set(WAYLAND_SESSION_COMMAND "${DATA_INSTALL_DIR}/scripts/wayland-session" CACHE PATH "Script to execute when starting the Wayland desktop session") -set(CONFIG_FILE "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ddm.conf" CACHE PATH "Path of the ddm config file") -set(CONFIG_DIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}/ddm.conf.d" CACHE PATH "Path of the ddm config directory") set(ACCOUNTSSERVICE_DATA_DIR "/var/lib/AccountsService" CACHE PATH "Path of the accountsservice data directory") -set(SYSTEM_CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/lib/ddm/ddm.conf.d" CACHE PATH "Path of the system ddm config directory") set(LOG_FILE "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/ddm.log" CACHE PATH "Path of the ddm log file") set(DBUS_CONFIG_FILENAME "org.freedesktop.DisplayManager.conf" CACHE STRING "Name of the ddm config file") set(COMPONENTS_TRANSLATION_DIR "${DATA_INSTALL_DIR}/translations" CACHE PATH "Components translations directory") diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index bef73ba..a1d1f9e 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -9,6 +9,21 @@ install(FILES DESTINATION "${DBUS_CONFIG_DIR}" ) +# DConfig schemas (org.deepin.dde.ddm) +set(DDM_DCONFIG_DIR "${CMAKE_INSTALL_FULL_DATADIR}/dsg/configs/org.deepin.dde.ddm") + +configure_file( + "dconfig/org.deepin.dde.ddm.json.in" + "${CMAKE_CURRENT_BINARY_DIR}/dconfig/org.deepin.dde.ddm.json" + @ONLY +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/dconfig/org.deepin.dde.ddm.json" + "dconfig/org.deepin.dde.ddm.state.json" + DESTINATION "${DDM_DCONFIG_DIR}" +) + install(FILES "scripts/Xsession" "scripts/Xsetup" diff --git a/data/dconfig/org.deepin.dde.ddm.json.in b/data/dconfig/org.deepin.dde.ddm.json.in new file mode 100644 index 0000000..e8f9bc2 --- /dev/null +++ b/data/dconfig/org.deepin.dde.ddm.json.in @@ -0,0 +1,237 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "haltCommand": { + "value": "@HALT_COMMAND@", + "serial": 0, + "flags": [], + "name": "Halt Command", + "name[zh_CN]": "关机命令", + "description": "Halt command.", + "description[zh_CN]": "关机命令。", + "permissions": "read", + "visibility": "private" + }, + "rebootCommand": { + "value": "@REBOOT_COMMAND@", + "serial": 0, + "flags": [], + "name": "Reboot Command", + "name[zh_CN]": "重启命令", + "description": "Reboot command.", + "description[zh_CN]": "重启命令。", + "permissions": "read", + "visibility": "private" + }, + "namespaces": { + "value": [], + "serial": 0, + "flags": [], + "name": "Namespaces", + "name[zh_CN]": "命名空间", + "description": "Comma-separated list of paths bound to Linux namespaces to enter with setns() before starting the user session.", + "description[zh_CN]": "启动用户会话前通过 setns() 进入的 Linux 命名空间路径列表。", + "permissions": "read", + "visibility": "private" + }, + "cursorTheme": { + "value": "", + "serial": 0, + "flags": [], + "name": "Cursor Theme", + "name[zh_CN]": "光标主题", + "description": "Cursor theme used in the greeter.", + "description[zh_CN]": "登录界面使用的光标主题。", + "permissions": "read", + "visibility": "private" + }, + "cursorSize": { + "value": "", + "serial": 0, + "flags": [], + "name": "Cursor Size", + "name[zh_CN]": "光标大小", + "description": "Cursor size used in the greeter.", + "description[zh_CN]": "登录界面使用的光标大小。", + "permissions": "read", + "visibility": "private" + }, + "x11ServerPath": { + "value": "/usr/bin/X", + "serial": 0, + "flags": [], + "name": "X11 Server Path", + "name[zh_CN]": "X11 Server 路径", + "description": "Path to the X server binary.", + "description[zh_CN]": "X server 可执行文件路径。", + "permissions": "read", + "visibility": "private" + }, + "x11ServerArguments": { + "value": "-nolisten tcp", + "serial": 0, + "flags": [], + "name": "X11 Server Arguments", + "name[zh_CN]": "X11 Server 参数", + "description": "Arguments passed to the X server invocation.", + "description[zh_CN]": "传递给 X server 的启动参数。", + "permissions": "read", + "visibility": "private" + }, + "x11SessionDir": { + "value": ["/usr/local/share/xsessions", "/usr/share/xsessions"], + "serial": 0, + "flags": [], + "name": "X11 Session Directories", + "name[zh_CN]": "X11 会话目录", + "description": "Comma-separated list of directories containing available X sessions.", + "description[zh_CN]": "包含可用 X 会话的目录列表。", + "permissions": "read", + "visibility": "private" + }, + "x11SessionCommand": { + "value": "@SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "X11 Session Command", + "name[zh_CN]": "X11 会话命令", + "description": "Path to a script to execute when starting the desktop session.", + "description[zh_CN]": "启动桌面会话时执行的脚本路径。", + "permissions": "read", + "visibility": "private" + }, + "x11SessionLogFile": { + "value": ".local/share/ddm/xorg-session.log", + "serial": 0, + "flags": [], + "name": "X11 Session Log File", + "name[zh_CN]": "X11 会话日志文件", + "description": "Path to the user session log file, relative to the home directory.", + "description[zh_CN]": "用户会话日志文件路径,相对于用户主目录。", + "permissions": "read", + "visibility": "private" + }, + "x11DisplayCommand": { + "value": "@DATA_INSTALL_DIR@/scripts/Xsetup", + "serial": 0, + "flags": [], + "name": "X11 Display Command", + "name[zh_CN]": "X11 显示启动命令", + "description": "Path to a script to execute when starting the display server.", + "description[zh_CN]": "启动显示服务器时执行的脚本路径。", + "permissions": "read", + "visibility": "private" + }, + "x11DisplayStopCommand": { + "value": "@DATA_INSTALL_DIR@/scripts/Xstop", + "serial": 0, + "flags": [], + "name": "X11 Display Stop Command", + "name[zh_CN]": "X11 显示停止命令", + "description": "Path to a script to execute when stopping the display server.", + "description[zh_CN]": "停止显示服务器时执行的脚本路径。", + "permissions": "read", + "visibility": "private" + }, + "waylandSessionDir": { + "value": ["/usr/local/share/wayland-sessions", "/usr/share/wayland-sessions"], + "serial": 0, + "flags": [], + "name": "Wayland Session Directories", + "name[zh_CN]": "Wayland 会话目录", + "description": "Comma-separated list of directories containing available Wayland sessions.", + "description[zh_CN]": "包含可用 Wayland 会话的目录列表。", + "permissions": "read", + "visibility": "private" + }, + "waylandSessionCommand": { + "value": "@WAYLAND_SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "Wayland Session Command", + "name[zh_CN]": "Wayland 会话命令", + "description": "Path to a script to execute when starting the desktop session.", + "description[zh_CN]": "启动桌面会话时执行的脚本路径。", + "permissions": "read", + "visibility": "private" + }, + "waylandSessionLogFile": { + "value": ".local/share/ddm/wayland-session.log", + "serial": 0, + "flags": [], + "name": "Wayland Session Log File", + "name[zh_CN]": "Wayland 会话日志文件", + "description": "Path to the user session log file, relative to the home directory.", + "description[zh_CN]": "用户会话日志文件路径,相对于用户主目录。", + "permissions": "read", + "visibility": "private" + }, + "singleSessionCommand": { + "value": "@WAYLAND_SESSION_COMMAND@", + "serial": 0, + "flags": [], + "name": "Single Session Command", + "name[zh_CN]": "单例会话命令", + "description": "Path to a script to execute when starting the Treeland user session.", + "description[zh_CN]": "启动 Treeland 用户会话时执行的脚本路径。", + "permissions": "read", + "visibility": "private" + }, + "usersDefaultPath": { + "value": "/usr/local/bin:/usr/bin:/bin", + "serial": 0, + "flags": [], + "name": "Users Default Path", + "name[zh_CN]": "用户默认 PATH", + "description": "Default $PATH for logged in users.", + "description[zh_CN]": "已登录用户的默认 $PATH。", + "permissions": "read", + "visibility": "private" + }, + "rememberLastUser": { + "value": true, + "serial": 0, + "flags": [], + "name": "Remember Last User", + "name[zh_CN]": "记住上次用户", + "description": "Remember the last successfully logged in user.", + "description[zh_CN]": "记住上次成功登录的用户。", + "permissions": "read", + "visibility": "private" + }, + "rememberLastSession": { + "value": true, + "serial": 0, + "flags": [], + "name": "Remember Last Session", + "name[zh_CN]": "记住上次会话", + "description": "Remember the session of the last successfully logged in user.", + "description[zh_CN]": "记住上次成功登录用户的会话。", + "permissions": "read", + "visibility": "private" + }, + "autologinUser": { + "value": "", + "serial": 0, + "flags": [], + "name": "Autologin User", + "name[zh_CN]": "自动登录用户", + "description": "User to log in automatically (empty disables autologin).", + "description[zh_CN]": "自动登录的用户(为空则禁用自动登录)。", + "permissions": "readwrite", + "visibility": "public" + }, + "autologinSession": { + "value": "", + "serial": 0, + "flags": [], + "name": "Autologin Session", + "name[zh_CN]": "自动登录会话", + "description": "Session to load for automatic login (overrides the last used session).", + "description[zh_CN]": "自动登录时加载的会话(覆盖上次使用的会话)。", + "permissions": "readwrite", + "visibility": "public" + } + } +} diff --git a/data/dconfig/org.deepin.dde.ddm.state.json b/data/dconfig/org.deepin.dde.ddm.state.json new file mode 100644 index 0000000..5b38218 --- /dev/null +++ b/data/dconfig/org.deepin.dde.ddm.state.json @@ -0,0 +1,28 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "lastUser": { + "value": "", + "serial": 0, + "flags": [], + "name": "Last User", + "name[zh_CN]": "上次用户", + "description": "Name of the last logged-in user. This user will be preselected when the login screen appears.", + "description[zh_CN]": "上次登录的用户名,登录界面将预选该用户。", + "permissions": "readwrite", + "visibility": "private" + }, + "lastSession": { + "value": "", + "serial": 0, + "flags": [], + "name": "Last Session", + "name[zh_CN]": "上次会话", + "description": "Name of the session for the last logged-in user. This session will be preselected when the login screen appears.", + "description[zh_CN]": "上次登录用户的会话名称,登录界面将预选该会话。", + "permissions": "readwrite", + "visibility": "private" + } + } +} diff --git a/debian/changelog b/debian/changelog index 98101df..e2c0a25 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +ddm (0.3.9) unstable; urgency=medium + + * feat(config): switch DDM configuration to dconfig + * deb: add libdtk6core-dev build dependency for DConfig support + + -- guolin Thu, 27 Aug 2026 10:58:49 +0800 + ddm (0.3.8) unstable; urgency=medium * feat: support automatic login for user sessions diff --git a/debian/control b/debian/control index 118d855..d0c4799 100644 --- a/debian/control +++ b/debian/control @@ -4,6 +4,7 @@ Maintainer: justforlxz Build-Depends: debhelper-compat (= 13), cmake (>= 3.4~), extra-cmake-modules (>= 1.4.0~), + libdtk6core-dev, libpam0g-dev, libsystemd-dev [linux-any], libwayland-dev, @@ -39,6 +40,7 @@ Package: libddm-dev Architecture: any Depends: ${misc:Depends}, libddm( =${binary:Version}), + libdtk6core-dev, qt6-base-dev Description: DDM devel library provide PAM headers diff --git a/services/ddm-tmpfiles.conf.in b/services/ddm-tmpfiles.conf.in index 1ccdc92..f041b51 100644 --- a/services/ddm-tmpfiles.conf.in +++ b/services/ddm-tmpfiles.conf.in @@ -1,4 +1,4 @@ -# Home dir of the ddm user, also contains state.conf +# Home dir of the ddm user d ${STATE_DIR} 0750 dde dde # This contains X11 auth files passed to Xorg and the greeter d ${RUNTIME_DIR} 0711 root root diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 2b91efc..8a142bf 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,7 +1,8 @@ configure_file("Constants.h.in" "Constants.h" IMMEDIATE @ONLY) + set(PUBLIC_HEADERS - ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.h ${CMAKE_SOURCE_DIR}/src/common/Configuration.h + ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.h ${CMAKE_SOURCE_DIR}/src/common/MessageHandler.h ${CMAKE_SOURCE_DIR}/src/common/Messages.h ${CMAKE_SOURCE_DIR}/src/common/Session.h @@ -13,8 +14,8 @@ set(PUBLIC_HEADERS ) set(SRCS - ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp + ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp ${CMAKE_SOURCE_DIR}/src/common/Session.cpp ${CMAKE_SOURCE_DIR}/src/common/SignalHandler.cpp ${CMAKE_SOURCE_DIR}/src/common/SocketWriter.cpp @@ -36,6 +37,8 @@ target_link_libraries(common Qt6::DBus Qt6::Network PkgConfig::LibXau + PUBLIC + Dtk6::Core ) target_include_directories(common INTERFACE diff --git a/src/common/ConfigReader.cpp b/src/common/ConfigReader.cpp index 00baf75..165a086 100644 --- a/src/common/ConfigReader.cpp +++ b/src/common/ConfigReader.cpp @@ -1,5 +1,5 @@ /* - * INI Configuration parser classes + * INI Configuration parser classes (DConfig backed) * Copyright (C) 2014 Martin Bříza * * This library is free software; you can redistribute it and/or @@ -19,16 +19,17 @@ */ #include "ConfigReader.h" -#include -#include +#include + +#include #include -#include +#include +#include #include -#include -#include -#include +#include #include +#include QTextStream &operator>>(QTextStream &str, QStringList &list) { list.clear(); @@ -63,12 +64,82 @@ QTextStream &operator<<(QTextStream &str, const bool &val) { } namespace DDM { - // has to be specialised because QTextStream reads only words into a QString + namespace { + QString dconfigKey(const QString §ion, const QString &entry) { + static const QHash s_overrides = { + { QStringLiteral("Theme.CursorTheme"), QStringLiteral("cursorTheme") }, + { QStringLiteral("Theme.CursorSize"), QStringLiteral("cursorSize") }, + { QStringLiteral("Users.RememberLastUser"), QStringLiteral("rememberLastUser") }, + { QStringLiteral("Users.RememberLastSession"), + QStringLiteral("rememberLastSession") }, + }; + + const QString fullName = section + QLatin1Char('.') + entry; + auto it = s_overrides.constFind(fullName); + if (it != s_overrides.constEnd()) + return it.value(); + + QString key; + if (section != QStringLiteral(IMPLICIT_SECTION)) + key = section + entry; + else + key = entry; + if (!key.isEmpty()) + key[0] = key[0].toLower(); + return key; + } + + DTK_CORE_NAMESPACE::DConfig *dconfigFor(const QString &appId, const QString &name) { + static QHash s_configs; + const QString cacheKey = appId + QLatin1Char('/') + name; + auto it = s_configs.constFind(cacheKey); + if (it != s_configs.constEnd()) + return it.value(); + auto *config = DTK_CORE_NAMESPACE::DConfig::create(appId, name, QString(), nullptr); + s_configs.insert(cacheKey, config); + return config; + } + + QString variantToString(const QVariant &value) { + if (value.typeId() == QMetaType::QStringList) + return value.toStringList().join(QLatin1Char(',')); + if (value.typeId() == QMetaType::QVariantList) { + QStringList stringList; + const auto list = value.toList(); + for (const QVariant &item : list) + stringList << item.toString(); + return stringList.join(QLatin1Char(',')); + } + return value.toString(); + } + + QVariant stringToVariant(DTK_CORE_NAMESPACE::DConfig *config, + const QString &key, + const QString &str) { + switch (config->value(key).typeId()) { + case QMetaType::Bool: + return QVariant(str.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0); + case QMetaType::QStringList: + case QMetaType::QVariantList: { + QStringList list; + const auto parts = QStringView{ str }.split(u','); + for (const QStringView &part : parts) { + const QStringView trimmed = part.trimmed(); + if (!trimmed.isEmpty()) + list.append(trimmed.toString()); + } + return QVariant(list); + } + default: + return QVariant(str); + } + } + } // namespace + template <> void ConfigEntry::setValue(const QString &str) { m_value = str.trimmed(); } - ConfigSection::ConfigSection(ConfigBase *parent, const QString &name) : m_parent(parent), m_name(name) { m_parent->m_sections.insert(name, this); @@ -118,8 +189,6 @@ namespace DDM { return QStringLiteral("[%1]").arg(name()); } - - ConfigBase::ConfigBase(const QString &configPath, const QString &configDir, const QString &sysConfigDir) : m_path(configPath), m_configDir(configDir), @@ -142,226 +211,50 @@ namespace DDM { void ConfigBase::load() { - //order of priority from least influence to most influence, is - // * m_sysConfigDir (system settings /usr/lib/ddm/ddm.conf.d/) in alphabetical order - // * m_configDir (user settings in /etc/ddm.conf.d/) in alphabetical order - // * m_path (classic fallback /etc/ddm.conf) - - QStringList files; - QDateTime latestModificationTime = QFileInfo(m_path).lastModified(); - - if (!m_sysConfigDir.isEmpty()) { - //include the configDir in modification time so we also reload on any files added/removed - QDir dir(m_sysConfigDir); - if (dir.exists()) { - latestModificationTime = std::max(latestModificationTime, QFileInfo(m_sysConfigDir).lastModified()); - const auto dirFiles = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware); - for (const QFileInfo &file : dirFiles) { - files << (file.absoluteFilePath()); - latestModificationTime = std::max(latestModificationTime, file.lastModified()); - } - } - } - if (!m_configDir.isEmpty()) { - //include the configDir in modification time so we also reload on any files added/removed - QDir dir(m_configDir); - if (dir.exists()) { - latestModificationTime = std::max(latestModificationTime, QFileInfo(m_configDir).lastModified()); - const auto dirFiles = dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::LocaleAware); - for (const QFileInfo &file : dirFiles) { - files << (file.absoluteFilePath()); - latestModificationTime = std::max(latestModificationTime, file.lastModified()); - } - } - } - - files << m_path; - - if (latestModificationTime <= m_fileModificationTime) { + // The Config macro constructs and loads the config objects during + // static initialization, before a QCoreApplication exists. DConfig + // cannot be used then, so defer the actual load to runtime. + if (!QCoreApplication::instance()) return; - } - m_fileModificationTime = latestModificationTime; - for (const QString &filepath : std::as_const(files)) { - loadInternal(filepath); - } - } - - - void ConfigBase::loadInternal(const QString &filepath) { - QString currentSection = QStringLiteral(IMPLICIT_SECTION); - - QFile in(filepath); - - if (!in.open(QIODevice::ReadOnly)) + auto *config = dconfigFor(m_configDir, m_path); + if (!config || !config->isValid()) return; - while (!in.atEnd()) { - QString line = QString::fromUtf8(in.readLine()); - QStringView lineRef = QStringView(line).trimmed(); - // get rid of comments first - lineRef = lineRef.left(lineRef.indexOf(QLatin1Char('#'))).trimmed(); - - // In version 0.14.0, these sections were renamed - if (currentSection == QStringLiteral("XDisplay")) - currentSection = QStringLiteral("X11"); - else if (currentSection == QStringLiteral("WaylandDisplay")) - currentSection = QStringLiteral("Wayland"); - - // value assignment - int separatorPosition = lineRef.indexOf(QLatin1Char('=')); - if (separatorPosition >= 0) { - QString name = lineRef.left(separatorPosition).trimmed().toString(); - QStringView value = lineRef.mid(separatorPosition + 1).trimmed(); - - auto sectionIterator = m_sections.constFind(currentSection); - if (sectionIterator != m_sections.constEnd() && sectionIterator.value()->entry(name)) - sectionIterator.value()->entry(name)->setValue(value.toString()); - else - // if we don't have such member in the config, nag about it - m_unusedVariables = true; + + for (ConfigSection *section : m_sections) { + const auto &entries = section->entries(); + for (auto it = entries.constBegin(); it != entries.constEnd(); ++it) { + const QVariant value = config->value(dconfigKey(section->name(), it.key())); + if (!value.isValid()) + continue; + it.value()->setValue(variantToString(value)); } - // section start - else if (lineRef.startsWith(QLatin1Char('[')) && lineRef.endsWith(QLatin1Char(']'))) - currentSection = lineRef.mid(1, lineRef.length() - 2).toString(); } } void ConfigBase::save(const ConfigSection *section, const ConfigEntryBase *entry) { - // to know if we should overwrite the config or not - bool changed = false; - // stores the order of the loaded sections - // each one could be there only once - if it occurs more times in the config, the occurrences are merged - QVector sectionOrder; - // the actual bytearray data for every section - QHash sectionData; - // map of nondefault entries which should be saved if they are not found in the current config file - QMultiHash remainingEntries; - - - /* - * Initialization of the map of nondefault values to be saved - */ - if (section) { - if (entry && !entry->matchesDefault()) - remainingEntries.insert(section, entry); - else { - for (const ConfigEntryBase *b : std::as_const(section->entries())) - if (!b->matchesDefault()) - remainingEntries.insert(section, b); - } - } - else { - for (const ConfigSection *s : std::as_const(m_sections)) { - for (const ConfigEntryBase *b : std::as_const(s->entries())) - if (!b->matchesDefault()) - remainingEntries.insert(s, b); - } - } - - // initialize the current section - General, usually - const ConfigSection *currentSection = m_sections.value(QStringLiteral(IMPLICIT_SECTION)); - - // stuff to store the pre-section stuff (comments) to the start of the right section, not the end of the previous one - QByteArray junk; - // stores the junk to the temporary storage - auto collectJunk = [&junk](const QString &data) { - junk.append(data.toUtf8()); - }; + auto *config = dconfigFor(m_configDir, m_path); + if (!config) + return; - // a short function to assign the current junk and current line to the right section, eventually create a new one - auto writeSectionData = [¤tSection, &junk, §ionOrder, §ionData](const QString &data) { - if (currentSection && !sectionOrder.contains(currentSection)) { - sectionOrder.append(currentSection); - sectionData[currentSection] = QByteArray(); - } - sectionData[currentSection].append(junk); - sectionData[currentSection].append(data.toUtf8()); - junk.clear(); + auto saveEntry = [&config](const ConfigSection *s, const ConfigEntryBase *e) { + const QString key = dconfigKey(s->name(), e->name()); + if (e->matchesDefault()) + config->reset(key); + else + config->setValue(key, stringToVariant(config, key, e->value())); }; - // loading and checking phase - QFile file(m_path); - bool ok = file.open(QIODevice::ReadOnly); // first just for reading - Q_ASSERT(ok); - while (!file.atEnd()) { - const QString line = QString::fromUtf8(file.readLine()); - // get rid of comments first - QStringView trimmedLine = QStringView{line}.left(line.indexOf(QLatin1Char('#'))).trimmed(); - QStringView comment; - if (line.indexOf(QLatin1Char('#')) >= 0) - comment = QStringView{line}.mid(line.indexOf(QLatin1Char('#'))).trimmed(); - - // value assignment - int separatorPosition = trimmedLine.indexOf(QLatin1Char('=')); - if (separatorPosition >= 0) { - QString name = trimmedLine.left(separatorPosition).trimmed().toString(); - QStringView value = trimmedLine.mid(separatorPosition + 1).trimmed(); - - if (currentSection && currentSection->entry(name)) { - // this monstrous condition checks the parameters if only one entry/section should be saved - if ((entry && section && section->name() == currentSection->name() && entry->name() == name) || - (!entry && section && section->name() == currentSection->name()) || - value != currentSection->entry(name)->value()) { - changed = true; - writeSectionData(QStringLiteral("%1=%2 %3\n").arg(name).arg(currentSection->entry(name)->value()).arg(comment.toString())); - } - else - writeSectionData(line); - remainingEntries.remove(currentSection, currentSection->entry(name)); - } - else { - if (currentSection) - m_unusedVariables = true; - writeSectionData(QStringLiteral("%1 %2\n").arg(trimmedLine.toString()).arg(QStringLiteral(UNUSED_VARIABLE_COMMENT))); - } - } - - // section start - else if (trimmedLine.startsWith(QLatin1Char('[')) && trimmedLine.endsWith(QLatin1Char(']'))) { - const QString name = trimmedLine.mid(1, trimmedLine.length() - 2).toString(); - auto sectionIterator = m_sections.constFind(name); - if (sectionIterator != m_sections.constEnd()) { - currentSection = sectionIterator.value(); - if (!sectionOrder.contains(currentSection)) - writeSectionData(line); - } - else { - m_unusedSections = true; - currentSection = nullptr; - writeSectionData(line); - } - } - - // other stuff, like comments and whatnot - else { - if (line != QStringLiteral(UNUSED_SECTION_COMMENT)) - collectJunk(line); - } - } - file.close(); - - for (auto it = remainingEntries.begin(); it != remainingEntries.end(); it++) { - changed = true; - currentSection = it.key(); - if (!sectionOrder.contains(currentSection)) - writeSectionData(currentSection->toConfigShort()); - writeSectionData(QStringLiteral("\n")); - writeSectionData(it.value()->toConfigFull()); - } - - // rewrite the whole thing only if there are changes - if (changed) { - bool ok = file.open(QIODevice::WriteOnly | QIODevice::Truncate); - Q_ASSERT(ok); - for (const ConfigSection *s : sectionOrder) - file.write(sectionData.value(s)); - - if (sectionData.contains(nullptr)) { - file.write("\n"); - file.write(UNUSED_SECTION_COMMENT); - file.write(sectionData.value(nullptr).trimmed()); - file.write("\n"); - } + if (section) { + if (entry) + saveEntry(section, entry); + else + for (const ConfigEntryBase *e : section->entries()) + saveEntry(section, e); + } else { + for (const ConfigSection *s : m_sections) + for (const ConfigEntryBase *e : s->entries()) + saveEntry(s, e); } } diff --git a/src/common/Configuration.h b/src/common/Configuration.h index da8c864..5a8d3e1 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -25,15 +25,13 @@ #include #include #include -#include #include "Constants.h" #include "ConfigReader.h" namespace DDM { - // Name File Sections and/or Entries (but anything else too, it's a class) - Entries in a Config are assumed to be in the General section - Config(MainConfig, QStringLiteral(CONFIG_FILE), QStringLiteral(CONFIG_DIR), QStringLiteral(SYSTEM_CONFIG_DIR), + Config(MainConfig, QStringLiteral("org.deepin.dde.ddm"), QStringLiteral("org.deepin.dde.ddm"), QString(), // Name Type Default value Description Entry(HaltCommand, QString, _S(HALT_COMMAND), _S("Halt command")); @@ -82,7 +80,7 @@ namespace DDM { ); ); - Config(StateConfig, []()->QString{auto tmp = getpwnam("ddm"); return tmp ? QString::fromLocal8Bit(tmp->pw_dir) : QStringLiteral(STATE_DIR);}().append(QStringLiteral("/state.conf")), QString(), QString(), + Config(StateConfig, QStringLiteral("org.deepin.dde.ddm.state"), QStringLiteral("org.deepin.dde.ddm"), QString(), Section(Last, Entry(Session, QString, QString(), _S("Name of the session for the last logged-in user.\n" "This session will be preselected when the login screen appears.")); @@ -95,4 +93,4 @@ namespace DDM { extern StateConfig stateConfig; } -#endif // DDM_CONFIGURATION_H +#endif // DDM_CONFIGURATION_H \ No newline at end of file diff --git a/src/common/Constants.h.in b/src/common/Constants.h.in index 098daee..8b1796a 100644 --- a/src/common/Constants.h.in +++ b/src/common/Constants.h.in @@ -23,7 +23,6 @@ #define BIN_INSTALL_DIR "@CMAKE_INSTALL_FULL_BINDIR@" #define LIBEXEC_INSTALL_DIR "@CMAKE_INSTALL_FULL_LIBEXECDIR@" #define DATA_INSTALL_DIR "@DATA_INSTALL_DIR@" -#define SYS_CONFIG_DIR "@CMAKE_INSTALL_FULL_SYSCONFDIR@" #define COMPONENTS_TRANSLATION_DIR "@COMPONENTS_TRANSLATION_DIR@" #define RUNTIME_DIR "@RUNTIME_DIR@" #define STATE_DIR "@STATE_DIR@" @@ -32,16 +31,12 @@ #define SESSION_COMMAND "@SESSION_COMMAND@" #define WAYLAND_SESSION_COMMAND "@WAYLAND_SESSION_COMMAND@" -#define CONFIG_FILE "@CONFIG_FILE@" -#define CONFIG_DIR "@CONFIG_DIR@" -#define SYSTEM_CONFIG_DIR "@SYSTEM_CONFIG_DIR@" - #define LOG_FILE "@LOG_FILE@" #define UID_MIN @UID_MIN@ #define UID_MAX @UID_MAX@ -#define HALT_COMMAND "@HALT_COMMAND@" -#define REBOOT_COMMAND "@REBOOT_COMMAND@" +#define HALT_COMMAND "@HALT_COMMAND@" +#define REBOOT_COMMAND "@REBOOT_COMMAND@" #endif // DDM_CONSTANTS_H diff --git a/src/daemon/DaemonApp.cpp b/src/daemon/DaemonApp.cpp index f80fdb8..3ac0675 100644 --- a/src/daemon/DaemonApp.cpp +++ b/src/daemon/DaemonApp.cpp @@ -136,6 +136,12 @@ int main(int argc, char **argv) { // create application DDM::DaemonApp app(argc, argv); + // DConfig is only usable once a QCoreApplication exists, so the + // configuration could not be loaded during static construction; (re)load + // it here before the daemon starts using it. + DDM::mainConfig.load(); + DDM::stateConfig.load(); + // run application return app.exec(); }