Skip to content

fix: prevent Electron app icon disappearing from taskbar - #1744

Open
mhduiy wants to merge 1 commit into
linuxdeepin:masterfrom
mhduiy:agent/pms-bug-bot/fd49f26123bf
Open

mhduiy wants to merge 1 commit into
linuxdeepin:masterfrom
mhduiy:agent/pms-bug-bot/fd49f26123bf

Conversation

@mhduiy

@mhduiy mhduiy commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

fix: prevent Electron app icon disappearing from taskbar

When an Electron app launches, its splash window closes and the main
window appears. The actual root cause is an AppItem lifecycle race
condition: deleteLater() in checkAppItemNeedDeleteAndDelete() is
asynchronous, so an AppItem scheduled for deletion remains in
m_appitems until the event loop processes the DeferredDelete event.
If the main window is created during this window, getAppItem()
returns the pending-deletion AppItem, appendWindow attaches the main
window to it, and the subsequent DeferredDelete still destroys the
AppItem — causing the taskbar icon to disappear.

This race is intermittent: it only triggers when the splash window is
destroyed before the main window is created. When the main window
appears first, hasWindow() stays true and deletion is never
scheduled.

Fix with three changes:

  1. appitem.cpp: replace immediate deleteLater() in
    checkAppItemNeedDeleteAndDelete() with a 500ms grace period via
    QTimer::singleShot, re-checking hasWindow() before deletion.
    This is the core fix — it allows the main window to re-attach
    during the grace period, cancelling the deferred deletion.

  2. taskmanager.cpp: add null check for desktopfile in handleWindowAdded
    before calling getAppItem(), as defensive programming.

  3. desktopfileamparser.cpp: refactor identifyWindow() to extract
    tryAmIdentify() and findParentPid() helpers, and walk up the process
    tree (up to 5 levels) when the initial PID identification fails,
    so renderer subprocess PIDs can find their parent process for AM
    identification.

Log: fix Electron app icon disappearing from taskbar
Bug: https://pms.uniontech.com/bug-view-367743.html

When an Electron app launches, its splash window closes and the main
window may appear with a renderer subprocess PID. The AM Identify call
fails because pidfd_open gets the wrong PID, and /proc/PID/cmdline
fallback also cannot match the .desktop file. createByWindow returns
nullptr, and handleWindowAdded dereferences it without a null check,
causing the AppItem to never be created — the taskbar icon disappears.

Fix at root-cause level with three changes:

1. taskmanager.cpp: add null check for desktopfile in handleWindowAdded
   before calling getAppItem(), preventing undefined behavior.

2. appitem.cpp: replace immediate deleteLater() in
   checkAppItemNeedDeleteAndDelete() with a 500ms grace period via
   QTimer::singleShot, allowing a new window (e.g. Electron main window
   after splash closes) to re-attach before the AppItem is destroyed.

3. desktopfileamparser.cpp: refactor identifyWindow() to extract
   tryAmIdentify() and findParentPid() helpers, and walk up the process
   tree (up to 5 levels) when the initial PID identification fails,
   so renderer subprocess PIDs can find their parent process for AM
   identification.

Log: fix Electron app icon disappearing from taskbar
Bug: https://pms.uniontech.com/bug-view-367743.html

@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.

Sorry @mhduiy, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 10 hours and 42 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Reviewer's Guide

Prevents Electron taskbar icons from disappearing by safely handling failed desktop-file identification, retaining AppItems briefly during splash-to-main-window transitions, and resolving renderer windows through parent-process Application Manager identification.

Sequence diagram for Electron window identification and AppItem retention

sequenceDiagram
    participant Window as ElectronWindow
    participant TaskManager
    participant Parser as DesktopFileAMParser
    participant AM as ApplicationManager
    participant Proc as Procfs
    participant AppItem

    TaskManager->>Parser: identifyWindow(Window)
    Parser->>AM: tryAmIdentify(Window.pid())
    AM-->>Parser: identification fails
    loop Up to 5 parent processes
        Parser->>Proc: findParentPid(currentPid)
        Proc-->>Parser: parentPid
        Parser->>AM: tryAmIdentify(parentPid)
        AM-->>Parser: desktopId or failure
    end
    Parser-->>TaskManager: desktopfile or null
    alt desktopfile is null
        TaskManager-->>TaskManager: skip window safely
    else desktopfile found
        TaskManager->>AppItem: getAppItem()
        AppItem->>AppItem: checkAppItemNeedDeleteAndDelete()
        AppItem->>AppItem: QTimer.singleShot(500ms)
        Window->>AppItem: re-attach within grace period
    end
