Skip to content

fix(dock): keep taskbar entry when an app replaces its window - #1748

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:bug/367743
Sep 21, 2026
Merged

deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
xionglinlin:bug/367743

Conversation

@xionglinlin

@xionglinlin xionglinlin commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

An Electron app such as SiYuan shows a splash window and then a main window. When the splash closes, the active-app model reports the row as removed, but because the notification is queued the model already holds the main window in that same row. The handler only looked for another cached entry of the app in m_data, found none, and deleted the dock row even though the app was still running. The main window never produced a new insert notification afterwards, so the taskbar entry disappeared permanently.

Fix:

  1. dockglobalelementmodel.cpp: decide by querying whether the app is still present in the active-app model, instead of trusting the cached m_data entries, and re-point the kept row at its current active row after the remaining rows are renumbered.
  2. taskmanager.cpp: skip a window whose desktop file could not be resolved instead of dereferencing a null pointer.
  3. appitem.cpp: defer the AppItem delete decision by a grace period and re-check inside the callback, so a window arriving in the handover gap can re-attach.

Log: keep taskbar entry when an app replaces its window

fix(dock): 应用替换窗口时保持任务栏入口

Electron 应用(例如思源笔记 SiYuan)启动时会先显示启动窗口(splash
window),随后再创建主窗口。当启动窗口关闭时,active-app model 会将该行报告为已移除。但由于通知 是异步排队处理的,此时 model 实际上已经在同一行中持有了新的主窗口。
处理逻辑之前只会在缓存的 m_data 中查找该应用是否还有其他缓存项,由于
找不到,就直接删除 dock 中对应的任务栏行,尽管该应用实际上仍在运行。
而新的主窗口之后也不会再产生新的 insert 通知,导致任务栏入口永久消失。

修复:

  1. dockglobalelementmodel.cpp:不再依赖缓存的 m_data 条目判断应用是否 仍然存在,而是通过查询 active-app model 判断应用是否仍处于活动状态;
    同时在剩余行重新编号后,将保留的任务栏行重新指向该应用当前对应的
    active row。
  2. taskmanager.cpp:当窗口无法解析对应的 desktop file 时直接跳过该 窗口,避免对空指针进行解引用。
  3. appitem.cpp:延迟一段 grace period 再决定是否删除 AppItem,并在 延迟回调中重新检查状态,使得窗口在新旧窗口交接间隙到达时,仍然可以
    重新关联到已有的 AppItem。

Log: 应用窗口交接时不再丢失任务栏图标
PMS: BUG-367743
Change-Id: I8e2d524b3ee590353601cfa947cd48b6781e73c0

Summary by Sourcery

Keep taskbar entries associated with running applications across window replacement and safely handle unidentified windows.

Bug Fixes:

  • Preserve the dock taskbar entry when an application replaces one window with another, such as during an Electron splash-to-main-window transition.
  • Skip windows whose desktop file cannot be identified instead of dereferencing an invalid object.
  • Allow windows arriving during application window handover to reattach to the existing app item before it is removed.

Enhancements:

  • Keep dock entries synchronized with the active application model when rows change during queued notifications.

@sourcery-ai

sourcery-ai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR prevents taskbar entries from disappearing during Electron-style window replacement by reconciling removals against the live active-app model, updating mappings after row shifts, and adding defensive handling for unresolved windows and transient AppItem gaps.

Sequence diagram for preserving the taskbar entry during window replacement

sequenceDiagram
    participant ActiveAppModel
    participant DockGlobalElementModel
    participant AppItem
    participant QTimer

    ActiveAppModel-->>DockGlobalElementModel: rowsRemoved
    DockGlobalElementModel->>DockGlobalElementModel: findActiveRowByDesktopId(id)
    alt app still has an active window
        DockGlobalElementModel->>DockGlobalElementModel: renumber remaining rows
        DockGlobalElementModel->>DockGlobalElementModel: findActiveRowByDesktopId(id)
        DockGlobalElementModel-->>DockGlobalElementModel: emit dataChanged
    else app is no longer active
        DockGlobalElementModel-->>DockGlobalElementModel: remove dock row
    end

    AppItem->>QTimer: singleShot(500)
    QTimer-->>AppItem: callback
    alt hasWindow() or isDocked()
        QTimer-->>AppItem: keep AppItem
    else no window and not docked
        AppItem->>AppItem: deleteLater()
    end
Loading

File-Level Changes

Change Details Files
Preserve the dock row when an app replaces its window by checking the live active-app model and refreshing the row mapping after model rows shift.
  • Detect whether the app still has an active row instead of relying on cached dock entries.
  • Defer row remapping until removals and index renumbering complete.
  • Emit deferred updates only for still-valid dock rows.
