Initial commit: pwweb - PipeWire WebUI

C++ backend with SSE streaming (reuses qpwgraph PipeWire callbacks).
Svelte frontend with custom SVG canvas for port-level connections.

Features:
- Live PipeWire graph via SSE
- Drag output->input port to connect
- Double-click or select+Delete to disconnect
- Node positions saved to localStorage
- Pan (drag bg) and zoom (scroll)
- Port type coloring (audio=green, midi=red, video=blue)
This commit is contained in:
joren
2026-03-29 22:40:07 +02:00
commit f8c57fbdd3
34 changed files with 24479 additions and 0 deletions

131
src/graph_engine.h Normal file
View File

@@ -0,0 +1,131 @@
#pragma once
#include "graph_types.h"
#include <functional>
#include <mutex>
#include <vector>
#include <unordered_map>
#include <thread>
#include <atomic>
#include <pipewire/utils.h> // pw_thread_loop, etc.
#include <spa/utils/list.h> // spa_list
#include <spa/utils/hook.h> // spa_hook
struct pw_thread_loop;
struct pw_context;
struct pw_core;
struct pw_registry;
namespace pwgraph {
class GraphEngine {
public:
using ChangeCallback = std::function<void()>;
GraphEngine();
~GraphEngine();
bool open();
void close();
// Set callback invoked when graph changes
void setOnChange(ChangeCallback cb);
// Thread-safe snapshot of the current graph state
struct Snapshot {
std::vector<Node> nodes;
std::vector<Port> ports;
std::vector<Link> links;
};
Snapshot snapshot() const;
// Connect/disconnect two ports (thread-safe, blocks until done)
bool connectPorts(uint32_t output_port_id, uint32_t input_port_id);
bool disconnectPorts(uint32_t output_port_id, uint32_t input_port_id);
// PipeWire internal data (exposed for C callbacks)
struct PwData {
pw_thread_loop *loop;
pw_context *context;
pw_core *core;
spa_hook core_listener;
pw_registry *registry;
spa_hook registry_listener;
int pending_seq;
spa_list pending; // doubly-linked list head for pending syncs
int last_seq;
int last_res;
bool error;
};
PwData& pwData() { return m_pw; }
// Object management (called from C callbacks)
struct Object {
enum Type { ObjNode, ObjPort, ObjLink };
uint32_t id;
Type type;
void *proxy;
void *info;
void (*destroy_info)(void*);
spa_hook proxy_listener;
spa_hook object_listener;
int pending_seq;
spa_list pending_link;
Object(uint32_t id, Type type);
virtual ~Object();
};
struct NodeObj : Object {
NodeObj(uint32_t id) : Object(id, ObjNode) {}
Node node;
};
struct PortObj : Object {
PortObj(uint32_t id) : Object(id, ObjPort) {}
Port port;
};
struct LinkObj : Object {
LinkObj(uint32_t id) : Object(id, ObjLink) {}
Link link;
};
void addObject(uint32_t id, Object *obj);
Object *findObject(uint32_t id) const;
void removeObject(uint32_t id);
void clearObjects();
NodeObj *findNode(uint32_t node_id) const;
PortObj *findPort(uint32_t port_id) const;
LinkObj *findLink(uint32_t link_id) const;
void notifyChanged();
private:
PwData m_pw;
mutable std::mutex m_mutex; // protects m_objects and graph data
std::unordered_map<uint32_t, Object*> m_objects_by_id;
std::vector<Object*> m_objects;
ChangeCallback m_on_change;
std::atomic<bool> m_running;
// Port type hashes
uint32_t m_audio_type;
uint32_t m_midi_type;
uint32_t m_midi2_type;
uint32_t m_video_type;
uint32_t m_other_type;
public:
uint32_t audioPortType() const { return m_audio_type; }
uint32_t midiPortType() const { return m_midi_type; }
uint32_t videoPortType() const { return m_video_type; }
uint32_t otherPortType() const { return m_other_type; }
};
} // namespace pwgraph