Skip to content

tests: update dss-network-plugin test example#494

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
ut003640:master
Feb 5, 2026
Merged

tests: update dss-network-plugin test example#494
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
ut003640:master

Conversation

@ut003640
Copy link
Contributor

@ut003640 ut003640 commented Feb 5, 2026

update dss-network-plugin test example

Log: update dss-network-plugin test example

Summary by Sourcery

Update the dss example application to integrate the real dss-network-plugin UI with per-screen management and popup windows instead of a simple test button.

New Features:

  • Add PopupWindow helper for displaying arrow-style floating content and tooltips for the network plugin UI.
  • Introduce DssScreenManager to manage and display a DssTestWidget instance on each available screen using a shared NetworkPlugin instance.

Enhancements:

  • Refactor DssTestWidget to be constructed with a dde::network::NetworkPlugin, embedding the plugin’s item, tips, and content widgets into a custom floating button with hover tooltips and clickable popup panels.
  • Rework main application setup to configure a light-themed Dtk palette and delegate window creation/showing to DssScreenManager.
  • Update licensing headers in example sources from LGPL-3.0-or-later to GPL-3.0-or-later where applicable.

Build:

  • Update the dss_example CMake configuration to include and link against the dss-network-plugin target and its headers.

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 5, 2026

Reviewer's Guide

Refactors the dss_example test app to integrate the real dss-network-plugin NetworkPlugin, add reusable popup window UI for content and tips, manage one test widget per screen, and update licensing headers and build configuration accordingly.

Sequence diagram for NetworkPlugin popup interaction on button click

sequenceDiagram
    actor User
    participant DssTestWidget
    participant FloatingButton
    participant PopupWindow as ContentPopupWindow
    participant NetworkPlugin

    User->>FloatingButton: click()
    FloatingButton-->>DssTestWidget: clicked()
    DssTestWidget->>DssTestWidget: onClickButton()
    DssTestWidget->>NetworkPlugin: content()
    alt content is null
        DssTestWidget-->>User: return without popup
    else content exists
        alt first time popup created
            DssTestWidget->>ContentPopupWindow: new PopupWindow(this)
            DssTestWidget->>NetworkPlugin: content()
            DssTestWidget->>ContentPopupWindow: setContent(netlistWidget)
            DssTestWidget->>ContentPopupWindow: resizeWithContent()
            DssTestWidget->>ContentPopupWindow: setArrowX(width/2)
        else popup already exists
            DssTestWidget->>ContentPopupWindow: setContent(netlistWidget)
            DssTestWidget->>ContentPopupWindow: resizeWithContent()
        end
        alt popup currently visible
            DssTestWidget->>ContentPopupWindow: hide()
            opt tip popup exists
                DssTestWidget->>PopupWindow: toggle()
            end
        else popup hidden
            opt tip popup exists
                DssTestWidget->>PopupWindow: hide()
            end
            DssTestWidget->>ContentPopupWindow: resizeWithContent()
            DssTestWidget->>ContentPopupWindow: show(centerPoint)
        end
    end
Loading

Sequence diagram for multi-screen DssTestWidget management

sequenceDiagram
    participant main as main
    participant QApplication
    participant DssScreenManager
    participant QGuiApplication
    participant QScreen
    participant DssTestWidget
    participant NetworkPlugin

    main->>QApplication: QApplication(argc, argv)
    main->>DssScreenManager: new DssScreenManager()
    DssScreenManager->>NetworkPlugin: new NetworkPlugin(this)
    DssScreenManager->>DssScreenManager: initConnection()
    DssScreenManager->>QGuiApplication: connect(screenAdded, onScreenAdded)
    DssScreenManager->>QGuiApplication: connect(screenRemoved, onScreenRemoved)

    DssScreenManager->>DssScreenManager: initScreen()
    DssScreenManager->>QGuiApplication: screens()
    loop for each screen
        QGuiApplication-->>DssScreenManager: QScreen *screen
        DssScreenManager->>DssTestWidget: new DssTestWidget(m_netModule)
        DssScreenManager->>DssScreenManager: m_screenWidget[screen] = testWidget
    end

    main->>DssScreenManager: showWindow()
    loop for each screen in m_screenWidget
        DssScreenManager->>DssScreenManager: showWindow(screen, testWidget)
        DssScreenManager->>QScreen: geometry()
        DssScreenManager->>DssTestWidget: resize(330, 800)
        DssScreenManager->>DssTestWidget: move(centerOnScreen)
        DssScreenManager->>DssTestWidget: show()
    end

    QGuiApplication-->>DssScreenManager: screenAdded(newScreen)
    DssScreenManager->>DssTestWidget: new DssTestWidget(m_netModule)
    DssScreenManager->>DssScreenManager: m_screenWidget[newScreen] = testWidget
    DssScreenManager->>DssScreenManager: showWindow(newScreen, testWidget)

    QGuiApplication-->>DssScreenManager: screenRemoved(removedScreen)
    DssScreenManager->>DssScreenManager: lookup testWidget
    DssScreenManager->>DssScreenManager: m_screenWidget.remove(removedScreen)
    DssScreenManager->>DssTestWidget: deleteLater()
Loading

Class diagram for updated dss_example test application structure

