Add capture routing rules (source → app) to complement playback rules

This commit is contained in:
Joey Yakimowich-Payne 2026-02-12 13:08:50 -07:00
commit 242d0ec09f
6 changed files with 418 additions and 78 deletions

View file

@ -1864,9 +1864,17 @@ void GraphEditorWidget::rebuildRulesList() {
matchLabel->setStyleSheet(valueStyle);
infoLayout->addWidget(matchLabel);
auto *targetLabel = new QLabel(
QString(QChar(0x2192)) + QStringLiteral(" ") +
QString::fromStdString(rule.target_node));
QString dirPrefix;
QString nodeTarget;
if (rule.direction == warppipe::RuleDirection::kCapture) {
dirPrefix = QString(QChar(0x2190)) + QStringLiteral(" ");
nodeTarget = QString::fromStdString(rule.source_node);
} else {
dirPrefix = QString(QChar(0x2192)) + QStringLiteral(" ");
nodeTarget = QString::fromStdString(rule.target_node);
}
auto *targetLabel = new QLabel(dirPrefix + nodeTarget);
targetLabel->setStyleSheet(labelStyle);
infoLayout->addWidget(targetLabel);
@ -1880,9 +1888,11 @@ void GraphEditorWidget::rebuildRulesList() {
std::string ruleBin = rule.match.process_binary;
std::string ruleRole = rule.match.media_role;
std::string ruleTarget = rule.target_node;
warppipe::RuleDirection ruleDir = rule.direction;
std::string ruleSource = rule.source_node;
connect(editBtn, &QPushButton::clicked, this,
[this, ruleApp, ruleBin, ruleRole, ruleTarget, ruleId]() {
showAddRuleDialog(ruleApp, ruleBin, ruleRole, ruleTarget, ruleId);
[this, ruleApp, ruleBin, ruleRole, ruleTarget, ruleId, ruleDir, ruleSource]() {
showAddRuleDialog(ruleApp, ruleBin, ruleRole, ruleTarget, ruleId, ruleDir, ruleSource);
});
cardLayout->addWidget(editBtn);
@ -1931,7 +1941,9 @@ void GraphEditorWidget::showAddRuleDialog(const std::string &prefillApp,
const std::string &prefillBin,
const std::string &prefillRole,
const std::string &prefillTarget,
warppipe::RuleId editRuleId) {
warppipe::RuleId editRuleId,
warppipe::RuleDirection prefillDirection,
const std::string &prefillSource) {
if (!m_client)
return;
@ -1955,6 +1967,15 @@ void GraphEditorWidget::showAddRuleDialog(const std::string &prefillApp,
form->setContentsMargins(16, 16, 16, 16);
form->setSpacing(8);
auto *directionCombo = new QComboBox();
directionCombo->addItem(QStringLiteral("Playback (App ") + QString(QChar(0x2192)) + QStringLiteral(" Sink)"),
static_cast<int>(warppipe::RuleDirection::kPlayback));
directionCombo->addItem(QStringLiteral("Capture (Source ") + QString(QChar(0x2192)) + QStringLiteral(" App)"),
static_cast<int>(warppipe::RuleDirection::kCapture));
if (prefillDirection == warppipe::RuleDirection::kCapture)
directionCombo->setCurrentIndex(1);
form->addRow(QStringLiteral("Direction:"), directionCombo);
auto *appNameEdit = new QLineEdit();
appNameEdit->setPlaceholderText(QStringLiteral("e.g. Firefox"));
if (!prefillApp.empty())
@ -1974,22 +1995,47 @@ void GraphEditorWidget::showAddRuleDialog(const std::string &prefillApp,
form->addRow(QStringLiteral("Media Role:"), mediaRoleEdit);
auto *targetCombo = new QComboBox();
auto *sourceCombo = new QComboBox();
auto *targetLabel = new QLabel(QStringLiteral("Target Sink:"));
auto *sourceLabel = new QLabel(QStringLiteral("Source Node:"));
auto nodesResult = m_client->ListNodes();
if (nodesResult.ok()) {
for (const auto &node : nodesResult.value) {
QString label = QString::fromStdString(
node.description.empty() ? node.name : node.description);
QString data = QString::fromStdString(node.name);
if (node.media_class.find("Sink") != std::string::npos) {
QString label = QString::fromStdString(
node.description.empty() ? node.name : node.description);
targetCombo->addItem(label, QString::fromStdString(node.name));
targetCombo->addItem(label, data);
}
if (node.media_class.find("Source") != std::string::npos) {
sourceCombo->addItem(label, data);
}
}
}
if (!prefillTarget.empty()) {
int idx = targetCombo->findData(QString::fromStdString(prefillTarget));
if (idx >= 0)
targetCombo->setCurrentIndex(idx);
if (idx >= 0) targetCombo->setCurrentIndex(idx);
}
form->addRow(QStringLiteral("Target Node:"), targetCombo);
if (!prefillSource.empty()) {
int idx = sourceCombo->findData(QString::fromStdString(prefillSource));
if (idx >= 0) sourceCombo->setCurrentIndex(idx);
}
form->addRow(targetLabel, targetCombo);
form->addRow(sourceLabel, sourceCombo);
auto updateVisibility = [=](int index) {
bool isCapture = directionCombo->itemData(index).toInt() ==
static_cast<int>(warppipe::RuleDirection::kCapture);
targetLabel->setVisible(!isCapture);
targetCombo->setVisible(!isCapture);
sourceLabel->setVisible(isCapture);
sourceCombo->setVisible(isCapture);
};
connect(directionCombo, &QComboBox::currentIndexChanged, &dlg, updateVisibility);
updateVisibility(directionCombo->currentIndex());
auto *buttons = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
@ -2007,16 +2053,26 @@ void GraphEditorWidget::showAddRuleDialog(const std::string &prefillApp,
std::string appName = appNameEdit->text().trimmed().toStdString();
std::string procBin = processBinEdit->text().trimmed().toStdString();
std::string role = mediaRoleEdit->text().trimmed().toStdString();
std::string target = targetCombo->currentData().toString().toStdString();
auto dir = static_cast<warppipe::RuleDirection>(
directionCombo->currentData().toInt());
if (appName.empty() && procBin.empty() && role.empty()) {
QMessageBox::warning(this, QStringLiteral("Invalid Rule"),
QStringLiteral("At least one match field must be filled."));
return;
}
if (target.empty()) {
std::string target = targetCombo->currentData().toString().toStdString();
std::string source = sourceCombo->currentData().toString().toStdString();
if (dir == warppipe::RuleDirection::kPlayback && target.empty()) {
QMessageBox::warning(this, QStringLiteral("Invalid Rule"),
QStringLiteral("A target node must be selected."));
QStringLiteral("A target sink must be selected."));
return;
}
if (dir == warppipe::RuleDirection::kCapture && source.empty()) {
QMessageBox::warning(this, QStringLiteral("Invalid Rule"),
QStringLiteral("A source node must be selected."));
return;
}
@ -2028,7 +2084,12 @@ void GraphEditorWidget::showAddRuleDialog(const std::string &prefillApp,
rule.match.application_name = appName;
rule.match.process_binary = procBin;
rule.match.media_role = role;
rule.target_node = target;
rule.direction = dir;
if (dir == warppipe::RuleDirection::kPlayback) {
rule.target_node = target;
} else {
rule.source_node = source;
}
m_client->AddRouteRule(rule);
rebuildRulesList();
}

View file

@ -101,7 +101,9 @@ private:
const std::string &prefillBin = {},
const std::string &prefillRole = {},
const std::string &prefillTarget = {},
warppipe::RuleId editRuleId = {});
warppipe::RuleId editRuleId = {},
warppipe::RuleDirection prefillDirection = warppipe::RuleDirection::kPlayback,
const std::string &prefillSource = {});
void setConnectionStyle(ConnectionStyleType style);
void onSelectionChanged();
void updateNodeDetailsPanel(QtNodes::NodeId nodeId);