Skip to content

fix: delay deepin-screen-recorder notify#1428

Closed
qxp930712 wants to merge 2 commits intolinuxdeepin:masterfrom
qxp930712:master
Closed

fix: delay deepin-screen-recorder notify#1428
qxp930712 wants to merge 2 commits intolinuxdeepin:masterfrom
qxp930712:master

Conversation

@qxp930712
Copy link

@qxp930712 qxp930712 commented Feb 4, 2026

  1. Added QTimer include for asynchronous delay handling
  2. Modified Notify method to check for "deepin-screen-recorder" appIcon
  3. Implemented 3-second delay using QTimer::singleShot for screen recorder notifications
  4. Ensured parameters are captured by value for the delayed execution

This change prevents the screen recorder notification from appearing too early, which may interfere with the recording start or UI behavior.

Log: Delayed the display time of screen recorder notifications to improve user experience.

Influence:

  1. Test notification triggering from deepin-screen-recorder and verify the 3-second delay
  2. Test notifications from other applications to ensure they appear immediately without delay
  3. Verify that mixed notifications (screen recorder and others) are processed correctly
  4. Check for potential memory leaks or crashes when notifications are delayed
  5. Verify the return value handling for delayed notifications

pms: BUG-338965/BUG-336159

Summary by Sourcery

Bug Fixes:

  • Prevent deepin-screen-recorder notifications from appearing too early by deferring their processing via an asynchronous delay.

1. Added QTimer include for asynchronous delay handling
2. Modified Notify method to check for "deepin-screen-recorder" appIcon
3. Implemented 3-second delay using QTimer::singleShot for screen
recorder notifications
4. Ensured parameters are captured by value for the delayed execution

This change prevents the screen recorder notification from appearing too
early, which may interfere with the recording start or UI behavior.

Log: Delayed the display time of screen recorder notifications to
improve user experience.

Influence:
1. Test notification triggering from deepin-screen-recorder and verify
the 3-second delay
2. Test notifications from other applications to ensure they appear
immediately without delay
3. Verify that mixed notifications (screen recorder and others) are
processed correctly
4. Check for potential memory leaks or crashes when notifications are
delayed
5. Verify the return value handling for delayed notifications

pms: BUG-338965/BUG-336159
@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: qxp930712

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

sourcery-ai bot commented Feb 4, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Introduces a special-case asynchronous handling path for notifications coming from the deepin-screen-recorder icon by delaying their processing 3 seconds via QTimer::singleShot, while preserving the existing synchronous path for all other notifications and ensuring DBus error replies are still sent if notification creation fails.

Sequence diagram for delayed deepin_screen_recorder notification handling

sequenceDiagram
    actor Client
    participant DbusAdaptor
    participant QTimer
    participant NotificationManager
    participant QDBusConnection

    Client->>DbusAdaptor: Notify(appName, replacesId, appIcon="deepin-screen-recorder", summary, body, actions, hints, expireTimeout)
    activate DbusAdaptor
    DbusAdaptor-->>Client: return 1
    deactivate DbusAdaptor

    Note over DbusAdaptor,QTimer: Asynchronous path via QTimer::singleShot(3000)

    DbusAdaptor->>QTimer: singleShot(3000, this, lambda)

    rect rgba(200, 200, 200, 0.1)
        Note over QTimer: After 3 seconds
        QTimer-->>DbusAdaptor: invoke lambda
        activate DbusAdaptor
        DbusAdaptor->>NotificationManager: Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout)
        NotificationManager-->>DbusAdaptor: id
        alt id == 0
            DbusAdaptor->>QDBusConnection: send(QDBusError InternalError "Notify failed.")
        end
        deactivate DbusAdaptor
    end
Loading

Flow diagram for conditional notification delay in Notify

flowchart TD
    A[Notify called] --> B{appIcon == deepin-screen-recorder}
    B -- Yes --> C[Schedule QTimer::singleShot<br/>delay 3000 ms<br/>capture parameters by value]
    C --> D[Return 1 to caller immediately]

    B -- No --> E[Call manager Notify synchronously]
    E --> F{id == 0?}
    F -- Yes --> G[Create QDBusError InternalError<br/>Notify failed<br/>Send error via QDBusConnection sessionBus]
    F -- No --> H[Return id to caller]

    subgraph Delayed_execution_after_3s
        I[QTimer fires<br/>lambda invoked] --> J[Call manager Notify with captured parameters]
        J --> K{id == 0?}
        K -- Yes --> L[Create QDBusError InternalError<br/>Notify failed<br/>Send error via QDBusConnection sessionBus]
        K -- No --> M[End async path]
    end
