From 0bbc1ca003354793ee17473297fa99c9f529cfe8 Mon Sep 17 00:00:00 2001 From: Cristian Gora Date: Tue, 7 Jul 2026 04:03:07 +0200 Subject: [PATCH] Monitor refactor; link_nodes PW utility --- pipewire.cpp | 70 ++++++++++++++++++++++++++++++++------------------ pipewire.hpp | 9 ++++--- soundboard.cpp | 46 ++++++++++++--------------------- 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/pipewire.cpp b/pipewire.cpp index 271c73f..8fbd4b9 100644 --- a/pipewire.cpp +++ b/pipewire.cpp @@ -9,31 +9,6 @@ namespace Pipewire { -bool link_ports(int out_port_id, int in_port_id) -{ - QProcess process; - process.start("pw-link", { QString::number(out_port_id), QString::number(in_port_id) }); - return process.waitForFinished() && process.exitCode() == 0; -} - -bool destroy_link(int link_id) -{ - QProcess process; - process.start("pw-cli", { "destroy", QString::number(link_id) }); - return process.waitForFinished() && process.exitCode() == 0; -} - -void set_volume(const QString& target_node, int volume_percent) -{ - volume_percent = std::max(0, std::min(100, volume_percent)); - - QProcess process; - process.startDetached("pactl", { - "set-sink-volume", target_node, - QString::number(volume_percent) + "%" - }); -} - QString Node::get_display_name() const { if (!application_name.isEmpty()) return application_name; @@ -259,4 +234,49 @@ void Sink::destroy(int pulse_audio_id) process.waitForFinished(); } +bool link_ports(int out_port_id, int in_port_id) +{ + QProcess process; + process.start("pw-link", { QString::number(out_port_id), QString::number(in_port_id) }); + return process.waitForFinished() && process.exitCode() == 0; +} + +bool link_nodes(int out_node_id, int in_node_id, const Graph& graph) +{ + auto out_ports = graph.get_ports_for_node(out_node_id, "out"); + auto in_ports = graph.get_ports_for_node(in_node_id, "in"); + + for (const Port& out_port : out_ports) { + for (const Port& in_port : in_ports) { + if (out_port.channel != in_port.channel) { + continue; + } + + if (!link_ports(out_port.id, in_port.id)) { + return false; + } + } + } + + return true; +} + +bool destroy_link(int link_id) +{ + QProcess process; + process.start("pw-cli", { "destroy", QString::number(link_id) }); + return process.waitForFinished() && process.exitCode() == 0; +} + +void set_volume(const QString& target_node, int volume_percent) +{ + volume_percent = std::max(0, std::min(100, volume_percent)); + + QProcess process; + process.startDetached("pactl", { + "set-sink-volume", target_node, + QString::number(volume_percent) + "%" + }); +} + } // namespace Pipewire diff --git a/pipewire.hpp b/pipewire.hpp index d95e4f6..2dde690 100644 --- a/pipewire.hpp +++ b/pipewire.hpp @@ -9,10 +9,6 @@ namespace Pipewire { -bool link_ports(int out_port_id, int in_port_id); -bool destroy_link(int link_id); -void set_volume(const QString& target_node, int volume_percent); - struct Port { int id; @@ -91,4 +87,9 @@ private: int m_pipewire_id = -1; }; +bool link_ports(int out_port_id, int in_port_id); +bool link_nodes(int out_node_id, int in_node_id, const Graph& graph); +bool destroy_link(int link_id); +void set_volume(const QString& target_node, int volume_percent); + } // namespace Pipewire diff --git a/soundboard.cpp b/soundboard.cpp index aeab3ac..fd505e5 100644 --- a/soundboard.cpp +++ b/soundboard.cpp @@ -99,57 +99,43 @@ Soundboard::~Soundboard() void Soundboard::on_app_selected(int index) { - if (index < 0 || index >= m_apps.size() || !m_sink) { + if (!m_sink) { return; } m_graph.refresh(); - int target_app_id = m_apps[index].id; - int sink_id = m_sink->get_id(); - // Destroy old output links from our sink - auto existing_links = m_graph.get_links_from_node(sink_id); + auto existing_links = m_graph.get_links_from_node(m_sink->get_id()); for (const auto& link : existing_links) { Pipewire::destroy_link(link.id); } - auto sink_out_ports = m_graph.get_ports_for_node(sink_id, "out"); - auto target_in_ports = m_graph.get_ports_for_node(target_app_id, "in"); - - // Link sink to target app - // TODO(1): create a wrapper function to link all channels - for (const auto& out_port : sink_out_ports) { - for (const auto& in_port : target_in_ports) { - if (out_port.channel == in_port.channel && - !Pipewire::link_ports(out_port.id, in_port.id)) { - std::cerr << "[ERROR] Failed to link sink with target node" << std::endl; - break; - } - } - } - - // Link to monitor only if checked + // Recover monitor state if (m_monitor_toggle->isChecked()) { + int sink_id = m_sink->get_id(); auto sinks = m_graph.get_all_sinks(); + for (const auto& system_sink : sinks) { // Skip our own virtual sink if (system_sink.id == sink_id) { continue; } - auto output_ids = m_graph.get_ports_for_node(system_sink.id, "in"); - // TODO(1): create a wrapper function to link all channels - for (const auto& out_port : sink_out_ports) { - for (const auto& in_port : output_ids) { - if (out_port.channel == in_port.channel) { - Pipewire::link_ports(out_port.id, in_port.id); - } - } - } + Pipewire::link_nodes(sink_id, system_sink.id, m_graph); } } + std::cout << "[INFO] Monitor: " << m_monitor_toggle->isChecked() << std::endl; + + if (index < 0 || index >= m_apps.size()) { + return; + } + + // Link sink to target app + int target_app_id = m_apps[index].id; + Pipewire::link_nodes(m_sink->get_id(), target_app_id, m_graph); + std::cout << "[INFO] Updated routing" << std::endl; }