dde-dock/plugins/sound/soundapplet.cpp
范朋程 2d958dd670 chore: V23接口改造适配
V23接口改造适配

Log: V23接口改造适配
Influence: 无
Task: https://pms.uniontech.com/task-view-207483.html
Change-Id: Ide530c023ea41f86fad2e8001ec67f1afaa897ab
2022-12-06 14:40:35 +08:00

647 lines
22 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2011 ~ 2018 Deepin Technology Co., Ltd.
*
* Author: sbw <sbw@sbw.so>
*
* Maintainer: sbw <sbw@sbw.so>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "soundapplet.h"
#include "sounddeviceport.h"
#include "util/horizontalseperator.h"
#include "../widgets/tipswidget.h"
#include "../frame/util/imageutil.h"
#include "util/utils.h"
#include <DGuiApplicationHelper>
#include <DApplication>
#include <DStandardItem>
#include <DFontSizeManager>
#include <DApplicationHelper>
#include <QLabel>
#include <QIcon>
#include <QScrollBar>
#include <QPainter>
#include <QListIterator>
#define SEPARATOR_HEIGHT 2
#define WIDTH 260
#define MAX_HEIGHT 300
#define ICON_SIZE 24
#define ITEM_HEIGHT 24
#define ITEM_SPACING 5
#define DEVICE_SPACING 10
#define SLIDER_HIGHT 70
#define TITLE_HEIGHT 46
#define GSETTING_SOUND_OUTPUT_SLIDER "soundOutputSlider"
DWIDGET_USE_NAMESPACE
DGUI_USE_NAMESPACE
using namespace Dock;
SoundApplet::SoundApplet(QWidget *parent)
: QScrollArea(parent)
, m_centralWidget(new QWidget(this))
, m_volumeIconMin(new QLabel(this))
, m_volumeIconMax(new QLabel(this))
, m_volumeSlider(new VolumeSlider(this))
, m_soundShow(new QLabel(this))
, m_deviceLabel(new QLabel(this))
, m_seperator(new HorizontalSeperator(this))
, m_secondSeperator(new HorizontalSeperator(this))
, m_audioInter(new DBusAudio("org.deepin.dde.Audio1", "/org/deepin/dde/Audio1", QDBusConnection::sessionBus(), this))
, m_defSinkInter(nullptr)
, m_listView(new DListView(this))
, m_model(new QStandardItemModel(m_listView))
, m_deviceInfo("")
, m_lastPort(nullptr)
, m_gsettings(Utils::ModuleSettingsPtr("sound", QByteArray(), this))
{
initUi();
m_volumeIconMin->installEventFilter(this);
}
void SoundApplet::initUi()
{
m_listView->setFrameShape(QFrame::NoFrame);
m_listView->setEditTriggers(DListView::NoEditTriggers);
m_listView->setSelectionMode(QAbstractItemView::NoSelection);
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_listView->setBackgroundType(DStyledItemDelegate::NoBackground);
m_listView->setItemRadius(0);
m_listView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
m_listView->setFixedHeight(0);
m_listView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_listView->setModel(m_model);
m_centralWidget->setAccessibleName("volumn-centralwidget");
m_volumeIconMin->setAccessibleName("volume-button");
m_volumeIconMax->setAccessibleName("volume-iconmax");
m_volumeSlider->setAccessibleName("volume-slider");
m_soundShow->setAccessibleName("volume-soundtips");
horizontalScrollBar()->setAccessibleName("volume-horizontalscrollbar");
verticalScrollBar()->setAccessibleName("volume-verticalscrollbar");
m_volumeIconMin->setFixedSize(ICON_SIZE, ICON_SIZE);
m_volumeIconMax->setFixedSize(ICON_SIZE, ICON_SIZE);
m_soundShow->setText(QString("%1%").arg(0));
m_soundShow->setFixedHeight(TITLE_HEIGHT);
m_soundShow->setForegroundRole(QPalette::BrightText);
DFontSizeManager::instance()->bind(m_soundShow, DFontSizeManager::T8, QFont::Medium);
m_deviceLabel->setText(tr("Device"));
m_deviceLabel->setFixedHeight(TITLE_HEIGHT);
m_deviceLabel->setForegroundRole(QPalette::BrightText);
DFontSizeManager::instance()->bind(m_deviceLabel, DFontSizeManager::T4, QFont::Medium);
m_volumeSlider->setFixedHeight(SLIDER_HIGHT);
m_volumeSlider->setMinimum(0);
m_volumeSlider->setMaximum(m_audioInter->maxUIVolume() * 100.0f);
// 标题部分
QHBoxLayout *deviceLayout = new QHBoxLayout;
deviceLayout->setSpacing(0);
deviceLayout->setMargin(0);
deviceLayout->setContentsMargins(20, 0, 10, 0);
deviceLayout->addWidget(m_deviceLabel, 0, Qt::AlignLeft);
deviceLayout->addWidget(m_soundShow, 0, Qt::AlignRight);
BackgroundWidget *deviceWidget = new BackgroundWidget(this);
deviceWidget->setLayout(deviceLayout);
// 音量滑动条
QHBoxLayout *volumeCtrlLayout = new QHBoxLayout;
volumeCtrlLayout->setSpacing(0);
volumeCtrlLayout->setMargin(0);
volumeCtrlLayout->setContentsMargins(12, 0, 12, 0);
volumeCtrlLayout->addWidget(m_volumeIconMin);
volumeCtrlLayout->addWidget(m_volumeSlider);
volumeCtrlLayout->addWidget(m_volumeIconMax);
BackgroundWidget *volumnWidget = new BackgroundWidget(this);
volumnWidget->setLayout(volumeCtrlLayout);
m_centralLayout = new QVBoxLayout(this);
m_centralLayout->setContentsMargins(0, 0, 0, 0);
m_centralLayout->setMargin(0);
m_centralLayout->setSpacing(0);
m_centralLayout->addWidget(deviceWidget);
m_centralLayout->addWidget(m_seperator);
m_centralLayout->addWidget(volumnWidget);
// 需要判断是否有声音端口
m_centralLayout->addWidget(m_secondSeperator);
m_centralLayout->addWidget(m_listView);
m_centralWidget->setLayout(m_centralLayout);
m_centralWidget->setFixedWidth(WIDTH);
m_centralWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
setFixedWidth(WIDTH);
setWidget(m_centralWidget);
setContentsMargins(0, 0, 0, 0);
setFrameShape(QFrame::NoFrame);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_centralWidget->setAutoFillBackground(false);
viewport()->setAutoFillBackground(false);
m_listView->setItemDelegate(new DStyledItemDelegate(m_listView));
m_secondSeperator->setVisible(m_model->rowCount() > 1);
updateVolumeSliderStatus(Utils::SettingValue("com.deepin.dde.dock.module.sound", QByteArray(), "Enabled").toString());
connect(m_gsettings, &QGSettings::changed, [ = ] (const QString &key) {
if (key == GSETTING_SOUND_OUTPUT_SLIDER) {
updateVolumeSliderStatus(m_gsettings->get(GSETTING_SOUND_OUTPUT_SLIDER).toString());
}
});
connect(qApp, &QGuiApplication::fontChanged, this, &SoundApplet::updateListHeight);
connect(m_volumeSlider, &VolumeSlider::valueChanged, this, &SoundApplet::volumeSliderValueChanged);
connect(m_audioInter, &DBusAudio::DefaultSinkChanged, this, &SoundApplet::onDefaultSinkChanged);
connect(m_audioInter, &DBusAudio::IncreaseVolumeChanged, this, &SoundApplet::increaseVolumeChanged);
connect(m_audioInter, &DBusAudio::PortEnabledChanged, [this](uint cardId, QString portId) {
portEnableChange(cardId, portId);
});;
connect(m_listView, &DListView::clicked, this, [this](const QModelIndex & idx) {
const SoundDevicePort *port = m_listView->model()->data(idx, Qt::WhatsThisPropertyRole).value<const SoundDevicePort *>();
if (port) {
m_audioInter->SetPort(port->cardId(), port->id(), int(port->direction()));
//手动勾选时启用设备
m_audioInter->SetPortEnabled(port->cardId(), port->id(), true);
}
});
connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::themeTypeChanged, this, &SoundApplet::refreshIcon);
connect(qApp, &DApplication::iconThemeChanged, this, &SoundApplet::refreshIcon);
QDBusConnection::sessionBus().connect("org.deepin.dde.Audio1", "/org/deepin/dde/Audio1", "org.freedesktop.DBus.Properties"
,"PropertiesChanged", "sa{sv}as", this, SLOT(haldleDbusSignal(QDBusMessage)));
QMetaObject::invokeMethod(this, "onDefaultSinkChanged", Qt::QueuedConnection);
refreshIcon();
updateCradsInfo();
}
int SoundApplet::volumeValue() const
{
return m_volumeSlider->value();
}
int SoundApplet::maxVolumeValue() const
{
return m_volumeSlider->maximum();
}
VolumeSlider *SoundApplet::mainSlider()
{
return m_volumeSlider;
}
void SoundApplet::onDefaultSinkChanged()
{
//防止手动切换设备,与后端交互时,获取到多个信号,设备切换多次,造成混乱
QThread::msleep(200);
if (m_defSinkInter) {
delete m_defSinkInter;
m_defSinkInter = nullptr;
}
const QDBusObjectPath defSinkPath = m_audioInter->defaultSink();
m_defSinkInter = new DBusSink("org.deepin.dde.Audio1", defSinkPath.path(), QDBusConnection::sessionBus(), this);
connect(m_defSinkInter, &DBusSink::VolumeChanged, this, &SoundApplet::onVolumeChanged);
connect(m_defSinkInter, &DBusSink::MuteChanged, this, [ = ] {
onVolumeChanged(m_defSinkInter->volume() * 100);
});
QString portId = m_defSinkInter->activePort().name;
uint cardId = m_defSinkInter->card();
//最后一个设备会被移除,但是当在控制中心选中此设备后需要添加,并勾选
if (!m_lastPort.isNull() && m_lastPort->cardId() == cardId && m_lastPort->id() == portId) {
startAddPort(m_lastPort);
}
activePort(portId,cardId);
//无声卡状态下会有伪sink设备显示音量为0
onVolumeChanged(findPort(portId, cardId) != nullptr ? m_defSinkInter->volume() * 100 : 0);
emit defaultSinkChanged(m_defSinkInter);
}
void SoundApplet::onVolumeChanged(double volume)
{
m_volumeSlider->setValue(std::min(150, qRound(volume * 100.0)));
m_soundShow->setText(QString::number(volume * 100) + '%');
emit volumeChanged(m_volumeSlider->value());
refreshIcon();
}
void SoundApplet::volumeSliderValueChanged()
{
m_defSinkInter->SetVolume(m_volumeSlider->value() / 100.0f, true);
if (m_defSinkInter->mute())
m_defSinkInter->SetMuteQueued(false);
}
void SoundApplet::cardsChanged(const QString &cards)
{
QMap<uint, QStringList> tmpCardIds;
QJsonDocument doc = QJsonDocument::fromJson(cards.toUtf8());
QJsonArray jCards = doc.array();
for (QJsonValue cV : jCards) {
QJsonObject jCard = cV.toObject();
const uint cardId = jCard["Id"].toInt();
const QString cardName = jCard["Name"].toString();
QJsonArray jPorts = jCard["Ports"].toArray();
QStringList tmpPorts;
for (QJsonValue pV : jPorts) {
QJsonObject jPort = pV.toObject();
const double portAvai = jPort["Available"].toDouble();
if (portAvai == 2 || portAvai == 0 ) { // 0 Unknow 1 Not available 2 Available
const QString portId = jPort["Name"].toString();
const QString portName = jPort["Description"].toString();
SoundDevicePort *port = findPort(portId, cardId);
const bool include = port != nullptr;
if (!include) { port = new SoundDevicePort(m_model); }
port->setId(portId);
port->setName(portName);
port->setDirection(SoundDevicePort::Direction(jPort["Direction"].toDouble()));
port->setCardId(cardId);
port->setCardName(cardName);
if (!include) {
startAddPort(port);
}
tmpPorts << portId;
}
}
tmpCardIds.insert(cardId, tmpPorts);
}
onDefaultSinkChanged();//重新获取切换的设备信息
enableDevice(existActiveOutputDevice());
for (SoundDevicePort *port : m_ports) {
//只要有一个设备在控制中心被禁用后,在任务栏声音设备列表中该设备会被移除,
if (!m_audioInter->IsPortEnabled(port->cardId(), port->id())) {
removeDisabledDevice(port->id(), port->cardId());
}
//判断端口是否在最新的设备列表中
if (tmpCardIds.contains(port->cardId())) {
if (!tmpCardIds[port->cardId()].contains(port->id())) {
startRemovePort(port->id(), port->cardId());
}
}
else {
startRemovePort(port->id(), port->cardId());
}
}
//当只有一个设备剩余时,该设备也需要移除
removeLastDevice();
updateListHeight();
}
void SoundApplet::increaseVolumeChanged()
{
// 触发VolumeSlider::valueChanged
m_volumeSlider->setMaximum(m_audioInter->maxUIVolume() * 100.0f);
// onDefaultSinkChanged()比increaseVolumeChanged()先执行,设置完最大值后需要重新设置当前值
if (m_defSinkInter) {
// 规避因开启声音增强声音值超过100后关闭声音增强后读取到数据为旧数据的问题
if (m_audioInter->increaseVolume())
m_volumeSlider->setValue(std::min(150, qRound(m_defSinkInter->volume() * 100.0)));
}
}
void SoundApplet::refreshIcon()
{
if (!m_defSinkInter)
return;
const bool mute = existActiveOutputDevice() ? m_defSinkInter->mute() : true;
QString volumeString;
if (mute) {
volumeString = "muted";
} else {
volumeString = "off";
}
QString iconLeft = QString("audio-volume-%1-symbolic").arg(volumeString);
QString iconRight = QString("audio-volume-high-symbolic");
if (DGuiApplicationHelper::instance()->themeType() == DGuiApplicationHelper::LightType) {
iconLeft.append("-dark");
iconRight.append("-dark");
}
const auto ratio = devicePixelRatioF();
QPixmap ret = ImageUtil::loadSvg(iconRight, ":/", ICON_SIZE, ratio);
m_volumeIconMax->setPixmap(ret);
ret = ImageUtil::loadSvg(iconLeft, ":/", ICON_SIZE, ratio);
m_volumeIconMin->setPixmap(ret);
}
/**
* @brief SoundApplet::startAddPort 添加端口前判断
* @param port 端口
*/
void SoundApplet::startAddPort(SoundDevicePort *port)
{
if (!containsPort(port) && port->direction() == SoundDevicePort::Out) {
m_ports.append(port);
addPort(port);
}
}
/**
* @brief SoundApplet::startRemovePort 移除端口前判断
* @param portId 端口
* @param cardId 声卡
*/
void SoundApplet::startRemovePort(const QString &portId, const uint &cardId)
{
SoundDevicePort *port = findPort(portId, cardId);
if (port) {
m_ports.removeOne(port);
port->deleteLater();
removePort(portId, cardId);
}
}
bool SoundApplet::containsPort(const SoundDevicePort *port)
{
return findPort(port->id(), port->cardId()) != nullptr;
}
SoundDevicePort *SoundApplet::findPort(const QString &portId, const uint &cardId) const
{
auto it = std::find_if(m_ports.begin(), m_ports.end(), [ = ] (SoundDevicePort *p) {
return (p->id() == portId && p->cardId() == cardId);
});
if (it != m_ports.end()) {
return *it;
}
return nullptr;
}
void SoundApplet::addPort(const SoundDevicePort *port)
{
DStandardItem *pi = new DStandardItem;
QString deviceName = port->name() + "(" + port->cardName() + ")";
pi->setText(deviceName);
pi->setTextColorRole(QPalette::BrightText);
pi->setData(QVariant::fromValue<const SoundDevicePort *>(port), Qt::WhatsThisPropertyRole);
connect(port, &SoundDevicePort::nameChanged, this, [ = ](const QString &str) {
QString devName = str + "(" + port->cardName() + ")";
pi->setText(devName);
});
connect(port, &SoundDevicePort::cardNameChanged, this, [ = ](const QString &str) {
QString devName = port->name() + "(" + str + ")";
pi->setText(devName);
});
connect(port, &SoundDevicePort::isActiveChanged, this, [ = ](bool isActive) {
pi->setCheckState(isActive ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
});
if (port->isActive()) {
pi->setCheckState(Qt::CheckState::Checked);
}
m_model->appendRow(pi);
m_model->sort(0);
m_secondSeperator->setVisible(m_model->rowCount() > 1);
updateListHeight();
}
void SoundApplet::removePort(const QString &portId, const uint &cardId)
{
auto rmFunc = [ = ](QStandardItemModel * model) {
for (int i = 0; i < model->rowCount();) {
auto item = model->item(i);
auto port = item->data(Qt::WhatsThisPropertyRole).value<const SoundDevicePort *>();
if (port->id() == portId && cardId == port->cardId()) {
model->removeRow(i);
break;
} else {
++i;
}
}
};
rmFunc(m_model);
m_secondSeperator->setVisible(m_model->rowCount() > 1);
updateListHeight();
}
/**
* @brief SoundApplet::activePort 激活某一指定端口
* @param portId 端口
* @param cardId 声卡
*/
void SoundApplet::activePort(const QString &portId, const uint &cardId)
{
for (SoundDevicePort *it : m_ports) {
if (it->id() == portId && it->cardId() == cardId) {
it->setIsActive(true);
enableDevice(true);
}
else {
it->setIsActive(false);
}
}
}
void SoundApplet::updateCradsInfo()
{
QString info = m_audioInter->property("CardsWithoutUnavailable").toString();
if(m_deviceInfo != info){
cardsChanged(info);
m_deviceInfo = info;
}
}
void SoundApplet::enableDevice(bool flag)
{
QString status = m_gsettings ? m_gsettings->get(GSETTING_SOUND_OUTPUT_SLIDER).toString() : "Enabled";
if ("Disabled" == status ) {
m_volumeSlider->setEnabled(false);
} else if ("Enabled" == status) {
m_volumeSlider->setEnabled(flag);
}
m_volumeIconMin->setEnabled(flag);
m_soundShow->setEnabled(flag);
m_volumeIconMax->setEnabled(flag);
m_deviceLabel->setEnabled(flag);
}
void SoundApplet::disableAllDevice()
{
for (SoundDevicePort *port : m_ports) {
port->setIsActive(false);
}
}
/**
* @brief SoundApplet::removeLastDevice
* 移除最后一个设备
*/
void SoundApplet::removeLastDevice()
{
if (m_ports.count() == 1 && m_ports.at(0)) {
m_lastPort = new SoundDevicePort(m_model);
m_lastPort->setId(m_ports.at(0)->id());
m_lastPort->setName(m_ports.at(0)->name());
m_lastPort->setDirection(m_ports.at(0)->direction());
m_lastPort->setCardId(m_ports.at(0)->cardId());
m_lastPort->setCardName(m_ports.at(0)->cardName());
startRemovePort(m_ports.at(0)->id(), m_ports.at(0)->cardId());
qDebug() << "remove last output device";
}
}
/**
* @brief SoundApplet::removeDisabledDevice 移除禁用设备
* @param portId
* @param cardId
*/
void SoundApplet::removeDisabledDevice(QString portId, unsigned int cardId)
{
startRemovePort(portId, cardId);
if (m_defSinkInter->activePort().name == portId && m_defSinkInter->card() == cardId) {
enableDevice(false);
disableAllDevice();
}
qDebug() << "remove disabled output device";
}
void SoundApplet::updateVolumeSliderStatus(const QString &status)
{
bool flag = true;
if ("Enabled" == status) {
flag = true;
} else if ("Disabled" == status) {
flag = false;
}
m_volumeSlider->setEnabled(flag);
m_volumeIconMin->setEnabled(flag);
m_volumeIconMax->setEnabled(flag);
flag = "Hiden" != status;
m_volumeSlider->setVisible(flag);
m_volumeIconMin->setVisible(flag);
m_volumeIconMax->setVisible(flag);
}
/** 判断是否存在未禁用的声音输出设备
* @brief SoundApplet::existActiveOutputDevice
* @return 存在返回true,否则返回false
*/
bool SoundApplet::existActiveOutputDevice()
{
QString info = m_audioInter->property("CardsWithoutUnavailable").toString();
QJsonDocument doc = QJsonDocument::fromJson(info.toUtf8());
QJsonArray jCards = doc.array();
for (QJsonValue cV : jCards) {
QJsonObject jCard = cV.toObject();
QJsonArray jPorts = jCard["Ports"].toArray();
for (QJsonValue pV : jPorts) {
QJsonObject jPort = pV.toObject();
if (jPort["Direction"].toInt() == 1 && jPort["Enabled"].toBool())
return true;
}
}
return false;
}
bool SoundApplet::eventFilter(QObject *watcher, QEvent *event)
{
// 当控制中心禁用所有输出设备时,静音按钮置灰,其他情况正常.
if (watcher == m_volumeIconMin && event->type() == QEvent::MouseButtonRelease) {
if (!existActiveOutputDevice()) {
m_volumeIconMin->setEnabled(false);
} else {
m_volumeIconMin->setEnabled(true);
m_defSinkInter->SetMuteQueued(!m_defSinkInter->mute());
}
}
return false;
}
void SoundApplet::haldleDbusSignal(const QDBusMessage &msg)
{
Q_UNUSED(msg)
updateCradsInfo();
}
void SoundApplet::updateListHeight()
{
//设备数多于10个时显示滚动条,固定高度
int count = m_model->rowCount() == 1 ? 0 : m_model->rowCount();
if (m_model->rowCount() > 10) {
count = 10;
m_listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
} else {
m_listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
int visualHeight = 0;
for (int i = 0; i < count; i++)
visualHeight += m_listView->visualRect(m_model->index(i, 0)).height();
int listMargin = m_listView->contentsMargins().top() + m_listView->contentsMargins().bottom();
//显示声音设备列表高度 = 设备的高度 + 间隔 + 边距
int viewHeight = visualHeight + m_listView->spacing() * (count - 1) + listMargin;
// 设备信息高度 = 设备标签 + 分隔线 + 滚动条 + 间隔
int labelHeight = m_deviceLabel->height() > m_soundShow->height() ? m_deviceLabel->height() : m_soundShow->height();
int infoHeight = labelHeight + m_seperator->height() * 2 + m_volumeSlider->height();
int margain = m_centralLayout->contentsMargins().top() + m_centralLayout->contentsMargins().bottom();
//整个界面高度 = 显示声音设备列表高度 + 设备信息高度 + 边距
int totalHeight = viewHeight + infoHeight + margain;
//加上分割线占用的高度否则显示界面高度不够显示会造成音频列表item最后一项比其它项的高度小
m_listView->setFixedHeight(viewHeight);
setFixedHeight(totalHeight);
m_centralWidget->setFixedHeight(totalHeight);
}
void SoundApplet::portEnableChange(unsigned int cardId, QString portId)
{
Q_UNUSED(cardId)
Q_UNUSED(portId)
m_deviceInfo = "";
updateCradsInfo();
}