Loading

File-Level Changes

Change Details Files
Add a special delayed-notification path for deepin-screen-recorder using QTimer::singleShot while keeping other notifications immediate.
  • Include QTimer to support scheduling asynchronous delayed work in the notification DBus adaptor implementation.
  • In Notify, short-circuit when appIcon equals "deepin-screen-recorder" to schedule manager()->Notify via QTimer::singleShot after 3000 ms using a capturing lambda.
  • Within the delayed lambda, call manager()->Notify with the captured parameters and, if it returns 0, construct and send an InternalError QDBus error reply on the session bus.
  • Return a fixed non-zero value (1) immediately from Notify in the delayed path so the DBus method call completes without blocking.
panels/notification/server/dbusadaptor.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 1 issue, and left some high level feedback:

  • Delaying the notification while still returning a hardcoded 1 from Notify changes the D-Bus method contract (callers won’t get the real notification ID and will see a success path even if the delayed Notify fails); consider either keeping Notify synchronous for this case or wiring a proper asynchronous D-Bus reply using setDelayedReply and sending the eventual ID/error back to the original caller.
  • The delayed error handling uses QDBusMessage::createError and QDBusConnection::sessionBus().send(reply) without relating the reply to the original D-Bus call; if you intend to reply asynchronously, you should construct the reply from the original message to ensure it matches the correct call and service.
  • The lambda passed to QTimer::singleShot captures this and runs 3 seconds later; if the adaptor object can be destroyed in that window, this can lead to a dangling pointer—consider using a QPointer or ensuring the object’s lifetime covers the delay.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Delaying the notification while still returning a hardcoded `1` from `Notify` changes the D-Bus method contract (callers won’t get the real notification ID and will see a success path even if the delayed `Notify` fails); consider either keeping `Notify` synchronous for this case or wiring a proper asynchronous D-Bus reply using `setDelayedReply` and sending the eventual ID/error back to the original caller.
- The delayed error handling uses `QDBusMessage::createError` and `QDBusConnection::sessionBus().send(reply)` without relating the reply to the original D-Bus call; if you intend to reply asynchronously, you should construct the reply from the original message to ensure it matches the correct call and service.
- The lambda passed to `QTimer::singleShot` captures `this` and runs 3 seconds later; if the adaptor object can be destroyed in that window, this can lead to a dangling pointer—consider using a `QPointer` or ensuring the object’s lifetime covers the delay.

## Individual Comments

### Comment 1
<location> `panels/notification/server/dbusadaptor.cpp:35-44` </location>
<code_context>
                          int expireTimeout)
 {
+    // Delay processing for 3 seconds when appIcon == "deepin-screen-recorder"
+    if (appIcon == "deepin-screen-recorder") {
+        // Use QTimer to delay processing asynchronously without blocking other notifications
+        QTimer::singleShot(3000, this, [this, appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout]() {
+                uint id = manager()->Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout);
+                if (id == 0) {
+                QDBusError error(QDBusError::InternalError, "Notify failed.");
+                QDBusMessage reply = QDBusMessage::createError(error);
+                QDBusConnection::sessionBus().send(reply);
+            }
+        });
+        return 1;
+    }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Delaying Notify via QTimer breaks D-Bus method-call semantics and may confuse clients

`Notify` now always returns `1` immediately, while the real notification (which may fail or get a different ID) is created up to 3 seconds later. The async error you send is not associated with the original D-Bus method call, so clients will see a successful reply with ID `1` even when `manager()->Notify(...)` fails or chooses another ID, breaking D-Bus method semantics and potentially causing timeouts or inconsistent client behavior. If a delay is needed, keep the D-Bus call synchronous with the actual `Notify` result (e.g., perform the delay inside `manager()->Notify` or a deeper layer, or use an internal queue/timer while still returning the final ID synchronously).
</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 on lines +35 to +44
if (appIcon == "deepin-screen-recorder") {
// Use QTimer to delay processing asynchronously without blocking other notifications
QTimer::singleShot(3000, this, [this, appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout]() {
uint id = manager()->Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout);
if (id == 0) {
QDBusError error(QDBusError::InternalError, "Notify failed.");
QDBusMessage reply = QDBusMessage::createError(error);
QDBusConnection::sessionBus().send(reply);
}
});
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Delaying Notify via QTimer breaks D-Bus method-call semantics and may confuse clients