classDiagram
    class DssTestWidget {
        +DssTestWidget(NetworkPlugin *networkPlugin, QWidget *parent)
        +~DssTestWidget()
        +bool eventFilter(QObject *watched, QEvent *event)
        +void resizeEvent(QResizeEvent *event)
        +void mousePressEvent(QMouseEvent *event)
        +void onClickButton()
        -NetworkPlugin *m_pModule
        -FloatingButton *m_iconButton
        -PopupWindow *m_container
        -PopupWindow *m_tipContainer
    }

    class FloatingButton {
        +FloatingButton(QWidget *parent)
    }

    class PopupWindow {
        +PopupWindow(QWidget *parent)
        +~PopupWindow()
        +bool model() const
        +void setContent(QWidget *content)
        +void show(const QPoint &pos)
        +void toggle()
        +void toggle(const QPoint &pos)
        +void hide()
        +void showEvent(QShowEvent *e)
        +void enterEvent(QEnterEvent *e)
        +bool eventFilter(QObject *o, QEvent *e)
        +void ensureRaised()
        -QPoint m_lastPoint
    }

    class DssScreenManager {
        +DssScreenManager(QObject *parent)
        +void showWindow()
        -void initConnection()
        -void initScreen()
        -void showWindow(QScreen *screen, DssTestWidget *testWidget)
        -void onScreenAdded(QScreen *screen)
        -void onScreenRemoved(QScreen *screen)
        -QMap~QScreen*,DssTestWidget*~ m_screenWidget
        -NetworkPlugin *m_netModule
    }

    class NetworkPlugin {
        +NetworkPlugin(QObject *parent)
        +void init()
        +QWidget *itemWidget()
        +QWidget *itemTipsWidget()
        +QWidget *content()
    }

    class DArrowRectangle {
    }

    class QWidget {
    }

    class QObject {
    }

    class QScreen {
    }

    class DFloatingButton {
    }

    DssTestWidget --> NetworkPlugin : uses
    DssTestWidget --> FloatingButton : owns
    DssTestWidget --> PopupWindow : owns
    FloatingButton --|> DFloatingButton
    PopupWindow --|> DArrowRectangle
    DssScreenManager --> DssTestWidget : manages per screen
    DssScreenManager --> NetworkPlugin : owns shared
    DssScreenManager --|> QObject
    DssTestWidget --|> QWidget
    PopupWindow --|> QWidget
    FloatingButton --|> QWidget
    QScreen --> DssScreenManager
Loading

File-Level Changes

Change Details Files
Refactor DssTestWidget to use dde::network::NetworkPlugin, custom FloatingButton, and popup windows for content and tips instead of simple message boxes.
  • Change DssTestWidget constructor to accept a NetworkPlugin pointer, store it, and call its init() method.
  • Replace raw DFloatingButton usage with a custom FloatingButton subclass and configure icon, palette, and layout with an inner icon widget and vertical centering.
  • Embed the plugin’s itemWidget() inside the floating button, installing event filters on the widget, button, and item to handle interactions.
  • Implement hover-based tooltip display using PopupWindow and the plugin’s itemTipsWidget() in eventFilter().
  • Add resizeEvent and mousePressEvent overrides to reposition or hide popups when the widget resizes or receives clicks outside.
  • Add onClickButton() slot to lazily create/show/hide a PopupWindow around the plugin’s content() widget, keeping a static netlistWidget pointer to avoid recreating content.
  • Ensure proper cleanup by deleting the NetworkPlugin via deleteLater() in the destructor.
dss_example/dsstestwidget.cpp
dss_example/dsstestwidget.h
Introduce PopupWindow helper class for arrowed, floating popup rectangles with content management and accessibility support.
  • Create PopupWindow as a DArrowRectangle subclass, configuring visual properties like margin, background, border, shadow, radius, and arrow geometry.
  • Provide setContent() override that installs an event filter on the content, updates accessibility name, and forwards to the base class.
  • Implement show(pos) / toggle(pos) / toggle() and hide() methods that manage visibility and remember last position, ensuring the embedded content is shown/hidden appropriately.
  • Implement eventFilter to respond to content resize (re-show to reposition) and parent changes (emit contentDetach).
  • Emit visibleChanged on show/hide and ensure popups stay on top via ensureRaised slot, invoked after show and on enter events.
dss_example/popupwindow.cpp
dss_example/popupwindow.h
Add DssScreenManager to create and manage one DssTestWidget per screen and handle dynamic screen add/remove.
  • Define DssScreenManager as a QObject that owns a single shared dde::network::NetworkPlugin instance.
  • On construction, connect to QGuiApplication::screenAdded and screenRemoved to respond to multi-screen changes.
  • Initialize all existing screens by creating a DssTestWidget for each and storing them in a QMap<QScreen*, DssTestWidget*>.
  • Implement showWindow() overloads that size and center each test widget (330x800) on its associated screen.
  • Handle screenAdded by creating and showing a widget; handle screenRemoved by deleting the associated widget and removing it from the map.
dss_example/dssscreenmanager.cpp
dss_example/dssscreenmanager.h
Update the dss_example application entry point to use DssScreenManager and configure Dtk palettes for a lock-screen-like test environment.
  • Replace direct construction and centering of a single DssTestWidget with construction of a DssScreenManager and call to showWindow().
  • Derive the application name from argv ("example" or "example-") and set it on QApplication.
  • Configure DGuiApplicationHelper palette type to LightType and adjust several DPalette roles (WindowText, Text, AlternateBase, Button, Light, Dark, ButtonText) for a white-on-dark style, regenerating palette colors via generatePaletteColor, then applying it as the application palette.
dss_example/main.cpp
Adjust CMake configuration and licensing headers for the example to link against and include the real dss-network-plugin.
  • Add ../dss-network-plugin to the target_include_directories for the dss_example target so it can include plugin headers like networkmodule.h and networkcontroller.h.
  • Link the dss_example target against the dss-network-plugin library in addition to existing Qt, KF6, and dde-network-core6 dependencies.
  • Update SPDX-FileCopyrightText years and switch license tags from LGPL-3.0-or-later to GPL-3.0-or-later in the example source files.
