Skip to content

fix: Speaker Display Anomaly#3012

Merged
deepin-bot[bot] merged 2 commits intolinuxdeepin:masterfrom
JWWTSL:master
Feb 6, 2026
Merged

fix: Speaker Display Anomaly#3012
deepin-bot[bot] merged 2 commits intolinuxdeepin:masterfrom
JWWTSL:master

Conversation

@JWWTSL
Copy link
Contributor

@JWWTSL JWWTSL commented Feb 6, 2026

log: The audio module's output device list/dropdown relies on SoundWorker::cardsChanged() for refreshes. However, in certain backend implementations, plugging/unplugging wired headphones primarily triggers either the DBus port enabled state change (PortEnabledChanged) or solely CardsChanged events. Previously, only CardsWithoutUnavailableChanged was monitored, causing the UI to fail to refresh and persistently display “Speaker”.

pms: bug-345709

Summary by Sourcery

Improve audio device hotplug handling so the output device list and labels stay in sync with actual hardware, especially when plugging or unplugging wired headphones.

Bug Fixes:

  • Ensure the sound device list refreshes on additional DBus events such as CardsChanged, SinksChanged, SourcesChanged, and PortEnabledChanged so the UI no longer gets stuck showing the speaker output.
  • Use Qt multimedia device hotplug notifications as a fallback when DBus signals are missing to keep the audio device list current.
  • Adjust port filtering so that when headphones are detected as plugged in or active on a laptop audio card, only the headphone output is shown instead of simultaneously displaying speakers.

JWWTSL and others added 2 commits February 6, 2026 13:13
log: The audio module's output device list/dropdown relies on SoundWorker::cardsChanged() for refreshes. However, in certain backend implementations, plugging/unplugging wired headphones primarily triggers either the DBus port enabled state change (PortEnabledChanged) or solely CardsChanged events. Previously, only CardsWithoutUnavailableChanged was monitored, causing the UI to fail to refresh and persistently display “Speaker”.

pms: bug-345709
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码主要是在处理音频设备的热插拔、状态变更以及音频端口的过滤逻辑。整体逻辑较为完善,考虑了多种后端(如PipeWire、PulseAudio)和不同硬件(蓝牙、有线耳机)的场景。以下是对代码的详细审查和改进建议:

1. 语法逻辑

优点:

  • 使用了Lambda表达式来封装refreshCards逻辑,减少了代码重复。
  • 使用了Q_UNUSED宏来消除未使用参数的警告,符合Qt编程规范。
  • PortEnabledChanged信号的处理考虑了端口尚未存在的情况,逻辑严谨。

改进建议:

  • Lambda捕获问题:在PortEnabledChanged的槽函数中,[this, refreshCards]捕获了thisrefreshCards。由于refreshCards本身已经捕获了this,这里存在潜在的悬空引用风险。建议改为:
    connect(m_soundDBusInter, &SoundDBusProxy::PortEnabledChanged, this,
            [this](uint cardId, const QString &portId, bool enabled) {
                Port *port = m_model->findPort(portId, cardId);
                if (!port) {
                    // If the port isn't present yet (e.g. previously unavailable), refresh card snapshot.
                    m_model->setAudioCards(m_soundDBusInter->cardsWithoutUnavailable());
                    return;
                }
                port->setEnabled(enabled);
                m_model->updatePortCombo();
                m_model->updateSoundDeviceModel(port);
                m_model->updateActiveComboIndex();
            });

2. 代码质量

优点:

  • 注释清晰,解释了为什么需要这些fallback逻辑。
  • 使用了isHeadphonesPort Lambda函数来封装判断逻辑,提高了可读性。

