Fix deletion

This commit is contained in:
Joey Yakimowich-Payne 2026-01-27 18:21:47 -07:00
commit 4f240b128b
3 changed files with 187 additions and 34 deletions

View file

@ -227,7 +227,6 @@ void GraphEditorWidget::syncGraph()
refreshNodeMeter(node.id, node);
}
}
const QVector<Potato::LinkInfo> links = m_controller->links();
for (const auto &link : links) {
onLinkAdded(link);
@ -241,6 +240,8 @@ void GraphEditorWidget::refreshGraph()
m_ignoreDelete.clear();
m_connectionToLinkId.clear();
m_linkIdToConnection.clear();
m_nodeLinkCounts.clear();
m_linksById.clear();
m_model->reset();
syncGraph();
@ -268,6 +269,7 @@ void GraphEditorWidget::onNodeChanged(const Potato::NodeInfo &node)
}
refreshNodeMeter(node.id, node);
updateNodeMeterState(node.id, node);
}
void GraphEditorWidget::onNodeRemoved(uint32_t nodeId)
@ -275,6 +277,7 @@ void GraphEditorWidget::onNodeRemoved(uint32_t nodeId)
m_model->removePipeWireNode(nodeId);
m_controller->removeNodeMeter(nodeId);
m_nodeLinkCounts.remove(nodeId);
if (m_nodeMeterRows.contains(nodeId)) {
QWidget *row = m_nodeMeterRows.take(nodeId);
@ -295,6 +298,23 @@ void GraphEditorWidget::onLinkAdded(const Potato::LinkInfo &link)
}
const QString key = connectionKey(connectionId);
const bool alreadyTracked = m_linksById.contains(link.id);
if (!alreadyTracked) {
m_linksById.insert(link.id, link);
if (!isMeterNode(link.outputNodeId) && !isMeterNode(link.inputNodeId)) {
m_nodeLinkCounts[link.outputNodeId] = m_nodeLinkCounts.value(link.outputNodeId, 0) + 1;
m_nodeLinkCounts[link.inputNodeId] = m_nodeLinkCounts.value(link.inputNodeId, 0) + 1;
const Potato::NodeInfo outNode = m_controller->nodeById(link.outputNodeId);
if (outNode.id != 0) {
updateNodeMeterState(outNode.id, outNode);
}
const Potato::NodeInfo inNode = m_controller->nodeById(link.inputNodeId);
if (inNode.id != 0) {
updateNodeMeterState(inNode.id, inNode);
}
}
}
if (m_connectionToLinkId.contains(key)) {
return;
}
@ -310,16 +330,22 @@ void GraphEditorWidget::onLinkAdded(const Potato::LinkInfo &link)
void GraphEditorWidget::onLinkRemoved(uint32_t linkId)
{
if (!m_linkIdToConnection.contains(linkId)) {
m_model->removePipeWireConnection(linkId);
if (m_ignoreLinkRemoved.contains(linkId)) {
m_ignoreLinkRemoved.remove(linkId);
return;
}
if (m_linkIdToConnection.contains(linkId)) {
const QString key = m_linkIdToConnection.value(linkId);
m_ignoreDelete.insert(key);
m_linkIdToConnection.remove(linkId);
m_connectionToLinkId.remove(key);
m_model->removePipeWireConnection(linkId);
} else {
m_model->removePipeWireConnection(linkId);
}
handleLinkRemoved(linkId);
}
void GraphEditorWidget::onConnectionCreated(QtNodes::ConnectionId const connectionId)
@ -362,6 +388,17 @@ void GraphEditorWidget::onConnectionCreated(QtNodes::ConnectionId const connecti
m_connectionToLinkId.insert(key, linkId);
m_linkIdToConnection.insert(linkId, key);
const Potato::LinkInfo link(linkId, outInfo->id, outputPortId, inInfo->id, inputPortId);
if (!m_linksById.contains(linkId)) {
m_linksById.insert(linkId, link);
if (!isMeterNode(link.outputNodeId) && !isMeterNode(link.inputNodeId)) {
m_nodeLinkCounts[link.outputNodeId] = m_nodeLinkCounts.value(link.outputNodeId, 0) + 1;
m_nodeLinkCounts[link.inputNodeId] = m_nodeLinkCounts.value(link.inputNodeId, 0) + 1;
updateNodeMeterState(outInfo->id, *outInfo);
updateNodeMeterState(inInfo->id, *inInfo);
}
}
}
void GraphEditorWidget::onConnectionDeleted(QtNodes::ConnectionId const connectionId)
@ -379,6 +416,8 @@ void GraphEditorWidget::onConnectionDeleted(QtNodes::ConnectionId const connecti
const uint32_t linkId = m_connectionToLinkId.value(key);
m_connectionToLinkId.remove(key);
m_linkIdToConnection.remove(linkId);
m_ignoreLinkRemoved.insert(linkId);
handleLinkRemoved(linkId);
m_controller->destroyLink(linkId);
}
@ -425,20 +464,6 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo
return;
}
if (!node.name.isEmpty()) {
bool captureSink = node.mediaClass == Potato::MediaClass::AudioSink
|| node.mediaClass == Potato::MediaClass::AudioDuplex;
if (!captureSink) {
for (const auto &port : node.outputPorts) {
if (port.name.contains("monitor", Qt::CaseInsensitive)) {
captureSink = true;
break;
}
}
}
m_controller->ensureNodeMeter(nodeId, node.name, captureSink);
}
auto *row = new QWidget(m_meterList);
auto *rowLayout = new QHBoxLayout(row);
rowLayout->setContentsMargins(0, 0, 0, 0);
@ -466,6 +491,103 @@ void GraphEditorWidget::refreshNodeMeter(uint32_t nodeId, const Potato::NodeInfo
m_nodeMeterRows.insert(nodeId, row);
m_nodeMeterLabels.insert(nodeId, label);
updateNodeMeterLabel(label);
updateNodeMeterState(nodeId, node);
}
void GraphEditorWidget::updateNodeMeterState(uint32_t nodeId, const Potato::NodeInfo &node)
{
const int linkCount = activeLinkCount(nodeId);
if (linkCount <= 0) {
m_controller->removeNodeMeter(nodeId);
return;
}
if (node.name.isEmpty()) {
return;
}
bool captureSink = node.mediaClass == Potato::MediaClass::AudioSink
|| node.mediaClass == Potato::MediaClass::AudioDuplex;
if (!captureSink) {
for (const auto &port : node.outputPorts) {
if (port.name.contains("monitor", Qt::CaseInsensitive)) {
captureSink = true;
break;
}
}
}
m_controller->ensureNodeMeter(nodeId, node.name, captureSink);
}
void GraphEditorWidget::handleLinkRemoved(uint32_t linkId)
{
Potato::LinkInfo link;
bool haveLink = false;
if (m_linksById.contains(linkId)) {
link = m_linksById.take(linkId);
haveLink = true;
} else {
const QVector<Potato::LinkInfo> links = m_controller->links();
for (const auto &candidate : links) {
if (candidate.id == linkId) {
link = candidate;
haveLink = true;
break;
}
}
}
if (!haveLink) {
return;
}
if (isMeterNode(link.outputNodeId) || isMeterNode(link.inputNodeId)) {
return;
}
const auto decrement = [this](uint32_t nodeId) {
if (nodeId == 0) {
return;
}
const int current = m_nodeLinkCounts.value(nodeId, 0);
if (current <= 1) {
m_nodeLinkCounts.remove(nodeId);
} else {
m_nodeLinkCounts[nodeId] = current - 1;
}
const Potato::NodeInfo node = m_controller->nodeById(nodeId);
if (node.id != 0) {
updateNodeMeterState(node.id, node);
}
};
decrement(link.outputNodeId);
decrement(link.inputNodeId);
}
bool GraphEditorWidget::isMeterNode(uint32_t nodeId) const
{
const Potato::NodeInfo node = m_controller->nodeById(nodeId);
if (node.name.startsWith("Potato-Meter") || node.name.startsWith("Potato-Node-Meter")) {
return true;
}
return false;
}
int GraphEditorWidget::activeLinkCount(uint32_t nodeId) const
{
int count = 0;
for (auto it = m_linksById.cbegin(); it != m_linksById.cend(); ++it) {
const Potato::LinkInfo &link = it.value();
if (isMeterNode(link.outputNodeId) || isMeterNode(link.inputNodeId)) {
continue;
}
if (link.outputNodeId == nodeId || link.inputNodeId == nodeId) {
++count;
}
}
return count;
}
void GraphEditorWidget::updateNodeMeterLabel(QLabel *label)

View file

@ -41,6 +41,10 @@ private:
void refreshGraph();
void updateLayoutState();
void updateNodeMeterLabel(QLabel *label);
void updateNodeMeterState(uint32_t nodeId, const Potato::NodeInfo &node);
void handleLinkRemoved(uint32_t linkId);
bool isMeterNode(uint32_t nodeId) const;
int activeLinkCount(uint32_t nodeId) const;
QString connectionKey(const QtNodes::ConnectionId &connectionId) const;
bool eventFilter(QObject *object, QEvent *event) override;
@ -52,6 +56,7 @@ private:
QSet<QString> m_ignoreCreate;
QSet<QString> m_ignoreDelete;
QSet<uint32_t> m_ignoreLinkRemoved;
QMap<QString, uint32_t> m_connectionToLinkId;
QMap<uint32_t, QString> m_linkIdToConnection;
AudioLevelMeter *m_meter = nullptr;
@ -62,4 +67,6 @@ private:
QMap<uint32_t, AudioLevelMeter*> m_nodeMeters;
QMap<uint32_t, QWidget*> m_nodeMeterRows;
QMap<uint32_t, QLabel*> m_nodeMeterLabels;
QMap<uint32_t, int> m_nodeLinkCounts;
QMap<uint32_t, Potato::LinkInfo> m_linksById;
};

View file

@ -472,14 +472,19 @@ void PipeWireController::ensureNodeMeter(uint32_t nodeId, const QString &targetN
void PipeWireController::removeNodeMeter(uint32_t nodeId)
{
NodeMeter *meter = nullptr;
{
QMutexLocker lock(&m_meterMutex);
if (!m_nodeMeters.contains(nodeId)) {
return;
}
meter = m_nodeMeters.take(nodeId);
}
NodeMeter *meter = m_nodeMeters.take(nodeId);
if (meter && meter->stream) {
lock();
pw_stream_destroy(meter->stream);
unlock();
}
delete meter;
}
@ -495,6 +500,19 @@ uint32_t PipeWireController::createLink(uint32_t outputNodeId, uint32_t outputPo
return 0;
}
{
QMutexLocker lock(&m_nodesMutex);
for (auto it = m_links.cbegin(); it != m_links.cend(); ++it) {
const LinkInfo &link = it.value();
if (link.outputNodeId == outputNodeId &&
link.outputPortId == outputPortId &&
link.inputNodeId == inputNodeId &&
link.inputPortId == inputPortId) {
return link.id;
}
}
}
lock();
QByteArray outNode = QByteArray::number(outputNodeId);
@ -591,15 +609,21 @@ bool PipeWireController::destroyLink(uint32_t linkId)
unlock();
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 2000) {
{
QMutexLocker lock(&m_nodesMutex);
m_links.remove(linkId);
}
emit linkRemoved(linkId);
if (!m_links.contains(linkId)) {
qInfo() << "Link destroyed:" << linkId;
return true;
}
}
QThread::msleep(10);
}
qWarning() << "Link destroy requested but ID still present" << linkId;
return false;
}
QString PipeWireController::dumpGraph() const