dss_example/CMakeLists.txt
dss_example/dsstestwidget.cpp
dss_example/dsstestwidget.h
dss_example/main.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 4 issues, and left some high level feedback:

  • In DssTestWidget the NetworkPlugin pointer is owned and deleted by DssScreenManager, but the widget destructor also calls m_pModule->deleteLater(), which risks double-deletion; the widget should not delete a plugin it didn’t create.
  • The static QWidget *netlistWidget in DssTestWidget::onClickButton causes all DssTestWidget instances to share a single content widget, which can break when multiple screens/widgets are involved; consider making this an instance member instead of a function-static.
  • In popupwindow.h the bool model() const; method is declared but never defined or used, which will lead to a link error or dead code; either implement it or remove the declaration.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DssTestWidget` the `NetworkPlugin` pointer is owned and deleted by `DssScreenManager`, but the widget destructor also calls `m_pModule->deleteLater()`, which risks double-deletion; the widget should not delete a plugin it didn’t create.
- The `static QWidget *netlistWidget` in `DssTestWidget::onClickButton` causes all `DssTestWidget` instances to share a single content widget, which can break when multiple screens/widgets are involved; consider making this an instance member instead of a function-static.
- In `popupwindow.h` the `bool model() const;` method is declared but never defined or used, which will lead to a link error or dead code; either implement it or remove the declaration.

## Individual Comments

### Comment 1
<location> `dss_example/dsstestwidget.cpp:63-65` </location>
<code_context>
+    m_iconButton->installEventFilter(this);
 }

 DssTestWidget::~DssTestWidget()
 {
+    m_pModule->deleteLater();
 }

</code_context>

<issue_to_address>
**issue (bug_risk):** NetworkPlugin lifetime management is inconsistent with DssScreenManager and can cause double-deletion

`DssScreenManager` creates a single `dde::network::NetworkPlugin` (`m_netModule`) and shares that pointer with multiple `DssTestWidget` instances, but each widget calls `m_pModule->deleteLater()` in its destructor. This schedules the same `QObject` for deletion multiple times and can crash. Either remove `deleteLater()` here and clearly document that `DssScreenManager` owns the plugin, or give each widget its own plugin instance so ownership is clear and singular.
</issue_to_address>

### Comment 2
<location> `dss_example/dsstestwidget.cpp:84-85` </location>
<code_context>
+            m_tipContainer->raise();
+            m_tipContainer->show(QPoint(rect().center().x(), m_iconButton->parentWidget()->y()));
+        } break;
+        case QEvent::Leave: {
+            m_tipContainer->hide();
             break;
-        case QEvent::Enter:
</code_context>

<issue_to_address>
**issue (bug_risk):** Potential null dereference when hiding m_tipContainer on Leave event

In the `QEvent::Leave` case you call `m_tipContainer->hide()` even though `m_tipContainer` is only set up in the `Enter` path. If `Leave` occurs before the popup is constructed, this will dereference a null pointer. Add a null check (e.g. `if (m_tipContainer) m_tipContainer->hide();`) to avoid a crash.
</issue_to_address>

### Comment 3
<location> `dss_example/dsstestwidget.h:21` </location>
<code_context>
+} // namespace dde

 class QPushButton;
+class FloatingButton;
+using namespace Dtk::Widget;

</code_context>

<issue_to_address>
**issue (complexity):** Consider replacing the trivial FloatingButton subclass with a type alias and removing the using-namespace directive from the header to simplify the public interface and reduce indirection.

You can simplify the header and reduce indirection by removing the trivial `FloatingButton` subclass and the `using namespace` in the header.

### 1. Drop the trivial `FloatingButton` subclass

`FloatingButton` adds no behavior beyond a forwarding constructor. You can keep the short name while avoiding an extra type and Q_OBJECT class by using a type alias:

```cpp
#include <DFloatingButton>
#include <QWidget>

namespace dde {
namespace network {
class NetworkPlugin;
} // namespace network
} // namespace dde

class PopupWindow;
class QPushButton;

// Avoid using namespace in headers; just define an alias:
using FloatingButton = Dtk::Widget::DFloatingButton;

class DssTestWidget : public QWidget
{
    Q_OBJECT

public:
    explicit DssTestWidget(dde::network::NetworkPlugin *networkPlugin,
                           QWidget *parent = Q_NULLPTR);
    ~DssTestWidget() override;

protected:
    bool eventFilter(QObject *watched, QEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;

protected slots:
    void onClickButton();

private:
    dde::network::NetworkPlugin *m_pModule;
    FloatingButton *m_iconButton;
    PopupWindow *m_container;
    PopupWindow *m_tipContainer;
};
```

Then remove the subclass entirely:

```cpp
// Remove this from the header:
class FloatingButton : public DFloatingButton
{
    Q_OBJECT

public:
    explicit FloatingButton(QWidget *parent = nullptr);
};
```

If the constructor had any non-trivial initialization logic in the `.cpp`, you can move that into a small helper function instead:

```cpp
inline FloatingButton *createFloatingButton(QWidget *parent)
{
    auto *btn = new FloatingButton(parent);
    // apply any customization here (icon, style, etc.)
    return btn;
}
```

Use `createFloatingButton` where you currently construct `FloatingButton` directly.

### 2. Avoid `using namespace` in a header

Headers should not introduce `using namespace` because they leak names into every translation unit that includes them. You already fix that by switching to the alias above; just remove this line:

```cpp
// Remove from header:
using namespace Dtk::Widget;
```

This keeps the public interface thinner and the types easier to reason about without changing any functionality.
</issue_to_address>

### Comment 4
<location> `dss_example/dsstestwidget.cpp:153` </location>
<code_context>
+    }
+}
+
+FloatingButton::FloatingButton(QWidget *parent)
+    : DFloatingButton(parent)
+{
</code_context>

<issue_to_address>
**issue (complexity):** Consider simplifying the widget logic by removing the trivial FloatingButton wrapper, using an instance member instead of a static netlist pointer, and extracting popup/tip show–hide helpers to reduce duplication and coupling.

You can simplify a few spots without changing behavior:

### 1) Remove the `FloatingButton` wrapper

`FloatingButton` adds no behavior over `DFloatingButton` and just adds a new type to track.

**Current:**
```cpp
class FloatingButton : public DFloatingButton {
    Q_OBJECT
public:
    explicit FloatingButton(QWidget *parent = nullptr);
};

FloatingButton::FloatingButton(QWidget *parent)
    : DFloatingButton(parent)
{
}
```

**Simpler:**
- Delete `FloatingButton` and use `DFloatingButton` directly:

```cpp
// in dssTestWidget.h
private:
    Dtk::Widget::DFloatingButton *m_iconButton = nullptr;
```

```cpp
// in constructor
m_iconButton = new Dtk::Widget::DFloatingButton(iconWidget);
```

No functionality changes, but one less class to understand.

---

### 2) Replace the `static QWidget *netlistWidget` with a member

The static pointer couples all `DssTestWidget` instances and makes lifetime harder to reason about. Fetch and cache the content per instance instead.

**Current:**
```cpp
void DssTestWidget::onClickButton()
{
    ...
    static QWidget *netlistWidget = nullptr;
    if (!netlistWidget) {
        netlistWidget = m_pModule->content();
    }
    netlistWidget->setParent(m_container);
    netlistWidget->adjustSize();
    m_container->setContent(netlistWidget);
    ...
}
```

**Suggested change:**

```cpp
// dssTestWidget.h
private:
    QWidget *m_netlistWidget = nullptr;
```

```cpp
// dssTestWidget.cpp
void DssTestWidget::onClickButton()
{
    if (!m_pModule->content())
        return;

    if (!m_container) {
        m_container = new PopupWindow(this);
        connect(m_container, &PopupWindow::contentDetach, this, [this] {
            m_container->setContent(nullptr);
            m_container->hide();
        });
    }

    if (!m_netlistWidget) {
        m_netlistWidget = m_pModule->content();
    }

    m_netlistWidget->setParent(m_container);
    m_netlistWidget->adjustSize();
    m_container->setContent(m_netlistWidget);
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);

    ...
}
```

This keeps the “fetch once, reuse later” behavior but confines it to the instance.

---

### 3) Centralize popup/tip show/hide logic into small helpers

Right now `eventFilter`, `mousePressEvent`, `resizeEvent`, and `onClickButton` all directly manipulate `m_container` and `m_tipContainer`. You can reduce branching and duplication with local helper methods, without introducing a whole new class.

**Add helpers:**
```cpp
// dssTestWidget.h
private:
    void showTipAtButton();
    void hideTip();
    void showContentAtCenter();
    void hideContent();
```

```cpp
// dssTestWidget.cpp
void DssTestWidget::showTipAtButton()
{
    if (!m_tipContainer) {
        QWidget *tipWidget = m_pModule->itemTipsWidget();
        m_tipContainer = new PopupWindow(this);
        m_tipContainer->setContent(tipWidget);
        m_tipContainer->resizeWithContent();
        m_tipContainer->setArrowX(tipWidget->width() / 2);
    }
    m_tipContainer->raise();
    m_tipContainer->show(QPoint(rect().center().x(), m_iconButton->parentWidget()->y()));
}

void DssTestWidget::hideTip()
{
    if (m_tipContainer)
        m_tipContainer->hide();
}

void DssTestWidget::showContentAtCenter()
{
    if (!m_container || !m_container->getContent())
        return;

    QWidget *content = m_container->getContent();
    content->adjustSize();
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);
    m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
}

void DssTestWidget::hideContent()
{
    if (m_container)
        m_container->hide();
}
```

**Then simplify callers:**

```cpp
bool DssTestWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_iconButton) {
        switch (event->type()) {
        case QEvent::Enter:
            showTipAtButton();
            break;
        case QEvent::Leave:
            hideTip();
            break;
        default:
            break;
        }
    }
    return QWidget::eventFilter(watched, event);
}

void DssTestWidget::mousePressEvent(QMouseEvent *event)
{
    hideTip();
    hideContent();
    QWidget::mousePressEvent(event);
}

void DssTestWidget::resizeEvent(QResizeEvent *event)
{
    if (m_container && m_container->isVisible())
        showContentAtCenter();
    QWidget::resizeEvent(event);
}

void DssTestWidget::onClickButton()
{
    ...
    if (m_container->isVisible()) {
        hideContent();
        if (m_tipContainer)
            m_tipContainer->toggle();
    } else {
        hideTip();
        showContentAtCenter();
    }
}
```

This keeps all existing behavior but puts the popup lifecycle in a few clear functions, making the control flow easier to follow.
</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 63 to 65
DssTestWidget::~DssTestWidget()
{
m_pModule->deleteLater();
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): NetworkPlugin lifetime management is inconsistent with DssScreenManager and can cause double-deletion

DssScreenManager creates a single dde::network::NetworkPlugin (m_netModule) and shares that pointer with multiple DssTestWidget instances, but each widget calls m_pModule->deleteLater() in its destructor. This schedules the same QObject for deletion multiple times and can crash. Either remove deleteLater() here and clearly document that DssScreenManager owns the plugin, or give each widget its own plugin instance so ownership is clear and singular.

Comment on lines +84 to +83
case QEvent::Leave: {
m_tipContainer->hide();
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): Potential null dereference when hiding m_tipContainer on Leave event

In the QEvent::Leave case you call m_tipContainer->hide() even though m_tipContainer is only set up in the Enter path. If Leave occurs before the popup is constructed, this will dereference a null pointer. Add a null check (e.g. if (m_tipContainer) m_tipContainer->hide();) to avoid a crash.

} // namespace dde

class QPushButton;
class FloatingButton;
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider replacing the trivial FloatingButton subclass with a type alias and removing the using-namespace directive from the header to simplify the public interface and reduce indirection.

You can simplify the header and reduce indirection by removing the trivial FloatingButton subclass and the using namespace in the header.

1. Drop the trivial FloatingButton subclass

FloatingButton adds no behavior beyond a forwarding constructor. You can keep the short name while avoiding an extra type and Q_OBJECT class by using a type alias:

#include <DFloatingButton>
#include <QWidget>

namespace dde {
namespace network {
class NetworkPlugin;
} // namespace network
} // namespace dde

class PopupWindow;
class QPushButton;

// Avoid using namespace in headers; just define an alias:
using FloatingButton = Dtk::Widget::DFloatingButton;

class DssTestWidget : public QWidget
{
    Q_OBJECT

public:
    explicit DssTestWidget(dde::network::NetworkPlugin *networkPlugin,
                           QWidget *parent = Q_NULLPTR);
    ~DssTestWidget() override;

protected:
    bool eventFilter(QObject *watched, QEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;

protected slots:
    void onClickButton();

private:
    dde::network::NetworkPlugin *m_pModule;
    FloatingButton *m_iconButton;
    PopupWindow *m_container;
    PopupWindow *m_tipContainer;
};

Then remove the subclass entirely:

// Remove this from the header:
class FloatingButton : public DFloatingButton
{
    Q_OBJECT

public:
    explicit FloatingButton(QWidget *parent = nullptr);
};

If the constructor had any non-trivial initialization logic in the .cpp, you can move that into a small helper function instead:

inline FloatingButton *createFloatingButton(QWidget *parent)
{
    auto *btn = new FloatingButton(parent);
    // apply any customization here (icon, style, etc.)
    return btn;
}

Use createFloatingButton where you currently construct FloatingButton directly.

2. Avoid using namespace in a header

Headers should not introduce using namespace because they leak names into every translation unit that includes them. You already fix that by switching to the alias above; just remove this line:

// Remove from header:
using namespace Dtk::Widget;

This keeps the public interface thinner and the types easier to reason about without changing any functionality.

}
}

FloatingButton::FloatingButton(QWidget *parent)
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider simplifying the widget logic by removing the trivial FloatingButton wrapper, using an instance member instead of a static netlist pointer, and extracting popup/tip show–hide helpers to reduce duplication and coupling.

You can simplify a few spots without changing behavior:

1) Remove the FloatingButton wrapper

FloatingButton adds no behavior over DFloatingButton and just adds a new type to track.

Current:

class FloatingButton : public DFloatingButton {
    Q_OBJECT
public:
    explicit FloatingButton(QWidget *parent = nullptr);
};

FloatingButton::FloatingButton(QWidget *parent)
    : DFloatingButton(parent)
{
}

Simpler:

  • Delete FloatingButton and use DFloatingButton directly:
// in dssTestWidget.h
private:
    Dtk::Widget::DFloatingButton *m_iconButton = nullptr;
// in constructor
m_iconButton = new Dtk::Widget::DFloatingButton(iconWidget);

No functionality changes, but one less class to understand.


2) Replace the static QWidget *netlistWidget with a member

The static pointer couples all DssTestWidget instances and makes lifetime harder to reason about. Fetch and cache the content per instance instead.

Current:

void DssTestWidget::onClickButton()
{
    ...
    static QWidget *netlistWidget = nullptr;
    if (!netlistWidget) {
        netlistWidget = m_pModule->content();
    }
    netlistWidget->setParent(m_container);
    netlistWidget->adjustSize();
    m_container->setContent(netlistWidget);
    ...
}

Suggested change:

// dssTestWidget.h
private:
    QWidget *m_netlistWidget = nullptr;
// dssTestWidget.cpp
void DssTestWidget::onClickButton()
{
    if (!m_pModule->content())
        return;

    if (!m_container) {
        m_container = new PopupWindow(this);
        connect(m_container, &PopupWindow::contentDetach, this, [this] {
            m_container->setContent(nullptr);
            m_container->hide();
        });
    }

    if (!m_netlistWidget) {
        m_netlistWidget = m_pModule->content();
    }

    m_netlistWidget->setParent(m_container);
    m_netlistWidget->adjustSize();
    m_container->setContent(m_netlistWidget);
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);

    ...
}

This keeps the “fetch once, reuse later” behavior but confines it to the instance.


3) Centralize popup/tip show/hide logic into small helpers

Right now eventFilter, mousePressEvent, resizeEvent, and onClickButton all directly manipulate m_container and m_tipContainer. You can reduce branching and duplication with local helper methods, without introducing a whole new class.

Add helpers:

// dssTestWidget.h
private:
    void showTipAtButton();
    void hideTip();
    void showContentAtCenter();
    void hideContent();
// dssTestWidget.cpp
void DssTestWidget::showTipAtButton()
{
    if (!m_tipContainer) {
        QWidget *tipWidget = m_pModule->itemTipsWidget();
        m_tipContainer = new PopupWindow(this);
        m_tipContainer->setContent(tipWidget);
        m_tipContainer->resizeWithContent();
        m_tipContainer->setArrowX(tipWidget->width() / 2);
    }
    m_tipContainer->raise();
    m_tipContainer->show(QPoint(rect().center().x(), m_iconButton->parentWidget()->y()));
}

void DssTestWidget::hideTip()
{
    if (m_tipContainer)
        m_tipContainer->hide();
}

void DssTestWidget::showContentAtCenter()
{
    if (!m_container || !m_container->getContent())
        return;

    QWidget *content = m_container->getContent();
    content->adjustSize();
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);
    m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
}

void DssTestWidget::hideContent()
{
    if (m_container)
        m_container->hide();
}

Then simplify callers:

bool DssTestWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_iconButton) {
        switch (event->type()) {
        case QEvent::Enter:
            showTipAtButton();
            break;
        case QEvent::Leave:
            hideTip();
            break;
        default:
            break;
        }
    }
    return QWidget::eventFilter(watched, event);
}

void DssTestWidget::mousePressEvent(QMouseEvent *event)
{
    hideTip();
    hideContent();
    QWidget::mousePressEvent(event);
}

void DssTestWidget::resizeEvent(QResizeEvent *event)
{
    if (m_container && m_container->isVisible())
        showContentAtCenter();
    QWidget::resizeEvent(event);
}

void DssTestWidget::onClickButton()
{
    ...
    if (m_container->isVisible()) {
        hideContent();
        if (m_tipContainer)
            m_tipContainer->toggle();
    } else {
        hideTip();
        showContentAtCenter();
    }
}

This keeps all existing behavior but puts the popup lifecycle in a few clear functions, making the control flow easier to follow.

@@ -1,65 +1,156 @@
// SPDX-FileCopyrightText: 2018 - 2026 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd.
Copy link
Contributor

Choose a reason for hiding this comment

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

时间改下,新增文件加下开源声明

@ut003640 ut003640 force-pushed the master branch 3 times, most recently from 997cc35 to 6419eec Compare February 5, 2026 02:11
caixr23
caixr23 previously approved these changes Feb 5, 2026
update dss-network-plugin test example

Log: update dss-network-plugin test example
@deepin-ci-robot
Copy link

deepin pr auto review

Git Diff 代码审查报告

经过对提供的 git diff 内容的仔细审查,我从语法逻辑、代码质量、代码性能和代码安全四个方面提出以下改进意见:

1. 语法逻辑问题

1.1 dsstestwidget.cpp 中的 onClickButton() 函数存在逻辑问题

void DssTestWidget::onClickButton()
{
    if (!m_pModule->content())
        return;

    // 左键弹出菜单
    if (!m_container) {
        m_container = new PopupWindow(this);
        connect(m_container, &PopupWindow::contentDetach, this, [ this ] {
            m_container->setContent(nullptr);
            m_container->hide();
        });
    }
    static QWidget *netlistWidget = nullptr;
    if (!netlistWidget) {
        netlistWidget = m_pModule->content();
    }
    netlistWidget->setParent(m_container);
    netlistWidget->adjustSize();
    m_container->setContent(netlistWidget);
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);

    if (m_container->isVisible()) {
        m_container->hide();
        if (m_tipContainer) {
            m_tipContainer->toggle();
        }
    } else {
        if (m_tipContainer) {
            m_tipContainer->hide();
        }
        QWidget *content = m_container->getContent();
        content->adjustSize();
        m_container->resizeWithContent();
        m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
    }
}

问题:使用 static QWidget *netlistWidget = nullptr; 存在逻辑问题,因为这是一个静态变量,在整个应用程序生命周期中只初始化一次。如果 m_pModule->content() 返回不同的实例,这个静态变量不会更新,可能导致显示错误的内容。

建议:移除 static 关键字,每次调用函数时重新获取内容:

void DssTestWidget::onClickButton()
{
    if (!m_pModule->content())
        return;

    // 左键弹出菜单
    if (!m_container) {
        m_container = new PopupWindow(this);
        connect(m_container, &PopupWindow::contentDetach, this, [this] {
            m_container->setContent(nullptr);
            m_container->hide();
        });
    }
    
    QWidget *netlistWidget = m_pModule->content();
    netlistWidget->setParent(m_container);
    netlistWidget->adjustSize();
    m_container->setContent(netlistWidget);
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);

    if (m_container->isVisible()) {
        m_container->hide();
        if (m_tipContainer) {
            m_tipContainer->toggle();
        }
    } else {
        if (m_tipContainer) {
            m_tipContainer->hide();
        }
        QWidget *content = m_container->getContent();
        content->adjustSize();
        m_container->resizeWithContent();
        m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
    }
}

1.2 dssscreenmanager.cpp 中的析构函数可能存在问题

DssScreenManager::~DssScreenManager()
{
    qDeleteAll(m_screenWidget);
    m_screenWidget.clear();
}

问题:当屏幕被移除时,onScreenRemoved 函数中使用 deleteLater() 延迟删除小部件,但在析构函数中使用 qDeleteAll() 直接删除。这可能导致同一个对象被删除两次,造成程序崩溃。

建议:在析构函数中也使用 deleteLater() 或者确保所有小部件被正确删除:

DssScreenManager::~DssScreenManager()
{
    for (auto widget : m_screenWidget) {
        widget->deleteLater();
    }
    m_screenWidget.clear();
}

2. 代码质量问题

2.1 dsstestwidget.cpp 中缺少必要的头文件

#include "dsstestwidget.h"
#include "networkmodule.h"
#include "popupwindow.h"

#include <networkcontroller.h>

#include <DPalette>
#include <DFloatingButton>
#include <QHBoxLayout>
#include <QMouseEvent>

问题:代码中使用了 QResizeEvent,但未包含 <QResizeEvent> 头文件。

建议:添加缺失的头文件:

#include "dsstestwidget.h"
#include "networkmodule.h"
#include "popupwindow.h"

#include <networkcontroller.h>

#include <DPalette>
#include <DFloatingButton>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QResizeEvent>  // 添加缺失的头文件

2.2 dsstestwidget.cpp 中事件过滤器逻辑复杂

bool DssTestWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_iconButton) {
        // 当鼠标移入的时候显示提示信息
        switch (event->type()) {
        case QEvent::Enter: {
            if (!m_tipContainer) {
                QWidget *tipWidget = m_pModule->itemTipsWidget();
                m_tipContainer = new PopupWindow(this);
                m_tipContainer->setContent(tipWidget);
                m_tipContainer->resizeWithContent();
                m_tipContainer->setArrowX(tipWidget->width() / 2);
            }
            m_tipContainer->raise();
            m_tipContainer->show(QPoint(rect().center().x(), m_iconButton->parentWidget()->y()));
        } break;
        case QEvent::Leave: {
            m_tipContainer->hide();
        } break;
        default:
            break;
        }
    }
    return QWidget::eventFilter(watched, event);
}

问题:事件过滤器逻辑可以简化,而且注释"当鼠标移入的时候显示提示信息"与代码不完全匹配,因为代码中只处理了 Enter 和 Leave 事件。

建议:简化事件过滤器逻辑,并添加更准确的注释:

bool DssTestWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_iconButton) {
        // 处理鼠标悬停事件以显示/隐藏提示信息
        switch (event->type()) {
        case QEvent::Enter:
            if (!m_tipContainer) {
                QWidget *tipWidget = m_pModule->itemTipsWidget();
                m_tipContainer = new PopupWindow(this);
                m_tipContainer->setContent(tipWidget);
                m_tipContainer->resizeWithContent();
                m_tipContainer->setArrowX(tipWidget->width() / 2);
            }
            m_tipContainer->raise();
            m_tipContainer->show(QPoint(rect().center().x(), m_iconButton->parentWidget()->y()));
            break;
        case QEvent::Leave:
            if (m_tipContainer) {
                m_tipContainer->hide();
            }
            break;
        default:
            break;
        }
    }
    return QWidget::eventFilter(watched, event);
}

3. 代码性能问题

3.1 dsstestwidget.cpp 中重复调用 adjustSize()resizeWithContent()

void DssTestWidget::onClickButton()
{
    // ... 其他代码 ...
    
    netlistWidget->setParent(m_container);
    netlistWidget->adjustSize();
    m_container->setContent(netlistWidget);
    m_container->resizeWithContent();
    m_container->setArrowX(m_container->width() / 2);

    if (m_container->isVisible()) {
        // ... 其他代码 ...
    } else {
        // ... 其他代码 ...
        QWidget *content = m_container->getContent();
        content->adjustSize();
        m_container->resizeWithContent();
        m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
    }
}

问题:在函数中多次调用 adjustSize()resizeWithContent(),可能导致不必要的布局计算和重绘。

建议:减少不必要的调整大小操作:

void DssTestWidget::onClickButton()
{
    if (!m_pModule->content())
        return;

    // 左键弹出菜单
    if (!m_container) {
        m_container = new PopupWindow(this);
        connect(m_container, &PopupWindow::contentDetach, this, [this] {
            m_container->setContent(nullptr);
            m_container->hide();
        });
    }
    
    QWidget *netlistWidget = m_pModule->content();
    netlistWidget->setParent(m_container);
    m_container->setContent(netlistWidget);
    m_container->setArrowX(m_container->width() / 2);

    if (m_container->isVisible()) {
        m_container->hide();
        if (m_tipContainer) {
            m_tipContainer->toggle();
        }
    } else {
        if (m_tipContainer) {
            m_tipContainer->hide();
        }
        // 只在显示前调整大小
        m_container->resizeWithContent();
        m_container->show(QPoint(rect().width() / 2, m_iconButton->parentWidget()->y()));
    }
}

3.2 dssscreenmanager.cpp 中使用 constBegin()constEnd() 迭代器

void DssScreenManager::showWindow()
{
    for (auto it = m_screenWidget.constBegin(); it != m_screenWidget.constEnd(); it++) {
        showWindow(it.key(), it.value());
    }
}

问题:使用 constBegin()constEnd() 迭代器,但函数名是 showWindow(),暗示可能会修改对象状态。

建议:如果不需要修改容器内容,可以使用基于范围的 for 循环,更简洁高效:

void DssScreenManager::showWindow()
{
    for (auto it = m_screenWidget.constBegin(); it != m_screenWidget.constEnd(); ++it) {
        showWindow(it.key(), it.value());
    }
    
    // 或者使用基于范围的 for 循环
    // for (auto [screen, widget] : m_screenWidget.asKeyValueRange()) {
    //     showWindow(screen, widget);
    // }
}

4. 代码安全问题

4.1 dsstestwidget.cpp 中使用 static QWidget *netlistWidget 存在潜在安全问题

void DssTestWidget::onClickButton()
{
    // ... 其他代码 ...
    static QWidget *netlistWidget = nullptr;
    if (!netlistWidget) {
        netlistWidget = m_pModule->content();
    }
    // ... 其他代码 ...
}

问题:使用静态变量存储小部件指针可能导致内存泄漏和悬垂指针问题,特别是在多实例场景下。

建议:移除静态变量,每次调用函数时重新获取内容,如前面所述。

4.2 dssscreenmanager.cpp 中屏幕管理可能存在内存泄漏

void DssScreenManager::onScreenAdded(QScreen *screen)
{
    DssTestWidget *testWidget = new DssTestWidget(m_netModule);
    m_screenWidget[screen] = testWidget;
    showWindow(screen, testWidget);
}

问题:如果同一个屏幕被多次添加(虽然不太可能),会导致内存泄漏,因为旧的 DssTestWidget 不会被删除。

建议:在添加新屏幕前检查是否已存在:

void DssScreenManager::onScreenAdded(QScreen *screen)
{
    // 检查屏幕是否已存在
    if (m_screenWidget.contains(screen)) {
        return;
    }
    
    DssTestWidget *testWidget = new DssTestWidget(m_netModule);
    m_screenWidget[screen] = testWidget;
    showWindow(screen, testWidget);
}

4.3 popupwindow.cpp 中事件过滤器可能存在安全问题

bool PopupWindow::eventFilter(QObject *o, QEvent *e)
{
    if (o != getContent())
        return false;

    switch (e->type()) {
    case QEvent::Resize: {
        // FIXME: ensure position move after global mouse release event
        // 目的是为了在content resize之后重新设置弹窗的位置和大小
        if (isVisible()) {
            QMetaObject::invokeMethod(this, [ this ] {
                if (isVisible())
                    show(m_lastPoint);
            }, Qt::QueuedConnection);
        }
    }
        break;
    case QEvent::ParentChange: {
        emit contentDetach();
    }
        break;
    default:
        break;
    }

    return false;
}

问题:使用 QMetaObject::invokeMethod 和 lambda 表达式可能导致悬垂指针问题,因为 lambda 捕获了 this 指针,但在 lambda 执行前对象可能已被删除。

建议:使用 QPointer 或者在 lambda 中检查对象有效性:

bool PopupWindow::eventFilter(QObject *o, QEvent *e)
{
    if (o != getContent())
        return false;

    switch (e->type()) {
    case QEvent::Resize: {
        // FIXME: ensure position move after global mouse release event
        // 目的是为了在content resize之后重新设置弹窗的位置和大小
        if (isVisible()) {
            QPointer<PopupWindow> self(this);
            QMetaObject::invokeMethod(this, [self] {
                if (self && self->isVisible())
                    self->show(self->m_lastPoint);
            }, Qt::QueuedConnection);
        }
    }
        break;
    case QEvent::ParentChange: {
        emit contentDetach();
    }
        break;
    default:
        break;
    }

    return false;
}

5. 其他建议

5.1 main.cpp 中大量调色板设置代码可以简化

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QString applicationName("example-lock");
    if (argc > 1) {
        applicationName = QString("%1-%2").arg("example").arg(argv[1]);
    }

    app.setApplicationName(applicationName);

    DGuiApplicationHelper::instance()->setPaletteType(DGuiApplicationHelper::LightType);
    DPalette pa = DGuiApplicationHelper::instance()->applicationPalette();
    pa.setColor(QPalette::Normal, DPalette::WindowText, QColor("#FFFFFF"));
    pa.setColor(QPalette::Normal, DPalette::Text, QColor("#FFFFFF"));
    pa.setColor(QPalette::Normal, DPalette::AlternateBase, QColor(0, 0, 0, 76));
    pa.setColor(QPalette::Normal, DPalette::Button, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::Light, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::Dark, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::ButtonText, QColor("#FFFFFF"));
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::WindowText, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::Text, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::AlternateBase, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::Button, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::Light, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::Dark, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::generatePaletteColor(pa, DPalette::ButtonText, DGuiApplicationHelper::LightType);
    DGuiApplicationHelper::instance()->setApplicationPalette(pa);

    DssScreenManager dssManager;
    dssManager.showWindow();

    return app.exec();
}

建议:将调色板设置代码封装到一个单独的函数中,提高代码可读性和可维护性:

void setupApplicationPalette()
{
    DGuiApplicationHelper::instance()->setPaletteType(DGuiApplicationHelper::LightType);
    DPalette pa = DGuiApplicationHelper::instance()->applicationPalette();
    
    // 设置颜色
    pa.setColor(QPalette::Normal, DPalette::WindowText, QColor("#FFFFFF"));
    pa.setColor(QPalette::Normal, DPalette::Text, QColor("#FFFFFF"));
    pa.setColor(QPalette::Normal, DPalette::AlternateBase, QColor(0, 0, 0, 76));
    pa.setColor(QPalette::Normal, DPalette::Button, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::Light, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::Dark, QColor(255, 255, 255, 76));
    pa.setColor(QPalette::Normal, DPalette::ButtonText, QColor("#FFFFFF"));
    
    // 生成调色板颜色
    const auto roles = {DPalette::WindowText, DPalette::Text, DPalette::AlternateBase,
                       DPalette::Button, DPalette::Light, DPalette::Dark, DPalette::ButtonText};
    for (auto role : roles) {
        DGuiApplicationHelper::generatePaletteColor(pa, role, DGuiApplicationHelper::LightType);
    }
    
    DGuiApplicationHelper::instance()->setApplicationPalette(pa);
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QString applicationName("example-lock");
    if (argc > 1) {
        applicationName = QString("%1-%2").arg("example").arg(argv[1]);
    }

    app.setApplicationName(applicationName);
    setupApplicationPalette();

    DssScreenManager dssManager;
    dssManager.showWindow();

    return app.exec();
}

总结

以上是对代码的全面审查,主要问题集中在静态变量的使用、内存管理、事件处理和代码组织方面。建议优先解决静态变量和内存管理问题,因为这些可能导致程序崩溃或内存泄漏。其他问题可以逐步改进,以提高代码质量和可维护性。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caixr23, ut003640

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

@ut003640
Copy link
Contributor Author

ut003640 commented Feb 5, 2026

/forcemerge

@deepin-bot
Copy link
Contributor

deepin-bot bot commented Feb 5, 2026

This pr force merged! (status: unstable)

@deepin-bot deepin-bot bot merged commit da30b7a into linuxdeepin:master Feb 5, 2026
16 of 18 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.

3 participants