Loading

File-Level Changes

Change Details Files
Harden window-to-application association when desktop-file identification fails.
  • Skip unidentifiable windows instead of dereferencing a null desktop-file pointer.
  • Log the skipped window for diagnosis.
panels/dock/taskmanager/taskmanager.cpp
Delay AppItem destruction to accommodate rapid window transitions.
  • Replace immediate deferred deletion with a 500 ms timer.
  • Cancel deletion if a window reattaches or the item becomes docked during the grace period.
panels/dock/taskmanager/appitem.cpp
Identify multi-process application windows through their process ancestry.
  • Extract PID-based Application Manager identification and /proc PPID parsing into helpers.
  • Retry identification through up to five parent processes when the window PID is not recognized.
  • Add diagnostics for failed and parent-process identification attempts.
panels/dock/taskmanager/desktopfileamparser.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

@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

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

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

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 未发现严重安全问题,代码修改有效修复了Electron应用图标从任务栏消失的问题,三处修改逻辑清晰、防御性编程到位
关联Bug PMS Bug #367743 - 打开第三方应用思源笔记,偶现任务栏上应用图标会消失了
分析模式 全量分析(GitHub PR)
修改文件 3个文件,+77行,-9行

📝 代码变更概述

本次PR修复了Electron应用(如思源笔记)图标从任务栏消失的问题,包含三处互补的修改:

  1. taskmanager.cpp: 在 handleWindowAdded() 中添加 desktopfile 空值检查,当窗口无法关联到desktop文件时记录警告并提前返回,防止空指针解引用。
  2. appitem.cpp: 将 checkAppItemNeedDeleteAndDelete() 中的立即 deleteLater() 替换为500ms延迟删除机制,允许新窗口(如Electron主窗口)在splash窗口关闭后重新关联到AppItem。
  3. desktopfileamparser.cpp: 重构 identifyWindow(),提取 tryAmIdentify() 和 findParentPid() 辅助函数,在初始PID识别失败时向上遍历进程树(最多5层),解决Electron渲染子进程PID无法被AM识别的问题。

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

评分: 25/25

分析:

  • 三处修改的C++语法均正确,使用了标准的Qt API(QTimer::singleShot、QFile、QTextStream)
  • QTimer::singleShot(500, this, lambda) 使用 this 作为上下文对象,当对象被销毁时lambda不会执行,避免了悬空指针风险
  • findParentPid() 中 parentPid == currentPid 检查防止了进程树遍历的潜在无限循环
  • parentPid <= 0 检查正确处理了 /proc 读取失败的情况
  • handleWindowAdded() 中的空值检查位置正确,在 getAppItem() 调用之前进行防御

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


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

评分: 23/25

分析:

  • 辅助函数提取(tryAmIdentify()、findParentPid())实现了良好的关注点分离,提高了代码可读性
  • 注释完整且有意义,解释了设计意图(如500ms延迟的原因、进程树遍历的目的)
  • 无残留调试代码
  • 无重复代码

潜在问题:

  1. panels/dock/taskmanager/desktopfileamparser.cpp,tryAmIdentify() 函数,约163行:文件描述符泄漏。pidfd_open() 返回的文件描述符在 QDBusUnixFileDescriptor(pidfd) 复制后未关闭。QDBusUnixFileDescriptor 构造函数会 dup() 该fd,原始 pidfd 应当通过 close(pidfd) 释放。此问题在原始代码中已存在,但重构后的进程树遍历(最多5层)会放大泄漏影响,每次窗口识别失败最多泄漏6个文件描述符。

建议:

在 tryAmIdentify() 函数中,D-Bus调用完成后添加 close(pidfd) 释放文件描述符。修复示例:

static QString tryAmIdentify(pid_t pid)
{
    auto pidfd = pidfd_open(pid, 0);
    if (pidfd < 0) {
        return QString();
    }
    auto res = DDBusSender().service("org.desktopspec.ApplicationManager1")
                         .interface("org.desktopspec.ApplicationManager1")
                         .path("/org/desktopspec/ApplicationManager1")
                         .method("Identify")
                         .arg(QDBusUnixFileDescriptor(pidfd))
                         .call();
    close(pidfd);  // 释放原始文件描述符
    // ... 后续逻辑 ...
}

3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

评分: 19/20

分析:

  • 500ms延迟删除机制设计合理,仅在AppItem即将被删除时触发,不会影响正常操作流程
  • 进程树遍历最多5层,每次仅读取 /proc/<pid>/status(轻量级文件系统操作)和一次D-Bus调用
  • 遍历仅在初始PID识别失败时触发,属于异常路径,不影响正常场景性能
  • 无不必要的内存拷贝或复杂算法

潜在问题:

  1. panels/dock/taskmanager/desktopfileamparser.cpp,identifyWindow() 函数,约205行:进程树遍历中每层都会发起D-Bus同步调用,最坏情况下(5层遍历全部失败)会产生6次D-Bus同步调用。但此路径仅在初始PID识别失败时触发,且遍历次数有上限(5次),实际影响可控。

建议:

500ms延迟删除机制设计合理,允许新窗口在Electron应用切换时重新关联


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

评分: 30/30

🔐 存在 0 个安全漏洞

安全分析:

  • PID来源于窗口系统(window->pid()),非用户可控输入,不存在路径遍历风险
  • /proc/<pid>/status 文件读取中,PID为数值类型,通过 QStringLiteral("/proc/%1/status").arg(pid) 构造路径,不存在注入风险
  • 无命令执行、SQL查询等危险操作
  • 无硬编码密钥或敏感信息
  • 无不安全密码算法使用
  • D-Bus调用使用标准接口,无权限绕过风险

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个

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


📈 评分汇总

维度 评分 满分 状态
语法逻辑 25 25 ✅ 通过
代码质量 23 25 ✅ 通过
代码性能 19 20 ✅ 通过
代码安全 30 30 ✅ 通过
总计 97 100 ✅ 通过

💡 改进建议代码示例

// 修复文件描述符泄漏 - desktopfileamparser.cpp tryAmIdentify() 函数
static QString tryAmIdentify(pid_t pid)
{
    auto pidfd = pidfd_open(pid, 0);
    if (pidfd < 0) {
        return QString();
    }
    
    auto res = DDBusSender().service("org.desktopspec.ApplicationManager1")
                         .interface("org.desktopspec.ApplicationManager1")
                         .path("/org/desktopspec/ApplicationManager1")
                         .method("Identify")
                         .arg(QDBusUnixFileDescriptor(pidfd))
                         .call();
    
    close(pidfd);  // 释放原始文件描述符,防止泄漏
    
    if (res.isErrored()) {
        qCDebug(amdesktopfileLog()) << "AM failed to identify pid" << pid
                                    << ", reason is:" << res.error().message();
        return QString();
    }
    
    auto reply = res.value();
    QList<QVariant> data = reply.arguments();
    return data.first().toString();
}

✅ 审查检查清单

  • 已按四维度标准完成代码分析(语法逻辑、代码质量、代码性能、代码安全)
  • 代码安全维度第一行包含"存在0个安全漏洞"
  • 安全漏洞已按等级分类(无安全漏洞)
  • 包含漏洞对比统计行(新增0/减少0/持平0)
  • 每个维度标记✓/✕与评价词一致
  • 审查结论考虑了commit message的目的(修复Electron应用图标消失问题)
  • 问题定位准确,行号和函数名正确
  • 环境变量使用正确,文件路径无误

本报告由 AI 代码审查工具自动生成
扫描时间:2026-09-15 11:40:00

@deepin-ci-robot

Copy link
Copy Markdown

@mhduiy: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
github-trigger-obs-ci 43822ca link true /test github-trigger-obs-ci

Full PR test history. Your PR dashboard.

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. I understand the commands that are listed here.

@deepin-bot

deepin-bot Bot commented Sep 22, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.55
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1752

@deepin-bot

deepin-bot Bot commented Sep 23, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 2.0.56
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #1753

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