Skip to content

fix(palette): break QML Palette windowText binding loop from the read side - #684

Draft
52cyb wants to merge 1 commit into
linuxdeepin:masterfrom
52cyb:windowText
Draft

fix(palette): break QML Palette windowText binding loop from the read side#684
52cyb wants to merge 1 commit into
linuxdeepin:masterfrom
52cyb:windowText

Conversation

@52cyb

@52cyb 52cyb commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
  1. Add Qt6-only _d_getControlPaletteForResolve(): resolve typed colors (Highlight / HighlightedText) from the stable application palette, and read only the Window role (theme detection) from the control -- never via QQuickPalette::toQPalette(), which reads back the DTK-overridden palette.windowText and re-triggers its QML binding
  2. Add Qt6-only _d_controlPaletteHasInactiveState(): compare Active vs Inactive groups role-by-role excluding windowText (a binding artifact, not a real inactive-state difference), avoiding the third windowText read-back point in getColorOf()
  3. Replace the three read points (typed-color branch / inactive gate / updateControlTheme theme detection) to use the new safe helpers
  4. Add a re-entrancy guard (m_updateDepth) in updateControlTheme() to skip the redundant recompute triggered by the palette.windowText binding writing the palette

Influence:

  1. Open control center; no more "Binding loop detected for property windowText" when operating Button / MenuItem / ItemDelegate
  2. Verify checked/highlighted text and icon foreground still turn white (HighlightedText) in light theme, and accent-color changes are followed
  3. Verify normal state keeps the default foreground color
  4. Verify light/dark theme switching
  5. Verify inactive state (window unfocused) text is not wrongly blended

fix(palette): 从读侧断开 QML Palette windowText 绑定环

  1. 新增仅 Qt6 的 _d_getControlPaletteForResolve():类型化颜色(Highlight/ HighlightedText)从稳定的应用调色板解析,仅 Window 角色(主题探测)从控件读取 —— 不再走 QQuickPalette::toQPalette(),避免读回被 DTK 覆盖的 palette.windowText 而重触发其 QML 绑定
  2. 新增仅 Qt6 的 _d_controlPaletteHasInactiveState():逐角色比较 Active/Inactive 组并排除 windowText(绑定产物,非真实 inactive 差异),消除 getColorOf() 中第三个 windowText 读回点
  3. 三处读点(类型化分支 / inactive 门控 / updateControlTheme 主题探测)改用新的安全函数
  4. 在 updateControlTheme() 增加重入守卫(m_updateDepth),跳过由 palette.windowText 绑定写 palette 触发的冗余重算

Influence:

  1. 打开控制中心,操作 Button / MenuItem / ItemDelegate 不再出现 "Binding loop detected for property windowText"
  2. 验证浅色主题下选中/高亮文字与图标前景仍为白色(HighlightedText),强调色变化跟随
  3. 验证普通态保持默认前景色
  4. 验证亮/暗主题切换
  5. 验证窗口失焦 inactive 态文字不被误混合

PMS: Task-392413

Summary by Sourcery

Break Qt 6 palette windowText binding loops from the read side while preserving correct control colors across themes and inactive states.

Bug Fixes:

  • Prevent QML palette.windowText binding loops on Qt 6 controls while preserving typed-color resolution, theme detection, inactive-state handling, and accent-color updates.

Enhancements:

  • Add safe palette resolution and inactive-state comparison that avoid reading back DTK-overridden windowText values.
  • Guard palette theme and color updates against redundant re-entrant recalculations.

… side

1. Add Qt6-only `_d_getControlPaletteForResolve()`: resolve typed colors
   (Highlight / HighlightedText) from the stable application palette, and
   read only the Window role (theme detection) from the control -- never via
   QQuickPalette::toQPalette(), which reads back the DTK-overridden
   `palette.windowText` and re-triggers its QML binding
2. Add Qt6-only `_d_controlPaletteHasInactiveState()`: compare Active vs
   Inactive groups role-by-role excluding `windowText` (a binding artifact,
   not a real inactive-state difference), avoiding the third windowText
   read-back point in getColorOf()
3. Replace the three read points (typed-color branch / inactive gate /
   updateControlTheme theme detection) to use the new safe helpers
4. Add a re-entrancy guard (`m_updateDepth`) in updateControlTheme() to skip
   the redundant recompute triggered by the `palette.windowText` binding
   writing the palette

Influence:
1. Open control center; no more "Binding loop detected for property windowText"
   when operating Button / MenuItem / ItemDelegate
2. Verify checked/highlighted text and icon foreground still turn white
   (HighlightedText) in light theme, and accent-color changes are followed
3. Verify normal state keeps the default foreground color
4. Verify light/dark theme switching
5. Verify inactive state (window unfocused) text is not wrongly blended

fix(palette): 从读侧断开 QML Palette windowText 绑定环

1. 新增仅 Qt6 的 `_d_getControlPaletteForResolve()`:类型化颜色(Highlight/
   HighlightedText)从稳定的应用调色板解析,仅 Window 角色(主题探测)从控件读取
   —— 不再走 QQuickPalette::toQPalette(),避免读回被 DTK 覆盖的
   `palette.windowText` 而重触发其 QML 绑定
2. 新增仅 Qt6 的 `_d_controlPaletteHasInactiveState()`:逐角色比较 Active/Inactive
   组并排除 `windowText`(绑定产物,非真实 inactive 差异),消除 getColorOf()
   中第三个 windowText 读回点
3. 三处读点(类型化分支 / inactive 门控 / updateControlTheme 主题探测)改用新的安全函数
4. 在 updateControlTheme() 增加重入守卫(`m_updateDepth`),跳过由
   `palette.windowText` 绑定写 palette 触发的冗余重算

Influence:
1. 打开控制中心,操作 Button / MenuItem / ItemDelegate 不再出现
   "Binding loop detected for property windowText"
2. 验证浅色主题下选中/高亮文字与图标前景仍为白色(HighlightedText),强调色变化跟随
3. 验证普通态保持默认前景色
4. 验证亮/暗主题切换
5. 验证窗口失焦 inactive 态文字不被误混合

PMS: Task-392413
@deepin-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 52cyb

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

sourcery-ai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR breaks the Qt 6 palette.windowText binding loop by eliminating unsafe palette read-backs, using targeted palette resolution and inactive-state checks, and guarding synchronous re-entrant theme updates while retaining the existing Qt 5 behavior.

Sequence diagram for safe Qt6 palette color resolution

sequenceDiagram
    participant Selector as DQuickControlColorSelector
    participant Control as QQuickControl
    participant Palette as QQuickPalette
    participant AppPalette as ApplicationPalette

    Selector->>AppPalette: applicationPalette(theme)
    AppPalette-->>Selector: Stable accent and typed-color roles
    Selector->>Control: property("palette")
    Control-->>Selector: QQuickPalette
    Selector->>Palette: active()->window()
    Palette-->>Selector: Window role
    Selector->>Selector: targetColor.toColor(resolvedPalette)

    opt InactiveState
        Selector->>Palette: Compare Active/Inactive roles
        Note over Selector,Palette: Exclude WindowText binding artifact
        Palette-->>Selector: Real inactive-state difference
    end
Loading

Sequence diagram for guarded palette theme updates

sequenceDiagram
    participant Palette as QQuickPalette
    participant Selector as DQuickControlColorSelector
    participant ColorProps as ColorProperties

    Palette->>Selector: updateControlTheme()
    Selector->>Selector: Check m_updateDepth
    Selector->>Selector: Increment m_updateDepth
    Selector->>Selector: Read Window role via _d_getControlPaletteForResolve()
    Selector->>ColorProps: updateAllColorProperties()
    ColorProps->>Selector: Increment m_updateDepth
    Selector->>Palette: Write palette.windowText
    Palette-->>Selector: Synchronous changed signal
    Selector->>Selector: Skip re-entrant updateControlTheme()
    ColorProps->>Selector: Decrement m_updateDepth
    Selector->>Selector: Decrement m_updateDepth
Loading

File-Level Changes

Change Details Files
Avoid QML binding feedback while resolving control palette colors on Qt 6.
  • Resolve typed colors from the application palette while sourcing only the control Window role.
  • Replace palette conversion at typed-color, inactive-state, and theme-detection read points.
  • Preserve the Qt 5 implementation path unchanged.
src/private/dquickcontrolpalette.cpp
Detect genuine inactive palette differences without reading the overridden windowText role.
  • Compare Active and Inactive color groups role-by-role through public QQuickColorGroup accessors.
  • Exclude WindowText and NoRole from the comparison to avoid treating binding artifacts as inactive-state changes.
src/private/dquickcontrolpalette.cpp
Prevent redundant re-entrant theme and color recomputation.
  • Track nested update activity with m_updateDepth.
  • Skip re-entered updateControlTheme() calls and balance the guard around property updates, including the early-return path.
src/private/dquickcontrolpalette.cpp
src/private/dquickcontrolpalette_p.h

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="src/private/dquickcontrolpalette.cpp" line_range="144-145" />
<code_context>
+        case QPalette::ToolTipBase:      return g->toolTipBase();
+        case QPalette::ToolTipText:      return g->toolTipText();
+        case QPalette::PlaceholderText:  return g->placeholderText();
+        case QPalette::Accent:           return g->accent();
+        case QPalette::NColorRoles:      break;
+        }
+        return QColor();
</code_context>
<issue_to_address>
**issue (bug_risk):** The Qt6-only helper does not compile with Qt versions where `QPalette::Accent` and `QQuickColorGroup::accent()` are unavailable, including the Qt 6.2-era versions this project otherwise explicitly supports. The unguarded enum cases prevent building those configurations.

**Triggers:** When building against Qt 6.2 or another Qt6 version before the Accent palette role was introduced.

**Suggested fix:** Guard the Accent case and accessor with the Qt version that introduced them, or omit that role for older Qt versions.
</issue_to_address>

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

Comment on lines +144 to +145
case QPalette::Accent: return g->accent();
case QPalette::NColorRoles: break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The Qt6-only helper does not compile with Qt versions where QPalette::Accent and QQuickColorGroup::accent() are unavailable, including the Qt 6.2-era versions this project otherwise explicitly supports. The unguarded enum cases prevent building those configurations.

Triggers: When building against Qt 6.2 or another Qt6 version before the Accent palette role was introduced.

Suggested fix: Guard the Accent case and accessor with the Qt version that introduced them, or omit that role for older Qt versions.

@deepin-ci-robot

Copy link
Copy Markdown
Contributor

deepin pr auto review

🤖 AI 代码审查报告

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

Pass


📊 总体评价

项目 结果
审查结论 优秀
评分详情 代码修复了QML Palette windowText绑定循环问题,实现方案清晰合理。新增了两个辅助函数避免读取windowText角色,添加了重入保护机制。代码注释详尽,逻辑正确,无安全漏洞。

📋 代码变更概要

PR 标题: fix(palette): break QML Palette windowText binding loop from the read side
作者: 52cyb
分支: windowText → master
修改文件: src/private/dquickcontrolpalette.cpp, src/private/dquickcontrolpalette_p.h
Commit: 3d83014

变更说明:
本PR修复了QML Palette中windowText角色的绑定循环问题。核心问题是:DTK通过QML绑定覆盖windowText角色,而toQPalette()会读取回这个被覆盖的windowText值,从而重新触发QML绑定,形成循环。

修复方案包括:

  1. 新增_d_getControlPaletteForResolve()函数,从应用调色板获取颜色,仅从控件读取Window角色,避免读取windowText
  2. 新增_d_controlPaletteHasInactiveState()函数,逐角色比较Active/Inactive组,排除windowText和NoRole
  3. 添加m_updateDepth重入保护,防止palette.windowText写入时同步触发QQuickPalette::changed信号导致的重入
  4. 使用#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)条件编译,Qt5保持原有路径

🔍 详细分析

1. 语法逻辑 ✓

评分: 25/25 分
评价: 语法正确,逻辑清晰

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

分析说明:

  1. colorOf lambda的switch语句覆盖了所有QPalette::ColorRole枚举值,包括NColorRoles(break并返回默认QColor())
  2. 重入保护逻辑正确:updateControlTheme()在行1113检查m_updateDepth > 0时提前返回(此时尚未递增,无需递减),行1131和1141在所有退出路径正确递减
  3. #if/#else/#endif条件编译块配对完整,Qt5/Qt6路径分明
  4. 空指针检查完善:_d_controlPaletteHasInactiveState()依次检查item、palette、activeGroup、inactiveGroup是否为空

2. 代码质量 ✓

评分: 24/25 分
评价: 代码结构清晰,注释完整

潜在问题:

  1. 重入保护使用手动递增/递减m_updateDepth计数器,而非RAII模式(如作用域守卫)。虽然当前所有返回路径都正确处理了递减,但使用RAII可以更安全地防止异常或未来代码修改引入的遗漏递减问题。(文件:src/private/dquickcontrolpalette.cpp,函数:updateControlTheme/updateAllColorProperties,行:1113-1141, 1164-1173)

建议:
考虑使用RAII作用域守卫替代手动m_updateDepth递增/递减,例如:

struct UpdateDepthGuard {
    int& depth;
    explicit UpdateDepthGuard(int& d) : depth(d) { ++depth; }
    ~UpdateDepthGuard() { --depth; }
    Q_DISABLE_COPY(UpdateDepthGuard)
};

// 在 updateControlTheme() 中使用:
void DQuickControlColorSelector::updateControlTheme()
{
    if (!m_control)
        return;
    if (m_updateDepth > 0)
        return;
    UpdateDepthGuard guard(m_updateDepth);
    // ... 函数体 ...
    // 无需手动 --m_updateDepth,guard 析构时自动递减
}

3. 代码性能 ✓

评分: 20/20 分
评价: 性能良好,资源使用合理

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

分析说明:

  1. _d_getControlPaletteForResolve()创建应用调色板副本并读取一个Window颜色,与原始_d_getControlPalette()调用toQPalette()的开销相当
  2. _d_controlPaletteHasInactiveState()遍历约20个颜色角色进行比较,虽比原始isEqual()稍多工作,但避免了toQPalette()的完整转换开销
  3. 重入保护仅增加整数比较和递增/递减的极小开销
  4. 本次修复实际上提升了性能——通过打破绑定循环避免了不必要的重复计算和QML绑定重评估

4. 代码安全 ✓

评分: 30/30 分
评价: 存在0个安全漏洞

🔐 发现 0 个安全漏洞

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

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

分析说明:
本代码为纯内部Qt/QML调色板管理逻辑,不涉及用户输入处理、网络操作、文件系统操作、命令执行、SQL查询等安全敏感操作。无硬编码密钥、无缓冲区溢出风险、无路径遍历风险。使用Qt安全API进行颜色操作。


💡 改进建议代码示例

// RAII 作用域守卫,替代手动 m_updateDepth 递增/递减
struct UpdateDepthGuard {
    int& depth;
    explicit UpdateDepthGuard(int& d) : depth(d) { ++depth; }
    ~UpdateDepthGuard() { --depth; }
    Q_DISABLE_COPY(UpdateDepthGuard)
};

// 在头文件中添加(dquickcontrolpalette_p.h)
class DQuickControlColorSelector : public QObject
{
    // ...
    int m_updateDepth = 0;
};

// updateControlTheme() 使用 RAII 守卫
void DQuickControlColorSelector::updateControlTheme()
{
    if (!m_control)
        return;
    if (m_updateDepth > 0)
        return;

    UpdateDepthGuard guard(m_updateDepth);  // 自动管理深度

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    const QPalette pa = _d_getControlPaletteForResolve(m_control, m_state->controlTheme);
    const QColor windowColor = pa.color(QPalette::Window);
#else
    const QPalette pa = _d_getControlPalette(m_control);
    const QColor windowColor = pa.color(QPalette::Window);
#endif

    if (!windowColor.isValid()) {
        updateAllColorProperties();
        return;  // guard 自动递减
    }

    const auto themeType = DGuiApplicationHelper::toColorType(windowColor);
    if (!setControlTheme(themeType)) {
        updateAllColorProperties();
    }
    // guard 自动递减
}

// updateAllColorProperties() 使用 RAII 守卫
void DQuickControlColorSelector::updateAllColorProperties()
{
    UpdateDepthGuard guard(m_updateDepth);

    for (int i = 0; i < m_metaObject->count(); ++i) {
        auto p = m_metaObject->name(i);
        if (p.isEmpty())
            continue;
        updatePropertyFromName(p);
    }
    // guard 自动递减
}

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

@52cyb
52cyb marked this pull request as draft September 10, 2026 07:10
@deepin-bot

deepin-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

TAG Bot

New tag: 6.7.49
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #685

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