This commit is contained in:
Joey Yakimowich-Payne 2026-01-31 11:21:28 -07:00
commit 9d57ba5d25
9 changed files with 235 additions and 19 deletions

View file

@ -1,8 +1,8 @@
# Warppipe API
# Warp Pipe API
## Overview
Warppipe is a C++17 library wrapping libpipewire for virtual audio node management, link routing, and per-app routing policy. The entire public API is in `<warppipe/warppipe.hpp>`.
Warp Pipe is a C++20 library wrapping libpipewire for virtual audio node management, link routing, and per-app routing policy. The entire public API is in `<warppipe/warppipe.hpp>`.
## Quick Start
@ -84,6 +84,19 @@ struct ConnectionOptions {
};
```
Notes:
- `ThreadingMode::kCallerThread` returns `StatusCode::kNotImplemented`.
- When `autoconnect` is false, operations return `kUnavailable` after a disconnect instead of reconnecting.
- `remote_name` connects to a non-default PipeWire instance.
## Lifecycle
```cpp
Status Shutdown();
```
`Shutdown()` stops the PipeWire thread loop and releases resources. It is called automatically by the destructor, but you can invoke it explicitly to tear down early.
### Policy-Only Mode
When `policy_only = true`, the policy engine observes the registry and matches rules, but does not create links. This avoids conflicts with WirePlumber or other session managers.
@ -117,8 +130,73 @@ Result<VirtualSource> CreateVirtualSource(std::string_view name, const VirtualNo
Status RemoveNode(NodeId node);
```
```cpp
struct VirtualNodeOptions {
AudioFormat format; // rate/channels
VirtualBehavior behavior; // kNull (default) or kLoopback
std::optional<std::string> target_node; // Required for kLoopback
std::optional<std::string> media_class_override; // Override PW_KEY_MEDIA_CLASS
std::string display_name; // PW_KEY_MEDIA_NAME / PW_KEY_NODE_DESCRIPTION
std::string group; // PW_KEY_NODE_GROUP
};
```
Virtual nodes are PipeWire streams with `PW_KEY_NODE_VIRTUAL = true`. They live as long as the `Client` (or until explicitly removed). Null behavior discards audio; loopback behavior forwards to a target node.
Behavior and validation:
- `format.rate` and `format.channels` must be non-zero (`kInvalidArgument` otherwise).
- `behavior = kLoopback` requires `target_node` and the target must exist (`kInvalidArgument`/`kNotFound`).
- `media_class_override` overrides the PipeWire media class (cannot be an empty string).
- `display_name` and `group` map to `PW_KEY_MEDIA_NAME` / `PW_KEY_NODE_DESCRIPTION` and `PW_KEY_NODE_GROUP`.
Example loopback node:
```cpp
warppipe::VirtualNodeOptions opts;
opts.behavior = warppipe::VirtualBehavior::kLoopback;
opts.target_node = "alsa_output.pci-0000_00_1f.3.analog-stereo";
auto sink = client->CreateVirtualSink("warppipe-loopback", opts);
```
## Volume Control
```cpp
Status SetNodeVolume(NodeId node, float volume, bool mute);
Result<VolumeState> GetNodeVolume(NodeId node) const;
struct VolumeState {
float volume; // 0.0 - 1.5
bool mute;
};
```
`SetNodeVolume` clamps `volume` to `[0.0, 1.5]` and stores the last known volume/mute state. `GetNodeVolume` returns the cached state (defaults to 1.0/false when unknown). Volume and mute are persisted in config files when saved.
## Audio Metering
```cpp
Status EnsureNodeMeter(NodeId node);
Status DisableNodeMeter(NodeId node);
Result<MeterState> NodeMeterPeak(NodeId node) const;
Result<MeterState> MeterPeak() const;
struct MeterState {
float peak_left;
float peak_right;
};
```
Call `EnsureNodeMeter` before querying `NodeMeterPeak`; otherwise you may receive `kNotFound` for unmetered nodes. `MeterPeak` returns master peak levels gathered by a monitor stream.
## Change Notifications
```cpp
using ChangeCallback = std::function<void()>;
void SetChangeCallback(ChangeCallback callback);
```
The callback fires when the registry cache changes (nodes/ports/links) and when volume/mute updates are observed. It is invoked on the PipeWire thread loop; do not call `Client` methods inside the callback to avoid deadlocks.
## Link Management
```cpp
@ -131,6 +209,8 @@ Status RemoveLink(LinkId link);
Links are created via `link-factory` (`pw_core_create_object`). Use `linger = true` so links persist after the creating client disconnects. Use `passive = true` for monitoring/side-chain links that don't affect the session manager's routing decisions.
`CreateLinkByName` matches by node and port name and returns `kNotFound` if no matching ports are found.
## Route Rules (Policy Engine)
```cpp
@ -148,6 +228,8 @@ Match criteria (all non-empty fields must match):
When a matching node appears, the engine waits for ports to register (via PipeWire sync), then creates links pairwise by port name order.
`AddRouteRule` returns `kInvalidArgument` if no match criteria are provided or if the target node name is empty.
Adding a rule also scans existing nodes for matches.
## Metadata
@ -173,7 +255,13 @@ JSON format. See `docs/config-schema.md` for the schema.
When `config_path` is set in `ConnectionOptions`:
- Config is loaded automatically after connection.
- Config is saved automatically after mutations (add/remove rules, create/remove virtual nodes).
- Config is saved automatically after mutations (add/remove rules, create/remove virtual nodes, `CreateLink`/`RemoveLink`, `SetNodeVolume`).
Saved configs include virtual nodes, routing rules, saved links, and per-node volume/mute state.
## Testing Hooks
When compiled with `-DWARPPIPE_TESTING`, additional helpers are available for tests (node/port/link injection, forcing disconnects, manual policy triggers, and meter/volume overrides). These APIs are not intended for production use.
## Performance
@ -206,3 +294,5 @@ warppipe_cli save-config <path>
warppipe_cli load-config <path>
warppipe_cli defaults
```
`link` uses node/port names (not IDs). `defaults` prints current and configured default sink/source names.

View file

@ -1,6 +1,6 @@
# Warppipe Configuration Schema
# Warp Pipe Configuration Schema
Warppipe uses JSON for configuration persistence. The config file stores virtual nodes and routing rules using stable identifiers (names, not serial IDs).
Warp Pipe uses JSON for configuration persistence. The config file stores virtual nodes, routing rules, saved links, and per-node volume/mute using stable identifiers (names, not serial IDs).
## Schema Version 1
@ -14,7 +14,9 @@ Warppipe uses JSON for configuration persistence. The config file stores virtual
"rate": 48000,
"channels": 2,
"loopback": false,
"target_node": ""
"target_node": "",
"volume": 1.0,
"mute": false
},
{
"name": "warppipe-mic-source",
@ -50,6 +52,14 @@ Warppipe uses JSON for configuration persistence. The config file stores virtual
},
"target_node": "alsa_output.usb-headset"
}
],
"links": [
{
"out_node": "Firefox",
"out_port": "output_FL",
"in_node": "warppipe-gaming-sink",
"in_port": "input_FL"
}
]
}
```
@ -64,6 +74,8 @@ Warppipe uses JSON for configuration persistence. The config file stores virtual
- `channels` (integer, default 2): Channel count
- `loopback` (boolean, default false): Whether node forwards to a target
- `target_node` (string, optional): Required when loopback is true
- `volume` (number, default 1.0): Stored node volume (0.0 - 1.5)
- `mute` (boolean, default false): Stored node mute state
### route_rules
@ -72,15 +84,27 @@ Rules match ephemeral audio sources to target sinks by stable application metada
- `match.application_name` (string): Match PW_KEY_APP_NAME
- `match.process_binary` (string): Match PW_KEY_APP_PROCESS_BINARY
- `match.media_role` (string): Match PW_KEY_MEDIA_ROLE
- `id` (integer, optional): Auto-saved rule id (ignored on load)
- `target_node` (string, required): Destination node name
All non-empty match fields must match (AND logic). At least one match field must be non-empty.
### links
Saved links are recreated by matching node and port names when both endpoints appear.
- `out_node` (string, required): Output node name
- `out_port` (string, required): Output port name
- `in_node` (string, required): Input node name
- `in_port` (string, required): Input port name
## Persistence Behavior
- **Auto-save**: When `ConnectionOptions::config_path` is set, config is saved after:
- Virtual node created/removed
- Routing rule added/removed
- `SetNodeVolume` updates
- `CreateLink`/`RemoveLink` updates
- **Load on startup**: When `config_path` is set and the file exists, it is loaded during `Client::Create()` after connection is established.

33
docs/gui-usage.md Normal file
View file

@ -0,0 +1,33 @@
# Warp Pipe GUI Usage
## Build
```
cmake -S . -B build
cmake --build build
```
Disable the GUI target with `-DWARPPIPE_BUILD_GUI=OFF`.
## Run
```
./build/warppipe-gui
```
## Configuration
The GUI uses `ConnectionOptions::config_path` pointed at Qt's `AppConfigLocation` with `/config.json` appended. On Linux this is typically under `~/.config/Warppipe/config.json`.
## Command-line options
- `--screenshot <path>`: capture a PNG screenshot and exit
- `--screenshot-delay <ms>`: delay before capture (default 800)
- `--debug-screenshot-dir <dir>`: save a timestamped screenshot on every graph update
- `--offscreen`: run with `QT_QPA_PLATFORM=offscreen`
- `--help`, `--version`
## Shortcuts
- `F12`: capture a screenshot to `~/Pictures/warppipe` with a timestamp
- `Ctrl+Q`: quit

View file

@ -3,7 +3,7 @@
## Build system
- CMake 3.20+
- C++17
- C++20
- pkg-config
- libpipewire-0.3 development files