Notify now always returns 1 immediately, while the real notification (which may fail or get a different ID) is created up to 3 seconds later. The async error you send is not associated with the original D-Bus method call, so clients will see a successful reply with ID 1 even when manager()->Notify(...) fails or chooses another ID, breaking D-Bus method semantics and potentially causing timeouts or inconsistent client behavior. If a delay is needed, keep the D-Bus call synchronous with the actual Notify result (e.g., perform the delay inside manager()->Notify or a deeper layer, or use an internal queue/timer while still returning the final ID synchronously).

QDBusConnection::sessionBus().send(reply);
}
});
return 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

这种是bubbleId,对调用方是有用的,
不应该这样去延迟,

Updated the logic in the AudioApplet to correctly identify and
display the mute icon when the volume level is less than or
equal to 1.

Previously, the code failed to explicitly handle the state where
volume was 0 or very low, potentially resulting in incorrect
icon rendering or undefined behavior. The change adds an else
block to return "osd_volume_mute" in these cases, ensuring
accurate UI feedback for users.

Log: Fixed the audio OSD mute icon display for silent state

Influence:
1. Test volume at 0% to ensure the mute icon is displayed
2. Test volume between 1% and 66% to ensure the low/mid volume icon
is displayed
3. Test volume above 66% to ensure the high volume icon is displayed
4. Verify icon switching behavior when sliding the volume slider

pms: BUG-349767
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码包含两个部分的修改,涉及音频图标显示逻辑和屏幕录制通知的延迟处理。以下是对这两处代码的详细审查和改进意见:

1. 音频图标显示逻辑 (audioapplet.cpp)

代码变更:

QString AudioApplet::fetchIconName() const
{
    // ... 前面的代码 ...
    else if (volume > 1)
        level = "more";
+   else
+       return "osd_volume_mute";
    
    return QString("osd_volume_%1").arg(level);
}

审查意见:

  1. 逻辑与语法

    • 代码逻辑是正确的。如果音量 volume 小于等于 1(假设 0 是静音),则返回静音图标。
    • 语法上没有问题。
  2. 代码质量

    • 建议: 代码的可读性尚可,但为了更清晰,建议显式检查静音条件。
    • 改进建议: 如果 volume 是浮点数,直接判断 <= 1 可能不够精确。通常静音状态可能由 m_mute 成员变量控制,或者 volume 为 0。建议确认静音的判断标准。如果 volume 是整数,当前写法没问题。
  3. 代码性能

    • 性能影响极小,仅涉及简单的条件判断和字符串返回。
  4. 代码安全

    • 无明显安全问题。

改进后的代码建议(假设静音有明确标志):

QString AudioApplet::fetchIconName() const
{
    if (m_mute || volume <= 0) {
        return "osd_volume_mute";
    }
    
    QString level;
    if (volume > 100)
        level = "100";
    else if (volume > 66)
        level = "66";
    // ... 其他判断 ...
    else if (volume > 1)
        level = "more";
    else 
        // 理论上上面已经覆盖了 <= 0 的情况,这里作为兜底
        return "osd_volume_mute";
        
    return QString("osd_volume_%1").arg(level);
}

(注:原代码中 else if (volume > 1) 的逻辑意味着 volume <= 1 时走 else 分支,这包含了 volume == 1 的情况。通常音量为 1 时可能不是静音,而是极小音量。请确认业务逻辑是否希望将音量 1 视为静音。如果希望音量 1 显示为最小音量图标,逻辑应调整为 else if (volume > 0)。)


2. 屏幕录制通知延迟处理 (dbusadaptor.cpp)

代码变更:

uint DbusAdaptor::Notify(...)
{
+   // Delay processing for 3 seconds when appIcon == "deepin-screen-recorder"
+   if (appIcon == "deepin-screen-recorder") {
+       // Use QTimer to delay processing asynchronously without blocking other notifications
+       QTimer::singleShot(3000, this, [this, appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout]() {
+               uint id = manager()->Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout);
+               if (id == 0) {
+               QDBusError error(QDBusError::InternalError, "Notify failed.");
+               QDBusMessage reply = QDBusMessage::createError(error);
+               QDBusConnection::sessionBus().send(reply);
+           }
+       });
+       return 1;
+   }
+
    uint id = manager()->Notify(...);
    // ...
}

审查意见:

  1. 逻辑与语法

    • 语法正确,使用了 C++11 的 lambda 表达式和 QTimer::singleShot 进行异步延迟。
    • 逻辑上,当应用图标为 deepin-screen-recorder 时,延迟 3 秒处理通知。
    • 严重逻辑问题: 函数立即返回了 1。在 DBus 通信中,返回值通常代表通知 ID。返回 1 意味着告诉调用者(屏幕录制器)通知已成功创建且 ID 为 1。但实际上,通知要在 3 秒后才真正创建。这会导致以下问题:
      • 如果调用者试图用返回的 ID (1) 去更新或关闭这个通知,它操作的可能是一个错误的通知(因为真正的通知 ID 还未生成)。
      • 如果延迟后的 manager()->Notify 失败了(返回 0),调用者已经收到了成功的响应(返回 1),造成了状态不一致。
  2. 代码质量

    • Lambda 捕获: 捕获了所有参数,虽然方便但略显冗余。Qt 的 QTimer 在 lambda 执行时会自动处理对象生命周期,但需确保 DbusAdaptor 对象在 3 秒内不被销毁,否则会导致崩溃。
    • 错误处理: 在 lambda 内部检查 id == 0 并尝试发送 DBus 错误回复。这是无效的,因为 DBus 方法调用已经在前面的 return 1 时结束了,此时发送 error reply 没有任何意义,调用者收不到。
  3. 代码性能

    • 使用 QTimer 是非阻塞的,不会影响主线程性能,这点做得很好。
  4. 代码安全

    • 内存/生命周期风险: 如前所述,如果 DbusAdaptor 实例在 3 秒内被析构,lambda 中的 this 指针将变成悬空指针,调用 manager()->Notify 会导致崩溃。建议使用 QPointer 或在 lambda 中检查对象有效性。

改进意见:

  1. 关于返回值: 如果业务逻辑确实需要"假装"立即返回一个 ID 给调用者,且不关心后续的更新/关闭操作,那么当前写法勉强可行。但这不符合标准的 DBus 通知规范。
  2. 关于异步回复: 如果需要真正的异步处理(即延迟 3 秒后才给调用者返回结果),这需要 DBus 支持异步消息(通常 Qt DBus 不直接支持在方法调用中挂起并在回调中返回,除非使用特殊的异步接口设计)。简单的 return 1 只是"掩耳盗铃"。
  3. 关于错误回复: 删除 lambda 中发送 QDBusError 的代码,因为它是无效的。

改进后的代码建议:

如果仅仅是为了延迟显示,而不需要调用者精确控制通知 ID,可以保留当前逻辑,但需增加安全性检查:

uint DbusAdaptor::Notify(const QString &appName, uint replacesId, const QString &appIcon, const QString &summary,
                         const QString &body, const QStringList &actions, const QVariantMap &hints,
                         int expireTimeout)
{
    // Delay processing for 3 seconds when appIcon == "deepin-screen-recorder"
    if (appIcon == "deepin-screen-recorder") {
        // 使用 QPointer 防止 DbusAdaptor 提前销毁导致崩溃
        QPointer<DbusAdaptor> self = this;
        
        QTimer::singleShot(3000, this, [self, appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout]() {
            // 检查对象是否依然存在
            if (!self) return;
            
            // 注意:这里无法将结果返回给原始的 DBus 调用者
            // 如果需要处理失败逻辑,只能记录日志或进行内部状态更新
            uint id = self->manager()->Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout);
            if (id == 0) {
                qCWarning(notifyLog) << "Failed to show notification for deepin-screen-recorder after delay";
            }
        });
        
        // 返回一个占位 ID,告知调用者请求已接收(虽然实际未处理)
        // 注意:这会导致调用者无法通过此 ID 更新或关闭通知
        return 1; 
    }

    uint id = manager()->Notify(appName, replacesId, appIcon, summary, body, actions, hints, expireTimeout);
    if (id == 0) {
        QDBusError error(QDBusError::InternalError, "Notify failed.");
        // ... 原有的错误处理 ...
    }
    return id;
}

总结:

  1. audioapplet.cpp 的修改主要是为了处理静音图标,逻辑基本正确,但需确认 volume <= 1 是否完全等同于静音状态。
  2. dbusadaptor.cpp 的修改实现了异步延迟,但存在严重的逻辑缺陷:它立即返回了一个伪造的通知 ID,导致调用者无法正确管理该通知。此外,lambda 中的错误回复代码是无效的,且存在潜在的内存安全问题(悬空指针)。
    • 如果屏幕录制器不需要通过 ID 控制通知,当前方案(加上 QPointer 保护)可以使用。
    • 如果需要精确控制,应重新设计交互逻辑(例如,让屏幕录制器自己处理延迟,或者服务端内部维护一个映射关系)。

@qxp930712 qxp930712 closed this Feb 4, 2026
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.

4 participants