Skip to content

fix(power): fix idle ignored after wakeup, add wakeup lock, and suppo…#74

Open
mhduiy wants to merge 1 commit into
linuxdeepin:masterfrom
mhduiy:idle
Open

fix(power): fix idle ignored after wakeup, add wakeup lock, and suppo…#74
mhduiy wants to merge 1 commit into
linuxdeepin:masterfrom
mhduiy:idle

Conversation

@mhduiy
Copy link
Copy Markdown
Contributor

@mhduiy mhduiy commented May 30, 2026

…rt output hotplug

  1. Reset prepareSuspend to PS_Normal in HandleIdleOff so idle-on events are not permanently ignored after suspend/resume cycle
  2. Call doLock in handleBeforeSleep unconditionally (not just Wayland) and in the wakeup delayed callback when sleepLock is set, ensuring lock screen shows after idle-timeout suspend
  3. Keep wl_registry alive in WaylandScreenController to receive output hotplug events (zwlr_output_power_v1 and treeland color control for new monitors)
  4. Use pointer-based lookup in signal handlers instead of captured vector index to avoid stale references after output removal
  5. Switch brightness animation key from int index to registryName to survive output reordering on unplug

Log: Fix idle state reset after wakeup, add wakeup lock logic, and support DPMS/brightness for hotplugged Wayland outputs

fix(power): 修复唤醒后 idle 被忽略、增加唤醒锁屏、支持屏幕热插拔

  1. HandleIdleOff 中重置 prepareSuspend 为 PS_Normal,修复待机唤醒后 idle 事件被永久忽略的问题
  2. handleBeforeSleep 中不再限制 Wayland 才锁屏,handleWakeup 延迟回调中 sleepLock 开启时调用 doLock,确保 idle 超时待机唤醒后显示锁屏界面
  3. WaylandScreenController 保持 wl_registry 活跃以接收热插拔事件,新接入屏幕自动获取 DPMS 和亮度控制
  4. 信号回调改用裸指针查找当前 index,避免拔屏后 index 偏移导致访问错误 output
  5. 亮度动画 key 改为 registryName,拔屏后 key 不受 index 偏移影响

Log: 修复唤醒后 idle 状态重置、增加唤醒锁屏、支持 Wayland 屏幕热插拔 DPMS/亮度管控

Summary by Sourcery

Fix power management idle handling after wakeup and improve Wayland output handling for hotplug and brightness control.

Bug Fixes:

  • Reset prepare-suspend state on idle-off so idle events are no longer ignored after a suspend/resume cycle.
  • Ensure the session is locked on sleep and after wakeup delay when sleep locking is enabled, regardless of compositor.
  • Avoid stale output references in Wayland signal handlers when outputs are unplugged or reordered.

Enhancements:

  • Keep the Wayland registry alive in the screen controller to receive output hotplug events and manage DPMS/brightness for newly connected monitors.
  • Use registry names instead of indices as keys for brightness animations to keep them consistent across output hotplug and reordering.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: mhduiy

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

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 30, 2026

Reviewer's Guide

Updates power/session behavior so idle is correctly reset on wakeup, lock-on-sleep/wakeup is applied consistently, and Wayland output DPMS/brightness continues to work across hotplug and output reordering by keeping wl_registry alive and using stable lookup keys instead of indices.

Sequence diagram for updated sleep/wakeup lock and idle reset behavior

sequenceDiagram
    participant System as SystemPowerEvents
    participant PM as PowerManager
    participant PSP as PowerSavePlan
    participant Timer as QTimer_singleShot

    System->>PM: handleBeforeSleep()
    PM->>PM: setBlackScreenActive(true)
    alt sleepLock enabled
        PM->>PM: doLock(true)
    end

    System->>PM: handleWakeup()
    PM->>PM: setDPMSModeOn()
    PM->>PSP: HandleIdleOff()
    PSP->>PM: SetPrepareSuspend(PS_Normal)

    PM->>Timer: singleShot(m_delayWakeupInterval,...)
    Timer-->>PM: wakeup delay callback
    PM->>PM: m_delayInActive = false
    alt sleepLock enabled
        PM->>PM: doLock(true)
    end
    PM->>PM: setBlackScreenActive(false)
    alt scheduledShutdownState
        PM->>PM: scheduledShutdown(SchedInit)
    end
Loading

File-Level Changes

Change Details Files
Keep Wayland registry and output state wired for hotplug, and make output signal handlers robust to output removal/reordering.
  • Store wl_registry in WaylandScreenController, keep it alive after discoverOutputs() instead of destroying immediately, and destroy it in the destructor.
  • In scOutputGlobal, change modeChanged and brightnessChanged connections to capture raw OutputPower/OutputColorControl pointers and look up the matching output at signal time instead of using a captured index.
  • On output removal, use the output’s registryName when stopping and deleting any associated brightness animation.
src/plugin-qt/power/session/screen/screencontroller_wl.cpp
src/plugin-qt/power/session/screen/screencontroller_wl.h
Use registryName as the key for brightness animations so they survive output index changes on hotplug.
  • Change m_brightnessAnims from QHash<int, QVariantAnimation*> to QHash<uint32_t, QVariantAnimation*> keyed by registryName.
  • In setBrightness, take/replace animations using registryName instead of index and use registryName-capturing lambdas that locate the correct output at run time before applying brightness updates.
src/plugin-qt/power/session/screen/screencontroller_wl.cpp
src/plugin-qt/power/session/screen/screencontroller_wl.h
Adjust sleep/wakeup handling so sessions are locked consistently and idle state is reset properly after wakeup.
  • In handleBeforeSleep, drop the Wayland-only guard and always call doLock(true) when sleepLock is enabled.
  • In handleWakeup, during the delayed wakeup callback, conditionally call doLock(true) when sleepLock is enabled before removing the black screen, and slightly refactor logging and guard clauses.
  • In HandleIdleOff, reset the power manager’s prepare suspend state to PS_Normal before turning screens back on, so idle events are not ignored after suspend/resume.
src/plugin-qt/power/session/powermanager.cpp
src/plugin-qt/power/session/powersaveplan.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
Copy Markdown

@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 1 issue, and left some high level feedback:

  • In WaylandScreenController::discoverOutputs, m_registry is overwritten each time without destroying any previously created registry, so if discoverOutputs() can be called more than once per instance you should clean up or reset the existing m_registry before assigning a new one to avoid leaks or double-destruction issues in the destructor.
  • With the new unconditional doLock(true) in both handleBeforeSleep and the delayed callback in handleWakeup when m_sleepLock is set, it would be good to confirm the lock semantics (and user experience) for non-Wayland sessions and ensure the session is not redundantly re-locked on wake if it was already locked before suspend.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `WaylandScreenController::discoverOutputs`, `m_registry` is overwritten each time without destroying any previously created registry, so if `discoverOutputs()` can be called more than once per instance you should clean up or reset the existing `m_registry` before assigning a new one to avoid leaks or double-destruction issues in the destructor.
- With the new unconditional `doLock(true)` in both `handleBeforeSleep` and the delayed callback in `handleWakeup` when `m_sleepLock` is set, it would be good to confirm the lock semantics (and user experience) for non-Wayland sessions and ensure the session is not redundantly re-locked on wake if it was already locked before suspend.

## Individual Comments

### Comment 1
<location path="src/plugin-qt/power/session/screen/screencontroller_wl.cpp" line_range="189" />
<code_context>
-                if (idx < int(sc->m_outputs.size())) {
-                    sc->m_outputs[idx].currentMode = m;
-                    Q_EMIT sc->modeChanged(idx, m == 0 ? ScreenController::Off : ScreenController::On);
+            auto *pwr = out.power.get();
+            QObject::connect(pwr, &OutputPower::modeChanged,
+                sc, [sc, pwr](uint32_t m) {
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the repeated output-lookup loops into small helper methods on WaylandScreenController and calling those from the lambdas to keep the new dynamic lookup behavior while simplifying the signal handlers.

You can reduce the added complexity by centralizing the lookup logic instead of hand‑rolling three similar loops in lambdas.

Introduce small lookup helpers on `WaylandScreenController`:

```cpp
int WaylandScreenController::indexForPower(const OutputPower *pwr) const
{
    for (int i = 0; i < int(m_outputs.size()); ++i) {
        if (m_outputs[i].power.get() == pwr) {
            return i;
        }
    }
    return -1;
}

int WaylandScreenController::indexForColorControl(const OutputColorControl *cc) const
{
    for (int i = 0; i < int(m_outputs.size()); ++i) {
        if (m_outputs[i].colorControl.get() == cc) {
            return i;
        }
    }
    return -1;
}

int WaylandScreenController::indexForRegistry(uint32_t regName) const
{
    for (int i = 0; i < int(m_outputs.size()); ++i) {
        if (m_outputs[i].registryName == regName) {
            return i;
        }
    }
    return -1;
}
```

Then simplify the lambdas:

```cpp
auto *pwr = out.power.get();
QObject::connect(pwr, &OutputPower::modeChanged,
    sc, [sc, pwr](uint32_t m) {
        const int i = sc->indexForPower(pwr);
        if (i < 0)
            return;
        sc->m_outputs[i].currentMode = m;
        Q_EMIT sc->modeChanged(i, m == 0 ? ScreenController::Off : ScreenController::On);
    });
```

```cpp
auto *cc = out.colorControl.get();
QObject::connect(cc, &OutputColorControl::brightnessChanged,
    sc, [sc, cc](double v) {
        const int i = sc->indexForColorControl(cc);
        if (i < 0)
            return;
        Q_EMIT sc->brightnessChanged(i, v);
    });
```

```cpp
auto regName = out.registryName;
connect(anim, &QVariantAnimation::valueChanged, this,
    [this, regName](const QVariant &v) {
        const int i = indexForRegistry(regName);
        if (i < 0)
            return;
        auto &o = m_outputs[i];
        if (!o.colorControl)
            return;
        o.colorControl->set_brightness(wl_fixed_from_double(v.toDouble()));
        o.colorControl->commit();
        wl_display_flush(m_display);
    });
```

This keeps the new behavior (dynamic lookups resilient to vector reordering and registry‑based animations) but removes duplicated search logic, making the signal handlers easier to read and future changes to lookup strategy localized to a few helpers.
</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.

Comment thread src/plugin-qt/power/session/screen/screencontroller_wl.cpp
…rt output hotplug

1. Reset prepareSuspend to PS_Normal in HandleIdleOff so idle-on events are not
   permanently ignored after suspend/resume cycle
2. Call doLock in handleBeforeSleep unconditionally (not just Wayland) and in
   the wakeup delayed callback when sleepLock is set, ensuring lock screen shows
   after idle-timeout suspend
3. Keep wl_registry alive in WaylandScreenController to receive output hotplug
   events (zwlr_output_power_v1 and treeland color control for new monitors)
4. Use pointer-based lookup in signal handlers instead of captured vector index
   to avoid stale references after output removal
5. Switch brightness animation key from int index to registryName to survive
   output reordering on unplug

Log: Fix idle state reset after wakeup, add wakeup lock logic, and support DPMS/brightness for hotplugged Wayland outputs

fix(power): 修复唤醒后 idle 被忽略、增加唤醒锁屏、支持屏幕热插拔

1. HandleIdleOff 中重置 prepareSuspend 为 PS_Normal,修复待机唤醒后 idle 事件被永久忽略的问题
2. handleBeforeSleep 中不再限制 Wayland 才锁屏,handleWakeup 延迟回调中 sleepLock
   开启时调用 doLock,确保 idle 超时待机唤醒后显示锁屏界面
3. WaylandScreenController 保持 wl_registry 活跃以接收热插拔事件,新接入屏幕自动获取
   DPMS 和亮度控制
4. 信号回调改用裸指针查找当前 index,避免拔屏后 index 偏移导致访问错误 output
5. 亮度动画 key 改为 registryName,拔屏后 key 不受 index 偏移影响

Log: 修复唤醒后 idle 状态重置、增加唤醒锁屏、支持 Wayland 屏幕热插拔 DPMS/亮度管控
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

这份 Git Diff 主要对电源管理模块和 Wayland 屏幕控制模块进行了重构和 Bug 修复。整体来看,修改方向非常正确,特别是将基于数组索引的信号/槽连接重构为基于指针或唯一标识符的查找,这极大地增强了代码的健壮性,避免了由于数组增删导致的悬空指针或越界访问。

不过,在代码审查中,我仍发现了一些关于语法逻辑、代码质量、代码性能和代码安全方面的改进空间。以下是详细的审查意见:

1. 语法与逻辑

  • m_delayInActive 状态标志可能引发逻辑混乱:
    handleWakeup 中,m_delayInActive 被设为 true,然后在 QTimer::singleShot 的 lambda 中被设为 false。但在 lambda 的逻辑中,先执行了 doLock(true),然后才设置 m_delayInActive = false
    问题:如果 doLock 内部或其它外部逻辑依赖于 m_delayInActive 的状态来判断当前是否处于“唤醒延迟”阶段,那么在 doLock 执行时,该标志仍为 true,这可能是预期行为,但也容易引发竞态或逻辑误判。
    建议:确认 doLock 是否需要感知 m_delayInActive。如果不需要,建议在执行具体动作前先重置标志:m_delayInActive = false; if (m_sleepLock) { doLock(true); } ...
  • HandleIdleOff 中的状态重置顺序:
    powersaveplan.cpp 中,新增了 m_powerManager->SetPrepareSuspend(static_cast<int>(PS_Normal));
    问题:在 screenController()->setAllModes(ScreenController::On) 之前重置挂起状态,如果底层逻辑依赖屏幕点亮来触发某些状态机流转,可能会导致时序问题。
    建议:仔细确认状态机的依赖关系。通常建议先恢复物理状态(亮屏),再重置逻辑状态(恢复正常),即把 SetPrepareSuspend 移到 setAllModes 之后。

2. 代码质量

  • Lambda 中使用原始指针捕获的潜在风险:
    screencontroller_wl.cpp 中,大量使用了 [sc, pwr][sc, cc] 的方式捕获原始指针。
    问题:如果 WaylandScreenController 对象被销毁,但 Wayland 服务器异步推送了信号,这些原始指针将变成悬空指针,导致崩溃。虽然你在 scOutputRemove 中添加了 QObject::disconnect,但这依赖于移除事件必定发生的假设。
    建议:使用 QPointer 进行捕获,确保在对象销毁时自动置空,并在 lambda 中检查有效性:
    auto *pwr = out.power.get();
    QPointer<WaylandScreenController> scPtr = sc;
    QObject::connect(pwr, &OutputPower::modeChanged, sc, [scPtr, pwr](uint32_t m) {
        if (!scPtr) return;
        // ... 查找逻辑
    });
  • discoverOutputs 中的 C 风格资源管理:
    discoverOutputs 中,每次调用都会销毁旧的 m_registry 并重新获取。
    问题:这是典型的 C 风格手动内存管理,如果在 wl_registry_destroywl_display_get_registry 之间发生异常或提前返回,容易导致资源泄漏。
    建议:考虑使用 RAII 机制(如自定义 deleter 的 std::unique_ptr)来管理 wl_registry 的生命周期,或者在 Qt 环境下确保该函数逻辑极简且不会抛出异常。
  • 代码格式一致性:
    handleWakeup 中对 if 语句增加了大括号(这是非常好的防御性编程),但希望整个文件的 if/else 分支都能保持这一风格,遵循代码规范。

3. 代码性能

  • Lambda 中的线性查找 (O(N)):
    将基于索引的 O(1) 访问改为基于指针或 registryNameO(N) 遍历查找。
    问题:对于屏幕数量(通常 1~3 个),O(N) 的遍历完全不是性能瓶颈,但每次亮度变化(brightnessChanged)或模式变化(modeChanged)都触发遍历,显得不够优雅。
    建议:当前实现在业务量小的情况下是可以接受的。如果追求极致,可以考虑在 Output 结构体中缓存其在 m_outputs 中的 index,或者使用 QHash<uint32_t, Output*> 来建立快速索引。但鉴于屏幕数量极少,维持现状也是合理的,只需在注释中说明由于屏幕数量极少,线性查找的开销可忽略即可。

4. 代码安全

  • m_registry 生命周期与多线程/重入风险:
    discoverOutputs 修改了类成员 m_registry
    问题:如果 discoverOutputs 被并发调用,或者在 wl_display_roundtrip 阻塞期间有其他逻辑尝试访问 m_registry,将导致严重的线程安全问题或段错误。
    建议:确保 discoverOutputs 只在主事件循环中被调用,或者增加互斥锁保护。
  • Wayland 对象的析构顺序:
    ~WaylandScreenController 中,先清理了 m_outputs,然后 m_treeLandMgr.reset(),最后 wl_registry_destroy(m_registry)
    问题m_outputs 中的 powercolorControl 可能是由 m_managerm_treeLandMgr 创建的。在 Wayland 协议中,必须确保子对象在父对象或 Factory 对象销毁前被销毁。
    建议:当前的析构顺序(先 outputs 后 manager)是正确的,但 wl_registry 作为 Wayland 注册表,通常应该在所有通过它创建的资源(即 m_manager 等)销毁后再销毁。目前的顺序 m_treeLandMgr.reset() -> m_manager.reset() -> wl_registry_destroy安全的,请保持这个顺序,不要随意调整。
  • Lambda 捕获 this 与对象生命周期:
    setBrightness 中,lambda 捕获了 thisregName,并绑定了 anim
    问题:如果 WaylandScreenController 被销毁,但 QVariantAnimation 仍在运行(虽然你在析构中可能处理了,但未在 diff 中体现),this 指针将失效。
    建议:同样建议使用 QPointer<WaylandScreenController> 捕获,或者在类的析构函数中显式调用 m_brightnessAnims 中所有动画的 stop() 并断开信号连接。

总结与修改建议代码片段

针对最核心的安全与生命周期问题,建议对 screencontroller_wl.cpp 中的信号连接做如下改进(以 scOutputGlobal 为例):

// 改进前:
auto *pwr = out.power.get();
QObject::connect(pwr, &OutputPower::modeChanged,
    sc, [sc, pwr](uint32_t m) {
    auto &outs = sc->m_outputs;
    for (int i = 0; i < int(outs.size()); ++i) {
        if (outs[i].power.get() == pwr) {
            outs[i].currentMode = m;
            Q_EMIT sc->modeChanged(i, m == 0 ? ScreenController::Off : ScreenController::On);
            return;
        }
    }
});

// 改进后 (使用 QPointer 防止悬空指针):
auto *pwr = out.power.get();
QPointer<WaylandScreenController> safeSc = sc; // 确保控制器生命周期安全
QObject::connect(pwr, &OutputPower::modeChanged,
    sc, [safeSc, pwr](uint32_t m) {
    if (!safeSc) return; // 如果控制器已销毁,直接返回
    auto &outs = safeSc->m_outputs;
    for (int i = 0; i < int(outs.size()); ++i) {
        if (outs[i].power.get() == pwr) {
            outs[i].currentMode = m;
            Q_EMIT safeSc->modeChanged(i, m == 0 ? ScreenController::Off : ScreenController::On);
            return;
        }
    }
});

总体而言,这次重构解决了之前基于数组索引带来的核心隐患,方向明确。只需在指针生命周期管理上再严谨一些,代码的质量和安全性将会更高。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants