Add capture routing rules (source → app) to complement playback rules
This commit is contained in:
parent
621d67ebab
commit
242d0ec09f
6 changed files with 418 additions and 78 deletions
226
src/warppipe.cpp
226
src/warppipe.cpp
|
|
@ -78,6 +78,7 @@ struct PendingAutoLink {
|
|||
uint32_t source_node_id = 0;
|
||||
std::string target_node_name;
|
||||
uint32_t rule_id = 0;
|
||||
RuleDirection direction = RuleDirection::kPlayback;
|
||||
};
|
||||
|
||||
bool MatchesRule(const NodeInfo& node, const RuleMatch& match) {
|
||||
|
|
@ -1200,44 +1201,88 @@ void Client::Impl::DisconnectLocked() {
|
|||
|
||||
void Client::Impl::CheckRulesForNode(const NodeInfo& node) {
|
||||
for (const auto& entry : route_rules) {
|
||||
if (MatchesRule(node, entry.second.match)) {
|
||||
PendingAutoLink pending;
|
||||
if (!MatchesRule(node, entry.second.match)) continue;
|
||||
const RouteRule& rule = entry.second;
|
||||
|
||||
PendingAutoLink pending;
|
||||
pending.rule_id = entry.first;
|
||||
pending.direction = rule.direction;
|
||||
|
||||
if (rule.direction == RuleDirection::kPlayback) {
|
||||
pending.source_node_id = node.id.value;
|
||||
pending.target_node_name = entry.second.target_node;
|
||||
pending.rule_id = entry.first;
|
||||
pending_auto_links.push_back(std::move(pending));
|
||||
SchedulePolicySync();
|
||||
pending.target_node_name = rule.target_node;
|
||||
} else if (!rule.source_node.empty()) {
|
||||
// source_node_id = app (input side); target_node_name = hw source (output side)
|
||||
pending.source_node_id = node.id.value;
|
||||
pending.target_node_name = rule.source_node;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
pending_auto_links.push_back(std::move(pending));
|
||||
SchedulePolicySync();
|
||||
}
|
||||
}
|
||||
|
||||
void Client::Impl::EnforceRulesForLink(uint32_t link_id, uint32_t out_port,
|
||||
uint32_t in_port) {
|
||||
auto port_it = ports.find(out_port);
|
||||
if (port_it == ports.end()) return;
|
||||
uint32_t src_node_id = port_it->second.node.value;
|
||||
auto node_it = nodes.find(src_node_id);
|
||||
if (node_it == nodes.end()) return;
|
||||
uint32_t in_port) {
|
||||
auto out_port_it = ports.find(out_port);
|
||||
auto in_port_it = ports.find(in_port);
|
||||
if (out_port_it == ports.end() || in_port_it == ports.end()) return;
|
||||
uint32_t src_node_id = out_port_it->second.node.value;
|
||||
uint32_t dest_node_id = in_port_it->second.node.value;
|
||||
|
||||
std::vector<uint32_t> rule_target_ids;
|
||||
for (const auto& rule_entry : route_rules) {
|
||||
if (!MatchesRule(node_it->second, rule_entry.second.match)) continue;
|
||||
for (const auto& n : nodes) {
|
||||
if (n.second.name == rule_entry.second.target_node) {
|
||||
rule_target_ids.push_back(n.first);
|
||||
break;
|
||||
auto src_node_it = nodes.find(src_node_id);
|
||||
auto dest_node_it = nodes.find(dest_node_id);
|
||||
|
||||
bool should_destroy = false;
|
||||
|
||||
// Playback enforcement: if source app matches a playback rule, link must go to rule target
|
||||
if (src_node_it != nodes.end()) {
|
||||
std::vector<uint32_t> rule_target_ids;
|
||||
for (const auto& rule_entry : route_rules) {
|
||||
if (rule_entry.second.direction != RuleDirection::kPlayback) continue;
|
||||
if (!MatchesRule(src_node_it->second, rule_entry.second.match)) continue;
|
||||
for (const auto& n : nodes) {
|
||||
if (n.second.name == rule_entry.second.target_node) {
|
||||
rule_target_ids.push_back(n.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rule_target_ids.empty()) {
|
||||
bool links_to_target = false;
|
||||
for (uint32_t tid : rule_target_ids) {
|
||||
if (dest_node_id == tid) { links_to_target = true; break; }
|
||||
}
|
||||
if (!links_to_target) should_destroy = true;
|
||||
}
|
||||
}
|
||||
if (rule_target_ids.empty()) return;
|
||||
|
||||
auto in_port_it = ports.find(in_port);
|
||||
if (in_port_it == ports.end()) return;
|
||||
uint32_t dest_node_id = in_port_it->second.node.value;
|
||||
for (uint32_t tid : rule_target_ids) {
|
||||
if (dest_node_id == tid) return;
|
||||
// Capture enforcement: if dest app matches a capture rule, link must come from rule source
|
||||
if (!should_destroy && dest_node_it != nodes.end()) {
|
||||
std::vector<uint32_t> rule_source_ids;
|
||||
for (const auto& rule_entry : route_rules) {
|
||||
if (rule_entry.second.direction != RuleDirection::kCapture) continue;
|
||||
if (!MatchesRule(dest_node_it->second, rule_entry.second.match)) continue;
|
||||
for (const auto& n : nodes) {
|
||||
if (n.second.name == rule_entry.second.source_node) {
|
||||
rule_source_ids.push_back(n.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rule_source_ids.empty()) {
|
||||
bool links_from_source = false;
|
||||
for (uint32_t sid : rule_source_ids) {
|
||||
if (src_node_id == sid) { links_from_source = true; break; }
|
||||
}
|
||||
if (!links_from_source) should_destroy = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!should_destroy) return;
|
||||
|
||||
if (link_proxies.count(link_id)) return;
|
||||
for (const auto& proxy : auto_link_proxies) {
|
||||
if (proxy && proxy->output_port == out_port &&
|
||||
|
|
@ -1305,13 +1350,26 @@ void Client::Impl::ProcessPendingAutoLinks() {
|
|||
std::vector<PortEntry> src_ports;
|
||||
std::vector<PortEntry> tgt_ports;
|
||||
|
||||
for (const auto& port_entry : ports) {
|
||||
const PortInfo& port = port_entry.second;
|
||||
if (port.node.value == it->source_node_id && !port.is_input) {
|
||||
src_ports.push_back({port_entry.first, port.name});
|
||||
if (it->direction == RuleDirection::kCapture) {
|
||||
// Capture: target_node_name is the hw source (outputs), source_node_id is the app (inputs)
|
||||
for (const auto& port_entry : ports) {
|
||||
const PortInfo& port = port_entry.second;
|
||||
if (port.node.value == target_node_id && !port.is_input) {
|
||||
src_ports.push_back({port_entry.first, port.name});
|
||||
}
|
||||
if (port.node.value == it->source_node_id && port.is_input) {
|
||||
tgt_ports.push_back({port_entry.first, port.name});
|
||||
}
|
||||
}
|
||||
if (port.node.value == target_node_id && port.is_input) {
|
||||
tgt_ports.push_back({port_entry.first, port.name});
|
||||
} else {
|
||||
for (const auto& port_entry : ports) {
|
||||
const PortInfo& port = port_entry.second;
|
||||
if (port.node.value == it->source_node_id && !port.is_input) {
|
||||
src_ports.push_back({port_entry.first, port.name});
|
||||
}
|
||||
if (port.node.value == target_node_id && port.is_input) {
|
||||
tgt_ports.push_back({port_entry.first, port.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1643,6 +1701,8 @@ void Client::Impl::AutoSave() {
|
|||
rule_obj["match"]["process_binary"] = entry.second.match.process_binary;
|
||||
rule_obj["match"]["media_role"] = entry.second.match.media_role;
|
||||
rule_obj["target_node"] = entry.second.target_node;
|
||||
rule_obj["direction"] = entry.second.direction == RuleDirection::kCapture ? "capture" : "playback";
|
||||
rule_obj["source_node"] = entry.second.source_node;
|
||||
rules_array.push_back(std::move(rule_obj));
|
||||
}
|
||||
}
|
||||
|
|
@ -2595,9 +2655,12 @@ Result<RuleId> Client::AddRouteRule(const RouteRule& rule) {
|
|||
rule.match.media_role.empty()) {
|
||||
return {Status::Error(StatusCode::kInvalidArgument, "rule match has no criteria"), {}};
|
||||
}
|
||||
if (rule.target_node.empty()) {
|
||||
if (rule.direction == RuleDirection::kPlayback && rule.target_node.empty()) {
|
||||
return {Status::Error(StatusCode::kInvalidArgument, "rule target node is empty"), {}};
|
||||
}
|
||||
if (rule.direction == RuleDirection::kCapture && rule.source_node.empty()) {
|
||||
return {Status::Error(StatusCode::kInvalidArgument, "rule source node is empty"), {}};
|
||||
}
|
||||
|
||||
uint32_t id = 0;
|
||||
{
|
||||
|
|
@ -2610,9 +2673,16 @@ Result<RuleId> Client::AddRouteRule(const RouteRule& rule) {
|
|||
for (const auto& node_entry : impl_->nodes) {
|
||||
if (MatchesRule(node_entry.second, rule.match)) {
|
||||
PendingAutoLink pending;
|
||||
pending.source_node_id = node_entry.first;
|
||||
pending.target_node_name = rule.target_node;
|
||||
pending.rule_id = id;
|
||||
pending.direction = rule.direction;
|
||||
|
||||
if (rule.direction == RuleDirection::kPlayback) {
|
||||
pending.source_node_id = node_entry.first;
|
||||
pending.target_node_name = rule.target_node;
|
||||
} else {
|
||||
pending.source_node_id = node_entry.first;
|
||||
pending.target_node_name = rule.source_node;
|
||||
}
|
||||
impl_->pending_auto_links.push_back(std::move(pending));
|
||||
}
|
||||
}
|
||||
|
|
@ -2631,6 +2701,8 @@ Result<RuleId> Client::AddRouteRule(const RouteRule& rule) {
|
|||
Status Client::RemoveRouteRule(RuleId id) {
|
||||
RuleMatch match;
|
||||
std::string target_node;
|
||||
std::string source_node;
|
||||
RuleDirection direction = RuleDirection::kPlayback;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||
|
|
@ -2640,6 +2712,8 @@ Status Client::RemoveRouteRule(RuleId id) {
|
|||
}
|
||||
match = it->second.match;
|
||||
target_node = it->second.target_node;
|
||||
source_node = it->second.source_node;
|
||||
direction = it->second.direction;
|
||||
impl_->route_rules.erase(it);
|
||||
|
||||
auto pending_it = impl_->pending_auto_links.begin();
|
||||
|
|
@ -2657,27 +2731,55 @@ Status Client::RemoveRouteRule(RuleId id) {
|
|||
std::vector<std::pair<uint32_t, uint32_t>> pairs_to_remove;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||
uint32_t target_id = 0;
|
||||
for (const auto& n : impl_->nodes) {
|
||||
if (n.second.name == target_node) {
|
||||
target_id = n.first;
|
||||
break;
|
||||
|
||||
if (direction == RuleDirection::kPlayback) {
|
||||
uint32_t target_id = 0;
|
||||
for (const auto& n : impl_->nodes) {
|
||||
if (n.second.name == target_node) {
|
||||
target_id = n.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (target_id) {
|
||||
for (const auto& link_entry : impl_->links) {
|
||||
uint32_t out_port = link_entry.second.output_port.value;
|
||||
uint32_t in_port = link_entry.second.input_port.value;
|
||||
auto in_port_it = impl_->ports.find(in_port);
|
||||
if (in_port_it == impl_->ports.end() ||
|
||||
in_port_it->second.node.value != target_id) continue;
|
||||
auto out_port_it = impl_->ports.find(out_port);
|
||||
if (out_port_it == impl_->ports.end()) continue;
|
||||
auto src_node_it = impl_->nodes.find(out_port_it->second.node.value);
|
||||
if (src_node_it == impl_->nodes.end()) continue;
|
||||
if (MatchesRule(src_node_it->second, match)) {
|
||||
links_to_destroy.push_back(link_entry.first);
|
||||
pairs_to_remove.emplace_back(out_port, in_port);
|
||||
if (target_id) {
|
||||
for (const auto& link_entry : impl_->links) {
|
||||
uint32_t out_port = link_entry.second.output_port.value;
|
||||
uint32_t in_port = link_entry.second.input_port.value;
|
||||
auto in_port_it = impl_->ports.find(in_port);
|
||||
if (in_port_it == impl_->ports.end() ||
|
||||
in_port_it->second.node.value != target_id) continue;
|
||||
auto out_port_it = impl_->ports.find(out_port);
|
||||
if (out_port_it == impl_->ports.end()) continue;
|
||||
auto src_node_it = impl_->nodes.find(out_port_it->second.node.value);
|
||||
if (src_node_it == impl_->nodes.end()) continue;
|
||||
if (MatchesRule(src_node_it->second, match)) {
|
||||
links_to_destroy.push_back(link_entry.first);
|
||||
pairs_to_remove.emplace_back(out_port, in_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint32_t source_id = 0;
|
||||
for (const auto& n : impl_->nodes) {
|
||||
if (n.second.name == source_node) {
|
||||
source_id = n.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (source_id) {
|
||||
for (const auto& link_entry : impl_->links) {
|
||||
uint32_t out_port = link_entry.second.output_port.value;
|
||||
uint32_t in_port = link_entry.second.input_port.value;
|
||||
auto out_port_it = impl_->ports.find(out_port);
|
||||
if (out_port_it == impl_->ports.end() ||
|
||||
out_port_it->second.node.value != source_id) continue;
|
||||
auto in_port_it = impl_->ports.find(in_port);
|
||||
if (in_port_it == impl_->ports.end()) continue;
|
||||
auto dest_node_it = impl_->nodes.find(in_port_it->second.node.value);
|
||||
if (dest_node_it == impl_->nodes.end()) continue;
|
||||
if (MatchesRule(dest_node_it->second, match)) {
|
||||
links_to_destroy.push_back(link_entry.first);
|
||||
pairs_to_remove.emplace_back(out_port, in_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2802,6 +2904,8 @@ Status Client::SaveConfig(std::string_view path) {
|
|||
rule_obj["match"]["process_binary"] = entry.second.match.process_binary;
|
||||
rule_obj["match"]["media_role"] = entry.second.match.media_role;
|
||||
rule_obj["target_node"] = entry.second.target_node;
|
||||
rule_obj["direction"] = entry.second.direction == RuleDirection::kCapture ? "capture" : "playback";
|
||||
rule_obj["source_node"] = entry.second.source_node;
|
||||
rules_array.push_back(std::move(rule_obj));
|
||||
}
|
||||
}
|
||||
|
|
@ -2943,10 +3047,16 @@ Status Client::LoadConfig(std::string_view path) {
|
|||
rule.match.media_role = m.value("media_role", "");
|
||||
}
|
||||
rule.target_node = rule_obj.value("target_node", "");
|
||||
if (!rule.target_node.empty() &&
|
||||
(!rule.match.application_name.empty() ||
|
||||
!rule.match.process_binary.empty() ||
|
||||
!rule.match.media_role.empty())) {
|
||||
rule.source_node = rule_obj.value("source_node", "");
|
||||
std::string dir_str = rule_obj.value("direction", "playback");
|
||||
rule.direction = (dir_str == "capture") ? RuleDirection::kCapture
|
||||
: RuleDirection::kPlayback;
|
||||
bool has_match = !rule.match.application_name.empty() ||
|
||||
!rule.match.process_binary.empty() ||
|
||||
!rule.match.media_role.empty();
|
||||
bool has_target = (rule.direction == RuleDirection::kPlayback && !rule.target_node.empty()) ||
|
||||
(rule.direction == RuleDirection::kCapture && !rule.source_node.empty());
|
||||
if (has_match && has_target) {
|
||||
AddRouteRule(rule);
|
||||
}
|
||||
} catch (...) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue