Skip to content

Conversation

@calsys456
Copy link
Contributor

@calsys456 calsys456 commented Jan 26, 2026

The original ConfigReader is buggy and difficult for maintainence, refactor to make it both simple and keep backward compatibility.

Summary by Sourcery

Introduce a new generic configuration API and migrate existing components to use it while adjusting session management to use systemd login for TTY checks.

Enhancements:

  • Replace the old Configuration/ConfigReader system with a unified Config class and shared main/state configuration instances backed by defaults.
  • Update daemon, display, session, and power management code to use the new Config API for accessing settings and serializing default configs.
  • Switch TTY-in-use detection in the display daemon from a logind D-Bus based implementation to systemd sd-login calls for improved robustness.
  • Clean up CMake build targets to remove obsolete configuration reader sources and expose the new Config header as the public configuration interface.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: calsys456

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@deepin-ci-robot
Copy link

Hi @calsys456. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 26, 2026

Reviewer's Guide

Refactors the configuration system by replacing the old ConfigReader/Configuration API with a new unified Config class and updates all call sites, while also switching TTY-in-use detection to systemd sd-login APIs and adjusting example-config generation.

Sequence diagram for main configuration usage during daemon startup

sequenceDiagram
    participant Main as main
    participant DaemonApp as DaemonApp
    participant Config as mainConfig

    Main->>DaemonApp: parseCommandLine()
    DaemonApp->>Config: load()
    alt load fails
        DaemonApp->>Config: wipe()
        DaemonApp->>Config: defaultConfig()
        DaemonApp-->>Main: print example config (when --example-config)
    else load succeeds
        DaemonApp->>Config: get<QString>(Users, DefaultPath)
        DaemonApp->>Config: get<bool>(Users, RememberLastUser)
        DaemonApp->>Config: get<bool>(Users, RememberLastSession)
        DaemonApp-->>Main: proceed with normal startup
    end
Loading

Class diagram for new unified Config API

classDiagram
    class Config {
        - QString m_configPath
        - QMap~QString, QMap~QString, QVariant~~ m_data
        - QMap~QString, QMap~QString, QPair~QVariant, QString~~~ m_defaults
        + Config(QString configPath, QMap~QString, QMap~QString, QPair~QVariant, QString~~~ defaults, QObject parent)
        + T get~T~(QString section, QString entry) const
        + T get~T~(QString entry) const
        + void set~T~(QString section, QString entry, T value)
        + void set~T~(QString entry, T value)
        + bool setDefault(QString section, QString entry)
        + bool setDefault(QString entry)
        + bool load()
        + bool save() const
        + QString defaultConfig() const
        + void wipe()
    }

    class mainConfig {
        <<singleton>>
    }

    class stateConfig {
        <<singleton>>
    }

    QObject <|-- Config
    mainConfig ..> Config : global_instance
    stateConfig ..> Config : global_instance

    class ThemeSection {
        <<config_section>>
        + CursorTheme : QString
        + CursorSize : QString
    }

    class X11Section {
        <<config_section>>
        + ServerPath : QString
        + ServerArguments : QString
        + SessionDir : QStringList
        + SessionCommand : QString
        + SessionLogFile : QString
        + DisplayCommand : QString
        + DisplayStopCommand : QString
    }

    class WaylandSection {
        <<config_section>>
        + SessionDir : QStringList
        + SessionCommand : QString
        + SessionLogFile : QString
    }

    class SingleSection {
        <<config_section>>
        + SessionCommand : QString
    }

    class UsersSection {
        <<config_section>>
        + DefaultPath : QString
        + RememberLastUser : bool
        + RememberLastSession : bool
    }

    class RootSection {
        <<config_section>>
        + HaltCommand : QString
        + RebootCommand : QString
        + Namespaces : QStringList
    }

    class LastSection {
        <<config_section>>
        + User : QString
        + Session : QString
    }

    Config "1" o-- "*" ThemeSection : defaults
    Config "1" o-- "*" X11Section : defaults
    Config "1" o-- "*" WaylandSection : defaults
    Config "1" o-- "*" SingleSection : defaults
    Config "1" o-- "*" UsersSection : defaults
    Config "1" o-- "*" RootSection : defaults
    Config "1" o-- "*" LastSection : defaults
Loading

File-Level Changes

Change Details Files
Introduce new generic Config class and define mainConfig/stateConfig defaults, replacing the old Configuration/ConfigReader implementation.
  • Add src/common/Config.h implementing a QObject-based Config wrapper that stores configuration data in QMaps, supports typed get/set, default fallbacks, load/save serialization, and default-config generation with descriptions.
  • Initialize a global mainConfig instance backed by CONFIG_FILE with structured defaults for core settings (power commands, users, theme, X11/Wayland/Single sections, namespaces).
  • Initialize a global stateConfig instance pointing at STATE_DIR/state.conf with defaults for last user and session information.
  • Remove legacy ConfigReader and Configuration implementations and their headers/sources from the codebase and CMakeLists.
src/common/Config.h
src/common/ConfigReader.cpp
src/common/ConfigReader.h
src/common/Configuration.cpp
src/common/Configuration.h
src/common/CMakeLists.txt
Migrate daemon components from the old Configuration API to the new Config API.
  • Update Display logic for remembering last user/session and setting PATH to use mainConfig.get() and stateConfig.set()/setDefault() instead of nested struct properties.
  • Update XorgDisplayServer to read theme cursor settings, X server path/arguments, display setup/stop commands, and user PATH via mainConfig.get().
  • Update UserSession to resolve session command paths, namespaces list, and session log file names from mainConfig.get() calls.
  • Update Session discovery to obtain X11/Wayland session directories from mainConfig.get().
  • Update PowerManager to use mainConfig.get() for HaltCommand and RebootCommand.
  • Change DaemonApp example-config output to use mainConfig.defaultConfig() instead of toConfigFull(), and replace includes of Configuration.h with Config.h throughout the daemon and common code.
src/daemon/Display.cpp
src/daemon/XorgDisplayServer.cpp
src/daemon/UserSession.cpp
src/common/Session.cpp
src/daemon/PowerManager.cpp
src/daemon/DaemonApp.cpp
src/daemon/SeatManager.cpp
src/daemon/Display.h (indirect via include changes)
src/common/XAuth.cpp
Replace D-Bus based session TTY detection with systemd sd-login based implementation and clean up includes/order.
  • Reimplement isTtyInUse() in Display.cpp to iterate sessions via sd_get_sessions(), query tty and state with sd_session_get_tty/sd_session_get_state, and log when a matching non-closing session is found.
  • Add lifetime guards using std::unique_ptr with custom deleters to free sd-login allocated char** and char* buffers safely.
  • Adjust includes in Display.cpp to bring in systemd/sd-login.h, reorganize Qt and project headers, and remove the old Login1Manager/Login1Session usage in this function.
src/daemon/Display.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In isTtyInUse, the RAII guards for sessions, tty, and state capture these pointers by value when they are still nullptr, so the lambdas will not see the updated pointers and will never free the allocated memory; capture them by reference (or wrap them in a small struct) so the cleanup actually runs on the allocated buffers.
  • The new systemd-based session probing in isTtyInUse calls sd_get_sessions, sd_session_get_tty, and sd_session_get_state without checking their return values or guarding against nullptr results (e.g. state passed to strcmp), which can lead to undefined behavior on error; add return-code checks and null checks before using these pointers.
  • The new Config class header uses std::runtime_error and QTextStream but does not include <stdexcept> or <QTextStream>, which can cause build or ODR issues on some toolchains; explicitly include these headers where they are used.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `isTtyInUse`, the RAII guards for `sessions`, `tty`, and `state` capture these pointers by value when they are still `nullptr`, so the lambdas will not see the updated pointers and will never free the allocated memory; capture them by reference (or wrap them in a small struct) so the cleanup actually runs on the allocated buffers.
- The new systemd-based session probing in `isTtyInUse` calls `sd_get_sessions`, `sd_session_get_tty`, and `sd_session_get_state` without checking their return values or guarding against `nullptr` results (e.g. `state` passed to `strcmp`), which can lead to undefined behavior on error; add return-code checks and null checks before using these pointers.
- The new `Config` class header uses `std::runtime_error` and `QTextStream` but does not include `<stdexcept>` or `<QTextStream>`, which can cause build or ODR issues on some toolchains; explicitly include these headers where they are used.

## Individual Comments

### Comment 1
<location> `src/daemon/Display.cpp:61-70` </location>
<code_context>
-                    qDebug() << "tty" << desiredTty << "already in use by" << session.user().path.path() << session.state()
-                                      << session.display() << session.desktop() << session.vTNr();
-                    return true;
+        char **sessions = nullptr;
+        std::unique_ptr<void, std::function<void(void *)>> guard(
+            nullptr,
+            [sessions]([[maybe_unused]] void *) {
+                if (sessions) {
+                    for (char **s = sessions; s && *s; ++s)
+                        free(*s);
+                    free(sessions);
                 }
+            });
+        sd_get_sessions(&sessions);
+        for (char **s = sessions; s && *s; ++s) {
+            char *tty = nullptr;
</code_context>

<issue_to_address>
**issue (bug_risk):** The RAII guard for `sessions` captures the pointer by value before it is initialized, so it will never free the allocated sessions.

Here `guard` is created while `sessions` is still `nullptr`, and the lambda captures `sessions` by value:

```cpp
char **sessions = nullptr;
std::unique_ptr<void, std::function<void(void *)>> guard(
    nullptr,
    [sessions]([[maybe_unused]] void *) {
        if (sessions) {
            for (char **s = sessions; s && *s; ++s)
                free(*s);
            free(sessions);
        }
    });
sd_get_sessions(&sessions);
``

Since the capture is by value, the deleter always sees `sessions == nullptr`, so the memory from `sd_get_sessions` is never freed. You can fix this by capturing by reference (`[&sessions](void *) { ... }`) or by using a small RAII type that stores a `char **&` and performs the free logic. The same issue appears for `tty`/`state` in the inner loop.
</issue_to_address>

### Comment 2
<location> `src/daemon/Display.cpp:73-82` </location>
<code_context>
+            });
+        sd_get_sessions(&sessions);
+        for (char **s = sessions; s && *s; ++s) {
+            char *tty = nullptr;
+            char *state = nullptr;
+            std::unique_ptr<void, std::function<void(void *)>> guard2(
+                nullptr,
+                [tty, state]([[maybe_unused]] void *) {
+                    if (tty)
+                        free(tty);
+                    if (state)
+                        free(state);
+                });
+            sd_session_get_tty(*s, &tty);
+            sd_session_get_state(*s, &state);
+            if (desiredTty == tty && strcmp(state, "closing") != 0) {
+                qDebug() << "tty" << desiredTty << "already in use by session" << *s;
+                return true;
</code_context>

<issue_to_address>
**issue (bug_risk):** `state` is used with `strcmp` without checking for null and without checking the return codes of the `sd_session_get_*` calls.

Inside the loop, `sd_session_get_tty` and `sd_session_get_state` may fail, leaving `tty`/`state` as `nullptr`, so `strcmp(state, "closing")` invokes undefined behavior and the return codes are ignored.

You can guard against this by checking the return values and only using `tty`/`state` on success, e.g.:

```cpp
if (sd_session_get_tty(*s, &tty) < 0)
    continue;
if (sd_session_get_state(*s, &state) < 0)
    continue;
if (desiredTty == QLatin1String(tty) && qstrcmp(state, "closing") != 0) {
    ...
}
```
</issue_to_address>

### Comment 3
<location> `src/daemon/Display.cpp:60` </location>
<code_context>
 #define STRINGIFY(x) #x

 namespace DDM {
     static bool isTtyInUse(const QString &desiredTty) {
-        if (Logind::isAvailable()) {
-            OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), Logind::managerPath(), QDBusConnection::systemBus());
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring `isTtyInUse` to use small typed RAII helpers instead of `std::function`-based `unique_ptr` guards to make ownership clearer and avoid leaks.

You can simplify `isTtyInUse` and remove the `std::function<void(void*)>`/lambda-based guards by using small, typed RAII helpers. That will both reduce complexity and fix a subtle bug with the current capture-by-value of `sessions` (the deleter sees the original `nullptr` and leaks the allocated array).

A concrete refactor could look like this:

```c++
namespace {

using CStr = std::unique_ptr<char, decltype(&free)>;

inline CStr makeCStr(char *p)
{
    return CStr(p, &free);
}

inline void freeSessions(char **sessions)
{
    if (!sessions)
        return;
    for (char **s = sessions; *s; ++s)
        free(*s);
    free(sessions);
}

using SessionList = std::unique_ptr<char *, decltype(&freeSessions)>;

} // namespace

static bool isTtyInUse(const QString &desiredTty)
{
    char **rawSessions = nullptr;
    if (sd_get_sessions(&rawSessions) < 0 || !rawSessions)
        return false;

    SessionList sessions(rawSessions, &freeSessions);

    for (char **s = sessions.get(); *s; ++s) {
        char *rawTty = nullptr;
        char *rawState = nullptr;

        if (sd_session_get_tty(*s, &rawTty) < 0 || !rawTty)
            continue;
        if (sd_session_get_state(*s, &rawState) < 0 || !rawState)
            continue;

        CStr tty = makeCStr(rawTty);
        CStr state = makeCStr(rawState);

        if (desiredTty == tty.get() && strcmp(state.get(), "closing") != 0) {
            qDebug() << "tty" << desiredTty << "already in use by session" << *s;
            return true;
        }
    }
    return false;
}
```

Key changes:

- Replace `std::unique_ptr<void, std::function<...>>` with typed aliases (`CStr`, `SessionList`) and simple free functions as deleters.
- Remove nested lifetime guards; resource ownership is explicit and linear.
- Avoid capturing `sessions`, `tty`, `state` in lambdas; the deleters now directly operate on their own pointer.
- Add basic error/null checks for the `sd_*` calls to keep the control flow straightforward and safer.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

The original ConfigReader is buggy and difficult for maintainence,
refactor to make it both simple and keep backward compatibility.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request refactors the configuration system by replacing the old ConfigReader/Configuration implementation with a new, simpler Config class. The refactor aims to simplify the codebase while maintaining backward compatibility.

Changes:

  • Introduced a new generic Config class with template-based get/set methods
  • Migrated all configuration access across the daemon components to use the new API
  • Removed the old ConfigReader and Configuration files
  • Updated CMakeLists.txt to reflect the new configuration interface

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/common/Config.h New configuration class implementation with inline global instances
src/common/Configuration.h Deleted old configuration definitions
src/common/Configuration.cpp Deleted old configuration implementation
src/common/ConfigReader.h Deleted old configuration reader interface
src/common/ConfigReader.cpp Deleted old configuration reader implementation
src/daemon/XorgDisplayServer.cpp Updated to use new Config API for X11 server configuration
src/daemon/UserSession.cpp Updated to use new Config API for session commands and namespaces
src/daemon/Display.cpp Updated to use new Config API for user preferences and state management
src/daemon/DaemonApp.cpp Updated to use new defaultConfig() method for --example-config
src/daemon/PowerManager.cpp Updated to use new Config API for halt/reboot commands
src/daemon/SeatManager.cpp Updated include to use new Config.h
src/common/Session.cpp Updated to use new Config API for session directories
src/common/XAuth.cpp Removed unused Configuration.h include
src/common/CMakeLists.txt Updated to expose new Config.h instead of old configuration headers

env.insert(QStringLiteral("DISPLAY"), display);
env.insert(QStringLiteral("HOME"), QStringLiteral("/"));
env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get());
env.insert(QStringLiteral("PATH"), mainConfig.get<QString>("Users", "DefaultPath"));
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation detected. This line uses spaces for indentation while surrounding lines use consistent formatting. The indentation should match the surrounding code style.

检测到不一致的缩进。此行使用空格缩进,而周围的行使用一致的格式。缩进应与周围的代码风格匹配。

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +147
} else if (value.indexOf(',') >= 0) {
QStringList list = value.split(',', Qt::SkipEmptyParts);
for (QString &item : list)
item = item.trimmed();
m_data[currentSection][name] = QVariant(list);
} else
m_data[currentSection][name] = QVariant(value);
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The load() method treats any value containing a comma as a QStringList (line 141-145), which may incorrectly parse string values that legitimately contain commas but are not intended to be lists. For example, a description like "Hello, world" would be parsed as a list with two elements.

Consider checking the default value's type (if available) to determine whether a value should be parsed as a list, rather than relying solely on the presence of a comma. This would make the parsing more robust and align with the actual data type expectations.

load() 方法将任何包含逗号的值视为 QStringList(第141-145行),这可能会错误解析合法包含逗号但不打算作为列表的字符串值。例如,像 "Hello, world" 这样的描述将被解析为包含两个元素的列表。

建议检查默认值的类型(如果可用)来确定值是否应解析为列表,而不是仅依赖逗号的存在。这将使解析更加健壮,并与实际的数据类型期望保持一致。

Suggested change
} else if (value.indexOf(',') >= 0) {
QStringList list = value.split(',', Qt::SkipEmptyParts);
for (QString &item : list)
item = item.trimmed();
m_data[currentSection][name] = QVariant(list);
} else
m_data[currentSection][name] = QVariant(value);
} else {
// Decide whether to treat the value as a list based on the default type.
bool treatAsList = false;
auto sectionDefaultsIt = m_defaults.constFind(currentSection);
if (sectionDefaultsIt != m_defaults.cend()) {
const auto &entryDefaults = sectionDefaultsIt.value();
auto entryIt = entryDefaults.constFind(name);
if (entryIt != entryDefaults.cend() &&
entryIt->first.userType() == QMetaType::QStringList) {
treatAsList = true;
}
}
if (treatAsList) {
QStringList list = value.split(',', Qt::SkipEmptyParts);
for (QString &item : list)
item = item.trimmed();
m_data[currentSection][name] = QVariant(list);
} else {
m_data[currentSection][name] = QVariant(value);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +65
template<typename T>
inline T get(const QString &entry) const {
return get<T>({}, entry);
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an empty string {} to represent the default section is inconsistent with the save() and defaultConfig() methods which explicitly check for non-empty section names (lines 167, 192). This works but makes the code less clear. Consider using a named constant like DEFAULT_SECTION or "General" to improve code readability and maintainability.

使用空字符串 {} 来表示默认部分与 save() 和 defaultConfig() 方法不一致,这些方法明确检查非空部分名称(第167、192行)。这有效但使代码不太清晰。建议使用命名常量,如 DEFAULT_SECTION 或 "General",以提高代码可读性和可维护性。

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +152
bool load() {
QFile file(m_configPath);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
QString currentSection{};
while (!file.atEnd()) {
QString line = QString::fromLocal8Bit(file.readLine()).trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue;
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.mid(1, line.length() - 2).trimmed();
} else {
int separatorPosition = line.indexOf('=');
if (separatorPosition >= 0) {
if (!m_data.contains(currentSection))
m_data[currentSection] = {};
QString name = line.left(separatorPosition).trimmed();
QString value = line.mid(separatorPosition + 1).trimmed();
if (value.isEmpty()) {
m_data[currentSection][name] = QVariant();
} else if (value.indexOf(',') >= 0) {
QStringList list = value.split(',', Qt::SkipEmptyParts);
for (QString &item : list)
item = item.trimmed();
m_data[currentSection][name] = QVariant(list);
} else
m_data[currentSection][name] = QVariant(value);
}
}
}
return true;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The load() method stores all configuration values as QString (line 147), including boolean values. However, when calling get(), the code attempts to convert QString to bool using QVariant::value(). QVariant's QString to bool conversion doesn't work as expected - it only returns true if the QString can be converted to a number and that number is non-zero, not based on "true"/"false" string values. This will cause boolean configuration values like "RememberLastUser" to always evaluate to false when loaded from a file.

Consider implementing proper type detection and conversion in the load() method. For example, check if a default value exists for the entry and use its type to determine how to parse the string value. Alternatively, implement special parsing for known boolean string values like "true", "false", "yes", "no", "1", "0" (case-insensitive).

boolean 配置值加载存在bug。load() 方法将所有配置值存储为 QString(第147行),包括布尔值。但调用 get() 时,代码尝试使用 QVariant::value() 将 QString 转换为 bool。QVariant 的 QString 到 bool 的转换不是基于 "true"/"false" 字符串,而是检查字符串是否可以转换为非零数字。这将导致从文件加载的布尔配置值(如 "RememberLastUser")总是评估为 false。

建议在 load() 方法中实现正确的类型检测和转换。例如,检查该条目是否存在默认值,并使用其类型确定如何解析字符串值。或者,为已知的布尔字符串值(如 "true"、"false"、"yes"、"no"、"1"、"0",不区分大小写)实现特殊解析。

Copilot uses AI. Check for mistakes.
Comment on lines +276 to +283
/** State configuration instance. */
inline Config stateConfig(
QStringLiteral(STATE_DIR "/state.conf"),
{ { "Last",
{ { "User", { QString(), "The last successfully logged in user" } },
{ "Session",
{ QString(), "The session of the last successfully logged in user" } } } } });
} // namespace DDM
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline stateConfig instance is never loaded from the configuration file. While stateConfig.save() is called when user state changes (lines 268, 392 in Display.cpp), there's no corresponding stateConfig.load() call anywhere in the codebase. This means the last user and session state are never restored across daemon restarts, breaking the "remember last user/session" functionality.

Add a stateConfig.load() call during application initialization, likely in DaemonApp constructor or before the first use in Display.cpp.

stateConfig 实例从未从配置文件加载。虽然在用户状态更改时调用了 stateConfig.save()(Display.cpp 的第268、392行),但代码库中没有相应的 stateConfig.load() 调用。这意味着最后一个用户和会话状态在守护进程重启后不会恢复,破坏了"记住上次用户/会话"功能。

建议在应用程序初始化期间添加 stateConfig.load() 调用,可能在 DaemonApp 构造函数中或在 Display.cpp 中首次使用之前。

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +152
bool load() {
QFile file(m_configPath);
if (!file.open(QIODevice::ReadOnly)) {
return false;
}
QString currentSection{};
while (!file.atEnd()) {
QString line = QString::fromLocal8Bit(file.readLine()).trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue;
if (line.startsWith('[') && line.endsWith(']')) {
currentSection = line.mid(1, line.length() - 2).trimmed();
} else {
int separatorPosition = line.indexOf('=');
if (separatorPosition >= 0) {
if (!m_data.contains(currentSection))
m_data[currentSection] = {};
QString name = line.left(separatorPosition).trimmed();
QString value = line.mid(separatorPosition + 1).trimmed();
if (value.isEmpty()) {
m_data[currentSection][name] = QVariant();
} else if (value.indexOf(',') >= 0) {
QStringList list = value.split(',', Qt::SkipEmptyParts);
for (QString &item : list)
item = item.trimmed();
m_data[currentSection][name] = QVariant(list);
} else
m_data[currentSection][name] = QVariant(value);
}
}
}
return true;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new Config class only loads from a single configuration file (m_configPath), while the old ConfigReader implementation supported loading from multiple configuration directories in order of priority: system config directory (/usr/lib/ddm/ddm.conf.d/), user config directory (/etc/ddm.conf.d/), and the main config file (/etc/ddm.conf). This is a breaking change that removes the ability to use drop-in configuration files, which is a common pattern in system configuration.

Consider restoring support for configuration directories to maintain backward compatibility and allow modular configuration. The old implementation loaded files from configDir and sysConfigDir in alphabetical order before loading the main config file.

新的 Config 类仅从单个配置文件(m_configPath)加载,而旧的 ConfigReader 实现支持按优先级顺序从多个配置目录加载:系统配置目录(/usr/lib/ddm/ddm.conf.d/)、用户配置目录(/etc/ddm.conf.d/)和主配置文件(/etc/ddm.conf)。这是一个破坏性更改,删除了使用插入式配置文件的能力,这是系统配置中的常见模式。

建议恢复对配置目录的支持以保持向后兼容性并允许模块化配置。旧实现在加载主配置文件之前按字母顺序从 configDir 和 sysConfigDir 加载文件。

Copilot uses AI. Check for mistakes.
m_data[currentSection][name] = QVariant(value);
}
}
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old ConfigReader implementation had backward compatibility for renamed sections: "XDisplay" was automatically mapped to "X11", and "WaylandDisplay" to "Wayland" during loading (see deleted ConfigReader.cpp lines 210-213). The new Config class doesn't include this backward compatibility, which means existing configuration files using the old section names will not work correctly.

Consider adding backward compatibility mappings in the load() method to handle legacy section names, or document this as a breaking change requiring manual configuration migration.

旧的 ConfigReader 实现对重命名的部分具有向后兼容性:"XDisplay" 自动映射到 "X11","WaylandDisplay" 映射到 "Wayland"(参见已删除的 ConfigReader.cpp 第210-213行)。新的 Config 类不包含此向后兼容性,这意味着使用旧部分名称的现有配置文件将无法正常工作。

建议在 load() 方法中添加向后兼容性映射以处理旧的部分名称,或将此记录为需要手动配置迁移的破坏性更改。

Suggested change
}
}
// Backward compatibility for legacy section names.
// Map "XDisplay" -> "X11".
if (m_data.contains(QStringLiteral("XDisplay"))) {
auto legacy = m_data.take(QStringLiteral("XDisplay"));
auto &target = m_data[QStringLiteral("X11")];
for (auto it = legacy.cbegin(); it != legacy.cend(); ++it) {
if (!target.contains(it.key())) {
target.insert(it.key(), it.value());
}
}
}
// Map "WaylandDisplay" -> "Wayland".
if (m_data.contains(QStringLiteral("WaylandDisplay"))) {
auto legacy = m_data.take(QStringLiteral("WaylandDisplay"));
auto &target = m_data[QStringLiteral("Wayland")];
for (auto it = legacy.cbegin(); it != legacy.cend(); ++it) {
if (!target.contains(it.key())) {
target.insert(it.key(), it.value());
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +52
/**
* Get a configuration entry value.
* If the entry is not found in the loaded configuration, the default value is returned.
* If no default value is set, an exception is thrown.
*
* @param section The configuration section name.
* @param entry The configuration entry name.
* @return The value of the configuration entry.
*/
template<typename T>
inline T get(const QString &section, const QString &entry) const {
if (m_data.contains(section) && m_data[section].contains(entry))
return m_data[section][entry].value<T>();
else if (m_defaults.contains(section) && m_defaults[section].contains(entry))
return m_defaults[section][entry].first.value<T>();
else
throw std::runtime_error(QStringLiteral("Config entry '%1/%2' not found")
.arg(section, entry)
.toStdString());
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The get() method throws std::runtime_error when a configuration entry is not found, but this exception type and the throwing behavior are not documented in the class documentation or the method's docstring. Callers may not expect exceptions and could fail to handle them properly, potentially causing crashes.

Add exception specifications to the documentation, or consider returning an optional/default value instead of throwing to make the API more robust and Qt-like. If exceptions are the intended design, document them clearly in both the class-level and method-level documentation.

get() 方法在找不到配置条目时抛出 std::runtime_error,但此异常类型和抛出行为未在类文档或方法的文档字符串中记录。调用者可能不期望异常,可能无法正确处理它们,可能导致崩溃。

建议在文档中添加异常规范,或考虑返回可选/默认值而不是抛出异常,以使 API 更健壮和更像 Qt。如果异常是预期的设计,请在类级别和方法级别的文档中明确记录它们。

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +283
/** Main configuration instance. */
inline Config mainConfig(
CONFIG_FILE,
{ { {},
{ { "HaltCommand", { HALT_COMMAND, "Halt command" } },
{ "RebootCommand", { REBOOT_COMMAND, "Reboot command" } },
{ "Namespaces",
{ QStringList(),
"List of Linux namespaces for user session to enter" } } } },
{ "Theme",
{ { "CursorTheme", { QString(), "Cursor theme used in the greeter" } },
{ "CursorSize", { QString(), "Cursor size used in the greeter" } } } },
{ "X11",
{ { "ServerPath", { "/usr/bin/X", "Path to X server binary" } },
{ "ServerArguments",
{ "-nolisten tcp", "Arguments passed to the X server invocation" } },
{ "SessionDir",
{ QStringList{ "/usr/local/share/xsessions", "/usr/share/xsessions" },
"Comma-separated list of directories containing available X sessions" } },
{ "SessionCommand",
{ SESSION_COMMAND,
"Path to a script to execute when starting the desktop session" } },
{ "SessionLogFile",
{ ".local/share/ddm/xorg-session.log", "Path to the user session log file" } },
{ "DisplayCommand",
{ QStringLiteral(DATA_INSTALL_DIR "/scripts/Xsetup"),
"Path to a script to execute when starting the display server" } },
{ "DisplayStopCommand",
{ QStringLiteral(DATA_INSTALL_DIR "/scripts/Xstop"),
"Path to a script to execute when stopping the display server" } } } },
{ "Wayland",
{ { "SessionDir",
{ QStringList{ "/usr/local/share/wayland-sessions", "/usr/share/wayland-sessions" },
"Comma-separated list of directories containing available Wayland sessions" } },
{ "SessionCommand",
{ WAYLAND_SESSION_COMMAND,
"Path to a script to execute when starting the desktop session" } },
{ "SessionLogFile",
{ ".local/share/ddm/wayland-session.log",
"Path to the user session log file" } } } },
{ "Single",
{ { "SessionCommand",
{ WAYLAND_SESSION_COMMAND,
"Path to a script to execute when starting the desktop session" } } } },
{ "Users",
{ { "DefaultPath",
{ "/usr/local/bin:/usr/bin:/bin", "Default $PATH for logged in users" } },
{ "RememberLastUser", { true, "Remember the last successfully logged in user" } },
{ "RememberLastSession",
{ true, "Remember the session of the last successfully logged in user" } } } } });

/** State configuration instance. */
inline Config stateConfig(
QStringLiteral(STATE_DIR "/state.conf"),
{ { "Last",
{ { "User", { QString(), "The last successfully logged in user" } },
{ "Session",
{ QString(), "The session of the last successfully logged in user" } } } } });
} // namespace DDM
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline global Config instances (mainConfig and stateConfig) are not thread-safe. These instances may be accessed from multiple threads (e.g., different Display instances, signal handlers, etc.), but the Config class has no synchronization mechanisms. Operations like load(), save(), get(), and set() can race with each other, potentially causing data corruption or crashes.

Since Config inherits from QObject, consider using QMutex to protect all member data access, or clearly document that these instances should only be accessed from the main thread.

内联全局 Config 实例(mainConfig 和 stateConfig)不是线程安全的。这些实例可能从多个线程访问(例如,不同的 Display 实例、信号处理程序等),但 Config 类没有同步机制。load()、save()、get() 和 set() 等操作可能相互竞争,可能导致数据损坏或崩溃。

由于 Config 继承自 QObject,建议使用 QMutex 保护所有成员数据访问,或明确记录这些实例只应从主线程访问。

Copilot uses AI. Check for mistakes.
env.insert(QStringLiteral("DISPLAY"), display);
env.insert(QStringLiteral("HOME"), QStringLiteral("/"));
env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get());
env.insert(QStringLiteral("PATH"), mainConfig.get<QString>("Users", "DefaultPath"));
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation detected. This line uses spaces for indentation while surrounding lines use consistent formatting. The indentation should match the surrounding code style.

检测到不一致的缩进。此行使用空格缩进,而周围的行使用一致的格式。缩进应与周围的代码风格匹配。

Suggested change
env.insert(QStringLiteral("PATH"), mainConfig.get<QString>("Users", "DefaultPath"));
env.insert(QStringLiteral("PATH"), mainConfig.get<QString>("Users", "DefaultPath"));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants