Skip to content

Fix: Abnormal interface display when clicking any hotkey#2972

Open
JWWTSL wants to merge 1 commit intolinuxdeepin:masterfrom
JWWTSL:fix-shortcut-focus
Open

Fix: Abnormal interface display when clicking any hotkey#2972
JWWTSL wants to merge 1 commit intolinuxdeepin:masterfrom
JWWTSL:fix-shortcut-focus

Conversation

@JWWTSL
Copy link
Contributor

@JWWTSL JWWTSL commented Jan 29, 2026

Log: When editing hotkeys, forceActiveAppearance is used to keep the Control Center main window “active” during global key capture, preventing the window from briefly turning gray due to focus loss.

pms: bug-303139

Summary by Sourcery

Keep the Control Center window visually active while capturing global shortcut keys to prevent abnormal interface appearance during hotkey editing.

Bug Fixes:

  • Prevent the Control Center main window from briefly appearing inactive or grayed out when editing or capturing hotkeys.

Enhancements:

  • Introduce a forceActiveAppearance flag on the main window and wire shortcut editing views to toggle it during global key capture.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: JWWTSL

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 Jan 29, 2026

Reviewer's Guide

Keeps the Control Center main window visually active while capturing global shortcut keys by introducing a forceActiveAppearance flag on the main window and toggling it from the shortcut editing QML components during key capture lifecycle events.

Sequence diagram for maintaining active appearance during shortcut key capture start

sequenceDiagram
    actor User
    participant ShortcutsView
    participant ShortcutSettingDialog
    participant dccData
    participant DccApp
    participant MainWindow
    participant RequestActivateTimer

    User->>ShortcutsView: Click_edit_global_shortcut
    ShortcutsView->>dccData: updateKey(id,type)
    ShortcutsView->>DccApp: mainWindow()
    DccApp-->>ShortcutsView: MainWindow_reference
    ShortcutsView->>MainWindow: set forceActiveAppearance = true

    User->>ShortcutSettingDialog: Open_shortcut_dialog_and_click_capture
    ShortcutSettingDialog->>dccData: updateKey(keyId,1)
    ShortcutSettingDialog->>DccApp: mainWindow()
    DccApp-->>ShortcutSettingDialog: MainWindow_reference
    ShortcutSettingDialog->>MainWindow: set forceActiveAppearance = true

    Note over MainWindow,RequestActivateTimer: Later, when window loses focus
    MainWindow-->>MainWindow: active_changed_to_false
    MainWindow->>RequestActivateTimer: start()
    RequestActivateTimer-->>RequestActivateTimer: timeout
    RequestActivateTimer->>MainWindow: requestActivate() when forceActiveAppearance == true
Loading

Sequence diagram for releasing active appearance after shortcut key capture end

sequenceDiagram
    participant dccData
    participant ShortcutsView
    participant ShortcutSettingDialog
    participant DccApp
    participant MainWindow

    %% Ending key capture from shortcuts page
    dccData-->>ShortcutsView: requestRestore
    ShortcutsView->>DccApp: mainWindow()
    DccApp-->>ShortcutsView: MainWindow_reference
    ShortcutsView->>MainWindow: set forceActiveAppearance = false
    ShortcutsView-->>ShortcutsView: restoreShortcutView()

    dccData-->>ShortcutsView: requestClear
    ShortcutsView->>DccApp: mainWindow()
    DccApp-->>ShortcutsView: MainWindow_reference
    ShortcutsView->>MainWindow: set forceActiveAppearance = false
    ShortcutsView-->>ShortcutsView: clearShortcut()

    dccData-->>ShortcutsView: keyConflicted
    ShortcutsView->>DccApp: mainWindow()
    DccApp-->>ShortcutsView: MainWindow_reference
    ShortcutsView->>MainWindow: set forceActiveAppearance = false

    dccData-->>ShortcutsView: keyDone
    ShortcutsView->>DccApp: mainWindow()
    DccApp-->>ShortcutsView: MainWindow_reference
    ShortcutsView->>MainWindow: set forceActiveAppearance = false

    %% Ending key capture from dialog
    dccData-->>ShortcutSettingDialog: requestRestore
    ShortcutSettingDialog->>DccApp: mainWindow()
    DccApp-->>ShortcutSettingDialog: MainWindow_reference
    ShortcutSettingDialog->>MainWindow: set forceActiveAppearance = false

    dccData-->>ShortcutSettingDialog: keyConflicted
    ShortcutSettingDialog->>DccApp: mainWindow()
    DccApp-->>ShortcutSettingDialog: MainWindow_reference
    ShortcutSettingDialog->>MainWindow: set forceActiveAppearance = false

    dccData-->>ShortcutSettingDialog: keyDone
    ShortcutSettingDialog->>DccApp: mainWindow()
    DccApp-->>ShortcutSettingDialog: MainWindow_reference
    ShortcutSettingDialog->>MainWindow: set forceActiveAppearance = false
Loading

Updated class diagram for main window and shortcut editing components

classDiagram
    class DccWindow {
        +string appLicense
        +real currentIndex
        +var sidebarPage
        +bool forceActiveAppearance
        +real minimumWidth
        +real minimumHeight
        +bool visible
        +bool active
        +requestActivate()
    }

    class RequestActivateTimer {
        +int interval
        +start()
        +onTriggered()
    }

    class DccApp {
        +mainWindow() DccWindow
    }

    class dccData {
        +updateKey(id,type)
        +requestRestore()
        +requestClear()
        +keyConflicted(oldAccels,newAccels)
        +keyDone(accels)
        +conflictText
        +formatKeys(accels)
    }

    class ShortcutsView {
        +int searchEditWidth
        +var viewScrollbar
        +var shortcutSettingsBody
        +var shortcutView
        +setForceActiveAppearance(value)
    }

    class ShortcutSettingDialog {
        +string keyId
        +string keyName
        +string cmdName
        +string keySequence
        +var edit
        +var conflictText
        +var saveKeys
        +setForceActiveAppearance(value)
    }

    DccWindow o-- RequestActivateTimer : contains
    DccApp --> DccWindow : returns_mainWindow
    ShortcutsView --> DccApp : calls_mainWindow
    ShortcutSettingDialog --> DccApp : calls_mainWindow
    ShortcutsView --> dccData : uses_updateKey_and_signals
    ShortcutSettingDialog --> dccData : uses_updateKey_and_signals
    ShortcutsView ..> DccWindow : toggles_forceActiveAppearance
    ShortcutSettingDialog ..> DccWindow : toggles_forceActiveAppearance
    RequestActivateTimer ..> DccWindow : calls_requestActivate_when_forceActiveAppearance
Loading

File-Level Changes

Change Details Files
Add a reusable helper in shortcut views/dialog to toggle the main window's forced active appearance during hotkey editing.
  • Define a setForceActiveAppearance(value) function that retrieves DccApp.mainWindow() and conditionally updates its forceActiveAppearance property.
  • Invoke the helper to set forceActiveAppearance to true when starting key capture in shortcut editing views.
  • Invoke the helper to reset forceActiveAppearance to false on all completion paths from key capture (restore, clear, conflict, done).
src/plugin-keyboard/qml/Shortcuts.qml
src/plugin-keyboard/qml/ShortcutSettingDialog.qml
Extend the main DccWindow to support a forceActiveAppearance flag that re-activates the window when focus is lost during hotkey capture.
  • Add a forceActiveAppearance boolean property on the main D.ApplicationWindow to represent whether the window should maintain an active appearance.
  • Hook into onActiveChanged to trigger a short Timer when the window becomes inactive but forceActiveAppearance is true.
  • Add a Timer that, when triggered and forceActiveAppearance remains true, calls mainWindow.requestActivate() to re-activate the window.
src/dde-control-center/plugin/DccWindow.qml

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 2 issues, and left some high level feedback:

  • The setForceActiveAppearance helper is duplicated in both Shortcuts.qml and ShortcutSettingDialog.qml; consider centralizing this logic (e.g., a shared util or a common base component) to avoid duplication and keep behavior consistent.
  • The requestActivateTimer in DccWindow.qml has no repeat flag specified, so it will fire repeatedly once started; if you only intend a single re-activation, set repeat: false or running: false with explicit start() to avoid unnecessary wakeups.
  • You rely on several specific dccData signals to reset forceActiveAppearance to false; review whether all possible exit/cancel/error paths from global key capture also clear this flag to avoid leaving the window permanently in a forced-active state.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `setForceActiveAppearance` helper is duplicated in both `Shortcuts.qml` and `ShortcutSettingDialog.qml`; consider centralizing this logic (e.g., a shared util or a common base component) to avoid duplication and keep behavior consistent.
- The `requestActivateTimer` in `DccWindow.qml` has no `repeat` flag specified, so it will fire repeatedly once started; if you only intend a single re-activation, set `repeat: false` or `running: false` with explicit `start()` to avoid unnecessary wakeups.
- You rely on several specific `dccData` signals to reset `forceActiveAppearance` to `false`; review whether all possible exit/cancel/error paths from global key capture also clear this flag to avoid leaving the window permanently in a forced-active state.

## Individual Comments

### Comment 1
<location> `src/plugin-keyboard/qml/Shortcuts.qml:20` </location>
<code_context>
     modality: Qt.WindowModal
     property string keyId
+
+    function setForceActiveAppearance(value) {
+        var win = DccApp.mainWindow()
+        if (win && win.forceActiveAppearance !== undefined)
</code_context>

<issue_to_address>
**suggestion:** Avoid duplicating `setForceActiveAppearance` implementation across QML files.

This helper is also defined in `ShortcutSettingDialog.qml`. To avoid duplication and keep behavior consistent, centralize it (for example via a helper on `DccApp`, or by calling `DccApp.mainWindow().forceActiveAppearance` directly where needed) instead of reimplementing it in multiple QML components.

Suggested implementation:

```
    icon: "keyboard_fn"
    weight: parent.weight // 300
    property int searchEditWidth: 600

    property var viewScrollbar: ScrollBar {
        width: 10
    }

```

```
                                edit.keys = ""
                                var win = DccApp.mainWindow()
                                if (win && win.forceActiveAppearance !== undefined)
                                    win.forceActiveAppearance = true
                                dccData.updateKey(model.id, model.type)

```

To fully remove duplication, consider applying a similar change in `ShortcutSettingDialog.qml`: either remove its local `setForceActiveAppearance` helper and call `DccApp.mainWindow().forceActiveAppearance` directly, or (preferably) introduce a single helper on `DccApp` (e.g. `DccApp.setForceActiveAppearance(value)`) and use that from all QML files.
</issue_to_address>

### Comment 2
<location> `src/dde-control-center/plugin/DccWindow.qml:18-21` </location>
<code_context>
     property string appLicense: "GPL-3.0-or-later"
     property real currentIndex: 1
     property var sidebarPage: null
+    property bool forceActiveAppearance: false  // 编辑快捷键时保持窗口激活外观

     minimumWidth: 520
</code_context>

<issue_to_address>
**issue (bug_risk):** Repeatedly re-activating the window can create focus-stealing or UX issues.

When `forceActiveAppearance` is true, losing activation will always trigger a timer that re-activates the window, which can effectively steal focus when the user switches apps/workspaces. Consider limiting this to a narrow period or a single re-activation per capture so it doesn’t continuously override the user’s focus choice.
</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.

Log: When editing hotkeys, forceActiveAppearance is used to keep the Control Center main window “active” during global key capture, preventing the window from briefly turning gray due to focus loss.

pms: bug-303139
@JWWTSL JWWTSL force-pushed the fix-shortcut-focus branch from 86be1ba to 8fc0b75 Compare January 29, 2026 08:21
@deepin-ci-robot
Copy link

deepin pr auto review

Git Diff 代码审查报告

概述

这段代码修改主要是为了在快捷键设置期间保持窗口激活外观,防止窗口在快捷键抓取过程中失去焦点。修改涉及三个文件:DccWindow.qmlShortcutSettingDialog.qmlShortcuts.qml

语法逻辑分析

  1. DccWindow.qml 修改

    • 添加了两个新属性:forceActiveAppearancependingReactivation
    • 添加了 onActiveChanged 事件处理器和定时器逻辑
    • 语法正确,逻辑合理
  2. ShortcutSettingDialog.qml 和 Shortcuts.qml 修改

    • 在多个地方添加了设置和重置这两个新属性的代码
    • 使用了条件检查 if (win.forceActiveAppearance !== undefined) 来确保属性存在
    • 语法正确,但代码重复较多

代码质量评估

  1. 代码重复

    • 在多个地方重复了相同的代码块来设置和重置 forceActiveAppearancependingReactivation 属性
    • 建议将这段逻辑封装成一个函数,减少重复代码
  2. 命名和注释

    • 属性命名清晰,注释解释了用途,这是好的实践
    • forceActiveAppearance 可能需要更明确的文档说明其具体行为
  3. 条件检查

    • 使用 if (win.forceActiveAppearance !== undefined) 是一种防御性编程,但略显冗余
    • 如果这些属性是 DccWindow 的固定属性,这种检查可能是不必要的

代码性能考虑

  1. 定时器间隔

    • 定时器间隔设置为 1ms,可能过于频繁
    • 建议考虑使用稍长的间隔,如 10-50ms,以减少不必要的系统调用
  2. 属性访问

    • 多次访问 DccApp.mainWindow() 可能会导致性能开销
    • 可以考虑缓存窗口引用,特别是在频繁调用的函数中

代码安全问题

  1. 属性访问安全性

    • 虽然使用了 !== undefined 检查,但仍然存在潜在风险
    • 如果 DccApp.mainWindow() 返回的对象结构发生变化,可能导致运行时错误
  2. 定时器处理

    • 定时器没有明确的清理机制,可能导致内存泄漏
    • 建议在窗口销毁时确保定时器被正确停止

改进建议

  1. 减少代码重复

    // 在 DccWindow.qml 中添加辅助函数
    function setForceActiveAppearance(value) {
        forceActiveAppearance = value;
        pendingReactivation = value;
    }
    
    // 在其他文件中使用
    var win = DccApp.mainWindow();
    if (win && win.setForceActiveAppearance !== undefined) {
        win.setForceActiveAppearance(true); // 或 false
    }
  2. 优化定时器

    Timer {
        id: requestActivateTimer
        interval: 50 // 增加间隔时间
        repeat: false
        onTriggered: {
            if (mainWindow.forceActiveAppearance) {
                mainWindow.requestActivate();
            }
        }
    }
  3. 添加清理逻辑

    // 在 DccWindow.qml 中添加
    Component.onDestruction: {
        requestActivateTimer.stop();
    }
  4. 考虑使用信号/槽机制

    // 在 DccWindow.qml 中添加信号
    signal forceActiveAppearanceChanged(bool value)
    
    // 在其他文件中连接信号
    Connections {
        target: DccApp.mainWindow()
        function onForceActiveAppearanceChanged(value) {
            // 处理状态变化
        }
    }
  5. 添加文档注释

    /*!
     * \brief 强制窗口保持激活外观
     * \param value 是否保持激活外观
     * 
     * 当设置为 true 时,窗口将尝试保持激活外观,
     * 即使实际失去焦点。这在快捷键抓取期间特别有用。
     */
    function setForceActiveAppearance(value) {
        // 实现
    }

总结

这段代码修改实现了预期的功能,即保持窗口激活外观,但存在一些代码重复和潜在的优化空间。通过减少重复代码、优化定时器设置和添加适当的清理机制,可以提高代码质量和可维护性。建议在合并前考虑上述改进建议。

@mhduiy
Copy link
Contributor

mhduiy commented Jan 29, 2026

录入快捷键的时候使用后端的SelectKeystroke接口,这个接口的grab操作会导致控制中心失焦,理论上可以直接使用Qt的接口去拿到当前用户按下的快捷键,暂时不清楚为什么要这样处理。另外快捷键(比如控制音量)也会导致短暂失焦,这些问题都需要在快捷键服务重构之间解决,所以这个问题在重构过程中一起看有没有其他更好的办法

@deepin-bot
Copy link

deepin-bot bot commented Jan 29, 2026

TAG Bot

New tag: 6.1.71
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #2974

@deepin-bot
Copy link

deepin-bot bot commented Feb 5, 2026

TAG Bot

New tag: 6.1.72
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #3009

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.

3 participants