改进建议:

  • 魔法数字portAvai == 2.0portAvai == 0.0使用了魔法数字,建议定义枚举或常量:
    enum PortAvailability {
        Unknown = 0,
        NotAvailable = 1,
        Available = 2
    };
    然后使用portAvai == PortAvailability::Available代替。
  • 字符串比较isHeadphonesPort中使用了多个字符串比较,可以考虑使用QSetQStringList来存储需要匹配的字符串:
    const auto isHeadphonesPort = [](const QJsonObject &jPort) -> bool {
        const QString portId = jPort["Name"].toString();
        const QString desc = jPort["Description"].toString();
        static const QStringList headphoneKeywords = {
            "headphone", "耳机"
        };
        return std::any_of(headphoneKeywords.begin(), headphoneKeywords.end(),
                          [&portId, &desc](const QString &keyword) {
                              return portId.contains(keyword, Qt::CaseInsensitive)
                                  || desc.contains(keyword, Qt::CaseInsensitive);
                          });
    };

3. 代码性能

改进建议:

  • 频繁的字符串比较isHeadphonesPort函数在每次端口遍历时都会执行,且包含多个字符串比较。如果端口数量较多,可能会影响性能。建议:
    • 使用QString::containsQt::CaseInsensitive标志时,可以考虑将portIddesc转换为小写后比较,减少大小写转换的开销。
    • 如果isHeadphonesPort会被频繁调用,可以考虑缓存结果。
  • 不必要的刷新refreshCards在多个信号触发时都会调用,可能会导致频繁的UI更新。建议:
    • 添加防抖逻辑(如使用QTimer::singleShot)来避免短时间内多次刷新:
      QTimer::singleShot(100, this, [this] {
          if (!m_soundDBusInter)
              return;
          m_model->setAudioCards(m_soundDBusInter->cardsWithoutUnavailable());
      });

4. 代码安全

改进建议:

  • 空指针检查m_soundDBusInterm_model的使用前都应确保非空。虽然refreshCards中已经检查了m_soundDBusInter,但在其他地方(如PortEnabledChanged的槽函数)也应检查。
  • JSON解析安全jPort["Available"].toDouble()等操作假设JSON中一定包含这些字段。如果后端返回的JSON格式不完整,可能会导致解析错误。建议:
    • 添加默认值:
      const double portAvai = jPort.value("Available").toDouble(0.0);
    • 或使用QJsonObject::contains检查字段是否存在:
      if (!jPort.contains("Available")) {
          qWarning() << "Port JSON missing 'Available' field";
          continue;
      }

5. 其他建议

  • 国际化支持isHeadphonesPort中硬编码了"耳机",如果未来需要支持更多语言,建议使用国际化机制(如QObject::tr)。
  • 日志记录:在关键逻辑(如端口过滤、刷新卡片)处添加日志,便于调试和问题排查:
    qInfo() << "Filtering ports due to headphones plugged. Card:" << cardId;

总结

这段代码整体质量较高,但在性能优化、安全性和可维护性方面仍有改进空间。建议重点关注:

  1. 消除Lambda捕获的潜在风险。
  2. 使用枚举替代魔法数字。
  3. 优化字符串比较逻辑。
  4. 添加防抖机制避免频繁刷新。
  5. 增强JSON解析的健壮性。

@sourcery-ai
Copy link

sourcery-ai bot commented Feb 6, 2026

Reviewer's Guide

Wire up additional DBus and Qt multimedia signals to keep the audio device list in sync on hotplug events and adjust port filtering so that when headphones are plugged, only the headphone output is shown instead of speakers on multi-port laptop cards.

Sequence diagram for headphone hotplug refresh and UI update

sequenceDiagram
    actor User
    participant HeadphoneJack as HeadphoneJack
    participant AudioBackend as AudioBackend
    participant SoundDBusProxy as SoundDBusProxy
    participant QMediaDevices as QMediaDevices
    participant SoundWorker as SoundWorker
    participant SoundModel as SoundModel
    participant AudioDeviceDropdown as AudioDeviceDropdown

    User->>HeadphoneJack: Plug headphones
    HeadphoneJack->>AudioBackend: Electrical state change
    AudioBackend-->>SoundDBusProxy: PortEnabledChanged(cardId, portId, enabled)

    SoundDBusProxy-->>SoundWorker: PortEnabledChanged(cardId, portId, enabled)
    activate SoundWorker
    SoundWorker->>SoundModel: findPort(portId, cardId)
    alt Port exists
        SoundWorker->>SoundModel: Port.setEnabled(enabled)
        SoundWorker->>SoundModel: updatePortCombo()
        SoundWorker->>SoundModel: updateSoundDeviceModel(port)
        SoundWorker->>SoundModel: updateActiveComboIndex()
    else Port missing
        SoundWorker->>SoundDBusProxy: cardsWithoutUnavailable()
        SoundDBusProxy-->>SoundWorker: cardsSnapshot
        SoundWorker->>SoundModel: setAudioCards(cardsSnapshot)
        SoundModel->>SoundWorker: audioCardsChanged(cards)
        SoundWorker->>SoundWorker: cardsChanged(cards)
        SoundWorker->>SoundModel: updatePortCombo()
        SoundWorker->>SoundModel: updateSoundDeviceModel(port)
        SoundWorker->>SoundModel: updateActiveComboIndex()
    end
    SoundModel-->>AudioDeviceDropdown: modelChanged
    deactivate SoundWorker

    Note over SoundWorker,SoundModel: Inside cardsChanged(cards), headphone ports are detected and non-headphone
    Note over SoundWorker,SoundModel: output ports are filtered out when headphones are plugged so dropdown shows Headphones only

    rect rgb(230, 245, 255)
    AudioBackend-->>SoundDBusProxy: SinksChanged or SourcesChanged
    SoundDBusProxy-->>SoundWorker: SinksChanged(value) or SourcesChanged(value)
    SoundWorker->>SoundDBusProxy: cardsWithoutUnavailable()
    SoundDBusProxy-->>SoundWorker: cardsSnapshot
    SoundWorker->>SoundModel: setAudioCards(cardsSnapshot)
    end

    rect rgb(235, 255, 235)
    QMediaDevices-->>SoundWorker: audioOutputsChanged
    SoundWorker->>SoundDBusProxy: cardsWithoutUnavailable()
    SoundDBusProxy-->>SoundWorker: cardsSnapshot
    SoundWorker->>SoundModel: setAudioCards(cardsSnapshot)
    end
Loading

Updated class diagram for SoundWorker sound hotplug handling and port filtering

classDiagram
    class SoundWorker {
        -SoundModel* m_model
        -SoundDBusProxy* m_soundDBusInter
        -QMediaDevices* m_mediaDevices
        -QTimer* m_pingTimer
        -QTimer* m_playAnimationTime
        -uint m_activeOutputCard
        -QString m_activeSinkPort
        +SoundWorker(SoundModel* model, QObject* parent)
        +initConnect() void
        +cardsChanged(QString cards) void
    }

    class SoundModel {
        +setAudioCards(QJsonArray cards) void
        +findPort(QString portId, uint cardId) Port*
        +updatePortCombo() void
        +updateSoundDeviceModel(Port* port) void
        +updateActiveComboIndex() void
        +setMaxUIVolume(int volume) void
        +setIncreaseVolume(bool enable) void
        +setReduceNoise(bool enable) void
        +setPausePlayer(bool pause) void
        +setBluetoothAudioModeOpts(QVariant opts) void
        +setAutoSwitchSpeaker(bool enable) void
        +setEnableSoundEffect(bool enable) void
        +setIsLaptop(bool isLaptop) void
        +signals audioCardsChanged(QString cards)
        +signals defaultSinkChanged()
        +signals defaultSourceChanged()
    }

    class SoundDBusProxy {
        +cardsWithoutUnavailable() QJsonArray
        +signal CardsWithoutUnavailableChanged(QJsonArray cards)
        +signal CardsChanged(QJsonArray cards)
        +signal SinksChanged(QList~QDBusObjectPath~ sinks)
        +signal SourcesChanged(QList~QDBusObjectPath~ sources)
        +signal PortEnabledChanged(uint cardId, QString portId, bool enabled)
        +signal MaxUIVolumeChanged(int volume)
        +signal IncreaseVolumeChanged(bool enable)
        +signal ReduceNoiseChanged(bool enable)
        +signal PausePlayerChanged(bool pause)
        +signal BluetoothAudioModeOptsChanged(QVariant opts)
        +signal AutoSwitchSpeakerChanged(bool enable)
        +signal EnableSoundEffectChanged(bool enable)
        +signal HasBatteryChanged(bool hasBattery)
        +Tick() void
    }

    class QMediaDevices {
        +signal audioInputsChanged()
        +signal audioOutputsChanged()
    }

    class Port {
        <<QObject>>
        +enum Direction
        +setEnabled(bool enabled) void
        +isEnabled() bool
        +getDirection() Direction
        +getId() QString
        +getCardId() uint
    }

    class AudioDeviceDropdown {
        +setModel(SoundModel* model) void
        +refreshView() void
    }

    SoundWorker --> SoundModel : uses
    SoundWorker --> SoundDBusProxy : calls
    SoundWorker --> QMediaDevices : observes
    SoundModel --> Port : contains
    AudioDeviceDropdown --> SoundModel : reads

    SoundDBusProxy ..> SoundWorker : emits_CardsWithoutUnavailableChanged
    SoundDBusProxy ..> SoundWorker : emits_CardsChanged
    SoundDBusProxy ..> SoundWorker : emits_SinksChanged
    SoundDBusProxy ..> SoundWorker : emits_SourcesChanged
    SoundDBusProxy ..> SoundWorker : emits_PortEnabledChanged

    QMediaDevices ..> SoundWorker : emits_audioInputsChanged
    QMediaDevices ..> SoundWorker : emits_audioOutputsChanged

    SoundModel ..> AudioDeviceDropdown : notifies_modelChanged
Loading

File-Level Changes

Change Details Files
Add a shared refreshCards helper and connect multiple backend hotplug signals to rebuild the audio card/port list when devices change.
  • Introduce a local refreshCards lambda in SoundWorker::initConnect to repopulate SoundModel audio cards from SoundDBusProxy::cardsWithoutUnavailable().
  • Connect SoundDBusProxy::CardsChanged directly to SoundModel::setAudioCards as a backend fallback for card changes.
  • Handle SinksChanged and SourcesChanged by invoking refreshCards via lambda slots that ignore the signal payload.
  • Connect QMediaDevices::audioInputsChanged and audioOutputsChanged signals to refreshCards as a non-DBus hotplug fallback.
src/plugin-sound/operation/soundworker.cpp
Update handling of PortEnabledChanged so that port enable state and UI models stay in sync and missing ports trigger a full card refresh.
  • Connect SoundDBusProxy::PortEnabledChanged to a lambda that finds the affected Port in the model, refreshing cards if the port is not found.
  • When the port exists, update its enabled state and then refresh the relevant UI models (port combo, device model, active combo index).
src/plugin-sound/operation/soundworker.cpp
Adjust cardsChanged processing to prefer headphone outputs on laptop internal cards when headphones are plugged, hiding other output ports such as speakers.
  • Add a helper lambda isHeadphonesPort that classifies ports as headphones based on ID or description (including Chinese term for headphones).
  • Determine a per-card flag shouldFilterToHeadphonesOut by scanning ports: treat headphones as active when explicitly Available==2 or when the port matches the active sink port on the active output card.
  • During port enumeration, compute the port direction and, when shouldFilterToHeadphonesOut is true, skip non-headphone output ports so they are excluded from the UI while headphones are active.
src/plugin-sound/operation/soundworker.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 1 issue, and left some high level feedback:

  • Consider extracting the refreshCards lambda into a small private helper method on SoundWorker so it can be reused more clearly across different signal handlers and more easily extended/debugged in the future.
  • In cardsChanged, the headphone detection and filtering logic does multiple toString()/toDouble() calls on the same QJsonObject; caching these values per iteration (and possibly encapsulating isHeadphonesPort and the shouldFilterToHeadphonesOut computation) would improve both readability and efficiency.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the `refreshCards` lambda into a small private helper method on `SoundWorker` so it can be reused more clearly across different signal handlers and more easily extended/debugged in the future.
- In `cardsChanged`, the headphone detection and filtering logic does multiple `toString()`/`toDouble()` calls on the same `QJsonObject`; caching these values per iteration (and possibly encapsulating `isHeadphonesPort` and the `shouldFilterToHeadphonesOut` computation) would improve both readability and efficiency.

## Individual Comments

### Comment 1
<location> `src/plugin-sound/operation/soundworker.cpp:55` </location>
<code_context>

 void SoundWorker::initConnect()
 {
+    auto refreshCards = [this] {
+        if (!m_soundDBusInter)
+            return;
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting the new lambdas and headphone-detection logic into small named helper methods/slots so the connection setup and cardsChanged() body remain simple and declarative.

You can reduce the added complexity substantially by extracting the inline lambdas into small, named member functions/slots and helpers, while keeping behavior identical.

### 1. `initConnect()` lambdas

`initConnect()` is now mixing connection wiring with non‑trivial logic in lambdas (`refreshCards`, `PortEnabledChanged`, `QMediaDevices` connections).

**Suggestion: extract `refreshCards` and `PortEnabledChanged` logic into private member functions/slots.**

```cpp
// soundworker.h
private slots:
    void refreshCardsSnapshot();
    void onPortEnabledChanged(uint cardId, const QString &portId, bool enabled);
```

```cpp
// soundworker.cpp
void SoundWorker::refreshCardsSnapshot()
{
    if (!m_soundDBusInter)
        return;

    m_model->setAudioCards(m_soundDBusInter->cardsWithoutUnavailable());
}

void SoundWorker::onPortEnabledChanged(uint cardId, const QString &portId, bool enabled)
{
    Port *port = m_model->findPort(portId, cardId);
    if (!port) {
        refreshCardsSnapshot();
        return;
    }

    port->setEnabled(enabled);
    m_model->updatePortCombo();
    m_model->updateSoundDeviceModel(port);
    m_model->updateActiveComboIndex();
}
```

Then `initConnect()` becomes declarative again:

```cpp
void SoundWorker::initConnect()
{
    // ...
    connect(m_soundDBusInter, &SoundDBusProxy::CardsWithoutUnavailableChanged,
            m_model, &SoundModel::setAudioCards);
    connect(m_soundDBusInter, &SoundDBusProxy::CardsChanged,
            m_model, &SoundModel::setAudioCards);

    connect(m_soundDBusInter, &SoundDBusProxy::SinksChanged,
            this, [this](const QList<QDBusObjectPath> &) { refreshCardsSnapshot(); });
    connect(m_soundDBusInter, &SoundDBusProxy::SourcesChanged,
            this, [this](const QList<QDBusObjectPath> &) { refreshCardsSnapshot(); });

    connect(m_soundDBusInter, &SoundDBusProxy::PortEnabledChanged,
            this, &SoundWorker::onPortEnabledChanged);

    connect(m_mediaDevices, &QMediaDevices::audioInputsChanged,
            this, &SoundWorker::refreshCardsSnapshot);
    connect(m_mediaDevices, &QMediaDevices::audioOutputsChanged,
            this, &SoundWorker::refreshCardsSnapshot);
    // ...
}
```

This keeps all current behavior but moves the business logic out of the constructor helper.

### 2. Headphone filtering logic in `cardsChanged()`

The new headphone logic adds an inline `isHeadphonesPort` lambda and a two‑pass loop structure. This is a good candidate for small helpers to keep `cardsChanged()` focused.

**Suggestion: extract headphone helpers and reuse them in a single, simple pattern.**

```cpp
// soundworker.h
private:
    bool isHeadphonesPort(const QJsonObject &jPort) const;
    bool shouldFilterToHeadphonesOut(const QJsonArray &jPorts, uint cardId) const;
```

```cpp
// soundworker.cpp
bool SoundWorker::isHeadphonesPort(const QJsonObject &jPort) const
{
    const QString portId = jPort["Name"].toString();
    const QString desc   = jPort["Description"].toString();
    return portId.contains("headphone", Qt::CaseInsensitive)
        || desc.contains("headphone", Qt::CaseInsensitive)
        || desc.contains(QStringLiteral("耳机"));
}

bool SoundWorker::shouldFilterToHeadphonesOut(const QJsonArray &jPorts, uint cardId) const
{
    for (const QJsonValue &pV : jPorts) {
        const QJsonObject jPort = pV.toObject();
        const auto dir = Port::Direction(jPort["Direction"].toDouble());
        if (dir != Port::Out || !isHeadphonesPort(jPort))
            continue;

        const double portAvai = jPort["Available"].toDouble();
        const QString portId  = jPort["Name"].toString();

        if (portAvai == 2.0) // Available
            return true;

        if (cardId == m_activeOutputCard && portId == m_activeSinkPort)
            return true;
    }
    return false;
}
```

Then in `cardsChanged()`:

```cpp
const bool filterToHeadphones = shouldFilterToHeadphonesOut(jPorts, cardId);

for (QJsonValue pV : jPorts) {
    QJsonObject jPort = pV.toObject();
    const double portAvai = jPort["Available"].toDouble();
    if (portAvai != 2.0 && portAvai != 0.0)
        continue;

    const auto dir = Port::Direction(jPort["Direction"].toDouble());
    if (filterToHeadphones && dir == Port::Out && !isHeadphonesPort(jPort))
        continue;

    const QString portId   = jPort["Name"].toString();
    const QString portName = jPort["Description"].toString();
    const bool isEnabled   = jPort["Enabled"].toBool();
    const bool isBluetooth = jPort["Bluetooth"].toBool();

    // existing Port creation/update logic...
}
```

This preserves all the new behavior (two‑phase decision: first decide if we filter, then apply it) but reduces nesting in `cardsChanged()` and clarifies intent via named helpers.
</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.


void SoundWorker::initConnect()
{
auto refreshCards = [this] {
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 extracting the new lambdas and headphone-detection logic into small named helper methods/slots so the connection setup and cardsChanged() body remain simple and declarative.

You can reduce the added complexity substantially by extracting the inline lambdas into small, named member functions/slots and helpers, while keeping behavior identical.

1. initConnect() lambdas

initConnect() is now mixing connection wiring with non‑trivial logic in lambdas (refreshCards, PortEnabledChanged, QMediaDevices connections).

Suggestion: extract refreshCards and PortEnabledChanged logic into private member functions/slots.

// soundworker.h
private slots:
    void refreshCardsSnapshot();
    void onPortEnabledChanged(uint cardId, const QString &portId, bool enabled);
// soundworker.cpp
void SoundWorker::refreshCardsSnapshot()
{
    if (!m_soundDBusInter)
        return;

    m_model->setAudioCards(m_soundDBusInter->cardsWithoutUnavailable());
}

void SoundWorker::onPortEnabledChanged(uint cardId, const QString &portId, bool enabled)
{
    Port *port = m_model->findPort(portId, cardId);
    if (!port) {
        refreshCardsSnapshot();
        return;
    }

    port->setEnabled(enabled);
    m_model->updatePortCombo();
    m_model->updateSoundDeviceModel(port);
    m_model->updateActiveComboIndex();
}

Then initConnect() becomes declarative again:

void SoundWorker::initConnect()
{
    // ...
    connect(m_soundDBusInter, &SoundDBusProxy::CardsWithoutUnavailableChanged,
            m_model, &SoundModel::setAudioCards);
    connect(m_soundDBusInter, &SoundDBusProxy::CardsChanged,
            m_model, &SoundModel::setAudioCards);

    connect(m_soundDBusInter, &SoundDBusProxy::SinksChanged,
            this, [this](const QList<QDBusObjectPath> &) { refreshCardsSnapshot(); });
    connect(m_soundDBusInter, &SoundDBusProxy::SourcesChanged,
            this, [this](const QList<QDBusObjectPath> &) { refreshCardsSnapshot(); });

    connect(m_soundDBusInter, &SoundDBusProxy::PortEnabledChanged,
            this, &SoundWorker::onPortEnabledChanged);

    connect(m_mediaDevices, &QMediaDevices::audioInputsChanged,
            this, &SoundWorker::refreshCardsSnapshot);
    connect(m_mediaDevices, &QMediaDevices::audioOutputsChanged,
            this, &SoundWorker::refreshCardsSnapshot);
    // ...
}

This keeps all current behavior but moves the business logic out of the constructor helper.

2. Headphone filtering logic in cardsChanged()

The new headphone logic adds an inline isHeadphonesPort lambda and a two‑pass loop structure. This is a good candidate for small helpers to keep cardsChanged() focused.

Suggestion: extract headphone helpers and reuse them in a single, simple pattern.

// soundworker.h
private:
    bool isHeadphonesPort(const QJsonObject &jPort) const;
    bool shouldFilterToHeadphonesOut(const QJsonArray &jPorts, uint cardId) const;
// soundworker.cpp
bool SoundWorker::isHeadphonesPort(const QJsonObject &jPort) const
{
    const QString portId = jPort["Name"].toString();
    const QString desc   = jPort["Description"].toString();
    return portId.contains("headphone", Qt::CaseInsensitive)
        || desc.contains("headphone", Qt::CaseInsensitive)
        || desc.contains(QStringLiteral("耳机"));
}

bool SoundWorker::shouldFilterToHeadphonesOut(const QJsonArray &jPorts, uint cardId) const
{
    for (const QJsonValue &pV : jPorts) {
        const QJsonObject jPort = pV.toObject();
        const auto dir = Port::Direction(jPort["Direction"].toDouble());
        if (dir != Port::Out || !isHeadphonesPort(jPort))
            continue;

        const double portAvai = jPort["Available"].toDouble();
        const QString portId  = jPort["Name"].toString();

        if (portAvai == 2.0) // Available
            return true;

        if (cardId == m_activeOutputCard && portId == m_activeSinkPort)
            return true;
    }
    return false;
}

Then in cardsChanged():

const bool filterToHeadphones = shouldFilterToHeadphonesOut(jPorts, cardId);

for (QJsonValue pV : jPorts) {
    QJsonObject jPort = pV.toObject();
    const double portAvai = jPort["Available"].toDouble();
    if (portAvai != 2.0 && portAvai != 0.0)
        continue;

    const auto dir = Port::Direction(jPort["Direction"].toDouble());
    if (filterToHeadphones && dir == Port::Out && !isHeadphonesPort(jPort))
        continue;

    const QString portId   = jPort["Name"].toString();
    const QString portName = jPort["Description"].toString();
    const bool isEnabled   = jPort["Enabled"].toBool();
    const bool isBluetooth = jPort["Bluetooth"].toBool();

    // existing Port creation/update logic...
}

This preserves all the new behavior (two‑phase decision: first decide if we filter, then apply it) but reduces nesting in cardsChanged() and clarifies intent via named helpers.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caixr23, 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

@JWWTSL
Copy link
Contributor Author

JWWTSL commented Feb 6, 2026

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Feb 6, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot bot merged commit c0856f1 into linuxdeepin:master Feb 6, 2026
14 of 17 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