diff --git a/src/gui/PipeWireGraphModel.cpp b/src/gui/PipeWireGraphModel.cpp index 9b4e811..e8fcdc2 100644 --- a/src/gui/PipeWireGraphModel.cpp +++ b/src/gui/PipeWireGraphModel.cpp @@ -120,6 +120,12 @@ QWidget *PipeWireGraphModel::nodeWidget(QtNodes::NodeId nodeId) const return it->second; } + uint32_t pipewireId = 0; + auto nodeIt = m_nodes.find(nodeId); + if (nodeIt != m_nodes.end()) { + pipewireId = nodeIt->second.id; + } + auto *widget = new QWidget(); auto *layout = new QHBoxLayout(widget); layout->setContentsMargins(6, 2, 6, 2); @@ -129,14 +135,25 @@ QWidget *PipeWireGraphModel::nodeWidget(QtNodes::NodeId nodeId) const muteButton->setText("M"); muteButton->setCheckable(true); muteButton->setFixedSize(20, 20); - muteButton->setToolTip(QString("Mute (UI only)")); + muteButton->setToolTip(QString("Mute")); auto *slider = new QSlider(Qt::Horizontal, widget); slider->setRange(0, 100); slider->setValue(100); slider->setFixedHeight(18); slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - slider->setToolTip(QString("Volume (UI only)")); + slider->setToolTip(QString("Volume")); + + const auto applyVolume = [this, pipewireId, slider, muteButton]() { + if (!m_controller || pipewireId == 0) { + return; + } + const float volume = static_cast(slider->value()) / 100.0f; + m_controller->setNodeVolume(pipewireId, volume, muteButton->isChecked()); + }; + + QObject::connect(slider, &QSlider::valueChanged, widget, [applyVolume](int) { applyVolume(); }); + QObject::connect(muteButton, &QToolButton::toggled, widget, [applyVolume](bool) { applyVolume(); }); layout->addWidget(muteButton); layout->addWidget(slider); @@ -266,7 +283,14 @@ bool PipeWireGraphModel::connectionPossible(QtNodes::ConnectionId const connecti return false; } - if (outInfo.mediaClass != inInfo.mediaClass) { + const auto isAudioClass = [](Potato::MediaClass mediaClass) { + return mediaClass == Potato::MediaClass::AudioSink + || mediaClass == Potato::MediaClass::AudioSource + || mediaClass == Potato::MediaClass::AudioDuplex + || mediaClass == Potato::MediaClass::Stream; + }; + + if (!isAudioClass(outInfo.mediaClass) || !isAudioClass(inInfo.mediaClass)) { return false; } diff --git a/src/pipewire/pipewirecontroller.cpp b/src/pipewire/pipewirecontroller.cpp index f1b305e..790ff45 100644 --- a/src/pipewire/pipewirecontroller.cpp +++ b/src/pipewire/pipewirecontroller.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -444,6 +446,40 @@ float PipeWireController::meterPeak() const return peak; } +bool PipeWireController::setNodeVolume(uint32_t nodeId, float volume, bool mute) +{ + if (!m_threadLoop || !m_core || !m_registry) { + return false; + } + + if (nodeId == 0) { + return false; + } + + volume = std::clamp(volume, 0.0f, 1.0f); + + lock(); + auto *node = static_cast( + pw_registry_bind(m_registry, nodeId, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0)); + if (!node) { + unlock(); + return false; + } + + uint8_t buffer[128]; + spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + auto *param = reinterpret_cast(spa_pod_builder_add_object( + &builder, + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_volume, SPA_POD_Float(volume), + SPA_PROP_mute, SPA_POD_Bool(mute))); + + pw_node_set_param(node, SPA_PARAM_Props, 0, param); + pw_proxy_destroy(reinterpret_cast(node)); + unlock(); + return true; +} + float PipeWireController::nodeMeterPeak(uint32_t nodeId) const { QMutexLocker lock(&m_meterMutex); diff --git a/src/pipewire/pipewirecontroller.h b/src/pipewire/pipewirecontroller.h index 1d9444d..06faca4 100644 --- a/src/pipewire/pipewirecontroller.h +++ b/src/pipewire/pipewirecontroller.h @@ -41,6 +41,7 @@ public: float nodeMeterPeak(uint32_t nodeId) const; void ensureNodeMeter(uint32_t nodeId, const QString &targetName, bool captureSink); void removeNodeMeter(uint32_t nodeId); + bool setNodeVolume(uint32_t nodeId, float volume, bool mute); uint32_t createLink(uint32_t outputNodeId, uint32_t outputPortId, uint32_t inputNodeId, uint32_t inputPortId);