panels/dock/taskmanager/dockglobalelementmodel.cpp
panels/dock/taskmanager/dockglobalelementmodel.h
Make window handover and app-item lifecycle handling resilient to transient or unidentifiable windows.
  • Skip windows whose desktop file cannot be resolved before dereferencing it.
  • Delay deletion of windowless, undocked app items by 500 ms and re-check state in the timer callback.
panels/dock/taskmanager/taskmanager.cpp
panels/dock/taskmanager/appitem.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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

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

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="panels/dock/taskmanager/appitem.cpp" line_range="354-358" />
<code_context>
+    // replaces one window with another (an Electron splash window closing as the
+    // main window appears). Defer the decision so a window arriving in that gap
+    // can re-attach instead of losing the item permanently.
+    QTimer::singleShot(500, this, [this]() {
+        if (!hasWindow() && !isDocked()) {
+            deleteLater();
+        }
+    });
 }
</code_context>
<issue_to_address>
**issue (bug_risk):** Every invocation schedules an independent 500 ms deletion callback, but later invocations do not cancel or reset earlier callbacks. If a transient window appears and disappears again, an older callback can delete the AppItem only shortly after the latest window disappeared, before the intended grace period has elapsed.

**Triggers:** When the AppItem becomes empty, briefly receives a window, becomes empty again, and the handover interval exceeds the original timer's remaining time.

**Suggested fix:** Use a restartable member QTimer or a generation/token check so only the callback associated with the latest empty-state transition can delete the AppItem.

```suggestion
    const auto deletionGeneration = property("_appItemDeletionGeneration").toInt() + 1;
    setProperty("_appItemDeletionGeneration", deletionGeneration);
    QTimer::singleShot(500, this, [this, deletionGeneration]() {
        if (property("_appItemDeletionGeneration").toInt() == deletionGeneration &&
            !hasWindow() && !isDocked()) {
            deleteLater();
        }
    });
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread panels/dock/taskmanager/appitem.cpp Outdated

@BLumia BLumia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

而新的主窗口之后也不会再产生新的 insert 通知,导致任务栏入口永久消失。

根因不应该是这个吗?应当确保 model 里的信息总是对的,加 timer 去延后删除是在绕过这个问题吧。

@xionglinlin

xionglinlin commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor Author

而新的主窗口之后也不会再产生新的 insert 通知,导致任务栏入口永久消失。

根因不应该是这个吗?应当确保 model 里的信息总是对的,加 timer 去延后删除是在绕过这个问题吧。
BUG367743_ANALYSIS.md
bug367743.log

附件是问题出现时候打印的日志信息和对日志的分析:
根据分析,延时应该非本次bug的根本原因,只是防御性修改,真正根本原因是在 删除窗口 的修复逻辑。延时逻辑是为了保证splash窗口能晚点销毁,然后让主窗口能够复用之前创建的AppItem,时序保证为: splash显示 -> 主窗口显示 -> splash销毁。 如果不加延时,可能会有这样的时序:splash显示 -> splash销毁 -> 主窗口显示,当出现这种时序的时候,任务栏图标可能不会消失,但有可能出现很小的抖动(显示-消失-再显示)。

@BLumia

BLumia commented Sep 20, 2026

Copy link
Copy Markdown
Member

真正根本原因是在 删除窗口 的修复逻辑

那我觉得应该修这个?

@xionglinlin

Copy link
Copy Markdown
Contributor Author

真正根本原因是在 删除窗口 的修复逻辑

那我觉得应该修这个?

修的就是这个啊,代码在:panels/dock/taskmanager/dockglobalelementmodel.cpp

@xionglinlin

Copy link
Copy Markdown
Contributor Author

实际多次验证了一下,去掉QTimer的延时,问题也已经修复了

@xionglinlin
xionglinlin requested a review from BLumia September 20, 2026 09:46
@BLumia

BLumia commented Sep 21, 2026

Copy link
Copy Markdown
Member

哦我之前的意思是只修那个。现在看上去合理了。

我本地也验一下就加分。

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: wjyrich, xionglinlin

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

An Electron app such as SiYuan shows a splash window and then a main window.
When the splash closes, the active-app model reports the row as removed, but
because the notification is queued the model already holds the main window in
that same row. The handler only looked for another cached entry of the app in
m_data, found none, and deleted the dock row even though the app was still
running. The main window's own insert notification is delivered too late (the
row it refers to no longer exists by then) and gets discarded, so the taskbar
entry disappeared permanently.

Fix:
1. dockglobalelementmodel.cpp: decide by querying whether the app is still
   present in the active-app model, instead of trusting the cached m_data
   entries, and re-point the kept row at its current active row after the
   remaining rows are renumbered.
2. taskmanager.cpp: skip a window whose desktop file could not be resolved
   instead of dereferencing a null pointer.

Note on the handover gap: an earlier revision of this change also deferred the
AppItem deletion behind a 500ms grace timer in appitem.cpp. Measurements with
the Electron splash->main handover showed that this path is never taken: the
main window is mapped before the splash is destroyed, so the window list never
becomes empty during the handover and the timer never protects anything. The
only times the timer fired were when the application had really exited, where
deleting immediately is correct. The grace period has therefore been dropped;
the delete decision stays immediate.

Log: keep taskbar entry when an app replaces its window

fix(dock): 应用替换窗口时保持任务栏入口

Electron 应用(例如思源笔记 SiYuan)启动时会先显示启动窗口(splash
window),随后再创建主窗口。当启动窗口关闭时,active-app model 会将该行
报告为已移除。但由于通知是异步排队处理的,此时 model 实际上已经在同一行中
持有了新的主窗口。处理逻辑之前只会在缓存的 m_data 中查找该应用是否还有
其他缓存项,由于找不到,就直接删除 dock 中对应的任务栏行,尽管该应用实际
上仍在运行。而主窗口自身的 insert 通知投递得太晚(此时它所指的行已不复
存在)而被丢弃,导致任务栏入口永久消失。

修复:
1. dockglobalelementmodel.cpp:不再依赖缓存的 m_data 条目判断应用是否
仍然存在,而是通过查询 active-app model 判断应用是否仍处于活动状态;
同时在剩余行重新编号后,将保留的任务栏行重新指向该应用当前对应的
active row。
2. taskmanager.cpp:当窗口无法解析对应的 desktop file 时直接跳过该
窗口,避免对空指针进行解引用。

关于交接间隙:本变更的早期版本还曾在 appitem.cpp 中用 500ms 宽限定时器
延迟删除 AppItem。实测 Electron 的 splash→main 交接过程不会走到该路径:
主窗口先于 splash 销毁被映射,因此交接期间窗口列表从不为空,该定时器保护
不到任何场景;它唯一触发的时机都是应用确实已退出,此时立即删除才是正确的。
因此去掉了宽限期,删除判断保持立即执行。

Log: 应用窗口交接时不再丢失任务栏图标
PMS: BUG-367743
Change-Id: I8e2d524b3ee590353601cfa947cd48b6781e73c0
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 97 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本次提交修复了Electron应用(如思源笔记)窗口替换时任务栏入口消失的Bug,同时修复了空指针解引用的潜在崩溃问题。代码逻辑正确,注释充分,无安全漏洞。OCR发现了一个预存的潜在位置索引失效问题,本次提交已通过边界检查部分缓解。

🔍 详细分析

1. 语法逻辑 ✅

评价: 良好 ✅ 通过

潜在问题:

  1. panels/dock/taskmanager/dockglobalelementmodel.cpp:259-262 - pendingDataChangedRows中存储的位置索引在循环内移除条目后可能失效。当一次rowsRemoved通知涉及多行时,先存储的pos值在后续条目被remove后会偏移。新增的边界检查防止了越界崩溃,但可能导致dataChanged信号被静默跳过或发送到错误的行。此问题为预存问题,本次提交已部分缓解。——非常重要

建议: 1. 考虑将pendingDataChangedRows改为存储desktop ID而非位置索引,在最终发射dataChanged信号时通过ID重新查找当前位置,避免位置失效问题。2. null指针修复(taskmanager.cpp第355行)是重要的安全改进,有效防止了desktopfile为空时的崩溃。


2. 代码质量 ✅

评价: 优秀 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: 1. 注释质量优秀,清晰解释了队列通知机制导致的窗口替换问题和修复思路。2. 新增findActiveRowByDesktopId函数封装良好,包含空值检查。3. 代码风格与现有代码库一致。


3. 代码性能 ✅

评价: 优秀 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: 1. findActiveRowByDesktopId使用match()进行O(n)查找,但在窗口替换场景中调用频率低,性能影响可忽略。2. 使用std::as_const(pendingRowRefresh)避免了不必要的拷贝,是良好实践。


4. 代码安全 🔒

评价: 优秀 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: 1. taskmanager.cpp中新增的null检查修复了潜在的空指针解引用崩溃,提升了代码安全性。2. 日志中仅输出window->id()(窗口标识符),无敏感信息泄露风险。3. 无注入风险、无硬编码密钥、无权限绕过问题。


💡 改进建议代码示例

// 暂无代码示例

本报告由 AI 代码审查工具自动生成

@xionglinlin

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown

This pr force merged! (status: blocked)

@deepin-bot
deepin-bot Bot merged commit dc761a1 into linuxdeepin:master Sep 21, 2026
5 of 8 checks passed
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